diff --git a/teavm-core/src/main/java/org/teavm/cache/ClassPathClassDateProvider.java b/teavm-core/src/main/java/org/teavm/cache/ClassPathClassDateProvider.java index a93684e3b..8ff68516a 100644 --- a/teavm-core/src/main/java/org/teavm/cache/ClassPathClassDateProvider.java +++ b/teavm-core/src/main/java/org/teavm/cache/ClassPathClassDateProvider.java @@ -33,7 +33,7 @@ public class ClassPathClassDateProvider implements ClassDateProvider { @Override public Date getModificationDate(String className) { - URL url = classLoader.getResource(className); + URL url = classLoader.getResource(className.replace('.', '/') + ".class"); if (url == null) { return null; } diff --git a/teavm-core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java b/teavm-core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java index f584fbbee..5d998633b 100644 --- a/teavm-core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java +++ b/teavm-core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java @@ -24,6 +24,8 @@ import org.teavm.model.*; * @author Alexey Andreev */ public class DiskCachedClassHolderSource implements ClassHolderSource { + private static AccessLevel[] accessLevels = AccessLevel.values(); + private static ElementModifier[] elementModifiers = ElementModifier.values(); private File directory; private SymbolTable symbolTable; private ClassHolderSource innerSource; @@ -47,8 +49,22 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { if (item == null) { item = new Item(); cache.put(name, item); - item.cls = innerSource.get(name); - newClasses.add(name); + File classFile = new File(directory, name.replace('.', '/') + ".teavm-cls"); + if (classFile.exists()) { + Date classDate = classDateProvider.getModificationDate(name); + if (classDate != null && classDate.before(new Date(classFile.lastModified()))) { + try (InputStream input = new FileInputStream(classFile)) { + item.cls = readClass(input, name); + } catch (IOException e) { + // We could not access cache file, so let's parse class file + item.cls = null; + } + } + } + if (item.cls == null) { + item.cls = innerSource.get(name); + newClasses.add(name); + } } return item.cls; } @@ -81,14 +97,41 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { output.writeInt(symbolTable.lookup(iface)); } writeAnnotations(output, cls.getAnnotations()); + output.writeShort(cls.getFields().size()); for (FieldHolder field : cls.getFields()) { writeField(output, field); } + output.writeShort(cls.getMethods().size()); for (MethodHolder method : cls.getMethods()) { writeMethod(stream, method); } } + private ClassHolder readClass(InputStream stream, String name) throws IOException { + DataInput input = new DataInputStream(stream); + ClassHolder cls = new ClassHolder(name); + cls.setLevel(accessLevels[input.readByte()]); + cls.getModifiers().addAll(unpackModifiers(input.readInt())); + int parentIndex = input.readInt(); + cls.setParent(parentIndex >= 0 ? symbolTable.at(parentIndex) : null); + int ownerIndex = input.readInt(); + cls.setOwnerName(ownerIndex >= 0 ? symbolTable.at(ownerIndex) : null); + int ifaceCount = input.readByte(); + for (int i = 0; i < ifaceCount; ++i) { + cls.getInterfaces().add(symbolTable.at(input.readInt())); + } + readAnnotations(input, cls.getAnnotations()); + int fieldCount = input.readShort(); + for (int i = 0; i < fieldCount; ++i) { + cls.addField(readField(input)); + } + int methodCount = input.readShort(); + for (int i = 0; i < methodCount; ++i) { + cls.addMethod(readMethod(stream)); + } + return cls; + } + private void writeField(DataOutput output, FieldHolder field) throws IOException { output.writeInt(symbolTable.lookup(field.getName())); output.writeInt(symbolTable.lookup(field.getType().toString())); @@ -98,6 +141,16 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { writeAnnotations(output, field.getAnnotations()); } + private FieldHolder readField(DataInput input) throws IOException { + FieldHolder field = new FieldHolder(symbolTable.at(input.readInt())); + field.setType(ValueType.parse(symbolTable.at(input.readInt()))); + field.setLevel(accessLevels[input.readByte()]); + field.getModifiers().addAll(unpackModifiers(input.readInt())); + field.setInitialValue(readFieldValue(input)); + readAnnotations(input, field.getAnnotations()); + return field; + } + private void writeFieldValue(DataOutput output, Object value) throws IOException { if (value == null) { output.writeByte(0); @@ -119,21 +172,53 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { } } + private Object readFieldValue(DataInput input) throws IOException { + int type = input.readByte(); + switch (type) { + case 0: + return null; + case 1: + return input.readInt(); + case 2: + return input.readLong(); + case 3: + return input.readFloat(); + case 4: + return input.readDouble(); + case 5: + return input.readUTF(); + default: + throw new RuntimeException("Unexpected field value type: " + type); + } + } + private void writeMethod(OutputStream stream, MethodHolder method) throws IOException { DataOutputStream output = new DataOutputStream(stream); - output.writeInt(symbolTable.lookup(method.getName())); output.writeInt(symbolTable.lookup(method.getDescriptor().toString())); output.writeByte(method.getLevel().ordinal()); output.writeInt(packModifiers(method.getModifiers())); writeAnnotations(output, method.getAnnotations()); if (method.getProgram() != null) { - output.writeByte(1); + output.writeBoolean(true); programIO.write(method.getProgram(), output); } else { - output.writeByte(0); + output.writeBoolean(false); } } + private MethodHolder readMethod(InputStream stream) throws IOException { + DataInputStream input = new DataInputStream(stream); + MethodHolder method = new MethodHolder(MethodDescriptor.parse(symbolTable.at(input.readInt()))); + method.setLevel(accessLevels[input.readByte()]); + method.getModifiers().addAll(unpackModifiers(input.readInt())); + readAnnotations(input, method.getAnnotations()); + boolean hasProgram = input.readBoolean(); + if (hasProgram) { + method.setProgram(programIO.read(input)); + } + return method; + } + private void writeAnnotations(DataOutput output, AnnotationContainer annotations) throws IOException { List annotationList = new ArrayList<>(); for (AnnotationHolder annot : annotations.all()) { @@ -145,6 +230,14 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { } } + private void readAnnotations(DataInput input, AnnotationContainer annotations) throws IOException { + int annotCount = input.readShort(); + for (int i = 0; i < annotCount; ++i) { + AnnotationHolder annot = readAnnotation(input); + annotations.add(annot); + } + } + private void writeAnnotation(DataOutput output, AnnotationHolder annotation) throws IOException { output.writeInt(symbolTable.lookup(annotation.getType())); output.writeShort(annotation.getValues().size()); @@ -154,6 +247,17 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { } } + private AnnotationHolder readAnnotation(DataInput input) throws IOException { + AnnotationHolder annotation = new AnnotationHolder(symbolTable.at(input.readInt())); + int valueCount = input.readShort(); + for (int i = 0; i < valueCount; ++i) { + String name = symbolTable.at(input.readInt()); + AnnotationValue value = readAnnotationValue(input); + annotation.getValues().put(name, value); + } + return annotation; + } + private void writeAnnotationValue(DataOutput output, AnnotationValue value) throws IOException { output.writeByte(value.getType()); switch (value.getType()) { @@ -174,7 +278,7 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { break; case AnnotationValue.ENUM: output.writeInt(symbolTable.lookup(value.getEnumValue().getClassName())); - output.writeInt(symbolTable.lookup(value.getEnumValue().getClassName())); + output.writeInt(symbolTable.lookup(value.getEnumValue().getFieldName())); break; case AnnotationValue.FLOAT: output.writeDouble(value.getFloat()); @@ -202,6 +306,47 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { } } + private AnnotationValue readAnnotationValue(DataInput input) throws IOException { + byte type = input.readByte(); + switch (type) { + case AnnotationValue.ANNOTATION: + return new AnnotationValue(readAnnotation(input)); + case AnnotationValue.BOOLEAN: + return new AnnotationValue(input.readBoolean()); + case AnnotationValue.BYTE: + return new AnnotationValue(input.readByte()); + case AnnotationValue.CLASS: + return new AnnotationValue(ValueType.parse(symbolTable.at(input.readInt()))); + case AnnotationValue.DOUBLE: + return new AnnotationValue(input.readDouble()); + case AnnotationValue.ENUM: { + String className = symbolTable.at(input.readInt()); + String fieldName = symbolTable.at(input.readInt()); + return new AnnotationValue(new FieldReference(className, fieldName)); + } + case AnnotationValue.FLOAT: + return new AnnotationValue(input.readFloat()); + case AnnotationValue.INT: + return new AnnotationValue(input.readInt()); + case AnnotationValue.LIST: { + List list = new ArrayList<>(); + int sz = input.readShort(); + for (int i = 0; i < sz; ++i) { + list.add(readAnnotationValue(input)); + } + return new AnnotationValue(list); + } + case AnnotationValue.LONG: + return new AnnotationValue(input.readLong()); + case AnnotationValue.SHORT: + return new AnnotationValue(input.readShort()); + case AnnotationValue.STRING: + return new AnnotationValue(input.readUTF()); + default: + throw new RuntimeException("Unexpected annotation value type: " + type); + } + } + private int packModifiers(Set modifiers) { int result = 0; for (ElementModifier modifier : modifiers) { @@ -209,4 +354,14 @@ public class DiskCachedClassHolderSource implements ClassHolderSource { } return result; } + + private Set unpackModifiers(int packed) { + Set modifiers = EnumSet.noneOf(ElementModifier.class); + while (packed != 0) { + int n = Integer.numberOfTrailingZeros(packed); + packed ^= 1 << n; + modifiers.add(elementModifiers[n]); + } + return modifiers; + } }