/* * Copyright 2008-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package griffon.core.editors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import java.beans.PropertyEditor; import java.beans.PropertyEditorManager; import java.beans.PropertyEditorSupport; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.LinkedHashMap; import java.util.Map; import java.util.WeakHashMap; import static java.util.Objects.requireNonNull; /** * The PropertyEditorResolver can be used to locate a property editor for * any given type name. This property editor must support the * java.beans.PropertyEditor interface for editing a given object. * <p> * * @author Andres Almiray * @since 2.0.0 */ public final class PropertyEditorResolver { private static final Logger LOG = LoggerFactory.getLogger(PropertyEditorResolver.class); private static final Object LOCK = new Object[0]; @GuardedBy("LOCK") private static final WeakCache<String, Class<? extends PropertyEditor>> propertyEditorRegistry = new WeakCache(); @GuardedBy("LOCK") private static final Map<String, PropertyEditorChain> propertyEditorChainRegistry = new LinkedHashMap<>(); private static final String ERROR_TARGET_TYPE_NULL = "Argument 'targetType' must not be null"; private PropertyEditorResolver() { } /** * Removes all currently registered value editors. * * @since 2.4.0 */ public static void clear() { synchronized (LOCK) { propertyEditorRegistry.clear(); propertyEditorChainRegistry.clear(); } } /** * Locate a value editor for a given target type. * <p> * If the input {@code type} is an Enum then an instance of {@code EnumPropertyEditor} * is returned with the {@code type} set as {@code enumType}. * * @param type The Class object for the type to be edited * @return An editor object for the given target class. * The result is null if no suitable editor can be found. * @see griffon.core.editors.EnumPropertyEditor */ @Nonnull @SuppressWarnings("unchecked") public static PropertyEditor findEditor(@Nonnull Class<?> type) { requireNonNull(type, "Argument 'type' must not be null"); LOG.trace("Searching PropertyEditor for {}", type.getName()); PropertyEditor editor; if (Enum.class.isAssignableFrom(type)) { editor = new EnumPropertyEditor(); ((EnumPropertyEditor) editor).setEnumType((Class<? extends Enum<?>>) type); } else { editor = doFindEditor(type); } if (editor == null) { // fallback editor = PropertyEditorManager.findEditor(type); } if (editor == null) { editor = new NoopPropertyEditor(); } LOG.trace("PropertyEditor for {} is {}", type.getName(), editor.getClass().getName()); return editor; } /** * Unregisters an editor class to edit values of the given target class. * * @param targetType the class object of the type to be edited * @since 2.4.0 */ public static void unregisterEditor(@Nonnull Class<?> targetType) { requireNonNull(targetType, ERROR_TARGET_TYPE_NULL); synchronized (LOCK) { String targetTypeName = targetType.getName(); propertyEditorChainRegistry.remove(targetTypeName); propertyEditorRegistry.remove(targetTypeName); } } /** * Registers an editor class to edit values of the given target class. * If the editor class is {@code null}, * then any existing definition will be removed. * Thus this method can be used to cancel the registration. * The registration is canceled automatically * if either the target or editor class is unloaded. * <p> * * @param targetType the class object of the type to be edited * @param editorClass the class object of the editor class * @since 2.4.0 */ @SuppressWarnings("unchecked") public static void registerEditor(@Nonnull Class<?> targetType, @Nullable Class<? extends PropertyEditor> editorClass) { requireNonNull(targetType, ERROR_TARGET_TYPE_NULL); synchronized (LOCK) { String targetTypeName = targetType.getName(); if (editorClass == null) { propertyEditorChainRegistry.remove(targetTypeName); propertyEditorRegistry.remove(targetTypeName); return; } // is targetType handled by a chain? PropertyEditorChain chain = propertyEditorChainRegistry.get(targetTypeName); if (chain != null) { PropertyEditorChain propertyEditorChain = chain.copyOf(editorClass); if (propertyEditorChain.getSize() > 1) { propertyEditorChainRegistry.put(targetTypeName, propertyEditorChain); } else { // standard registration propertyEditorChainRegistry.remove(targetTypeName); propertyEditorRegistry.put(targetTypeName, editorClass); } } else { // is targetType handled by an editor ? Class<? extends PropertyEditor> propertyEditorType = propertyEditorRegistry.get(targetTypeName); if (propertyEditorType != null) { propertyEditorRegistry.remove(targetTypeName); Class<? extends PropertyEditor>[] propertyEditorClasses = new Class[2]; propertyEditorClasses[0] = propertyEditorType; propertyEditorClasses[1] = editorClass; PropertyEditorChain propertyEditorChain = new PropertyEditorChain(targetType, propertyEditorClasses); if (propertyEditorChain.getSize() > 1) { propertyEditorChainRegistry.put(targetTypeName, propertyEditorChain); } else { // standard registration propertyEditorChainRegistry.remove(targetTypeName); propertyEditorRegistry.put(targetTypeName, editorClass); } } else { // standard registration propertyEditorChainRegistry.remove(targetTypeName); propertyEditorRegistry.put(targetTypeName, editorClass); } } } } private static PropertyEditor doFindEditor(Class<?> targetType) { synchronized (LOCK) { String targetTypeName = targetType.getName(); if (propertyEditorChainRegistry.containsKey(targetTypeName)) { PropertyEditorChain chain = propertyEditorChainRegistry.get(targetTypeName); return chain.copyOf(); } else { Class<?> propertyEditorType = propertyEditorRegistry.get(targetTypeName); if (propertyEditorType != null) { try { return (PropertyEditor) propertyEditorType.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new IllegalStateException("Can't instantiate " + propertyEditorType, e); } } } } return null; } public static final class NoopPropertyEditor extends PropertyEditorSupport { } private static final class WeakCache<K, V> { private final Map<K, Reference<V>> map = new WeakHashMap<>(); private V get(K key) { Reference reference = this.map.get(key); if (reference == null) { return null; } else { V value = (V) reference.get(); if (value == null) { this.map.remove(key); } return value; } } private void put(K key, V value) { if (value != null) { this.map.put(key, new WeakReference<>(value)); } else { this.map.remove(key); } } private void remove(K key) { this.map.remove(key); } private boolean contains(K key) { Reference reference = this.map.get(key); if (reference == null) { return false; } else { V value = (V) reference.get(); if (value == null) { this.map.remove(key); return false; } return true; } } private void clear() { this.map.clear(); } } }