1/*
2 * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package java.beans;
26
27import java.lang.ref.Reference;
28import java.lang.reflect.Method;
29import java.util.Map.Entry;
30
31import com.sun.beans.introspect.PropertyInfo;
32
33/**
34 * An IndexedPropertyDescriptor describes a property that acts like an
35 * array and has an indexed read and/or indexed write method to access
36 * specific elements of the array.
37 * <p>
38 * An indexed property may also provide simple non-indexed read and write
39 * methods.  If these are present, they read and write arrays of the type
40 * returned by the indexed read method.
41 *
42 * @since 1.1
43 */
44
45public class IndexedPropertyDescriptor extends PropertyDescriptor {
46
47    private Reference<? extends Class<?>> indexedPropertyTypeRef;
48    private final MethodRef indexedReadMethodRef = new MethodRef();
49    private final MethodRef indexedWriteMethodRef = new MethodRef();
50
51    private String indexedReadMethodName;
52    private String indexedWriteMethodName;
53
54    /**
55     * This constructor constructs an IndexedPropertyDescriptor for a property
56     * that follows the standard Java conventions by having getFoo and setFoo
57     * accessor methods, for both indexed access and array access.
58     * <p>
59     * Thus if the argument name is "fred", it will assume that there
60     * is an indexed reader method "getFred", a non-indexed (array) reader
61     * method also called "getFred", an indexed writer method "setFred",
62     * and finally a non-indexed writer method "setFred".
63     *
64     * @param propertyName The programmatic name of the property.
65     * @param beanClass The Class object for the target bean.
66     * @exception IntrospectionException if an exception occurs during
67     *              introspection.
68     */
69    public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass)
70                throws IntrospectionException {
71        this(propertyName, beanClass,
72             Introspector.GET_PREFIX + NameGenerator.capitalize(propertyName),
73             Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName),
74             Introspector.GET_PREFIX + NameGenerator.capitalize(propertyName),
75             Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
76    }
77
78    /**
79     * This constructor takes the name of a simple property, and method
80     * names for reading and writing the property, both indexed
81     * and non-indexed.
82     *
83     * @param propertyName The programmatic name of the property.
84     * @param beanClass  The Class object for the target bean.
85     * @param readMethodName The name of the method used for reading the property
86     *           values as an array.  May be null if the property is write-only
87     *           or must be indexed.
88     * @param writeMethodName The name of the method used for writing the property
89     *           values as an array.  May be null if the property is read-only
90     *           or must be indexed.
91     * @param indexedReadMethodName The name of the method used for reading
92     *          an indexed property value.
93     *          May be null if the property is write-only.
94     * @param indexedWriteMethodName The name of the method used for writing
95     *          an indexed property value.
96     *          May be null if the property is read-only.
97     * @exception IntrospectionException if an exception occurs during
98     *              introspection.
99     */
100    public IndexedPropertyDescriptor(String propertyName, Class<?> beanClass,
101                String readMethodName, String writeMethodName,
102                String indexedReadMethodName, String indexedWriteMethodName)
103                throws IntrospectionException {
104        super(propertyName, beanClass, readMethodName, writeMethodName);
105
106        this.indexedReadMethodName = indexedReadMethodName;
107        if (indexedReadMethodName != null && getIndexedReadMethod() == null) {
108            throw new IntrospectionException("Method not found: " + indexedReadMethodName);
109        }
110
111        this.indexedWriteMethodName = indexedWriteMethodName;
112        if (indexedWriteMethodName != null && getIndexedWriteMethod() == null) {
113            throw new IntrospectionException("Method not found: " + indexedWriteMethodName);
114        }
115        // Implemented only for type checking.
116        findIndexedPropertyType(getIndexedReadMethod(), getIndexedWriteMethod());
117    }
118
119    /**
120     * This constructor takes the name of a simple property, and Method
121     * objects for reading and writing the property.
122     *
123     * @param propertyName The programmatic name of the property.
124     * @param readMethod The method used for reading the property values as an array.
125     *          May be null if the property is write-only or must be indexed.
126     * @param writeMethod The method used for writing the property values as an array.
127     *          May be null if the property is read-only or must be indexed.
128     * @param indexedReadMethod The method used for reading an indexed property value.
129     *          May be null if the property is write-only.
130     * @param indexedWriteMethod The method used for writing an indexed property value.
131     *          May be null if the property is read-only.
132     * @exception IntrospectionException if an exception occurs during
133     *              introspection.
134     */
135    public IndexedPropertyDescriptor(String propertyName, Method readMethod, Method writeMethod,
136                                            Method indexedReadMethod, Method indexedWriteMethod)
137                throws IntrospectionException {
138        super(propertyName, readMethod, writeMethod);
139
140        setIndexedReadMethod0(indexedReadMethod);
141        setIndexedWriteMethod0(indexedWriteMethod);
142
143        // Type checking
144        setIndexedPropertyType(findIndexedPropertyType(indexedReadMethod, indexedWriteMethod));
145    }
146
147    /**
148     * Creates {@code IndexedPropertyDescriptor} from the specified property info.
149     *
150     * @param entry  the key-value pair,
151     *               where the {@code key} is the base name of the property (the rest of the method name)
152     *               and the {@code value} is the automatically generated property info
153     * @param bound  the flag indicating whether it is possible to treat this property as a bound property
154     *
155     * @since 9
156     */
157    IndexedPropertyDescriptor(Entry<String,PropertyInfo> entry, boolean bound) {
158        super(entry, bound);
159        PropertyInfo info = entry.getValue().getIndexed();
160        setIndexedReadMethod0(info.getReadMethod());
161        setIndexedWriteMethod0(info.getWriteMethod());
162        setIndexedPropertyType(info.getPropertyType());
163    }
164
165    /**
166     * Gets the method that should be used to read an indexed
167     * property value.
168     *
169     * @return The method that should be used to read an indexed
170     * property value.
171     * May return null if the property isn't indexed or is write-only.
172     */
173    public synchronized Method getIndexedReadMethod() {
174        Method indexedReadMethod = this.indexedReadMethodRef.get();
175        if (indexedReadMethod == null) {
176            Class<?> cls = getClass0();
177            if (cls == null ||
178                (indexedReadMethodName == null && !this.indexedReadMethodRef.isSet())) {
179                // the Indexed readMethod was explicitly set to null.
180                return null;
181            }
182            String nextMethodName = Introspector.GET_PREFIX + getBaseName();
183            if (indexedReadMethodName == null) {
184                Class<?> type = getIndexedPropertyType0();
185                if (type == boolean.class || type == null) {
186                    indexedReadMethodName = Introspector.IS_PREFIX + getBaseName();
187                } else {
188                    indexedReadMethodName = nextMethodName;
189                }
190            }
191
192            Class<?>[] args = { int.class };
193            indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
194            if ((indexedReadMethod == null) && !indexedReadMethodName.equals(nextMethodName)) {
195                // no "is" method, so look for a "get" method.
196                indexedReadMethodName = nextMethodName;
197                indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
198            }
199            setIndexedReadMethod0(indexedReadMethod);
200        }
201        return indexedReadMethod;
202    }
203
204    /**
205     * Sets the method that should be used to read an indexed property value.
206     *
207     * @param readMethod The new indexed read method.
208     * @throws IntrospectionException if an exception occurs during
209     * introspection.
210     *
211     * @since 1.2
212     */
213    public synchronized void setIndexedReadMethod(Method readMethod)
214        throws IntrospectionException {
215
216        // the indexed property type is set by the reader.
217        setIndexedPropertyType(findIndexedPropertyType(readMethod,
218                                                       this.indexedWriteMethodRef.get()));
219        setIndexedReadMethod0(readMethod);
220    }
221
222    private void setIndexedReadMethod0(Method readMethod) {
223        this.indexedReadMethodRef.set(readMethod);
224        if (readMethod == null) {
225            indexedReadMethodName = null;
226            return;
227        }
228        setClass0(readMethod.getDeclaringClass());
229
230        indexedReadMethodName = readMethod.getName();
231        setTransient(readMethod.getAnnotation(Transient.class));
232    }
233
234
235    /**
236     * Gets the method that should be used to write an indexed property value.
237     *
238     * @return The method that should be used to write an indexed
239     * property value.
240     * May return null if the property isn't indexed or is read-only.
241     */
242    public synchronized Method getIndexedWriteMethod() {
243        Method indexedWriteMethod = this.indexedWriteMethodRef.get();
244        if (indexedWriteMethod == null) {
245            Class<?> cls = getClass0();
246            if (cls == null ||
247                (indexedWriteMethodName == null && !this.indexedWriteMethodRef.isSet())) {
248                // the Indexed writeMethod was explicitly set to null.
249                return null;
250            }
251
252            // We need the indexed type to ensure that we get the correct method.
253            // Cannot use the getIndexedPropertyType method since that could
254            // result in an infinite loop.
255            Class<?> type = getIndexedPropertyType0();
256            if (type == null) {
257                try {
258                    type = findIndexedPropertyType(getIndexedReadMethod(), null);
259                    setIndexedPropertyType(type);
260                } catch (IntrospectionException ex) {
261                    // Set iprop type to be the classic type
262                    Class<?> propType = getPropertyType();
263                    if (propType.isArray()) {
264                        type = propType.getComponentType();
265                    }
266                }
267            }
268
269            if (indexedWriteMethodName == null) {
270                indexedWriteMethodName = Introspector.SET_PREFIX + getBaseName();
271            }
272
273            Class<?>[] args = (type == null) ? null : new Class<?>[] { int.class, type };
274            indexedWriteMethod = Introspector.findMethod(cls, indexedWriteMethodName, 2, args);
275            if (indexedWriteMethod != null) {
276                if (!indexedWriteMethod.getReturnType().equals(void.class)) {
277                    indexedWriteMethod = null;
278                }
279            }
280            setIndexedWriteMethod0(indexedWriteMethod);
281        }
282        return indexedWriteMethod;
283    }
284
285    /**
286     * Sets the method that should be used to write an indexed property value.
287     *
288     * @param writeMethod The new indexed write method.
289     * @throws IntrospectionException if an exception occurs during
290     * introspection.
291     *
292     * @since 1.2
293     */
294    public synchronized void setIndexedWriteMethod(Method writeMethod)
295        throws IntrospectionException {
296
297        // If the indexed property type has not been set, then set it.
298        Class<?> type = findIndexedPropertyType(getIndexedReadMethod(),
299                                             writeMethod);
300        setIndexedPropertyType(type);
301        setIndexedWriteMethod0(writeMethod);
302    }
303
304    private void setIndexedWriteMethod0(Method writeMethod) {
305        this.indexedWriteMethodRef.set(writeMethod);
306        if (writeMethod == null) {
307            indexedWriteMethodName = null;
308            return;
309        }
310        setClass0(writeMethod.getDeclaringClass());
311
312        indexedWriteMethodName = writeMethod.getName();
313        setTransient(writeMethod.getAnnotation(Transient.class));
314    }
315
316    /**
317     * Returns the Java type info for the indexed property.
318     * Note that the {@code Class} object may describe
319     * primitive Java types such as {@code int}.
320     * This type is returned by the indexed read method
321     * or is used as the parameter type of the indexed write method.
322     *
323     * @return the {@code Class} object that represents the Java type info,
324     *         or {@code null} if the type cannot be determined
325     */
326    public synchronized Class<?> getIndexedPropertyType() {
327        Class<?> type = getIndexedPropertyType0();
328        if (type == null) {
329            try {
330                type = findIndexedPropertyType(getIndexedReadMethod(),
331                                               getIndexedWriteMethod());
332                setIndexedPropertyType(type);
333            } catch (IntrospectionException ex) {
334                // fall
335            }
336        }
337        return type;
338    }
339
340    // Private methods which set get/set the Reference objects
341
342    private void setIndexedPropertyType(Class<?> type) {
343        this.indexedPropertyTypeRef = getWeakReference(type);
344    }
345
346    private Class<?> getIndexedPropertyType0() {
347        return (this.indexedPropertyTypeRef != null)
348                ? this.indexedPropertyTypeRef.get()
349                : null;
350    }
351
352    private Class<?> findIndexedPropertyType(Method indexedReadMethod,
353                                          Method indexedWriteMethod)
354        throws IntrospectionException {
355        Class<?> indexedPropertyType = null;
356
357        if (indexedReadMethod != null) {
358            Class<?>[] params = getParameterTypes(getClass0(), indexedReadMethod);
359            if (params.length != 1) {
360                throw new IntrospectionException("bad indexed read method arg count");
361            }
362            if (params[0] != Integer.TYPE) {
363                throw new IntrospectionException("non int index to indexed read method");
364            }
365            indexedPropertyType = getReturnType(getClass0(), indexedReadMethod);
366            if (indexedPropertyType == Void.TYPE) {
367                throw new IntrospectionException("indexed read method returns void");
368            }
369        }
370        if (indexedWriteMethod != null) {
371            Class<?>[] params = getParameterTypes(getClass0(), indexedWriteMethod);
372            if (params.length != 2) {
373                throw new IntrospectionException("bad indexed write method arg count");
374            }
375            if (params[0] != Integer.TYPE) {
376                throw new IntrospectionException("non int index to indexed write method");
377            }
378            if (indexedPropertyType == null || params[1].isAssignableFrom(indexedPropertyType)) {
379                indexedPropertyType = params[1];
380            } else if (!indexedPropertyType.isAssignableFrom(params[1])) {
381                throw new IntrospectionException(
382                                                 "type mismatch between indexed read and indexed write methods: "
383                                                 + getName());
384            }
385        }
386        Class<?> propertyType = getPropertyType();
387        if (propertyType != null && (!propertyType.isArray() ||
388                                     propertyType.getComponentType() != indexedPropertyType)) {
389            throw new IntrospectionException("type mismatch between indexed and non-indexed methods: "
390                                             + getName());
391        }
392        return indexedPropertyType;
393    }
394
395    /**
396     * Compares this {@code PropertyDescriptor} against the specified object.
397     * Returns true if the objects are the same. Two {@code PropertyDescriptor}s
398     * are the same if the read, write, property types, property editor and
399     * flags  are equivalent.
400     *
401     * @since 1.4
402     */
403    public boolean equals(Object obj) {
404        // Note: This would be identical to PropertyDescriptor but they don't
405        // share the same fields.
406        if (this == obj) {
407            return true;
408        }
409
410        if (obj != null && obj instanceof IndexedPropertyDescriptor) {
411            IndexedPropertyDescriptor other = (IndexedPropertyDescriptor)obj;
412            Method otherIndexedReadMethod = other.getIndexedReadMethod();
413            Method otherIndexedWriteMethod = other.getIndexedWriteMethod();
414
415            if (!compareMethods(getIndexedReadMethod(), otherIndexedReadMethod)) {
416                return false;
417            }
418
419            if (!compareMethods(getIndexedWriteMethod(), otherIndexedWriteMethod)) {
420                return false;
421            }
422
423            if (getIndexedPropertyType() != other.getIndexedPropertyType()) {
424                return false;
425            }
426            return super.equals(obj);
427        }
428        return false;
429    }
430
431    /**
432     * Package-private constructor.
433     * Merge two property descriptors.  Where they conflict, give the
434     * second argument (y) priority over the first argument (x).
435     *
436     * @param x  The first (lower priority) PropertyDescriptor
437     * @param y  The second (higher priority) PropertyDescriptor
438     */
439
440    IndexedPropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) {
441        super(x,y);
442        Method tr = null;
443        Method tw = null;
444
445        if (x instanceof IndexedPropertyDescriptor) {
446            IndexedPropertyDescriptor ix = (IndexedPropertyDescriptor) x;
447            tr = ix.getIndexedReadMethod();
448            tw = ix.getIndexedWriteMethod();
449        }
450        if (y instanceof IndexedPropertyDescriptor) {
451            IndexedPropertyDescriptor iy = (IndexedPropertyDescriptor) y;
452            Method yr = iy.getIndexedReadMethod();
453            if (isAssignable(tr, yr)) {
454                tr = yr;
455            }
456
457            Method yw = iy.getIndexedWriteMethod();
458            if (isAssignable(tw, yw)) {
459                tw = yw;
460            }
461        }
462
463        try {
464            if(tr != null) {
465                setIndexedReadMethod(tr);
466            }
467            if(tw != null) {
468                setIndexedWriteMethod(tw);
469            }
470        } catch(IntrospectionException ex) {
471            // Should not happen
472            throw new AssertionError(ex);
473        }
474    }
475
476    /*
477     * Package-private dup constructor
478     * This must isolate the new object from any changes to the old object.
479     */
480    IndexedPropertyDescriptor(IndexedPropertyDescriptor old) {
481        super(old);
482        this.indexedReadMethodRef.set(old.indexedReadMethodRef.get());
483        this.indexedWriteMethodRef.set(old.indexedWriteMethodRef.get());
484        indexedPropertyTypeRef = old.indexedPropertyTypeRef;
485        indexedWriteMethodName = old.indexedWriteMethodName;
486        indexedReadMethodName = old.indexedReadMethodName;
487    }
488
489    void updateGenericsFor(Class<?> type) {
490        super.updateGenericsFor(type);
491        try {
492            setIndexedPropertyType(findIndexedPropertyType(this.indexedReadMethodRef.get(), this.indexedWriteMethodRef.get()));
493        }
494        catch (IntrospectionException exception) {
495            setIndexedPropertyType(null);
496        }
497    }
498
499    /**
500     * Returns a hash code value for the object.
501     * See {@link java.lang.Object#hashCode} for a complete description.
502     *
503     * @return a hash code value for this object.
504     * @since 1.5
505     */
506    public int hashCode() {
507        int result = super.hashCode();
508
509        result = 37 * result + ((indexedWriteMethodName == null) ? 0 :
510                                indexedWriteMethodName.hashCode());
511        result = 37 * result + ((indexedReadMethodName == null) ? 0 :
512                                indexedReadMethodName.hashCode());
513        result = 37 * result + ((getIndexedPropertyType() == null) ? 0 :
514                                getIndexedPropertyType().hashCode());
515
516        return result;
517    }
518
519    void appendTo(StringBuilder sb) {
520        super.appendTo(sb);
521        appendTo(sb, "indexedPropertyType", this.indexedPropertyTypeRef);
522        appendTo(sb, "indexedReadMethod", this.indexedReadMethodRef.get());
523        appendTo(sb, "indexedWriteMethod", this.indexedWriteMethodRef.get());
524    }
525}
526