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 ((EnumPropertyEditor) editor).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 (PropertyEditor) propertyEditorType.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 = (V) reference.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 = (V) reference.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 }
|