From e6ce22b9601601dc80af4d6e8c2ce824132c3acf Mon Sep 17 00:00:00 2001
From: konsoletyper <konsoletyper@gmail.com>
Date: Mon, 8 Sep 2014 18:08:23 +0400
Subject: [PATCH] Complete disk cached class source

---
 .../cache/ClassPathClassDateProvider.java     |   2 +-
 .../cache/DiskCachedClassHolderSource.java    | 167 +++++++++++++++++-
 2 files changed, 162 insertions(+), 7 deletions(-)

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 <konsoletyper@gmail.com>
  */
 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<AnnotationHolder> 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<AnnotationValue> 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<ElementModifier> modifiers) {
         int result = 0;
         for (ElementModifier modifier : modifiers) {
@@ -209,4 +354,14 @@ public class DiskCachedClassHolderSource implements ClassHolderSource {
         }
         return result;
     }
+
+    private Set<ElementModifier> unpackModifiers(int packed) {
+        Set<ElementModifier> modifiers = EnumSet.noneOf(ElementModifier.class);
+        while (packed != 0) {
+            int n = Integer.numberOfTrailingZeros(packed);
+            packed ^= 1 << n;
+            modifiers.add(elementModifiers[n]);
+        }
+        return modifiers;
+    }
 }