package nl.tudelft.simulation.dsol.interpreter.classfile; import java.io.DataInput; import java.io.DataInputStream; import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.util.LinkedHashMap; import java.util.Map; /** * A ClassDescriptor. *

* Copyright (c) 2002-2022 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See * for project information https://simulation.tudelft.nl. The DSOL * project is distributed under a three-clause BSD-style license, which can be found at * * https://simulation.tudelft.nl/dsol/3.0/license.html. *

* @author Peter Jacobs * @author Alexander Verbraeck * @since 1.5 */ public final class ClassDescriptor { /** the repository which caches descriptors. */ private static final Map, ClassDescriptor> CACHE = new LinkedHashMap, ClassDescriptor>(); /** the constantPool. */ private Constant[] constantPool = null; /** the localVariables. */ private Map methods = new LinkedHashMap(); /** the javaClass we are reading. */ private final Class javaClass; /** * returns the classDescriptor of this class. * @param clazz Class<?>; the class the clazz to parse * @return ClassDescriptor the descriptor * @throws IOException on IO exception * @throws ClassNotFoundException if clazz cannot be found */ public static ClassDescriptor get(final Class clazz) throws IOException, ClassNotFoundException { synchronized (CACHE) { if (CACHE.containsKey(clazz)) { return CACHE.get(clazz); } ClassDescriptor classDescriptor = new ClassDescriptor(clazz); CACHE.put(clazz, classDescriptor); return classDescriptor; } } /** * constructs a new ClassDescriptor. * @param javaClass Class<?>; the class to read * @throws IOException on IO - failure * @throws ClassNotFoundException on incomplete classPath */ private ClassDescriptor(final Class javaClass) throws IOException, ClassNotFoundException { super(); this.javaClass = javaClass; ClassLoader classLoader = javaClass.getClassLoader(); if (classLoader == null) { classLoader = ClassLoader.getSystemClassLoader(); } this.readClass(new DataInputStream(classLoader.getResourceAsStream(javaClass.getName().replace('.', '/') + ".class"))); } /** * returns the methodDescriptor of the method. * @param method AccessibleObject; the method to resolve. * @return its descriptor. */ public MethodDescriptor getMethod(final AccessibleObject method) { return this.methods.get(method); } /** * returns the constantpool of a classfile. * @return Constant[] the constantpool */ public Constant[] getConstantPool() { return this.constantPool; } /** * returns the methods of this class. * @return MethodDescriptor[] */ public MethodDescriptor[] getMethods() { return this.methods.values().toArray(new MethodDescriptor[this.methods.size()]); } /** ** PRIVATE PARSING METHODS *** */ /** * reads the class. * @param dataInput DataInput; the dataInput * @throws IOException on failure * @throws ClassNotFoundException on incomplete classPath */ private void readClass(final DataInput dataInput) throws IOException, ClassNotFoundException { // We skip the magic(u4) and version(u2,u2) dataInput.skipBytes(8); // Now we read the constantPool this.readConstantPool(dataInput); // We skip the accessFlag(u2), thisClass(u2),superClass(u2) dataInput.skipBytes(6); // We skip the interfaces int interfacesCount = dataInput.readUnsignedShort(); dataInput.skipBytes(2 * interfacesCount); // We skip the fields // AttributeInfo has nameIndex(u2),length(u4) int fieldCount = dataInput.readUnsignedShort(); for (int i = 0; i < fieldCount; i++) { // fieldInfo has accessFlag(u2),name(u2),descriptor(u2) dataInput.skipBytes(6); int attributeCount = dataInput.readUnsignedShort(); for (int j = 0; j < attributeCount; j++) { // AttributeInfo has nameIndex(u2) dataInput.skipBytes(2); dataInput.skipBytes(dataInput.readInt()); } } // Finally we read the methods int methodCount = dataInput.readUnsignedShort(); for (int i = 0; i < methodCount; i++) { MethodDescriptor methodDescriptor = new MethodDescriptor(dataInput, this.constantPool); AccessibleObject method = this.parseMethod(methodDescriptor.getName(), methodDescriptor.getMethodSignature().getParameterTypes()); methodDescriptor.setMethod(method); this.methods.put(method, methodDescriptor); } } /** * reads the constantpool from file. * @param dataInput DataInput; the stream * @throws IOException on failure */ private void readConstantPool(final DataInput dataInput) throws IOException { this.constantPool = new Constant[dataInput.readUnsignedShort()]; /* * constant_pool[0] is unused by the compiler and may be used freely by the implementation. */ for (int i = 1; i < this.constantPool.length; i++) { this.constantPool[i] = Constant.readConstant(this.constantPool, dataInput); if (this.constantPool[i] instanceof ConstantDouble || this.constantPool[i] instanceof ConstantLong) { /* * Quote from the JVM specification: "All eight byte constants take up two spots in the constant pool. If this * is the n'th byte in the constant pool, then the next item will be numbered n+2" Thus we have to increment the * index counter. */ i++; } } } /** * parses a methodName and descriptor to an accessibleObject. * @param methodName String; the name of the method * @param argumentClasses Class<?>[]; the argumentClasses * @return the AccessibleObject */ private AccessibleObject parseMethod(final String methodName, final Class[] argumentClasses) { try { if (methodName.equals("")) { return null; } if (!methodName.equals("")) { return this.javaClass.getDeclaredMethod(methodName, argumentClasses); } return this.javaClass.getDeclaredConstructor(argumentClasses); } catch (Exception exception) { return null; } } }