/*
 * Copyright (c) 2016, 2019, 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.internal.consumer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import jdk.jfr.EventType;
import jdk.jfr.ValueDescriptor;
import jdk.jfr.internal.LongMap;
import jdk.jfr.internal.MetadataDescriptor;
import jdk.jfr.internal.PrivateAccess;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.consumer.Parser;
import jdk.jfr.internal.consumer.RecordingInput;

Class that create parsers suitable for reading events and constant pools
/** * Class that create parsers suitable for reading events and constant pools */
final class ParserFactory { private final LongMap<Parser> parsers = new LongMap<>(); private final TimeConverter timeConverter; private final LongMap<Type> types = new LongMap<>(); private final LongMap<ConstantLookup> constantLookups; public ParserFactory(MetadataDescriptor metadata, LongMap<ConstantLookup> constantLookups, TimeConverter timeConverter) throws IOException { this.constantLookups = constantLookups; this.timeConverter = timeConverter; for (Type t : metadata.getTypes()) { types.put(t.getId(), t); } // Add to separate list // so createCompositeParser can throw // IOException outside lambda List<Type> typeList = new ArrayList<>(); types.forEach(typeList::add); for (Type t : typeList) { if (!t.getFields().isEmpty()) { // Avoid primitives CompositeParser cp = createCompositeParser(t, false); if (t.isSimpleType()) { // Reduce to nested parser parsers.put(t.getId(), cp.parsers[0]); } } } // Override event types with event parsers for (EventType t : metadata.getEventTypes()) { parsers.put(t.getId(), createEventParser(t)); } } public LongMap<Parser> getParsers() { return parsers; } public LongMap<Type> getTypeMap() { return types; } private EventParser createEventParser(EventType eventType) throws IOException { List<Parser> parsers = new ArrayList<Parser>(); for (ValueDescriptor f : eventType.getFields()) { parsers.add(createParser(f, true)); } return new EventParser(timeConverter, eventType, parsers.toArray(new Parser[0])); } private Parser createParser(ValueDescriptor v, boolean event) throws IOException { boolean constantPool = PrivateAccess.getInstance().isConstantPool(v); if (v.isArray()) { Type valueType = PrivateAccess.getInstance().getType(v); ValueDescriptor element = PrivateAccess.getInstance().newValueDescriptor(v.getName(), valueType, v.getAnnotationElements(), 0, constantPool, null); return new ArrayParser(createParser(element, event)); } long id = v.getTypeId(); Type type = types.get(id); if (type == null) { throw new IOException("Type '" + v.getTypeName() + "' is not defined"); } if (constantPool) { ConstantLookup lookup = constantLookups.get(id); if (lookup == null) { ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName()); lookup = new ConstantLookup(pool, type); constantLookups.put(id, lookup); } if (event) { return new EventValueConstantParser(lookup); } return new ConstantValueParser(lookup); } Parser parser = parsers.get(id); if (parser == null) { if (!v.getFields().isEmpty()) { return createCompositeParser(type, event); } else { return registerParserType(type, createPrimitiveParser(type, constantPool)); } } return parser; } private Parser createPrimitiveParser(Type type, boolean event) throws IOException { switch (type.getName()) { case "int": return new IntegerParser(); case "long": return new LongParser(); case "float": return new FloatParser(); case "double": return new DoubleParser(); case "char": return new CharacterParser(); case "boolean": return new BooleanParser(); case "short": return new ShortParser(); case "byte": return new ByteParser(); case "java.lang.String": ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName()); ConstantLookup lookup = new ConstantLookup(pool, type); constantLookups.put(type.getId(), lookup); return new StringParser(lookup, event); default: throw new IOException("Unknown primitive type " + type.getName()); } } private Parser registerParserType(Type t, Parser parser) { Parser p = parsers.get(t.getId()); // check if parser exists (known type) if (p != null) { return p; } parsers.put(t.getId(), parser); return parser; } private CompositeParser createCompositeParser(Type type, boolean event) throws IOException { List<ValueDescriptor> vds = type.getFields(); Parser[] parsers = new Parser[vds.size()]; CompositeParser composite = new CompositeParser(parsers); // need to pre-register so recursive types can be handled registerParserType(type, composite); int index = 0; for (ValueDescriptor vd : vds) { parsers[index++] = createParser(vd, event); } return composite; } private static final class BooleanParser extends Parser { @Override public Object parse(RecordingInput input) throws IOException { return input.readBoolean() ? Boolean.TRUE : Boolean.FALSE; } @Override public void skip(RecordingInput input) throws IOException { input.skipBytes(1); } } private static final class ByteParser extends Parser { @Override public Object parse(RecordingInput input) throws IOException { return Byte.valueOf(input.readByte()); } @Override public void skip(RecordingInput input) throws IOException { input.skipBytes(1); } } private static final class LongParser extends Parser { private Object lastLongObject = Long.valueOf(0); private long last = 0; @Override public Object parse(RecordingInput input) throws IOException { long l = input.readLong(); if (l == last) { return lastLongObject; } last = l; lastLongObject = Long.valueOf(l); return lastLongObject; } @Override public void skip(RecordingInput input) throws IOException { input.readLong(); } } private static final class IntegerParser extends Parser { private Integer lastIntegergObject = Integer.valueOf(0); private int last = 0; @Override public Object parse(RecordingInput input) throws IOException { int i = input.readInt(); if (i != last) { last = i; lastIntegergObject = Integer.valueOf(i); } return lastIntegergObject; } @Override public void skip(RecordingInput input) throws IOException { input.readInt(); } } private static final class ShortParser extends Parser { @Override public Object parse(RecordingInput input) throws IOException { return Short.valueOf(input.readShort()); } @Override public void skip(RecordingInput input) throws IOException { input.readShort(); } } private static final class CharacterParser extends Parser { @Override public Object parse(RecordingInput input) throws IOException { return Character.valueOf(input.readChar()); } @Override public void skip(RecordingInput input) throws IOException { input.readChar(); } } private static final class FloatParser extends Parser { @Override public Object parse(RecordingInput input) throws IOException { return Float.valueOf(input.readFloat()); } @Override public void skip(RecordingInput input) throws IOException { input.skipBytes(Float.SIZE); } } private static final class DoubleParser extends Parser { @Override public Object parse(RecordingInput input) throws IOException { return Double.valueOf(input.readDouble()); } @Override public void skip(RecordingInput input) throws IOException { input.skipBytes(Double.SIZE); } } private final static class ArrayParser extends Parser { private final Parser elementParser; public ArrayParser(Parser elementParser) { this.elementParser = elementParser; } @Override public Object parse(RecordingInput input) throws IOException { final int size = input.readInt(); final Object[] array = new Object[size]; for (int i = 0; i < size; i++) { array[i] = elementParser.parse(input); } return array; } @Override public void skip(RecordingInput input) throws IOException { final int size = input.readInt(); for (int i = 0; i < size; i++) { elementParser.skip(input); } } } private final static class CompositeParser extends Parser { private final Parser[] parsers; public CompositeParser(Parser[] valueParsers) { this.parsers = valueParsers; } @Override public Object parse(RecordingInput input) throws IOException { final Object[] values = new Object[parsers.length]; for (int i = 0; i < values.length; i++) { values[i] = parsers[i].parse(input); } return values; } @Override public void skip(RecordingInput input) throws IOException { for (int i = 0; i < parsers.length; i++) { parsers[i].skip(input); } } } private static final class EventValueConstantParser extends Parser { private final ConstantLookup lookup; private Object lastValue = 0; private long lastKey = -1; EventValueConstantParser(ConstantLookup lookup) { this.lookup = lookup; } @Override public Object parse(RecordingInput input) throws IOException { long key = input.readLong(); if (key == lastKey) { return lastValue; } lastKey = key; lastValue = lookup.getCurrentResolved(key); return lastValue; } @Override public void skip(RecordingInput input) throws IOException { input.readLong(); } } private static final class ConstantValueParser extends Parser { private final ConstantLookup lookup; ConstantValueParser(ConstantLookup lookup) { this.lookup = lookup; } @Override public Object parse(RecordingInput input) throws IOException { return lookup.getCurrent(input.readLong()); } @Override public void skip(RecordingInput input) throws IOException { input.readLong(); } } }