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