PropertyEditorResolver.java
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2008-2017 the original author or authors.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package griffon.core.editors;
019 
020 import org.slf4j.Logger;
021 import org.slf4j.LoggerFactory;
022 
023 import javax.annotation.Nonnull;
024 import javax.annotation.Nullable;
025 import javax.annotation.concurrent.GuardedBy;
026 import java.beans.PropertyEditor;
027 import java.beans.PropertyEditorManager;
028 import java.beans.PropertyEditorSupport;
029 import java.lang.ref.Reference;
030 import java.lang.ref.WeakReference;
031 import java.util.Map;
032 import java.util.WeakHashMap;
033 import java.util.concurrent.ConcurrentHashMap;
034 
035 import static java.util.Objects.requireNonNull;
036 
037 /**
038  * The PropertyEditorResolver can be used to locate a property editor for
039  * any given type name. This property editor must support the
040  * java.beans.PropertyEditor interface for editing a given object.
041  <p>
042  *
043  @author Andres Almiray
044  @since 2.0.0
045  */
046 public final class PropertyEditorResolver {
047     private static final Logger LOG = LoggerFactory.getLogger(PropertyEditorResolver.class);
048     private static final Object LOCK = new Object[0];
049     @GuardedBy("LOCK")
050     private static final WeakCache<String, Class<? extends PropertyEditor>> propertyEditorRegistry = new WeakCache<>();
051     @GuardedBy("LOCK")
052     private static final Map<String, PropertyEditorChain> propertyEditorChainRegistry = new ConcurrentHashMap<>();
053     private static final String ERROR_TARGET_TYPE_NULL = "Argument 'targetType' must not be null";
054     public static final PropertyEditor NOOP_PROPERTY_EDITOR = new NoopPropertyEditor();
055 
056     private PropertyEditorResolver() {
057 
058     }
059 
060     /**
061      * Removes all currently registered value editors.
062      *
063      @since 2.4.0
064      */
065     public static void clear() {
066         synchronized (LOCK) {
067             propertyEditorRegistry.clear();
068             propertyEditorChainRegistry.clear();
069         }
070     }
071 
072     /**
073      * Locate a value editor for a given target type.
074      <p>
075      * If the input {@code type} is an Enum then an instance of {@code EnumPropertyEditor}
076      * is returned with the {@code type} set as {@code enumType}.
077      *
078      @param type The Class object for the type to be edited
079      *
080      @return An editor object for the given target class.
081      * The result is null if no suitable editor can be found.
082      *
083      @see griffon.core.editors.EnumPropertyEditor
084      */
085     @Nonnull
086     @SuppressWarnings("unchecked")
087     public static PropertyEditor findEditor(@Nonnull Class<?> type) {
088         requireNonNull(type, ERROR_TARGET_TYPE_NULL);
089         LOG.trace("Searching PropertyEditor for {}", type.getName());
090 
091         PropertyEditor editor;
092         if (Enum.class.isAssignableFrom(type)) {
093             editor = new EnumPropertyEditor();
094             ((EnumPropertyEditoreditor).setEnumType((Class<? extends Enum<?>>type);
095         else {
096             editor = doFindEditor(type);
097         }
098 
099         if (editor == null) {
100             // fallback
101             editor = PropertyEditorManager.findEditor(type);
102         }
103 
104         if (editor == null) {
105             editor = new NoopPropertyEditor();
106         }
107 
108         LOG.trace("PropertyEditor for {} is {}", type.getName(), editor.getClass().getName());
109         return editor;
110     }
111 
112     /**
113      * Unregisters an editor class to edit values of the given target class.
114      *
115      @param targetType the class object of the type to be edited
116      *
117      @since 2.4.0
118      */
119     public static void unregisterEditor(@Nonnull Class<?> targetType) {
120         requireNonNull(targetType, ERROR_TARGET_TYPE_NULL);
121         synchronized (LOCK) {
122             String targetTypeName = targetType.getName();
123             propertyEditorChainRegistry.remove(targetTypeName);
124             propertyEditorRegistry.remove(targetTypeName);
125         }
126     }
127 
128     /**
129      * Registers an editor class to edit values of the given target class.
130      * If the editor class is {@code null},
131      * then any existing definition will be removed.
132      * Thus this method can be used to cancel the registration.
133      * The registration is canceled automatically
134      * if either the target or editor class is unloaded.
135      <p>
136      *
137      @param targetType  the class object of the type to be edited
138      @param editorClass the class object of the editor class
139      *
140      @since 2.4.0
141      */
142     @SuppressWarnings("unchecked")
143     public static void registerEditor(@Nonnull Class<?> targetType, @Nullable Class<? extends PropertyEditor> editorClass) {
144         requireNonNull(targetType, ERROR_TARGET_TYPE_NULL);
145         synchronized (LOCK) {
146             String targetTypeName = targetType.getName();
147             if (editorClass == null) {
148                 propertyEditorChainRegistry.remove(targetTypeName);
149                 propertyEditorRegistry.remove(targetTypeName);
150                 return;
151             }
152 
153             // is targetType handled by a chain?
154             PropertyEditorChain chain = propertyEditorChainRegistry.get(targetTypeName);
155             if (chain != null) {
156                 PropertyEditorChain propertyEditorChain = chain.copyOf(editorClass);
157                 if (propertyEditorChain.getSize() 1) {
158                     propertyEditorChainRegistry.put(targetTypeName, propertyEditorChain);
159                 else {
160                     // standard registration
161                     propertyEditorChainRegistry.remove(targetTypeName);
162                     propertyEditorRegistry.put(targetTypeName, editorClass);
163                 }
164             else {
165                 // is targetType handled by an editor ?
166                 Class<? extends PropertyEditor> propertyEditorType = propertyEditorRegistry.get(targetTypeName);
167                 if (propertyEditorType != null) {
168                     propertyEditorRegistry.remove(targetTypeName);
169                     Class<? extends PropertyEditor>[] propertyEditorClasses = new Class[2];
170                     propertyEditorClasses[0= propertyEditorType;
171                     propertyEditorClasses[1= editorClass;
172                     PropertyEditorChain propertyEditorChain = new PropertyEditorChain(targetType, propertyEditorClasses);
173                     if (propertyEditorChain.getSize() 1) {
174                         propertyEditorChainRegistry.put(targetTypeName, propertyEditorChain);
175                     else {
176                         // standard registration
177                         propertyEditorChainRegistry.remove(targetTypeName);
178                         propertyEditorRegistry.put(targetTypeName, editorClass);
179                     }
180                 else {
181                     // standard registration
182                     propertyEditorChainRegistry.remove(targetTypeName);
183                     propertyEditorRegistry.put(targetTypeName, editorClass);
184                 }
185             }
186         }
187     }
188 
189     @Nullable
190     private static PropertyEditor doFindEditor(@Nonnull Class<?> targetType) {
191         synchronized (LOCK) {
192             String targetTypeName = targetType.getName();
193             if (propertyEditorChainRegistry.containsKey(targetTypeName)) {
194                 PropertyEditorChain chain = propertyEditorChainRegistry.get(targetTypeName);
195                 return chain.copyOf();
196             else {
197                 Class<?> propertyEditorType = propertyEditorRegistry.get(targetTypeName);
198                 if (propertyEditorType != null) {
199                     try {
200                         return (PropertyEditorpropertyEditorType.newInstance();
201                     catch (InstantiationException | IllegalAccessException e) {
202                         throw new IllegalStateException("Can't instantiate " + propertyEditorType, e);
203                     }
204                 }
205             }
206         }
207         return null;
208     }
209 
210     public static final class NoopPropertyEditor extends PropertyEditorSupport {
211 
212     }
213 
214     private static final class WeakCache<K, V> {
215         private final Map<K, Reference<V>> map = new WeakHashMap<>();
216 
217         private V get(K key) {
218             Reference<V> reference = this.map.get(key);
219             if (reference == null) {
220                 return null;
221             else {
222                 V value = reference.get();
223                 if (value == null) {
224                     this.map.remove(key);
225                 }
226 
227                 return value;
228             }
229         }
230 
231         private void put(K key, V value) {
232             if (value != null) {
233                 this.map.put(key, new WeakReference<>(value));
234             else {
235                 this.map.remove(key);
236             }
237         }
238 
239         private void remove(K key) {
240             this.map.remove(key);
241         }
242 
243         private void clear() {
244             this.map.clear();
245         }
246     }
247 }