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 -~----------~----~----~----~------~----~------~--~---