1 /*
2  * Copyright (c) 2016, 2018, 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  */
25 
26 package jdk.jfr;
27 
28 import java.lang.annotation.Annotation;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Set;
38 import java.util.StringJoiner;
39 
40 import jdk.jfr.internal.Type;
41 import jdk.jfr.internal.TypeLibrary;
42 import jdk.jfr.internal.Utils;
43 
44 /**
45  * Describes event metadata, such as labels, descriptions and units.
46  * <p>
47  * The following example shows how {@code AnnotationElement} can be used to dynamically define events.
48  *
49  * <pre>
50  * <code>
51  *   List{@literal <}AnnotationElement{@literal >} typeAnnotations = new ArrayList{@literal <}{@literal >}();
52  *   typeannotations.add(new AnnotationElement(Name.class, "com.example.HelloWorld");
53  *   typeAnnotations.add(new AnnotationElement(Label.class, "Hello World"));
54  *   typeAnnotations.add(new AnnotationElement(Description.class, "Helps programmer getting started"));
55  *
56  *   List{@literal <}AnnotationElement{@literal >} fieldAnnotations = new ArrayList{@literal <}{@literal >}();
57  *   fieldAnnotations.add(new AnnotationElement(Label.class, "Message"));
58  *
59  *   List{@literal <}ValueDescriptor{@literal >} fields = new ArrayList{@literal <}{@literal >}();
60  *   fields.add(new ValueDescriptor(String.class, "message", fieldAnnotations));
61  *
62  *   EventFactory f = EventFactory.create(typeAnnotations, fields);
63  *   Event event = f.newEvent();
64  *   event.commit();
65  * </code>
66  * </pre>
67  *
68  * @since 9
69  */
70 public final class AnnotationElement {
71     private final Type type;
72     private final List<Object> annotationValues;
73     private final List<String> annotationNames;
74     private final boolean inBootClassLoader;
75 
76     // package private
AnnotationElement(Type type, List<Object> objects, boolean boot)77     AnnotationElement(Type type, List<Object> objects, boolean boot) {
78         Objects.requireNonNull(type);
79         Objects.requireNonNull(objects);
80         this.type = type;
81         if (objects.size() != type.getFields().size()) {
82             StringJoiner descriptors = new StringJoiner(",", "[", "]");
83             for (ValueDescriptor v : type.getFields()) {
84                 descriptors.add(v.getName());
85             }
86             StringJoiner values = new StringJoiner(",", "[", "]");
87             for (Object object : objects) {
88                 descriptors.add(String.valueOf(object));
89             }
90             throw new IllegalArgumentException("Annotation " + descriptors + " for " + type.getName() + " doesn't match number of values " + values);
91         }
92 
93         List<String> n = new ArrayList<>();
94         List<Object> v = new ArrayList<>();
95         int index = 0;
96         for (ValueDescriptor valueDescriptor : type.getFields()) {
97             Object object = objects.get(index);
98             if (object == null) {
99                 throw new IllegalArgumentException("Annotation value can't be null");
100             }
101             Class<?> valueType = object.getClass();
102             if (valueDescriptor.isArray()) {
103                 valueType = valueType.getComponentType();
104             }
105             checkType(Utils.unboxType(valueType));
106             n.add(valueDescriptor.getName());
107             v.add(object);
108             index++;
109         }
110         this.annotationValues = Utils.smallUnmodifiable(v);
111         this.annotationNames = Utils.smallUnmodifiable(n);
112         this.inBootClassLoader = boot;
113     }
114 
115     /**
116      * Creates an annotation element to use for dynamically defined events.
117      * <p>
118      * Supported value types are {@code byte}, {@code int}, {@code short},
119      * {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char},
120      * and {@code String}. Enums, arrays and classes, are not supported.
121      * <p>
122      * If {@code annotationType} has annotations (directly present, indirectly
123      * present, or associated), then those annotation are recursively included.
124      * However, both the {@code annotationType} and any annotation found recursively
125      * must have the {@link MetadataDefinition} annotation.
126      * <p>
127      * To statically define events, see {@link Event} class.
128      *
129      * @param annotationType interface extending
130      *        {@code java.lang.annotation.Annotation}, not {@code null}
131      * @param values a {@code Map} with keys that match method names of the specified
132      *        annotation interface
133      * @throws IllegalArgumentException if value/key is {@code null}, an unsupported
134      *         value type is used, or a value/key is used that doesn't match the
135      *         signatures in the {@code annotationType}
136      */
AnnotationElement(Class<? extends Annotation> annotationType, Map<String, Object> values)137     public AnnotationElement(Class<? extends Annotation> annotationType, Map<String, Object> values) {
138         Objects.requireNonNull(annotationType);
139         Objects.requireNonNull(values);
140         Utils.checkRegisterPermission();
141         // copy values to avoid modification after validation
142         HashMap<String, Object> map = new HashMap<>(values);
143         for (Map.Entry<String, Object> entry : map.entrySet()) {
144             if (entry.getKey() == null) {
145                 throw new NullPointerException("Name of annotation method can't be null");
146             }
147             if (entry.getValue() == null) {
148                 throw new NullPointerException("Return value for annotation method can't be null");
149             }
150         }
151 
152         if (AnnotationElement.class.isAssignableFrom(annotationType) && annotationType.isInterface()) {
153             throw new IllegalArgumentException("Must be interface extending " + Annotation.class.getName());
154         }
155         if (!isKnownJFRAnnotation(annotationType) && annotationType.getAnnotation(MetadataDefinition.class) == null) {
156             throw new IllegalArgumentException("Annotation class must be annotated with jdk.jfr.MetadataDefinition to be valid");
157         }
158         if (isKnownJFRAnnotation(annotationType)) {
159             this.type = new Type(annotationType.getCanonicalName(), Type.SUPER_TYPE_ANNOTATION, Type.getTypeId(annotationType));
160         } else {
161             this.type = TypeLibrary.createAnnotationType(annotationType);
162         }
163         Method[] methods = annotationType.getDeclaredMethods();
164         if (methods.length != map.size()) {
165             throw new IllegalArgumentException("Number of declared methods must match size of value map");
166         }
167         List<String> n = new ArrayList<>();
168         List<Object> v = new ArrayList<>();
169         Set<String> nameSet = new HashSet<>();
170         for (Method method : methods) {
171             String fieldName = method.getName();
172             Object object = map.get(fieldName);
173             if (object == null) {
174                 throw new IllegalArgumentException("No method in annotation interface " + annotationType.getName() + " matching name " + fieldName);
175             }
176             Class<?> fieldType = object.getClass();
177 
178             if (fieldType == Class.class) {
179                 throw new IllegalArgumentException("Annotation value for " + fieldName + " can't be class");
180             }
181             if (object instanceof Enum) {
182                 throw new IllegalArgumentException("Annotation value for " + fieldName + " can't be enum");
183             }
184             if (!fieldType.equals(object.getClass())) {
185                 throw new IllegalArgumentException("Return type of annotation " + fieldType.getName() + " must match type of object" + object.getClass());
186             }
187 
188             if (fieldType.isArray()) {
189                 Class<?> componentType = fieldType.getComponentType();
190                 checkType(componentType);
191                 if (componentType.equals(String.class)) {
192                     String[] stringArray = (String[]) object;
193                     for (int i = 0; i < stringArray.length; i++) {
194                         if (stringArray[i] == null) {
195                             throw new IllegalArgumentException("Annotation value for " + fieldName + " contains null");
196                         }
197                     }
198                 }
199             } else {
200                 fieldType = Utils.unboxType(object.getClass());
201                 checkType(fieldType);
202             }
203             if (nameSet.contains(fieldName)) {
204                 throw new IllegalArgumentException("Value with name '" + fieldName + "' already exists");
205             }
206             if (isKnownJFRAnnotation(annotationType)) {
207                 ValueDescriptor vd = new ValueDescriptor(fieldType, fieldName, Collections.emptyList(), true);
208                 type.add(vd);
209             }
210             n.add(fieldName);
211             v.add(object);
212         }
213         this.annotationValues = Utils.smallUnmodifiable(v);
214         this.annotationNames = Utils.smallUnmodifiable(n);
215         this.inBootClassLoader = annotationType.getClassLoader() == null;
216     }
217 
218     /**
219      * Creates an annotation element to use for dynamically defined events.
220      * <p>
221      * Supported value types are {@code byte}, {@code int}, {@code short},
222      * {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char},
223      * and {@code String}. Enums, arrays, and classes are not supported.
224      * <p>
225      * If {@code annotationType} has annotations (directly present, indirectly
226      * present, or associated), then those annotations are recursively included.
227      * However, both {@code annotationType} and any annotation found recursively
228      * must have the {@link MetadataDefinition} annotation.
229      * <p>
230      * To statically define events, see {@link Event} class.
231      *
232      * @param annotationType interface extending
233      *        {@code java.lang.annotation.Annotation,} not {@code null}
234      * @param value the value that matches the {@code value} method of the specified
235      *        {@code annotationType}
236      * @throws IllegalArgumentException if value/key is {@code null}, an unsupported
237      *         value type is used, or a value/key is used that doesn't match the
238      *         signatures in the {@code annotationType}
239      */
AnnotationElement(Class<? extends Annotation> annotationType, Object value)240     public AnnotationElement(Class<? extends Annotation> annotationType, Object value) {
241         this(annotationType, Collections.singletonMap("value", Objects.requireNonNull(value)));
242     }
243 
244     /**
245      * Creates an annotation element to use for dynamically defined events.
246      * <p>
247      * Supported value types are {@code byte}, {@code short}, {@code int},
248      * {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char},
249      * and {@code String}. Enums, arrays, and classes are not supported.
250      * <p>
251      * If {@code annotationType} has annotations (directly present, indirectly
252      * present or associated), then those annotation are recursively included.
253      * However, both {@code annotationType} and any annotation found recursively
254      * must have the {@link MetadataDefinition} annotation.
255      * <p>
256      * To statically define events, see {@link Event} class.
257      *
258      * @param annotationType interface extending java.lang.annotation.Annotation,
259      *        not {@code null}
260      */
AnnotationElement(Class<? extends Annotation> annotationType)261     public AnnotationElement(Class<? extends Annotation> annotationType) {
262         this(annotationType, Collections.emptyMap());
263     }
264 
265     /**
266      * Returns an immutable list of annotation values in an order that matches the
267      * value descriptors for this {@code AnnotationElement}.
268      *
269      * @return list of values, not {@code null}
270      */
getValues()271     public List<Object> getValues() {
272         return annotationValues;
273     }
274 
275     /**
276      * Returns an immutable list of descriptors that describes the annotation values
277      * for this {@code AnnotationElement}.
278      *
279      * @return the list of value descriptors for this {@code Annotation}, not
280      *         {@code null}
281      */
getValueDescriptors()282     public List<ValueDescriptor> getValueDescriptors() {
283         return Collections.unmodifiableList(type.getFields());
284     }
285 
286     /**
287      * Returns an immutable list of annotation elements for this
288      * {@code AnnotationElement}.
289      *
290      * @return a list of meta annotation, not {@code null}
291      */
getAnnotationElements()292     public List<AnnotationElement> getAnnotationElements() {
293         return type.getAnnotationElements();
294     }
295 
296     /**
297      * Returns the fully qualified name of the annotation type that corresponds to
298      * this {@code AnnotationElement} (for example, {@code "jdk.jfr.Label"}).
299      *
300      * @return type name, not {@code null}
301      */
getTypeName()302     public String getTypeName() {
303         return type.getName();
304     }
305 
306     /**
307      * Returns a value for this {@code AnnotationElement}.
308      *
309      * @param name the name of the method in the annotation interface, not
310      *        {@code null}.
311      *
312      * @return the annotation value, not {@code null}.
313      *
314      * @throws IllegalArgumentException if a method with the specified name does
315      *         not exist in the annotation
316      */
getValue(String name)317     public Object getValue(String name) {
318         Objects.requireNonNull(name);
319         int index = 0;
320         for (String n : annotationNames) {
321             if (name.equals(n)) {
322                 return annotationValues.get(index);
323             }
324             index++;
325         }
326         StringJoiner valueNames = new StringJoiner(",", "[", "]");
327         for (ValueDescriptor v : type.getFields()) {
328             valueNames.add(v.getName());
329         }
330         throw new IllegalArgumentException("No value with name '" + name + "'. Valid names are " + valueNames);
331     }
332 
333     /**
334      * Returns {@code true} if an annotation value with the specified name exists in
335      * this {@code AnnotationElement}.
336      *
337      * @param name name of the method in the annotation interface to find, not
338      *        {@code null}
339      *
340      * @return {@code true} if method exists, {@code false} otherwise
341      */
hasValue(String name)342     public boolean hasValue(String name) {
343         Objects.requireNonNull(name);
344         for (String n : annotationNames) {
345             if (name.equals(n)) {
346                 return true;
347             }
348         }
349         return false;
350     }
351 
352     /**
353      * Returns the first annotation for the specified type if an
354      * {@code AnnotationElement} with the same name exists, else {@code null}.
355      *
356      * @param <A> the type of the annotation to query for and return if it exists
357      * @param annotationType the {@code Class object} corresponding to the annotation type,
358      *        not {@code null}
359      * @return this element's annotation for the specified annotation type if
360      *         it it exists, else {@code null}
361      */
getAnnotation(Class<? extends Annotation> annotationType)362     public final <A> A getAnnotation(Class<? extends Annotation> annotationType) {
363         Objects.requireNonNull(annotationType);
364         return type.getAnnotation(annotationType);
365     }
366 
367     /**
368      * Returns the type ID for this {@code AnnotationElement}.
369      * <p>
370      * The ID is a unique identifier for the type in the Java Virtual Machine (JVM). The ID might not
371      * be the same between JVM instances.
372      *
373      * @return the type ID, not negative
374      */
getTypeId()375     public long getTypeId() {
376         return type.getId();
377     }
378 
379     // package private
getType()380     Type getType() {
381         return type;
382     }
383 
checkType(Class<?> type)384     private static void checkType(Class<?> type) {
385         if (type.isPrimitive()) {
386             return;
387         }
388         if (type == String.class) {
389             return;
390         }
391         throw new IllegalArgumentException("Only primitives types or java.lang.String are allowed");
392     }
393 
394     // Whitelist of annotation classes that are allowed, even though
395     // they don't have @MetadataDefinition.
isKnownJFRAnnotation(Class<? extends Annotation> annotationType)396     private static boolean isKnownJFRAnnotation(Class<? extends Annotation> annotationType) {
397         if (annotationType == Registered.class) {
398             return true;
399         }
400         if (annotationType == Threshold.class) {
401             return true;
402         }
403         if (annotationType == StackTrace.class) {
404             return true;
405         }
406         if (annotationType == Period.class) {
407             return true;
408         }
409         if (annotationType == Enabled.class) {
410             return true;
411         }
412         return false;
413     }
414 
415     // package private
isInBoot()416     boolean isInBoot() {
417         return inBootClassLoader;
418     }
419 
420 }
421