| /* | |
| * Copyright (c) 2016, 2025, 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.invoke.MethodHandle; | |
| import java.lang.invoke.MethodHandles; | |
| import java.util.ArrayList; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Objects; | |
| import java.util.Set; | |
| import jdk.internal.module.Checks; | |
| import jdk.jfr.internal.EventClassBuilder; | |
| import jdk.jfr.internal.JVMSupport; | |
| import jdk.jfr.internal.MetadataRepository; | |
| import jdk.jfr.internal.Type; | |
| import jdk.jfr.internal.util.Utils; | |
| /** | |
| * Class for defining an event at runtime. | |
| * <p> | |
| * It's highly recommended that the event is defined at compile time, if the | |
| * field layout is known, so the Java Virtual Machine (JVM) can optimize the | |
| * code, possibly remove all instrumentation if Flight Recorder is inactive or | |
| * if the enabled setting for this event is set to {@code false}. | |
| * <p> | |
| * To define an event at compile time, see {@link Event}. | |
| * <p> | |
| * The following example shows how to implement a dynamic {@code Event} class. | |
| * | |
| * {@snippet class="Snippets" region="EventFactoryOverview"} | |
| * | |
| * @since 9 | |
| */ | |
| public final class EventFactory { | |
| private static final long REGISTERED_ID = Type.getTypeId(Registered.class); | |
| private final Class<? extends Event> eventClass; | |
| private final MethodHandle constructorHandle; | |
| private final List<AnnotationElement> sanitizedAnnotation; | |
| private final List<ValueDescriptor> sanitizedFields; | |
| private EventFactory(Class<? extends Event> eventClass, List<AnnotationElement> sanitizedAnnotation, List<ValueDescriptor> sanitizedFields) throws IllegalAccessException, NoSuchMethodException, SecurityException { | |
| this.constructorHandle = MethodHandles.lookup().unreflectConstructor(eventClass.getConstructor()); | |
| this.eventClass = eventClass; | |
| this.sanitizedAnnotation = sanitizedAnnotation; | |
| this.sanitizedFields = sanitizedFields; | |
| } | |
| /** | |
| * Creates an {@code EventFactory} object. | |
| * <p> | |
| * The order of the value descriptors specifies the index to use when setting | |
| * event values. | |
| * | |
| * @param annotationElements list of annotation elements that describes the | |
| * annotations on the event, not {@code null} | |
| * | |
| * @param fields list of descriptors that describes the fields of the event, not | |
| * {@code null} | |
| * | |
| * @return event factory, not {@code null} | |
| * | |
| * @throws IllegalArgumentException if the input is not valid. For example, | |
| * input might not be valid if the field type or name is not valid in | |
| * the Java language or an annotation element references a type that | |
| * can't be found. | |
| * | |
| * @see Event#set(int, Object) | |
| */ | |
| public static EventFactory create(List<AnnotationElement> annotationElements, List<ValueDescriptor> fields) { | |
| Objects.requireNonNull(annotationElements, "annotationElements"); | |
| Objects.requireNonNull(fields, "fields"); | |
| JVMSupport.ensureWithInternalError(); | |
| List<AnnotationElement> sanitizedAnnotation = Utils.sanitizeNullFreeList(annotationElements, AnnotationElement.class); | |
| List<ValueDescriptor> sanitizedFields = Utils.sanitizeNullFreeList(fields, ValueDescriptor.class); | |
| Set<String> nameSet = HashSet.newHashSet(sanitizedFields.size()); | |
| for (ValueDescriptor v : sanitizedFields) { | |
| String name = v.getName(); | |
| if (v.isArray()) { | |
| throw new IllegalArgumentException("Array types are not allowed for fields"); | |
| } | |
| if (!Type.isValidJavaFieldType(v.getTypeName())) { | |
| throw new IllegalArgumentException(v.getTypeName() + " is not a valid type for an event field"); | |
| } | |
| if (!Checks.isJavaIdentifier(v.getName())) { | |
| throw new IllegalArgumentException(name + " is not a valid name for an event field"); | |
| } | |
| if (nameSet.contains(name)) { | |
| throw new IllegalArgumentException("Name of fields must be unique. Found two instances of " + name); | |
| } | |
| nameSet.add(name); | |
| } | |
| // Prevent event from being registered in <clinit> | |
| // and only use annotations that can be resolved (those in boot class loader) | |
| boolean needRegister = true; | |
| List<AnnotationElement> bootAnnotations = new ArrayList<>(); | |
| for (AnnotationElement ae : sanitizedAnnotation) { | |
| long id = ae.getTypeId(); | |
| if (ae.isInBoot()) { | |
| if (id == REGISTERED_ID) { | |
| if (Boolean.FALSE.equals(ae.getValue("value"))) { | |
| needRegister = false; | |
| } | |
| } else { | |
| bootAnnotations.add(ae); | |
| } | |
| } | |
| } | |
| bootAnnotations.add(new AnnotationElement(Registered.class, false)); | |
| EventClassBuilder ecb = new EventClassBuilder(bootAnnotations, sanitizedFields); | |
| Class<? extends Event> eventClass = ecb.build(); | |
| if (needRegister) { | |
| MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields); | |
| } | |
| try { | |
| return new EventFactory(eventClass, sanitizedAnnotation, sanitizedFields); | |
| } catch (IllegalAccessException e) { | |
| throw new IllegalAccessError("Could not access constructor of generated event class, " + e.getMessage()); | |
| } catch (NoSuchMethodException e) { | |
| throw new InternalError("Could not find constructor in generated event class, " + e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Instantiates an event, so it can be populated with data and written to the | |
| * Flight Recorder system. | |
| * <p> | |
| * Use the {@link Event#set(int, Object)} method to set a value. | |
| * | |
| * @return an event instance, not {@code null} | |
| */ | |
| public Event newEvent() { | |
| try { | |
| return (Event) constructorHandle.invoke(); | |
| } catch (Throwable e) { | |
| throw new InstantiationError("Could not instantiate dynamically generated event class " + eventClass.getName() + ". " + e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Returns the event type that is associated with this event factory. | |
| * | |
| * @return event type that is associated with this event factory, not | |
| * {@code null} | |
| * | |
| * @throws java.lang.IllegalStateException if the event factory is created with | |
| * the {@code Registered(false)} annotation and the event class is not | |
| * manually registered before the invocation of this method | |
| */ | |
| public EventType getEventType() { | |
| return EventType.getEventType(eventClass); | |
| } | |
| /** | |
| * Registers an unregistered event. | |
| * <p> | |
| * By default, the event class associated with this event factory is registered | |
| * when the event factory is created, unless the event has the | |
| * {@link Registered} annotation. | |
| * <p> | |
| * A registered event class can write data to Flight Recorder and event metadata | |
| * can be obtained by invoking {@link FlightRecorder#getEventTypes()}. | |
| * <p> | |
| * If the event class associated with this event factory is already registered, | |
| * the call to this method is ignored. | |
| * | |
| * @see Registered | |
| * @see FlightRecorder#register(Class) | |
| */ | |
| public void register() { | |
| MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields); | |
| } | |
| /** | |
| * Unregisters the event that is associated with this event factory. | |
| * <p> | |
| * A unregistered event class can't write data to Flight Recorder and event | |
| * metadata can't be obtained by invoking | |
| * {@link FlightRecorder#getEventTypes()}. | |
| * <p> | |
| * If the event class associated with this event factory is not already | |
| * registered, the call to this method is ignored. | |
| * | |
| * @see Registered | |
| * @see FlightRecorder#unregister(Class) | |
| */ | |
| public void unregister() { | |
| MetadataRepository.getInstance().unregister(eventClass); | |
| } | |
| } |