/* * 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 javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import java.beans.PropertyEditor; import java.beans.PropertyEditorSupport; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import static griffon.util.GriffonClassUtils.requireNonEmpty; import static griffon.util.GriffonNameUtils.isBlank; import static java.util.Objects.requireNonNull; /** * @author Andres Almiray * @since 2.4.0 */ public class PropertyEditorChain extends PropertyEditorSupport implements ExtendedPropertyEditor { private final Class<?> targetClass; private final Object lock = new Object[0]; private final WeakReference<Class<? extends PropertyEditor>>[] propertyEditorClasses; @GuardedBy("lock") private WeakReference<PropertyEditor>[] propertyEditors; private String format; @SuppressWarnings("unchecked") public PropertyEditorChain(@Nonnull Class<?> targetClass, @Nonnull Class<? extends PropertyEditor>[] propertyEditorClasses) { this.targetClass = requireNonNull(targetClass, "Argument 'targetClass' must not be null"); requireNonEmpty(propertyEditorClasses, "Argument 'propertyEditorClasses' must not be null nor empty"); // let's make sure propertyEditorClasses contains unique elements Set<Class<? extends PropertyEditor>> classes = new LinkedHashSet<>(); Collections.addAll(classes, propertyEditorClasses); int i = 0; this.propertyEditorClasses = new WeakReference[classes.size()]; for (Class<? extends PropertyEditor> klass : classes) { this.propertyEditorClasses[i++] = new WeakReference<Class<? extends PropertyEditor>>(klass); } } @Nullable public String getFormat() { return format; } public void setFormat(@Nullable String format) { this.format = format; } public int getSize() { return propertyEditorClasses.length; } @SuppressWarnings("unchecked") public PropertyEditorChain copyOf() { List<Class<? extends PropertyEditor>> classes = new ArrayList<>(); for (WeakReference<Class<? extends PropertyEditor>> reference : propertyEditorClasses) { if (reference.get() != null) { classes.add(reference.get()); } } return new PropertyEditorChain(targetClass, classes.toArray(new Class[classes.size()])); } @SuppressWarnings("unchecked") public PropertyEditorChain copyOf(Class<? extends PropertyEditor> propertyEditorClass) { requireNonNull(propertyEditorClass, "Argument 'propertyEditorClass' must not be null"); List<Class<? extends PropertyEditor>> classes = new ArrayList<>(); for (WeakReference<Class<? extends PropertyEditor>> reference : propertyEditorClasses) { if (reference.get() != null) { classes.add(reference.get()); } } if (!classes.contains(propertyEditorClass)) { classes.add(propertyEditorClass); } return new PropertyEditorChain(targetClass, classes.toArray(new Class[classes.size()])); } @Override public String toString() { StringBuilder sb = new StringBuilder(super.toString()); sb.append("[").append(targetClass.getName()).append(']'); return sb.toString(); } @Override public String getAsText() { return isBlank(getFormat()) ? getAsTextInternal() : getFormattedValue(); } @Override public void setAsText(String str) throws IllegalArgumentException { if (isBlank(getFormat())) { setAsTextInternal(str); } else { setFormattedValue(str); } } @Override public void setValue(Object value) { if (value instanceof CharSequence) { setFormattedValue(String.valueOf(value)); } else { setValueInternal(value); } } @SuppressWarnings({"unchecked", "rawtypes"}) public String getFormattedValue() { initPropertyEditors(); Object value = super.getValue(); for (WeakReference<PropertyEditor> reference : propertyEditors) { try { PropertyEditor propertyEditor = reference.get(); if (propertyEditor != null && propertyEditor instanceof ExtendedPropertyEditor) { ExtendedPropertyEditor extendedPropertyEditor = (ExtendedPropertyEditor) propertyEditor; extendedPropertyEditor.setFormat(format); extendedPropertyEditor.setValue(value); return extendedPropertyEditor.getFormattedValue(); } } catch (Exception e) { // ignore. next editor } } throw illegalValue(value, targetClass); } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void setFormattedValue(String value) { initPropertyEditors(); for (WeakReference<PropertyEditor> reference : propertyEditors) { try { PropertyEditor propertyEditor = reference.get(); if (propertyEditor != null && propertyEditor instanceof ExtendedPropertyEditor) { ExtendedPropertyEditor extendedPropertyEditor = (ExtendedPropertyEditor) propertyEditor; extendedPropertyEditor.setFormat(format); extendedPropertyEditor.setValue(value); super.setValue(extendedPropertyEditor.getValue()); return; } } catch (Exception e) { // ignore. next editor } } throw illegalValue(value, targetClass); } protected void setValueInternal(Object value) throws IllegalArgumentException { initPropertyEditors(); for (WeakReference<PropertyEditor> reference : propertyEditors) { try { PropertyEditor propertyEditor = reference.get(); if (propertyEditor != null) { propertyEditor.setValue(value); super.setValue(propertyEditor.getValue()); return; } } catch (Exception e) { // ignore. next editor } } throw illegalValue(value, targetClass); } protected Object getValueInternal() { initPropertyEditors(); Object value = super.getValue(); for (WeakReference<PropertyEditor> reference : propertyEditors) { try { PropertyEditor propertyEditor = reference.get(); if (propertyEditor != null) { propertyEditor.setValue(value); return propertyEditor.getValue(); } } catch (Exception e) { // ignore. next editor } } throw illegalValue(value, targetClass); } protected String getAsTextInternal() { initPropertyEditors(); Object value = super.getValue(); for (WeakReference<PropertyEditor> reference : propertyEditors) { try { PropertyEditor propertyEditor = reference.get(); if (propertyEditor != null) { propertyEditor.setValue(value); return propertyEditor.getAsText(); } } catch (Exception e) { // ignore. next editor } } throw illegalValue(value, targetClass); } protected void setAsTextInternal(String text) throws IllegalArgumentException { initPropertyEditors(); for (WeakReference<PropertyEditor> reference : propertyEditors) { try { PropertyEditor propertyEditor = reference.get(); if (propertyEditor != null) { propertyEditor.setAsText(text); super.setValue(propertyEditor.getValue()); return; } } catch (Exception e) { // ignore. next editor } } throw illegalValue(text, targetClass); } protected ValueConversionException illegalValue(Object value, Class<?> klass) { throw new ValueConversionException(value, klass); } protected ValueConversionException illegalValue(Object value, Class<?> klass, Exception e) { throw new ValueConversionException(value, klass, e); } @SuppressWarnings("unchecked") private void initPropertyEditors() { synchronized (lock) { if (propertyEditors == null) { List<WeakReference<PropertyEditor>> editors = new ArrayList<>(); for (WeakReference<Class<? extends PropertyEditor>> propertyEditorClass : propertyEditorClasses) { try { Class<? extends PropertyEditor> klass = propertyEditorClass.get(); if (klass != null) { editors.add(new WeakReference<>(klass.newInstance())); } } catch (InstantiationException | IllegalAccessException e) { throw new IllegalArgumentException("Can't create instance", e); } } if (editors.size() > 0) { propertyEditors = editors.toArray(new WeakReference[editors.size()]); } else { throw new IllegalStateException("No available PropertyEditors for " + this); } } } } }