modules/tool-classfile/src/main/java/net/multiphasicapps/classfile/ClassFile.java
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
// Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------
package net.multiphasicapps.classfile;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import net.multiphasicapps.collections.UnmodifiableArrayList;
import net.multiphasicapps.io.SizeLimitedInputStream;
/**
* This represents a class file which was most likely derived from the binary
* class file format of a class.
*
* @since 2017/09/26
*/
public final class ClassFile
implements Annotated, HasAccessibleFlags
{
/** The magic number of the class file. */
private static final int _MAGIC_NUMBER =
0xCAFEBABE;
/** The version of this class. */
protected final ClassVersion version;
/** The flags for this class. */
protected final ClassFlags classflags;
/** The name of this class. */
protected final ClassName thisname;
/** The class this extends. */
protected final ClassName supername;
/** The annotation table of the class. */
protected final AnnotationTable annotations;
/** The referenced inner classes. */
protected final InnerClasses innerclasses;
/** The source file the class is in. */
protected final String sourcefilename;
/** The interfaces this class implements. */
protected final ClassNames interfaces;
/** The constant pool of the class. */
protected final Pool pool;
/** The fields within this class. */
private final Field[] _fields;
/** The methods within this class. */
private final Method[] _methods;
/**
* Initializes the class file.
*
* @param __ver The version of the class.
* @param __cf The flags for this class.
* @param __tn The name of this class.
* @param __sn The class this class extends, may be {@code null}.
* @param __in The interfaces this class implements.
* @param __fs The fields in this class.
* @param __ms The methods in this class.
* @param __icl Defined inner classes.
* @param __at Annotations that are declared on the class.
* @param __sfn Source file name.
* @param __pool The constant pool.
* @throws InvalidClassFormatException If the class is not valid.
* @throws NullPointerException On null arguments, except for {@code __sn}.
* @since 2017/09/26
*/
ClassFile(ClassVersion __ver, ClassFlags __cf, ClassName __tn,
ClassName __sn, ClassName[] __in, Field[] __fs, Method[] __ms,
InnerClasses __icl, AnnotationTable __at, String __sfn,
Pool __pool)
throws InvalidClassFormatException, NullPointerException
{
if (__ver == null || __cf == null || __tn == null ||
__in == null || __fs == null || __ms == null || __icl == null ||
__at == null)
throw new NullPointerException("NARG");
// Check sub-arrays for null
for (Object[] foo : new Object[][]{(__in = __in.clone()),
(__fs = __fs.clone()), (__ms = __ms.clone())})
for (Object f : foo)
if (f == null)
throw new NullPointerException("NARG");
/* {@squirreljme.error JC29 Either Object has a superclass which it
cannot extend any class or any other class does not have a super
class. Additionally primitive types cannot have a super class.
(The current class name; The super class name; Object class name;
Is this primitive?)} */
ClassName objectcn = new ClassName("java/lang/Object");
if ((__tn.isPrimitive() ||
__tn.equals(objectcn)) != (__sn == null))
throw new InvalidClassFormatException(String.format(
"JC29 %s %s %s %s",
__tn, __sn, objectcn, __tn.isPrimitive()));
// Set
this.version = __ver;
this.classflags = __cf;
this.thisname = __tn;
this.supername = __sn;
this.innerclasses = __icl;
this.annotations = __at;
this.interfaces = new ClassNames(__in);
this._fields = __fs;
this._methods = __ms;
this.sourcefilename = __sfn;
this.pool = __pool;
}
/**
* {@inheritDoc}
* @since 2018/03/06
*/
@Override
public final AnnotationTable annotationTable()
{
return this.annotations;
}
/**
* Returns the fields within this class.
*
* @return The class fields.
* @since 2017/10/12
*/
public final List<Field> fields()
{
return UnmodifiableArrayList.<Field>of(this._fields);
}
/**
* {@inheritDoc}
* @since 2017/10/09
*/
@Override
public final ClassFlags flags()
{
return this.classflags;
}
/**
* Returns the inner classes.
*
* @return The inner classes.
* @since 2018/06/16
*/
public final InnerClasses innerClasses()
{
return this.innerclasses;
}
/**
* Returns the names of implemented interfaces.
*
* @return The implemented interface names.
* @since 2017/10/09
*/
public final ClassNames interfaceNames()
{
return this.interfaces;
}
/**
* Methods which exist within this class.
*
* @return The class methods.
* @since 2017/10/09
*/
public final Method[] methods()
{
return this._methods.clone();
}
/**
* Returns the constant pool.
*
* @return The constant pool.
* @since 2024/01/20
*/
public final Pool pool()
{
return this.pool;
}
/**
* Returns the source file the class is in.
*
* @return The source file the class is in or {@code null} if it is not
* in one.
* @since 2018/09/08
*/
public final String sourceFile()
{
return this.sourcefilename;
}
/**
* Returns the name of the super class.
*
* @return The name of the super class.
* @since 2017/10/09
*/
public final ClassName superName()
{
return this.supername;
}
/**
* Returns the name of the current class.
*
* @return The current class name.
* @since 2017/10/02
*/
public final ClassName thisName()
{
return this.thisname;
}
/**
* Returns the type of class this is.
*
* @return The class type.
* @since 2018/05/14
*/
public final ClassType type()
{
ClassFlags flags = this.classflags;
if (flags.isEnum())
return ClassType.ENUM;
else if (flags.isAnnotation())
return ClassType.ANNOTATION;
else if (flags.isInterface())
return ClassType.INTERFACE;
return ClassType.CLASS;
}
/**
* Returns the class version.
*
* @return The class version.
* @since 2019/04/14
*/
public final ClassVersion version()
{
return this.version;
}
/**
* Initializes a class file which is a special representation of the
* following field descriptor which is either an array or a primitive
* type.
*
* @param __d The descriptor to create a special class for.
* @return The generated class file from the specified descriptor.
* @throws IllegalArgumentException If the descriptor is not an array
* or a pritimive type.
* @throws NullPointerException On null arguments.
* @since 2017/10/09
*/
public static ClassFile special(FieldDescriptor __d)
throws IllegalArgumentException, NullPointerException
{
if (__d == null)
throw new NullPointerException("NARG");
/* {@squirreljme.error JC2a Cannot create a special class because it
is not an array or primitive type. (The descriptor)} */
if (!__d.isArray() && !__d.isPrimitive())
throw new IllegalArgumentException(String.format("JC2a %s", __d));
// Pre-composed parts to fake things
ClassFlags cflags = new ClassFlags(ClassFlag.PUBLIC, ClassFlag.FINAL,
ClassFlag.SUPER, ClassFlag.SYNTHETIC);
Method[] methods = new Method[0];
// Use the names of the types in the language
ClassName name;
boolean isPrimitive = __d.isPrimitive();
if (isPrimitive)
name = ClassName.fromPrimitiveType(__d.primitiveType());
// Something else
else
name = __d.className();
// Build
return new ClassFile(ClassVersion.MAX_VERSION, cflags, name,
(isPrimitive ? null : new ClassName("java/lang/Object")),
new ClassName[0], new Field[0], methods, new InnerClasses(),
new AnnotationTable(), "<special>", null);
}
/**
* This parses the input stream as a class file and returns the
* representation of that class file.
*
* @param __is The input stream to source classes from.
* @return The decoded class file.
* @throws InvalidClassFormatException If the class file is not formatted
* correctly.
* @throws IOException On read errors.
* @throws NullPointerException On null arguments.
* @since 2017/09/26
*/
public static ClassFile decode(InputStream __is)
throws InvalidClassFormatException, IOException, NullPointerException
{
// Check
if (__is == null)
throw new NullPointerException("NARG");
/* {@squirreljme.error JC2b The magic number for the class is not
valid. (The read magic number; The expected magic number)} */
DataInputStream in = new DataInputStream(__is);
int magic = in.readInt();
if (magic != ClassFile._MAGIC_NUMBER)
throw new InvalidClassFormatException(String.format(
"JC2b %08x %08x", magic, ClassFile._MAGIC_NUMBER));
/* {@squirreljme.error JC2c The version number of the input class
file is not valid. (The version number)} */
int cver = in.readShort() | (in.readShort() << 16);
ClassVersion version = ClassVersion.findVersion(cver);
if (version == null)
throw new InvalidClassFormatException(String.format("JC2c %d.%d",
cver >>> 16, (cver & 0xFFFF)));
// Decode the constant pool
Pool pool = Pool.decode(in);
// Decode flags
ClassFlags classflags = new ClassFlags(in.readUnsignedShort());
// Name of the current class
ClassName thisname = pool.<ClassName>require(ClassName.class,
in.readUnsignedShort());
// Read super class
ClassName supername = pool.<ClassName>get(ClassName.class,
in.readUnsignedShort());
// Read interfaces
int icount = in.readUnsignedShort();
ClassName[] interfaces = new ClassName[icount];
for (int i = 0; i < icount; i++)
interfaces[i] = pool.<ClassName>require(ClassName.class,
in.readUnsignedShort());
// Read fields
Field[] fields = Field.decode(version, thisname, classflags, pool, in);
// Read methods
Method[] methods = Method.decode(version, thisname, classflags, pool,
in);
// Read in attributes
AttributeTable attrs = AttributeTable.parse(pool, in);
// Read annotation table, if it is valid and exists
AnnotationTable annotations = AnnotationTable.parse(pool, attrs);
// Parse inner classes
InnerClasses innerclasses = InnerClasses.parse(pool, attrs);
/* {@squirreljme.error JC2d Expected end of the class to follow the
attributes in the class. (The name of this class)} */
if (in.read() >= 0)
throw new InvalidClassFormatException(
String.format("JC2d %s", thisname));
// Source file name, if any
Attribute attr = attrs.get("SourceFile");
String sourcefilename = null;
if (attr != null)
try (DataInputStream ai = attr.open())
{
sourcefilename = pool.<UTFConstantEntry>require(
UTFConstantEntry.class, ai.readUnsignedShort()).toString();
}
// Build
return new ClassFile(version, classflags, thisname, supername,
interfaces, fields, methods, innerclasses, annotations,
sourcefilename, pool);
}
/**
* Reads the next attribute from the class.
*
* @param __in The input stream where bytes come from.
* @param __pool The constant pool.
* @param __aname The output name of the attribute which was just read.
* @param __alens The length of the attribute.
* @return The stream to the attribute which just has been read.
* @throws IOException On read errors.
* @throws InvalidClassFormatException If the attribute is not correct.
* @throws NullPointerException On null arguments.
* @since 2017/04/09
*/
@Deprecated
static DataInputStream __nextAttribute(DataInputStream __in,
Pool __pool, String[] __aname, int[] __alens)
throws InvalidClassFormatException, IOException, NullPointerException
{
// Check
if (__aname == null || __alens == null)
throw new NullPointerException("NARG");
// The name is not parsed here
__aname[0] = __pool.<UTFConstantEntry>require(UTFConstantEntry.class,
__in.readUnsignedShort()).toString();
/* {@squirreljme.error JC2e Attribute exceeds 2GiB in length. (The
size of the attribute)} */
int len = __in.readInt();
if (len < 0)
throw new InvalidClassFormatException(String.format("JC2e %d",
len & 0xFFFFFFFFL));
// Used as a hint
__alens[0] = len;
// Setup reader
return new DataInputStream(new SizeLimitedInputStream(__in, len, true,
false));
}
}