Undid style changes, reduced OO boilerplate.

This commit is contained in:
Michael Bayne
2016-09-25 10:28:50 -07:00
parent 0388c590cb
commit 49a7e28216
25 changed files with 373 additions and 753 deletions
@@ -1,61 +0,0 @@
package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.concurrent.TimeUnit;
import com.threerings.getdown.classpath.cache.GarbageCollector;
import com.threerings.getdown.classpath.cache.ResourceCache;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.file.FileWalker;
/**
* Instead of building the class path from the application directory, this class path builder puts
* the files that make up the class path into a cache and assembles the class path from the cache
* directory.
*/
public class CacheBasedClassPathBuilder extends ClassPathBuilderBase
{
static final String CACHE_DIR = ".cache";
public CacheBasedClassPathBuilder(Application application)
{
super(application);
}
@Override
public ClassPath buildClassPath () throws IOException
{
File cacheDir = new File(_application.getAppdir(), CACHE_DIR);
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
if (_application.getCodeCacheRetentionDays() <= 0) {
runGarbageCollection(cacheDir);
}
ResourceCache cache = new ResourceCache(cacheDir);
LinkedHashSet<ClassPathElement> classPathEntries = new LinkedHashSet<ClassPathElement>();
for (Resource resource: _application.getActiveCodeResources()) {
classPathEntries.add(
new ClassPathElement(
cache.cacheFile(resource.getFinalTarget(), _application.getDigest(resource))));
}
if (_application.getCodeCacheRetentionDays() > 0) {
runGarbageCollection(cacheDir);
}
return new ClassPath(classPathEntries);
}
private void runGarbageCollection(File cacheDir) {
GarbageCollector gc = new GarbageCollector(
new FileWalker(cacheDir), _application.getCodeCacheRetentionDays(), TimeUnit.DAYS);
gc.collectGarbage();
}
}
@@ -1,6 +1,7 @@
package com.threerings.getdown.classpath; package com.threerings.getdown.classpath;
import java.io.File; import java.io.File;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Collections; import java.util.Collections;
@@ -14,13 +15,13 @@ import java.util.Set;
*/ */
public class ClassPath public class ClassPath
{ {
public ClassPath (LinkedHashSet<ClassPathElement> classPathEntries) public ClassPath (LinkedHashSet<File> classPathEntries)
{ {
this._classPathEntries = Collections.unmodifiableSet(classPathEntries); _classPathEntries = Collections.unmodifiableSet(classPathEntries);
} }
/** /**
* Returns the class path as an java command line argument string, e.g * Returns the class path as an java command line argument string, e.g.
* *
* <pre> * <pre>
* /path/to/a.jar:/path/to/b.jar * /path/to/a.jar:/path/to/b.jar
@@ -30,16 +31,10 @@ public class ClassPath
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
String delimiter = ""; String delimiter = "";
for (File entry: _classPathEntries) {
for (ClassPathElement entry: _classPathEntries) builder.append(delimiter).append(entry.getAbsolutePath());
{
builder
.append(delimiter)
.append(entry.getAbsolutePath());
delimiter = File.pathSeparator; delimiter = File.pathSeparator;
} }
return builder.toString(); return builder.toString();
} }
@@ -50,20 +45,27 @@ public class ClassPath
public URL[] asUrls () public URL[] asUrls ()
{ {
URL[] urls = new URL[_classPathEntries.size()]; URL[] urls = new URL[_classPathEntries.size()];
int i = 0; int i = 0;
for (File entry : _classPathEntries) {
for (ClassPathElement entry : _classPathEntries) { urls[i++] = getURL(entry);
urls[i++] = entry.getURL();
} }
return urls; return urls;
} }
public Set<ClassPathElement> getClassPathEntries () public Set<File> getClassPathEntries ()
{ {
return _classPathEntries; return _classPathEntries;
} }
private final Set<ClassPathElement> _classPathEntries;
private static URL getURL (File file)
{
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException("URL of file is illegal: " + file.getAbsolutePath(), e);
}
}
private final Set<File> _classPathEntries;
} }
@@ -1,13 +0,0 @@
package com.threerings.getdown.classpath;
import java.io.IOException;
/**
* A class path builder compiles the class path of the application to be launched.
*/
public interface ClassPathBuilder {
/**
* Builds and returns the class path of the application to be launched.
*/
ClassPath buildClassPath () throws IOException;
}
@@ -1,14 +0,0 @@
package com.threerings.getdown.classpath;
import com.threerings.getdown.data.Application;
/**
* Base class for {@link ClassPathBuilder class path builders}.
*/
abstract class ClassPathBuilderBase implements ClassPathBuilder {
public ClassPathBuilderBase(Application _application) {
this._application = _application;
}
protected final Application _application;
}
@@ -1,22 +0,0 @@
package com.threerings.getdown.classpath;
import com.threerings.getdown.data.Application;
/**
* A factory for {@link ClassPathBuilder class path builders}.
*/
public abstract class ClassPathBuilderFactory {
private ClassPathBuilderFactory () {
}
/**
* Creates a class path builder that is able to compile the class path of the supplied
* {@link Application application}.
*/
public static ClassPathBuilder create (Application application)
{
return application.isUseCodeCache()
? new CacheBasedClassPathBuilder(application)
: new DefaultClassPathBuilder(application);
}
}
@@ -1,47 +0,0 @@
package com.threerings.getdown.classpath;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Represents an element of an applications class path.
*/
public class ClassPathElement
{
public ClassPathElement (File _file)
{
this._file = _file;
}
/**
* Returns the code resource.
*/
public File getFile ()
{
return _file;
}
/**
* Returns the elements absolute path on the file system.
*/
public String getAbsolutePath ()
{
return _file.getAbsolutePath();
}
/**
* Returns the file URL of this class path element.
*/
public URL getURL ()
{
try {
return _file.toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException("URL of file is illegal: " + getAbsolutePath(), e);
}
}
private final File _file;
}
@@ -0,0 +1,76 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.concurrent.TimeUnit;
import com.threerings.getdown.classpath.cache.GarbageCollector;
import com.threerings.getdown.classpath.cache.ResourceCache;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
public class ClassPaths
{
static final String CACHE_DIR = ".cache";
/**
* Builds either a default or cached classpath based on {@code app}'s configuration.
*/
public static ClassPath buildClassPath (Application app) throws IOException
{
return app.useCodeCache() ? buildDefaultClassPath(app) : buildCachedClassPath(app);
}
/**
* Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
* the app directory.
*/
public static ClassPath buildDefaultClassPath (Application app)
{
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
for (Resource resource: app.getActiveCodeResources()) {
classPathEntries.add(resource.getFinalTarget());
}
return new ClassPath(classPathEntries);
}
/**
* Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into
* a cache directory and then referencing them from there. This avoids problems with
* overwriting in-use classpath elements when the application is later updated. This also
* "garbage collects" expired caches if necessary.
*/
public static ClassPath buildCachedClassPath (Application app) throws IOException
{
File cacheDir = new File(app.getAppdir(), CACHE_DIR);
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
if (app.getCodeCacheRetentionDays() <= 0) {
runGarbageCollection(app, cacheDir);
}
ResourceCache cache = new ResourceCache(cacheDir);
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
for (Resource resource: app.getActiveCodeResources()) {
File entry = cache.cacheFile(resource.getFinalTarget(), app.getDigest(resource));
classPathEntries.add(entry);
}
if (app.getCodeCacheRetentionDays() > 0) {
runGarbageCollection(app, cacheDir);
}
return new ClassPath(classPathEntries);
}
private static void runGarbageCollection (Application app, File cacheDir)
{
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
GarbageCollector.collect(cacheDir, retainMillis);
}
}
@@ -1,30 +0,0 @@
package com.threerings.getdown.classpath;
import java.util.LinkedHashSet;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
/**
* The default class path builder assembles the class path from the code resources to be found in
* the application directory.
*/
public class DefaultClassPathBuilder extends ClassPathBuilderBase
{
public DefaultClassPathBuilder(Application _application) {
super(_application);
}
@Override
public ClassPath buildClassPath ()
{
LinkedHashSet<ClassPathElement> classPathEntries = new LinkedHashSet<ClassPathElement>();
for (Resource resource: _application.getActiveCodeResources()) {
classPathEntries.add(new ClassPathElement(resource.getFinalTarget()));
}
return new ClassPath(classPathEntries);
}
}
@@ -1,10 +1,7 @@
package com.threerings.getdown.classpath.cache; package com.threerings.getdown.classpath.cache;
import java.io.File; import java.io.File;
import java.util.concurrent.TimeUnit; import com.threerings.getdown.util.FileUtil;
import com.threerings.getdown.util.file.FileVisitor;
import com.threerings.getdown.util.file.FileWalker;
/** /**
* Collects elements in the {@link ResourceCache cache} which became unused and deletes them * Collects elements in the {@link ResourceCache cache} which became unused and deletes them
@@ -12,80 +9,53 @@ import com.threerings.getdown.util.file.FileWalker;
*/ */
public class GarbageCollector public class GarbageCollector
{ {
public GarbageCollector (
FileWalker _fileWalker, int retentionPeriod, TimeUnit retentionPeriodTimeUnit)
{
this._fileWalker = _fileWalker;
this._retentionMillis = retentionPeriodTimeUnit.toMillis(retentionPeriod);
}
/** /**
* Collect and delete the garbage in the cache. * Collect and delete the garbage in the cache.
*/ */
public void collectGarbage () public static void collect (File cacheDir, final long retentionPeriodMillis)
{ {
_fileWalker.walkTree(new FileVisitor() { FileUtil.walkTree(cacheDir, new FileUtil.Visitor() {
@Override @Override public void visit (File file) {
public void visit(File file) File cachedFile = getCachedFile(file);
{ File lastAccessedFile = getLastAccessedFile(file);
collect(file); if (!cachedFile.exists() || !lastAccessedFile.exists()) {
if (cachedFile.exists()) {
cachedFile.delete();
} else {
lastAccessedFile.delete();
}
} else if (shouldDelete(lastAccessedFile, retentionPeriodMillis)) {
lastAccessedFile.delete();
cachedFile.delete();
}
File folder = file.getParentFile();
if (folder.list().length == 0) {
folder.delete();
}
} }
}); });
} }
private void collect (File file) { private static boolean shouldDelete (File lastAccessedFile, long retentionMillis)
File cachedFile = getCachedFile(file);
File lastAccessedFile = getLastAccessedFile(file);
if (!cachedFile.exists() || !lastAccessedFile.exists()) {
if (cachedFile.exists()) {
cachedFile.delete();
} else {
lastAccessedFile.delete();
}
} else if (shouldDelete(lastAccessedFile)) {
lastAccessedFile.delete();
cachedFile.delete();
}
File folder = file.getParentFile();
if (folder.list().length == 0) {
folder.delete();
}
}
private boolean shouldDelete(File lastAccessedFile) {
return System.currentTimeMillis() - lastAccessedFile.lastModified() > _retentionMillis;
}
private File getLastAccessedFile (File file)
{ {
if (isLastAccessedFile(file)) { return System.currentTimeMillis() - lastAccessedFile.lastModified() > retentionMillis;
return file;
}
return new File(
file.getParentFile(),
file.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
} }
private boolean isLastAccessedFile (File file) private static File getLastAccessedFile (File file)
{
return isLastAccessedFile(file) ? file : new File(
file.getParentFile(), file.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
}
private static boolean isLastAccessedFile (File file)
{ {
return file.getName().endsWith(ResourceCache.LAST_ACCESSED_FILE_SUFFIX); return file.getName().endsWith(ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
} }
private File getCachedFile (File file) private static File getCachedFile (File file)
{ {
if (!isLastAccessedFile(file)) { return !isLastAccessedFile(file) ? file : new File(
return file; file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf(".")));
}
return new File(
file.getParentFile(),
file.getName().substring(0, file.getName().lastIndexOf(".")));
} }
private final FileWalker _fileWalker;
private final long _retentionMillis;
} }
@@ -5,21 +5,9 @@
package com.threerings.getdown.data; package com.threerings.getdown.data;
import static com.threerings.getdown.Log.log;
import java.awt.Color; import java.awt.Color;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.io.BufferedReader; import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
@@ -28,46 +16,29 @@ import java.net.URLConnection;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException; import java.nio.channels.OverlappingFileLockException;
import java.security.AllPermission; import java.security.*;
import java.security.CodeSource;
import java.security.GeneralSecurityException;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Signature;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.swing.JApplet; import javax.swing.JApplet;
import org.apache.commons.codec.binary.Base64;
import com.samskivert.io.StreamUtil; import com.samskivert.io.StreamUtil;
import com.samskivert.text.MessageUtil; import com.samskivert.text.MessageUtil;
import com.samskivert.util.ArrayUtil; import com.samskivert.util.ArrayUtil;
import com.samskivert.util.RandomUtil; import com.samskivert.util.RandomUtil;
import com.samskivert.util.RunAnywhere; import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import org.apache.commons.codec.binary.Base64;
import com.threerings.getdown.classpath.ClassPaths;
import com.threerings.getdown.classpath.ClassPath; import com.threerings.getdown.classpath.ClassPath;
import com.threerings.getdown.classpath.ClassPathBuilderFactory;
import com.threerings.getdown.launcher.RotatingBackgrounds; import com.threerings.getdown.launcher.RotatingBackgrounds;
import com.threerings.getdown.util.ConfigUtil; import com.threerings.getdown.util.*;
import com.threerings.getdown.util.ConnectionUtil;
import com.threerings.getdown.util.FileUtil; import static com.threerings.getdown.Log.log;
import com.threerings.getdown.util.LaunchUtil;
import com.threerings.getdown.util.ProgressAggregator;
import com.threerings.getdown.util.ProgressObserver;
import com.threerings.getdown.util.VersionUtil;
/** /**
* Parses and provide access to the information contained in the <code>getdown.txt</code> * Parses and provide access to the information contained in the <code>getdown.txt</code>
@@ -257,10 +228,36 @@ public class Application
{ {
_appdir = appdir; _appdir = appdir;
_appid = appid; _appid = appid;
_signers = signers == null ? Collections.<Certificate>emptyList() : signers; _signers = (signers == null) ? Collections.<Certificate>emptyList() : signers;
_config = getLocalPath(CONFIG_FILE); _config = getLocalPath(CONFIG_FILE);
_extraJvmArgs = jvmargs == null ? ArrayUtil.EMPTY_STRING : jvmargs; _extraJvmArgs = (jvmargs == null) ? ArrayUtil.EMPTY_STRING : jvmargs;
_extraAppArgs = appargs == null ? ArrayUtil.EMPTY_STRING : appargs; _extraAppArgs = (appargs == null) ? ArrayUtil.EMPTY_STRING : appargs;
}
/**
* Returns the configured application directory.
*/
public File getAppdir()
{
return _appdir;
}
/**
* Returns whether the application should cache code resources prior to launching the
* application.
*/
public boolean useCodeCache ()
{
return _useCodeCache;
}
/**
* Returns the number of days a cached code resource is allowed to stay unused before it
* becomes eligible for deletion.
*/
public int getCodeCacheRetentionDays ()
{
return _codeCacheRetentionDays;
} }
/** /**
@@ -291,6 +288,14 @@ public class Application
return _resources; return _resources;
} }
/**
* Returns the digest of the given {@code resource}.
*/
public String getDigest (Resource resource)
{
return _digest.getDigest(resource);
}
/** /**
* Returns a list of all the active {@link Resource} objects used by this application (code and * Returns a list of all the active {@link Resource} objects used by this application (code and
* non-code). * non-code).
@@ -383,7 +388,7 @@ public class Application
return null; return null;
} }
String infix = auxgroup == null ? "" : "-" + auxgroup; String infix = (auxgroup == null) ? "" : ("-" + auxgroup);
String pfile = "patch" + infix + _version + ".dat"; String pfile = "patch" + infix + _version + ".dat";
try { try {
URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile)); URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile));
@@ -545,7 +550,7 @@ public class Application
// if we are a versioned deployment, create a versioned appbase // if we are a versioned deployment, create a versioned appbase
try { try {
_vappbase = _version < 0 ? new URL(_appbase) : createVAppBase(_version); _vappbase = (_version < 0) ? new URL(_appbase) : createVAppBase(_version);
} catch (MalformedURLException mue) { } catch (MalformedURLException mue) {
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase); String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
throw (IOException) new IOException(err).initCause(mue); throw (IOException) new IOException(err).initCause(mue);
@@ -562,7 +567,7 @@ public class Application
} }
} }
String appPrefix = StringUtil.isBlank(_appid) ? "" : _appid + "."; String appPrefix = StringUtil.isBlank(_appid) ? "" : (_appid + ".");
// determine our application class name // determine our application class name
_class = (String)cdata.get(appPrefix + "class"); _class = (String)cdata.get(appPrefix + "class");
@@ -689,8 +694,8 @@ public class Application
// whether to cache code resources and launch from cache // whether to cache code resources and launch from cache
_useCodeCache = Boolean.parseBoolean((String) cdata.get("use_code_cache")); _useCodeCache = Boolean.parseBoolean((String) cdata.get("use_code_cache"));
_codeCacheRetentionDays = cdata.containsKey("code_cache_retention_days") _codeCacheRetentionDays = cdata.containsKey("code_cache_retention_days") ?
? Integer.parseInt((String) cdata.get("use_code_cache")) : 7; Integer.parseInt((String) cdata.get("use_code_cache")) : 7;
// parse and return our application config // parse and return our application config
UpdateInterface ui = new UpdateInterface(); UpdateInterface ui = new UpdateInterface();
@@ -709,9 +714,9 @@ public class Application
} }
// and now ui.background can refer to the background color, but fall back to black // and now ui.background can refer to the background color, but fall back to black
// or white, depending on the brightness of the progressText // or white, depending on the brightness of the progressText
Color defaultBackground = .5f < Color.RGBtoHSB( Color defaultBackground = (.5f < Color.RGBtoHSB(
ui.progressText.getRed(), ui.progressText.getGreen(), ui.progressText.getBlue(), ui.progressText.getRed(), ui.progressText.getGreen(), ui.progressText.getBlue(),
null)[2] null)[2])
? Color.BLACK ? Color.BLACK
: Color.WHITE; : Color.WHITE;
ui.background = parseColor(cdata, "ui.background", defaultBackground); ui.background = parseColor(cdata, "ui.background", defaultBackground);
@@ -726,7 +731,7 @@ public class Application
// On an installation error, where do we point the user. // On an installation error, where do we point the user.
String installError = parseUrl(cdata, "ui.install_error", null); String installError = parseUrl(cdata, "ui.install_error", null);
ui.installError = installError == null ? ui.installError = (installError == null) ?
"m.default_install_error" : MessageUtil.taint(installError); "m.default_install_error" : MessageUtil.taint(installError);
// the patch notes bits // the patch notes bits
@@ -838,8 +843,8 @@ public class Application
} }
} }
boolean minVersionOK = _javaMinVersion == 0 || version >= _javaMinVersion; boolean minVersionOK = (_javaMinVersion == 0) || (version >= _javaMinVersion);
boolean maxVersionOK = _javaMaxVersion == 0 || version <= _javaMaxVersion; boolean maxVersionOK = (_javaMaxVersion == 0) || (version <= _javaMaxVersion);
return minVersionOK && maxVersionOK; return minVersionOK && maxVersionOK;
} catch (RuntimeException re) { } catch (RuntimeException re) {
@@ -937,7 +942,7 @@ public class Application
boolean dashJarMode = MANIFEST_CLASS.equals(_class); boolean dashJarMode = MANIFEST_CLASS.equals(_class);
// add the -classpath arguments if we're not in -jar mode // add the -classpath arguments if we're not in -jar mode
ClassPath classPath = ClassPathBuilderFactory.create(this).buildClassPath(); ClassPath classPath = ClassPaths.buildClassPath(this);
if (!dashJarMode) { if (!dashJarMode) {
args.add("-classpath"); args.add("-classpath");
@@ -1037,18 +1042,14 @@ public class Application
/** /**
* Runs this application directly in the current VM. * Runs this application directly in the current VM.
* @throws IOException
*/ */
public void invokeDirect (JApplet applet) throws IOException public void invokeDirect (JApplet applet) throws IOException
{ {
ClassPath classPath = ClassPathBuilderFactory.create(this).buildClassPath(); ClassPath classPath = ClassPaths.buildClassPath(this);
URL[] jarUrls = classPath.asUrls(); URL[] jarUrls = classPath.asUrls();
// create custom class loader // create custom class loader
URLClassLoader loader = new URLClassLoader( URLClassLoader loader = new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader()) {
jarUrls,
ClassLoader.getSystemClassLoader()) {
@Override protected PermissionCollection getPermissions (CodeSource code) { @Override protected PermissionCollection getPermissions (CodeSource code) {
Permissions perms = new Permissions(); Permissions perms = new Permissions();
perms.add(new AllPermission()); perms.add(new AllPermission());
@@ -1058,9 +1059,7 @@ public class Application
Thread.currentThread().setContextClassLoader(loader); Thread.currentThread().setContextClassLoader(loader);
log.info("Configured URL class loader:"); log.info("Configured URL class loader:");
for (URL url : jarUrls) { for (URL url : jarUrls) log.info(" " + url);
log.info(" " + url);
}
// configure any system properties that we can // configure any system properties that we can
for (String jvmarg : _jvmargs) { for (String jvmarg : _jvmargs) {
@@ -1169,7 +1168,7 @@ public class Application
if (_version == -1) { if (_version == -1) {
// make a note of the old meta-digest, if this changes we need to revalidate all of our // make a note of the old meta-digest, if this changes we need to revalidate all of our
// resources as one or more of them have also changed // resources as one or more of them have also changed
String olddig = _digest == null ? "" : _digest.getMetaDigest(); String olddig = (_digest == null) ? "" : _digest.getMetaDigest();
try { try {
status.updateStatus("m.checking"); status.updateStatus("m.checking");
downloadDigestFile(); downloadDigestFile();
@@ -1323,7 +1322,7 @@ public class Application
failures.add(rsrc); failures.add(rsrc);
} }
return failures.size() == 0 ? null : failures; return (failures.size() == 0) ? null : failures;
} }
/** /**
@@ -1381,24 +1380,6 @@ public class Application
return _version; return _version;
} }
/**
* Returns whether the application should cache code resources prior to launching the
* application.
*/
public boolean isUseCodeCache ()
{
return _useCodeCache;
}
/**
* Returns the number of days a cached code resource is allowed to stay unused before it
* becomes eligible for deletion.
*/
public int getCodeCacheRetentionDays ()
{
return _codeCacheRetentionDays;
}
/** /**
* Creates a versioned application base URL for the specified version. * Creates a versioned application base URL for the specified version.
*/ */
@@ -1644,7 +1625,7 @@ public class Application
{ {
String value = (String)cdata.get(name); String value = (String)cdata.get(name);
Rectangle rect = parseRect(name, value); Rectangle rect = parseRect(name, value);
return rect == null ? def : rect; return (rect == null) ? def : rect;
} }
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */ /** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
@@ -1689,7 +1670,7 @@ public class Application
{ {
String value = (String)cdata.get(name); String value = (String)cdata.get(name);
Color color = parseColor(value); Color color = parseColor(value);
return color == null ? def : color; return (color == null) ? def : color;
} }
/** /**
@@ -1712,7 +1693,7 @@ public class Application
protected String[] parseList (Map<String, Object> cdata, String name) protected String[] parseList (Map<String, Object> cdata, String name)
{ {
String value = (String)cdata.get(name); String value = (String)cdata.get(name);
return value == null ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value); return (value == null) ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value);
} }
/** /**
@@ -1788,19 +1769,6 @@ public class Application
} }
} }
/**
* Returns the digest of the given {@code resource}.
*/
public String getDigest (Resource resource)
{
return _digest.getDigest(resource);
}
public File getAppdir()
{
return _appdir;
}
protected File _appdir; protected File _appdir;
protected String _appid; protected String _appid;
protected File _config; protected File _config;
@@ -5,8 +5,6 @@
package com.threerings.getdown.data; package com.threerings.getdown.data;
import static com.threerings.getdown.Log.log;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -20,9 +18,12 @@ import java.util.List;
import com.samskivert.io.StreamUtil; import com.samskivert.io.StreamUtil;
import com.samskivert.text.MessageUtil; import com.samskivert.text.MessageUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.getdown.util.ConfigUtil; import com.threerings.getdown.util.ConfigUtil;
import com.threerings.getdown.util.ProgressObserver; import com.threerings.getdown.util.ProgressObserver;
import static com.threerings.getdown.Log.log;
/** /**
* Manages the <code>digest.txt</code> file and the computing and processing of MD5 digests for an * Manages the <code>digest.txt</code> file and the computing and processing of MD5 digests for an
* application. * application.
@@ -92,6 +93,14 @@ public class Digest
return false; return false;
} }
/**
* Returns the digest of the given {@code resource}.
*/
public String getDigest (Resource resource)
{
return _digests.get(resource.getPath());
}
/** /**
* Creates a digest file at the specified location using the supplied list of resources. * Creates a digest file at the specified location using the supplied list of resources.
*/ */
@@ -145,14 +154,6 @@ public class Digest
data.append(path).append(" = ").append(digest).append("\n"); data.append(path).append(" = ").append(digest).append("\n");
} }
/**
* Returns the digest of the given {@code resource}.
*/
public String getDigest (Resource resource)
{
return _digests.get(resource.getPath());
}
protected HashMap<String, String> _digests = new HashMap<String, String>(); protected HashMap<String, String> _digests = new HashMap<String, String>();
protected String _metaDigest = ""; protected String _metaDigest = "";
} }
@@ -5,16 +5,11 @@
package com.threerings.getdown.util; package com.threerings.getdown.util;
import static com.threerings.getdown.Log.log; import java.io.*;
import java.util.ArrayDeque;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import java.util.jar.Pack200; import java.util.jar.Pack200;
@@ -22,6 +17,8 @@ import java.util.zip.GZIPInputStream;
import com.samskivert.io.StreamUtil; import com.samskivert.io.StreamUtil;
import static com.threerings.getdown.Log.log;
/** /**
* File related utilities. * File related utilities.
*/ */
@@ -115,6 +112,7 @@ public class FileUtil extends com.samskivert.util.FileUtil
Pack200.Unpacker unpacker = Pack200.newUnpacker(); Pack200.Unpacker unpacker = Pack200.newUnpacker();
unpacker.unpack(packedJarIn, jarOutputStream); unpacker.unpack(packedJarIn, jarOutputStream);
return true; return true;
} catch (IOException e) { } catch (IOException e) {
log.warning("Failed to unpack packed 200 jar file", "jar", packedJar, "error", e); log.warning("Failed to unpack packed 200 jar file", "jar", packedJar, "error", e);
return false; return false;
@@ -129,18 +127,43 @@ public class FileUtil extends com.samskivert.util.FileUtil
/** /**
* Copies the given {@code source} file to the given {@code target}. * Copies the given {@code source} file to the given {@code target}.
*/ */
public static void copy(File source, File target) throws IOException { public static void copy (File source, File target) throws IOException {
FileInputStream in = null; FileInputStream in = null;
FileOutputStream out = null; FileOutputStream out = null;
try { try {
in = new FileInputStream(source); in = new FileInputStream(source);
out = new FileOutputStream(target); out = new FileOutputStream(target);
StreamUtil.copy(in, out); StreamUtil.copy(in, out);
} finally { } finally {
StreamUtil.close(in); StreamUtil.close(in);
StreamUtil.close(out); StreamUtil.close(out);
} }
} }
/**
* Used by {@link #walkTree}.
*/
public interface Visitor
{
void visit (File file);
}
/**
* Walks all files in {@code root}, calling {@code visitor} on each file in the tree.
*/
public static void walkTree (File root, Visitor visitor)
{
Deque<File> stack = new ArrayDeque<File>(Arrays.asList(root.listFiles()));
while (!stack.isEmpty()) {
File currentFile = stack.pop();
if (currentFile.exists()) {
visitor.visit(currentFile);
if (currentFile.isDirectory()) {
for (File file: currentFile.listFiles()) {
stack.push(file);
}
}
}
}
}
} }
@@ -10,7 +10,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import com.samskivert.io.StreamUtil;
import com.samskivert.util.RunAnywhere; import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
@@ -155,7 +154,7 @@ public class LaunchUtil
try { try {
// copy the moved file back to getdown-dop-new.jar so that we don't end up // copy the moved file back to getdown-dop-new.jar so that we don't end up
// downloading another copy next time // downloading another copy next time
StreamUtil.copy(new FileInputStream(curgd), new FileOutputStream(newgd)); FileUtil.copy(curgd, newgd);
} catch (IOException e) { } catch (IOException e) {
log.warning("Error copying updated Getdown back: " + e); log.warning("Error copying updated Getdown back: " + e);
} }
@@ -172,7 +171,7 @@ public class LaunchUtil
// that didn't work, let's try copying it // that didn't work, let's try copying it
log.info("Attempting to upgrade by copying over " + curgd + "..."); log.info("Attempting to upgrade by copying over " + curgd + "...");
try { try {
StreamUtil.copy(new FileInputStream(newgd), new FileOutputStream(curgd)); FileUtil.copy(newgd, curgd);
} catch (IOException ioe) { } catch (IOException ioe) {
log.warning("Mayday! Brute force copy method also failed.", ioe); log.warning("Mayday! Brute force copy method also failed.", ioe);
} }
@@ -1,6 +0,0 @@
package com.threerings.getdown.util.file;
import java.io.File;
public interface FileVisitor {
void visit(File file);
}
@@ -1,42 +0,0 @@
package com.threerings.getdown.util.file;
import java.io.File;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
/**
* Recursively walks over files and folders. A {@link FileVisitor visitor} is informed for each path
* traversed.
*/
public class FileWalker
{
public FileWalker (File start)
{
this._start = start;
}
/**
* Walks over the directory tree with the provided {@code visitor}.
*/
public void walkTree (FileVisitor visitor)
{
Deque<File> stack = new ArrayDeque<File>(Arrays.asList(_start.listFiles()));
while (!stack.isEmpty()) {
File currentFile = stack.pop();
if (currentFile.exists()) {
visitor.visit(currentFile);
if (currentFile.isDirectory()) {
for (File file: currentFile.listFiles()) {
stack.push(file);
}
}
}
}
}
private final File _start;
}
@@ -1,44 +0,0 @@
package com.threerings.getdown.classpath;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import com.threerings.getdown.util.FileUtil;
/**
* Tests {@link CacheBasedClassPathBuilderTest}.
*/
@RunWith(MockitoJUnitRunner.class)
public class CacheBasedClassPathBuilderTest extends ClassPathBuilderTestBase
{
@Test
public void shouldBuildCacheBasedClassPath () throws IOException
{
when(_application.getDigest(_firstJar)).thenReturn("first");
when(_application.getDigest(_secondJar)).thenReturn("second");
when(_application.getCodeCacheRetentionDays()).thenReturn(1);
File firstCachedJarFile = FileUtil.newFile(
_appdir.getRoot(),
CacheBasedClassPathBuilder.CACHE_DIR, "fi", "first.jar");
File secondCachedJarFile = FileUtil.newFile(
_appdir.getRoot(),
CacheBasedClassPathBuilder.CACHE_DIR, "se", "second.jar");
String expectedClassPath = firstCachedJarFile.getAbsolutePath()
+ File.pathSeparator
+ secondCachedJarFile.getAbsolutePath();
ClassPath classPath = new CacheBasedClassPathBuilder(_application).buildClassPath();
assertEquals(expectedClassPath, classPath.asArgumentString());
}
}
@@ -1,37 +0,0 @@
package com.threerings.getdown.classpath;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import com.threerings.getdown.data.Application;
/**
* Checks wether the correct {@link ClassPathBuilder class path builders} are created.
*/
@RunWith(MockitoJUnitRunner.class)
public class ClassPathBuilderFactoryTest
{
@Test
public void shouldCreateDefaultClassPathBuilder ()
{
when(_application.isUseCodeCache()).thenReturn(Boolean.FALSE);
assertTrue(ClassPathBuilderFactory.create(_application) instanceof DefaultClassPathBuilder);
}
@Test
public void shouldCreateCacheClassPathBuilder ()
{
when(_application.isUseCodeCache()).thenReturn(Boolean.TRUE);
assertTrue(ClassPathBuilderFactory.create(_application) instanceof CacheBasedClassPathBuilder);
}
@Mock
private Application _application;
}
@@ -1,48 +0,0 @@
package com.threerings.getdown.classpath;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
/**
* Base class for {@link ClassPathBuilder} tests.
*/
public abstract class ClassPathBuilderTestBase {
@Before
public void setupFilesAndResources () throws IOException
{
_firstJarFile = _appdir.newFile("a.jar");
_secondJarFile = _appdir.newFile("b.jar");
when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile);
when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile);
when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar));
when(_application.getAppdir()).thenReturn(_appdir.getRoot());
}
@Mock
protected Application _application;
@Mock
protected Resource _firstJar;
protected File _firstJarFile;
@Mock
protected Resource _secondJar;
protected File _secondJarFile;
@Rule
public TemporaryFolder _appdir = new TemporaryFolder();
}
@@ -1,7 +1,5 @@
package com.threerings.getdown.classpath; package com.threerings.getdown.classpath;
import static org.junit.Assert.assertEquals;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@@ -9,52 +7,43 @@ import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import org.junit.Before; import org.junit.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.assertEquals;
/** /**
* Tests for {@link ClassPath}. * Tests for {@link ClassPath}.
*/ */
public class ClassPathTest public class ClassPathTest
{ {
@Before @Before public void createJarsAndSetupClassPath () throws IOException
public void createJarsAndSetupClassPath () throws IOException
{ {
_firstJar = _folder.newFile("a.jar"); _firstJar = _folder.newFile("a.jar");
_secondJar = _folder.newFile("b.jar"); _secondJar = _folder.newFile("b.jar");
LinkedHashSet<ClassPathElement> classPathEntries = new LinkedHashSet<ClassPathElement>(); LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
classPathEntries.add(_firstJar);
classPathEntries.add(new ClassPathElement(_firstJar)); classPathEntries.add(_secondJar);
classPathEntries.add(new ClassPathElement(_secondJar));
_classPath = new ClassPath(classPathEntries); _classPath = new ClassPath(classPathEntries);
} }
@Test @Test public void shouldCreateValidArgumentString ()
public void shouldCreateValidArgumentString ()
{ {
assertEquals( assertEquals(
_firstJar.getAbsolutePath() + File.pathSeparator + _secondJar.getAbsolutePath(), _firstJar.getAbsolutePath() + File.pathSeparator + _secondJar.getAbsolutePath(),
_classPath.asArgumentString()); _classPath.asArgumentString());
} }
@Test @Test public void shouldProvideJarUrls () throws MalformedURLException, URISyntaxException
public void shouldProvideJarUrls () throws MalformedURLException, URISyntaxException
{ {
URL[] actualUrls = _classPath.asUrls(); URL[] actualUrls = _classPath.asUrls();
assertEquals(_firstJar, new File(actualUrls[0].toURI())); assertEquals(_firstJar, new File(actualUrls[0].toURI()));
assertEquals(_secondJar, new File(actualUrls[1].toURI())); assertEquals(_secondJar, new File(actualUrls[1].toURI()));
} }
@Rule @Rule public TemporaryFolder _folder = new TemporaryFolder();
public TemporaryFolder _folder = new TemporaryFolder();
private File _firstJar;
private File _secondJar;
private File _firstJar, _secondJar;
private ClassPath _classPath; private ClassPath _classPath;
} }
@@ -0,0 +1,68 @@
package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.FileUtil;
@RunWith(MockitoJUnitRunner.class)
public class ClassPathsTest
{
@Before public void setupFilesAndResources () throws IOException
{
_firstJarFile = _appdir.newFile("a.jar");
_secondJarFile = _appdir.newFile("b.jar");
when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile);
when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile);
when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar));
when(_application.getAppdir()).thenReturn(_appdir.getRoot());
}
@Test public void shouldBuildDefaultClassPath () throws IOException
{
ClassPath classPath = ClassPaths.buildDefaultClassPath(_application);
String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator +
_secondJarFile.getAbsolutePath();
assertEquals(expectedClassPath, classPath.asArgumentString());
}
@Test public void shouldBuildCachedClassPath () throws IOException
{
when(_application.getDigest(_firstJar)).thenReturn("first");
when(_application.getDigest(_secondJar)).thenReturn("second");
when(_application.getCodeCacheRetentionDays()).thenReturn(1);
File firstCachedJarFile = FileUtil.newFile(
_appdir.getRoot(), ClassPaths.CACHE_DIR, "fi", "first.jar");
File secondCachedJarFile = FileUtil.newFile(
_appdir.getRoot(), ClassPaths.CACHE_DIR, "se", "second.jar");
String expectedClassPath = firstCachedJarFile.getAbsolutePath() + File.pathSeparator +
secondCachedJarFile.getAbsolutePath();
ClassPath classPath = ClassPaths.buildCachedClassPath(_application);
assertEquals(expectedClassPath, classPath.asArgumentString());
}
@Mock protected Application _application;
@Mock protected Resource _firstJar;
@Mock protected Resource _secondJar;
protected File _firstJarFile, _secondJarFile;
@Rule public TemporaryFolder _appdir = new TemporaryFolder();
}
@@ -1,31 +0,0 @@
package com.threerings.getdown.classpath;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Tests the {@link DefaultClassPathBuilder}.
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultClassPathBuilderTest extends ClassPathBuilderTestBase
{
@Test
public void shouldBuildClassPath () throws IOException
{
ClassPath classPath = new DefaultClassPathBuilder(_application).buildClassPath();
String expectedClassPath = _firstJarFile.getAbsolutePath()
+ File.pathSeparator
+ _secondJarFile.getAbsolutePath();
assertEquals(expectedClassPath, classPath.asArgumentString());
}
}
@@ -1,90 +1,65 @@
package com.threerings.getdown.classpath.cache; package com.threerings.getdown.classpath.cache;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.junit.Before; import org.junit.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import com.threerings.getdown.util.file.FileWalker; import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
/** /**
* Validates that cache garbage is collected and deleted correctly. * Validates that cache garbage is collected and deleted correctly.
*/ */
public class GarbageCollectorTest public class GarbageCollectorTest
{ {
@Before @Before public void setupFiles () throws IOException
public void setupFiles () throws IOException
{ {
_cachedFile = _folder.newFile("abc123.jar"); _cachedFile = _folder.newFile("abc123.jar");
_lastAccessedFile = _folder.newFile("abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX); _lastAccessedFile = _folder.newFile("abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
} }
@Test @Test public void shouldDeleteCacheEntryIfRetentionPeriodIsReached ()
public void shouldDeleteCacheEntryIfRetentionPeriodIsReached ()
{ {
gcNow(); gcNow();
assertFalse(_cachedFile.exists()); assertFalse(_cachedFile.exists());
assertFalse(_lastAccessedFile.exists()); assertFalse(_lastAccessedFile.exists());
} }
@Test @Test public void shouldDeleteCacheFolderIfFolderIsEmpty ()
public void shouldDeleteCacheFolderIfFolderIsEmpty ()
{ {
gcNow(); gcNow();
assertFalse(_folder.getRoot().exists()); assertFalse(_folder.getRoot().exists());
} }
private void gcNow() { private void gcNow() {
GarbageCollector collector = new GarbageCollector( GarbageCollector.collect(_folder.getRoot(), -1);
new FileWalker(_folder.getRoot()), -1, TimeUnit.MILLISECONDS);
collector.collectGarbage();
} }
@Test @Test public void shouldKeepFilesInCacheIfRententionPeriodIsNotReached ()
public void shouldKeepFilesInCacheIfRententionPeriodIsNotReached ()
{ {
GarbageCollector collector = new GarbageCollector( GarbageCollector.collect(_folder.getRoot(), TimeUnit.DAYS.toMillis(1));
new FileWalker(_folder.getRoot()), 1, TimeUnit.DAYS);
collector.collectGarbage();
assertTrue(_cachedFile.exists()); assertTrue(_cachedFile.exists());
assertTrue(_lastAccessedFile.exists()); assertTrue(_lastAccessedFile.exists());
} }
@Test @Test public void shouldDeleteCachedFileIfLastAccessedFileIsMissing ()
public void shouldDeleteCachedFileIfLastAccessedFileIsMissing ()
{ {
assumeTrue(_lastAccessedFile.delete()); assumeTrue(_lastAccessedFile.delete());
gcNow(); gcNow();
assertFalse(_cachedFile.exists()); assertFalse(_cachedFile.exists());
} }
@Test @Test public void shouldDeleteLastAccessedFileIfCachedFileIsMissing ()
public void shouldDeleteLastAccessedFileIfCachedFileIsMissing ()
{ {
assumeTrue(_cachedFile.delete()); assumeTrue(_cachedFile.delete());
gcNow(); gcNow();
assertFalse(_lastAccessedFile.exists()); assertFalse(_lastAccessedFile.exists());
} }
@Rule @Rule public TemporaryFolder _folder = new TemporaryFolder();
public TemporaryFolder _folder = new TemporaryFolder();
private File _cachedFile; private File _cachedFile;
private File _lastAccessedFile; private File _lastAccessedFile;
@@ -1,30 +1,25 @@
package com.threerings.getdown.classpath.cache; package com.threerings.getdown.classpath.cache;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.junit.Before; import org.junit.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.*;
/** /**
* Asserts the correct functionality of the {@link ResourceCache}. * Asserts the correct functionality of the {@link ResourceCache}.
*/ */
public class ResourceCacheTest public class ResourceCacheTest
{ {
@Before @Before public void setupCache () throws IOException {
public void setupCache () throws IOException {
_fileToCache = _folder.newFile("filetocache.jar"); _fileToCache = _folder.newFile("filetocache.jar");
_cache = new ResourceCache(_folder.newFolder(".cache")); _cache = new ResourceCache(_folder.newFolder(".cache"));
} }
@Test @Test public void shouldCacheFile () throws IOException
public void shouldCacheFile () throws IOException
{ {
assertEquals("abc123.jar", cacheFile().getName()); assertEquals("abc123.jar", cacheFile().getName());
} }
@@ -34,53 +29,36 @@ public class ResourceCacheTest
return _cache.cacheFile(_fileToCache, "abc123"); return _cache.cacheFile(_fileToCache, "abc123");
} }
@Test @Test public void shouldTrackFileUsage () throws IOException
public void shouldTrackFileUsage () throws IOException
{ {
File lastAccessedFile = new File( String name = "abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX;
cacheFile().getParentFile(), File lastAccessedFile = new File(cacheFile().getParentFile(), name);
"abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
assertTrue(lastAccessedFile.exists()); assertTrue(lastAccessedFile.exists());
} }
@Test @Test public void shouldNotCacheTheSameFile () throws Exception
public void shouldNotCacheTheSameFile () throws Exception
{ {
File cachedFile = cacheFile(); File cachedFile = cacheFile();
cachedFile.setLastModified(YESTERDAY); cachedFile.setLastModified(YESTERDAY);
long expectedLastModified = cachedFile.lastModified(); long expectedLastModified = cachedFile.lastModified();
// caching it another time // caching it another time
File sameCachedFile = cacheFile(); File sameCachedFile = cacheFile();
assertEquals(expectedLastModified, sameCachedFile.lastModified()); assertEquals(expectedLastModified, sameCachedFile.lastModified());
} }
@Test @Test public void shouldRememberWhenFileWasRequested () throws Exception
public void shouldRememberWhenFileWasRequested () throws Exception
{ {
File cachedFile = cacheFile(); File cachedFile = cacheFile();
String name = cachedFile.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX;
File lastAccessedFile = new File( File lastAccessedFile = new File(cachedFile.getParentFile(), name);
cachedFile.getParentFile(),
cachedFile.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
lastAccessedFile.setLastModified(YESTERDAY); lastAccessedFile.setLastModified(YESTERDAY);
long lastAccessed = lastAccessedFile.lastModified(); long lastAccessed = lastAccessedFile.lastModified();
// caching it another time // caching it another time
cacheFile(); cacheFile();
assertTrue(lastAccessedFile.lastModified() > lastAccessed); assertTrue(lastAccessedFile.lastModified() > lastAccessed);
} }
@Rule @Rule public TemporaryFolder _folder = new TemporaryFolder();
public TemporaryFolder _folder = new TemporaryFolder();
private File _fileToCache; private File _fileToCache;
private ResourceCache _cache; private ResourceCache _cache;
@@ -5,49 +5,56 @@
package com.threerings.getdown.util; package com.threerings.getdown.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.List; import java.util.List;
import org.junit.Rule; import org.junit.*;
import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.*;
/** /**
* Tests {@link FileUtil}. * Tests {@link FileUtil}.
*/ */
public class FileUtilTest public class FileUtilTest
{ {
@Test @Test public void testReadLines () throws IOException
public void testReadLines () throws IOException
{ {
String data = "This is a test\nof a file with\na few lines\n"; String data = "This is a test\nof a file with\na few lines\n";
List<String> lines = FileUtil.readLines(new StringReader(data)); List<String> lines = FileUtil.readLines(new StringReader(data));
String[] linesBySplit = data.split("\n"); String[] linesBySplit = data.split("\n");
assertEquals(linesBySplit.length, lines.size()); assertEquals(linesBySplit.length, lines.size());
for (int i = 0; i < lines.size(); i++) { for (int ii = 0; ii < lines.size(); ii++) {
assertEquals(linesBySplit[i], lines.get(i)); assertEquals(linesBySplit[ii], lines.get(ii));
} }
} }
@Test @Test public void shouldCopyFile () throws IOException
public void shouldCopyFile () throws IOException
{ {
File source = _folder.newFile("source.txt"); File source = _folder.newFile("source.txt");
File target = new File(_folder.getRoot(), "target.txt"); File target = new File(_folder.getRoot(), "target.txt");
assertFalse(target.exists()); assertFalse(target.exists());
FileUtil.copy(source, target); FileUtil.copy(source, target);
assertTrue(target.exists()); assertTrue(target.exists());
} }
@Rule @Test public void shouldRecursivelyWalkOverFilesAndFolders () throws IOException
public TemporaryFolder _folder = new TemporaryFolder(); {
_folder.newFile("a.txt");
new File(_folder.newFolder("b"), "b.txt").createNewFile();
class CountingVisitor implements FileUtil.Visitor {
int fileCount = 0;
@Override public void visit(File file) {
fileCount++;
}
}
CountingVisitor visitor = new CountingVisitor();
FileUtil.walkTree(_folder.getRoot(), visitor);
assertEquals(3, visitor.fileCount);
}
@Rule public TemporaryFolder _folder = new TemporaryFolder();
} }
@@ -1,41 +0,0 @@
package com.threerings.getdown.util.file;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
/**
* Class under test: {@link FileWalker}.
*/
public class FileWalkerTest
{
@Test
public void shouldRecursivelyWalkOverFilesAndFolders () throws IOException
{
_folder.newFile("a.txt");
new File(_folder.newFolder("b"), "b.txt").createNewFile();
CountingVisitor visitor = new CountingVisitor();
new FileWalker(_folder.getRoot()).walkTree(visitor);
assertEquals(3, visitor.fileCount);
}
public static class CountingVisitor implements FileVisitor
{
int fileCount = 0;
@Override
public void visit(File file) {
fileCount++;
}
}
@Rule
public TemporaryFolder _folder = new TemporaryFolder();
}