PropertyEditorChain.java
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 javax.annotation.Nonnull;
021 import javax.annotation.Nullable;
022 import javax.annotation.concurrent.GuardedBy;
023 import java.beans.PropertyEditor;
024 import java.beans.PropertyEditorSupport;
025 import java.lang.ref.WeakReference;
026 import java.util.ArrayList;
027 import java.util.Collections;
028 import java.util.LinkedHashSet;
029 import java.util.List;
030 import java.util.Set;
031 
032 import static griffon.util.GriffonClassUtils.requireNonEmpty;
033 import static griffon.util.GriffonNameUtils.isBlank;
034 import static java.util.Objects.requireNonNull;
035 
036 /**
037  @author Andres Almiray
038  @since 2.4.0
039  */
040 public class PropertyEditorChain extends PropertyEditorSupport implements ExtendedPropertyEditor {
041     private final Class<?> targetClass;
042     private final Object lock = new Object[0];
043     private final WeakReference<Class<? extends PropertyEditor>>[] propertyEditorClasses;
044     @GuardedBy("lock")
045     private WeakReference<PropertyEditor>[] propertyEditors;
046     private String format;
047 
048     @SuppressWarnings("unchecked")
049     public PropertyEditorChain(@Nonnull Class<?> targetClass, @Nonnull Class<? extends PropertyEditor>[] propertyEditorClasses) {
050         this.targetClass = requireNonNull(targetClass, "Argument 'targetClass' must not be null");
051         requireNonEmpty(propertyEditorClasses, "Argument 'propertyEditorClasses' must not be null nor empty");
052         // let's make sure propertyEditorClasses contains unique elements
053         Set<Class<? extends PropertyEditor>> classes = new LinkedHashSet<>();
054         Collections.addAll(classes, propertyEditorClasses);
055 
056         int i = 0;
057         this.propertyEditorClasses = new WeakReference[classes.size()];
058         for (Class<? extends PropertyEditor> klass : classes) {
059             this.propertyEditorClasses[i++new WeakReference<Class<? extends PropertyEditor>>(klass);
060         }
061     }
062 
063     @Override
064     @Nullable
065     public String getFormat() {
066         return format;
067     }
068 
069     @Override
070     public void setFormat(@Nullable String format) {
071         this.format = format;
072     }
073 
074     public int getSize() {
075         return propertyEditorClasses.length;
076     }
077 
078     @SuppressWarnings("unchecked")
079     public PropertyEditorChain copyOf() {
080         List<Class<? extends PropertyEditor>> classes = new ArrayList<>();
081         for (WeakReference<Class<? extends PropertyEditor>> reference : propertyEditorClasses) {
082             if (reference.get() != null) {
083                 classes.add(reference.get());
084             }
085         }
086         return new PropertyEditorChain(targetClass, classes.toArray(new Class[classes.size()]));
087     }
088 
089     @SuppressWarnings("unchecked")
090     public PropertyEditorChain copyOf(Class<? extends PropertyEditor> propertyEditorClass) {
091         requireNonNull(propertyEditorClass, "Argument 'propertyEditorClass' must not be null");
092         List<Class<? extends PropertyEditor>> classes = new ArrayList<>();
093         for (WeakReference<Class<? extends PropertyEditor>> reference : propertyEditorClasses) {
094             if (reference.get() != null) {
095                 classes.add(reference.get());
096             }
097         }
098         if (!classes.contains(propertyEditorClass)) {
099             classes.add(propertyEditorClass);
100         }
101         return new PropertyEditorChain(targetClass, classes.toArray(new Class[classes.size()]));
102     }
103 
104     @Override
105     public String toString() {
106         StringBuilder sb = new StringBuilder(super.toString());
107         sb.append("[").append(targetClass.getName()).append(']');
108         return sb.toString();
109     }
110 
111     @Override
112     public String getAsText() {
113         return isBlank(getFormat()) ? getAsTextInternal() : getFormattedValue();
114     }
115 
116     @Override
117     public void setAsText(String strthrows IllegalArgumentException {
118         if (isBlank(getFormat())) {
119             setAsTextInternal(str);
120         else {
121             setFormattedValue(str);
122         }
123     }
124 
125     @Override
126     public void setValue(Object value) {
127         if (value instanceof CharSequence) {
128             setFormattedValue(String.valueOf(value));
129         else {
130             setValueInternal(value);
131         }
132     }
133 
134     @Override
135     @SuppressWarnings({"unchecked""rawtypes"})
136     public String getFormattedValue() {
137         initPropertyEditors();
138 
139         Object value = super.getValue();
140         for (WeakReference<PropertyEditor> reference : propertyEditors) {
141             try {
142                 PropertyEditor propertyEditor = reference.get();
143                 if (propertyEditor != null && propertyEditor instanceof ExtendedPropertyEditor) {
144                     ExtendedPropertyEditor extendedPropertyEditor = (ExtendedPropertyEditorpropertyEditor;
145                     extendedPropertyEditor.setFormat(format);
146                     extendedPropertyEditor.setValue(value);
147                     return extendedPropertyEditor.getFormattedValue();
148                 }
149             catch (Exception e) {
150                 // ignore. next editor
151             }
152         }
153 
154         throw illegalValue(value, targetClass);
155     }
156 
157     @Override
158     @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
159     public void setFormattedValue(String value) {
160         initPropertyEditors();
161 
162         for (WeakReference<PropertyEditor> reference : propertyEditors) {
163             try {
164                 PropertyEditor propertyEditor = reference.get();
165                 if (propertyEditor != null && propertyEditor instanceof ExtendedPropertyEditor) {
166                     ExtendedPropertyEditor extendedPropertyEditor = (ExtendedPropertyEditorpropertyEditor;
167                     extendedPropertyEditor.setFormat(format);
168                     extendedPropertyEditor.setValue(value);
169                     super.setValue(extendedPropertyEditor.getValue());
170                     return;
171                 }
172             catch (Exception e) {
173                 // ignore. next editor
174             }
175         }
176 
177         throw illegalValue(value, targetClass);
178     }
179 
180     protected void setValueInternal(Object valuethrows IllegalArgumentException {
181         initPropertyEditors();
182 
183         for (WeakReference<PropertyEditor> reference : propertyEditors) {
184             try {
185                 PropertyEditor propertyEditor = reference.get();
186                 if (propertyEditor != null) {
187                     propertyEditor.setValue(value);
188                     super.setValue(propertyEditor.getValue());
189                     return;
190                 }
191             catch (Exception e) {
192                 // ignore. next editor
193             }
194         }
195 
196         throw illegalValue(value, targetClass);
197     }
198 
199     protected Object getValueInternal() {
200         initPropertyEditors();
201 
202         Object value = super.getValue();
203         for (WeakReference<PropertyEditor> reference : propertyEditors) {
204             try {
205                 PropertyEditor propertyEditor = reference.get();
206                 if (propertyEditor != null) {
207                     propertyEditor.setValue(value);
208                     return propertyEditor.getValue();
209                 }
210             catch (Exception e) {
211                 // ignore. next editor
212             }
213         }
214 
215         throw illegalValue(value, targetClass);
216     }
217 
218     protected String getAsTextInternal() {
219         initPropertyEditors();
220 
221         Object value = super.getValue();
222 
223         for (WeakReference<PropertyEditor> reference : propertyEditors) {
224             try {
225                 PropertyEditor propertyEditor = reference.get();
226                 if (propertyEditor != null) {
227                     propertyEditor.setValue(value);
228                     return propertyEditor.getAsText();
229                 }
230             catch (Exception e) {
231                 // ignore. next editor
232             }
233         }
234 
235         throw illegalValue(value, targetClass);
236     }
237 
238     protected void setAsTextInternal(String textthrows IllegalArgumentException {
239         initPropertyEditors();
240 
241         for (WeakReference<PropertyEditor> reference : propertyEditors) {
242             try {
243                 PropertyEditor propertyEditor = reference.get();
244                 if (propertyEditor != null) {
245                     propertyEditor.setAsText(text);
246                     super.setValue(propertyEditor.getValue());
247                     return;
248                 }
249             catch (Exception e) {
250                 // ignore. next editor
251             }
252         }
253 
254         throw illegalValue(text, targetClass);
255     }
256 
257     protected ValueConversionException illegalValue(Object value, Class<?> klass) {
258         throw new ValueConversionException(value, klass);
259     }
260 
261     protected ValueConversionException illegalValue(Object value, Class<?> klass, Exception e) {
262         throw new ValueConversionException(value, klass, e);
263     }
264 
265     @SuppressWarnings("unchecked")
266     private void initPropertyEditors() {
267         synchronized (lock) {
268             if (propertyEditors == null) {
269                 List<WeakReference<PropertyEditor>> editors = new ArrayList<>();
270                 for (WeakReference<Class<? extends PropertyEditor>> propertyEditorClass : propertyEditorClasses) {
271                     try {
272                         Class<? extends PropertyEditor> klass = propertyEditorClass.get();
273                         if (klass != null) {
274                             editors.add(new WeakReference<>(klass.newInstance()));
275                         }
276                     catch (InstantiationException | IllegalAccessException e) {
277                         throw new IllegalArgumentException("Can't create instance", e);
278                     }
279                 }
280 
281                 if (!editors.isEmpty()) {
282                     propertyEditors = editors.toArray(new WeakReference[editors.size()]);
283                 else {
284                     throw new IllegalStateException("No available PropertyEditors for " this);
285                 }
286             }
287         }
288     }
289 }