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