1 /*
   2  * Copyright (c) 2016, 2019, 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 8
  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
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 375     public long getTypeId() {
 376         return type.getId();
 377     }
 378 
 379     // package private
 380     Type getType() {
 381         return type;
 382     }
 383 
 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.
 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
 416     boolean isInBoot() {
 417         return inBootClassLoader;
 418     }
 419 
 420 }