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 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