PropertyEditorResolver.java
001 /*
002  * Copyright 2008-2015 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      @since 2.4.0
060      */
061     public static void clear() {
062         synchronized (LOCK) {
063             propertyEditorRegistry.clear();
064             propertyEditorChainRegistry.clear();
065         }
066     }
067 
068     /**
069      * Locate a value editor for a given target type.
070      <p>
071      * If the input {@code type} is an Enum then an instance of {@code EnumPropertyEditor}
072      * is returned with the {@code type} set as {@code enumType}.
073      *
074      @param type The Class object for the type to be edited
075      @return An editor object for the given target class.
076      * The result is null if no suitable editor can be found.
077      @see griffon.core.editors.EnumPropertyEditor
078      */
079     @Nonnull
080     @SuppressWarnings("unchecked")
081     public static PropertyEditor findEditor(@Nonnull Class<?> type) {
082         requireNonNull(type, "Argument 'type' must not be  null");
083         LOG.trace("Searching PropertyEditor for {}", type.getName());
084 
085         PropertyEditor editor;
086         if (Enum.class.isAssignableFrom(type)) {
087             editor = new EnumPropertyEditor();
088             ((EnumPropertyEditoreditor).setEnumType((Class<? extends Enum<?>>type);
089         else {
090             editor = doFindEditor(type);
091         }
092 
093         if (editor == null) {
094             // fallback
095             editor = PropertyEditorManager.findEditor(type);
096         }
097 
098         if (editor == null) {
099             editor = new NoopPropertyEditor();
100         }
101 
102         LOG.trace("PropertyEditor for {} is {}", type.getName(), editor.getClass().getName());
103         return editor;
104     }
105 
106     /**
107      * Unregisters an editor class to edit values of the given target class.
108      *
109      @param targetType the class object of the type to be edited
110      @since 2.4.0
111      */
112     public static void unregisterEditor(@Nonnull Class<?> targetType) {
113         requireNonNull(targetType, ERROR_TARGET_TYPE_NULL);
114         synchronized (LOCK) {
115             String targetTypeName = targetType.getName();
116             propertyEditorChainRegistry.remove(targetTypeName);
117             propertyEditorRegistry.remove(targetTypeName);
118         }
119     }
120 
121     /**
122      * Registers an editor class to edit values of the given target class.
123      * If the editor class is {@code null},
124      * then any existing definition will be removed.
125      * Thus this method can be used to cancel the registration.
126      * The registration is canceled automatically
127      * if either the target or editor class is unloaded.
128      <p>
129      *
130      @param targetType  the class object of the type to be edited
131      @param editorClass the class object of the editor class
132      @since 2.4.0
133      */
134     @SuppressWarnings("unchecked")
135     public static void registerEditor(@Nonnull Class<?> targetType, @Nullable Class<? extends PropertyEditor> editorClass) {
136         requireNonNull(targetType, ERROR_TARGET_TYPE_NULL);
137         synchronized (LOCK) {
138             String targetTypeName = targetType.getName();
139             if (editorClass == null) {
140                 propertyEditorChainRegistry.remove(targetTypeName);
141                 propertyEditorRegistry.remove(targetTypeName);
142                 return;
143             }
144 
145             // is targetType handled by a chain?
146             PropertyEditorChain chain = propertyEditorChainRegistry.get(targetTypeName);
147             if (chain != null) {
148                 propertyEditorChainRegistry.put(targetTypeName, chain.copyOf(editorClass));
149             else {
150                 // is targetType handled by an editor ?
151                 Class<? extends PropertyEditor> propertyEditorType = propertyEditorRegistry.get(targetTypeName);
152                 if (propertyEditorType != null) {
153                     propertyEditorRegistry.remove(targetTypeName);
154                     Class<? extends PropertyEditor>[] propertyEditorClasses = new Class[2];
155                     propertyEditorClasses[0= propertyEditorType;
156                     propertyEditorClasses[1= editorClass;
157                     propertyEditorChainRegistry.put(targetTypeName, new PropertyEditorChain(targetType, propertyEditorClasses));
158                 else {
159                     // standard registration
160                     propertyEditorRegistry.put(targetTypeName, editorClass);
161                 }
162             }
163         }
164     }
165 
166     private static PropertyEditor doFindEditor(Class<?> targetType) {
167         synchronized (LOCK) {
168             String targetTypeName = targetType.getName();
169             if (propertyEditorChainRegistry.containsKey(targetTypeName)) {
170                 PropertyEditorChain chain = propertyEditorChainRegistry.get(targetTypeName);
171                 return chain.copyOf();
172             else {
173                 Class<?> propertyEditorType = propertyEditorRegistry.get(targetTypeName);
174                 if (propertyEditorType != null) {
175                     try {
176                         return (PropertyEditorpropertyEditorType.newInstance();
177                     catch (InstantiationException | IllegalAccessException e) {
178                         throw new IllegalStateException("Can't instantiate " + propertyEditorType, e);
179                     }
180                 }
181             }
182         }
183         return null;
184     }
185 
186     private static final class NoopPropertyEditor extends PropertyEditorSupport {
187 
188     }
189 
190     private static final class WeakCache<K, V> {
191         private final Map<K, Reference<V>> map = new WeakHashMap<>();
192 
193         private V get(K key) {
194             Reference reference = this.map.get(key);
195             if (reference == null) {
196                 return null;
197             else {
198                 V value = (Vreference.get();
199                 if (value == null) {
200                     this.map.remove(key);
201                 }
202 
203                 return value;
204             }
205         }
206 
207         private void put(K key, V value) {
208             if (value != null) {
209                 this.map.put(key, new WeakReference<>(value));
210             else {
211                 this.map.remove(key);
212             }
213         }
214 
215         private void remove(K key) {
216             this.map.remove(key);
217         }
218 
219         private boolean contains(K key) {
220             Reference reference = this.map.get(key);
221             if (reference == null) {
222                 return false;
223             else {
224                 V value = (Vreference.get();
225                 if (value == null) {
226                     this.map.remove(key);
227                     return false;
228                 }
229 
230                 return true;
231             }
232         }
233 
234         private void clear() {
235             this.map.clear();
236         }
237     }
238 }