1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package libcore.reflect;
19
20import java.io.ByteArrayInputStream;
21import java.io.ByteArrayOutputStream;
22import java.io.ObjectInputStream;
23import java.io.ObjectOutputStream;
24import java.io.Serializable;
25import java.lang.annotation.AnnotationTypeMismatchException;
26import java.lang.reflect.Array;
27import java.lang.reflect.Method;
28import java.util.Arrays;
29
30/**
31 * This class represents member element of an annotation.
32 * It consists of name and value, supplemented with element
33 * definition information (such as declared type of element).
34 * <br>The value may be one of the following types:
35 * <ul>
36 * <li> boxed primitive
37 * <li> Class
38 * <li> enum constant
39 * <li> annotation (nested)
40 * <li> one-dimensional array of the above
41 * <li> Throwable
42 * </ul>
43 * The last type is specific for this implementation; a Throwable value
44 * means that the error occured during parsing or resolution of corresponding
45 * class-data structures and throwing is delayed until the element
46 * is requested for value.
47 *
48 * @see AnnotationFactory
49 *
50 * @author Alexey V. Varlamov, Serguei S. Zapreyev
51 * @version $Revision$
52 */
53@SuppressWarnings({"serial"})
54public final class AnnotationMember implements Serializable {
55
56    /**
57     * Tag description of a Throwable value type.
58     */
59    protected static final char ERROR = '!';
60
61    /**
62     * Tag description of an array value type.
63     */
64    protected static final char ARRAY = '[';
65
66    /**
67     * Tag description of all value types except arrays and Throwables.
68     */
69    protected static final char OTHER = '*';
70
71//    public static final char INT = 'I';
72//    public static final char CHAR = 'C';
73//    public static final char DOUBLE = 'D';
74//    public static final char FLOAT = 'F';
75//    public static final char BYTE = 'B';
76//    public static final char LONG = 'J';
77//    public static final char SHORT = 'S';
78//    public static final char BOOL = 'Z';
79//    public static final char CLASS = 'c';
80//    public static final char ENUM = 'e';
81//    public static final char ANTN = '@';
82
83    private enum DefaultValues {NO_VALUE}
84
85    /**
86     * Singleton representing missing element value.
87     */
88    protected static final Object NO_VALUE = DefaultValues.NO_VALUE;
89
90    protected final String name;
91    protected final Object value; // a primitive value is wrapped to the corresponding wrapper class
92    protected final char tag;
93    // no sense to serialize definition info as it can be changed arbitrarily
94    protected transient Class<?> elementType;
95    protected transient Method definingMethod;
96
97
98    /**
99     * Creates a new element with specified name and value.
100     * Definition info will be provided later when this
101     * element becomes actual annotation member.
102     * @param name element name, must not be null
103     * @param val element value, should be of addmissible type,
104     * as specified in the description of this class
105     *
106     * @see #setDefinition(AnnotationMember)
107     */
108    public AnnotationMember(String name, Object val) {
109        this.name = name;
110        value = val == null ? NO_VALUE : val;
111        if (value instanceof Throwable) {
112            tag = ERROR;
113        } else if (value.getClass().isArray()) {
114            tag = ARRAY;
115        } else {
116            tag = OTHER;
117        }
118    }
119
120    /**
121     * Creates the completely defined element.
122     * @param name element name, must not be null
123     * @param value element value, should be of addmissible type,
124     * as specified in the description of this class
125     * @param m element-defining method, reflected on the annotation type
126     * @param type declared type of this element
127     * (return type of the defining method)
128     */
129    public AnnotationMember(String name, Object val, Class type, Method m) {
130        this(name, val);
131
132        definingMethod = m;
133
134        if (type == int.class) {
135            elementType = Integer.class;
136        } else if (type == boolean.class) {
137            elementType = Boolean.class;
138        } else if (type == char.class) {
139            elementType = Character.class;
140        } else if (type == float.class) {
141            elementType = Float.class;
142        } else if (type == double.class) {
143            elementType = Double.class;
144        } else if (type == long.class) {
145            elementType = Long.class;
146        } else if (type == short.class) {
147            elementType = Short.class;
148        } else if (type == byte.class) {
149            elementType = Byte.class;
150        } else {
151            elementType = type;
152        }
153    }
154
155    /**
156     * Fills in element's definition info and returns this.
157     */
158    protected AnnotationMember setDefinition(AnnotationMember copy) {
159        definingMethod = copy.definingMethod;
160        elementType = copy.elementType;
161        return this;
162    }
163
164    /**
165     * Returns readable description of this annotation value.
166     */
167    public String toString() {
168        if (tag == ARRAY) {
169            StringBuilder sb = new StringBuilder(80);
170            sb.append(name).append("=[");
171            int len = Array.getLength(value);
172            for (int i = 0; i < len; i++) {
173                if (i != 0) sb.append(", ");
174                sb.append(Array.get(value, i));
175            }
176            return sb.append("]").toString();
177        } else {
178            return name+ "=" +value;
179        }
180    }
181
182    /**
183     * Returns true if the specified object represents equal element
184     * (equivalent name-value pair).
185     * <br> A special case is the contained Throwable value; it is considered
186     * transcendent so no other element would be equal.
187     * @return true if passed object is equivalent element representation,
188     * false otherwise
189     * @see #equalArrayValue(Object)
190     * @see java.lang.annotation.Annotation#equals(Object)
191     */
192    public boolean equals(Object obj) {
193        if (obj == this) {
194            // not a mere optimization,
195            // this is needed for consistency with hashCode()
196            return true;
197        }
198        if (obj instanceof AnnotationMember) {
199            AnnotationMember that = (AnnotationMember)obj;
200            if (name.equals(that.name) && tag == that.tag) {
201                if (tag == ARRAY) {
202                    return equalArrayValue(that.value);
203                } else if (tag == ERROR) {
204                    // undefined value is incomparable (transcendent)
205                    return false;
206                } else {
207                    return value.equals(that.value);
208                }
209            }
210        }
211        return false;
212    }
213
214    /**
215     * Returns true if the contained value and a passed object are equal arrays,
216     * false otherwise. Appropriate overloaded method of Arrays.equals()
217     * is used for equality testing.
218     * @see java.util.Arrays#equals(java.lang.Object[], java.lang.Object[])
219     * @return true if the value is array and is equal to specified object,
220     * false otherwise
221     */
222    public boolean equalArrayValue(Object otherValue) {
223        if (value instanceof Object[] && otherValue instanceof Object[]) {
224            return Arrays.equals((Object[])value, (Object[])otherValue);
225        }
226        Class type = value.getClass();
227        if (type != otherValue.getClass()) {
228            return false;
229        }
230        if (type == int[].class) {
231            return Arrays.equals((int[])value, (int[])otherValue);
232        } else if (type == byte[].class) {
233            return Arrays.equals((byte[])value, (byte[])otherValue);
234        } else if (type == short[].class) {
235            return Arrays.equals((short[])value, (short[])otherValue);
236        } else if (type == long[].class) {
237            return Arrays.equals((long[])value, (long[])otherValue);
238        } else if (type == char[].class) {
239            return Arrays.equals((char[])value, (char[])otherValue);
240        } else if (type == boolean[].class) {
241            return Arrays.equals((boolean[])value, (boolean[])otherValue);
242        } else if (type == float[].class) {
243            return Arrays.equals((float[])value, (float[])otherValue);
244        } else if (type == double[].class) {
245            return Arrays.equals((double[])value, (double[])otherValue);
246        }
247        return false;
248    }
249
250    /**
251     * Computes hash code of this element. The formula is as follows:
252     * <code> (name.hashCode() * 127) ^ value.hashCode() </code>
253     * <br>If value is an array, one of overloaded Arrays.hashCode()
254     * methods is used.
255     * @return the hash code
256     * @see java.util.Arrays#hashCode(java.lang.Object[])
257     * @see java.lang.annotation.Annotation#hashCode()
258     */
259    public int hashCode() {
260        int hash = name.hashCode() * 127;
261        if (tag == ARRAY) {
262            Class type = value.getClass();
263            if (type == int[].class) {
264                return hash ^ Arrays.hashCode((int[])value);
265            } else if (type == byte[].class) {
266                return hash ^ Arrays.hashCode((byte[])value);
267            } else if (type == short[].class) {
268                return hash ^ Arrays.hashCode((short[])value);
269            } else if (type == long[].class) {
270                return hash ^ Arrays.hashCode((long[])value);
271            } else if (type == char[].class) {
272                return hash ^ Arrays.hashCode((char[])value);
273            } else if (type == boolean[].class) {
274                return hash ^ Arrays.hashCode((boolean[])value);
275            } else if (type == float[].class) {
276                return hash ^ Arrays.hashCode((float[])value);
277            } else if (type == double[].class) {
278                return hash ^ Arrays.hashCode((double[])value);
279            }
280            return hash ^ Arrays.hashCode((Object[])value);
281        } else {
282            return hash ^ value.hashCode();
283        }
284    }
285
286    /**
287     * Throws contained error (if any) with a renewed stack trace.
288     */
289    public void rethrowError() throws Throwable {
290        if (tag == ERROR) {
291            // need to throw cloned exception for thread safety
292            // besides it is better to provide actual stack trace
293            // rather than recorded during parsing
294
295            // first check for expected types
296            if (value instanceof TypeNotPresentException) {
297                TypeNotPresentException tnpe = (TypeNotPresentException)value;
298                throw new TypeNotPresentException(tnpe.typeName(), tnpe.getCause());
299            } else if (value instanceof EnumConstantNotPresentException) {
300                EnumConstantNotPresentException ecnpe = (EnumConstantNotPresentException)value;
301                throw new EnumConstantNotPresentException(ecnpe.enumType(), ecnpe.constantName());
302            } else if (value instanceof ArrayStoreException) {
303                ArrayStoreException ase = (ArrayStoreException)value;
304                throw new ArrayStoreException(ase.getMessage());
305            }
306            // got some other error, have to go with deep cloning
307            // via serialization mechanism
308            Throwable error = (Throwable)value;
309            StackTraceElement[] ste = error.getStackTrace();
310            ByteArrayOutputStream bos = new ByteArrayOutputStream(
311                    ste == null ? 512 : (ste.length + 1) * 80);
312            ObjectOutputStream oos = new ObjectOutputStream(bos);
313            oos.writeObject(error);
314            oos.flush();
315            oos.close();
316            ByteArrayInputStream bis = new ByteArrayInputStream(bos
317                    .toByteArray());
318            ObjectInputStream ois = new ObjectInputStream(bis);
319            error = (Throwable)ois.readObject();
320            ois.close();
321
322            throw error;
323        }
324    }
325
326    /**
327     * Validates contained value against its member definition
328     * and if ok returns the value.
329     * Otherwise, if the value type mismatches definition
330     * or the value itself describes an error,
331     * throws appropriate exception.
332     * <br> Note, this method may return null if this element was constructed
333     * with such value.
334     *
335     * @see #rethrowError()
336     * @see #copyValue()
337     * @return actual valid value or null if no value
338     */
339    public Object validateValue() throws Throwable {
340        if (tag == ERROR) {
341            rethrowError();
342        }
343        if (value == NO_VALUE) {
344            return null;
345        }
346        if (elementType == value.getClass()
347                || elementType.isInstance(value)) { // nested annotation value
348            return copyValue();
349        } else {
350            throw new AnnotationTypeMismatchException(definingMethod,
351                    value.getClass().getName());
352        }
353
354    }
355
356
357    /**
358     * Provides mutation-safe access to contained value. That is, caller is free
359     * to modify the returned value, it will not affect the contained data value.
360     * @return cloned value if it is mutable or the original immutable value
361     */
362    public Object copyValue() throws Throwable
363    {
364        if (tag != ARRAY || Array.getLength(value) == 0) {
365            return value;
366        }
367        Class type = value.getClass();
368        if (type == int[].class) {
369            return ((int[])value).clone();
370        } else if (type == byte[].class) {
371            return ((byte[])value).clone();
372        } else if (type == short[].class) {
373            return ((short[])value).clone();
374        } else if (type == long[].class) {
375            return ((long[])value).clone();
376        } else if (type == char[].class) {
377            return ((char[])value).clone();
378        } else if (type == boolean[].class) {
379            return ((boolean[])value).clone();
380        } else if (type == float[].class) {
381            return ((float[])value).clone();
382        } else if (type == double[].class) {
383            return ((double[])value).clone();
384        }
385        return ((Object[])value).clone();
386    }
387}
388