Eliminate samskivert dependency in core.

I've long wanted to do this, but the launcher's use of all the samskivert UI
stuff made it onerous. Now that the launcher is a separate module, we can
remove the samskivert dependency in core (and tools), which simplifies things
and makes it easier to depend on core in a (future) self-updating app (when we
add support for that).
This commit is contained in:
Michael Bayne
2018-09-04 13:42:40 -07:00
parent 852d15a033
commit 97ec8632ae
17 changed files with 573 additions and 53 deletions
-5
View File
@@ -13,11 +13,6 @@
<description>Core Getdown functionality</description>
<dependencies>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>samskivert</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -5,13 +5,129 @@
package com.threerings.getdown;
import com.samskivert.util.Logger;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.*;
/**
* A placeholder class that contains a reference to the log object used by the Getdown code.
*/
public class Log
{
/** We dispatch our log messages through this logger. */
public static final Logger log = Logger.getLogger("com.threerings.getdown");
public static class Shim {
/**
* Logs a debug message.
*
* @param message the message to be logged.
* @param args a list of key/value pairs and an optional final Throwable.
*/
public void debug (Object message, Object... args) { doLog(0, message, args); }
/**
* Logs an info message.
*
* @param message the message to be logged.
* @param args a list of key/value pairs and an optional final Throwable.
*/
public void info (Object message, Object... args) { doLog(1, message, args); }
/**
* Logs a warning message.
*
* @param message the message to be logged.
* @param args a list of key/value pairs and an optional final Throwable.
*/
public void warning (Object message, Object... args) { doLog(2, message, args); }
protected void doLog (int levIdx, Object message, Object[] args) {
if (_impl.isLoggable(LEVELS[levIdx])) {
Throwable err = null;
int nn = args.length;
if (message instanceof Throwable) {
err = (Throwable)message;
} else if (nn % 2 == 1 && (args[nn - 1] instanceof Throwable)) {
err = (Throwable)args[--nn];
}
_impl.log(LEVELS[levIdx], format(message, args), err);
}
}
protected final Logger _impl = Logger.getLogger("com.threerings.getdown");
}
/** We dispatch our log messages through this logging shim. */
public static final Shim log = new Shim();
public static String format (Object message, Object... args) {
if (args.length == 0) return String.valueOf(message);
StringBuilder buf = new StringBuilder(String.valueOf(message));
if (buf.length() > 0) {
buf.append(' ');
}
buf.append('[');
for (int ii = 0; ii < args.length; ii += 2) {
if (ii > 0) {
buf.append(',').append(' ');
}
buf.append(args[ii]).append('=');
try {
buf.append(args[ii+1]);
} catch (Throwable t) {
buf.append("<toString() failure: ").append(t).append(">");
}
}
return buf.append(']').toString();
}
static {
Formatter formatter = new OneLineFormatter();
Logger logger = LogManager.getLogManager().getLogger("");
for (Handler handler : logger.getHandlers()) {
handler.setFormatter(formatter);
}
}
protected static class OneLineFormatter extends Formatter {
@Override public String format (LogRecord record) {
StringBuffer buf = new StringBuffer();
// append the timestamp
_date.setTime(record.getMillis());
_format.format(_date, buf, _fpos);
// append the log level
buf.append(" ");
buf.append(record.getLevel().getLocalizedName());
buf.append(" ");
// append the message itself
buf.append(formatMessage(record));
buf.append(System.lineSeparator());
// if an exception was also provided, append that
if (record.getThrown() != null) {
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
record.getThrown().printStackTrace(pw);
pw.close();
buf.append(sw.toString());
} catch (Exception ex) {
buf.append("Format failure:").append(ex);
}
}
return buf.toString();
}
protected Date _date = new Date();
protected SimpleDateFormat _format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss:SSS");
protected FieldPosition _fpos = new FieldPosition(SimpleDateFormat.DATE_FIELD);
}
protected static final String DATE_FORMAT = "{0,date} {0,time}";
protected static final Level[] LEVELS = {Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE};
}
@@ -25,13 +25,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import com.samskivert.io.StreamUtil;
import com.samskivert.text.MessageUtil;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.RandomUtil;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.classpath.ClassPaths;
import com.threerings.getdown.classpath.ClassPath;
import com.threerings.getdown.util.*;
@@ -229,8 +222,8 @@ public class Application
_appid = appid;
_signers = (signers == null) ? Collections.<Certificate>emptyList() : signers;
_config = getLocalPath(CONFIG_FILE);
_extraJvmArgs = (jvmargs == null) ? ArrayUtil.EMPTY_STRING : jvmargs;
_extraAppArgs = (appargs == null) ? ArrayUtil.EMPTY_STRING : appargs;
_extraJvmArgs = (jvmargs == null) ? EMPTY_STRING_ARRAY : jvmargs;
_extraAppArgs = (appargs == null) ? EMPTY_STRING_ARRAY : appargs;
}
/**
@@ -952,7 +945,7 @@ public class Application
}
// we love our Mac users, so we do nice things to preserve our application identity
if (RunAnywhere.isMacOS()) {
if (LaunchUtil.isMacOS()) {
args.add("-Xdock:icon=" + getLocalPath(_dockIconPath).getAbsolutePath());
args.add("-Xdock:name=" + _name);
}
@@ -1097,7 +1090,7 @@ public class Application
try {
log.info("Loading " + _class);
Class<?> appclass = loader.loadClass(_class);
Method main = appclass.getMethod("main", SA_PROTO.getClass());
Method main = appclass.getMethod("main", EMPTY_STRING_ARRAY.getClass());
log.info("Invoking main({" + StringUtil.join(args, ", ") + "})");
main.invoke(null, new Object[] { args });
} catch (Exception e) {
@@ -1674,7 +1667,8 @@ public class Application
_trackingStart = time;
}
if (_trackingId == 0) {
_trackingId = RandomUtil.getInRange(100000000, 1000000000);
int low = 100000000, high = 1000000000;
_trackingId = low + _rando.nextInt(high-low);
}
StringBuilder cookie = new StringBuilder("&utmcc=__utma%3D").append(_trackingGAHash);
cookie.append(".").append(_trackingId);
@@ -1683,7 +1677,8 @@ public class Application
cookie.append("__utmz%3D").append(_trackingGAHash).append(".");
cookie.append(_trackingStart).append(".1.1.");
cookie.append("utmcsr%3D(direct)%7Cutmccn%3D(direct)%7Cutmcmd%3D(none)%3B");
cookie.append("&utmn=").append(RandomUtil.getInRange(1000000000, 2000000000));
int low = 1000000000, high = 2000000000;
cookie.append("&utmn=").append(_rando.nextInt(high-low));
return cookie.toString();
}
@@ -1764,7 +1759,9 @@ public class Application
/** Channel to the file underlying _lock. Kept around solely so the lock doesn't close. */
protected FileChannel _lockChannel;
protected static final String[] SA_PROTO = ArrayUtil.EMPTY_STRING;
protected Random _rando = new Random();
protected static final String[] EMPTY_STRING_ARRAY = new String[0];
protected static final String ENV_VAR_PREFIX = "%ENV.";
protected static final Pattern ENV_VAR_PATTERN = Pattern.compile("%ENV\\.(.*?)%");
@@ -12,11 +12,10 @@ import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.*;
import com.samskivert.text.MessageUtil;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.util.Config;
import com.threerings.getdown.util.MessageUtil;
import com.threerings.getdown.util.ProgressObserver;
import com.threerings.getdown.util.StringUtil;
import static com.threerings.getdown.Log.log;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -16,10 +16,9 @@ import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.util.FileUtil;
import com.threerings.getdown.util.ProgressObserver;
import com.threerings.getdown.util.StringUtil;
import static com.threerings.getdown.Log.log;
@@ -20,8 +20,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.util.StringUtil;
import static com.threerings.getdown.Log.log;
@@ -312,7 +311,7 @@ public class Config
public String[] getList (String name)
{
String value = getString(name);
return (value == null) ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value);
return (value == null) ? new String[0] : StringUtil.parseStringArray(value);
}
/**
@@ -10,9 +10,7 @@ import java.util.*;
import java.util.jar.*;
import java.util.zip.GZIPInputStream;
import com.samskivert.io.StreamUtil;
import com.samskivert.util.Logger;
import com.threerings.getdown.Log;
import static com.threerings.getdown.Log.log;
/**
@@ -125,7 +123,7 @@ public class FileUtil
StreamUtil.copy(jin, fout);
} catch (Exception e) {
throw new IOException(
Logger.format("Failure unpacking", "jar", jar, "entry", efile), e);
Log.format("Failure unpacking", "jar", jar, "entry", efile), e);
}
}
}
@@ -11,9 +11,6 @@ import java.io.IOException;
import java.io.PrintStream;
import java.util.Locale;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
import static com.threerings.getdown.Log.log;
/**
@@ -111,7 +108,7 @@ public class LaunchUtil
// from /usr/bin/java, and not if launched by directly referring to <java.home>/bin/java,
// even though the former is a symlink to the latter! To work around this, see if the
// desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead.
if (RunAnywhere.isMacOS()) {
if (isMacOS()) {
try {
File localVM = new File("/usr/bin/java").getCanonicalFile();
if (localVM.equals(new File(vmpath).getCanonicalFile())) {
@@ -188,6 +185,21 @@ public class LaunchUtil
return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1);
}
/**
* Returns true if we're running in a JVM that identifies its operating system as Windows.
*/
public static final boolean isWindows () { return _isWindows; }
/**
* Returns true if we're running in a JVM that identifies its operating system as MacOS.
*/
public static final boolean isMacOS () { return _isMacOS; }
/**
* Returns true if we're running in a JVM that identifies its operating system as Linux.
*/
public static final boolean isLinux () { return _isLinux; }
/**
* Checks whether a Java Virtual Machine can be located in the supplied path.
*/
@@ -213,4 +225,24 @@ public class LaunchUtil
return null;
}
/** Flag indicating that we're on Windows; initialized when this class is first loaded. */
protected static boolean _isWindows;
/** Flag indicating that we're on MacOS; initialized when this class is first loaded. */
protected static boolean _isMacOS;
/** Flag indicating that we're on Linux; initialized when this class is first loaded. */
protected static boolean _isLinux;
static {
try {
String osname = System.getProperty("os.name");
osname = (osname == null) ? "" : osname;
_isWindows = (osname.indexOf("Windows") != -1);
_isMacOS = (osname.indexOf("Mac OS") != -1 ||
osname.indexOf("MacOS") != -1);
_isLinux = (osname.indexOf("Linux") != -1);
} catch (Exception e) {
// can't grab system properties; we'll just pretend we're not on any of these OSes
}
}
}
@@ -0,0 +1,88 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.util;
public class MessageUtil {
/**
* Composes a message key with an array of arguments. The message can subsequently be
* decomposed and translated without prior knowledge of how many arguments were provided.
*/
public static String compose (String key, Object... args)
{
StringBuilder buf = new StringBuilder();
buf.append(key);
buf.append('|');
for (int i = 0; i < args.length; i++) {
if (i > 0) {
buf.append('|');
}
// escape the string while adding to the buffer
String arg = (args[i] == null) ? "" : String.valueOf(args[i]);
int alength = arg.length();
for (int p = 0; p < alength; p++) {
char ch = arg.charAt(p);
if (ch == '|') {
buf.append("\\!");
} else if (ch == '\\') {
buf.append("\\\\");
} else {
buf.append(ch);
}
}
}
return buf.toString();
}
/**
* Compose a message with String args. This is just a convenience so callers do not have to
* cast their String[] to an Object[].
*/
public static String compose (String key, String... args)
{
return compose(key, (Object[]) args);
}
/**
* Call this to "taint" any string that has been entered by an entity outside the application
* so that the translation code knows not to attempt to translate this string when doing
* recursive translations.
*/
public static String taint (Object text)
{
return TAINT_CHAR + text;
}
/**
* A convenience method for calling {@link #compose(String,Object[])} with an array of
* arguments that will be automatically tainted (see {@link #taint}).
*/
public static String tcompose (String key, Object... args)
{
int acount = args.length;
String[] targs = new String[acount];
for (int ii = 0; ii < acount; ii++) {
targs[ii] = taint(args[ii]);
}
return compose(key, (Object[]) targs);
}
/**
* A convenience method for calling {@link #compose(String,String[])} with an array of argument
* that will be automatically tainted.
*/
public static String tcompose (String key, String... args)
{
for (int ii = 0, nn = args.length; ii < nn; ii++) {
args[ii] = taint(args[ii]);
}
return compose(key, args);
}
/** Text prefixed by this character will be considered tainted when doing recursive
* translations and won't be translated. */
protected static final String TAINT_CHAR = "~";
}
@@ -0,0 +1,96 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import static com.threerings.getdown.Log.log;
public class StreamUtil {
/**
* Convenient close for a stream. Use in a finally clause and love life.
*/
public static void close (InputStream in)
{
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
log.warning("Error closing input stream", "stream", in, "cause", ioe);
}
}
}
/**
* Convenient close for a stream. Use in a finally clause and love life.
*/
public static void close (OutputStream out)
{
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
log.warning("Error closing output stream", "stream", out, "cause", ioe);
}
}
}
/**
* Convenient close for a Reader. Use in a finally clause and love life.
*/
public static void close (Reader in)
{
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
log.warning("Error closing reader", "reader", in, "cause", ioe);
}
}
}
/**
* Convenient close for a Writer. Use in a finally clause and love life.
*/
public static void close (Writer out)
{
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
log.warning("Error closing writer", "writer", out, "cause", ioe);
}
}
}
/**
* Copies the contents of the supplied input stream to the supplied output stream.
*/
public static <T extends OutputStream> T copy (InputStream in, T out)
throws IOException
{
byte[] buffer = new byte[4096];
for (int read = 0; (read = in.read(buffer)) > 0; ) {
out.write(buffer, 0, read);
}
return out;
}
/**
* Reads the contents of the supplied stream into a byte array.
*/
public static byte[] toByteArray (InputStream stream)
throws IOException
{
return copy(stream, new ByteArrayOutputStream()).toByteArray();
}
}
@@ -0,0 +1,198 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.util;
import java.util.StringTokenizer;
public class StringUtil {
/**
* @return true if the string is null or consists only of whitespace, false otherwise.
*/
public static boolean isBlank (String value)
{
for (int ii = 0, ll = (value == null) ? 0 : value.length(); ii < ll; ii++) {
if (!Character.isWhitespace(value.charAt(ii))) {
return false;
}
}
return true;
}
/**
* Parses an array of integers from it's string representation. The array should be represented
* as a bare list of numbers separated by commas, for example:
*
* <pre>25, 17, 21, 99</pre>
*
* Any inability to parse the int array will result in the function returning null.
*/
public static int[] parseIntArray (String source)
{
StringTokenizer tok = new StringTokenizer(source, ",");
int[] vals = new int[tok.countTokens()];
for (int i = 0; tok.hasMoreTokens(); i++) {
try {
// trim the whitespace from the token
vals[i] = Integer.parseInt(tok.nextToken().trim());
} catch (NumberFormatException nfe) {
return null;
}
}
return vals;
}
/**
* Parses an array of strings from a single string. The array should be represented as a bare
* list of strings separated by commas, for example:
*
* <pre>mary, had, a, little, lamb, and, an, escaped, comma,,</pre>
*
* If a comma is desired in one of the strings, it should be escaped by putting two commas in a
* row. Any inability to parse the string array will result in the function returning null.
*/
public static String[] parseStringArray (String source)
{
return parseStringArray(source, false);
}
/**
* Like {@link #parseStringArray(String)} but can be instructed to invoke {@link String#intern}
* on the strings being parsed into the array.
*/
public static String[] parseStringArray (String source, boolean intern)
{
int tcount = 0, tpos = -1, tstart = 0;
// empty strings result in zero length arrays
if (source.length() == 0) {
return new String[0];
}
// sort out escaped commas
source = source.replace(",,", "%COMMA%");
// count up the number of tokens
while ((tpos = source.indexOf(",", tpos+1)) != -1) {
tcount++;
}
String[] tokens = new String[tcount+1];
tpos = -1; tcount = 0;
// do the split
while ((tpos = source.indexOf(",", tpos+1)) != -1) {
tokens[tcount] = source.substring(tstart, tpos);
tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ",");
if (intern) {
tokens[tcount] = tokens[tcount].intern();
}
tstart = tpos+1;
tcount++;
}
// grab the last token
tokens[tcount] = source.substring(tstart);
tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ",");
return tokens;
}
/**
* @return the supplied string if it is non-null, "" if it is null.
*/
public static String deNull (String value)
{
return (value == null) ? "" : value;
}
/**
* Generates a string from the supplied bytes that is the HEX encoded representation of those
* bytes. Returns the empty string for a <code>null</code> or empty byte array.
*
* @param bytes the bytes for which we want a string representation.
* @param count the number of bytes to stop at (which will be coerced into being {@code <=} the
* length of the array).
*/
public static String hexlate (byte[] bytes, int count)
{
if (bytes == null) {
return "";
}
count = Math.min(count, bytes.length);
char[] chars = new char[count*2];
for (int i = 0; i < count; i++) {
int val = bytes[i];
if (val < 0) {
val += 256;
}
chars[2*i] = XLATE.charAt(val/16);
chars[2*i+1] = XLATE.charAt(val%16);
}
return new String(chars);
}
/**
* Generates a string from the supplied bytes that is the HEX encoded representation of those
* bytes.
*/
public static String hexlate (byte[] bytes)
{
return (bytes == null) ? "" : hexlate(bytes, bytes.length);
}
/**
* Joins an array of strings (or objects which will be converted to strings) into a single
* string separated by commas.
*/
public static String join (Object[] values)
{
return join(values, false);
}
/**
* Joins an array of strings into a single string, separated by commas, and optionally escaping
* commas that occur in the individual string values such that a subsequent call to {@link
* #parseStringArray} would recreate the string array properly. Any elements in the values
* array that are null will be treated as an empty string.
*/
public static String join (Object[] values, boolean escape)
{
return join(values, ", ", escape);
}
/**
* Joins the supplied array of strings into a single string separated by the supplied
* separator.
*/
public static String join (Object[] values, String separator)
{
return join(values, separator, false);
}
/**
* Helper function for the various <code>join</code> methods.
*/
protected static String join (Object[] values, String separator, boolean escape)
{
StringBuilder buf = new StringBuilder();
int vlength = values.length;
for (int i = 0; i < vlength; i++) {
if (i > 0) {
buf.append(separator);
}
String value = (values[i] == null) ? "" : values[i].toString();
buf.append((escape) ? value.replace(",", ",,") : value);
}
return buf.toString();
}
/** Used by {@link #hexlate} and {@link #unhexlate}. */
protected static final String XLATE = "0123456789abcdef";
}
@@ -15,8 +15,6 @@ import java.io.PrintStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.data.SysProps;
import static com.threerings.getdown.Log.log;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -2,6 +2,7 @@ package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import org.junit.*;
@@ -13,7 +14,6 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
import com.samskivert.util.FileUtil;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
@@ -45,14 +45,14 @@ public class ClassPathsTest
when(_application.getDigest(_secondJar)).thenReturn("second");
when(_application.getCodeCacheRetentionDays()).thenReturn(1);
File firstCachedJarFile = FileUtil.newFile(
_appdir.getRoot(), ClassPaths.CACHE_DIR, "fi", "first.jar");
Path firstCachedJarFile = _appdir.getRoot().toPath().
resolve(ClassPaths.CACHE_DIR).resolve("fi").resolve("first.jar");
File secondCachedJarFile = FileUtil.newFile(
_appdir.getRoot(), ClassPaths.CACHE_DIR, "se", "second.jar");
Path secondCachedJarFile = _appdir.getRoot().toPath().
resolve(ClassPaths.CACHE_DIR).resolve("se").resolve("second.jar");
String expectedClassPath = firstCachedJarFile.getAbsolutePath() + File.pathSeparator +
secondCachedJarFile.getAbsolutePath();
String expectedClassPath = firstCachedJarFile.toAbsolutePath() + File.pathSeparator +
secondCachedJarFile.toAbsolutePath();
ClassPath classPath = ClassPaths.buildCachedClassPath(_application);
assertEquals(expectedClassPath, classPath.asArgumentString());
@@ -8,8 +8,7 @@ package com.threerings.getdown.util;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import com.samskivert.util.RandomUtil;
import java.util.Random;
import org.junit.*;
import static org.junit.Assert.*;
@@ -165,6 +164,8 @@ public class ConfigTest
protected static String whitespace ()
{
return RandomUtil.getBoolean() ? " " : "";
return _rando.nextBoolean() ? " " : "";
}
protected static Random _rando = new Random();
}
+5
View File
@@ -30,6 +30,11 @@
<artifactId>getdown-tools</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>samskivert</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>jregistrykey</groupId>
<artifactId>jregistrykey</artifactId>
@@ -21,11 +21,11 @@ import java.util.zip.ZipEntry;
import java.security.MessageDigest;
import com.samskivert.io.StreamUtil;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Digest;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.FileUtil;
import com.threerings.getdown.util.StreamUtil;
/**
* Generates patch files between two particular revisions of an
@@ -15,10 +15,9 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import com.samskivert.io.StreamUtil;
import com.threerings.getdown.util.FileUtil;
import com.threerings.getdown.util.ProgressObserver;
import com.threerings.getdown.util.StreamUtil;
import static com.threerings.getdown.Log.log;