Skip to content

Commit

Permalink
JANDEX-21 Detect no-args constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
mkouba committed Jan 14, 2014
1 parent c05209a commit fd4a5ce
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 17 deletions.
60 changes: 57 additions & 3 deletions src/main/java/org/jboss/jandex/ClassInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,21 @@
*
*/
public final class ClassInfo implements AnnotationTarget {

private final DotName name;
private short flags;
private final DotName superName;
private final DotName[] interfaces;
private final Map<DotName, List<AnnotationInstance>> annotations;
private final ValueHolder<Boolean> hasNoArgsConstructor;

ClassInfo(DotName name, DotName superName, short flags, DotName[] interfaces, Map<DotName, List<AnnotationInstance>> annotations) {
ClassInfo(DotName name, DotName superName, short flags, DotName[] interfaces, Map<DotName, List<AnnotationInstance>> annotations, ValueHolder<Boolean> hasNoArgsConstructor) {
this.name = name;
this.superName = superName;
this.flags = flags;
this.interfaces = interfaces;
this.annotations = Collections.unmodifiableMap(annotations);
this.hasNoArgsConstructor = hasNoArgsConstructor;
}

/**
Expand All @@ -68,8 +71,8 @@ public final class ClassInfo implements AnnotationTarget {
* @param annotations the annotations on this class
* @return a new mock class representation
*/
public static final ClassInfo create(DotName name, DotName superName, short flags, DotName[] interfaces, Map<DotName, List<AnnotationInstance>> annotations) {
return new ClassInfo(name, superName, flags, interfaces, annotations);
public static final ClassInfo create(DotName name, DotName superName, short flags, DotName[] interfaces, Map<DotName, List<AnnotationInstance>> annotations, ValueHolder<Boolean> isTopLevelWithNoArgsConstructor) {
return new ClassInfo(name, superName, flags, interfaces, annotations, isTopLevelWithNoArgsConstructor);
}

public String toString() {
Expand All @@ -95,4 +98,55 @@ public final DotName[] interfaces() {
public final Map<DotName, List<AnnotationInstance>> annotations() {
return annotations;
}

/**
* @return {@link Boolean#TRUE} in case of the Java class is top-level or
* static nested with no-args constructor, {@link Boolean#FALSE} if
* it is not and <code>null</code> if info not available
*/
public final Boolean hasNoArgsConstructor() {
return hasNoArgsConstructor.get();
}

public static class ValueHolder<T> {

T value;

public ValueHolder(T initialValue) {
this.value = initialValue;
}

public T get() {
return value;
}

public void set(T value) {
this.value = value;
}

}

public static class ImmutableValueHolder<T> extends ValueHolder<T> {

@SuppressWarnings({ "rawtypes", "unchecked" })
static final ImmutableValueHolder EMPTY_HOLDER = new ImmutableValueHolder(null);
static final ImmutableValueHolder<Boolean> TRUE_HOLDER = new ImmutableValueHolder<Boolean>(true);
static final ImmutableValueHolder<Boolean> FALSE_HOLDER = new ImmutableValueHolder<Boolean>(false);

@SuppressWarnings("unchecked")
static <T> ImmutableValueHolder<T> emptyHolder() {
return (ImmutableValueHolder<T>) EMPTY_HOLDER;
}

public ImmutableValueHolder(T initialValue) {
super(initialValue);
}

@Override
public void set(Object value) {
throw new UnsupportedOperationException();
}

}

}
29 changes: 24 additions & 5 deletions src/main/java/org/jboss/jandex/IndexReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import java.util.List;
import java.util.Map;

import org.jboss.jandex.ClassInfo.ImmutableValueHolder;
import org.jboss.jandex.ClassInfo.ValueHolder;

/**
* Reads a Jandex index file and returns the saved index. See {@link Indexer}
* for a thorough description of how the Index data is produced.
Expand Down Expand Up @@ -92,18 +95,23 @@ public IndexReader(InputStream input) {
*/
public Index read() throws IOException {
PackedDataInputStream stream = new PackedDataInputStream(new BufferedInputStream(input));
if (stream.readInt() != MAGIC)
if (stream.readInt() != MAGIC) {
stream.close();
throw new IllegalArgumentException("Not a jandex index");
}
byte version = stream.readByte();

if (version != VERSION)
// All previous versions are supported
if (version < 1 || version > VERSION) {
stream.close();
throw new UnsupportedVersion("Version: " + version);
}

try {
masterAnnotations = new HashMap<DotName, List<AnnotationInstance>>();
readClassTable(stream);
readStringTable(stream);
return readClasses(stream);
return readClasses(stream, version);
} finally {
classTable = null;
stringTable = null;
Expand All @@ -112,7 +120,7 @@ public Index read() throws IOException {
}


private Index readClasses(PackedDataInputStream stream) throws IOException {
private Index readClasses(PackedDataInputStream stream, byte version) throws IOException {
int entries = stream.readPackedU32();
HashMap<DotName, List<ClassInfo>> subclasses = new HashMap<DotName, List<ClassInfo>>();
HashMap<DotName, List<ClassInfo>> implementors = new HashMap<DotName, List<ClassInfo>>();
Expand All @@ -123,14 +131,25 @@ private Index readClasses(PackedDataInputStream stream) throws IOException {
DotName name = classTable[stream.readPackedU32()];
DotName superName = classTable[stream.readPackedU32()];
short flags = stream.readShort();

// Immutable value holders used here to save some resources
ValueHolder<Boolean> hasNoArgsConstructor;
if (version < 2) {
// hasNoArgsConstructor supported since version 2
hasNoArgsConstructor = ImmutableValueHolder.emptyHolder();
} else {
hasNoArgsConstructor = stream.readBoolean() ? ImmutableValueHolder.TRUE_HOLDER
: ImmutableValueHolder.FALSE_HOLDER;
}

int numIntfs = stream.readPackedU32();
DotName[] interfaces = new DotName[numIntfs];
for (int j = 0; j < numIntfs; j++) {
interfaces[j] = classTable[stream.readPackedU32()];
}

Map<DotName, List<AnnotationInstance>> annotations = new HashMap<DotName, List<AnnotationInstance>>();
ClassInfo clazz = new ClassInfo(name, superName, flags, interfaces, annotations);
ClassInfo clazz = new ClassInfo(name, superName, flags, interfaces, annotations, hasNoArgsConstructor);
classes.put(name, clazz);
addClassToMap(subclasses, superName, clazz);
for (DotName interfaceName : interfaces) {
Expand Down
32 changes: 28 additions & 4 deletions src/main/java/org/jboss/jandex/IndexWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,41 @@ public IndexWriter(OutputStream out) {
this.out = out;
}

/**
* Writes the specified index to the associated output stream. This may be called multiple times in order
* to write multiple indexes. The default version of index file is used.
*
* @param index
* @return the number of bytes written to the stream
* @throws IOException
*/
public int write(Index index) throws IOException {
return write(index, VERSION);
}

/**
* Writes the specified index to the associated output stream. This may be called multiple times in order
* to write multiple indexes.
*
* @param index the index to write to the stream
* @param version the index file version
* @return the number of bytes written to the stream
* @throws IOException if any i/o error occurs
*/
public int write(Index index) throws IOException {
public int write(Index index, byte version) throws IOException {

if (version < 1 || version > VERSION) {
throw new UnsupportedVersion("Version: " + version);
}

PackedDataOutputStream stream = new PackedDataOutputStream(new BufferedOutputStream(out));
stream.writeInt(MAGIC);
stream.writeByte(VERSION);
stream.writeByte(version);

buildTables(index);
writeClassTable(stream);
writeStringTable(stream);
writeClasses(stream, index);
writeClasses(stream, index, version);
stream.flush();
return stream.size();
}
Expand Down Expand Up @@ -152,13 +170,19 @@ private int positionOf(DotName className) {
}


private void writeClasses(PackedDataOutputStream stream, Index index) throws IOException {
private void writeClasses(PackedDataOutputStream stream, Index index, byte version) throws IOException {
Collection<ClassInfo> classes = index.getKnownClasses();
stream.writePackedU32(classes.size());
for (ClassInfo clazz: classes) {
stream.writePackedU32(positionOf(clazz.name()));
stream.writePackedU32(clazz.superName() == null ? 0 : positionOf(clazz.superName()));
stream.writeShort(clazz.flags());

// hasNoArgsConstructor supported since version 2
if (version >= 2) {
stream.writeBoolean(clazz.hasNoArgsConstructor());
}

DotName[] interfaces = clazz.interfaces();
stream.writePackedU32(interfaces.length);
for (DotName intf: interfaces)
Expand Down
62 changes: 59 additions & 3 deletions src/main/java/org/jboss/jandex/Indexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import java.util.List;
import java.util.Map;

import org.jboss.jandex.ClassInfo.ValueHolder;

/**
* Analyzes and indexes the annotation and key structural information of a set
* of classes. The indexer will purposefully skip any class that is not Java 5
Expand Down Expand Up @@ -76,7 +78,7 @@ public final class Indexer {
private final static int CONSTANT_INVOKEDYNAMIC = 18;
private final static int CONSTANT_METHODHANDLE = 15;
private final static int CONSTANT_METHODTYPE = 16;

// "RuntimeVisibleAnnotations"
private final static byte[] RUNTIME_ANNOTATIONS = new byte[] {
0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x56, 0x69, 0x73, 0x69, 0x62,
Expand All @@ -97,6 +99,8 @@ public final class Indexer {
private final static int HAS_RUNTIME_ANNOTATION = 1;
private final static int HAS_RUNTIME_PARAM_ANNOTATION = 2;

private final static String INIT_METHOD_NAME = "<init>";

private static boolean match(byte[] target, int offset, byte[] expected) {
if (target.length - offset < expected.length)
return false;
Expand Down Expand Up @@ -136,6 +140,8 @@ private static void skipFully(InputStream s, long n) throws IOException {
private HashMap<DotName, List<AnnotationInstance>> classAnnotations;
private StrongInternPool<String> internPool;

private ValueHolder<Boolean> hasNoArgsConstructor;

// Index lifespan fields
private Map<DotName, List<AnnotationInstance>> masterAnnotations;
private Map<DotName, List<ClassInfo>> subclasses;
Expand Down Expand Up @@ -195,10 +201,45 @@ private void processMethodInfo(DataInputStream data) throws IOException {

MethodInfo method = new MethodInfo(currentClass, name, args, returnType, flags);

if(INIT_METHOD_NAME.equals(name) && args.length == 0) {
hasNoArgsConstructor.set(true);
}
processAttributes(data, method);
}
}

private void detectNoArgsConstructor(DataInputStream data) throws IOException {

int numFields = data.readUnsignedShort();

for (int i = 0; i < numFields; i++) {
// Flags, name, type
skipFully(data, 6);
skipAttributes(data);
}

int numMethods = data.readUnsignedShort();

for (int i = 0; i < numMethods; i++) {
// Flags not needed
skipFully(data, 2);
String name = intern(decodeUtf8Entry(data.readUnsignedShort()));
String descriptor = decodeUtf8Entry(data.readUnsignedShort());

if(INIT_METHOD_NAME.equals(name)) {

IntegerHolder pos = new IntegerHolder();
Type[] args = parseMethodArgs(descriptor, pos);

if(args.length == 0) {
hasNoArgsConstructor.set(true);
return;
}
}
skipAttributes(data);
}
}

private void processFieldInfo(DataInputStream data) throws IOException {
int numFields = data.readUnsignedShort();

Expand All @@ -212,6 +253,16 @@ private void processFieldInfo(DataInputStream data) throws IOException {
}
}

private void skipAttributes(DataInputStream data) throws IOException {
int numAttrs = data.readUnsignedShort();
for (int a = 0; a < numAttrs; a++) {
// Constant pool index
skipFully(data, 2);
long attributeLen = data.readInt() & 0xFFFFFFFFL;
skipFully(data, attributeLen);
}
}

private void processAttributes(DataInputStream data, AnnotationTarget target) throws IOException {
int numAttrs = data.readUnsignedShort();
for (int a = 0; a < numAttrs; a++) {
Expand Down Expand Up @@ -343,7 +394,8 @@ private void processClassInfo(DataInputStream data) throws IOException {
}

this.classAnnotations = new HashMap<DotName, List<AnnotationInstance>>();
this.currentClass = new ClassInfo(thisName, superName, flags, interfaces, classAnnotations);
this.hasNoArgsConstructor = new ValueHolder<Boolean>(false);
this.currentClass = new ClassInfo(thisName, superName, flags, interfaces, classAnnotations, hasNoArgsConstructor);

if (superName != null)
addSubclass(superName, currentClass);
Expand Down Expand Up @@ -653,8 +705,10 @@ public ClassInfo index(InputStream stream) throws IOException {
boolean hasAnnotations = processConstantPool(data);

processClassInfo(data);
if (!hasAnnotations)
if (!hasAnnotations) {
detectNoArgsConstructor(data);
return currentClass;
}

processFieldInfo(data);
processMethodInfo(data);
Expand All @@ -672,6 +726,8 @@ public ClassInfo index(InputStream stream) throws IOException {
currentClass = null;
classAnnotations = null;
internPool = null;

hasNoArgsConstructor = null;
}
}

Expand Down
Loading

0 comments on commit fd4a5ce

Please sign in to comment.