Author: sco...@google.com
Date: Thu Apr  2 13:40:27 2009
New Revision: 5163

Added:
    trunk/dev/core/src/com/google/gwt/dev/util/DiskCache.java
    trunk/dev/core/test/com/google/gwt/dev/util/DiskCacheTest.java
Modified:
    trunk/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
    trunk/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
    trunk/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
    trunk/dev/core/src/com/google/gwt/dev/javac/impl/FileCompilationUnit.java
     
trunk/dev/core/src/com/google/gwt/dev/javac/impl/SourceFileCompilationUnit.java
    trunk/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java

Log:
Created a new DiskCache class which caches large blocks of data on disk to  
converse heap.

- Generated CompilationUnits cache their source code.
- CompiledClasses cache their bytes.

Review by: jat, bobv

Modified: trunk/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java     
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java    Thu  
Apr  2 13:40:27 2009
@@ -435,13 +435,6 @@
    }

    /**
-   * Called when this unit no longer needs to keep an internal cache of its
-   * source.
-   */
-  protected void dumpSource() {
-  }
-
-  /**
     * If compiled, returns all contained classes; otherwise returns
     * <code>null</code>.
     */
@@ -479,7 +472,6 @@
    }

    void setChecked() {
-    dumpSource();
      assert cud != null || state == State.GRAVEYARD;
      for (CompiledClass compiledClass : getCompiledClasses()) {
        compiledClass.checked();
@@ -500,14 +492,12 @@
    }

    void setError() {
-    dumpSource();
      this.errors = cud.compilationResult().getErrors();
      invalidate();
      state = State.ERROR;
    }

    void setFresh() {
-    dumpSource();
      this.errors = null;
      invalidate();
      state = State.FRESH;

Modified: trunk/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java       
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java      Thu Apr 
  
2 13:40:27 2009
@@ -17,6 +17,7 @@

  import com.google.gwt.core.ext.typeinfo.JRealClassType;
  import com.google.gwt.dev.javac.impl.Shared;
+import com.google.gwt.dev.util.DiskCache;

  import org.eclipse.jdt.core.compiler.CharOperation;
  import org.eclipse.jdt.internal.compiler.ClassFile;
@@ -24,7 +25,6 @@
  import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
  import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
  import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
-import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
  import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;

  /**
@@ -32,6 +32,8 @@
   */
  public final class CompiledClass {

+  private static final DiskCache diskCache = new DiskCache();
+
    private static ClassFile getClassFile(TypeDeclaration typeDecl,
        String binaryName) {
      for (ClassFile tryClassFile :  
typeDecl.compilationResult().getClassFiles()) {
@@ -45,24 +47,21 @@
      return null;
    }

-  private static String getPackagePrefix(String packageName) {
-    return packageName.length() > 0 ? packageName + "." : "";
-  }
-
    protected final String binaryName;
-  protected final byte[] bytes;
    protected final CompiledClass enclosingClass;
    protected final String location;
-  protected final String packageName;
-  protected final String sourceName;
    protected final CompilationUnit unit;

-  // The state below is transient.
-  private NameEnvironmentAnswer nameEnvironmentAnswer;
-  private JRealClassType realClassType;
+  /**
+   * A token to retrieve this object's bytes from the disk cache.
+   */
+  private final long cacheToken;

+  // The state below is transient.
+  private transient NameEnvironmentAnswer nameEnvironmentAnswer;
+  private transient JRealClassType realClassType;
    // Can be killed after parent is CHECKED.
-  private TypeDeclaration typeDeclaration;
+  private transient TypeDeclaration typeDeclaration;

    CompiledClass(CompilationUnit unit, TypeDeclaration typeDeclaration,
        CompiledClass enclosingClass) {
@@ -71,17 +70,9 @@
      this.enclosingClass = enclosingClass;
      SourceTypeBinding binding = typeDeclaration.binding;
      this.binaryName =  
CharOperation.charToString(binding.constantPoolName());
-    this.packageName = Shared.getPackageNameFromBinary(binaryName);
-    if (binding instanceof LocalTypeBinding) {
-      // The source name of a local type must be determined from binary.
-      String qualifiedName = binaryName.replace('/', '.');
-      this.sourceName = qualifiedName.replace('$', '.');
-    } else {
-      this.sourceName = getPackagePrefix(packageName)
-          + String.valueOf(binding.qualifiedSourceName());
-    }
      ClassFile classFile = getClassFile(typeDeclaration, binaryName);
-    this.bytes = classFile.getBytes();
+    byte[] bytes = classFile.getBytes();
+    this.cacheToken = diskCache.writeByteArray(bytes);
      this.location = String.valueOf(classFile.fileName());
    }

@@ -96,7 +87,7 @@
     * Returns the bytes of the compiled class.
     */
    public byte[] getBytes() {
-    return bytes;
+    return diskCache.readByteArray(cacheToken);
    }

    public CompiledClass getEnclosingClass() {
@@ -107,14 +98,14 @@
     * Returns the enclosing package, e.g. {...@code java.util}.
     */
    public String getPackageName() {
-    return packageName;
+    return Shared.getPackageNameFromBinary(binaryName);
    }

    /**
     * Returns the qualified source name, e.g. {...@code java.util.Map.Entry}.
     */
    public String getSourceName() {
-    return sourceName;
+    return binaryName.replace('/', '.').replace('$', '.');
    }

    public CompilationUnit getUnit() {
@@ -136,7 +127,8 @@
    NameEnvironmentAnswer getNameEnvironmentAnswer() {
      if (nameEnvironmentAnswer == null) {
        try {
-        ClassFileReader cfr = new ClassFileReader(bytes,  
location.toCharArray());
+        ClassFileReader cfr = new ClassFileReader(getBytes(),
+            location.toCharArray());
          nameEnvironmentAnswer = new NameEnvironmentAnswer(cfr, null);
        } catch (ClassFormatException e) {
          throw new RuntimeException("Unexpectedly unable to parse class  
file", e);

Modified: trunk/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
==============================================================================
--- trunk/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java       
(original)
+++ trunk/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java      Thu Apr 
  
2 13:40:27 2009
@@ -133,16 +133,38 @@
      }
    }

+  /**
+   * Just a little class to grab source only once for a compilation unit.
+   *
+   * TODO: parse JSNI <i>during</i> JDT's cycle to avoiding having to read  
from
+   * disk twice.
+   */
+  private static final class SourceCache {
+    private String source;
+    private final CompilationUnit unit;
+
+    public SourceCache(CompilationUnit unit) {
+      this.unit = unit;
+    }
+
+    public String get() {
+      if (source == null) {
+        source = unit.getSource();
+      }
+      return source;
+    }
+  }
+
    public static void collectJsniMethods(TreeLogger logger,
        Set<CompilationUnit> units, JsProgram program) {
      for (CompilationUnit unit : units) {
        if (unit.getState() == State.COMPILED) {
          String loc = unit.getDisplayLocation();
-        String source = unit.getSource();
+        SourceCache sourceCache = new SourceCache(unit);
          assert unit.getJsniMethods() == null;
          List<JsniMethod> jsniMethods = new ArrayList<JsniMethod>();
          for (CompiledClass compiledClass : unit.getCompiledClasses()) {
-          jsniMethods.addAll(collectJsniMethods(logger, loc, source,
+          jsniMethods.addAll(collectJsniMethods(logger, loc, sourceCache,
                compiledClass, program));
          }
          unit.setJsniMethods(jsniMethods);
@@ -154,7 +176,8 @@
     * TODO: log real errors, replacing GenerateJavaScriptAST?
     */
    private static List<JsniMethod> collectJsniMethods(TreeLogger logger,
-      String loc, String source, CompiledClass compiledClass, JsProgram  
program) {
+      String loc, SourceCache sourceCache, CompiledClass compiledClass,
+      JsProgram program) {
      TypeDeclaration typeDecl = compiledClass.getTypeDeclaration();
      int[] lineEnds =  
typeDecl.compilationResult.getLineSeparatorPositions();
      List<JsniMethod> jsniMethods = new ArrayList<JsniMethod>();
@@ -165,6 +188,7 @@
          if (!method.isNative()) {
            continue;
          }
+        String source = sourceCache.get();
          Interval interval = findJsniSource(source, method);
          if (interval == null) {
            String msg = "No JavaScript body found for native method '" +  
method

Modified:  
trunk/dev/core/src/com/google/gwt/dev/javac/impl/FileCompilationUnit.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/javac/impl/FileCompilationUnit.java       
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/javac/impl/FileCompilationUnit.java       
 
Thu Apr  2 13:40:27 2009
@@ -23,7 +23,7 @@
  /**
   * A compilation unit based on a file.
   */
-public final class FileCompilationUnit extends CompilationUnit {
+public class FileCompilationUnit extends CompilationUnit {
    private final File file;
    private final String typeName;


Modified:  
trunk/dev/core/src/com/google/gwt/dev/javac/impl/SourceFileCompilationUnit.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/javac/impl/SourceFileCompilationUnit.java 
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/javac/impl/SourceFileCompilationUnit.java 
 
Thu Apr  2 13:40:27 2009
@@ -23,7 +23,6 @@
   */
  public class SourceFileCompilationUnit extends CompilationUnit {

-  private String sourceCode;
    private JavaSourceFile sourceFile;

    public SourceFileCompilationUnit(JavaSourceFile sourceFile) {
@@ -42,10 +41,7 @@

    @Override
    public String getSource() {
-    if (sourceCode == null) {
-      sourceCode = sourceFile.readSource();
-    }
-    return sourceCode;
+    return sourceFile.readSource();
    }

    public JavaSourceFile getSourceFile() {
@@ -66,10 +62,4 @@
    public boolean isSuperSource() {
      return sourceFile.isSuperSource();
    }
-
-  @Override
-  protected void dumpSource() {
-    sourceCode = null;
-  }
-
  }

Modified:  
trunk/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java
==============================================================================
---  
trunk/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java       
 
(original)
+++  
trunk/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java       
 
Thu Apr  2 13:40:27 2009
@@ -29,14 +29,18 @@
  import com.google.gwt.dev.cfg.PublicOracle;
  import com.google.gwt.dev.javac.CompilationState;
  import com.google.gwt.dev.javac.CompilationUnit;
-import com.google.gwt.dev.javac.impl.Shared;
+import com.google.gwt.dev.javac.impl.FileCompilationUnit;
+import com.google.gwt.dev.util.DiskCache;
  import com.google.gwt.dev.util.Util;
  import com.google.gwt.dev.util.collect.HashSet;
  import com.google.gwt.dev.util.collect.IdentityHashMap;

  import java.io.ByteArrayOutputStream;
  import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
  import java.io.OutputStream;
+import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.util.ArrayList;
@@ -52,47 +56,57 @@
  public class StandardGeneratorContext implements GeneratorContext {

    /**
+   * Extras added to {...@link CompilationUnit}.
+   */
+  interface Generated {
+    void abort();
+
+    void commit();
+
+    String getTypeName();
+  }
+
+  /**
     * This compilation unit acts as a normal compilation unit as well as a  
buffer
     * into which generators can write their source. A controller should  
ensure
     * that source isn't requested until the generator has finished writing  
it.
+   * This version is backed by {...@link StandardGeneratorContext#diskCache}.
     */
-  private static class GeneratedUnitWithFile extends CompilationUnit {
-
-    private long creationTime;
-
-    private File file;
+  private static class GeneratedUnit extends CompilationUnit implements
+      Generated {

-    private PrintWriter pw;
+    /**
+     * A token to retrieve this object's bytes from the disk cache.
+     */
+    private long cacheToken;

-    private String source;
+    private long creationTime;

      private StringWriter sw;

      private final String typeName;

-    public GeneratedUnitWithFile(String typeName) {
+    public GeneratedUnit(StringWriter sw, String typeName) {
        this.typeName = typeName;
-      sw = new StringWriter();
-      pw = new PrintWriter(sw, true);
+      this.sw = sw;
+    }
+
+    public void abort() {
+      sw = null;
      }

      /**
       * Finalizes the source and adds this compilation unit to the host.
       */
      public void commit() {
-      source = sw.toString();
-      pw = null;
+      cacheToken = diskCache.writeString(sw.toString());
        sw = null;
        creationTime = System.currentTimeMillis();
      }

      @Override
      public String getDisplayLocation() {
-      if (file == null) {
-        return "transient source for " + typeName;
-      } else {
-        return file.getAbsoluteFile().toURI().toString();
-      }
+      return "transient source for " + typeName;
      }

      @Override
@@ -102,14 +116,10 @@

      @Override
      public String getSource() {
-      if (source == null && file == null) {
+      if (sw != null) {
          throw new IllegalStateException("source not committed");
        }
-      if (source == null) {
-        source = Util.readFileAsString(file);
-      }
-      assert (source != null);
-      return source;
+      return diskCache.readString(cacheToken);
      }

      @Override
@@ -122,25 +132,54 @@
        return true;
      }

-    public boolean isOnDisk() {
-      return file != null;
-    }
-
      @Override
      public boolean isSuperSource() {
        return false;
      }
+  }
+
+  /**
+   * This compilation unit acts as a normal compilation unit as well as a  
buffer
+   * into which generators can write their source. A controller should  
ensure
+   * that source isn't requested until the generator has finished writing  
it.
+   * This version is backed by an explicit generated file.
+   */
+  private static class GeneratedUnitWithFile extends FileCompilationUnit
+      implements Generated {
+
+    private PrintWriter pw;
+
+    public GeneratedUnitWithFile(File file, PrintWriter pw, String  
packageName) {
+      super(file, packageName);
+      this.pw = pw;
+    }
+
+    public void abort() {
+      pw.close();
+      pw = null;
+    }

-    public void setFile(File file) {
-      assert (file.exists() && file.canRead());
-      this.file = file;
+    public void commit() {
+      pw.close();
+      pw = null;
      }

      @Override
-    protected void dumpSource() {
-      if (file != null) {
-        source = null;
+    public String getSource() {
+      if (pw != null) {
+        throw new IllegalStateException("source not committed");
        }
+      return super.getSource();
+    }
+
+    @Override
+    public boolean isGenerated() {
+      return true;
+    }
+
+    @Override
+    public boolean isSuperSource() {
+      return false;
      }
    }

@@ -178,9 +217,11 @@
      }
    }

+  private static DiskCache diskCache;
+
    private final ArtifactSet allGeneratedArtifacts;

-  private final Set<GeneratedUnitWithFile> committedGeneratedCups = new  
HashSet<GeneratedUnitWithFile>();
+  private final Set<CompilationUnit> committedGeneratedCups = new  
HashSet<CompilationUnit>();

    private final CompilationState compilationState;

@@ -200,7 +241,7 @@

    private final PublicOracle publicOracle;

-  private final Map<PrintWriter, GeneratedUnitWithFile>  
uncommittedGeneratedCupsByPrintWriter = new IdentityHashMap<PrintWriter,  
GeneratedUnitWithFile>();
+  private final Map<PrintWriter, Generated>  
uncommittedGeneratedCupsByPrintWriter = new IdentityHashMap<PrintWriter,  
Generated>();

    /**
     * Normally, the compiler host would be aware of the same types that are
@@ -215,17 +256,20 @@
      this.genDir = genDir;
      this.generatorResourcesDir = generatorResourcesDir;
      this.allGeneratedArtifacts = allGeneratedArtifacts;
+    if (genDir == null && diskCache == null) {
+      diskCache = new DiskCache();
+    }
    }

    /**
     * Commits a pending generated type.
     */
    public final void commit(TreeLogger logger, PrintWriter pw) {
-    GeneratedUnitWithFile gcup =  
uncommittedGeneratedCupsByPrintWriter.get(pw);
+    Generated gcup = uncommittedGeneratedCupsByPrintWriter.get(pw);
      if (gcup != null) {
        gcup.commit();
        uncommittedGeneratedCupsByPrintWriter.remove(pw);
-      committedGeneratedCups.add(gcup);
+      committedGeneratedCups.add((CompilationUnit) gcup);
      } else {
        logger.log(TreeLogger.WARN,
            "Generator attempted to commit an unknown PrintWriter", null);
@@ -302,11 +346,9 @@
                "Generated source files...", null);
          }

-        for (GeneratedUnitWithFile gcup : committedGeneratedCups) {
+        for (CompilationUnit gcup : committedGeneratedCups) {
            String qualifiedTypeName = gcup.getTypeName();
            genTypeNames.add(qualifiedTypeName);
-          maybeWriteSource(gcup, qualifiedTypeName);
-
            if (subBranch != null) {
              subBranch.log(TreeLogger.DEBUG, gcup.getDisplayLocation(),  
null);
            }
@@ -333,7 +375,7 @@
          String msg = "For the following type(s), generated source was  
never committed (did you forget to call commit()?)";
          logger = logger.branch(TreeLogger.WARN, msg, null);

-        for (GeneratedUnitWithFile unit :  
uncommittedGeneratedCupsByPrintWriter.values()) {
+        for (Generated unit :  
uncommittedGeneratedCupsByPrintWriter.values()) {
            logger.log(TreeLogger.WARN, unit.getTypeName(), null);
          }
        }
@@ -359,8 +401,12 @@

    public final PrintWriter tryCreate(TreeLogger logger, String packageName,
        String simpleTypeName) {
-    String typeName = packageName + "." + simpleTypeName;
-
+    String typeName;
+    if (packageName.length() == 0) {
+      typeName = simpleTypeName;
+    } else {
+      typeName = packageName + '.' + simpleTypeName;
+    }
      // Is type already known to the host?
      JClassType existingType = getTypeOracle().findType(packageName,
          simpleTypeName);
@@ -381,17 +427,34 @@

      // The type isn't there, so we can let the caller create it. Remember  
that
      // it is pending so another attempt to create the same type will fail.
-    String qualifiedSourceName;
-    if (packageName.length() == 0) {
-      qualifiedSourceName = simpleTypeName;
+    Generated gcup;
+    PrintWriter pw;
+    if (this.genDir == null) {
+      StringWriter sw = new StringWriter();
+      pw = new PrintWriter(sw, true);
+      gcup = new GeneratedUnit(sw, typeName);
      } else {
-      qualifiedSourceName = packageName + '.' + simpleTypeName;
+      File dir = new File(genDir, packageName.replace('.',  
File.separatorChar));
+      dir.mkdirs();
+      File srcFile = new File(dir, simpleTypeName + ".java");
+      if (srcFile.exists()) {
+        srcFile.delete();
+      }
+      try {
+        FileOutputStream fos = new FileOutputStream(srcFile);
+        // Critical to set the encoding here, or UTF chars get whacked.
+        OutputStreamWriter osw = new OutputStreamWriter(fos,
+            Util.DEFAULT_ENCODING);
+        pw = new PrintWriter(osw);
+        gcup = new GeneratedUnitWithFile(srcFile, pw, packageName);
+      } catch (IOException e) {
+        throw new RuntimeException("Error writing out generated unit at '"
+            + srcFile.getAbsolutePath() + "'", e);
+      }
      }
-    GeneratedUnitWithFile gcup = new  
GeneratedUnitWithFile(qualifiedSourceName);
-    uncommittedGeneratedCupsByPrintWriter.put(gcup.pw, gcup);
+    uncommittedGeneratedCupsByPrintWriter.put(pw, gcup);
      newlyGeneratedTypeNames.add(typeName);
-
-    return gcup.pw;
+    return pw;
    }

    public OutputStream tryCreateResource(TreeLogger logger, String  
partialPath)
@@ -481,32 +544,6 @@
        }
      } finally {
        pendingResourcesByOutputStream.clear();
-    }
-  }
-
-  /**
-   * Writes the source of the specified compilation unit to disk if a gen
-   * directory is specified.
-   *
-   * @param unit the compilation unit whose contents might need to be  
written
-   * @param qualifiedTypeName the fully-qualified type name
-   */
-  private void maybeWriteSource(GeneratedUnitWithFile unit,
-      String qualifiedTypeName) {
-
-    if (unit.isOnDisk() || genDir == null) {
-      // No place to write it.
-      return;
-    }
-
-    // Let's do write it.
-    String packageName = Shared.getPackageName(qualifiedTypeName);
-    String shortName = Shared.getShortName(qualifiedTypeName);
-    File dir = new File(genDir, packageName.replace('.',  
File.separatorChar));
-    dir.mkdirs();
-    File srcFile = new File(dir, shortName + ".java");
-    if (Util.writeStringAsFile(srcFile, unit.getSource())) {
-      unit.setFile(srcFile);
      }
    }
  }

Added: trunk/dev/core/src/com/google/gwt/dev/util/DiskCache.java
==============================================================================
--- (empty file)
+++ trunk/dev/core/src/com/google/gwt/dev/util/DiskCache.java   Thu Apr  2  
13:40:27 2009
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.dev.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A nifty class that lets you squirrel away data on the file system. Write
+ * once, read many times. Instance of this are thread-safe by way of  
internal
+ * synchronization.
+ *
+ * Note that in the current implementation, the backing temp file will get
+ * arbitrarily large as you continue adding things to it. There is no  
internal
+ * GC or compaction.
+ */
+public class DiskCache {
+  /**
+   * For future thought: if we used Object tokens instead of longs, we  
could
+   * actually track references and do GC/compaction on the underlying file.
+   *
+   * I considered using memory mapping, but I didn't see any obvious way  
to make
+   * the map larger after the fact, which kind of defeats the  
infinite-append
+   * design. At any rate, I measured the current performance of this  
design to
+   * be so fast relative to what I'm using it for, I didn't pursue this  
further.
+   */
+
+  private static class Shutdown implements Runnable {
+    public void run() {
+      for (DiskCache diskCache : shutdownList) {
+        try {
+          diskCache.finalize();
+        } catch (Throwable e) {
+        }
+      }
+    }
+  }
+
+  private static List<DiskCache> shutdownList;
+
+  private boolean atEnd = true;
+  private RandomAccessFile file;
+
+  public DiskCache() {
+    try {
+      File temp = File.createTempFile("gwt", "byte-cache");
+      temp.deleteOnExit();
+      file = new RandomAccessFile(temp, "rw");
+      file.setLength(0);
+      if (shutdownList == null) {
+        shutdownList = new ArrayList<DiskCache>();
+        Runtime.getRuntime().addShutdownHook(new Thread(new Shutdown()));
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Unable to initialize byte cache", e);
+    }
+  }
+
+  /**
+   * Read some bytes off disk.
+   *
+   * @param token a handle previously returned from
+   *          {...@link #writeByteArray(byte[])}
+   * @return the bytes that were written
+   */
+  public byte[] readByteArray(long token) {
+    try {
+      atEnd = false;
+      file.seek(token);
+      int length = file.readInt();
+      byte[] result = new byte[length];
+      file.readFully(result);
+      return result;
+    } catch (IOException e) {
+      throw new RuntimeException("Unable to read from byte cache", e);
+    }
+  }
+
+  /**
+   * Read a String from disk.
+   *
+   * @param token a handle previously returned from {...@link  
#writeString(String)}
+   * @return the String that was written
+   */
+  public String readString(long token) {
+    return Util.toString(readByteArray(token));
+  }
+
+  /**
+   * Write a byte array to disk.
+   *
+   * @return a handle to retrieve it later
+   */
+  public long writeByteArray(byte[] bytes) {
+    try {
+      if (!atEnd) {
+        file.seek(file.length());
+      }
+      long position = file.getFilePointer();
+      file.writeInt(bytes.length);
+      file.write(bytes);
+      return position;
+    } catch (IOException e) {
+      throw new RuntimeException("Unable to write to byte cache", e);
+    }
+  }
+
+  /**
+   * Write a String to disk.
+   *
+   * @return a handle to retrieve it later
+   */
+  public long writeString(String str) {
+    return writeByteArray(Util.getBytes(str));
+  }
+
+  @Override
+  protected void finalize() throws Throwable {
+    if (file != null) {
+      file.setLength(0);
+      file.close();
+      file = null;
+    }
+  }
+}
\ No newline at end of file

Added: trunk/dev/core/test/com/google/gwt/dev/util/DiskCacheTest.java
==============================================================================
--- (empty file)
+++ trunk/dev/core/test/com/google/gwt/dev/util/DiskCacheTest.java      Thu Apr 
  
2 13:40:27 2009
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.dev.util;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * Tests {...@link DiskCache}.
+ */
+public class DiskCacheTest extends TestCase {
+  private final DiskCache diskCache = new DiskCache();
+
+  public void testBytes() {
+    byte[] a = new byte[0];
+    byte[] b = new byte[] {1, 5, 9, 7, 3, 4, 2};
+    byte[] c = new byte[3524];
+    for (int i = 1; i < c.length; ++i) {
+      c[i] = (byte) (i * 31 + c[i - 1]);
+    }
+    byte[][] insertOrder = new byte[][] {a, b, c, b, c, a, a, b, b, c, c,  
a};
+    long[] tokens = new long[insertOrder.length];
+    for (int i = 0; i < insertOrder.length; ++i) {
+      tokens[i] = diskCache.writeByteArray(insertOrder[i]);
+    }
+
+    int testIndex = 0;
+    for (int i = 0; i < 20; ++i) {
+      testIndex += tokens[i % tokens.length];
+      testIndex %= insertOrder.length;
+      byte[] expected = insertOrder[testIndex];
+      byte[] actual = diskCache.readByteArray(tokens[testIndex]);
+      assertTrue("Values were not equals at index '" + testIndex + "'",
+          Arrays.equals(expected, actual));
+    }
+  }
+
+  public void testStrings() {
+    String a = "";
+    String b = "abjdsfkl;jasdf";
+    char[] c = new char[2759];
+    for (int i = 1; i < c.length; ++i) {
+      c[i] = (char) (i * 31 + c[i - 1]);
+      // Avoid problematic characters.
+      c[i] &= 0x7FFF;
+      --c[i];
+    }
+    String s = String.valueOf(c);
+    String[] insertOrder = new String[] {s, a, b, s, b, s, a, a, s, b, b,  
a, s};
+    long[] tokens = new long[insertOrder.length];
+    for (int i = 0; i < insertOrder.length; ++i) {
+      tokens[i] = diskCache.writeString(insertOrder[i]);
+    }
+
+    int testIndex = 0;
+    for (int i = 0; i < 20; ++i) {
+      testIndex += tokens[i % tokens.length];
+      testIndex %= insertOrder.length;
+      String expected = insertOrder[testIndex];
+      String actual = diskCache.readString(tokens[testIndex]);
+      assertEquals("Values were not equals at index '" + testIndex + "'",
+          expected, actual);
+    }
+  }
+}

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to