/*
 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.jfr;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;

import jdk.jfr.internal.Type;
import jdk.jfr.internal.TypeLibrary;
import jdk.jfr.internal.Utils;

Describes event metadata, such as labels, descriptions and units.

The following example shows how AnnotationElement can be used to dynamically define events.

 List<AnnotationElement> typeAnnotations = new ArrayList<>(); typeannotations.add(new AnnotationElement(Name.class, "com.example.HelloWorld"); typeAnnotations.add(new AnnotationElement(Label.class, "Hello World")); typeAnnotations.add(new AnnotationElement(Description.class, "Helps programmer getting started")); List<AnnotationElement> fieldAnnotations = new ArrayList<>(); fieldAnnotations.add(new AnnotationElement(Label.class, "Message")); List<ValueDescriptor> fields = new ArrayList<>(); fields.add(new ValueDescriptor(String.class, "message", fieldAnnotations)); EventFactory f = EventFactory.create(typeAnnotations, fields); Event event = f.newEvent(); event.commit(); 
Since:9
/** * Describes event metadata, such as labels, descriptions and units. * <p> * The following example shows how {@code AnnotationElement} can be used to dynamically define events. * * <pre> * <code> * List{@literal <}AnnotationElement{@literal >} typeAnnotations = new ArrayList{@literal <}{@literal >}(); * typeannotations.add(new AnnotationElement(Name.class, "com.example.HelloWorld"); * typeAnnotations.add(new AnnotationElement(Label.class, "Hello World")); * typeAnnotations.add(new AnnotationElement(Description.class, "Helps programmer getting started")); * * List{@literal <}AnnotationElement{@literal >} fieldAnnotations = new ArrayList{@literal <}{@literal >}(); * fieldAnnotations.add(new AnnotationElement(Label.class, "Message")); * * List{@literal <}ValueDescriptor{@literal >} fields = new ArrayList{@literal <}{@literal >}(); * fields.add(new ValueDescriptor(String.class, "message", fieldAnnotations)); * * EventFactory f = EventFactory.create(typeAnnotations, fields); * Event event = f.newEvent(); * event.commit(); * </code> * </pre> * * @since 9 */
public final class AnnotationElement { private final Type type; private final List<Object> annotationValues; private final List<String> annotationNames; private final boolean inBootClassLoader; // package private AnnotationElement(Type type, List<Object> objects, boolean boot) { Objects.requireNonNull(type); Objects.requireNonNull(objects); this.type = type; if (objects.size() != type.getFields().size()) { StringJoiner descriptors = new StringJoiner(",", "[", "]"); for (ValueDescriptor v : type.getFields()) { descriptors.add(v.getName()); } StringJoiner values = new StringJoiner(",", "[", "]"); for (Object object : objects) { descriptors.add(String.valueOf(object)); } throw new IllegalArgumentException("Annotation " + descriptors + " for " + type.getName() + " doesn't match number of values " + values); } List<String> n = new ArrayList<>(); List<Object> v = new ArrayList<>(); int index = 0; for (ValueDescriptor valueDescriptor : type.getFields()) { Object object = objects.get(index); if (object == null) { throw new IllegalArgumentException("Annotation value can't be null"); } Class<?> valueType = object.getClass(); if (valueDescriptor.isArray()) { valueType = valueType.getComponentType(); } checkType(Utils.unboxType(valueType)); n.add(valueDescriptor.getName()); v.add(object); index++; } this.annotationValues = Utils.smallUnmodifiable(v); this.annotationNames = Utils.smallUnmodifiable(n); this.inBootClassLoader = boot; }
Creates an annotation element to use for dynamically defined events.

Supported value types are byte, int, short, long, double, float, boolean, char, and String. Enums, arrays and classes, are not supported.

If annotationType has annotations (directly present, indirectly present, or associated), then those annotation are recursively included. However, both the annotationType and any annotation found recursively must have the MetadataDefinition annotation.

To statically define events, see Event class.

Params:
  • annotationType – interface extending java.lang.annotation.Annotation, not null
  • values – a Map with keys that match method names of the specified annotation interface
Throws:
  • IllegalArgumentException – if value/key is null, an unsupported value type is used, or a value/key is used that doesn't match the signatures in the annotationType
/** * Creates an annotation element to use for dynamically defined events. * <p> * Supported value types are {@code byte}, {@code int}, {@code short}, * {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char}, * and {@code String}. Enums, arrays and classes, are not supported. * <p> * If {@code annotationType} has annotations (directly present, indirectly * present, or associated), then those annotation are recursively included. * However, both the {@code annotationType} and any annotation found recursively * must have the {@link MetadataDefinition} annotation. * <p> * To statically define events, see {@link Event} class. * * @param annotationType interface extending * {@code java.lang.annotation.Annotation}, not {@code null} * @param values a {@code Map} with keys that match method names of the specified * annotation interface * @throws IllegalArgumentException if value/key is {@code null}, an unsupported * value type is used, or a value/key is used that doesn't match the * signatures in the {@code annotationType} */
public AnnotationElement(Class<? extends Annotation> annotationType, Map<String, Object> values) { Objects.requireNonNull(annotationType); Objects.requireNonNull(values); Utils.checkRegisterPermission(); // copy values to avoid modification after validation HashMap<String, Object> map = new HashMap<>(values); for (Map.Entry<String, Object> entry : map.entrySet()) { if (entry.getKey() == null) { throw new NullPointerException("Name of annotation method can't be null"); } if (entry.getValue() == null) { throw new NullPointerException("Return value for annotation method can't be null"); } } if (AnnotationElement.class.isAssignableFrom(annotationType) && annotationType.isInterface()) { throw new IllegalArgumentException("Must be interface extending " + Annotation.class.getName()); } if (!isKnownJFRAnnotation(annotationType) && annotationType.getAnnotation(MetadataDefinition.class) == null) { throw new IllegalArgumentException("Annotation class must be annotated with jdk.jfr.MetadataDefinition to be valid"); } if (isKnownJFRAnnotation(annotationType)) { this.type = new Type(annotationType.getCanonicalName(), Type.SUPER_TYPE_ANNOTATION, Type.getTypeId(annotationType)); } else { this.type = TypeLibrary.createAnnotationType(annotationType); } Method[] methods = annotationType.getDeclaredMethods(); if (methods.length != map.size()) { throw new IllegalArgumentException("Number of declared methods must match size of value map"); } List<String> n = new ArrayList<>(); List<Object> v = new ArrayList<>(); Set<String> nameSet = new HashSet<>(); for (Method method : methods) { String fieldName = method.getName(); Object object = map.get(fieldName); if (object == null) { throw new IllegalArgumentException("No method in annotation interface " + annotationType.getName() + " matching name " + fieldName); } Class<?> fieldType = object.getClass(); if (fieldType == Class.class) { throw new IllegalArgumentException("Annotation value for " + fieldName + " can't be class"); } if (object instanceof Enum) { throw new IllegalArgumentException("Annotation value for " + fieldName + " can't be enum"); } if (!fieldType.equals(object.getClass())) { throw new IllegalArgumentException("Return type of annotation " + fieldType.getName() + " must match type of object" + object.getClass()); } if (fieldType.isArray()) { Class<?> componentType = fieldType.getComponentType(); checkType(componentType); if (componentType.equals(String.class)) { String[] stringArray = (String[]) object; for (int i = 0; i < stringArray.length; i++) { if (stringArray[i] == null) { throw new IllegalArgumentException("Annotation value for " + fieldName + " contains null"); } } } } else { fieldType = Utils.unboxType(object.getClass()); checkType(fieldType); } if (nameSet.contains(fieldName)) { throw new IllegalArgumentException("Value with name '" + fieldName + "' already exists"); } if (isKnownJFRAnnotation(annotationType)) { ValueDescriptor vd = new ValueDescriptor(fieldType, fieldName, Collections.emptyList(), true); type.add(vd); } n.add(fieldName); v.add(object); } this.annotationValues = Utils.smallUnmodifiable(v); this.annotationNames = Utils.smallUnmodifiable(n); this.inBootClassLoader = annotationType.getClassLoader() == null; }
Creates an annotation element to use for dynamically defined events.

Supported value types are byte, int, short, long, double, float, boolean, char, and String. Enums, arrays, and classes are not supported.

If annotationType has annotations (directly present, indirectly present, or associated), then those annotations are recursively included. However, both annotationType and any annotation found recursively must have the MetadataDefinition annotation.

To statically define events, see Event class.

Params:
  • annotationType – interface extending java.lang.annotation.Annotation, not null
  • value – the value that matches the value method of the specified annotationType
Throws:
  • IllegalArgumentException – if value/key is null, an unsupported value type is used, or a value/key is used that doesn't match the signatures in the annotationType
/** * Creates an annotation element to use for dynamically defined events. * <p> * Supported value types are {@code byte}, {@code int}, {@code short}, * {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char}, * and {@code String}. Enums, arrays, and classes are not supported. * <p> * If {@code annotationType} has annotations (directly present, indirectly * present, or associated), then those annotations are recursively included. * However, both {@code annotationType} and any annotation found recursively * must have the {@link MetadataDefinition} annotation. * <p> * To statically define events, see {@link Event} class. * * @param annotationType interface extending * {@code java.lang.annotation.Annotation,} not {@code null} * @param value the value that matches the {@code value} method of the specified * {@code annotationType} * @throws IllegalArgumentException if value/key is {@code null}, an unsupported * value type is used, or a value/key is used that doesn't match the * signatures in the {@code annotationType} */
public AnnotationElement(Class<? extends Annotation> annotationType, Object value) { this(annotationType, Collections.singletonMap("value", Objects.requireNonNull(value))); }
Creates an annotation element to use for dynamically defined events.

Supported value types are byte, short, int, long, double, float, boolean, char, and String. Enums, arrays, and classes are not supported.

If annotationType has annotations (directly present, indirectly present or associated), then those annotation are recursively included. However, both annotationType and any annotation found recursively must have the MetadataDefinition annotation.

To statically define events, see Event class.

Params:
  • annotationType – interface extending java.lang.annotation.Annotation, not null
/** * Creates an annotation element to use for dynamically defined events. * <p> * Supported value types are {@code byte}, {@code short}, {@code int}, * {@code long}, {@code double}, {@code float}, {@code boolean}, {@code char}, * and {@code String}. Enums, arrays, and classes are not supported. * <p> * If {@code annotationType} has annotations (directly present, indirectly * present or associated), then those annotation are recursively included. * However, both {@code annotationType} and any annotation found recursively * must have the {@link MetadataDefinition} annotation. * <p> * To statically define events, see {@link Event} class. * * @param annotationType interface extending java.lang.annotation.Annotation, * not {@code null} */
public AnnotationElement(Class<? extends Annotation> annotationType) { this(annotationType, Collections.emptyMap()); }
Returns an immutable list of annotation values in an order that matches the value descriptors for this AnnotationElement.
Returns:list of values, not null
/** * Returns an immutable list of annotation values in an order that matches the * value descriptors for this {@code AnnotationElement}. * * @return list of values, not {@code null} */
public List<Object> getValues() { return annotationValues; }
Returns an immutable list of descriptors that describes the annotation values for this AnnotationElement.
Returns:the list of value descriptors for this Annotation, not null
/** * Returns an immutable list of descriptors that describes the annotation values * for this {@code AnnotationElement}. * * @return the list of value descriptors for this {@code Annotation}, not * {@code null} */
public List<ValueDescriptor> getValueDescriptors() { return Collections.unmodifiableList(type.getFields()); }
Returns an immutable list of annotation elements for this AnnotationElement.
Returns:a list of meta annotation, not null
/** * Returns an immutable list of annotation elements for this * {@code AnnotationElement}. * * @return a list of meta annotation, not {@code null} */
public List<AnnotationElement> getAnnotationElements() { return type.getAnnotationElements(); }
Returns the fully qualified name of the annotation type that corresponds to this AnnotationElement (for example, "jdk.jfr.Label").
Returns:type name, not null
/** * Returns the fully qualified name of the annotation type that corresponds to * this {@code AnnotationElement} (for example, {@code "jdk.jfr.Label"}). * * @return type name, not {@code null} */
public String getTypeName() { return type.getName(); }
Returns a value for this AnnotationElement.
Params:
  • name – the name of the method in the annotation interface, not null.
Throws:
Returns:the annotation value, not null.
/** * Returns a value for this {@code AnnotationElement}. * * @param name the name of the method in the annotation interface, not * {@code null}. * * @return the annotation value, not {@code null}. * * @throws IllegalArgumentException if a method with the specified name does * not exist in the annotation */
public Object getValue(String name) { Objects.requireNonNull(name); int index = 0; for (String n : annotationNames) { if (name.equals(n)) { return annotationValues.get(index); } index++; } StringJoiner valueNames = new StringJoiner(",", "[", "]"); for (ValueDescriptor v : type.getFields()) { valueNames.add(v.getName()); } throw new IllegalArgumentException("No value with name '" + name + "'. Valid names are " + valueNames); }
Returns true if an annotation value with the specified name exists in this AnnotationElement.
Params:
  • name – name of the method in the annotation interface to find, not null
Returns:true if method exists, false otherwise
/** * Returns {@code true} if an annotation value with the specified name exists in * this {@code AnnotationElement}. * * @param name name of the method in the annotation interface to find, not * {@code null} * * @return {@code true} if method exists, {@code false} otherwise */
public boolean hasValue(String name) { Objects.requireNonNull(name); for (String n : annotationNames) { if (name.equals(n)) { return true; } } return false; }
Returns the first annotation for the specified type if an AnnotationElement with the same name exists, else null.
Params:
  • annotationType – the Class object corresponding to the annotation type, not null
Type parameters:
  • <A> – the type of the annotation to query for and return if it exists
Returns:this element's annotation for the specified annotation type if it it exists, else null
/** * Returns the first annotation for the specified type if an * {@code AnnotationElement} with the same name exists, else {@code null}. * * @param <A> the type of the annotation to query for and return if it exists * @param annotationType the {@code Class object} corresponding to the annotation type, * not {@code null} * @return this element's annotation for the specified annotation type if * it it exists, else {@code null} */
public final <A> A getAnnotation(Class<? extends Annotation> annotationType) { Objects.requireNonNull(annotationType); return type.getAnnotation(annotationType); }
Returns the type ID for this AnnotationElement.

The ID is a unique identifier for the type in the Java Virtual Machine (JVM). The ID might not be the same between JVM instances.

Returns:the type ID, not negative
/** * Returns the type ID for this {@code AnnotationElement}. * <p> * The ID is a unique identifier for the type in the Java Virtual Machine (JVM). The ID might not * be the same between JVM instances. * * @return the type ID, not negative */
public long getTypeId() { return type.getId(); } // package private Type getType() { return type; } private static void checkType(Class<?> type) { if (type.isPrimitive()) { return; } if (type == String.class) { return; } throw new IllegalArgumentException("Only primitives types or java.lang.String are allowed"); } // Whitelist of annotation classes that are allowed, even though // they don't have @MetadataDefinition. private static boolean isKnownJFRAnnotation(Class<? extends Annotation> annotationType) { if (annotationType == Registered.class) { return true; } if (annotationType == Threshold.class) { return true; } if (annotationType == StackTrace.class) { return true; } if (annotationType == Period.class) { return true; } if (annotationType == Enabled.class) { return true; } return false; } // package private boolean isInBoot() { return inBootClassLoader; } }