Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e60d2753 | ||
|
|
ff0750f1f7 | ||
|
|
f8eec1b7d5 | ||
|
|
d2162f31e4 | ||
|
|
fee3e5251d | ||
|
|
409fc83819 | ||
|
|
54e14a25da |
No files matched your search
+1
-1
@@ -26,7 +26,7 @@ if (!hasProperty('mainClass')) {
|
||||
ext.mainClass = 'org.jackhuang.hellominecraft.launcher.Main'
|
||||
}
|
||||
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") == null ? ".7" : "."+System.getenv("BUILD_NUMBER")
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") == null ? ".8" : "."+System.getenv("BUILD_NUMBER")
|
||||
|
||||
String mavenGroupId = 'HMCL'
|
||||
String mavenVersion = '2.3.5' + buildnumber
|
||||
|
||||
@@ -47,8 +47,6 @@ import org.jackhuang.hellominecraft.lookandfeel.HelloMinecraftLookAndFeel;
|
||||
import org.jackhuang.hellominecraft.utils.MathUtils;
|
||||
import org.jackhuang.hellominecraft.utils.StrUtils;
|
||||
import org.jackhuang.hellominecraft.utils.VersionNumber;
|
||||
import org.jackhuang.hellominecraft.utils.system.Compressor;
|
||||
import rx.concurrency.Schedulers;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -88,7 +86,7 @@ public final class Main implements Runnable {
|
||||
}
|
||||
|
||||
public static final String LAUNCHER_NAME = "Hello Minecraft! Launcher";
|
||||
public static final byte VERSION_FIRST = 2, VERSION_SECOND = 3, VERSION_THIRD = 5, VERSION_FORTH = 8;
|
||||
public static final byte VERSION_FIRST = 2, VERSION_SECOND = 3, VERSION_THIRD = 5, VERSION_FORTH = 9;
|
||||
public static final int MINIMUM_LAUNCHER_VERSION = 16;
|
||||
//public static Proxy PROXY;
|
||||
|
||||
@@ -147,8 +145,7 @@ public final class Main implements Runnable {
|
||||
}
|
||||
|
||||
Settings.UPDATE_CHECKER.outdated.register(IUpgrader.NOW_UPGRADER);
|
||||
Settings.UPDATE_CHECKER.process(false).subscribeOn(Schedulers.newThread()).subscribe(t
|
||||
-> Main.invokeUpdate());
|
||||
Settings.UPDATE_CHECKER.process(false).reg(t -> Main.invokeUpdate()).execute();
|
||||
|
||||
if (StrUtils.isNotBlank(Settings.getInstance().getProxyHost()) && StrUtils.isNotBlank(Settings.getInstance().getProxyPort()) && MathUtils.canParseInt(Settings.getInstance().getProxyPort())) {
|
||||
HMCLog.log("Initializing customized proxy");
|
||||
|
||||
+35
-56
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hellominecraft.launcher.core.assets;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
@@ -30,9 +29,8 @@ import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.utils.StrUtils;
|
||||
import org.jackhuang.hellominecraft.launcher.core.download.IDownloadProvider;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hellominecraft.utils.OverridableSwingWorker;
|
||||
import org.jackhuang.hellominecraft.utils.VersionNumber;
|
||||
import rx.Observable;
|
||||
import rx.Observer;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -45,61 +43,42 @@ public class AssetsMojangLoader extends IAssetsHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<String[]> getList(MinecraftVersion mv, IMinecraftAssetService mp) {
|
||||
return Observable.<String[]>createWithEmptySubscription((Observer<String[]> t1) -> {
|
||||
if (mv == null) {
|
||||
t1.onError(null);
|
||||
return;
|
||||
}
|
||||
String assetsId = mv.assets == null ? "legacy" : mv.assets;
|
||||
File assets = mp.getAssets();
|
||||
HMCLog.log("Get index: " + assetsId);
|
||||
File f = IOUtils.tryGetCanonicalFile(new File(assets, "indexes/" + assetsId + ".json"));
|
||||
if (!f.exists() && !mp.downloadMinecraftAssetsIndex(assetsId)) {
|
||||
t1.onError(null);
|
||||
return;
|
||||
}
|
||||
public OverridableSwingWorker<String[]> getList(final MinecraftVersion mv, final IMinecraftAssetService mp) {
|
||||
return new OverridableSwingWorker<String[]>() {
|
||||
@Override
|
||||
protected void work() throws Exception {
|
||||
if (mv == null)
|
||||
throw new IllegalArgumentException("AssetsMojangLoader: null argument: MinecraftVersion");
|
||||
String assetsId = mv.assets == null ? "legacy" : mv.assets;
|
||||
File assets = mp.getAssets();
|
||||
HMCLog.log("Gathering asset index: " + assetsId);
|
||||
File f = IOUtils.tryGetCanonicalFile(new File(assets, "indexes/" + assetsId + ".json"));
|
||||
if (!f.exists() && !mp.downloadMinecraftAssetsIndex(assetsId))
|
||||
throw new IllegalStateException("Failed to get index json");
|
||||
|
||||
String result;
|
||||
try {
|
||||
result = FileUtils.readFileToString(f);
|
||||
} catch (IOException ex) {
|
||||
HMCLog.warn("Failed to read index json: " + f, ex);
|
||||
t1.onError(null);
|
||||
return;
|
||||
}
|
||||
if (StrUtils.isBlank(result)) {
|
||||
HMCLog.err("Index json is empty, please redownload it!");
|
||||
t1.onError(null);
|
||||
return;
|
||||
}
|
||||
AssetsIndex o;
|
||||
try {
|
||||
o = C.gson.fromJson(result, AssetsIndex.class);
|
||||
} catch (Exception e) {
|
||||
HMCLog.err("Failed to parse index json, please redownload it!", e);
|
||||
t1.onError(null);
|
||||
return;
|
||||
}
|
||||
assetsDownloadURLs = new ArrayList<>();
|
||||
assetsLocalNames = new ArrayList<>();
|
||||
ArrayList<String> al = new ArrayList<>();
|
||||
contents = new ArrayList<>();
|
||||
if (o != null && o.getFileMap() != null)
|
||||
for (Map.Entry<String, AssetsObject> e : o.getFileMap().entrySet()) {
|
||||
Contents c = new Contents();
|
||||
c.eTag = e.getValue().getHash();
|
||||
c.key = c.eTag.substring(0, 2) + "/" + e.getValue().getHash();
|
||||
c.size = e.getValue().getSize();
|
||||
contents.add(c);
|
||||
assetsDownloadURLs.add(c.key);
|
||||
assetsLocalNames.add(new File(assets, "objects" + File.separator + c.key.replace("/", File.separator)));
|
||||
al.add(e.getKey());
|
||||
}
|
||||
String result = FileUtils.readFileToString(f);
|
||||
if (StrUtils.isBlank(result))
|
||||
throw new IllegalStateException("Index json is empty, please redownload it!");
|
||||
AssetsIndex o = C.gson.fromJson(result, AssetsIndex.class);
|
||||
assetsDownloadURLs = new ArrayList<>();
|
||||
assetsLocalNames = new ArrayList<>();
|
||||
ArrayList<String> al = new ArrayList<>();
|
||||
contents = new ArrayList<>();
|
||||
if (o != null && o.getFileMap() != null)
|
||||
for (Map.Entry<String, AssetsObject> e : o.getFileMap().entrySet()) {
|
||||
Contents c = new Contents();
|
||||
c.eTag = e.getValue().getHash();
|
||||
c.key = c.eTag.substring(0, 2) + "/" + e.getValue().getHash();
|
||||
c.size = e.getValue().getSize();
|
||||
contents.add(c);
|
||||
assetsDownloadURLs.add(c.key);
|
||||
assetsLocalNames.add(new File(assets, "objects" + File.separator + c.key.replace("/", File.separator)));
|
||||
al.add(e.getKey());
|
||||
}
|
||||
|
||||
t1.onNext(al.toArray(new String[1]));
|
||||
t1.onCompleted();
|
||||
});
|
||||
publish(al.toArray(new String[1]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask;
|
||||
import org.jackhuang.hellominecraft.utils.code.DigestUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.utils.NetUtils;
|
||||
import rx.Observable;
|
||||
import org.jackhuang.hellominecraft.utils.OverridableSwingWorker;
|
||||
|
||||
/**
|
||||
* Assets
|
||||
@@ -73,7 +73,7 @@ public abstract class IAssetsHandler {
|
||||
* @param mp Asset Service
|
||||
* @param x finished event
|
||||
*/
|
||||
public abstract Observable<String[]> getList(MinecraftVersion mv, IMinecraftAssetService mp);
|
||||
public abstract OverridableSwingWorker<String[]> getList(MinecraftVersion mv, IMinecraftAssetService mp);
|
||||
|
||||
/**
|
||||
* Will be invoked when the user invoked "Download all assets".
|
||||
|
||||
+2
-5
@@ -29,7 +29,6 @@ import org.jackhuang.hellominecraft.utils.tasks.TaskWindow;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask;
|
||||
import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import rx.concurrency.Schedulers;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -42,16 +41,14 @@ public class MinecraftAssetService extends IMinecraftAssetService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task downloadAssets(String mcVersion) {
|
||||
public Task downloadAssets(final String mcVersion) {
|
||||
return new Task() {
|
||||
|
||||
@Override
|
||||
public void executeTask() throws Throwable {
|
||||
IAssetsHandler type = IAssetsHandler.ASSETS_HANDLER;
|
||||
type.getList(service.version().getVersionById(mcVersion), service.asset())
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(Schedulers.eventQueue())
|
||||
.subscribe((t) -> TaskWindow.getInstance().addTask(type.getDownloadTask(service.getDownloadType().getProvider())).start());
|
||||
.reg((t) -> TaskWindow.getInstance().addTask(type.getDownloadTask(service.getDownloadType().getProvider())).start()).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-15
@@ -31,11 +31,12 @@ import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.TaskWindow;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask;
|
||||
import org.jackhuang.hellominecraft.utils.NetUtils;
|
||||
import org.jackhuang.hellominecraft.utils.OverridableSwingWorker;
|
||||
import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.Task;
|
||||
import org.jackhuang.hellominecraft.utils.version.MinecraftRemoteVersion;
|
||||
import org.jackhuang.hellominecraft.utils.version.MinecraftRemoteVersions;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -119,16 +120,9 @@ public class MinecraftDownloadService extends IMinecraftDownloadService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean downloadMinecraftJarTo(String id, File mvt) {
|
||||
public Task downloadMinecraftJarTo(String id, File mvt) {
|
||||
String vurl = service.getDownloadType().getProvider().getVersionsDownloadURL() + id + "/";
|
||||
if (TaskWindow.getInstance()
|
||||
.addTask(new FileDownloadTask(vurl + id + ".jar", IOUtils.tryGetCanonicalFile(mvt)).setTag(id + ".jar"))
|
||||
.start())
|
||||
return true;
|
||||
else {
|
||||
mvt.delete();
|
||||
return false;
|
||||
}
|
||||
return new FileDownloadTask(vurl + id + ".jar", IOUtils.tryGetCanonicalFile(mvt)).setTag(id + ".jar");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -157,10 +151,14 @@ public class MinecraftDownloadService extends IMinecraftDownloadService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Observable<MinecraftRemoteVersion> getRemoteVersions() {
|
||||
return NetUtils.getRx(service.getDownloadType().getProvider().getVersionsListDownloadURL())
|
||||
.map(r -> C.gson.fromJson(r, MinecraftRemoteVersions.class))
|
||||
.filter(r -> r != null && r.versions != null)
|
||||
.flatMap(r -> Observable.from(r.versions));
|
||||
public OverridableSwingWorker<MinecraftRemoteVersion> getRemoteVersions() {
|
||||
return new OverridableSwingWorker<MinecraftRemoteVersion>() {
|
||||
@Override
|
||||
protected void work() throws Exception {
|
||||
MinecraftRemoteVersions r = C.gson.fromJson(NetUtils.get(service.getDownloadType().getProvider().getVersionsListDownloadURL()), MinecraftRemoteVersions.class);
|
||||
if (r != null && r.versions != null)
|
||||
publish(r.versions.toArray(new MinecraftRemoteVersion[0]));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+1
-11
@@ -29,7 +29,7 @@ import org.jackhuang.hellominecraft.utils.system.JdkVersion;
|
||||
*/
|
||||
public class LaunchOptions {
|
||||
|
||||
private String name, versionName, javaArgs, minecraftArgs, maxMemory, permSize, width, height, userProperties, serverIp;
|
||||
private String name, versionName, javaArgs, minecraftArgs, maxMemory, permSize, width, height, serverIp;
|
||||
private String proxyHost, proxyPort, proxyUser, proxyPass, javaDir, launchVersion;
|
||||
private boolean fullscreen, debug, noJVMArgs, canceledWrapper;
|
||||
private JdkVersion java;
|
||||
@@ -114,16 +114,6 @@ public class LaunchOptions {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public String getUserProperties() {
|
||||
if (userProperties == null)
|
||||
return "";
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
public void setUserProperties(String userProperties) {
|
||||
this.userProperties = userProperties;
|
||||
}
|
||||
|
||||
public boolean isFullscreen() {
|
||||
return fullscreen;
|
||||
}
|
||||
|
||||
-65
@@ -18,21 +18,10 @@
|
||||
package org.jackhuang.hellominecraft.launcher.core.launch;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import org.jackhuang.hellominecraft.launcher.core.download.DownloadLibraryJob;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -49,14 +38,6 @@ public class LibraryDownloadTask extends FileDownloadTask {
|
||||
|
||||
@Override
|
||||
public void executeTask() throws Throwable {
|
||||
File packFile = new File(job.path.getParentFile(), job.path.getName() + ".pack.xz");
|
||||
/*
|
||||
* if (job.name.contains("typesafe")) {
|
||||
* download(new URL(job.url + ".pack.xz"), packFile);
|
||||
* unpackLibrary(job.path, packFile);
|
||||
* packFile.delete();
|
||||
* } else {
|
||||
*/
|
||||
if (job.name.startsWith("net.minecraftforge:forge:")) {
|
||||
String[] s = job.name.split(":");
|
||||
if (s.length == 3)
|
||||
@@ -68,7 +49,6 @@ public class LibraryDownloadTask extends FileDownloadTask {
|
||||
job.url = "http://dl.liteloader.com/versions/com/mumfrey/liteloader/" + s[2].substring(0, s[2].length() - 3) + "/liteloader-" + s[2] + ".jar";
|
||||
}
|
||||
download(new URL(job.url), job.path);
|
||||
//}
|
||||
}
|
||||
|
||||
void download(URL url, File filePath) throws Throwable {
|
||||
@@ -77,51 +57,6 @@ public class LibraryDownloadTask extends FileDownloadTask {
|
||||
super.executeTask();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedAssignment")
|
||||
public static void unpackLibrary(File output, File input)
|
||||
throws IOException {
|
||||
HMCLog.log("Unpacking " + input);
|
||||
if (output.exists())
|
||||
output.delete();
|
||||
|
||||
byte[] decompressed = IOUtils.readFully(new XZInputStream(new FileInputStream(input)));
|
||||
|
||||
String end = new String(decompressed, decompressed.length - 4, 4);
|
||||
if (!end.equals("SIGN")) {
|
||||
HMCLog.log("Unpacking failed, signature missing " + end);
|
||||
return;
|
||||
}
|
||||
|
||||
int x = decompressed.length;
|
||||
int len = decompressed[(x - 8)] & 0xFF | (decompressed[(x - 7)] & 0xFF) << 8 | (decompressed[(x - 6)] & 0xFF) << 16 | (decompressed[(x - 5)] & 0xFF) << 24;
|
||||
|
||||
File temp = File.createTempFile("art", ".pack");
|
||||
HMCLog.log(" Signed");
|
||||
HMCLog.log(" Checksum Length: " + len);
|
||||
HMCLog.log(" Total Length: " + (decompressed.length - len - 8));
|
||||
HMCLog.log(" Temp File: " + temp.getAbsolutePath());
|
||||
|
||||
byte[] checksums = Arrays.copyOfRange(decompressed, decompressed.length - len - 8, decompressed.length - 8);
|
||||
|
||||
try (OutputStream out = new FileOutputStream(temp)) {
|
||||
out.write(decompressed, 0, decompressed.length - len - 8);
|
||||
}
|
||||
decompressed = null;
|
||||
System.gc();
|
||||
|
||||
try (FileOutputStream jarBytes = new FileOutputStream(output); JarOutputStream jos = new JarOutputStream(jarBytes)) {
|
||||
|
||||
Pack200.newUnpacker().unpack(temp, jos);
|
||||
|
||||
JarEntry checksumsFile = new JarEntry("checksums.sha1");
|
||||
checksumsFile.setTime(0L);
|
||||
jos.putNextEntry(checksumsFile);
|
||||
jos.write(checksums);
|
||||
jos.closeEntry();
|
||||
}
|
||||
temp.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInfo() {
|
||||
return C.i18n("download") + ": " + job.name;
|
||||
|
||||
+1
-2
@@ -76,8 +76,7 @@ public class MinecraftLoader extends AbstractMinecraftLoader {
|
||||
|
||||
if (!checkAssetsExist())
|
||||
if (MessageBox.Show(C.i18n("assets.no_assets"), MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION) {
|
||||
IAssetsHandler.ASSETS_HANDLER.getList(version, service.asset()).subscribe(a -> {
|
||||
});
|
||||
IAssetsHandler.ASSETS_HANDLER.getList(version, service.asset()).execute();
|
||||
TaskWindow.getInstance().addTask(IAssetsHandler.ASSETS_HANDLER.getDownloadTask(service.getDownloadType().getProvider())).start();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -74,7 +74,8 @@ public class MinecraftModService extends IMinecraftModService {
|
||||
}
|
||||
}
|
||||
Collections.sort(mods);
|
||||
return modCache.put(id, mods);
|
||||
modCache.put(id, mods);
|
||||
return mods;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+126
-58
@@ -20,10 +20,10 @@ package org.jackhuang.hellominecraft.launcher.core.mod;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
@@ -32,9 +32,12 @@ import org.jackhuang.hellominecraft.launcher.core.GameException;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftProvider;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hellominecraft.utils.functions.BiFunction;
|
||||
import org.jackhuang.hellominecraft.utils.functions.Predicate;
|
||||
import org.jackhuang.hellominecraft.utils.system.Compressor;
|
||||
import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.ZipEngine;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.Task;
|
||||
import org.jackhuang.hellominecraft.utils.version.MinecraftVersionRequest;
|
||||
|
||||
/**
|
||||
@@ -57,61 +60,121 @@ import org.jackhuang.hellominecraft.utils.version.MinecraftVersionRequest;
|
||||
*/
|
||||
public final class ModpackManager {
|
||||
|
||||
public static void install(File input, IMinecraftService service, String id) throws IOException, FileAlreadyExistsException {
|
||||
File versions = new File(service.baseDirectory(), "versions");
|
||||
File oldFile = new File(versions, "minecraft"), newFile = null;
|
||||
if (oldFile.exists()) {
|
||||
newFile = new File(versions, "minecraft-" + System.currentTimeMillis());
|
||||
if (newFile.isDirectory())
|
||||
FileUtils.deleteDirectory(newFile);
|
||||
else if (newFile.isFile())
|
||||
newFile.delete();
|
||||
oldFile.renameTo(newFile);
|
||||
}
|
||||
public static Task install(final File input, final IMinecraftService service, final String id) {
|
||||
return new Task() {
|
||||
Collection<Task> c = new ArrayList<>();
|
||||
|
||||
File preVersion = new File(versions, id), preVersionRenamed = null;
|
||||
if (preVersion.exists()) {
|
||||
String preId = id + "-" + System.currentTimeMillis();
|
||||
preVersion.renameTo(preVersionRenamed = new File(versions, preId));
|
||||
new File(preVersionRenamed, id + ".json").renameTo(new File(preVersionRenamed, preId + ".json"));
|
||||
new File(preVersionRenamed, id + ".jar").renameTo(new File(preVersionRenamed, preId + ".jar"));
|
||||
}
|
||||
@Override
|
||||
public void executeTask() throws Throwable {
|
||||
File versions = new File(service.baseDirectory(), "versions");
|
||||
File oldFile = new File(versions, "minecraft"), newFile = null;
|
||||
if (oldFile.exists()) {
|
||||
newFile = new File(versions, "minecraft-" + System.currentTimeMillis());
|
||||
if (newFile.isDirectory())
|
||||
FileUtils.deleteDirectory(newFile);
|
||||
else if (newFile.isFile())
|
||||
newFile.delete();
|
||||
oldFile.renameTo(newFile);
|
||||
}
|
||||
|
||||
try {
|
||||
AtomicInteger b = new AtomicInteger(0);
|
||||
HMCLog.log("Decompressing modpack");
|
||||
Compressor.unzip(input, versions, t -> {
|
||||
if (t.equals("minecraft/pack.json"))
|
||||
b.incrementAndGet();
|
||||
return true;
|
||||
}, true);
|
||||
if (b.get() < 1)
|
||||
throw new FileNotFoundException(C.i18n("modpack.incorrect_format.no_json"));
|
||||
File nowFile = new File(versions, id);
|
||||
oldFile.renameTo(nowFile);
|
||||
File preVersion = new File(versions, id), preVersionRenamed = null;
|
||||
if (preVersion.exists()) {
|
||||
HMCLog.log("Backing up the game");
|
||||
String preId = id + "-" + System.currentTimeMillis();
|
||||
preVersion.renameTo(preVersionRenamed = new File(versions, preId));
|
||||
new File(preVersionRenamed, id + ".json").renameTo(new File(preVersionRenamed, preId + ".json"));
|
||||
new File(preVersionRenamed, id + ".jar").renameTo(new File(preVersionRenamed, preId + ".jar"));
|
||||
}
|
||||
|
||||
File json = new File(nowFile, "pack.json");
|
||||
MinecraftVersion mv = C.gson.fromJson(FileUtils.readFileToString(json), MinecraftVersion.class);
|
||||
if (mv.jar == null)
|
||||
throw new FileNotFoundException(C.i18n("modpack.incorrect_format.no_jar"));
|
||||
service.download().downloadMinecraftJarTo(mv.jar, new File(nowFile, id + ".jar"));
|
||||
mv.jar = null;
|
||||
FileUtils.writeStringToFile(json, C.gsonPrettyPrinting.toJson(mv));
|
||||
json.renameTo(new File(nowFile, id + ".json"));
|
||||
try {
|
||||
final AtomicInteger b = new AtomicInteger(0);
|
||||
HMCLog.log("Decompressing modpack");
|
||||
Compressor.unzip(input, versions, new Predicate<String>() {
|
||||
@Override
|
||||
public boolean apply(String t) {
|
||||
if (t.equals("minecraft/pack.json"))
|
||||
b.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
}, true);
|
||||
if (b.get() < 1)
|
||||
throw new FileNotFoundException(C.i18n("modpack.incorrect_format.no_json"));
|
||||
File nowFile = new File(versions, id);
|
||||
oldFile.renameTo(nowFile);
|
||||
|
||||
if (preVersionRenamed != null) {
|
||||
File presaves = new File(preVersionRenamed, "saves");
|
||||
File saves = new File(nowFile, "saves");
|
||||
if (presaves.exists()) {
|
||||
FileUtils.deleteDirectory(saves);
|
||||
FileUtils.copyDirectory(presaves, saves);
|
||||
File json = new File(nowFile, "pack.json");
|
||||
MinecraftVersion mv = C.gson.fromJson(FileUtils.readFileToString(json), MinecraftVersion.class);
|
||||
if (mv.jar == null)
|
||||
throw new FileNotFoundException(C.i18n("modpack.incorrect_format.no_jar"));
|
||||
|
||||
c.add(service.download().downloadMinecraftJarTo(mv.jar, new File(nowFile, id + ".jar")));
|
||||
mv.jar = null;
|
||||
FileUtils.writeStringToFile(json, C.gsonPrettyPrinting.toJson(mv));
|
||||
json.renameTo(new File(nowFile, id + ".json"));
|
||||
|
||||
if (preVersionRenamed != null) {
|
||||
HMCLog.log("Restoring saves");
|
||||
File presaves = new File(preVersionRenamed, "saves");
|
||||
File saves = new File(nowFile, "saves");
|
||||
if (presaves.exists()) {
|
||||
FileUtils.deleteDirectory(saves);
|
||||
FileUtils.copyDirectory(presaves, saves);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
FileUtils.deleteDirectoryQuietly(oldFile);
|
||||
if (newFile != null)
|
||||
newFile.renameTo(oldFile);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
FileUtils.deleteDirectoryQuietly(oldFile);
|
||||
if (newFile != null)
|
||||
newFile.renameTo(oldFile);
|
||||
|
||||
@Override
|
||||
public String getInfo() {
|
||||
return C.i18n("modpack.install.task");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getAfterTasks() {
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public static final List<String> MODPACK_BLACK_LIST = Arrays.asList(new String[] { "usernamecache.json", "asm", "logs", "backups", "versions", "assets", "usercache.json", "libraries", "crash-reports", "launcher_profiles.json", "NVIDIA", "TCNodeTracker", "screenshots", "natives", "native", "hmclversion.cfg", "pack.json" });
|
||||
public static final List<String> MODPACK_SUGGESTED_BLACK_LIST = Arrays.asList(new String[] { "saves", "servers.dat", "options.txt", "optionsshaders.txt", "mods/VoxelMods" });
|
||||
|
||||
/**
|
||||
* < String, Boolean, Boolean >: Folder/File name, Is Directory,
|
||||
* Return 0: non blocked, 1: non shown, 2: suggested, checked.
|
||||
*/
|
||||
public static final BiFunction<String, Boolean, Integer> MODPACK_PREDICATE = new BiFunction<String, Boolean, Integer>() {
|
||||
@Override
|
||||
public Integer apply(String x, Boolean y) {
|
||||
if (ModpackManager.MODPACK_BLACK_LIST_PREDICATE.apply(x, y))
|
||||
return 1;
|
||||
if (ModpackManager.MODPACK_SUGGESTED_BLACK_LIST_PREDICATE.apply(x, y))
|
||||
return 2;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
public static final BiFunction<String, Boolean, Boolean> MODPACK_BLACK_LIST_PREDICATE = modpackPredicateMaker(MODPACK_BLACK_LIST);
|
||||
public static final BiFunction<String, Boolean, Boolean> MODPACK_SUGGESTED_BLACK_LIST_PREDICATE = modpackPredicateMaker(MODPACK_SUGGESTED_BLACK_LIST);
|
||||
|
||||
private static BiFunction<String, Boolean, Boolean> modpackPredicateMaker(final List<String> l) {
|
||||
return new BiFunction<String, Boolean, Boolean>() {
|
||||
@Override
|
||||
public Boolean apply(String x, Boolean y) {
|
||||
for (String s : l)
|
||||
if (y) {
|
||||
if (x.startsWith(s + "/"))
|
||||
return true;
|
||||
} else if (x.equals(s))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,29 +188,34 @@ public final class ModpackManager {
|
||||
* @throws IOException if create tmp directory failed
|
||||
*/
|
||||
public static void export(File output, IMinecraftProvider provider, String version, List<String> blacklist) throws IOException, GameException {
|
||||
ArrayList<String> b = new ArrayList<>(Arrays.asList(new String[] { "usernamecache.json", "asm", "logs", "backups", "versions", "assets", "usercache.json", "libraries", "crash-reports", "launcher_profiles.json", "NVIDIA", "TCNodeTracker" }));
|
||||
final ArrayList<String> b = new ArrayList<>(MODPACK_BLACK_LIST);
|
||||
if (blacklist != null)
|
||||
b.addAll(blacklist);
|
||||
b.add(version + ".jar");
|
||||
b.add(version + ".json");
|
||||
HMCLog.log("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
|
||||
ZipEngine zip = null;
|
||||
try {
|
||||
zip = new ZipEngine(output);
|
||||
zip.putDirectory(provider.getRunDirectory(version), (String x, Boolean y) -> {
|
||||
for (String s : b)
|
||||
if (y) {
|
||||
if (x.startsWith(s + "/"))
|
||||
zip.putDirectory(provider.getRunDirectory(version), new BiFunction<String, Boolean, String>() {
|
||||
@Override
|
||||
public String apply(String x, Boolean y) {
|
||||
for (String s : b)
|
||||
if (y) {
|
||||
if (x.startsWith(s + "/"))
|
||||
return null;
|
||||
} else if (x.equals(s))
|
||||
return null;
|
||||
} else if (x.equals(s))
|
||||
return null;
|
||||
return "minecraft/" + x;
|
||||
return "minecraft/" + x;
|
||||
}
|
||||
});
|
||||
|
||||
MinecraftVersion mv = provider.getVersionById(version).resolve(provider);
|
||||
mv.runDir = "version";
|
||||
MinecraftVersionRequest r = MinecraftVersionRequest.minecraftVersion(provider.getMinecraftJar(version));
|
||||
if (r.type != MinecraftVersionRequest.OK)
|
||||
throw new FileSystemException(C.i18n("modpack.cannot_read_version") + ": " + MinecraftVersionRequest.getResponse(r));
|
||||
mv.jar = r.version;
|
||||
mv.runDir = "version";
|
||||
zip.putTextFile(C.gsonPrettyPrinting.toJson(mv), "minecraft/pack.json");
|
||||
} finally {
|
||||
if (zip != null)
|
||||
|
||||
+4
-3
@@ -22,8 +22,9 @@ import java.util.List;
|
||||
import org.jackhuang.hellominecraft.launcher.core.GameException;
|
||||
import org.jackhuang.hellominecraft.launcher.core.download.DownloadLibraryJob;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hellominecraft.utils.OverridableSwingWorker;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.Task;
|
||||
import org.jackhuang.hellominecraft.utils.version.MinecraftRemoteVersion;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -39,7 +40,7 @@ public abstract class IMinecraftDownloadService extends IMinecraftBasicService {
|
||||
|
||||
public abstract boolean downloadMinecraftJar(String id);
|
||||
|
||||
public abstract boolean downloadMinecraftJarTo(String id, File f);
|
||||
public abstract Task downloadMinecraftJarTo(String id, File f);
|
||||
|
||||
public abstract boolean downloadMinecraftVersionJson(String id);
|
||||
|
||||
@@ -50,6 +51,6 @@ public abstract class IMinecraftDownloadService extends IMinecraftBasicService {
|
||||
*/
|
||||
public abstract List<DownloadLibraryJob> getDownloadLibraries(MinecraftVersion mv) throws GameException;
|
||||
|
||||
public abstract Observable<MinecraftRemoteVersion> getRemoteVersions();
|
||||
public abstract OverridableSwingWorker<MinecraftRemoteVersion> getRemoteVersions();
|
||||
|
||||
}
|
||||
+13
-7
@@ -21,8 +21,8 @@ import java.io.File;
|
||||
import java.util.Collection;
|
||||
import org.jackhuang.hellominecraft.launcher.core.GameException;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.DecompressLibraryJob;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.GameDirType;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hellominecraft.utils.EventHandler;
|
||||
import org.jackhuang.hellominecraft.utils.functions.Consumer;
|
||||
|
||||
/**
|
||||
@@ -54,6 +54,8 @@ public abstract class IMinecraftProvider {
|
||||
*/
|
||||
public abstract File getRunDirectory(String id);
|
||||
|
||||
public abstract File versionRoot(String id);
|
||||
|
||||
public File getRunDirectory(String id, String subFolder) {
|
||||
return new File(getRunDirectory(id), subFolder);
|
||||
}
|
||||
@@ -91,12 +93,6 @@ public abstract class IMinecraftProvider {
|
||||
*/
|
||||
public abstract File getMinecraftJar(String id);
|
||||
|
||||
protected GameDirType gameDirType = GameDirType.ROOT_FOLDER;
|
||||
|
||||
public void setGameDirType(GameDirType gameDirType) {
|
||||
this.gameDirType = gameDirType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename version
|
||||
*
|
||||
@@ -148,9 +144,15 @@ public abstract class IMinecraftProvider {
|
||||
|
||||
/**
|
||||
* Refind the versions in this profile.
|
||||
* Must call onRefreshingVersions, onRefreshedVersions, onLoadedVersion
|
||||
* Events.
|
||||
*/
|
||||
public abstract void refreshVersions();
|
||||
|
||||
public final EventHandler<Void> onRefreshingVersions = new EventHandler<>(this),
|
||||
onRefreshedVersions = new EventHandler<>(this);
|
||||
public final EventHandler<String> onLoadedVersion = new EventHandler<>(this);
|
||||
|
||||
/**
|
||||
* Clean redundant files.
|
||||
*/
|
||||
@@ -163,4 +165,8 @@ public abstract class IMinecraftProvider {
|
||||
*/
|
||||
public abstract boolean onLaunch();
|
||||
|
||||
public File baseDirectory() {
|
||||
return service.baseDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
+19
-15
@@ -68,6 +68,8 @@ public class MinecraftVersionManager extends IMinecraftProvider {
|
||||
|
||||
@Override
|
||||
public void refreshVersions() {
|
||||
onRefreshingVersions.execute(null);
|
||||
|
||||
try {
|
||||
MCUtils.tryWriteProfile(service.baseDirectory());
|
||||
} catch (IOException ex) {
|
||||
@@ -140,15 +142,22 @@ public class MinecraftVersionManager extends IMinecraftProvider {
|
||||
for (MinecraftLibrary ml : mcVersion.libraries)
|
||||
ml.init();
|
||||
versions.put(id, mcVersion);
|
||||
onLoadedVersion.execute(id);
|
||||
} catch (Exception e) {
|
||||
HMCLog.warn("Ignoring: " + dir + ", the json of this Minecraft is malformed.", e);
|
||||
}
|
||||
}
|
||||
onRefreshedVersions.execute(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File versionRoot(String id) {
|
||||
return new File(service.baseDirectory(), "versions/" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeVersionFromDisk(String name) {
|
||||
File version = new File(service.baseDirectory(), "versions/" + name);
|
||||
File version = versionRoot(name);
|
||||
if (!version.exists())
|
||||
return true;
|
||||
|
||||
@@ -159,12 +168,12 @@ public class MinecraftVersionManager extends IMinecraftProvider {
|
||||
@Override
|
||||
public boolean renameVersion(String from, String to) {
|
||||
try {
|
||||
File fromJson = new File(service.baseDirectory(), "versions/" + from + "/" + from + ".json");
|
||||
File fromJson = new File(versionRoot(from), from + ".json");
|
||||
MinecraftVersion mcVersion = C.gson.fromJson(FileUtils.readFileToString(fromJson), MinecraftVersion.class);
|
||||
mcVersion.id = to;
|
||||
FileUtils.writeQuietly(fromJson, C.gsonPrettyPrinting.toJson(mcVersion));
|
||||
File toDir = new File(service.baseDirectory(), "versions/" + to);
|
||||
new File(service.baseDirectory(), "versions/" + from).renameTo(toDir);
|
||||
File toDir = versionRoot(to);
|
||||
versionRoot(from).renameTo(toDir);
|
||||
File toJson = new File(toDir, to + ".json");
|
||||
File toJar = new File(toDir, to + ".jar");
|
||||
new File(toDir, from + ".json").renameTo(toJson);
|
||||
@@ -180,14 +189,9 @@ public class MinecraftVersionManager extends IMinecraftProvider {
|
||||
|
||||
@Override
|
||||
public File getRunDirectory(String id) {
|
||||
if ("version".equals(versions.get(id).runDir))
|
||||
return new File(service.baseDirectory(), "versions/" + id + "/");
|
||||
switch (gameDirType) {
|
||||
case VERSION_FOLDER:
|
||||
return new File(service.baseDirectory(), "versions/" + id + "/");
|
||||
default:
|
||||
return service.baseDirectory();
|
||||
}
|
||||
if ("version".equals(getVersionById(id).runDir))
|
||||
return versionRoot(id);
|
||||
return baseDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -195,7 +199,7 @@ public class MinecraftVersionManager extends IMinecraftProvider {
|
||||
MinecraftVersion v = service.download().downloadMinecraft(id);
|
||||
if (callback != null) {
|
||||
callback.accept(v);
|
||||
File mvt = new File(service.baseDirectory(), "versions/" + id + "/" + id + ".json");
|
||||
File mvt = new File(versionRoot(id), id + ".json");
|
||||
FileUtils.writeQuietly(mvt, C.gsonPrettyPrinting.toJson(v));
|
||||
}
|
||||
if (v != null) {
|
||||
@@ -265,9 +269,9 @@ public class MinecraftVersionManager extends IMinecraftProvider {
|
||||
@Override
|
||||
public void cleanFolder() {
|
||||
for (MinecraftVersion s : getVersions()) {
|
||||
FileUtils.deleteDirectoryQuietly(new File(service.baseDirectory(), "versions" + File.separator + s.id + File.separator + s.id + "-natives"));
|
||||
FileUtils.deleteDirectoryQuietly(new File(versionRoot(s.id), s.id + "-natives"));
|
||||
File f = getRunDirectory(s.id);
|
||||
String[] dir = { "logs", "asm", "NVIDIA", "crash-reports", "server-resource-packs", "natives", "native" };
|
||||
String[] dir = { "asm", "NVIDIA", "server-resource-packs", "natives", "native" };
|
||||
for (String str : dir)
|
||||
FileUtils.deleteDirectoryQuietly(new File(f, str));
|
||||
String[] files = { "output-client.log", "usercache.json", "usernamecache.json", "hmclmc.log" };
|
||||
|
||||
+46
-4
@@ -15,11 +15,12 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.launcher.utils;
|
||||
package org.jackhuang.hellominecraft.launcher.settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hellominecraft.launcher.core.GameException;
|
||||
import org.jackhuang.hellominecraft.launcher.settings.Profile;
|
||||
import org.jackhuang.hellominecraft.launcher.core.installers.MinecraftInstallerService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.assets.MinecraftAssetService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.auth.UserProfileProvider;
|
||||
@@ -34,7 +35,8 @@ import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftModService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftProvider;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.mod.MinecraftModService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersionManager;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -44,15 +46,51 @@ public class DefaultMinecraftService extends IMinecraftService {
|
||||
|
||||
File base;
|
||||
Profile p;
|
||||
final Map<String, VersionSetting> versionSettings = new HashMap<>();
|
||||
|
||||
public DefaultMinecraftService(Profile p) {
|
||||
this.p = p;
|
||||
this.provider = new MinecraftVersionManager(this);
|
||||
this.provider = new HMCLGameProvider(this);
|
||||
provider.initializeMiencraft();
|
||||
provider.onRefreshingVersions.register((sender, x) -> {
|
||||
versionSettings.clear();
|
||||
return true;
|
||||
});
|
||||
provider.onLoadedVersion.register((sender, id) -> {
|
||||
VersionSetting vs = new VersionSetting();
|
||||
File f = new File(provider.versionRoot(id), "hmclversion.cfg");
|
||||
if (f.exists()) {
|
||||
String s = FileUtils.readFileToStringQuietly(f);
|
||||
if (s != null)
|
||||
vs = C.gson.fromJson(s, VersionSetting.class);
|
||||
}
|
||||
vs.id = id;
|
||||
versionSettings.put(id, vs);
|
||||
return true;
|
||||
});
|
||||
this.mms = new MinecraftModService(this);
|
||||
this.mds = new MinecraftDownloadService(this);
|
||||
this.mas = new MinecraftAssetService(this);
|
||||
this.mis = new MinecraftInstallerService(this);
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (String key : versionSettings.keySet())
|
||||
saveVersionSetting(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
return versionSettings.get(id);
|
||||
}
|
||||
|
||||
public void saveVersionSetting(String id) {
|
||||
if (!versionSettings.containsKey(id))
|
||||
return;
|
||||
File f = new File(provider.versionRoot(id), "hmclversion.cfg");
|
||||
FileUtils.writeQuietly(f, C.gson.toJson(versionSettings.get(id)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,4 +138,8 @@ public class DefaultMinecraftService extends IMinecraftService {
|
||||
return new MinecraftLoader(options, this, p);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return p;
|
||||
}
|
||||
|
||||
}
|
||||
+8
-7
@@ -17,10 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.launcher.settings;
|
||||
|
||||
import org.jackhuang.hellominecraft.launcher.utils.DefaultMinecraftService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService;
|
||||
import org.jackhuang.hellominecraft.launcher.api.IPlugin;
|
||||
import org.jackhuang.hellominecraft.launcher.settings.Settings;
|
||||
import org.jackhuang.hellominecraft.launcher.core.auth.IAuthenticator;
|
||||
import org.jackhuang.hellominecraft.launcher.core.auth.OfflineAuthenticator;
|
||||
import org.jackhuang.hellominecraft.launcher.core.auth.SkinmeAuthenticator;
|
||||
@@ -54,11 +52,14 @@ public class DefaultPlugin implements IPlugin {
|
||||
SKINME_LOGIN = new SkinmeAuthenticator(clientToken);
|
||||
SKINME_LOGIN.onLoadSettings(Settings.getInstance().getAuthenticatorConfig(SKINME_LOGIN.id()));
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
Settings.getInstance().setAuthenticatorConfig(OFFLINE_LOGIN.id(), OFFLINE_LOGIN.onSaveSettings());
|
||||
Settings.getInstance().setAuthenticatorConfig(YGGDRASIL_LOGIN.id(), YGGDRASIL_LOGIN.onSaveSettings());
|
||||
Settings.getInstance().setAuthenticatorConfig(SKINME_LOGIN.id(), SKINME_LOGIN.onSaveSettings());
|
||||
}));
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Settings.getInstance().setAuthenticatorConfig(OFFLINE_LOGIN.id(), OFFLINE_LOGIN.onSaveSettings());
|
||||
Settings.getInstance().setAuthenticatorConfig(YGGDRASIL_LOGIN.id(), YGGDRASIL_LOGIN.onSaveSettings());
|
||||
Settings.getInstance().setAuthenticatorConfig(SKINME_LOGIN.id(), SKINME_LOGIN.onSaveSettings());
|
||||
}
|
||||
});
|
||||
apply.accept(OFFLINE_LOGIN);
|
||||
apply.accept(YGGDRASIL_LOGIN);
|
||||
apply.accept(SKINME_LOGIN);
|
||||
|
||||
Executable → Regular
+11
-16
@@ -15,31 +15,26 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils.tasks;
|
||||
package org.jackhuang.hellominecraft.launcher.settings;
|
||||
|
||||
import rx.Observable;
|
||||
import java.io.File;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.GameDirType;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersionManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class TaskObservable<T> extends TaskInfo {
|
||||
public class HMCLGameProvider extends MinecraftVersionManager {
|
||||
|
||||
private final Observable<T> r;
|
||||
|
||||
public TaskObservable(String info, Observable<T> r) {
|
||||
super(info);
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
public TaskObservable(Observable<T> r) {
|
||||
this("TaskObservable", r);
|
||||
public HMCLGameProvider(DefaultMinecraftService p) {
|
||||
super(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeTask() {
|
||||
r.subscribe(t -> {
|
||||
});
|
||||
public File getRunDirectory(String id) {
|
||||
return ((DefaultMinecraftService) service).getVersionSetting(id).getGameDirType() == GameDirType.VERSION_FOLDER
|
||||
? service.version().versionRoot(id)
|
||||
: super.getRunDirectory(id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,22 +18,12 @@
|
||||
package org.jackhuang.hellominecraft.launcher.settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import org.jackhuang.hellominecraft.launcher.Main;
|
||||
import org.jackhuang.hellominecraft.launcher.api.PluginManager;
|
||||
import org.jackhuang.hellominecraft.launcher.core.LauncherVisibility;
|
||||
import org.jackhuang.hellominecraft.launcher.core.MCUtils;
|
||||
import org.jackhuang.hellominecraft.launcher.core.launch.LaunchOptions;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.GameDirType;
|
||||
import org.jackhuang.hellominecraft.utils.StrUtils;
|
||||
import org.jackhuang.hellominecraft.utils.Utils;
|
||||
import org.jackhuang.hellominecraft.utils.EventHandler;
|
||||
import org.jackhuang.hellominecraft.utils.system.Java;
|
||||
import org.jackhuang.hellominecraft.utils.system.JdkVersion;
|
||||
import org.jackhuang.hellominecraft.utils.system.OS;
|
||||
|
||||
/**
|
||||
@@ -42,22 +32,7 @@ import org.jackhuang.hellominecraft.utils.system.OS;
|
||||
*/
|
||||
public final class Profile {
|
||||
|
||||
private String name, selectedMinecraftVersion = "", javaArgs, minecraftArgs, maxMemory, permSize, width, height, userProperties;
|
||||
private String gameDir, javaDir, precalledCommand, serverIp, java;
|
||||
private boolean fullscreen, debug, noJVMArgs, canceledWrapper;
|
||||
|
||||
/**
|
||||
* 0 - Close the launcher when the game starts.<br/>
|
||||
* 1 - Hide the launcher when the game starts.<br/>
|
||||
* 2 - Keep the launcher open.<br/>
|
||||
*/
|
||||
private int launcherVisibility;
|
||||
|
||||
/**
|
||||
* 0 - .minecraft<br/>
|
||||
* 1 - .minecraft/versions/<version>/<br/>
|
||||
*/
|
||||
private int gameDirType;
|
||||
private String name, selectedMinecraftVersion = "", gameDir;
|
||||
|
||||
protected transient IMinecraftService service;
|
||||
public transient final EventHandler<String> propertyChanged = new EventHandler<>(this);
|
||||
@@ -69,14 +44,7 @@ public final class Profile {
|
||||
public Profile(String name) {
|
||||
this.name = name;
|
||||
gameDir = MCUtils.getInitGameDir().getPath();
|
||||
debug = fullscreen = canceledWrapper = false;
|
||||
launcherVisibility = gameDirType = 0;
|
||||
PluginManager.NOW_PLUGIN.onInitializingProfile(this);
|
||||
javaDir = java = minecraftArgs = serverIp = precalledCommand = "";
|
||||
}
|
||||
|
||||
public void initialize(int gameDirType) {
|
||||
this.gameDirType = gameDirType;
|
||||
}
|
||||
|
||||
public Profile(Profile v) {
|
||||
@@ -85,22 +53,6 @@ public final class Profile {
|
||||
return;
|
||||
name = v.name;
|
||||
gameDir = v.gameDir;
|
||||
maxMemory = v.maxMemory;
|
||||
width = v.width;
|
||||
height = v.height;
|
||||
java = v.java;
|
||||
fullscreen = v.fullscreen;
|
||||
javaArgs = v.javaArgs;
|
||||
javaDir = v.javaDir;
|
||||
debug = v.debug;
|
||||
minecraftArgs = v.minecraftArgs;
|
||||
permSize = v.permSize;
|
||||
gameDirType = v.gameDirType;
|
||||
canceledWrapper = v.canceledWrapper;
|
||||
noJVMArgs = v.noJVMArgs;
|
||||
launcherVisibility = v.launcherVisibility;
|
||||
precalledCommand = v.precalledCommand;
|
||||
serverIp = v.serverIp;
|
||||
}
|
||||
|
||||
public IMinecraftService service() {
|
||||
@@ -109,6 +61,14 @@ public final class Profile {
|
||||
return service;
|
||||
}
|
||||
|
||||
public VersionSetting getSelectedVersionSetting() {
|
||||
return getVersionSetting(getSelectedVersion());
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
return ((DefaultMinecraftService) service()).getVersionSetting(id);
|
||||
}
|
||||
|
||||
public String getSettingsSelectedMinecraftVersion() {
|
||||
return selectedMinecraftVersion;
|
||||
}
|
||||
@@ -116,11 +76,12 @@ public final class Profile {
|
||||
public String getSelectedVersion() {
|
||||
String v = selectedMinecraftVersion;
|
||||
if (StrUtils.isBlank(v) || service.version().getVersionById(v) == null) {
|
||||
v = service.version().getOneVersion().id;
|
||||
if (v != null)
|
||||
if (service.version().getVersionCount() > 0)
|
||||
v = service.version().getOneVersion().id;
|
||||
if (StrUtils.isNotBlank(v))
|
||||
setSelectedMinecraftVersion(v);
|
||||
}
|
||||
return v;
|
||||
return StrUtils.isBlank(v) ? null : v;
|
||||
}
|
||||
|
||||
public transient final EventHandler<String> selectedVersionChangedEvent = new EventHandler<>(this);
|
||||
@@ -156,60 +117,6 @@ public final class Profile {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getJavaDir() {
|
||||
Java j = getJava();
|
||||
if (j.getHome() == null)
|
||||
return javaDir;
|
||||
else
|
||||
return j.getJava();
|
||||
}
|
||||
|
||||
public String getSettingsJavaDir() {
|
||||
return javaDir;
|
||||
}
|
||||
|
||||
public File getJavaDirFile() {
|
||||
return new File(getJavaDir());
|
||||
}
|
||||
|
||||
public void setJavaDir(String javaDir) {
|
||||
this.javaDir = javaDir;
|
||||
propertyChanged.execute("javaDir");
|
||||
}
|
||||
|
||||
public Java getJava() {
|
||||
return Java.JAVA.get(getJavaIndexInAllJavas());
|
||||
}
|
||||
|
||||
public int getJavaIndexInAllJavas() {
|
||||
if (StrUtils.isBlank(java) && StrUtils.isNotBlank(javaDir))
|
||||
java = "Custom";
|
||||
int idx = Java.JAVA.indexOf(new Java(java, null));
|
||||
if (idx == -1) {
|
||||
java = "Default";
|
||||
idx = 0;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
public void setJava(Java java) {
|
||||
if (java == null)
|
||||
this.java = Java.JAVA.get(0).getName();
|
||||
else {
|
||||
int idx = Java.JAVA.indexOf(java);
|
||||
if (idx == -1)
|
||||
return;
|
||||
this.java = java.getName();
|
||||
}
|
||||
propertyChanged.execute("java");
|
||||
}
|
||||
|
||||
public File getFolder(String folder) {
|
||||
if (getSelectedVersion() == null)
|
||||
return new File(getCanonicalGameDirFile(), folder);
|
||||
return service().version().getRunDirectory(getSelectedVersion(), folder);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -219,207 +126,7 @@ public final class Profile {
|
||||
propertyChanged.execute("name");
|
||||
}
|
||||
|
||||
public String getJavaArgs() {
|
||||
if (StrUtils.isBlank(javaArgs))
|
||||
return "";
|
||||
return javaArgs;
|
||||
}
|
||||
|
||||
public void setJavaArgs(String javaArgs) {
|
||||
this.javaArgs = javaArgs;
|
||||
propertyChanged.execute("javaArgs");
|
||||
}
|
||||
|
||||
public boolean hasJavaArgs() {
|
||||
return StrUtils.isNotBlank(getJavaArgs().trim());
|
||||
}
|
||||
|
||||
public String getMaxMemory() {
|
||||
if (StrUtils.isBlank(maxMemory))
|
||||
return String.valueOf(Utils.getSuggestedMemorySize());
|
||||
return maxMemory;
|
||||
}
|
||||
|
||||
public void setMaxMemory(String maxMemory) {
|
||||
this.maxMemory = maxMemory;
|
||||
propertyChanged.execute("maxMemory");
|
||||
}
|
||||
|
||||
public String getWidth() {
|
||||
if (StrUtils.isBlank(width))
|
||||
return "854";
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(String width) {
|
||||
this.width = width;
|
||||
propertyChanged.execute("width");
|
||||
}
|
||||
|
||||
public String getHeight() {
|
||||
if (StrUtils.isBlank(height))
|
||||
return "480";
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(String height) {
|
||||
this.height = height;
|
||||
propertyChanged.execute("height");
|
||||
}
|
||||
|
||||
public String getUserProperties() {
|
||||
if (userProperties == null)
|
||||
return "";
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
public void setUserProperties(String userProperties) {
|
||||
this.userProperties = userProperties;
|
||||
propertyChanged.execute("userProperties");
|
||||
}
|
||||
|
||||
public boolean isFullscreen() {
|
||||
return fullscreen;
|
||||
}
|
||||
|
||||
public void setFullscreen(boolean fullscreen) {
|
||||
this.fullscreen = fullscreen;
|
||||
propertyChanged.execute("fullscreen");
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
propertyChanged.execute("debug");
|
||||
}
|
||||
|
||||
public LauncherVisibility getLauncherVisibility() {
|
||||
return LauncherVisibility.values()[launcherVisibility];
|
||||
}
|
||||
|
||||
public void setLauncherVisibility(LauncherVisibility launcherVisibility) {
|
||||
this.launcherVisibility = launcherVisibility.ordinal();
|
||||
propertyChanged.execute("launcherVisibility");
|
||||
}
|
||||
|
||||
public GameDirType getGameDirType() {
|
||||
if (gameDirType < 0 || gameDirType > 1)
|
||||
setGameDirType(GameDirType.ROOT_FOLDER);
|
||||
return GameDirType.values()[gameDirType];
|
||||
}
|
||||
|
||||
public void setGameDirType(GameDirType gameDirType) {
|
||||
this.gameDirType = gameDirType.ordinal();
|
||||
service().version().setGameDirType(getGameDirType());
|
||||
propertyChanged.execute("gameDirType");
|
||||
}
|
||||
|
||||
public String getPermSize() {
|
||||
return permSize;
|
||||
}
|
||||
|
||||
public void setPermSize(String permSize) {
|
||||
this.permSize = permSize;
|
||||
propertyChanged.execute("permSize");
|
||||
}
|
||||
|
||||
public boolean isNoJVMArgs() {
|
||||
return noJVMArgs;
|
||||
}
|
||||
|
||||
public void setNoJVMArgs(boolean noJVMArgs) {
|
||||
this.noJVMArgs = noJVMArgs;
|
||||
propertyChanged.execute("noJVMArgs");
|
||||
}
|
||||
|
||||
public String getMinecraftArgs() {
|
||||
return minecraftArgs;
|
||||
}
|
||||
|
||||
public void setMinecraftArgs(String minecraftArgs) {
|
||||
this.minecraftArgs = minecraftArgs;
|
||||
propertyChanged.execute("minecraftArgs");
|
||||
}
|
||||
|
||||
public boolean isCanceledWrapper() {
|
||||
return canceledWrapper;
|
||||
}
|
||||
|
||||
public void setCanceledWrapper(boolean canceledWrapper) {
|
||||
this.canceledWrapper = canceledWrapper;
|
||||
propertyChanged.execute("canceledWrapper");
|
||||
}
|
||||
|
||||
public String getPrecalledCommand() {
|
||||
return precalledCommand;
|
||||
}
|
||||
|
||||
public void setPrecalledCommand(String precalledCommand) {
|
||||
this.precalledCommand = precalledCommand;
|
||||
propertyChanged.execute("precalledCommand");
|
||||
}
|
||||
|
||||
public String getServerIp() {
|
||||
return serverIp;
|
||||
}
|
||||
|
||||
public void setServerIp(String serverIp) {
|
||||
this.serverIp = serverIp;
|
||||
propertyChanged.execute("serverIp");
|
||||
}
|
||||
|
||||
public void checkFormat() {
|
||||
gameDir = gameDir.replace('/', OS.os().fileSeparator).replace('\\', OS.os().fileSeparator);
|
||||
}
|
||||
|
||||
public LaunchOptions createLaunchOptions() {
|
||||
LaunchOptions x = new LaunchOptions();
|
||||
x.setCanceledWrapper(isCanceledWrapper());
|
||||
x.setDebug(isDebug());
|
||||
x.setFullscreen(isFullscreen());
|
||||
x.setGameDir(getCanonicalGameDirFile());
|
||||
x.setGameDirType(getGameDirType());
|
||||
x.setHeight(getHeight());
|
||||
x.setJavaArgs(getJavaArgs());
|
||||
x.setLaunchVersion(getSelectedVersion());
|
||||
x.setMaxMemory(getMaxMemory());
|
||||
x.setMinecraftArgs(getMinecraftArgs());
|
||||
x.setName(getName());
|
||||
x.setNoJVMArgs(isNoJVMArgs());
|
||||
x.setPermSize(getPermSize());
|
||||
x.setPrecalledCommand(getPrecalledCommand());
|
||||
x.setProxyHost(Settings.getInstance().getProxyHost());
|
||||
x.setProxyPort(Settings.getInstance().getProxyPort());
|
||||
x.setProxyUser(Settings.getInstance().getProxyUserName());
|
||||
x.setProxyPass(Settings.getInstance().getProxyPassword());
|
||||
x.setServerIp(getServerIp());
|
||||
x.setUserProperties(getUserProperties());
|
||||
x.setVersionName(Main.makeTitle());
|
||||
x.setWidth(getWidth());
|
||||
|
||||
String str = getJavaDir();
|
||||
if (!getJavaDirFile().exists()) {
|
||||
HMCLog.err(C.i18n("launch.wrong_javadir"));
|
||||
setJava(null);
|
||||
str = getJavaDir();
|
||||
}
|
||||
JdkVersion jv = new JdkVersion(str);
|
||||
if (Settings.getInstance().getJava().contains(jv))
|
||||
jv = Settings.getInstance().getJava().get(Settings.getInstance().getJava().indexOf(jv));
|
||||
else
|
||||
try {
|
||||
jv = JdkVersion.getJavaVersionFromExecutable(str);
|
||||
Settings.getInstance().getJava().add(jv);
|
||||
Settings.save();
|
||||
} catch (IOException ex) {
|
||||
HMCLog.warn("Failed to get java version", ex);
|
||||
jv = null;
|
||||
}
|
||||
x.setJava(jv);
|
||||
x.setJavaDir(str);
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -20,21 +20,20 @@ package org.jackhuang.hellominecraft.launcher.settings;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import org.jackhuang.hellominecraft.launcher.Main;
|
||||
import org.jackhuang.hellominecraft.launcher.core.download.DownloadType;
|
||||
import org.jackhuang.hellominecraft.utils.CollectionUtils;
|
||||
import org.jackhuang.hellominecraft.utils.Event;
|
||||
import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.utils.MessageBox;
|
||||
import org.jackhuang.hellominecraft.utils.UpdateChecker;
|
||||
import org.jackhuang.hellominecraft.utils.VersionNumber;
|
||||
import org.jackhuang.hellominecraft.utils.functions.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -56,9 +55,12 @@ public final class Settings {
|
||||
|
||||
static {
|
||||
SETTINGS = initSettings();
|
||||
SETTINGS.downloadTypeChangedEvent.register((s, t) -> {
|
||||
DownloadType.setSuggestedDownloadType(t);
|
||||
return true;
|
||||
SETTINGS.downloadTypeChangedEvent.register(new Event<DownloadType>() {
|
||||
@Override
|
||||
public boolean call(Object sender, DownloadType t) {
|
||||
DownloadType.setSuggestedDownloadType(t);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
DownloadType.setSuggestedDownloadType(SETTINGS.getDownloadSource());
|
||||
if (!getProfiles().containsKey(DEFAULT_PROFILE))
|
||||
@@ -66,9 +68,12 @@ public final class Settings {
|
||||
|
||||
for (Profile e : getProfiles().values()) {
|
||||
e.checkFormat();
|
||||
e.propertyChanged.register((sender, t) -> {
|
||||
save();
|
||||
return true;
|
||||
e.propertyChanged.register(new Event<String>() {
|
||||
@Override
|
||||
public boolean call(Object sender, String t) {
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -121,7 +126,12 @@ public final class Settings {
|
||||
}
|
||||
|
||||
public static Collection<Profile> getProfilesFiltered() {
|
||||
return CollectionUtils.map(getProfiles().values(), (t) -> t != null && t.getName() != null);
|
||||
return CollectionUtils.map(getProfiles().values(), new Predicate<Profile>() {
|
||||
@Override
|
||||
public boolean apply(Profile t) {
|
||||
return t != null && t.getName() != null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Profile getOneProfile() {
|
||||
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.launcher.settings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import org.jackhuang.hellominecraft.launcher.Main;
|
||||
import org.jackhuang.hellominecraft.launcher.core.LauncherVisibility;
|
||||
import org.jackhuang.hellominecraft.launcher.core.launch.LaunchOptions;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.GameDirType;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.EventHandler;
|
||||
import org.jackhuang.hellominecraft.utils.StrUtils;
|
||||
import org.jackhuang.hellominecraft.utils.Utils;
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import org.jackhuang.hellominecraft.utils.system.Java;
|
||||
import org.jackhuang.hellominecraft.utils.system.JdkVersion;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class VersionSetting {
|
||||
|
||||
public transient String id;
|
||||
|
||||
private String javaArgs, minecraftArgs, maxMemory, permSize, width, height;
|
||||
private String javaDir, precalledCommand, serverIp, java;
|
||||
private boolean fullscreen, debug, noJVMArgs, canceledWrapper;
|
||||
|
||||
/**
|
||||
* 0 - Close the launcher when the game starts.<br/>
|
||||
* 1 - Hide the launcher when the game starts.<br/>
|
||||
* 2 - Keep the launcher open.<br/>
|
||||
*/
|
||||
private int launcherVisibility;
|
||||
|
||||
/**
|
||||
* 0 - .minecraft<br/>
|
||||
* 1 - .minecraft/versions/<version>/<br/>
|
||||
*/
|
||||
private int gameDirType;
|
||||
|
||||
public transient final EventHandler<String> propertyChanged = new EventHandler<>(this);
|
||||
|
||||
public VersionSetting() {
|
||||
debug = fullscreen = canceledWrapper = false;
|
||||
launcherVisibility = gameDirType = 0;
|
||||
javaDir = java = minecraftArgs = serverIp = precalledCommand = "";
|
||||
}
|
||||
|
||||
public VersionSetting(VersionSetting v) {
|
||||
this();
|
||||
if (v == null)
|
||||
return;
|
||||
maxMemory = v.maxMemory;
|
||||
width = v.width;
|
||||
height = v.height;
|
||||
java = v.java;
|
||||
fullscreen = v.fullscreen;
|
||||
javaArgs = v.javaArgs;
|
||||
javaDir = v.javaDir;
|
||||
debug = v.debug;
|
||||
minecraftArgs = v.minecraftArgs;
|
||||
permSize = v.permSize;
|
||||
gameDirType = v.gameDirType;
|
||||
canceledWrapper = v.canceledWrapper;
|
||||
noJVMArgs = v.noJVMArgs;
|
||||
launcherVisibility = v.launcherVisibility;
|
||||
precalledCommand = v.precalledCommand;
|
||||
serverIp = v.serverIp;
|
||||
}
|
||||
|
||||
public String getJavaDir() {
|
||||
Java j = getJava();
|
||||
if (j.getHome() == null)
|
||||
return javaDir;
|
||||
else
|
||||
return j.getJava();
|
||||
}
|
||||
|
||||
public String getSettingsJavaDir() {
|
||||
return javaDir;
|
||||
}
|
||||
|
||||
public File getJavaDirFile() {
|
||||
return new File(getJavaDir());
|
||||
}
|
||||
|
||||
public void setJavaDir(String javaDir) {
|
||||
this.javaDir = javaDir;
|
||||
propertyChanged.execute("javaDir");
|
||||
}
|
||||
|
||||
public Java getJava() {
|
||||
return Java.JAVA.get(getJavaIndexInAllJavas());
|
||||
}
|
||||
|
||||
public int getJavaIndexInAllJavas() {
|
||||
if (StrUtils.isBlank(java) && StrUtils.isNotBlank(javaDir))
|
||||
java = "Custom";
|
||||
int idx = Java.JAVA.indexOf(new Java(java, null));
|
||||
if (idx == -1) {
|
||||
java = "Default";
|
||||
idx = 0;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
public void setJava(Java java) {
|
||||
if (java == null)
|
||||
this.java = Java.JAVA.get(0).getName();
|
||||
else {
|
||||
int idx = Java.JAVA.indexOf(java);
|
||||
if (idx == -1)
|
||||
return;
|
||||
this.java = java.getName();
|
||||
}
|
||||
propertyChanged.execute("java");
|
||||
}
|
||||
|
||||
public String getJavaArgs() {
|
||||
if (StrUtils.isBlank(javaArgs))
|
||||
return "";
|
||||
return javaArgs;
|
||||
}
|
||||
|
||||
public void setJavaArgs(String javaArgs) {
|
||||
this.javaArgs = javaArgs;
|
||||
propertyChanged.execute("javaArgs");
|
||||
}
|
||||
|
||||
public boolean hasJavaArgs() {
|
||||
return StrUtils.isNotBlank(getJavaArgs().trim());
|
||||
}
|
||||
|
||||
public String getMaxMemory() {
|
||||
if (StrUtils.isBlank(maxMemory))
|
||||
return String.valueOf(Utils.getSuggestedMemorySize());
|
||||
return maxMemory;
|
||||
}
|
||||
|
||||
public void setMaxMemory(String maxMemory) {
|
||||
this.maxMemory = maxMemory;
|
||||
propertyChanged.execute("maxMemory");
|
||||
}
|
||||
|
||||
public String getWidth() {
|
||||
if (StrUtils.isBlank(width))
|
||||
return "854";
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(String width) {
|
||||
this.width = width;
|
||||
propertyChanged.execute("width");
|
||||
}
|
||||
|
||||
public String getHeight() {
|
||||
if (StrUtils.isBlank(height))
|
||||
return "480";
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(String height) {
|
||||
this.height = height;
|
||||
propertyChanged.execute("height");
|
||||
}
|
||||
|
||||
public boolean isFullscreen() {
|
||||
return fullscreen;
|
||||
}
|
||||
|
||||
public void setFullscreen(boolean fullscreen) {
|
||||
this.fullscreen = fullscreen;
|
||||
propertyChanged.execute("fullscreen");
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
propertyChanged.execute("debug");
|
||||
}
|
||||
|
||||
public LauncherVisibility getLauncherVisibility() {
|
||||
return LauncherVisibility.values()[launcherVisibility];
|
||||
}
|
||||
|
||||
public void setLauncherVisibility(LauncherVisibility launcherVisibility) {
|
||||
this.launcherVisibility = launcherVisibility.ordinal();
|
||||
propertyChanged.execute("launcherVisibility");
|
||||
}
|
||||
|
||||
public GameDirType getGameDirType() {
|
||||
if (gameDirType < 0 || gameDirType > 1)
|
||||
setGameDirType(GameDirType.ROOT_FOLDER);
|
||||
return GameDirType.values()[gameDirType];
|
||||
}
|
||||
|
||||
public void setGameDirType(GameDirType gameDirType) {
|
||||
this.gameDirType = gameDirType.ordinal();
|
||||
propertyChanged.execute("gameDirType");
|
||||
}
|
||||
|
||||
public String getPermSize() {
|
||||
return permSize;
|
||||
}
|
||||
|
||||
public void setPermSize(String permSize) {
|
||||
this.permSize = permSize;
|
||||
propertyChanged.execute("permSize");
|
||||
}
|
||||
|
||||
public boolean isNoJVMArgs() {
|
||||
return noJVMArgs;
|
||||
}
|
||||
|
||||
public void setNoJVMArgs(boolean noJVMArgs) {
|
||||
this.noJVMArgs = noJVMArgs;
|
||||
propertyChanged.execute("noJVMArgs");
|
||||
}
|
||||
|
||||
public String getMinecraftArgs() {
|
||||
return minecraftArgs;
|
||||
}
|
||||
|
||||
public void setMinecraftArgs(String minecraftArgs) {
|
||||
this.minecraftArgs = minecraftArgs;
|
||||
propertyChanged.execute("minecraftArgs");
|
||||
}
|
||||
|
||||
public boolean isCanceledWrapper() {
|
||||
return canceledWrapper;
|
||||
}
|
||||
|
||||
public void setCanceledWrapper(boolean canceledWrapper) {
|
||||
this.canceledWrapper = canceledWrapper;
|
||||
propertyChanged.execute("canceledWrapper");
|
||||
}
|
||||
|
||||
public String getPrecalledCommand() {
|
||||
return precalledCommand;
|
||||
}
|
||||
|
||||
public void setPrecalledCommand(String precalledCommand) {
|
||||
this.precalledCommand = precalledCommand;
|
||||
propertyChanged.execute("precalledCommand");
|
||||
}
|
||||
|
||||
public String getServerIp() {
|
||||
return serverIp;
|
||||
}
|
||||
|
||||
public void setServerIp(String serverIp) {
|
||||
this.serverIp = serverIp;
|
||||
propertyChanged.execute("serverIp");
|
||||
}
|
||||
|
||||
public LaunchOptions createLaunchOptions(File gameDir) {
|
||||
LaunchOptions x = new LaunchOptions();
|
||||
x.setCanceledWrapper(isCanceledWrapper());
|
||||
x.setDebug(isDebug());
|
||||
x.setFullscreen(isFullscreen());
|
||||
x.setGameDir(gameDir);
|
||||
x.setGameDirType(getGameDirType());
|
||||
x.setHeight(getHeight());
|
||||
x.setJavaArgs(getJavaArgs());
|
||||
x.setLaunchVersion(id);
|
||||
x.setMaxMemory(getMaxMemory());
|
||||
x.setMinecraftArgs(getMinecraftArgs());
|
||||
x.setName(Main.makeTitle());
|
||||
x.setNoJVMArgs(isNoJVMArgs());
|
||||
x.setPermSize(getPermSize());
|
||||
x.setPrecalledCommand(getPrecalledCommand());
|
||||
x.setProxyHost(Settings.getInstance().getProxyHost());
|
||||
x.setProxyPort(Settings.getInstance().getProxyPort());
|
||||
x.setProxyUser(Settings.getInstance().getProxyUserName());
|
||||
x.setProxyPass(Settings.getInstance().getProxyPassword());
|
||||
x.setServerIp(getServerIp());
|
||||
x.setVersionName(Main.makeTitle());
|
||||
x.setWidth(getWidth());
|
||||
|
||||
String str = getJavaDir();
|
||||
if (!getJavaDirFile().exists()) {
|
||||
HMCLog.err(C.i18n("launch.wrong_javadir"));
|
||||
setJava(null);
|
||||
str = getJavaDir();
|
||||
}
|
||||
JdkVersion jv = new JdkVersion(str);
|
||||
if (Settings.getInstance().getJava().contains(jv))
|
||||
jv = Settings.getInstance().getJava().get(Settings.getInstance().getJava().indexOf(jv));
|
||||
else
|
||||
try {
|
||||
jv = JdkVersion.getJavaVersionFromExecutable(str);
|
||||
Settings.getInstance().getJava().add(jv);
|
||||
Settings.save();
|
||||
} catch (IOException ex) {
|
||||
HMCLog.warn("Failed to get java version", ex);
|
||||
jv = null;
|
||||
}
|
||||
x.setJava(jv);
|
||||
x.setJavaDir(str);
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,12 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
System.out.println(text);
|
||||
|
||||
if (checkThrowable(e) && !System.getProperty("java.vm.name").contains("OpenJDK")) {
|
||||
SwingUtilities.invokeLater(() -> LogWindow.INSTANCE.showAsCrashWindow(Settings.UPDATE_CHECKER.OUT_DATED));
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogWindow.INSTANCE.showAsCrashWindow(Settings.UPDATE_CHECKER.OUT_DATED);
|
||||
}
|
||||
});
|
||||
if (!Settings.UPDATE_CHECKER.OUT_DATED)
|
||||
reportToServer(text, s);
|
||||
}
|
||||
@@ -127,20 +132,23 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static final HashSet<String> THROWABLE_SET = new HashSet<>();
|
||||
|
||||
void reportToServer(String text, String stacktrace) {
|
||||
void reportToServer(final String text, String stacktrace) {
|
||||
if (THROWABLE_SET.contains(stacktrace))
|
||||
return;
|
||||
THROWABLE_SET.add(stacktrace);
|
||||
new Thread(() -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("CrashReport", text);
|
||||
try {
|
||||
System.out.println(NetUtils.post(NetUtils.constantURL("http://huangyuhui.duapp.com/crash.php"), map));
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Failed to send post request to HMCL server.");
|
||||
ex.printStackTrace();
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("CrashReport", text);
|
||||
try {
|
||||
System.out.println(NetUtils.post(NetUtils.constantURL("http://huangyuhui.duapp.com/crash.php"), map));
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Failed to send post request to HMCL server.");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}.start();
|
||||
}
|
||||
|
||||
}
|
||||
+33
-30
@@ -45,10 +45,10 @@ import org.jackhuang.hellominecraft.utils.MessageBox;
|
||||
import org.jackhuang.hellominecraft.utils.UpdateChecker;
|
||||
import org.jackhuang.hellominecraft.utils.Utils;
|
||||
import org.jackhuang.hellominecraft.utils.VersionNumber;
|
||||
import org.jackhuang.hellominecraft.utils.functions.Consumer;
|
||||
import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.OS;
|
||||
import rx.concurrency.Schedulers;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -90,37 +90,40 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean call(Object sender, VersionNumber number) {
|
||||
((UpdateChecker) sender).requestDownloadLink().subscribeOn(Schedulers.newThread()).observeOn(Schedulers.eventQueue()).subscribe(map -> {
|
||||
if (map != null && map.containsKey("pack"))
|
||||
try {
|
||||
if (TaskWindow.getInstance().addTask(new AppDataUpgraderTask(map.get("pack"), number.version)).start()) {
|
||||
new ProcessBuilder(new String[] { IOUtils.getJavaDir(), "-jar", AppDataUpgraderTask.getSelf(number.version).getAbsolutePath() }).directory(new File(".")).start();
|
||||
System.exit(0);
|
||||
public boolean call(Object sender, final VersionNumber number) {
|
||||
((UpdateChecker) sender).requestDownloadLink().reg(new Consumer<Map<String, String>>() {
|
||||
@Override
|
||||
public void accept(Map<String, String> map) {
|
||||
if (map != null && map.containsKey("pack"))
|
||||
try {
|
||||
if (TaskWindow.getInstance().addTask(new AppDataUpgraderTask(map.get("pack"), number.version)).start()) {
|
||||
new ProcessBuilder(new String[] { IOUtils.getJavaDir(), "-jar", AppDataUpgraderTask.getSelf(number.version).getAbsolutePath() }).directory(new File(".")).start();
|
||||
System.exit(0);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
HMCLog.err("Failed to create upgrader", ex);
|
||||
}
|
||||
if (MessageBox.Show(C.i18n("update.newest_version") + number.firstVer + "." + number.secondVer + "." + number.thirdVer + "\n"
|
||||
+ C.i18n("update.should_open_link"),
|
||||
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION) {
|
||||
String url = C.URL_PUBLISH;
|
||||
if (map != null)
|
||||
if (map.containsKey(OS.os().checked_name))
|
||||
url = map.get(OS.os().checked_name);
|
||||
else if (map.containsKey(OS.UNKOWN.checked_name))
|
||||
url = map.get(OS.UNKOWN.checked_name);
|
||||
if (url == null)
|
||||
url = C.URL_PUBLISH;
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(url));
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
HMCLog.warn("Failed to browse uri: " + url, e);
|
||||
Utils.setClipborad(url);
|
||||
MessageBox.Show(C.i18n("update.no_browser"));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
HMCLog.err("Failed to create upgrader", ex);
|
||||
}
|
||||
if (MessageBox.Show(C.i18n("update.newest_version") + number.firstVer + "." + number.secondVer + "." + number.thirdVer + "\n"
|
||||
+ C.i18n("update.should_open_link"),
|
||||
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION) {
|
||||
String url = C.URL_PUBLISH;
|
||||
if (map != null)
|
||||
if (map.containsKey(OS.os().checked_name))
|
||||
url = map.get(OS.os().checked_name);
|
||||
else if (map.containsKey(OS.UNKOWN.checked_name))
|
||||
url = map.get(OS.UNKOWN.checked_name);
|
||||
if (url == null)
|
||||
url = C.URL_PUBLISH;
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(url));
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
HMCLog.warn("Failed to browse uri: " + url, e);
|
||||
Utils.setClipborad(url);
|
||||
MessageBox.Show(C.i18n("update.no_browser"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}).execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -143,7 +146,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependTasks() {
|
||||
public Collection<? extends Task> getDependTasks() {
|
||||
return Arrays.asList(new FileDownloadTask(downloadLink, tempFile));
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -23,7 +23,6 @@ import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import org.jackhuang.hellominecraft.utils.MessageBox;
|
||||
import org.jackhuang.hellominecraft.utils.StrUtils;
|
||||
import org.jackhuang.hellominecraft.utils.views.SwingUtils;
|
||||
import rx.concurrency.Schedulers;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -108,13 +107,13 @@ public class GameDownloadPanel extends AnimatedPanel implements Selectable {
|
||||
public void refreshDownloads() {
|
||||
DefaultTableModel model = SwingUtils.clearDefaultTable(lstDownloads);
|
||||
gsp.getProfile().service().download().getRemoteVersions()
|
||||
.observeOn(Schedulers.eventQueue()).subscribeOn(Schedulers.newThread())
|
||||
.subscribe((ver) -> model.addRow(new Object[] { ver.id, ver.time,
|
||||
StrUtils.equalsOne(ver.type, "old_beta", "old_alpha", "release", "snapshot") ? C.i18n("versions." + ver.type) : ver.type }),
|
||||
(e) -> {
|
||||
.reg((ver) -> model.addRow(new Object[] { ver.id, ver.time,
|
||||
StrUtils.equalsOne(ver.type, "old_beta", "old_alpha", "release", "snapshot") ? C.i18n("versions." + ver.type) : ver.type }))
|
||||
.regDone(lstDownloads::updateUI).execute();
|
||||
/*(e) -> {
|
||||
MessageBox.Show("Failed to refresh download: " + e.getLocalizedMessage());
|
||||
HMCLog.err("Failed to refresh download.", e);
|
||||
}, lstDownloads::updateUI);
|
||||
}, );*/
|
||||
}
|
||||
|
||||
void downloadMinecraft() {
|
||||
|
||||
+3
-3
@@ -112,7 +112,7 @@
|
||||
<Component id="lblDimensionX" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtHeight" min="-2" pref="100" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="444" max="32767" attributes="0"/>
|
||||
<EmptySpace pref="410" max="32767" attributes="0"/>
|
||||
<Component id="chkFullscreen" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
@@ -300,7 +300,7 @@
|
||||
<Component class="javax.swing.JLabel" name="lblRunDirectory">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hellominecraft/launcher/I18N.properties" key="advancedsettings.run_directory" replaceFormat="C.i18n("{key}")"/>
|
||||
<ResourceString bundle="org/jackhuang/hellominecraft/launcher/I18N.properties" key="settings.run_directory" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
@@ -461,7 +461,7 @@
|
||||
<Component class="javax.swing.JCheckBox" name="chkDebug">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hellominecraft/launcher/I18N.properties" key="advencedsettings.debug_mode" replaceFormat="C.i18n("{key}")"/>
|
||||
<ResourceString bundle="org/jackhuang/hellominecraft/launcher/I18N.properties" key="advancedsettings.debug_mode" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
|
||||
+56
-68
@@ -32,9 +32,7 @@ import java.awt.event.ItemEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
@@ -46,7 +44,6 @@ import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import org.jackhuang.hellominecraft.launcher.core.GameException;
|
||||
import org.jackhuang.hellominecraft.launcher.core.LauncherVisibility;
|
||||
import org.jackhuang.hellominecraft.launcher.settings.Profile;
|
||||
import org.jackhuang.hellominecraft.launcher.settings.Settings;
|
||||
@@ -56,13 +53,14 @@ import org.jackhuang.hellominecraft.launcher.core.installers.InstallerType;
|
||||
import org.jackhuang.hellominecraft.launcher.core.mod.ModpackManager;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.GameDirType;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hellominecraft.launcher.views.modpack.ModpackInitializationPanel;
|
||||
import org.jackhuang.hellominecraft.launcher.settings.DefaultMinecraftService;
|
||||
import org.jackhuang.hellominecraft.launcher.settings.VersionSetting;
|
||||
import org.jackhuang.hellominecraft.launcher.views.modpack.ModpackWizard;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.TaskRunnable;
|
||||
import org.jackhuang.hellominecraft.utils.tasks.TaskWindow;
|
||||
import org.jackhuang.hellominecraft.utils.Event;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.utils.MessageBox;
|
||||
import org.jackhuang.hellominecraft.utils.OverridableSwingWorker;
|
||||
import org.jackhuang.hellominecraft.utils.version.MinecraftVersionRequest;
|
||||
import org.jackhuang.hellominecraft.utils.system.OS;
|
||||
import org.jackhuang.hellominecraft.utils.StrUtils;
|
||||
@@ -70,8 +68,6 @@ import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
import org.jackhuang.hellominecraft.utils.views.SwingUtils;
|
||||
import org.jackhuang.hellominecraft.utils.system.Java;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer;
|
||||
import rx.Observable;
|
||||
import rx.concurrency.Schedulers;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -378,7 +374,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
|
||||
lblLauncherVisibility.setText(C.i18n("advancedsettings.launcher_visible")); // NOI18N
|
||||
|
||||
lblRunDirectory.setText(C.i18n("advancedsettings.run_directory")); // NOI18N
|
||||
lblRunDirectory.setText(C.i18n("settings.run_directory")); // NOI18N
|
||||
|
||||
cboRunDirectory.setModel(new javax.swing.DefaultComboBoxModel(new String[] { C.I18N.getString("advancedsettings.game_dir.default"), C.I18N.getString("advancedsettings.game_dir.independent") }));
|
||||
cboRunDirectory.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
@@ -452,7 +448,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
.addComponent(lblDimensionX)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtHeight, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 444, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 410, Short.MAX_VALUE)
|
||||
.addComponent(chkFullscreen))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnlSettingsLayout.createSequentialGroup()
|
||||
.addComponent(txtMaxMemory)
|
||||
@@ -514,7 +510,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
|
||||
tabVersionEdit.addTab(C.i18n("settings"), pnlSettings); // NOI18N
|
||||
|
||||
chkDebug.setText(C.i18n("advencedsettings.debug_mode")); // NOI18N
|
||||
chkDebug.setText(C.i18n("advancedsettings.debug_mode")); // NOI18N
|
||||
chkDebug.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
chkDebugFocusLost(evt);
|
||||
@@ -921,7 +917,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
Settings.getInstance().setLast((String) cboProfiles.getSelectedItem());
|
||||
if (getProfile().service().version().getVersionCount() <= 0)
|
||||
versionChanged(null);
|
||||
prepare(getProfile());
|
||||
prepareProfile(getProfile());
|
||||
}
|
||||
}//GEN-LAST:event_cboProfilesItemStateChanged
|
||||
|
||||
@@ -980,45 +976,45 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
}//GEN-LAST:event_btnModifyMouseClicked
|
||||
|
||||
private void txtJavaArgsFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtJavaArgsFocusLost
|
||||
getProfile().setJavaArgs(txtJavaArgs.getText());
|
||||
getProfile().getSelectedVersionSetting().setJavaArgs(txtJavaArgs.getText());
|
||||
}//GEN-LAST:event_txtJavaArgsFocusLost
|
||||
|
||||
private void txtMinecraftArgsFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtMinecraftArgsFocusLost
|
||||
getProfile().setMinecraftArgs(txtMinecraftArgs.getText());
|
||||
getProfile().getSelectedVersionSetting().setMinecraftArgs(txtMinecraftArgs.getText());
|
||||
}//GEN-LAST:event_txtMinecraftArgsFocusLost
|
||||
|
||||
private void txtPermSizeFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtPermSizeFocusLost
|
||||
getProfile().setPermSize(txtPermSize.getText());
|
||||
getProfile().getSelectedVersionSetting().setPermSize(txtPermSize.getText());
|
||||
}//GEN-LAST:event_txtPermSizeFocusLost
|
||||
|
||||
private void chkDebugFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_chkDebugFocusLost
|
||||
getProfile().setDebug(chkDebug.isSelected());
|
||||
getProfile().getSelectedVersionSetting().setDebug(chkDebug.isSelected());
|
||||
}//GEN-LAST:event_chkDebugFocusLost
|
||||
|
||||
private void chkNoJVMArgsFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_chkNoJVMArgsFocusLost
|
||||
getProfile().setNoJVMArgs(chkNoJVMArgs.isSelected());
|
||||
getProfile().getSelectedVersionSetting().setNoJVMArgs(chkNoJVMArgs.isSelected());
|
||||
}//GEN-LAST:event_chkNoJVMArgsFocusLost
|
||||
|
||||
private void chkCancelWrapperFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_chkCancelWrapperFocusLost
|
||||
getProfile().setCanceledWrapper(chkCancelWrapper.isSelected());
|
||||
getProfile().getSelectedVersionSetting().setCanceledWrapper(chkCancelWrapper.isSelected());
|
||||
}//GEN-LAST:event_chkCancelWrapperFocusLost
|
||||
|
||||
private void txtPrecalledCommandFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtPrecalledCommandFocusLost
|
||||
getProfile().setPrecalledCommand(txtPrecalledCommand.getText());
|
||||
getProfile().getSelectedVersionSetting().setPrecalledCommand(txtPrecalledCommand.getText());
|
||||
}//GEN-LAST:event_txtPrecalledCommandFocusLost
|
||||
|
||||
private void txtServerIPFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtServerIPFocusLost
|
||||
getProfile().setServerIp(txtServerIP.getText());
|
||||
getProfile().getSelectedVersionSetting().setServerIp(txtServerIP.getText());
|
||||
}//GEN-LAST:event_txtServerIPFocusLost
|
||||
|
||||
private void cboRunDirectoryFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_cboRunDirectoryFocusLost
|
||||
if (cboRunDirectory.getSelectedIndex() >= 0)
|
||||
getProfile().setGameDirType(GameDirType.values()[cboRunDirectory.getSelectedIndex()]);
|
||||
getProfile().getSelectedVersionSetting().setGameDirType(GameDirType.values()[cboRunDirectory.getSelectedIndex()]);
|
||||
}//GEN-LAST:event_cboRunDirectoryFocusLost
|
||||
|
||||
private void cboLauncherVisibilityFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_cboLauncherVisibilityFocusLost
|
||||
if (cboLauncherVisibility.getSelectedIndex() >= 0)
|
||||
getProfile().setLauncherVisibility(LauncherVisibility.values()[cboLauncherVisibility.getSelectedIndex()]);
|
||||
getProfile().getSelectedVersionSetting().setLauncherVisibility(LauncherVisibility.values()[cboLauncherVisibility.getSelectedIndex()]);
|
||||
}//GEN-LAST:event_cboLauncherVisibilityFocusLost
|
||||
|
||||
private void btnDownloadAllAssetsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDownloadAllAssetsActionPerformed
|
||||
@@ -1027,23 +1023,23 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
}//GEN-LAST:event_btnDownloadAllAssetsActionPerformed
|
||||
|
||||
private void txtMaxMemoryFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtMaxMemoryFocusLost
|
||||
getProfile().setMaxMemory(txtMaxMemory.getText());
|
||||
getProfile().getSelectedVersionSetting().setMaxMemory(txtMaxMemory.getText());
|
||||
}//GEN-LAST:event_txtMaxMemoryFocusLost
|
||||
|
||||
private void txtJavaDirFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtJavaDirFocusLost
|
||||
getProfile().setJavaDir(txtJavaDir.getText());
|
||||
getProfile().getSelectedVersionSetting().setJavaDir(txtJavaDir.getText());
|
||||
}//GEN-LAST:event_txtJavaDirFocusLost
|
||||
|
||||
private void chkFullscreenFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_chkFullscreenFocusLost
|
||||
getProfile().setFullscreen(chkFullscreen.isSelected());
|
||||
getProfile().getSelectedVersionSetting().setFullscreen(chkFullscreen.isSelected());
|
||||
}//GEN-LAST:event_chkFullscreenFocusLost
|
||||
|
||||
private void txtHeightFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtHeightFocusLost
|
||||
getProfile().setHeight(txtHeight.getText());
|
||||
getProfile().getSelectedVersionSetting().setHeight(txtHeight.getText());
|
||||
}//GEN-LAST:event_txtHeightFocusLost
|
||||
|
||||
private void txtWidthFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtWidthFocusLost
|
||||
getProfile().setWidth(txtWidth.getText());
|
||||
getProfile().getSelectedVersionSetting().setWidth(txtWidth.getText());
|
||||
}//GEN-LAST:event_txtWidthFocusLost
|
||||
|
||||
private void txtGameDirFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtGameDirFocusLost
|
||||
@@ -1067,7 +1063,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
try {
|
||||
String path = fc.getSelectedFile().getCanonicalPath();
|
||||
txtJavaDir.setText(path);
|
||||
getProfile().setJavaDir(txtJavaDir.getText());
|
||||
getProfile().getSelectedVersionSetting().setJavaDir(txtJavaDir.getText());
|
||||
} catch (IOException e) {
|
||||
HMCLog.warn("Failed to set java path.", e);
|
||||
MessageBox.Show(C.i18n("ui.label.failed_set") + e.getMessage());
|
||||
@@ -1080,9 +1076,9 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
int idx = cboJava.getSelectedIndex();
|
||||
if (idx != -1) {
|
||||
Java j = Java.JAVA.get(idx);
|
||||
getProfile().setJava(j);
|
||||
getProfile().getSelectedVersionSetting().setJava(j);
|
||||
txtJavaDir.setEnabled(idx == 1);
|
||||
txtJavaDir.setText(j.getHome() == null ? getProfile().getSettingsJavaDir() : j.getJava());
|
||||
txtJavaDir.setText(j.getHome() == null ? getProfile().getSelectedVersionSetting().getSettingsJavaDir() : j.getJava());
|
||||
}
|
||||
}//GEN-LAST:event_cboJavaItemStateChanged
|
||||
|
||||
@@ -1149,31 +1145,14 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
if (fc.getSelectedFile() == null)
|
||||
return;
|
||||
String suggestedModpackId = JOptionPane.showInputDialog("Please enter your favourite game name", FileUtils.getBaseName(fc.getSelectedFile().getName()));
|
||||
TaskWindow.getInstance().addTask(new TaskRunnable(C.i18n("modpack.install.task"), () -> {
|
||||
try {
|
||||
ModpackManager.install(fc.getSelectedFile(), getProfile().service(), suggestedModpackId);
|
||||
} catch (IOException ex) {
|
||||
MessageBox.Show(C.i18n("modpack.install_error"));
|
||||
HMCLog.err("Failed to install modpack", ex);
|
||||
}
|
||||
})).start();
|
||||
TaskWindow.getInstance().addTask(ModpackManager.install(fc.getSelectedFile(), getProfile().service(), suggestedModpackId)).start();
|
||||
refreshVersions();
|
||||
}//GEN-LAST:event_btnImportModpackActionPerformed
|
||||
|
||||
private void btnExportModpackActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExportModpackActionPerformed
|
||||
Map settings = (Map) WizardDisplayer.showWizard(new ModpackWizard(getProfile().service().version()).createWizard());
|
||||
if (settings != null)
|
||||
TaskWindow.getInstance().addTask(new TaskRunnable(C.i18n("modpack.save.task"),
|
||||
() -> {
|
||||
try {
|
||||
ModpackManager.export(new File((String) settings.get(ModpackInitializationPanel.KEY_MODPACK_LOCATION)),
|
||||
getProfile().service().version(),
|
||||
(String) settings.get(ModpackInitializationPanel.KEY_GAME_VERSION),
|
||||
((Boolean) settings.get(ModpackInitializationPanel.KEY_SAVE) == false) ? Arrays.asList("saves") : null);
|
||||
} catch (IOException | GameException ex) {
|
||||
MessageBox.Show(C.i18n("modpack.export_error"));
|
||||
HMCLog.err("Failed to export modpack", ex);
|
||||
}
|
||||
})).start();
|
||||
if (getProfile().service().version().getVersionCount() <= 0)
|
||||
return;
|
||||
WizardDisplayer.showWizard(new ModpackWizard(getProfile().service()).createWizard());
|
||||
}//GEN-LAST:event_btnExportModpackActionPerformed
|
||||
|
||||
// </editor-fold>
|
||||
@@ -1194,7 +1173,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
isLoading = true;
|
||||
cboProfiles.setSelectedIndex(index);
|
||||
isLoading = false;
|
||||
prepare(getProfile());
|
||||
prepareProfile(getProfile());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,15 +1181,26 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
return Settings.getProfile((String) cboProfiles.getSelectedItem());
|
||||
}
|
||||
|
||||
void prepare(Profile profile) {
|
||||
final String mcVersion() {
|
||||
return getProfile().getSelectedVersion();
|
||||
}
|
||||
|
||||
void prepareProfile(Profile profile) {
|
||||
if (profile == null)
|
||||
return;
|
||||
profile.selectedVersionChangedEvent.register(this);
|
||||
txtGameDir.setText(profile.getGameDir());
|
||||
|
||||
loadVersions();
|
||||
}
|
||||
|
||||
void prepareVersionSetting(VersionSetting profile) {
|
||||
if (profile == null)
|
||||
return;
|
||||
txtWidth.setText(profile.getWidth());
|
||||
txtHeight.setText(profile.getHeight());
|
||||
txtMaxMemory.setText(profile.getMaxMemory());
|
||||
txtPermSize.setText(profile.getPermSize());
|
||||
txtGameDir.setText(profile.getGameDir());
|
||||
txtJavaArgs.setText(profile.getJavaArgs());
|
||||
txtMinecraftArgs.setText(profile.getMinecraftArgs());
|
||||
txtJavaDir.setText(profile.getSettingsJavaDir());
|
||||
@@ -1227,9 +1217,6 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
cboJava.setSelectedIndex(profile.getJavaIndexInAllJavas());
|
||||
isLoading = false;
|
||||
cboJavaItemStateChanged(new ItemEvent(cboJava, 0, cboJava.getSelectedItem(), ItemEvent.SELECTED));
|
||||
|
||||
loadVersions();
|
||||
loadMinecraftVersion();
|
||||
}
|
||||
|
||||
void loadVersions() {
|
||||
@@ -1248,9 +1235,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
cboVersions.setSelectedIndex(index);
|
||||
|
||||
reloadMods();
|
||||
}
|
||||
|
||||
void loadMinecraftVersion() {
|
||||
prepareVersionSetting(((DefaultMinecraftService) getProfile().service()).getVersionSetting(getProfile().getSelectedVersion()));
|
||||
loadMinecraftVersion(getProfile().getSelectedVersion());
|
||||
}
|
||||
|
||||
@@ -1316,19 +1301,22 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget
|
||||
return;
|
||||
reloadingMods = true;
|
||||
DefaultTableModel model = SwingUtils.clearDefaultTable(lstExternalMods);
|
||||
Observable.<List<ModInfo>>createWithEmptySubscription(
|
||||
t -> t.onNext(getProfile().service().mod().recacheMods(getProfile().getSelectedVersion())))
|
||||
.subscribeOn(Schedulers.newThread()).observeOn(Schedulers.eventQueue())
|
||||
.subscribe(t -> {
|
||||
for (ModInfo x : t)
|
||||
model.addRow(new Object[] { x.isActive(), x, x.version });
|
||||
reloadingMods = false;
|
||||
});
|
||||
new OverridableSwingWorker<List<ModInfo>>() {
|
||||
@Override
|
||||
protected void work() throws Exception {
|
||||
publish(getProfile().service().mod().recacheMods(getProfile().getSelectedVersion()));
|
||||
}
|
||||
}.reg(t -> {
|
||||
for (ModInfo x : t)
|
||||
model.addRow(new Object[] { x.isActive(), x, x.version });
|
||||
reloadingMods = false;
|
||||
}).execute();
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
public void versionChanged(String version) {
|
||||
this.mcVersion = version;
|
||||
prepareVersionSetting(getProfile().getVersionSetting(version));
|
||||
for (InstallerPanel p : installerPanels)
|
||||
p.loadVersions();
|
||||
}
|
||||
|
||||
+5
-4
@@ -29,7 +29,6 @@ import org.jackhuang.hellominecraft.launcher.core.download.DownloadType;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import org.jackhuang.hellominecraft.utils.MessageBox;
|
||||
import org.jackhuang.hellominecraft.utils.views.SwingUtils;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,8 +42,10 @@ public class LauncherSettingsPanel extends AnimatedPanel {
|
||||
public LauncherSettingsPanel() {
|
||||
initComponents();
|
||||
|
||||
Observable.from(DownloadType.values()).map(t -> t.getName()).toList()
|
||||
.subscribe(t -> cboDownloadSource.setModel(new DefaultComboBoxModel(t.toArray(new String[0]))));
|
||||
DefaultComboBoxModel d = new DefaultComboBoxModel();
|
||||
for (DownloadType type : DownloadType.values())
|
||||
d.addElement(type.getName());
|
||||
cboDownloadSource.setModel(d);
|
||||
|
||||
txtBackgroundPath.setText(Settings.getInstance().getBgpath());
|
||||
txtProxyHost.setText(Settings.getInstance().getProxyHost());
|
||||
@@ -362,7 +363,7 @@ public class LauncherSettingsPanel extends AnimatedPanel {
|
||||
private void chkDecoratedFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_chkDecoratedFocusLost
|
||||
Settings.getInstance().setDecorated(chkDecorated.isSelected());
|
||||
}//GEN-LAST:event_chkDecoratedFocusLost
|
||||
|
||||
|
||||
private void jLabel9MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jLabel9MouseClicked
|
||||
SwingUtils.openLink("http://blog.163.com/huanghongxun2008@126/blog/static/7738046920160323812771/");
|
||||
}//GEN-LAST:event_jLabel9MouseClicked
|
||||
|
||||
@@ -319,10 +319,10 @@ public class MainPagePanel extends AnimatedPanel implements Event<String> {
|
||||
}//GEN-LAST:event_cboProfilesItemStateChanged
|
||||
|
||||
private void cboVersionsItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboVersionsItemStateChanged
|
||||
if (isLoading || evt.getStateChange() != ItemEvent.SELECTED || cboVersions.getSelectedIndex() < 0 || StrUtils.isBlank((String) cboVersions.getSelectedItem()) || getCurrentProfile() == null)
|
||||
if (isLoading || evt.getStateChange() != ItemEvent.SELECTED || cboVersions.getSelectedIndex() < 0 || StrUtils.isBlank((String) cboVersions.getSelectedItem()) || getProfile() == null)
|
||||
return;
|
||||
String mcv = (String) cboVersions.getSelectedItem();
|
||||
getCurrentProfile().setSelectedMinecraftVersion(mcv);
|
||||
getProfile().setSelectedMinecraftVersion(mcv);
|
||||
}//GEN-LAST:event_cboVersionsItemStateChanged
|
||||
|
||||
@Override
|
||||
@@ -379,18 +379,18 @@ public class MainPagePanel extends AnimatedPanel implements Event<String> {
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="Game Launch">
|
||||
void genLaunchCode(final Consumer<GameLauncher> listener) {
|
||||
if (isLaunching || getCurrentProfile() == null)
|
||||
if (isLaunching || getProfile() == null)
|
||||
return;
|
||||
isLaunching = true;
|
||||
HMCLog.log("Start generating launching command...");
|
||||
File file = getCurrentProfile().getCanonicalGameDirFile();
|
||||
File file = getProfile().getCanonicalGameDirFile();
|
||||
if (!file.exists()) {
|
||||
HMCLog.warn("The minecraft path is wrong, please check it yourself.");
|
||||
MessageBox.ShowLocalized("minecraft.wrong_path");
|
||||
return;
|
||||
}
|
||||
final String name = (String) cboProfiles.getSelectedItem();
|
||||
if (StrUtils.isBlank(name) || getCurrentProfile().getSelectedVersion() == null) {
|
||||
if (StrUtils.isBlank(name) || getProfile().getSelectedVersion() == null) {
|
||||
HMCLog.warn("There's no selected version, rechoose a version.");
|
||||
MessageBox.ShowLocalized("minecraft.no_selected_version");
|
||||
return;
|
||||
@@ -408,8 +408,8 @@ public class MainPagePanel extends AnimatedPanel implements Event<String> {
|
||||
@Override
|
||||
public void run() {
|
||||
Thread.currentThread().setName("Game Launcher");
|
||||
DefaultGameLauncher gl = new DefaultGameLauncher(getCurrentProfile().createLaunchOptions(), getCurrentProfile().service(), li, l);
|
||||
gl.setTag(getCurrentProfile().getLauncherVisibility());
|
||||
DefaultGameLauncher gl = new DefaultGameLauncher(getProfile().getSelectedVersionSetting().createLaunchOptions(getProfile().getCanonicalGameDirFile()), getProfile().service(), li, l);
|
||||
gl.setTag(getProfile().getSelectedVersionSetting().getLauncherVisibility());
|
||||
gl.successEvent.register((sender, s) -> {
|
||||
isLaunching = false;
|
||||
return true;
|
||||
@@ -478,10 +478,10 @@ public class MainPagePanel extends AnimatedPanel implements Event<String> {
|
||||
isLoading = true;
|
||||
cboVersions.removeAllItems();
|
||||
int index = 0, i = 0;
|
||||
getCurrentProfile().selectedVersionChangedEvent.register(this);
|
||||
getCurrentProfile().service().version().refreshVersions();
|
||||
String selVersion = getCurrentProfile().getSelectedVersion();
|
||||
if (getCurrentProfile().service().version().getVersions().isEmpty()) {
|
||||
getProfile().selectedVersionChangedEvent.register(this);
|
||||
getProfile().service().version().refreshVersions();
|
||||
String selVersion = getProfile().getSelectedVersion();
|
||||
if (getProfile().service().version().getVersions().isEmpty()) {
|
||||
if (!showedNoVersion)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (MessageBox.Show(C.i18n("mainwindow.no_version"), MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION) {
|
||||
@@ -491,7 +491,7 @@ public class MainPagePanel extends AnimatedPanel implements Event<String> {
|
||||
showedNoVersion = true;
|
||||
});
|
||||
} else {
|
||||
for (MinecraftVersion mcVersion : getCurrentProfile().service().version().getVersions()) {
|
||||
for (MinecraftVersion mcVersion : getProfile().service().version().getVersions()) {
|
||||
if (mcVersion.hidden)
|
||||
continue;
|
||||
cboVersions.addItem(mcVersion.id);
|
||||
@@ -628,7 +628,7 @@ public class MainPagePanel extends AnimatedPanel implements Event<String> {
|
||||
|
||||
}
|
||||
|
||||
public Profile getCurrentProfile() {
|
||||
public Profile getProfile() {
|
||||
return Settings.getProfile((String) cboProfiles.getSelectedItem());
|
||||
}
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jScrollPane1" alignment="0" pref="400" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jScrollPane1" alignment="1" pref="300" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTree" name="jTree1">
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.launcher.views.modpack;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import org.jackhuang.hellominecraft.utils.functions.BiFunction;
|
||||
import org.jackhuang.hellominecraft.utils.system.FileUtils;
|
||||
import org.jackhuang.hellominecraft.utils.views.checktree.CheckBoxTreeCellRenderer;
|
||||
import org.jackhuang.hellominecraft.utils.views.checktree.CheckBoxTreeNode;
|
||||
import org.jackhuang.hellominecraft.utils.views.checktree.CheckBoxTreeNodeSelectionListener;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardController;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class ModpackFileSelectionPanel extends javax.swing.JPanel {
|
||||
|
||||
private final WizardController controller;
|
||||
private final Map wizardData;
|
||||
private final BiFunction<String, Boolean, Integer> blackList;
|
||||
private final Set<String> bannedFiles = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Creates new form ModpackFileSelectionPanel
|
||||
*
|
||||
* @param blackList Return 0: non blocked, 1: non shown, 2: suggested,
|
||||
* checked
|
||||
*/
|
||||
public ModpackFileSelectionPanel(WizardController controller, Map wizardData, File gameDir, BiFunction<String, Boolean, Integer> blackList) {
|
||||
initComponents();
|
||||
|
||||
this.controller = controller;
|
||||
this.wizardData = wizardData;
|
||||
this.blackList = blackList;
|
||||
|
||||
CheckBoxTreeNode root = create(gameDir, "minecraft");
|
||||
|
||||
jTree1.setModel(new DefaultTreeModel(root));
|
||||
jTree1.setCellRenderer(new CheckBoxTreeCellRenderer());
|
||||
jTree1.addMouseListener(new CheckBoxTreeNodeSelectionListener());
|
||||
|
||||
wizardData.put("blackList", root);
|
||||
}
|
||||
|
||||
CheckBoxTreeNode create(File file, String basePath) {
|
||||
int state = 0;
|
||||
if (basePath.length() > "minecraft/".length())
|
||||
if ((state = blackList.apply(basePath.substring("minecraft/".length()) + (file.isDirectory() ? "/" : ""), file.isDirectory())) == 1)
|
||||
return null;
|
||||
CheckBoxTreeNode node = new CheckBoxTreeNode(FileUtils.getName(basePath));
|
||||
if (state == 2)
|
||||
node.setSelected(true);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
File[] f = file.listFiles();
|
||||
for (File subFile : f) {
|
||||
CheckBoxTreeNode subNode = create(subFile, basePath + "/" + subFile.getName());
|
||||
if (subNode != null) {
|
||||
subNode.setSelected(subNode.isSelected() | node.isSelected());
|
||||
node.add(subNode);
|
||||
}
|
||||
}
|
||||
if (!node.children().hasMoreElements())
|
||||
return null;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to
|
||||
* initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is
|
||||
* always regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jScrollPane1 = new javax.swing.JScrollPane();
|
||||
jTree1 = new javax.swing.JTree();
|
||||
|
||||
jScrollPane1.setViewportView(jTree1);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
private javax.swing.JTree jTree1;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
+1
-12
@@ -29,7 +29,6 @@
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="lblGameVersion" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="chkSave" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="67" max="32767" attributes="0"/>
|
||||
@@ -44,7 +43,7 @@
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="92" max="32767" attributes="0"/>
|
||||
<EmptySpace pref="120" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblModpackLocation" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="cboModpackLocation" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
@@ -56,8 +55,6 @@
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="cboGameVersion" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="chkSave" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@@ -101,14 +98,6 @@
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="chkSave">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="允许导出存档"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="chkSaveItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
|
||||
+3
-23
@@ -32,7 +32,6 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
|
||||
public static final String KEY_GAME_VERSION = "gameVersion";
|
||||
public static final String KEY_MODPACK_LOCATION = "modpackLocation";
|
||||
public static final String KEY_SAVE = "save";
|
||||
|
||||
private final WizardController controller;
|
||||
private final Map wizardData;
|
||||
@@ -47,18 +46,15 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
this.wizardData = wizardData;
|
||||
wizardData.put(KEY_GAME_VERSION, versions);
|
||||
|
||||
wizardData.put(KEY_SAVE, false);
|
||||
|
||||
configureComboContents();
|
||||
controller.setProblem(C.i18n("modpack.not_a_valid_location"));
|
||||
|
||||
controller.setForwardNavigationMode(WizardController.MODE_CAN_FINISH);
|
||||
}
|
||||
|
||||
private void configureComboContents() {
|
||||
String[] versions = (String[]) wizardData.get(KEY_GAME_VERSION);
|
||||
cboGameVersion.setModel(new DefaultComboBoxModel<>(versions));
|
||||
|
||||
if (versions.length > 0)
|
||||
wizardData.put(KEY_GAME_VERSION, versions[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +72,6 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
cboModpackLocation = new javax.swing.JButton();
|
||||
lblGameVersion = new javax.swing.JLabel();
|
||||
cboGameVersion = new javax.swing.JComboBox<>();
|
||||
chkSave = new javax.swing.JCheckBox();
|
||||
jLabel1 = new javax.swing.JLabel();
|
||||
|
||||
lblModpackLocation.setText(C.i18n("modpack.save")); // NOI18N
|
||||
@@ -102,13 +97,6 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
chkSave.setText("允许导出存档");
|
||||
chkSave.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
chkSaveItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
jLabel1.setText(C.i18n("modpack.warning")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
@@ -127,7 +115,6 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(lblGameVersion)
|
||||
.addComponent(chkSave)
|
||||
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(0, 67, Short.MAX_VALUE)))
|
||||
.addContainerGap())
|
||||
@@ -137,7 +124,7 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 92, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 120, Short.MAX_VALUE)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblModpackLocation)
|
||||
.addComponent(cboModpackLocation))
|
||||
@@ -147,8 +134,6 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
.addComponent(lblGameVersion)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(cboGameVersion, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(chkSave)
|
||||
.addContainerGap())
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
@@ -173,10 +158,6 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
controller.setProblem(null);
|
||||
}//GEN-LAST:event_txtModpackLocationCaretUpdate
|
||||
|
||||
private void chkSaveItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkSaveItemStateChanged
|
||||
wizardData.put(KEY_SAVE, chkSave.isSelected());
|
||||
}//GEN-LAST:event_chkSaveItemStateChanged
|
||||
|
||||
private void cboGameVersionItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboGameVersionItemStateChanged
|
||||
wizardData.put(KEY_GAME_VERSION, cboGameVersion.getSelectedItem());
|
||||
}//GEN-LAST:event_cboGameVersionItemStateChanged
|
||||
@@ -184,7 +165,6 @@ public class ModpackInitializationPanel extends javax.swing.JPanel {
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JComboBox<String> cboGameVersion;
|
||||
private javax.swing.JButton cboModpackLocation;
|
||||
private javax.swing.JCheckBox chkSave;
|
||||
private javax.swing.JLabel jLabel1;
|
||||
private javax.swing.JLabel lblGameVersion;
|
||||
private javax.swing.JLabel lblModpackLocation;
|
||||
|
||||
+62
-5
@@ -17,14 +17,27 @@
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.launcher.views.modpack;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.JComponent;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftProvider;
|
||||
import org.jackhuang.hellominecraft.launcher.core.GameException;
|
||||
import org.jackhuang.hellominecraft.launcher.core.mod.ModpackManager;
|
||||
import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService;
|
||||
import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import org.jackhuang.hellominecraft.utils.views.checktree.CheckBoxTreeNode;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.DeferredWizardResult;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.ResultProgressHandle;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.Summary;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardBranchController;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardController;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardException;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPanelProvider;
|
||||
|
||||
/**
|
||||
@@ -33,18 +46,62 @@ import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPanelProvider;
|
||||
*/
|
||||
public class ModpackWizard extends WizardBranchController {
|
||||
|
||||
public ModpackWizard(IMinecraftProvider provider) {
|
||||
super(new WizardPanelProvider(C.i18n("modpack.wizard"), new String[] { C.i18n("modpack.wizard.step.1") }, new String[] { C.i18n("modpack.wizard.step.1.title") }) {
|
||||
static void process(CheckBoxTreeNode node, String basePath, List<String> list) {
|
||||
if (node.isSelected()) {
|
||||
if (basePath.length() > "minecraft/".length())
|
||||
list.add(basePath.substring("minecraft/".length()));
|
||||
return;
|
||||
}
|
||||
Enumeration<CheckBoxTreeNode> e = node.children();
|
||||
for (; e.hasMoreElements();) {
|
||||
CheckBoxTreeNode n = e.nextElement();
|
||||
process(n, basePath + "/" + n.getUserObject(), list);
|
||||
}
|
||||
}
|
||||
|
||||
public ModpackWizard(IMinecraftService service) {
|
||||
super(new WizardPanelProvider(C.i18n("modpack.wizard"), new String[] { C.i18n("modpack.wizard.step.1"), C.i18n("modpack.wizard.step.2") }, new String[] { C.i18n("modpack.wizard.step.1.title"), C.i18n("modpack.wizard.step.2.title") }) {
|
||||
|
||||
@Override
|
||||
protected Object finish(Map settings) throws WizardException {
|
||||
return new DeferredWizardResult(false) {
|
||||
@Override
|
||||
public void start(Map settings, ResultProgressHandle progress) {
|
||||
progress.setBusy("Processing modpack");
|
||||
ArrayList<String> blackList = new ArrayList<>(ModpackManager.MODPACK_BLACK_LIST);
|
||||
CheckBoxTreeNode root = (CheckBoxTreeNode) settings.get("blackList");
|
||||
process(root, "minecraft", blackList);
|
||||
try {
|
||||
File loc = new File((String) settings.get(ModpackInitializationPanel.KEY_MODPACK_LOCATION));
|
||||
ModpackManager.export(loc,
|
||||
service.version(),
|
||||
(String) settings.get(ModpackInitializationPanel.KEY_GAME_VERSION),
|
||||
blackList);
|
||||
progress.finished(new Summary(C.i18n("modpack.export_finished") + ": " + loc.getAbsolutePath(), null));
|
||||
} catch (IOException | GameException ex) {
|
||||
HMCLog.err("Failed to export modpack", ex);
|
||||
progress.failed(C.i18n("modpack.export_error") + ": " + ex.getClass().getName() + ", " + ex.getLocalizedMessage(), true);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createPanel(WizardController controller, String id, Map settings) {
|
||||
switch (indexOfStep(id)) {
|
||||
case 0:
|
||||
String[] s = new String[provider.getVersionCount()];
|
||||
Iterator<MinecraftVersion> it = provider.getVersions().iterator();
|
||||
String[] s = new String[service.version().getVersionCount()];
|
||||
Iterator<MinecraftVersion> it = service.version().getVersions().iterator();
|
||||
for (int i = 0; i < s.length; i++)
|
||||
s[i] = it.next().id;
|
||||
|
||||
controller.setForwardNavigationMode(WizardController.MODE_CAN_CONTINUE);
|
||||
|
||||
return new ModpackInitializationPanel(controller, settings, s);
|
||||
case 1:
|
||||
controller.setForwardNavigationMode(WizardController.MODE_CAN_FINISH);
|
||||
|
||||
return new ModpackFileSelectionPanel(controller, settings, service.baseDirectory(), ModpackManager.MODPACK_PREDICATE);
|
||||
default:
|
||||
throw new IllegalArgumentException(id);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.jackhuang.hellominecraft.utils.functions.Predicate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -31,7 +30,9 @@ public final class CollectionUtils {
|
||||
|
||||
public static <T> ArrayList<T> map(Collection<T> coll, Predicate<T> p) {
|
||||
ArrayList<T> newColl = new ArrayList<>();
|
||||
Observable.from(coll).filter(t -> p.apply(t)).subscribe(t -> newColl.add(t));
|
||||
for (T t : coll)
|
||||
if (p.apply(t))
|
||||
newColl.add(t);
|
||||
return newColl;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hellominecraft.utils;
|
||||
|
||||
import java.util.Map;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -50,13 +49,13 @@ public interface IUpdateChecker {
|
||||
*
|
||||
* @return the process observable.
|
||||
*/
|
||||
Observable process(boolean showMessage);
|
||||
OverridableSwingWorker<VersionNumber> process(boolean showMessage);
|
||||
|
||||
/**
|
||||
* Get the download links.
|
||||
*
|
||||
* @return a JSON, which contains the server response.
|
||||
*/
|
||||
Observable<Map<String, String>> requestDownloadLink();
|
||||
OverridableSwingWorker<Map<String, String>> requestDownloadLink();
|
||||
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hellominecraft.utils.system.IOUtils;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -158,14 +157,4 @@ public final class NetUtils {
|
||||
throw new IllegalArgumentException("Could not concatenate given URL with GET arguments!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Observable<String> getRx(String url) {
|
||||
return Observable.createWithEmptySubscription(t1 -> {
|
||||
try {
|
||||
t1.onNext(get(url));
|
||||
} catch (Exception e) {
|
||||
t1.onError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.jackhuang.hellominecraft.utils.functions.Consumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class OverridableSwingWorker<T> extends SwingWorker<Void, T> {
|
||||
|
||||
List<Consumer<T>> processListeners = new ArrayList<>();
|
||||
List<Runnable> doneListeners = new ArrayList<>();
|
||||
|
||||
protected abstract void work() throws Exception;
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
for (Runnable c : doneListeners)
|
||||
c.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
work();
|
||||
return null;
|
||||
}
|
||||
|
||||
public OverridableSwingWorker reg(Consumer<T> c) {
|
||||
Utils.requireNonNull(c);
|
||||
processListeners.add(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OverridableSwingWorker regDone(Runnable c) {
|
||||
Utils.requireNonNull(c);
|
||||
doneListeners.add(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process(List<T> chunks) {
|
||||
for (T t : chunks)
|
||||
for (Consumer<T> c : processListeners)
|
||||
c.accept(t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hellominecraft.utils;
|
||||
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
import java.util.Map;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -41,27 +40,26 @@ public final class UpdateChecker implements IUpdateChecker {
|
||||
VersionNumber value;
|
||||
|
||||
@Override
|
||||
public Observable<VersionNumber> process(boolean showMessage) {
|
||||
return Observable.createWithEmptySubscription(t -> {
|
||||
if (value == null) {
|
||||
try {
|
||||
versionString = NetUtils.get("http://huangyuhui.duapp.com/info.php?type=" + type);
|
||||
} catch (Exception e) {
|
||||
HMCLog.warn("Failed to get update url.", e);
|
||||
return;
|
||||
}
|
||||
value = VersionNumber.check(versionString);
|
||||
}
|
||||
public OverridableSwingWorker<VersionNumber> process(final boolean showMessage) {
|
||||
return new OverridableSwingWorker() {
|
||||
@Override
|
||||
protected void work() throws Exception {
|
||||
|
||||
if (value == null) {
|
||||
HMCLog.warn("Failed to check update...");
|
||||
if (showMessage)
|
||||
MessageBox.Show(C.i18n("update.failed"));
|
||||
} else if (VersionNumber.isOlder(base, value))
|
||||
OUT_DATED = true;
|
||||
if (OUT_DATED)
|
||||
t.onNext(value);
|
||||
});
|
||||
if (value == null) {
|
||||
versionString = NetUtils.get("http://huangyuhui.duapp.com/info.php?type=" + type);
|
||||
value = VersionNumber.check(versionString);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
HMCLog.warn("Failed to check update...");
|
||||
if (showMessage)
|
||||
MessageBox.Show(C.i18n("update.failed"));
|
||||
} else if (VersionNumber.isOlder(base, value))
|
||||
OUT_DATED = true;
|
||||
if (OUT_DATED)
|
||||
publish(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,16 +68,19 @@ public final class UpdateChecker implements IUpdateChecker {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Observable<Map<String, String>> requestDownloadLink() {
|
||||
return Observable.createWithEmptySubscription(t -> {
|
||||
if (download_link == null)
|
||||
try {
|
||||
download_link = C.gson.fromJson(NetUtils.get("http://huangyuhui.duapp.com/update_link.php?type=" + type), Map.class);
|
||||
} catch (Exception e) {
|
||||
HMCLog.warn("Failed to get update link.", e);
|
||||
}
|
||||
t.onNext(download_link);
|
||||
});
|
||||
public synchronized OverridableSwingWorker<Map<String, String>> requestDownloadLink() {
|
||||
return new OverridableSwingWorker() {
|
||||
@Override
|
||||
protected void work() throws Exception {
|
||||
if (download_link == null)
|
||||
try {
|
||||
download_link = C.gson.fromJson(NetUtils.get("http://huangyuhui.duapp.com/update_link.php?type=" + type), Map.class);
|
||||
} catch (Exception e) {
|
||||
HMCLog.warn("Failed to get update link.", e);
|
||||
}
|
||||
publish(download_link);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public final EventHandler<VersionNumber> outdated = new EventHandler<>(this);
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.jackhuang.hellominecraft.utils.logging.logger.Logger;
|
||||
*/
|
||||
public class HMCLog {
|
||||
|
||||
public static Logger logger = new Logger("Hello Minecraft!");
|
||||
private static final Logger logger = new Logger("Hello Minecraft!");
|
||||
|
||||
public static void log(String message) {
|
||||
logger.info(message);
|
||||
|
||||
@@ -102,7 +102,7 @@ public class ZipEngine {
|
||||
} else {
|
||||
pathName = file.getPath().substring(basePath.length() + 1);
|
||||
if (pathNameCallback != null)
|
||||
pathName = pathNameCallback.apply(pathName, true);
|
||||
pathName = pathNameCallback.apply(pathName, false);
|
||||
if (pathName == null)
|
||||
continue;
|
||||
putFile(file, pathName);
|
||||
|
||||
@@ -87,11 +87,11 @@ public abstract class Task {
|
||||
|
||||
public abstract String getInfo();
|
||||
|
||||
public Collection<Task> getDependTasks() {
|
||||
public Collection<? extends Task> getDependTasks() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Collection<Task> getAfterTasks() {
|
||||
public Collection<? extends Task> getAfterTasks() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -110,11 +110,13 @@ public abstract class Task {
|
||||
return new DoubleTask(t, this);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
public boolean run() {
|
||||
try {
|
||||
executeTask();
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
HMCLog.err("Failed to execute task", t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,15 +79,15 @@ public class TaskList extends Thread {
|
||||
public void run() {
|
||||
executeTask(task);
|
||||
s.remove(this);
|
||||
threadPool.remove(this);
|
||||
THREAD_POOL.remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final Set<InvokeThread> threadPool = Collections.synchronizedSet(new HashSet<InvokeThread>());
|
||||
static final Set<Task> taskPool = Collections.synchronizedSet(new HashSet<Task>());
|
||||
static final Set<InvokeThread> THREAD_POOL = Collections.synchronizedSet(new HashSet<InvokeThread>());
|
||||
static final Set<Task> TASK_POOL = Collections.synchronizedSet(new HashSet<Task>());
|
||||
|
||||
private void processTasks(Collection<Task> c) {
|
||||
private void processTasks(Collection<? extends Task> c) {
|
||||
if (c == null)
|
||||
return;
|
||||
this.totTask += c.size();
|
||||
@@ -95,7 +95,7 @@ public class TaskList extends Thread {
|
||||
for (Task t2 : c) {
|
||||
t2.setParallelExecuting(true);
|
||||
InvokeThread thread = new InvokeThread(t2, runningThread);
|
||||
threadPool.add(thread);
|
||||
THREAD_POOL.add(thread);
|
||||
runningThread.add(thread);
|
||||
thread.start();
|
||||
}
|
||||
@@ -147,7 +147,7 @@ public class TaskList extends Thread {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("TaskList");
|
||||
|
||||
threadPool.clear();
|
||||
THREAD_POOL.clear();
|
||||
totTask = taskQueue.size();
|
||||
while (!taskQueue.isEmpty())
|
||||
executeTask(taskQueue.remove(0));
|
||||
@@ -162,12 +162,12 @@ public class TaskList extends Thread {
|
||||
|
||||
public void abort() {
|
||||
shouldContinue = false;
|
||||
while (!threadPool.isEmpty())
|
||||
synchronized (threadPool) {
|
||||
InvokeThread it = threadPool.iterator().next();
|
||||
while (!THREAD_POOL.isEmpty())
|
||||
synchronized (THREAD_POOL) {
|
||||
InvokeThread it = THREAD_POOL.iterator().next();
|
||||
if (!it.task.abort())
|
||||
it.interrupt();
|
||||
threadPool.remove(it);
|
||||
THREAD_POOL.remove(it);
|
||||
}
|
||||
this.interrupt();
|
||||
}
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils.views.checktree;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ColorUIResource;
|
||||
import javax.swing.tree.TreeCellRenderer;
|
||||
|
||||
public class CheckBoxTreeCellRenderer extends JPanel implements TreeCellRenderer {
|
||||
|
||||
protected JCheckBox check;
|
||||
protected CheckBoxTreeLabel label;
|
||||
|
||||
public CheckBoxTreeCellRenderer() {
|
||||
setLayout(null);
|
||||
add(check = new JCheckBox());
|
||||
add(label = new CheckBoxTreeLabel());
|
||||
check.setBackground(UIManager.getColor("Tree.textBackground"));
|
||||
label.setForeground(UIManager.getColor("Tree.textForeground"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回的是一个<code>JPanel</code>对象,该对象中包含一个<code>JCheckBox</code>对象
|
||||
* 和一个<code>JLabel</code>对象。并且根据每个结点是否被选中来决定<code>JCheckBox</code>
|
||||
* 是否被选中。
|
||||
*/
|
||||
@Override
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value,
|
||||
boolean selected, boolean expanded, boolean leaf, int row,
|
||||
boolean hasFocus) {
|
||||
String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
|
||||
setEnabled(tree.isEnabled());
|
||||
check.setSelected(((CheckBoxTreeNode) value).isSelected());
|
||||
label.setFont(tree.getFont());
|
||||
label.setText(stringValue);
|
||||
label.setSelected(selected);
|
||||
label.setFocus(hasFocus);
|
||||
if (leaf)
|
||||
label.setIcon(UIManager.getIcon("Tree.leafIcon"));
|
||||
else if (expanded)
|
||||
label.setIcon(UIManager.getIcon("Tree.openIcon"));
|
||||
else
|
||||
label.setIcon(UIManager.getIcon("Tree.closedIcon"));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension dCheck = check.getPreferredSize();
|
||||
Dimension dLabel = label.getPreferredSize();
|
||||
return new Dimension(dCheck.width + dLabel.width, dCheck.height < dLabel.height ? dLabel.height : dCheck.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
Dimension dCheck = check.getPreferredSize();
|
||||
Dimension dLabel = label.getPreferredSize();
|
||||
int yCheck = 0;
|
||||
int yLabel = 0;
|
||||
if (dCheck.height < dLabel.height)
|
||||
yCheck = (dLabel.height - dCheck.height) / 2;
|
||||
else
|
||||
yLabel = (dCheck.height - dLabel.height) / 2;
|
||||
check.setLocation(0, yCheck);
|
||||
check.setBounds(0, yCheck, dCheck.width, dCheck.height);
|
||||
label.setLocation(dCheck.width, yLabel);
|
||||
label.setBounds(dCheck.width, yLabel, dLabel.width, dLabel.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackground(Color color) {
|
||||
if (color instanceof ColorUIResource)
|
||||
color = null;
|
||||
super.setBackground(color);
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils.views.checktree;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ColorUIResource;
|
||||
|
||||
public class CheckBoxTreeLabel extends JLabel {
|
||||
|
||||
private boolean isSelected;
|
||||
private boolean hasFocus;
|
||||
|
||||
public CheckBoxTreeLabel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackground(Color color) {
|
||||
if (color instanceof ColorUIResource)
|
||||
color = null;
|
||||
super.setBackground(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
/*String str;
|
||||
if ((str = getText()) != null)
|
||||
if (0 < str.length()) {
|
||||
if (isSelected)
|
||||
g.setColor(UIManager.getColor("Tree.selectionBackground"));
|
||||
else
|
||||
g.setColor(UIManager.getColor("Tree.textBackground"));
|
||||
Dimension d = getPreferredSize();
|
||||
int imageOffset = 0;
|
||||
Icon currentIcon = getIcon();
|
||||
if (currentIcon != null)
|
||||
imageOffset = currentIcon.getIconWidth() + Math.max(0, getIconTextGap() - 1);
|
||||
g.fillRect(imageOffset, 0, d.width - 1 - imageOffset, d.height);
|
||||
if (hasFocus) {
|
||||
g.setColor(UIManager.getColor("Tree.selectionBorderColor"));
|
||||
g.drawRect(imageOffset, 0, d.width - 1 - imageOffset, d.height - 1);
|
||||
}
|
||||
}*/
|
||||
super.paint(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension retDimension = super.getPreferredSize();
|
||||
if (retDimension != null)
|
||||
retDimension = new Dimension(retDimension.width + 3, retDimension.height);
|
||||
return retDimension;
|
||||
}
|
||||
|
||||
public void setSelected(boolean isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
|
||||
public void setFocus(boolean hasFocus) {
|
||||
this.hasFocus = hasFocus;
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils.views.checktree;
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class CheckBoxTreeNode extends DefaultMutableTreeNode {
|
||||
|
||||
protected boolean isSelected;
|
||||
|
||||
public CheckBoxTreeNode() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public CheckBoxTreeNode(Object userObject) {
|
||||
this(userObject, true, false);
|
||||
}
|
||||
|
||||
public CheckBoxTreeNode(Object userObject, boolean allowsChildren, boolean isSelected) {
|
||||
super(userObject, allowsChildren);
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean _isSelected) {
|
||||
this.isSelected = _isSelected;
|
||||
|
||||
if (_isSelected) {
|
||||
// 如果选中,则将其所有的子结点都选中
|
||||
if (children != null)
|
||||
for (Object obj : children) {
|
||||
CheckBoxTreeNode node = (CheckBoxTreeNode) obj;
|
||||
if (_isSelected != node.isSelected())
|
||||
node.setSelected(_isSelected);
|
||||
}
|
||||
// 向上检查,如果父结点的所有子结点都被选中,那么将父结点也选中
|
||||
CheckBoxTreeNode pNode = (CheckBoxTreeNode) parent;
|
||||
// 开始检查pNode的所有子节点是否都被选中
|
||||
if (pNode != null) {
|
||||
int index = 0;
|
||||
for (; index < pNode.children.size(); ++index) {
|
||||
CheckBoxTreeNode pChildNode = (CheckBoxTreeNode) pNode.children.get(index);
|
||||
if (!pChildNode.isSelected())
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* 表明pNode所有子结点都已经选中,则选中父结点,
|
||||
* 该方法是一个递归方法,因此在此不需要进行迭代,因为
|
||||
* 当选中父结点后,父结点本身会向上检查的。
|
||||
*/
|
||||
if (index == pNode.children.size())
|
||||
if (pNode.isSelected() != _isSelected)
|
||||
pNode.setSelected(_isSelected);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* 如果是取消父结点导致子结点取消,那么此时所有的子结点都应该是选择上的;
|
||||
* 否则就是子结点取消导致父结点取消,然后父结点取消导致需要取消子结点,但
|
||||
* 是这时候是不需要取消子结点的。
|
||||
*/
|
||||
if (children != null) {
|
||||
int index = 0;
|
||||
for (; index < children.size(); ++index) {
|
||||
CheckBoxTreeNode childNode = (CheckBoxTreeNode) children.get(index);
|
||||
if (!childNode.isSelected())
|
||||
break;
|
||||
}
|
||||
// 从上向下取消的时候
|
||||
if (index == children.size())
|
||||
for (int i = 0; i < children.size(); ++i) {
|
||||
CheckBoxTreeNode node = (CheckBoxTreeNode) children.get(i);
|
||||
if (node.isSelected() != _isSelected)
|
||||
node.setSelected(_isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
// 向上取消,只要存在一个子节点不是选上的,那么父节点就不应该被选上。
|
||||
CheckBoxTreeNode pNode = (CheckBoxTreeNode) parent;
|
||||
if (pNode != null && pNode.isSelected() != _isSelected)
|
||||
pNode.setSelected(_isSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils.views.checktree;
|
||||
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
|
||||
public class CheckBoxTreeNodeSelectionListener extends MouseAdapter {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent event) {
|
||||
JTree tree = (JTree) event.getSource();
|
||||
int x = event.getX();
|
||||
int y = event.getY();
|
||||
int row = tree.getRowForLocation(x, y);
|
||||
TreePath path = tree.getPathForRow(row);
|
||||
if (path != null) {
|
||||
CheckBoxTreeNode node = (CheckBoxTreeNode) path.getLastPathComponent();
|
||||
if (node != null) {
|
||||
boolean isSelected = !node.isSelected();
|
||||
node.setSelected(isSelected);
|
||||
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -207,15 +207,13 @@ public abstract class WizardDisplayer {
|
||||
if (new HashSet(Arrays.asList(s)).size() != s.length)
|
||||
throw new RuntimeException("steps are duplicated: " + Arrays.asList(s));
|
||||
if (s.length == 1 && Wizard.UNDETERMINED_STEP.equals(s[0]))
|
||||
// assert false : "Only ID may not be UNDETERMINED_ID"; //NOI18N
|
||||
// assert false : "Only ID may not be UNDETERMINED_ID";
|
||||
throw new RuntimeException("Only ID may not be UNDETERMINED_ID");
|
||||
for (int i = 0; i < s.length; i++)
|
||||
if (Wizard.UNDETERMINED_STEP.equals(s[i]) && i != s.length - 1)
|
||||
// assert false : "UNDETERMINED_ID may only be last element in" + //NOI18N
|
||||
// " ids array " + Arrays.asList(s); //NOI18N
|
||||
throw new RuntimeException("UNDETERMINED_ID may only be last element in"
|
||||
+ //NOI18N
|
||||
" ids array " + Arrays.asList(s)); //NOI18N)
|
||||
// assert false : "UNDETERMINED_ID may only be last element in" +
|
||||
// " ids array " + Arrays.asList(s);
|
||||
throw new RuntimeException("UNDETERMINED_ID may only be last element in ids array " + Arrays.asList(s));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+53
-49
@@ -12,7 +12,6 @@ import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.Action;
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -62,9 +61,6 @@ public class NavButtonManager implements ActionListener {
|
||||
*/
|
||||
static final String DEFERRED_FAILED = "FAILED_";
|
||||
|
||||
private static final Logger logger
|
||||
= Logger.getLogger(NavButtonManager.class.getName());
|
||||
|
||||
JButton next = null;
|
||||
|
||||
JButton prev = null;
|
||||
@@ -105,7 +101,7 @@ public class NavButtonManager implements ActionListener {
|
||||
prev.setName(NAME_PREV);
|
||||
prev.setMnemonic(C.i18n("wizard.prev_mnemonic").charAt(0));
|
||||
|
||||
finish = new JButton(C.i18n("wizard.finish")); // NOI18N
|
||||
finish = new JButton(C.i18n("wizard.finish"));
|
||||
finish.setName(NAME_FINISH);
|
||||
finish.setMnemonic(C.i18n("wizard.finish_mnemonic").charAt(0));
|
||||
|
||||
@@ -127,7 +123,7 @@ public class NavButtonManager implements ActionListener {
|
||||
help.setVisible(helpAction != null);
|
||||
|
||||
// Use standard default-button-last order on Aqua L&F
|
||||
final boolean aqua = "Aqua".equals(UIManager.getLookAndFeel().getID()); // NOI18N
|
||||
final boolean aqua = "Aqua".equals(UIManager.getLookAndFeel().getID());
|
||||
|
||||
buttons = new JPanel() {
|
||||
public void doLayout() {
|
||||
@@ -163,7 +159,7 @@ public class NavButtonManager implements ActionListener {
|
||||
}
|
||||
};
|
||||
buttons.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, UIManager
|
||||
.getColor("textText"))); // NOI18N
|
||||
.getColor("textText")));
|
||||
|
||||
buttons.add(prev);
|
||||
buttons.add(next);
|
||||
@@ -203,20 +199,23 @@ public class NavButtonManager implements ActionListener {
|
||||
final boolean enableNext = nextStep != null && canContinue && problem == null && !isDeferredResult;
|
||||
final boolean enablePrevious = wizard.getPreviousStep() != null && !isDeferredResult;
|
||||
|
||||
final Runnable runnable = () -> {
|
||||
next.setEnabled(enableNext);
|
||||
prev.setEnabled(enablePrevious);
|
||||
finish.setEnabled(enableFinish);
|
||||
JRootPane root = next.getRootPane();
|
||||
if (root != null)
|
||||
if (next.isEnabled())
|
||||
root.setDefaultButton(next);
|
||||
else if (finish.isEnabled())
|
||||
root.setDefaultButton(finish);
|
||||
else if (prev.isEnabled())
|
||||
root.setDefaultButton(prev);
|
||||
else
|
||||
root.setDefaultButton(null);
|
||||
final Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
next.setEnabled(enableNext);
|
||||
prev.setEnabled(enablePrevious);
|
||||
finish.setEnabled(enableFinish);
|
||||
JRootPane root = next.getRootPane();
|
||||
if (root != null)
|
||||
if (next.isEnabled())
|
||||
root.setDefaultButton(next);
|
||||
else if (finish.isEnabled())
|
||||
root.setDefaultButton(finish);
|
||||
else if (prev.isEnabled())
|
||||
root.setDefaultButton(prev);
|
||||
else
|
||||
root.setDefaultButton(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (EventQueue.isDispatchThread())
|
||||
@@ -267,18 +266,21 @@ public class NavButtonManager implements ActionListener {
|
||||
}
|
||||
|
||||
void deferredResultFailed(final boolean canGoBack) {
|
||||
final Runnable runnable = () -> {
|
||||
if (!canGoBack)
|
||||
getCancel().setText(getCloseString());
|
||||
getPrev().setEnabled(true);
|
||||
getNext().setEnabled(false);
|
||||
getCancel().setEnabled(true);
|
||||
getFinish().setEnabled(false);
|
||||
final Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!canGoBack)
|
||||
getCancel().setText(getCloseString());
|
||||
getPrev().setEnabled(true);
|
||||
getNext().setEnabled(false);
|
||||
getCancel().setEnabled(true);
|
||||
getFinish().setEnabled(false);
|
||||
|
||||
if (NAME_CLOSE.equals(deferredStatus)) {
|
||||
// no action
|
||||
} else
|
||||
deferredStatus = DEFERRED_FAILED + deferredStatus;
|
||||
if (NAME_CLOSE.equals(deferredStatus)) {
|
||||
// no action
|
||||
} else
|
||||
deferredStatus = DEFERRED_FAILED + deferredStatus;
|
||||
}
|
||||
};
|
||||
if (EventQueue.isDispatchThread())
|
||||
runnable.run();
|
||||
@@ -494,7 +496,6 @@ public class NavButtonManager implements ActionListener {
|
||||
boolean closeWindow;
|
||||
|
||||
if (reallyCancel && parent.cancel()) {
|
||||
logger.fine("calling wizard cancel method on " + wizard);
|
||||
wizard.cancel(settings);
|
||||
return;
|
||||
}
|
||||
@@ -530,7 +531,7 @@ public class NavButtonManager implements ActionListener {
|
||||
if (window != null && parent.receiver == null && window instanceof JDialog)
|
||||
((JDialog) window).getRootPane().setDefaultButton(cancel);
|
||||
|
||||
cancel.setText(getCloseString()); // NOI18N
|
||||
cancel.setText(getCloseString());
|
||||
cancel.setMnemonic(C.i18n("wizard.close_mnemonic").charAt(0));
|
||||
cancel.setName(NAME_CLOSE);
|
||||
deferredStatus = null; // ?? should summary be different
|
||||
@@ -601,22 +602,25 @@ public class NavButtonManager implements ActionListener {
|
||||
}
|
||||
|
||||
public void navigabilityChanged(final Wizard wizard) {
|
||||
final Runnable runnable = () -> {
|
||||
if (wizard.isBusy()) {
|
||||
next.setEnabled(false);
|
||||
prev.setEnabled(false);
|
||||
finish.setEnabled(false);
|
||||
cancel.setEnabled(false);
|
||||
parent.getOuterPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
wasBusy = true;
|
||||
return;
|
||||
} else if (wasBusy) {
|
||||
cancel.setEnabled(true);
|
||||
parent.getOuterPanel().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
configureNavigationButtons(wizard, prev, next, finish);
|
||||
final Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (wizard.isBusy()) {
|
||||
next.setEnabled(false);
|
||||
prev.setEnabled(false);
|
||||
finish.setEnabled(false);
|
||||
cancel.setEnabled(false);
|
||||
parent.getOuterPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
wasBusy = true;
|
||||
return;
|
||||
} else if (wasBusy) {
|
||||
cancel.setEnabled(true);
|
||||
parent.getOuterPanel().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
configureNavigationButtons(wizard, prev, next, finish);
|
||||
|
||||
parent.updateProblem();
|
||||
parent.updateProblem();
|
||||
}
|
||||
};
|
||||
if (EventQueue.isDispatchThread())
|
||||
runnable.run();
|
||||
|
||||
+6
-49
@@ -3,15 +3,12 @@ package org.jackhuang.hellominecraft.utils.views.wizard.api.displayer;
|
||||
import java.awt.Container;
|
||||
import java.awt.EventQueue;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import org.jackhuang.hellominecraft.utils.C;
|
||||
import org.jackhuang.hellominecraft.utils.logging.HMCLog;
|
||||
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.ResultProgressHandle;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.spi.Summary;
|
||||
@@ -31,21 +28,14 @@ import org.jackhuang.hellominecraft.utils.views.wizard.spi.Summary;
|
||||
*/
|
||||
public class NavProgress implements ResultProgressHandle {
|
||||
|
||||
private static final Logger logger
|
||||
= Logger.getLogger(NavProgress.class.getName());
|
||||
|
||||
JProgressBar progressBar = new JProgressBar();
|
||||
|
||||
JLabel lbl = new JLabel();
|
||||
|
||||
JLabel busy = new JLabel();
|
||||
|
||||
WizardDisplayerImpl parent;
|
||||
|
||||
String failMessage = null;
|
||||
|
||||
boolean isUseBusy = false;
|
||||
|
||||
Container ipanel = null;
|
||||
|
||||
boolean isInitialized = false;
|
||||
@@ -55,25 +45,20 @@ public class NavProgress implements ResultProgressHandle {
|
||||
*/
|
||||
boolean isRunning = true;
|
||||
|
||||
NavProgress(WizardDisplayerImpl impl, boolean useBusy) {
|
||||
NavProgress(WizardDisplayerImpl impl) {
|
||||
this.parent = impl;
|
||||
isUseBusy = useBusy;
|
||||
}
|
||||
|
||||
public void addProgressComponents(Container panel) {
|
||||
panel.add(lbl);
|
||||
if (isUseBusy) {
|
||||
ensureBusyInitialized();
|
||||
panel.add(busy);
|
||||
} else
|
||||
panel.add(progressBar);
|
||||
panel.add(progressBar);
|
||||
isInitialized = true;
|
||||
ipanel = panel;
|
||||
}
|
||||
|
||||
public void setProgress(final String description, final int currentStep, final int totalSteps) {
|
||||
invoke(() -> {
|
||||
lbl.setText(description == null ? " " : description); // NOI18N
|
||||
lbl.setText(description == null ? " " : description);
|
||||
setProgress(currentStep, totalSteps);
|
||||
});
|
||||
}
|
||||
@@ -93,8 +78,6 @@ public class NavProgress implements ResultProgressHandle {
|
||||
progressBar.setMaximum(totalSteps);
|
||||
progressBar.setValue(currentStep);
|
||||
}
|
||||
|
||||
setUseBusy(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,34 +86,9 @@ public class NavProgress implements ResultProgressHandle {
|
||||
lbl.setText(description == null ? " " : description);
|
||||
|
||||
progressBar.setIndeterminate(true);
|
||||
|
||||
setUseBusy(true);
|
||||
});
|
||||
}
|
||||
|
||||
protected void setUseBusy(boolean useBusy) {
|
||||
if (isInitialized)
|
||||
if (useBusy && (!isUseBusy)) {
|
||||
ipanel.remove(progressBar);
|
||||
ensureBusyInitialized();
|
||||
ipanel.add(busy);
|
||||
ipanel.invalidate();
|
||||
} else if (!useBusy && isUseBusy) {
|
||||
ipanel.remove(busy);
|
||||
ipanel.add(progressBar);
|
||||
ipanel.invalidate();
|
||||
}
|
||||
isUseBusy = useBusy;
|
||||
}
|
||||
|
||||
private void ensureBusyInitialized() {
|
||||
if (busy.getIcon() == null) {
|
||||
URL url = getClass().getResource("/org/jackhuang/hellominecraft/busy.gif");
|
||||
Icon icon = new ImageIcon(url);
|
||||
busy.setIcon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
private void invoke(Runnable r) {
|
||||
if (EventQueue.isDispatchThread())
|
||||
r.run();
|
||||
@@ -138,8 +96,7 @@ public class NavProgress implements ResultProgressHandle {
|
||||
try {
|
||||
EventQueue.invokeAndWait(r);
|
||||
} catch (InvocationTargetException | InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
logger.severe("Error invoking operation " + ex.getClass().getName() + " " + ex.getMessage());
|
||||
HMCLog.err("NavProgress: Error invoking operation", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +123,7 @@ public class NavProgress implements ResultProgressHandle {
|
||||
|
||||
Runnable r = () -> {
|
||||
// cheap word wrap
|
||||
JLabel comp = new JLabel("<html><body>" + message); // NOI18N
|
||||
JLabel comp = new JLabel("<html><body>" + message);
|
||||
comp.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
parent.setCurrentWizardPanel(comp);
|
||||
parent.getTtlLabel().setText(C.i18n("wizard.failed"));
|
||||
|
||||
+16
-20
@@ -34,8 +34,6 @@ import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -125,7 +123,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
ttlLabel = new JLabel(wizard.getStepDescription(wizard.getAllSteps()[0]));
|
||||
ttlLabel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
|
||||
.createEmptyBorder(5, 5, 12, 5), BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager
|
||||
.getColor("textText")))); // NOI18N
|
||||
.getColor("textText"))));
|
||||
ttlPanel = new JPanel() {
|
||||
public void doLayout() {
|
||||
Dimension d = ttlLabel.getPreferredSize();
|
||||
@@ -142,7 +140,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
ttlPanel.add(ttlLabel);
|
||||
Font f = ttlLabel.getFont();
|
||||
if (f == null)
|
||||
f = UIManager.getFont("controlFont"); // NOI18N
|
||||
f = UIManager.getFont("controlFont");
|
||||
if (f != null) {
|
||||
f = f.deriveFont(Font.BOLD);
|
||||
ttlLabel.setFont(f);
|
||||
@@ -173,7 +171,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
outerPanel = new JPanel();
|
||||
|
||||
if (wizard.getAllSteps().length == 0)
|
||||
throw new IllegalArgumentException("Wizard has no steps"); // NOI18N
|
||||
throw new IllegalArgumentException("Wizard has no steps");
|
||||
|
||||
// initialize the ttl* stuff
|
||||
buildStepTitle();
|
||||
@@ -190,8 +188,8 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
}
|
||||
};
|
||||
outerPanel.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW).put(
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); //NOI18N
|
||||
outerPanel.getActionMap().put("cancel", kbdCancel); //NOI18N
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
|
||||
outerPanel.getActionMap().put("cancel", kbdCancel);
|
||||
|
||||
instructions = createInstructionsPanel();
|
||||
|
||||
@@ -202,7 +200,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
inner.add(ttlPanel, BorderLayout.NORTH);
|
||||
|
||||
problem = new JLabel(" ");
|
||||
Color fg = UIManager.getColor("nb.errorColor"); // NOI18N
|
||||
Color fg = UIManager.getColor("nb.errorColor");
|
||||
problem.setForeground(fg == null ? Color.BLUE : fg);
|
||||
inner.add(problem, BorderLayout.SOUTH);
|
||||
problem.setPreferredSize(new Dimension(20, 20));
|
||||
@@ -254,10 +252,9 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
public Object show(final Wizard awizard, Rectangle bounds, Action helpAction,
|
||||
Map initialProperties) {
|
||||
if (!EventQueue.isDispatchThread() && !warned) {
|
||||
Logger.getLogger(WizardDisplayerImpl.class.getName()).log(Level.WARNING,
|
||||
"show() should be called from the AWT Event Thread. This "
|
||||
+ "call may deadlock - c.f. "
|
||||
+ "http://java.net/jira/browse/WIZARD-33", new Throwable());
|
||||
HMCLog.warn("WizardDisplayerImpl: show() should be called from the AWT Event Thread. This "
|
||||
+ "call may deadlock - c.f. "
|
||||
+ "http://java.net/jira/browse/WIZARD-33", new Throwable());
|
||||
warned = true;
|
||||
}
|
||||
createOuterPanel(awizard, bounds, helpAction, initialProperties);
|
||||
@@ -364,8 +361,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Illegal forward "
|
||||
+ // NOI18N
|
||||
"navigation mode: " + i); // NOI18N
|
||||
+ "navigation mode: " + i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,8 +409,8 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
|
||||
}
|
||||
|
||||
protected ResultProgressHandle createProgressDisplay(boolean isUseBusy) {
|
||||
return new NavProgress(this, isUseBusy);
|
||||
protected ResultProgressHandle createProgressDisplay() {
|
||||
return new NavProgress(this);
|
||||
}
|
||||
|
||||
void handleDeferredWizardResult(final DeferredWizardResult r, final boolean inSummary) {
|
||||
@@ -422,7 +418,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
deferredResult = r;
|
||||
}
|
||||
wizardPanel.setEnabled(false);
|
||||
progress = createProgressDisplay(r.isUseBusy());
|
||||
progress = createProgressDisplay();
|
||||
Container inst = instructions.getComponent();
|
||||
progress.addProgressComponents(inst);
|
||||
inst.invalidate();
|
||||
@@ -450,7 +446,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
}
|
||||
}
|
||||
else {
|
||||
synchronized (this) {
|
||||
synchronized (WizardDisplayerImpl.this) {
|
||||
deferredResult = null;
|
||||
}
|
||||
buttonManager.getCancel().setEnabled(true);
|
||||
@@ -463,7 +459,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
}
|
||||
}
|
||||
};
|
||||
Thread runner = new Thread(run, "Wizard Background Result Thread " + r); // NOI18N
|
||||
Thread runner = new Thread(run, "Wizard Background Result Thread " + r);
|
||||
runner.start();
|
||||
}
|
||||
|
||||
@@ -553,7 +549,7 @@ public class WizardDisplayerImpl extends WizardDisplayer {
|
||||
|
||||
void updateProblem() {
|
||||
String prob = wizard.getProblem();
|
||||
problem.setText(prob == null ? " " : prob); // NOI18N
|
||||
problem.setText(prob == null ? " " : prob);
|
||||
if (prob != null && prob.trim().length() == 0)
|
||||
// Issue 3 - provide ability to disable next w/o
|
||||
// showing the error line
|
||||
|
||||
+34
-65
@@ -29,7 +29,6 @@ import java.awt.IllegalComponentStateException;
|
||||
import java.awt.Insets;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Arrays;
|
||||
import javax.accessibility.Accessible;
|
||||
@@ -72,7 +71,7 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
|
||||
public InstructionsPanelImpl(Wizard wiz) {
|
||||
this(null, wiz);
|
||||
Font f = UIManager.getFont("Tree.font"); //NOI18N
|
||||
Font f = UIManager.getFont("Tree.font");
|
||||
if (f != null)
|
||||
setFont(f);
|
||||
}
|
||||
@@ -119,39 +118,10 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
}
|
||||
|
||||
public InstructionsPanelImpl(BufferedImage img, Wizard wizard) {
|
||||
if (img == null)
|
||||
//In the event of classloader issues, also have a way to get
|
||||
//the image from UIManager - slightly more portable for large
|
||||
//apps
|
||||
img = (BufferedImage) UIManager.get("wizard.sidebar.image"); //NOI18N
|
||||
|
||||
String imgStr = System.getProperty("wizard.sidebar.image"); //NOI18N
|
||||
//image has not been loaded and user wishes to supply their own image
|
||||
if (img == null && imgStr != null) {
|
||||
//get an URL, works for jars
|
||||
ClassLoader cl = this.getClass().getClassLoader();
|
||||
URL url = cl.getResource(imgStr);
|
||||
//successfully parsed the URL
|
||||
if (url != null)
|
||||
try {
|
||||
img = ImageIO.read(url);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Could not load wizard image "
|
||||
+ //NOI18N
|
||||
ioe.getMessage());
|
||||
System.setProperty("wizard.sidebar.image", null); //NOI18N
|
||||
img = null; //error loading img, set to null to use default
|
||||
}
|
||||
else { //URL was not successfully parsed, set img to null to use default
|
||||
System.err.println("Bad URL for wizard image " + imgStr); //NOI18N
|
||||
System.setProperty("wizard.sidebar.image", null); //NOI18N
|
||||
img = null;
|
||||
}
|
||||
}
|
||||
if (img == null)
|
||||
try {
|
||||
img = ImageIO.read(InstructionsPanelImpl.class.getResourceAsStream(
|
||||
"/org/jackhuang/hellominecraft/wizard.jpg")); //NOI18N
|
||||
"/org/jackhuang/hellominecraft/wizard.jpg"));
|
||||
} catch (IOException ioe) {
|
||||
HMCLog.err("Failed to load wizard.jpg, maybe you fucking modified the launcher", ioe);
|
||||
}
|
||||
@@ -189,7 +159,7 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
|
||||
public final void paintComponent(Graphics g) {
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
Font f = getFont() != null ? getFont() : UIManager.getFont("controlFont"); //NOI18N
|
||||
Font f = getFont() != null ? getFont() : UIManager.getFont("controlFont");
|
||||
FontMetrics fm = g.getFontMetrics(f);
|
||||
Insets ins = getInsets();
|
||||
int dx = ins.left;
|
||||
@@ -202,13 +172,13 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
//Don't fetch step list if in summary page, there will
|
||||
//only be the base ones
|
||||
steps = wizard.getAllSteps();
|
||||
String steps[] = this.steps;
|
||||
String[] steps2 = this.steps;
|
||||
if (inSummaryPage) {
|
||||
String summaryStep = C.i18n("wizard.summary");
|
||||
String[] nue = new String[steps.length + 1];
|
||||
System.arraycopy(steps, 0, nue, 0, steps.length);
|
||||
String[] nue = new String[steps2.length + 1];
|
||||
System.arraycopy(steps2, 0, nue, 0, steps2.length);
|
||||
nue[nue.length - 1] = summaryStep;
|
||||
steps = nue;
|
||||
steps2 = nue;
|
||||
}
|
||||
int y = fm.getMaxAscent() + ins.top + MARGIN;
|
||||
int x = ins.left + MARGIN;
|
||||
@@ -228,27 +198,27 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
|
||||
y += h + 10;
|
||||
int first = 0;
|
||||
int stop = steps.length;
|
||||
boolean wontFit = y + (h * (steps.length)) > getHeight();
|
||||
int stop = steps2.length;
|
||||
boolean wontFit = y + (h * (steps2.length)) > getHeight();
|
||||
if (wontFit) {
|
||||
//try to center the current step
|
||||
int availHeight = bottom - y;
|
||||
int willFit = availHeight / h;
|
||||
int currStepIndex = Arrays.asList(steps).indexOf(currentStep);
|
||||
int currStepIndex = Arrays.asList(steps2).indexOf(currentStep);
|
||||
int rangeStart = Math.max(0, currStepIndex - (willFit / 2));
|
||||
int rangeEnd = Math.min(rangeStart + willFit, steps.length);
|
||||
if (rangeStart + willFit > steps.length) {
|
||||
int rangeEnd = Math.min(rangeStart + willFit, steps2.length);
|
||||
if (rangeStart + willFit > steps2.length) {
|
||||
//Don't scroll off if there's room
|
||||
rangeStart = steps.length - willFit;
|
||||
rangeEnd = steps.length;
|
||||
rangeStart = steps2.length - willFit;
|
||||
rangeEnd = steps2.length;
|
||||
}
|
||||
steps = (String[]) steps.clone();
|
||||
steps2 = (String[]) steps2.clone();
|
||||
if (rangeStart != 0) {
|
||||
steps[rangeStart] = elipsis;
|
||||
steps2[rangeStart] = elipsis;
|
||||
first = rangeStart;
|
||||
}
|
||||
if (rangeEnd != steps.length && rangeEnd > 0) {
|
||||
steps[rangeEnd - 1] = elipsis;
|
||||
if (rangeEnd != steps2.length && rangeEnd > 0) {
|
||||
steps2[rangeEnd - 1] = elipsis;
|
||||
stop = rangeEnd;
|
||||
}
|
||||
}
|
||||
@@ -257,25 +227,25 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
g.setColor(getForeground());
|
||||
|
||||
for (int i = first; i < stop; i++) {
|
||||
boolean isUndetermined = Wizard.UNDETERMINED_STEP.equals(steps[i]);
|
||||
boolean isUndetermined = Wizard.UNDETERMINED_STEP.equals(steps2[i]);
|
||||
boolean canOnlyFinish = wizard.getForwardNavigationMode()
|
||||
== Wizard.MODE_CAN_FINISH;
|
||||
if (isUndetermined && canOnlyFinish)
|
||||
break;
|
||||
String curr;
|
||||
if (!elipsis.equals(steps[i]))
|
||||
if (!elipsis.equals(steps2[i]))
|
||||
if (inSummaryPage && i == this.steps.length)
|
||||
curr = (i + 1) + ". " + steps[i];
|
||||
curr = (i + 1) + ". " + steps2[i];
|
||||
else
|
||||
curr = (i + 1) + ". " + (isUndetermined
|
||||
? elipsis
|
||||
: steps[i].equals(elipsis) ? elipsis
|
||||
: wizard.getStepDescription(steps[i]));
|
||||
: steps2[i].equals(elipsis) ? elipsis
|
||||
: wizard.getStepDescription(steps2[i]));
|
||||
else
|
||||
curr = elipsis;
|
||||
if (curr != null) {
|
||||
boolean selected = (steps[i].equals(currentStep) && !inSummaryPage)
|
||||
|| (inSummaryPage && i == steps.length - 1);
|
||||
boolean selected = (steps2[i].equals(currentStep) && !inSummaryPage)
|
||||
|| (inSummaryPage && i == steps2.length - 1);
|
||||
if (selected)
|
||||
g.setFont(boldFont);
|
||||
|
||||
@@ -297,7 +267,7 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
|
||||
public final Dimension getPreferredSize() {
|
||||
Font f = getFont() != null ? getFont()
|
||||
: UIManager.getFont("controlFont"); //NOI18N
|
||||
: UIManager.getFont("controlFont");
|
||||
|
||||
Graphics g = getGraphics();
|
||||
if (g == null)
|
||||
@@ -307,25 +277,24 @@ public class InstructionsPanelImpl extends JComponent implements WizardObserver,
|
||||
Insets ins = getInsets();
|
||||
int h = fm.getHeight();
|
||||
|
||||
String[] steps = wizard.getAllSteps();
|
||||
String[] steps2 = wizard.getAllSteps();
|
||||
int w = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < steps.length; i++) {
|
||||
String desc = i + ". " + (Wizard.UNDETERMINED_STEP.equals(steps[i])
|
||||
for (int i = 0; i < steps2.length; i++) {
|
||||
String desc = i + ". " + (Wizard.UNDETERMINED_STEP.equals(steps2[i])
|
||||
? elipsis
|
||||
: //NOI18N
|
||||
wizard.getStepDescription(steps[i]));
|
||||
: wizard.getStepDescription(steps2[i]));
|
||||
if (desc != null)
|
||||
w = Math.max(w, fm.stringWidth(desc) + MARGIN);
|
||||
}
|
||||
if (Integer.MIN_VALUE == w)
|
||||
w = 250;
|
||||
BufferedImage img = getImage();
|
||||
if (img != null)
|
||||
w = Math.max(w, img.getWidth());
|
||||
BufferedImage image = getImage();
|
||||
if (image != null)
|
||||
w = Math.max(w, image.getWidth());
|
||||
//Make sure we can grow but not shrink
|
||||
w = Math.max(w, historicWidth);
|
||||
historicWidth = w;
|
||||
return new Dimension(w, ins.top + ins.bottom + ((h + 3) * steps.length));
|
||||
return new Dimension(w, ins.top + ins.bottom + ((h + 3) * steps2.length));
|
||||
}
|
||||
|
||||
private boolean inSummaryPage;
|
||||
|
||||
+77
-98
@@ -8,12 +8,11 @@ and include the License file at http://www.netbeans.org/cddl.txt.
|
||||
If applicable, add the following below the CDDL Header, with the fields
|
||||
enclosed by brackets [] replaced by your own identifying information:
|
||||
"Portions Copyrighted [year] [name of copyright owner]" */
|
||||
/*
|
||||
/*
|
||||
* MergeMap.java
|
||||
*
|
||||
* Created on February 22, 2005, 4:06 PM
|
||||
*/
|
||||
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.modules;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -28,38 +27,43 @@ import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* A map which proxies a collection of sub-maps each of which has a
|
||||
* unique id. Submaps can be added or removed en banc. Values from
|
||||
* removed maps are retained; if push ("someKnownId") happens, the
|
||||
* A map which proxies a collection of sub-maps each of which has a
|
||||
* unique id. Submaps can be added or removed en banc. Values from
|
||||
* removed maps are retained; if push ("someKnownId") happens, the
|
||||
* values previously added to the map while that ID was active reappear.
|
||||
* <p>
|
||||
* This allows us to implement backward/forward semantics for wizards,
|
||||
* in which each pane (identified with a unique ID) can add its own
|
||||
* settings to the settings map, but if the user presses the Back
|
||||
* button, the settings from the formerly active pane can disappear -
|
||||
* settings to the settings map, but if the user presses the Back
|
||||
* button, the settings from the formerly active pane can disappear -
|
||||
* but if the user moves forward again, they are not lost.
|
||||
* <p>
|
||||
* Calling remove("someKeyBelongingToAnEarlierId") will completely
|
||||
* remove that value; calling put ("someKeyBelongingToAnEarlierId", "newValue")
|
||||
* remove that value; calling put ("someKeyBelongingToAnEarlierId", "newValue")
|
||||
* replaces the earler value permanently.
|
||||
* <p>
|
||||
* <b><i><font color="red">This class is NOT AN API CLASS. There is no
|
||||
* <b><i><font color="red">This class is NOT AN API CLASS. There is no
|
||||
* commitment that it will remain backward compatible or even exist in the
|
||||
* future. The API of this library is in the packages <code>org.netbeans.api.wizard</code>
|
||||
* and <code>org.netbeans.spi.wizard</code></font></i></b>.
|
||||
* future. The API of this library is in the packages
|
||||
* <code>org.netbeans.api.wizard</code>
|
||||
* and <code>org.netbeans.spi.wizard</code></font></i></b>.
|
||||
*
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
public class MergeMap implements Map {
|
||||
private Stack order = new Stack();
|
||||
private Map id2map = new HashMap();
|
||||
|
||||
/** Creates a new instance of MergeMap */
|
||||
|
||||
private final Stack order = new Stack();
|
||||
private final Map id2map = new HashMap();
|
||||
|
||||
/**
|
||||
* Creates a new instance of MergeMap
|
||||
*/
|
||||
public MergeMap(String currID) {
|
||||
push (currID);
|
||||
push(currID);
|
||||
}
|
||||
|
||||
private static final String BASE = "__BASE"; //NOI18N
|
||||
|
||||
private static final String BASE = "__BASE";
|
||||
|
||||
/**
|
||||
* Creates a MergeMap with a set of key/value pairs that are
|
||||
* always there (they came from a legacy wizard - used for bridging the
|
||||
@@ -69,69 +73,64 @@ public class MergeMap implements Map {
|
||||
*/
|
||||
public MergeMap(String currId, Map everpresent) {
|
||||
order.push(BASE);
|
||||
id2map.put (BASE, everpresent);
|
||||
push (currId);
|
||||
id2map.put(BASE, everpresent);
|
||||
push(currId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move to a different ID (meaning add a new named map to proxy which can be
|
||||
* calved off if necessary).
|
||||
*/
|
||||
public Map push (String id) {
|
||||
// assert !order.contains(id) : id + " already present"; //NOI18N
|
||||
if (order.contains(id)) {
|
||||
throw new RuntimeException (id + " already present"); //NOI18N
|
||||
}
|
||||
// assert !order.contains(id) : id + " already present"; //NOI18N
|
||||
if (!order.isEmpty() && id.equals(order.peek())) {
|
||||
public Map push(String id) {
|
||||
// assert !order.contains(id) : id + " already present";
|
||||
if (order.contains(id))
|
||||
throw new RuntimeException(id + " already present");
|
||||
// assert !order.contains(id) : id + " already present";
|
||||
if (!order.isEmpty() && id.equals(order.peek()))
|
||||
return (Map) id2map.get(id);
|
||||
}
|
||||
Map result = (Map) id2map.get(id);
|
||||
if (result == null) {
|
||||
result = new HashMap();
|
||||
id2map.put (id, result);
|
||||
id2map.put(id, result);
|
||||
}
|
||||
order.push (id);
|
||||
order.push(id);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the ID of the current sub-map being written into.
|
||||
*/
|
||||
public String currID() {
|
||||
return (String) order.peek();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the current sub-map. Removes all of its settings from the
|
||||
* Remove the current sub-map. Removes all of its settings from the
|
||||
* MergedMap, but if push() is called with the returned value, the
|
||||
* values associated with the ID being removed will be restored.
|
||||
*/
|
||||
public String popAndCalve() {
|
||||
if (order.size() == 0) {
|
||||
throw new NoSuchElementException ("Cannot back out past first " + //NOI18N
|
||||
"entry"); //NOI18N
|
||||
}
|
||||
if (order.isEmpty())
|
||||
throw new NoSuchElementException("Cannot back out past first "
|
||||
+ "entry");
|
||||
//Get the current map
|
||||
String result = (String) order.peek();
|
||||
Map curr = (Map) id2map.get (result);
|
||||
Map curr = (Map) id2map.get(result);
|
||||
order.pop();
|
||||
|
||||
|
||||
//Though unlikely, it is possible that a later step in a wizard
|
||||
//overwrote a key/value pair from a previous step of the wizard.
|
||||
//We do not want to revert that write, so iterate all the keys
|
||||
//we're removing, and if any of them are in steps lower on the
|
||||
//we're removing, and if any of them are in steps lower on the
|
||||
//stack, change those lower steps values to whatever was written
|
||||
//into the map we're calving off
|
||||
|
||||
Set keysForCurr = curr.keySet();
|
||||
for (Iterator i=orderIterator(); i.hasNext();) {
|
||||
for (Iterator i = orderIterator(); i.hasNext();) {
|
||||
Map other = (Map) id2map.get(i.next());
|
||||
for (Iterator j=curr.keySet().iterator(); j.hasNext();) {
|
||||
for (Iterator j = curr.keySet().iterator(); j.hasNext();) {
|
||||
Object key = j.next();
|
||||
if (other.containsKey(key)) {
|
||||
other.put (key, curr.get(key));
|
||||
}
|
||||
if (other.containsKey(key))
|
||||
other.put(key, curr.get(key));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -144,9 +143,8 @@ public class MergeMap implements Map {
|
||||
public boolean containsKey(Object obj) {
|
||||
for (Iterator i = orderIterator(); i.hasNext();) {
|
||||
Map curr = (Map) id2map.get(i.next());
|
||||
if (curr.containsKey(obj)) {
|
||||
if (curr.containsKey(obj))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -154,9 +152,8 @@ public class MergeMap implements Map {
|
||||
public boolean containsValue(Object obj) {
|
||||
for (Iterator i = orderIterator(); i.hasNext();) {
|
||||
Map curr = (Map) id2map.get(i.next());
|
||||
if (curr.containsValue(obj)) {
|
||||
if (curr.containsValue(obj))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -165,7 +162,7 @@ public class MergeMap implements Map {
|
||||
HashSet result = new HashSet();
|
||||
for (Iterator i = orderIterator(); i.hasNext();) {
|
||||
Map curr = (Map) id2map.get(i.next());
|
||||
result.addAll (curr.entrySet());
|
||||
result.addAll(curr.entrySet());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -175,9 +172,8 @@ public class MergeMap implements Map {
|
||||
String id = (String) i.next();
|
||||
Map curr = (Map) id2map.get(id);
|
||||
Object result = curr.get(obj);
|
||||
if (result != null) {
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -190,42 +186,39 @@ public class MergeMap implements Map {
|
||||
HashSet result = new HashSet();
|
||||
for (Iterator i = orderIterator(); i.hasNext();) {
|
||||
Map curr = (Map) id2map.get(i.next());
|
||||
result.addAll (curr.keySet());
|
||||
result.addAll(curr.keySet());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Object put(Object obj, Object obj1) {
|
||||
Map curr = (Map) id2map.get (order.peek());
|
||||
return curr.put (obj, obj1);
|
||||
Map curr = (Map) id2map.get(order.peek());
|
||||
return curr.put(obj, obj1);
|
||||
}
|
||||
|
||||
public void putAll(Map map) {
|
||||
Map curr = (Map) id2map.get (order.peek());
|
||||
curr.putAll (map);
|
||||
Map curr = (Map) id2map.get(order.peek());
|
||||
curr.putAll(map);
|
||||
}
|
||||
|
||||
private Object doRemove(Object obj) {
|
||||
Map curr = (Map) id2map.get (order.peek());
|
||||
Object result = curr.remove (obj);
|
||||
if (result == null) {
|
||||
Map curr = (Map) id2map.get(order.peek());
|
||||
Object result = curr.remove(obj);
|
||||
if (result == null)
|
||||
for (Iterator i = orderIterator(); i.hasNext();) {
|
||||
curr = (Map) id2map.get(i.next());
|
||||
result = curr.remove (obj);
|
||||
if (result != null) {
|
||||
result = curr.remove(obj);
|
||||
if (result != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public Object remove(Object obj) {
|
||||
//Ensure we remove any duplicates in upper arrays
|
||||
Object result = get(obj);
|
||||
while (get(obj) != null) {
|
||||
doRemove (obj);
|
||||
}
|
||||
while (get(obj) != null)
|
||||
doRemove(obj);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -237,54 +230,40 @@ public class MergeMap implements Map {
|
||||
public Collection values() {
|
||||
HashSet result = new HashSet();
|
||||
Set keys = keySet();
|
||||
for (Iterator i = keys.iterator(); i.hasNext();) {
|
||||
result.add (get(i.next()));
|
||||
}
|
||||
for (Iterator i = keys.iterator(); i.hasNext();)
|
||||
result.add(get(i.next()));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private Iterator orderIterator() {
|
||||
return new ReverseIterator(order);
|
||||
}
|
||||
|
||||
|
||||
private static final class ReverseIterator implements Iterator {
|
||||
|
||||
private int pos;
|
||||
private List l;
|
||||
public ReverseIterator (Stack s) {
|
||||
pos = s.size()-1;
|
||||
private final List l;
|
||||
|
||||
public ReverseIterator(Stack s) {
|
||||
pos = s.size() - 1;
|
||||
l = new ArrayList(s);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasNext() {
|
||||
return pos != -1;
|
||||
}
|
||||
|
||||
|
||||
public Object next() {
|
||||
if (pos < 0) {
|
||||
if (pos < 0)
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Object result = l.get(pos);
|
||||
pos--;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (Iterator i = keySet().iterator(); i.hasNext();) {
|
||||
Object key = (Object) i.next();
|
||||
sb.append ('[');
|
||||
sb.append (key);
|
||||
sb.append('=');
|
||||
sb.append(get(key));
|
||||
sb.append(']');
|
||||
if (i.hasNext()) sb.append (',');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+73
-96
@@ -9,12 +9,11 @@ If applicable, add the following below the CDDL Header, with the fields
|
||||
enclosed by brackets [] replaced by your own identifying information:
|
||||
"Portions Copyrighted [year] [name of copyright owner]" */
|
||||
|
||||
/*
|
||||
/*
|
||||
* BranchingWizard.java
|
||||
*
|
||||
* Created on March 4, 2005, 10:56 PM
|
||||
*/
|
||||
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -25,7 +24,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A Wizard with indeterminate branches. The actual branch decision-making
|
||||
* A Wizard with indeterminate branches. The actual branch decision-making
|
||||
* is done by the WizardBranchController passed to the constructor.
|
||||
* <p/>
|
||||
* Wizards with arbitrary numbers of branches can be handled by a
|
||||
@@ -38,8 +37,9 @@ import java.util.Map;
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
final class BranchingWizard implements WizardImplementation {
|
||||
private final List listenerList = Collections.synchronizedList (
|
||||
new LinkedList());
|
||||
|
||||
private final List listenerList = Collections.synchronizedList(
|
||||
new LinkedList());
|
||||
|
||||
private final WizardBranchController brancher;
|
||||
final WizardImplementation initialSteps;
|
||||
@@ -63,70 +63,63 @@ final class BranchingWizard implements WizardImplementation {
|
||||
}
|
||||
|
||||
private void checkForSecondary() {
|
||||
if (wizardData == null) {
|
||||
if (wizardData == null)
|
||||
return;
|
||||
}
|
||||
|
||||
WizardImplementation newSecondary = createSecondary(wizardData);
|
||||
|
||||
/*
|
||||
/*
|
||||
* johnflournoy 7/20/07
|
||||
* check for secondary should be adding the secondary to the activeWizard
|
||||
* not the initial wizard. Adding it to the initial wizard was breaking
|
||||
* multiple branching - to accomplish this created a new method:
|
||||
* setSecondary()
|
||||
*/
|
||||
if (activeWizard instanceof BranchingWizard) {
|
||||
if (activeWizard instanceof BranchingWizard)
|
||||
((BranchingWizard) activeWizard).setSecondary(newSecondary);
|
||||
} else {
|
||||
this.setSecondary(newSecondary);
|
||||
}
|
||||
else
|
||||
this.setSecondary(newSecondary);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the secondary for this <code>BranchingWizard</code>.
|
||||
* Set the secondary for this <code>BranchingWizard</code>.
|
||||
*
|
||||
* @param newSecondary is a WizardImplementation.
|
||||
*/
|
||||
private void setSecondary(WizardImplementation newSecondary) {
|
||||
/* johnflournoy added additional condition: secondary != this */
|
||||
if ((((subsequentSteps == null) != (newSecondary == null))
|
||||
|| (subsequentSteps != null && !subsequentSteps.equals(newSecondary)))
|
||||
&& !this.equals(newSecondary)) {
|
||||
|
||||
/*
|
||||
* johnflournoy: only set the subsequent steps if it
|
||||
if ((((subsequentSteps == null) != (newSecondary == null))
|
||||
|| (subsequentSteps != null && !subsequentSteps.equals(newSecondary)))
|
||||
&& !this.equals(newSecondary))
|
||||
|
||||
/*
|
||||
* johnflournoy: only set the subsequent steps if it
|
||||
* this wizard owns the current step.
|
||||
*/
|
||||
if (Arrays.asList(initialSteps.getAllSteps()).contains(currStep)) {
|
||||
*/
|
||||
if (Arrays.asList(initialSteps.getAllSteps()).contains(currStep)) {
|
||||
subsequentSteps = newSecondary;
|
||||
fireStepsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getForwardNavigationMode() {
|
||||
return activeWizard.getForwardNavigationMode();
|
||||
}
|
||||
|
||||
private void setCurrent(WizardImplementation wizard) {
|
||||
if (activeWizard == wizard) {
|
||||
if (activeWizard == wizard)
|
||||
return;
|
||||
}
|
||||
|
||||
if (wizard == null) {
|
||||
if (wizard == null)
|
||||
throw new NullPointerException("Can't set current wizard to null");
|
||||
}
|
||||
|
||||
if ((activeWizard != null) && (wl != null)) {
|
||||
if ((activeWizard != null) && (wl != null))
|
||||
activeWizard.removeWizardObserver(wl);
|
||||
}
|
||||
|
||||
activeWizard = wizard;
|
||||
|
||||
if (wl == null) {
|
||||
if (wl == null)
|
||||
wl = new WL();
|
||||
}
|
||||
|
||||
activeWizard.addWizardObserver(wl);
|
||||
}
|
||||
@@ -140,17 +133,15 @@ final class BranchingWizard implements WizardImplementation {
|
||||
Object result = activeWizard.finish(settings);
|
||||
initialSteps.removeWizardObserver(wl);
|
||||
//Can be null, we allow bail-out with finish mid-wizard now
|
||||
if (subsequentSteps != null) {
|
||||
if (subsequentSteps != null)
|
||||
subsequentSteps.removeWizardObserver(wl);
|
||||
}
|
||||
return result;
|
||||
} catch (WizardException we) {
|
||||
if (we.getStepToReturnTo() != null) {
|
||||
initialSteps.addWizardObserver(wl);
|
||||
//Can be null, we allow bail-out with finish mid-wizard now
|
||||
if (subsequentSteps != null) {
|
||||
if (subsequentSteps != null)
|
||||
subsequentSteps.addWizardObserver(wl);
|
||||
}
|
||||
}
|
||||
throw we;
|
||||
}
|
||||
@@ -179,46 +170,40 @@ final class BranchingWizard implements WizardImplementation {
|
||||
|
||||
public final String getNextStep() {
|
||||
String result;
|
||||
if (currStep == null) {
|
||||
if (currStep == null)
|
||||
result = getAllSteps()[0];
|
||||
} else {
|
||||
else {
|
||||
String[] steps = getAllSteps();
|
||||
int idx = Arrays.asList(steps).indexOf(currStep);
|
||||
if (idx == -1) {
|
||||
throw new IllegalStateException("Current step not in" + //NOI18N
|
||||
" available steps: " + currStep + " not in " + //NOI18N
|
||||
Arrays.asList(steps));
|
||||
} else {
|
||||
if (idx == steps.length - 1) {
|
||||
if (subsequentSteps == null) {
|
||||
if (idx == -1)
|
||||
throw new IllegalStateException("Current step not in"
|
||||
+ " available steps: " + currStep + " not in "
|
||||
+ Arrays.asList(steps));
|
||||
else if (idx == steps.length - 1)
|
||||
if (subsequentSteps == null)
|
||||
result = UNDETERMINED_STEP;
|
||||
else
|
||||
result = subsequentSteps.getNextStep();
|
||||
else {
|
||||
WizardImplementation w = ownerOf(currStep);
|
||||
if (w == initialSteps && idx == initialSteps.getAllSteps().length - 1) {
|
||||
checkForSecondary();
|
||||
if (subsequentSteps != null)
|
||||
result = subsequentSteps.getAllSteps()[0];
|
||||
else
|
||||
result = UNDETERMINED_STEP;
|
||||
} else {
|
||||
result = subsequentSteps.getNextStep();
|
||||
}
|
||||
} else {
|
||||
WizardImplementation w = ownerOf(currStep);
|
||||
if (w == initialSteps && idx == initialSteps.getAllSteps().length - 1) {
|
||||
checkForSecondary();
|
||||
if (subsequentSteps != null) {
|
||||
result = subsequentSteps.getAllSteps()[0];
|
||||
} else {
|
||||
result = UNDETERMINED_STEP;
|
||||
}
|
||||
} else {
|
||||
result = w.getNextStep();
|
||||
}
|
||||
}
|
||||
} else
|
||||
result = w.getNextStep();
|
||||
}
|
||||
}
|
||||
return getProblem() == null ? result : UNDETERMINED_STEP.equals(result) ? result : null;
|
||||
}
|
||||
|
||||
public final String getPreviousStep() {
|
||||
if (activeWizard == subsequentSteps && subsequentSteps.getAllSteps()[0].equals(currStep)) {
|
||||
if (activeWizard == subsequentSteps && subsequentSteps.getAllSteps()[0].equals(currStep))
|
||||
return initialSteps.getAllSteps()[initialSteps.getAllSteps().length - 1];
|
||||
} else {
|
||||
else
|
||||
return activeWizard.getPreviousStep();
|
||||
}
|
||||
}
|
||||
|
||||
public final String getProblem() {
|
||||
@@ -227,17 +212,15 @@ final class BranchingWizard implements WizardImplementation {
|
||||
|
||||
public final String getStepDescription(String id) {
|
||||
WizardImplementation w = ownerOf(id);
|
||||
if (w == null) {
|
||||
if (w == null)
|
||||
return null;
|
||||
}
|
||||
return w.getStepDescription(id);
|
||||
}
|
||||
|
||||
|
||||
public final String getLongDescription(String id) {
|
||||
WizardImplementation w = ownerOf(id);
|
||||
if (w == null) {
|
||||
if (w == null)
|
||||
return null;
|
||||
}
|
||||
return w.getLongDescription(id);
|
||||
}
|
||||
|
||||
@@ -246,24 +229,22 @@ final class BranchingWizard implements WizardImplementation {
|
||||
checkForSecondary();
|
||||
return subsequentSteps;
|
||||
}
|
||||
if (Arrays.asList(initialSteps.getAllSteps()).contains(id)) {
|
||||
if (Arrays.asList(initialSteps.getAllSteps()).contains(id))
|
||||
return initialSteps;
|
||||
} else {
|
||||
else {
|
||||
/*
|
||||
* johnflournoy
|
||||
* johnflournoy
|
||||
* need to check an existing subsequentsteps to see if
|
||||
* we can find the owner of "id", otherwise we were losing
|
||||
* a wizard if we had multiple branches and we backed up to an
|
||||
* a wizard if we had multiple branches and we backed up to an
|
||||
* earlier wizard and then went down the same path again.
|
||||
*/
|
||||
if (subsequentSteps != null) {
|
||||
if (!Arrays.asList(subsequentSteps.getAllSteps()).contains(id)) {
|
||||
if (!Arrays.asList(subsequentSteps.getAllSteps()).contains(id))
|
||||
checkForSecondary();
|
||||
}
|
||||
} else {
|
||||
} else
|
||||
checkForSecondary();
|
||||
}
|
||||
|
||||
|
||||
return subsequentSteps;
|
||||
}
|
||||
}
|
||||
@@ -273,35 +254,32 @@ final class BranchingWizard implements WizardImplementation {
|
||||
}
|
||||
|
||||
public final JComponent navigatingTo(String id, Map settings) {
|
||||
if (id == null) {
|
||||
if (id == null)
|
||||
throw new NullPointerException();
|
||||
}
|
||||
currStep = id;
|
||||
wizardData = settings;
|
||||
|
||||
WizardImplementation impl = ownerOf (id);
|
||||
if (impl == null) {
|
||||
throw new NullPointerException ("No owning WizardImplementation for" +
|
||||
" id " + id);
|
||||
}
|
||||
WizardImplementation impl = ownerOf(id);
|
||||
if (impl == null)
|
||||
throw new NullPointerException("No owning WizardImplementation for"
|
||||
+ " id " + id);
|
||||
setCurrent(impl);
|
||||
|
||||
return activeWizard.navigatingTo(id, settings);
|
||||
}
|
||||
|
||||
public final void removeWizardObserver (WizardObserver observer) {
|
||||
public final void removeWizardObserver(WizardObserver observer) {
|
||||
listenerList.remove(observer);
|
||||
}
|
||||
|
||||
public final void addWizardObserver (WizardObserver observer) {
|
||||
public final void addWizardObserver(WizardObserver observer) {
|
||||
listenerList.add(observer);
|
||||
}
|
||||
|
||||
private void fireStepsChanged() {
|
||||
WizardObserver[] listeners = (WizardObserver[])
|
||||
listenerList.toArray (new WizardObserver[0]);
|
||||
WizardObserver[] listeners = (WizardObserver[]) listenerList.toArray(new WizardObserver[0]);
|
||||
|
||||
for (int i = listeners.length - 1; i >= 0; i --) {
|
||||
for (int i = listeners.length - 1; i >= 0; i--) {
|
||||
WizardObserver l = (WizardObserver) listeners[i];
|
||||
l.stepsChanged(null);
|
||||
}
|
||||
@@ -310,20 +288,18 @@ final class BranchingWizard implements WizardImplementation {
|
||||
private void fireNavigabilityChanged() {
|
||||
checkForSecondary();
|
||||
|
||||
WizardObserver[] listeners = (WizardObserver[])
|
||||
listenerList.toArray (new WizardObserver[0]);
|
||||
WizardObserver[] listeners = (WizardObserver[]) listenerList.toArray(new WizardObserver[0]);
|
||||
|
||||
for (int i = listeners.length - 1; i >= 0; i --) {
|
||||
for (int i = listeners.length - 1; i >= 0; i--) {
|
||||
WizardObserver l = (WizardObserver) listeners[i];
|
||||
l.navigabilityChanged(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void fireSelectionChanged() {
|
||||
WizardObserver[] listeners = (WizardObserver[])
|
||||
listenerList.toArray (new WizardObserver[0]);
|
||||
WizardObserver[] listeners = (WizardObserver[]) listenerList.toArray(new WizardObserver[0]);
|
||||
|
||||
for (int i = listeners.length - 1; i >= 0; i --) {
|
||||
for (int i = listeners.length - 1; i >= 0; i--) {
|
||||
WizardObserver l = (WizardObserver) listeners[i];
|
||||
l.selectionChanged(null);
|
||||
}
|
||||
@@ -334,6 +310,7 @@ final class BranchingWizard implements WizardImplementation {
|
||||
}
|
||||
|
||||
private class WL implements WizardObserver {
|
||||
|
||||
public void stepsChanged(Wizard wizard) {
|
||||
fireStepsChanged();
|
||||
}
|
||||
|
||||
+43
-68
@@ -9,7 +9,7 @@ If applicable, add the following below the CDDL Header, with the fields
|
||||
enclosed by brackets [] replaced by your own identifying information:
|
||||
"Portions Copyrighted [year] [name of copyright owner]" */
|
||||
|
||||
/*
|
||||
/*
|
||||
* DeferredWizardResult.java
|
||||
*
|
||||
* Created on September 24, 2006, 3:42 AM
|
||||
@@ -17,95 +17,84 @@ enclosed by brackets [] replaced by your own identifying information:
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Object which can be returned from
|
||||
* Object which can be returned from
|
||||
* <code>WizardPage.WizardResultProducer.finish()</code>
|
||||
* or <code>WizardPanelProvider.finish()</code>. A DeferredWizardResult does
|
||||
* not immediately calculate its result; it is used for cases where some
|
||||
* or <code>WizardPanelProvider.finish()</code>. A DeferredWizardResult does
|
||||
* not immediately calculate its result; it is used for cases where some
|
||||
* time consuming work needs to be performed to compute the result (such as
|
||||
* creating files on disk), and a progress bar should be shown until the work
|
||||
* is completed.
|
||||
*
|
||||
* @see org.jackhuang.hellominecraft.utils.views.wizard.spi.ResultProgressHandle
|
||||
*
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
public abstract class DeferredWizardResult {
|
||||
|
||||
private final boolean canAbort;
|
||||
private final boolean useBusy;
|
||||
/**
|
||||
* Creates a new instance of DeferredWizardResult which cannot be
|
||||
|
||||
/**
|
||||
* Creates a new instance of DeferredWizardResult which cannot be
|
||||
* aborted and shows a progress bar.
|
||||
*/
|
||||
public DeferredWizardResult() {
|
||||
useBusy = false;
|
||||
canAbort = false;
|
||||
}
|
||||
|
||||
/** Creates a new instance of DeferredWizardResult which may or may not
|
||||
* be able to be aborted.
|
||||
|
||||
/**
|
||||
* Creates a new instance of DeferredWizardResult which may or may not
|
||||
* be able to be aborted.
|
||||
*
|
||||
* @param canAbort determine if background computation can be aborted by
|
||||
* calling the <code>abort()</code> method
|
||||
* calling the <code>abort()</code> method
|
||||
*/
|
||||
public DeferredWizardResult (boolean canAbort) {
|
||||
public DeferredWizardResult(boolean canAbort) {
|
||||
this.canAbort = canAbort;
|
||||
this.useBusy = false;
|
||||
}
|
||||
|
||||
/** Creates a new instance of DeferredWizardResult which may or may not
|
||||
* be able to be aborted, and which may simply disable the wizard's UI
|
||||
* instead of showing a progress bar while the background work runs.
|
||||
*
|
||||
* @param canAbort
|
||||
* @param useBusy
|
||||
*/
|
||||
public DeferredWizardResult (boolean canAbort, boolean useBusy) {
|
||||
this.canAbort = canAbort;
|
||||
this.useBusy = useBusy;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begin computing the result. This method is called on a background
|
||||
|
||||
/**
|
||||
* Begin computing the result. This method is called on a background
|
||||
* thread, not the AWT event thread, and computation can immediately begin.
|
||||
* Use the progress handle to set progress as the work progresses.
|
||||
*
|
||||
* IMPORTANT: This method MUST call either progress.finished with the result,
|
||||
* or progress.failed with an error message. If this method returns without
|
||||
* Use the progress handle to set progress as the work progresses.
|
||||
*
|
||||
* IMPORTANT: This method MUST call either progress.finished with the
|
||||
* result,
|
||||
* or progress.failed with an error message. If this method returns without
|
||||
* calling either of those methods, it will be assumed to have failed.
|
||||
*
|
||||
*
|
||||
* @param settings The settings gathered over the course of the wizard
|
||||
* @param progress A handle which can be used to affect the progress bar.
|
||||
*/
|
||||
public abstract void start (Map settings, ResultProgressHandle progress);
|
||||
|
||||
public abstract void start(Map settings, ResultProgressHandle progress);
|
||||
|
||||
/**
|
||||
* If true, the background thread can be aborted. If it is possible to
|
||||
* If true, the background thread can be aborted. If it is possible to
|
||||
* abort, then the UI may allow the dialog to be closed while the result
|
||||
* is being computed.
|
||||
*/
|
||||
*/
|
||||
public final boolean canAbort() {
|
||||
return canAbort;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Abort computation of the result. This method will usually be called on
|
||||
* Abort computation of the result. This method will usually be called on
|
||||
* the event thread, after <code>start()<code> has been called, and before
|
||||
* <code>finished()</code> has been called on the <code>ResultProgressHandle</code>
|
||||
* <code>finished()</code> has been called on the
|
||||
* <code>ResultProgressHandle</code>
|
||||
* that is passed to <code>start()</code>, for example, if the user clicks
|
||||
* the close button on the dialog showing the wizard while the result is
|
||||
* being computed.
|
||||
* <p>
|
||||
* <b>This method does <i>nothing</i> by default</b> - it is left empty so
|
||||
* that people who do not want to support aborting background work do not
|
||||
* have to override it. It is up to the implementor
|
||||
* to set a flag or otherwise notify the background thread to halt
|
||||
* computation. A simple method for doing so is as follows:
|
||||
* have to override it. It is up to the implementor
|
||||
* to set a flag or otherwise notify the background thread to halt
|
||||
* computation. A simple method for doing so is as follows:
|
||||
* <pre>
|
||||
* volatile Thread thread;
|
||||
* public void start (Map settings, ResultProgressHandle handle) {
|
||||
@@ -113,8 +102,8 @@ public abstract class DeferredWizardResult {
|
||||
* synchronized (this) {
|
||||
* thread = Thread.currentThread();
|
||||
* }
|
||||
*
|
||||
* //do the background computation, update progress. Every so often,
|
||||
*
|
||||
* //do the background computation, update progress. Every so often,
|
||||
* //check Thread.interrupted() and exit if true
|
||||
* } finally {
|
||||
* synchronized (this) {
|
||||
@@ -122,31 +111,17 @@ public abstract class DeferredWizardResult {
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* public synchronized void abort() {
|
||||
* if (thread != null) thread.interrupt();
|
||||
* }
|
||||
* </pre>
|
||||
* or you can use a <code>volatile boolean</code> flag that you set in
|
||||
* <code>abort()</code> and periodically check in the body of <code>start()</code>.
|
||||
*
|
||||
*/
|
||||
* <code>abort()</code> and periodically check in the body of
|
||||
* <code>start()</code>.
|
||||
*
|
||||
*/
|
||||
public void abort() {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the UI should be completely disabled while the background
|
||||
* work is running (i.e. you do not want a progress bar, you just want all
|
||||
* navigation disabled [note on some window managers, the user will still
|
||||
* be able to click the dialog's window drag-bar close button, so you still
|
||||
* should override abort() to stop computation if possible]).
|
||||
*
|
||||
* @return true if no progress bar should be displayed and the UI should
|
||||
* just disable itself
|
||||
*/
|
||||
public final boolean isUseBusy()
|
||||
{
|
||||
return useBusy;
|
||||
}
|
||||
}
|
||||
+96
-160
@@ -9,12 +9,11 @@ If applicable, add the following below the CDDL Header, with the fields
|
||||
enclosed by brackets [] replaced by your own identifying information:
|
||||
"Portions Copyrighted [year] [name of copyright owner]" */
|
||||
|
||||
/*
|
||||
/*
|
||||
* GenericListener.java
|
||||
*
|
||||
* Created on October 5, 2004, 12:36 AM
|
||||
*/
|
||||
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import java.awt.Component;
|
||||
@@ -30,8 +29,6 @@ import java.util.EventObject;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
import javax.swing.tree.*;
|
||||
@@ -56,17 +53,15 @@ import javax.swing.tree.TreeSelectionModel;
|
||||
* validate its contents.
|
||||
*
|
||||
* If you use subclasses of the swing components, you will also need to subclass
|
||||
* this listener and override at least the methods isProbablyContainer, attachTo and detachFrom.
|
||||
* this listener and override at least the methods isProbablyContainer, attachTo
|
||||
* and detachFrom.
|
||||
*
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
final class GenericListener
|
||||
implements ActionListener, PropertyChangeListener, ItemListener,
|
||||
ContainerListener, DocumentListener, ChangeListener,
|
||||
ListSelectionListener, TreeSelectionListener, TableModelListener {
|
||||
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(GenericListener.class.getName());
|
||||
implements ActionListener, PropertyChangeListener, ItemListener,
|
||||
ContainerListener, DocumentListener, ChangeListener,
|
||||
ListSelectionListener, TreeSelectionListener, TableModelListener {
|
||||
|
||||
private final WizardPage wizardPage;
|
||||
|
||||
@@ -76,218 +71,191 @@ final class GenericListener
|
||||
* Set of components that we're listening to models of, so we can look
|
||||
* up the component from the model as needed
|
||||
*/
|
||||
private Set listenedTo = new HashSet();
|
||||
private final Set listenedTo = new HashSet();
|
||||
|
||||
private final WizardPage.CustomComponentListener extListener;
|
||||
private final WizardPage.CustomComponentNotifier extNotifier;
|
||||
|
||||
public GenericListener(WizardPage wizardPage, WizardPage.CustomComponentListener l,
|
||||
WizardPage.CustomComponentNotifier n) {
|
||||
WizardPage.CustomComponentNotifier n) {
|
||||
this.extListener = l;
|
||||
this.extNotifier = n;
|
||||
if ((extListener == null) != (extNotifier == null)) {
|
||||
if ((extListener == null) != (extNotifier == null))
|
||||
throw new RuntimeException();
|
||||
}
|
||||
// assert wizardPage != null : "WizardPage may not be null"; // NOI18N
|
||||
if (wizardPage == null) {
|
||||
throw new IllegalArgumentException("WizardPage may not be null"); // NOI18N)
|
||||
}
|
||||
// assert wizardPage != null : "WizardPage may not be null";
|
||||
if (wizardPage == null)
|
||||
throw new IllegalArgumentException("WizardPage may not be null");
|
||||
this.wizardPage = wizardPage;
|
||||
wizardPage.addContainerListener(this);
|
||||
}
|
||||
|
||||
public GenericListener (WizardPage page) {
|
||||
this (page, null, null);
|
||||
|
||||
public GenericListener(WizardPage page) {
|
||||
this(page, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given component is likely to be a container such the each
|
||||
* Return true if the given component is likely to be a container such the
|
||||
* each
|
||||
* component within the container should be be considered as a user input.
|
||||
*
|
||||
*
|
||||
* @param c
|
||||
*
|
||||
* @return true if the component children should have this listener added.
|
||||
*/
|
||||
protected boolean isProbablyAContainer (Component c) {
|
||||
protected boolean isProbablyAContainer(Component c) {
|
||||
boolean result = extListener != null ? extListener.isContainer(c) : false;
|
||||
if (!result) {
|
||||
boolean isSwing = isSwingClass(c);
|
||||
if (isSwing) {
|
||||
result = c instanceof JPanel || c instanceof JSplitPane || c instanceof
|
||||
JToolBar || c instanceof JViewport || c instanceof JScrollPane ||
|
||||
c instanceof JFrame || c instanceof JRootPane || c instanceof
|
||||
Window || c instanceof Frame || c instanceof Dialog ||
|
||||
c instanceof JTabbedPane || c instanceof JInternalFrame ||
|
||||
c instanceof JDesktopPane || c instanceof JLayeredPane ||
|
||||
c instanceof Box;
|
||||
} else {
|
||||
if (isSwing)
|
||||
result = c instanceof JPanel || c instanceof JSplitPane || c instanceof JToolBar || c instanceof JViewport || c instanceof JScrollPane
|
||||
|| c instanceof JFrame || c instanceof JRootPane || c instanceof Window || c instanceof Frame || c instanceof Dialog
|
||||
|| c instanceof JTabbedPane || c instanceof JInternalFrame
|
||||
|| c instanceof JDesktopPane || c instanceof JLayeredPane
|
||||
|| c instanceof Box;
|
||||
else
|
||||
result = c instanceof Container;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the given component is likely to be a swing primitive or a subclass.
|
||||
* The default implmentation here just checks for the package of the component to be "javax.swing"
|
||||
* If you use subclasses of swing components, you will need to override this method
|
||||
* Return true if the given component is likely to be a swing primitive or a
|
||||
* subclass.
|
||||
* The default implmentation here just checks for the package of the
|
||||
* component to be "javax.swing"
|
||||
* If you use subclasses of swing components, you will need to override this
|
||||
* method
|
||||
* to get proper behavior.
|
||||
*
|
||||
* @param c
|
||||
* @return true if the component should be examined more closely (see isProbablyAContainer)
|
||||
*
|
||||
* @return true if the component should be examined more closely (see
|
||||
* isProbablyAContainer)
|
||||
*/
|
||||
protected boolean isSwingClass (Component c)
|
||||
{
|
||||
protected boolean isSwingClass(Component c) {
|
||||
String packageName = c.getClass().getPackage().getName();
|
||||
boolean swing = packageName.equals ("javax.swing"); //NOI18N
|
||||
boolean swing = packageName.equals("javax.swing");
|
||||
return swing;
|
||||
}
|
||||
|
||||
protected void attachTo(Component jc) {
|
||||
if (extListener != null && extListener.accept (jc)) {
|
||||
if (extListener != null && extListener.accept(jc)) {
|
||||
extListener.startListeningTo(jc, extNotifier);
|
||||
listenedTo.add (jc);
|
||||
if (wizardPage.getMapKeyFor(jc) != null) {
|
||||
listenedTo.add(jc);
|
||||
if (wizardPage.getMapKeyFor(jc) != null)
|
||||
wizardPage.maybeUpdateMap(jc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isProbablyAContainer(jc)) {
|
||||
if (isProbablyAContainer(jc))
|
||||
attachToHierarchyOf((Container) jc);
|
||||
} else if (jc instanceof JList) {
|
||||
else if (jc instanceof JList) {
|
||||
listenedTo.add(jc);
|
||||
((JList) jc).addListSelectionListener(this);
|
||||
} else if (jc instanceof JComboBox) {
|
||||
} else if (jc instanceof JComboBox)
|
||||
((JComboBox) jc).addActionListener(this);
|
||||
} else if (jc instanceof JTree) {
|
||||
else if (jc instanceof JTree) {
|
||||
listenedTo.add(jc);
|
||||
((JTree) jc).getSelectionModel().addTreeSelectionListener(this);
|
||||
} else if (jc instanceof JToggleButton) {
|
||||
} else if (jc instanceof JToggleButton)
|
||||
((AbstractButton) jc).addItemListener(this);
|
||||
} else if (jc instanceof JFormattedTextField) { //JFormattedTextField must be tested before JTextCompoent
|
||||
else if (jc instanceof JFormattedTextField) //JFormattedTextField must be tested before JTextCompoent
|
||||
jc.addPropertyChangeListener("value", this);
|
||||
} else if (jc instanceof JTextComponent) {
|
||||
else if (jc instanceof JTextComponent) {
|
||||
listenedTo.add(jc);
|
||||
((JTextComponent) jc).getDocument().addDocumentListener(this);
|
||||
} else if (jc instanceof JColorChooser) {
|
||||
listenedTo.add(jc);
|
||||
((JColorChooser) jc).getSelectionModel().addChangeListener(this);
|
||||
} else if (jc instanceof JSpinner) {
|
||||
} else if (jc instanceof JSpinner)
|
||||
((JSpinner) jc).addChangeListener(this);
|
||||
} else if (jc instanceof JSlider) {
|
||||
else if (jc instanceof JSlider)
|
||||
((JSlider) jc).addChangeListener(this);
|
||||
} else if (jc instanceof JTable) {
|
||||
else if (jc instanceof JTable) {
|
||||
listenedTo.add(jc);
|
||||
((JTable) jc).getSelectionModel().addListSelectionListener(this);
|
||||
} else {
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Don't know how to listen to a " + // NOI18N
|
||||
jc.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (accept(jc) && !(jc instanceof JPanel)) {
|
||||
jc.addPropertyChangeListener("name", this);
|
||||
if (wizardPage.getMapKeyFor(jc) != null) {
|
||||
if (wizardPage.getMapKeyFor(jc) != null)
|
||||
wizardPage.maybeUpdateMap(jc);
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isLoggable(Level.FINE) && accept(jc)) {
|
||||
logger.fine("Begin listening to " + jc); // NOI18N
|
||||
}
|
||||
}
|
||||
|
||||
protected void detachFrom(Component jc) {
|
||||
listenedTo.remove(jc);
|
||||
if (extListener != null && extListener.accept (jc)) {
|
||||
if (extListener != null && extListener.accept(jc))
|
||||
extListener.stopListeningTo(jc);
|
||||
}
|
||||
if (isProbablyAContainer(jc)) {
|
||||
if (isProbablyAContainer(jc))
|
||||
detachFromHierarchyOf((Container) jc);
|
||||
} else if (jc instanceof JList) {
|
||||
else if (jc instanceof JList)
|
||||
((JList) jc).removeListSelectionListener(this);
|
||||
} else if (jc instanceof JComboBox) {
|
||||
else if (jc instanceof JComboBox)
|
||||
((JComboBox) jc).removeActionListener(this);
|
||||
} else if (jc instanceof JTree) {
|
||||
else if (jc instanceof JTree)
|
||||
((JTree) jc).getSelectionModel().removeTreeSelectionListener(this);
|
||||
} else if (jc instanceof JToggleButton) {
|
||||
else if (jc instanceof JToggleButton)
|
||||
((AbstractButton) jc).removeActionListener(this);
|
||||
} else if (jc instanceof JTextComponent) {
|
||||
else if (jc instanceof JTextComponent) {
|
||||
} else if (jc instanceof JFormattedTextField) { //JFormattedTextField must be tested before JTextCompoent
|
||||
jc.removePropertyChangeListener("value", this);
|
||||
((JTextComponent) jc).getDocument().removeDocumentListener(this);
|
||||
} else if (jc instanceof JColorChooser) {
|
||||
} else if (jc instanceof JColorChooser)
|
||||
((JColorChooser) jc).getSelectionModel().removeChangeListener(this);
|
||||
} else if (jc instanceof JSpinner) {
|
||||
else if (jc instanceof JSpinner)
|
||||
((JSpinner) jc).removeChangeListener(this);
|
||||
} else if (jc instanceof JSlider) {
|
||||
else if (jc instanceof JSlider)
|
||||
((JSlider) jc).removeChangeListener(this);
|
||||
} else if (jc instanceof JTable) {
|
||||
else if (jc instanceof JTable)
|
||||
((JTable) jc).getSelectionModel().removeListSelectionListener(this);
|
||||
}
|
||||
|
||||
if (accept(jc) && !(jc instanceof JPanel)) {
|
||||
jc.removePropertyChangeListener("name", this);
|
||||
Object key = wizardPage.getMapKeyFor(jc);
|
||||
|
||||
if (key != null) {
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Named component removed from hierarchy: " + // NOI18N
|
||||
key + ". Removing any corresponding " + // NOI18N
|
||||
"value from the wizard settings map."); // NOI18N
|
||||
}
|
||||
|
||||
if (key != null)
|
||||
wizardPage.removeFromMap(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isLoggable(Level.FINE) && accept(jc)) {
|
||||
logger.fine("Stop listening to " + jc); // NOI18N
|
||||
}
|
||||
}
|
||||
|
||||
private void detachFromHierarchyOf(Container container) {
|
||||
container.removeContainerListener(this);
|
||||
Component[] components = container.getComponents();
|
||||
for (int i = 0; i < components.length; i++) {
|
||||
detachFrom(components[i]); // Will callback recursively any nested JPanels
|
||||
}
|
||||
for (Component component : components)
|
||||
detachFrom(component); // Will callback recursively any nested JPanels
|
||||
}
|
||||
|
||||
void attachToHierarchyOf(Container container) {
|
||||
if (!Arrays.asList (container.getContainerListeners()).contains(this)) {
|
||||
if (!Arrays.asList(container.getContainerListeners()).contains(this))
|
||||
container.addContainerListener(this);
|
||||
}
|
||||
Component[] components = container.getComponents();
|
||||
for (int i = 0; i < components.length; i++) {
|
||||
attachTo(components[i]); // Will recursively add any child components in
|
||||
}
|
||||
for (Component component : components)
|
||||
attachTo(component); // Will recursively add any child components in
|
||||
}
|
||||
|
||||
protected boolean accept(Component jc) {
|
||||
if (extListener != null && extListener.accept(jc)) {
|
||||
if (extListener != null && extListener.accept(jc))
|
||||
return true;
|
||||
}
|
||||
if (!(jc instanceof JComponent)) {
|
||||
if (!(jc instanceof JComponent))
|
||||
return false;
|
||||
}
|
||||
if (jc instanceof TableCellEditor || jc instanceof TreeCellEditor ||
|
||||
SwingUtilities.getAncestorOfClass(JTable.class, jc) != null ||
|
||||
SwingUtilities.getAncestorOfClass(JTree.class, jc) != null ||
|
||||
SwingUtilities.getAncestorOfClass(JList.class, jc) != null){
|
||||
if (jc instanceof TableCellEditor || jc instanceof TreeCellEditor
|
||||
|| SwingUtilities.getAncestorOfClass(JTable.class, jc) != null
|
||||
|| SwingUtilities.getAncestorOfClass(JTree.class, jc) != null
|
||||
|| SwingUtilities.getAncestorOfClass(JList.class, jc) != null)
|
||||
//Don't listen to cell editors, we can end up listening to them
|
||||
//multiple times, and the tree/table model will give us the event
|
||||
//we need
|
||||
return false;
|
||||
}
|
||||
return isProbablyAContainer (jc) ||
|
||||
jc instanceof JList ||
|
||||
jc instanceof JComboBox ||
|
||||
jc instanceof JTree ||
|
||||
jc instanceof JToggleButton || //covers toggle, radio, checkbox
|
||||
jc instanceof JTextComponent ||
|
||||
jc instanceof JColorChooser ||
|
||||
jc instanceof JSpinner ||
|
||||
jc instanceof JSlider;
|
||||
return isProbablyAContainer(jc)
|
||||
|| jc instanceof JList
|
||||
|| jc instanceof JComboBox
|
||||
|| jc instanceof JTree
|
||||
|| jc instanceof JToggleButton
|
||||
|| //covers toggle, radio, checkbox
|
||||
jc instanceof JTextComponent
|
||||
|| jc instanceof JColorChooser
|
||||
|| jc instanceof JSpinner
|
||||
|| jc instanceof JSlider;
|
||||
}
|
||||
|
||||
void setIgnoreEvents(boolean val) {
|
||||
@@ -299,59 +267,40 @@ final class GenericListener
|
||||
setIgnoreEvents(true);
|
||||
try {
|
||||
//XXX this could be prettier...
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Event received: " + e); // NOI18N
|
||||
}
|
||||
if (e instanceof EventObject && ((EventObject) e).getSource() instanceof Component) {
|
||||
if (e instanceof EventObject && ((EventObject) e).getSource() instanceof Component)
|
||||
wizardPage.userInputReceived((Component) ((EventObject) e).getSource(), e);
|
||||
} else if (e instanceof TreeSelectionEvent) {
|
||||
logger.fine("Looking for a tree for a tree selection event"); // NOI18N
|
||||
else if (e instanceof TreeSelectionEvent) {
|
||||
TreeSelectionModel mdl = (TreeSelectionModel) ((TreeSelectionEvent) e).getSource();
|
||||
for (Iterator i = listenedTo.iterator(); i.hasNext();) {
|
||||
Object o = i.next();
|
||||
if (o instanceof JTree && ((JTree) o).getSelectionModel() == mdl) {
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine(" found it: " + o); // NOI18N
|
||||
}
|
||||
wizardPage.userInputReceived((Component) o, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (e instanceof DocumentEvent) {
|
||||
logger.fine("Looking for a JTextComponent for a DocumentEvent"); // NOI18N
|
||||
Document document = ((DocumentEvent) e).getDocument();
|
||||
for (Iterator i = listenedTo.iterator(); i.hasNext();) {
|
||||
Object o = i.next();
|
||||
if (o instanceof JTextComponent && ((JTextComponent) o).getDocument() == document) {
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine(" found it: " + o); // NOI18N
|
||||
}
|
||||
wizardPage.userInputReceived((Component) o, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (e instanceof ListSelectionEvent) {
|
||||
logger.fine("Looking for a JList or JTable for a ListSelectionEvent"); // NOI18N
|
||||
ListSelectionModel model = (ListSelectionModel) ((ListSelectionEvent) e).getSource();
|
||||
for (Iterator i = listenedTo.iterator(); i.hasNext();) {
|
||||
Object o = i.next();
|
||||
if (o instanceof JList && ((JList) o).getSelectionModel() == model) {
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine(" found it: " + o); // NOI18N
|
||||
}
|
||||
wizardPage.userInputReceived((Component) o, e);
|
||||
break;
|
||||
} else if (o instanceof JTable && ((JTable) o).getSelectionModel() == model) {
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine(" found it: " + o); // NOI18N
|
||||
}
|
||||
wizardPage.userInputReceived((Component) o, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else
|
||||
wizardPage.userInputReceived(null, e);
|
||||
}
|
||||
} finally {
|
||||
setIgnoreEvents(false);
|
||||
}
|
||||
@@ -366,19 +315,8 @@ final class GenericListener
|
||||
if (e.getSource() instanceof JComponent && "name".equals(e.getPropertyName())) {
|
||||
// Note - most components do NOT fire a property change on
|
||||
// setName(), but it is possible for this to be done intentionally
|
||||
if (e.getOldValue() instanceof String) {
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Name of component changed from " + e.getOldValue() + // NOI18N
|
||||
" to " + e.getNewValue() + ". Removing any values for " + // NOI18N
|
||||
e.getOldValue() + " from the wizard data map"); // NOI18N
|
||||
}
|
||||
if (e.getOldValue() instanceof String)
|
||||
wizardPage.removeFromMap(e.getOldValue());
|
||||
}
|
||||
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Possibly update map for renamed component " + // NOI18N
|
||||
e.getSource());
|
||||
}
|
||||
|
||||
} else if (e.getSource() instanceof JFormattedTextField && "value".equals(e.getPropertyName())) {
|
||||
fire(e);
|
||||
@@ -395,18 +333,16 @@ final class GenericListener
|
||||
// extListener.startListeningTo(e.getChild(), extNotifier);
|
||||
// listenedTo.add (e.getChild());
|
||||
// } else if (accept(e.getChild())) {
|
||||
if (accept (e.getChild())) {
|
||||
if (accept(e.getChild()))
|
||||
attachTo(e.getChild());
|
||||
}
|
||||
}
|
||||
|
||||
public void componentRemoved(ContainerEvent e) {
|
||||
if (extListener != null && extListener.accept (e.getChild())) {
|
||||
extListener.stopListeningTo (e.getChild());
|
||||
listenedTo.remove (e.getChild());
|
||||
} else if (accept(e.getChild())) {
|
||||
if (extListener != null && extListener.accept(e.getChild())) {
|
||||
extListener.stopListeningTo(e.getChild());
|
||||
listenedTo.remove(e.getChild());
|
||||
} else if (accept(e.getChild()))
|
||||
detachFrom(e.getChild());
|
||||
}
|
||||
}
|
||||
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
|
||||
+4
-14
@@ -172,11 +172,8 @@ final class SimpleWizardInfo implements WizardControllerImplementation {
|
||||
}
|
||||
|
||||
private int index() {
|
||||
SimpleWizard wizard = getWizard();
|
||||
if (wizard != null)
|
||||
return wizard.currentStepIndex();
|
||||
else
|
||||
return 0;
|
||||
SimpleWizard a = getWizard();
|
||||
return a == null ? 0 : a.currentStepIndex();
|
||||
}
|
||||
|
||||
public final void setBusy(boolean value) {
|
||||
@@ -281,11 +278,9 @@ final class SimpleWizardInfo implements WizardControllerImplementation {
|
||||
|
||||
// lookup the step by name
|
||||
boolean containsStep(String name) {
|
||||
for (int i = 0; i < steps.length; i++) {
|
||||
String step = steps[i];
|
||||
for (String step : steps)
|
||||
if (name.equals(step))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -308,7 +303,7 @@ final class SimpleWizardInfo implements WizardControllerImplementation {
|
||||
|
||||
return Arrays.equals(info.descriptions, descriptions)
|
||||
&& Arrays.equals(info.steps, steps)
|
||||
&& info.title == title;
|
||||
&& (info.title == null ? title == null : info.title.equals(title));
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
@@ -323,9 +318,4 @@ final class SimpleWizardInfo implements WizardControllerImplementation {
|
||||
boolean cancel(Map settings) {
|
||||
return provider.cancel(settings);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "SimpleWizardInfo@" + System.identityHashCode(this) + " for "
|
||||
+ provider;
|
||||
}
|
||||
}
|
||||
+78
-87
@@ -8,7 +8,7 @@ and include the License file at http://www.netbeans.org/cddl.txt.
|
||||
If applicable, add the following below the CDDL Header, with the fields
|
||||
enclosed by brackets [] replaced by your own identifying information:
|
||||
"Portions Copyrighted [year] [name of copyright owner]" */
|
||||
/*
|
||||
/*
|
||||
* Summary.java
|
||||
*
|
||||
* Created on September 24, 2006, 4:05 AM
|
||||
@@ -16,7 +16,6 @@ enclosed by brackets [] replaced by your own identifying information:
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import java.awt.Component;
|
||||
@@ -27,122 +26,114 @@ import javax.swing.JTextArea;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
/**
|
||||
* Object which may be returned from <code>WizardPage.WizardResultProducer.finish()</code>
|
||||
* or <code>WizardPanelProvider.finish()</code>, or passed to
|
||||
* <code>DeferredWizardResult.ResultProgressHandle.finish()</code>. If an
|
||||
* instance of <code>Summary</code> is used, then the UI should, rather
|
||||
* than disappearing, show the component provided by the <code>Summary</code>
|
||||
* object. Convenience constructors are provided for plain text and list style
|
||||
* Object which may be returned from
|
||||
* <code>WizardPage.WizardResultProducer.finish()</code>
|
||||
* or <code>WizardPanelProvider.finish()</code>, or passed to
|
||||
* <code>DeferredWizardResult.ResultProgressHandle.finish()</code>. If an
|
||||
* instance of <code>Summary</code> is used, then the UI should, rather
|
||||
* than disappearing, show the component provided by the <code>Summary</code>
|
||||
* object. Convenience constructors are provided for plain text and list style
|
||||
* views.
|
||||
*
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
public class Summary {
|
||||
|
||||
private final Component comp;
|
||||
private Object result;
|
||||
|
||||
//constructors package private - only unit tests should be able to subclass
|
||||
//Summary
|
||||
|
||||
Summary(String text, Object result) {
|
||||
|
||||
/**
|
||||
* Create a <code>Summary</code> object which will display the
|
||||
* passed <code>String</code> in a text component of some sort.
|
||||
*
|
||||
* @param text The text to display - must be non-null, greater than zero
|
||||
* length and not completely whitespace
|
||||
* @param result The result that should be returned when the Wizard is
|
||||
* closed
|
||||
*
|
||||
* @return the requested <code>Summary</code> object
|
||||
*/
|
||||
public Summary(String text, Object result) {
|
||||
//XXX this is creating components off the AWT thread - needs to change
|
||||
//to use invokeAndWait where appropriate
|
||||
if (text == null) {
|
||||
throw new NullPointerException ("Text is null"); //NOI18N
|
||||
}
|
||||
if (text.trim().length() == 0) {
|
||||
throw new IllegalArgumentException ("Text is empty or all " + //NOI18N
|
||||
"whitespace"); //NOI18N
|
||||
}
|
||||
if (text == null)
|
||||
throw new NullPointerException("Text is null");
|
||||
if (text.trim().length() == 0)
|
||||
throw new IllegalArgumentException("Text is empty or all whitespace");
|
||||
this.result = result;
|
||||
JTextArea jta = new JTextArea();
|
||||
jta.setText (text);
|
||||
jta.setText(text);
|
||||
jta.setWrapStyleWord(true);
|
||||
jta.setLineWrap(true);
|
||||
jta.getCaret().setBlinkRate(0);
|
||||
jta.setEditable(false);
|
||||
jta.getCaret().setVisible(true);
|
||||
Font f = UIManager.getFont ("Label.font");
|
||||
if (f != null) { //may be on old GTK L&F, etc.
|
||||
jta.setFont (f);
|
||||
}
|
||||
comp = new JScrollPane (jta);
|
||||
Font f = UIManager.getFont("Label.font");
|
||||
if (f != null) //may be on old GTK L&F, etc.
|
||||
jta.setFont(f);
|
||||
comp = new JScrollPane(jta);
|
||||
}
|
||||
|
||||
Summary(String[] items, Object result) {
|
||||
if (items == null) {
|
||||
throw new NullPointerException ("Items array null"); //NOI18N
|
||||
}
|
||||
if (items.length == 0) {
|
||||
throw new IllegalArgumentException ("Items array empty"); //NOI18N
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <code>Summary</code> object that will display the passed
|
||||
* <code>String</code>s in a <code>JList</code> or similar.
|
||||
*
|
||||
* @param items A non-null list of one or more Strings to be displayed
|
||||
* @param result The result that should be returned when the Wizard is
|
||||
* closed
|
||||
*
|
||||
* @return the requested <code>Summary</code> object
|
||||
*/
|
||||
public Summary(String[] items, Object result) {
|
||||
if (items == null)
|
||||
throw new NullPointerException("Items array null");
|
||||
if (items.length == 0)
|
||||
throw new IllegalArgumentException("Items array empty");
|
||||
this.result = result;
|
||||
JList list = new JList(items);
|
||||
comp = new JScrollPane (list);
|
||||
comp = new JScrollPane(list);
|
||||
}
|
||||
|
||||
Summary(Component comp, Object result) {
|
||||
|
||||
/**
|
||||
* Create a <code>Summary</code> object that will display the passed
|
||||
* component.
|
||||
*
|
||||
* @param comp A custom component to show on the summary page after the
|
||||
* Wizard has been completed
|
||||
* @param result The result that should be returned when the
|
||||
* <code>Wizard</code> is
|
||||
* closed
|
||||
*
|
||||
* @return the requested <code>Summary</code> object
|
||||
*/
|
||||
public Summary(Component comp, Object result) {
|
||||
this.result = result;
|
||||
this.comp = comp;
|
||||
if (comp == null) {
|
||||
throw new NullPointerException ("Null component"); //NOI18N
|
||||
}
|
||||
if (comp == null)
|
||||
throw new NullPointerException("Null component");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <code>Summary</code> object that will display the passed
|
||||
* <code>String</code>s in a <code>JList</code> or similar.
|
||||
* @param items A non-null list of one or more Strings to be displayed
|
||||
* @param result The result that should be returned when the Wizard is
|
||||
* closed
|
||||
* @return the requested <code>Summary</code> object
|
||||
*/
|
||||
public static Summary create (String[] items, Object result) {
|
||||
return new Summary (items, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <code>Summary</code> object that will display the passed component.
|
||||
* @param comp A custom component to show on the summary page after the
|
||||
* Wizard has been completed
|
||||
* @param result The result that should be returned when the <code>Wizard</code> is
|
||||
* closed
|
||||
* @return the requested <code>Summary</code> object
|
||||
*/
|
||||
public static Summary create (Component comp, Object result) {
|
||||
return new Summary (comp, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <code>Summary</code> object which will display the
|
||||
* passed <code>String</code> in a text component of some sort.
|
||||
* @param text The text to display - must be non-null, greater than zero
|
||||
* length and not completely whitespace
|
||||
* @param result The result that should be returned when the Wizard is
|
||||
* closed
|
||||
* @return the requested <code>Summary</code> object
|
||||
*/
|
||||
public static Summary create (String text, Object result) {
|
||||
return new Summary (text, result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the component that will display the summary information.
|
||||
*
|
||||
* @return an appropriate component, the type of which may differ depending
|
||||
* on the factory method used to create this component
|
||||
*/
|
||||
* on the factory method used to create this component
|
||||
*/
|
||||
public Component getSummaryComponent() {
|
||||
return comp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the object that represents the actual result of whatever the <code>Wizard</code>
|
||||
* that created this <code>Summary</code> object computes. Note this method may not
|
||||
* return another instance of <code>Summary</code> or an instance of
|
||||
* Get the object that represents the actual result of whatever the
|
||||
* <code>Wizard</code>
|
||||
* that created this <code>Summary</code> object computes. Note this method
|
||||
* may not
|
||||
* return another instance of <code>Summary</code> or an instance of
|
||||
* <code>DeferredWizardResult</code>.
|
||||
* @return the object passed to the factory method that created this
|
||||
* Summary object, or null.
|
||||
*/
|
||||
*
|
||||
* @return the object passed to the factory method that created this
|
||||
* Summary object, or null.
|
||||
*/
|
||||
public Object getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
+45
-55
@@ -2,7 +2,6 @@
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -16,8 +15,10 @@ import java.util.Set;
|
||||
* @author Tim Boudreau
|
||||
*/
|
||||
final class Util {
|
||||
private Util(){}
|
||||
|
||||
|
||||
private Util() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of step ids from an array of WizardPages
|
||||
*/
|
||||
@@ -28,22 +29,21 @@ final class Util {
|
||||
for (int i = 0; i < pages.length; i++) {
|
||||
result[i] = pages[i].id();
|
||||
if (result[i] == null || uniqueNames.contains(result[i])) {
|
||||
result[i] = uniquify (getIDFromStaticMethod(pages[i].getClass()),
|
||||
uniqueNames);
|
||||
result[i] = uniquify(getIDFromStaticMethod(pages[i].getClass()),
|
||||
uniqueNames);
|
||||
pages[i].id = result[i];
|
||||
}
|
||||
uniqueNames.add (result[i]);
|
||||
uniqueNames.add(result[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static String uniquify (String s, Set /* <String> */ used) {
|
||||
|
||||
static String uniquify(String s, Set /* <String> */ used) {
|
||||
String test = s;
|
||||
if (test != null) {
|
||||
int ix = 0;
|
||||
while (used.contains(test)) {
|
||||
while (used.contains(test))
|
||||
test = s + "_" + ix++;
|
||||
}
|
||||
}
|
||||
return test;
|
||||
}
|
||||
@@ -56,24 +56,22 @@ final class Util {
|
||||
|
||||
for (int i = 0; i < pages.length; i++) {
|
||||
result[i] = pages[i].description();
|
||||
if (result[i] == null) {
|
||||
result[i] = getDescriptionFromStaticMethod (pages[i].getClass());
|
||||
}
|
||||
if (result[i] == null)
|
||||
result[i] = getDescriptionFromStaticMethod(pages[i].getClass());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static String getIDFromStaticMethod (Class clazz) {
|
||||
static String getIDFromStaticMethod(Class clazz) {
|
||||
// System.err.println("GetID by method for " + clazz);
|
||||
String result = null;
|
||||
try {
|
||||
Method m = clazz.getDeclaredMethod("getStep", new Class[] {});
|
||||
// assert m.getReturnType() == String.class;
|
||||
result = (String) m.invoke(clazz, (Object[]) null);
|
||||
if (result == null) {
|
||||
throw new NullPointerException ("getStep may not return null");
|
||||
}
|
||||
if (result == null)
|
||||
throw new NullPointerException("getStep may not return null");
|
||||
} catch (Exception ex) {
|
||||
//do nothing
|
||||
}
|
||||
@@ -85,27 +83,23 @@ final class Util {
|
||||
* class object passed
|
||||
*/
|
||||
static String[] getSteps(Class[] pages) {
|
||||
if (pages == null) {
|
||||
throw new NullPointerException("Null array of classes"); //NOI18N
|
||||
}
|
||||
if (pages == null)
|
||||
throw new NullPointerException("Null array of classes");
|
||||
|
||||
String[] result = new String[pages.length];
|
||||
|
||||
Set used = new HashSet (pages.length);
|
||||
Set used = new HashSet(pages.length);
|
||||
for (int i = 0; i < pages.length; i++) {
|
||||
if (pages[i] == null) {
|
||||
throw new NullPointerException("Null at " + i + " in array " + //NOI18N
|
||||
"of panel classes"); //NOI18N
|
||||
}
|
||||
if (pages[i] == null)
|
||||
throw new NullPointerException("Null at " + i + " in array "
|
||||
+ "of panel classes");
|
||||
|
||||
if (!WizardPage.class.isAssignableFrom(pages[i])) {
|
||||
throw new IllegalArgumentException(pages[i] +
|
||||
" is not a subclass of WizardPage"); //NOI18N
|
||||
}
|
||||
result[i] = uniquify (getIDFromStaticMethod (pages[i]), used);
|
||||
if (result[i] == null) {
|
||||
if (!WizardPage.class.isAssignableFrom(pages[i]))
|
||||
throw new IllegalArgumentException(pages[i]
|
||||
+ " is not a subclass of WizardPage");
|
||||
result[i] = uniquify(getIDFromStaticMethod(pages[i]), used);
|
||||
if (result[i] == null)
|
||||
result[i] = pages[i].getName();
|
||||
}
|
||||
}
|
||||
// System.err.println("Returning " + Arrays.asList(result));
|
||||
return result;
|
||||
@@ -120,7 +114,6 @@ final class Util {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get an array of descriptions by looking for the static method
|
||||
* getDescription() on each passed class object
|
||||
@@ -128,9 +121,8 @@ final class Util {
|
||||
static String[] getDescriptions(Class[] pages) {
|
||||
String[] result = new String[pages.length];
|
||||
|
||||
for (int i = 0; i < pages.length; i++) {
|
||||
for (int i = 0; i < pages.length; i++)
|
||||
result[i] = getDescriptionFromStaticMethod(pages[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -139,35 +131,33 @@ final class Util {
|
||||
String result = null;
|
||||
Method m;
|
||||
try {
|
||||
m = clazz.getDeclaredMethod("getDescription", (Class[]) null); //NOI18N
|
||||
m = clazz.getDeclaredMethod("getDescription", (Class[]) null);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Could not find or access " + //NOI18N
|
||||
"public static String " + clazz.getName() + //NOI18N
|
||||
".getDescription() - make sure it exists"); //NOI18N
|
||||
throw new IllegalArgumentException("Could not find or access "
|
||||
+ "public static String " + clazz.getName()
|
||||
+ ".getDescription() - make sure it exists");
|
||||
}
|
||||
|
||||
if (m.getReturnType() != String.class) {
|
||||
throw new IllegalArgumentException("getStep has wrong " //NOI18N
|
||||
+ " return type: " + m.getReturnType() + " on " + //NOI18N
|
||||
clazz);
|
||||
}
|
||||
if (m.getReturnType() != String.class)
|
||||
throw new IllegalArgumentException("getStep has wrong "
|
||||
+ " return type: " + m.getReturnType() + " on "
|
||||
+ clazz);
|
||||
|
||||
if (!Modifier.isStatic(m.getModifiers())) {
|
||||
throw new IllegalArgumentException("getStep is not " + //NOI18N
|
||||
"static on " + clazz); //NOI18N
|
||||
}
|
||||
if (!Modifier.isStatic(m.getModifiers()))
|
||||
throw new IllegalArgumentException("getStep is not "
|
||||
+ "static on " + clazz);
|
||||
|
||||
try {
|
||||
m.setAccessible(true);
|
||||
result= (String) m.invoke(null, (Object[]) null);
|
||||
result = (String) m.invoke(null, (Object[]) null);
|
||||
} catch (InvocationTargetException ite) {
|
||||
throw new IllegalArgumentException("Could not invoke " + //NOI18N
|
||||
"public static String " + clazz.getName() + //NOI18N
|
||||
".getDescription() - make sure it exists."); //NOI18N
|
||||
throw new IllegalArgumentException("Could not invoke "
|
||||
+ "public static String " + clazz.getName()
|
||||
+ ".getDescription() - make sure it exists.");
|
||||
} catch (IllegalAccessException iae) {
|
||||
throw new IllegalArgumentException("Could not invoke " + //NOI18N
|
||||
"public static String " + clazz.getName() + //NOI18N
|
||||
".getDescription() - make sure it exists."); //NOI18N
|
||||
throw new IllegalArgumentException("Could not invoke "
|
||||
+ "public static String " + clazz.getName()
|
||||
+ ".getDescription() - make sure it exists.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+126
-104
@@ -8,7 +8,6 @@ and include the License file at http://www.netbeans.org/cddl.txt.
|
||||
If applicable, add the following below the CDDL Header, with the fields
|
||||
enclosed by brackets [] replaced by your own identifying information:
|
||||
"Portions Copyrighted [year] [name of copyright owner]" */
|
||||
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
@@ -21,33 +20,35 @@ import javax.swing.JComponent;
|
||||
import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer;
|
||||
|
||||
/**
|
||||
* Encapsulates the logic and state of a Wizard. A Wizard gathers information
|
||||
* Encapsulates the logic and state of a Wizard. A Wizard gathers information
|
||||
* into a Map, and then performs some action with that information when the
|
||||
* user clicks Finish. To display a wizard, pass it to one of the methods
|
||||
* user clicks Finish. To display a wizard, pass it to one of the methods
|
||||
* on <code>WizardDisplayer.getDefault()</code>.
|
||||
* <p>
|
||||
* A Wizard is a series of one or more steps represented
|
||||
* by panels in the user interface. Each step is identified by a unique String ID.
|
||||
* by panels in the user interface. Each step is identified by a unique String
|
||||
* ID.
|
||||
* Panels are created, usually on-demand, as the user navigates through
|
||||
* the UI of the wizard. Panels typically listen on components they contain
|
||||
* and put values into the Map where the wizard gathers data. Note that if the
|
||||
* the UI of the wizard. Panels typically listen on components they contain
|
||||
* and put values into the Map where the wizard gathers data. Note that if the
|
||||
* user navigates <i>backward</i>, data entered on pages after the current one
|
||||
* disappears from the Map.
|
||||
* <p>
|
||||
* To create a Wizard, you do not implement or instantiate this class directly,
|
||||
* but rather, use one of the convenience classes in this package. There are
|
||||
* but rather, use one of the convenience classes in this package. There are
|
||||
* three:
|
||||
* <ul>
|
||||
* <li><code>WizardPage</code> - use or subclass WizardPage, and pass an array
|
||||
* of instances, or an array of the <code>Class</code> objects of your subclasses
|
||||
* to <code>WizardPage.createWizard()</code>. This class offers the added
|
||||
* of instances, or an array of the <code>Class</code> objects of your
|
||||
* subclasses
|
||||
* to <code>WizardPage.createWizard()</code>. This class offers the added
|
||||
* convenience that standard Swing components will be listened to automatically,
|
||||
* and if their Name property is set, the value from the component will be
|
||||
* automatically put into the settings map.
|
||||
* automatically put into the settings map.
|
||||
* </li>
|
||||
*
|
||||
* <li><code>WizardPanelProvider</code> - subclass this to create a Wizard
|
||||
* with a fixed set of steps. You provide a set of unique ID strings to the
|
||||
* with a fixed set of steps. You provide a set of unique ID strings to the
|
||||
* constructor, for all of the steps in the wizard, and override
|
||||
* createPanel() to create the GUI component that should be displayed for
|
||||
* each step - it will be called on demand as the user moves through the
|
||||
@@ -55,12 +56,12 @@ import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer;
|
||||
*
|
||||
* <li><code>WizardBranchController</code> - this is for creating complex
|
||||
* wizards with decision points after which the future steps change, depending
|
||||
* on what the user chooses. Create a simple wizard using WizardPage or
|
||||
* on what the user chooses. Create a simple wizard using WizardPage or
|
||||
* WizardPanelProvider to represent the initial steps.
|
||||
* Then override <code>getWizardForStep()</code> or
|
||||
* <code>getPanelProviderForStep()</code> to return a different Wizard to
|
||||
* represent the remaining steps at any point where the set of future steps
|
||||
* changes. You can have as many branch points as you want, simply by
|
||||
* changes. You can have as many branch points as you want, simply by
|
||||
* using WizardBranchController to create the wizards for different decision
|
||||
* points.
|
||||
* <p>
|
||||
@@ -81,58 +82,63 @@ import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer;
|
||||
* @author Timothy Boudreau
|
||||
*/
|
||||
public final class Wizard {
|
||||
|
||||
/**
|
||||
* Constant that can be returned by <code>getForwardNavigationMode()</code>
|
||||
* to indicate that the Next button can be enabled (or the Finish button
|
||||
* if the current panel is the last one in the wizard).
|
||||
*/
|
||||
public static final int MODE_CAN_CONTINUE =
|
||||
WizardController.MODE_CAN_CONTINUE;
|
||||
*/
|
||||
public static final int MODE_CAN_CONTINUE
|
||||
= WizardController.MODE_CAN_CONTINUE;
|
||||
|
||||
/**
|
||||
* Constant that can be returned by <code>getForwardNavigationMode</code> to indicate
|
||||
* Constant that can be returned by <code>getForwardNavigationMode</code> to
|
||||
* indicate
|
||||
* that the Finish button can be enabled if the problem string is null.
|
||||
*/
|
||||
public static final int MODE_CAN_FINISH =
|
||||
WizardController.MODE_CAN_FINISH;
|
||||
public static final int MODE_CAN_FINISH
|
||||
= WizardController.MODE_CAN_FINISH;
|
||||
/**
|
||||
* Constant that can be returned by <code>getForwardNavigationMode</code> to indicate
|
||||
* that both the Finish and Next buttons can be enabled if the problem
|
||||
* string is null. This value is a bitmask - i.e.
|
||||
* <code>MODE_CAN_CONTINUE_OR_FINISH == MODE_CAN_CONTINUE |
|
||||
* Constant that can be returned by <code>getForwardNavigationMode</code> to
|
||||
* indicate
|
||||
* that both the Finish and Next buttons can be enabled if the problem
|
||||
* string is null. This value is a bitmask - i.e.
|
||||
* <code>MODE_CAN_CONTINUE_OR_FINISH == MODE_CAN_CONTINUE |
|
||||
* MODE_CAN_FINISH</code>
|
||||
*/
|
||||
public static final int MODE_CAN_CONTINUE_OR_FINISH =
|
||||
WizardController.MODE_CAN_CONTINUE_OR_FINISH;
|
||||
|
||||
public static final int MODE_CAN_CONTINUE_OR_FINISH
|
||||
= WizardController.MODE_CAN_CONTINUE_OR_FINISH;
|
||||
|
||||
/**
|
||||
* Special panel ID key indicating a branch point in the wizard,
|
||||
* after which the next step(s) are unknown.
|
||||
*/
|
||||
public static final String UNDETERMINED_STEP = "_#UndeterminedStep";
|
||||
|
||||
|
||||
final WizardImplementation impl; //package private for unit tests
|
||||
|
||||
/** Creates a new instance of Wizard */
|
||||
|
||||
/**
|
||||
* Creates a new instance of Wizard
|
||||
*/
|
||||
Wizard(WizardImplementation impl) {
|
||||
this.impl = impl;
|
||||
if (impl == null) {
|
||||
if (impl == null)
|
||||
throw new NullPointerException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the wizard that the user is navigating to a different panel,
|
||||
* as identified by the passed <code>id</code>.
|
||||
* @param id The id of the panel being navigated to
|
||||
*
|
||||
* @param id The id of the panel being navigated to
|
||||
* @param wizardData The data gathered thus far as the user has progressed
|
||||
* through the wizard. The contents of this map should not contain any
|
||||
* key/values that were assigned on future panels, if the user is
|
||||
* navigating backward.
|
||||
* through the wizard. The contents of this map should not contain any
|
||||
* key/values that were assigned on future panels, if the user is
|
||||
* navigating backward.
|
||||
*
|
||||
* @return The component that should be shown for step <code>id</code>
|
||||
* of the <code>Wizard</code>
|
||||
*/
|
||||
* of the <code>Wizard</code>
|
||||
*/
|
||||
public JComponent navigatingTo(String id, Map wizardData) {
|
||||
return impl.navigatingTo(id, wizardData);
|
||||
}
|
||||
@@ -140,7 +146,7 @@ public final class Wizard {
|
||||
/**
|
||||
* Get the current step the wizard is on, as determined by the most recent
|
||||
* call to <code>navigatingTo()</code>.
|
||||
*/
|
||||
*/
|
||||
public String getCurrentStep() {
|
||||
return impl.getCurrentStep();
|
||||
}
|
||||
@@ -148,31 +154,34 @@ public final class Wizard {
|
||||
/**
|
||||
* Get the id of the step that comes after current step returned by
|
||||
* <code>getCurrentStep()</code>.
|
||||
* @return Null if this is the last step of the wizard;
|
||||
* <code>UNDETERMINED_STEP</code> if this is a branch point and the
|
||||
* user yet needs to do some interaction with the UI of the current
|
||||
* panel to trigger computation of the id of the next step; otherwise,
|
||||
* the unique id of the next step.
|
||||
*/
|
||||
*
|
||||
* @return Null if this is the last step of the wizard;
|
||||
* <code>UNDETERMINED_STEP</code> if this is a branch point and the
|
||||
* user yet needs to do some interaction with the UI of the current
|
||||
* panel to trigger computation of the id of the next step; otherwise,
|
||||
* the unique id of the next step.
|
||||
*/
|
||||
public String getNextStep() {
|
||||
return impl.getNextStep();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the preceding step to the current one as returned by
|
||||
* <code>getCurrentStep()</code>, or null if the current step is the
|
||||
* <code>getCurrentStep()</code>, or null if the current step is the
|
||||
* first page of the wizard.
|
||||
*
|
||||
* @return the id of the previous step or null
|
||||
*/
|
||||
*/
|
||||
public String getPreviousStep() {
|
||||
return impl.getPreviousStep();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the problem string that should be displayed to the user.
|
||||
*
|
||||
* @return A string describing what the user needs to do to enable
|
||||
* the Next or Finish buttons, or null if the buttons may be enabled
|
||||
*/
|
||||
* the Next or Finish buttons, or null if the buttons may be enabled
|
||||
*/
|
||||
public String getProblem() {
|
||||
return impl.getProblem();
|
||||
}
|
||||
@@ -181,43 +190,49 @@ public final class Wizard {
|
||||
* Get the string IDs of all known steps in this wizard, terminating
|
||||
* with <code>UNDETERMINED_STEP</code> if subsequent steps of the
|
||||
* wizard depend on the user's interaction beyond that point.
|
||||
*
|
||||
* @return an array of strings which may individually be passed to
|
||||
* <code>navigatingTo</code> to change the current step of the wizard
|
||||
*/
|
||||
* <code>navigatingTo</code> to change the current step of the wizard
|
||||
*/
|
||||
public String[] getAllSteps() {
|
||||
return impl.getAllSteps();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a long description for this panel. The long description should be
|
||||
* Get a long description for this panel. The long description should be
|
||||
* used in preference to the short description in the top of a wizard
|
||||
* panel in the UI, if it returns non-null.
|
||||
*
|
||||
* @param stepId The ID of the step for which a description is requested
|
||||
*
|
||||
* @return A more detailed localized description or null
|
||||
*/
|
||||
*/
|
||||
public String getLongDescription(String stepId) {
|
||||
return impl.getLongDescription (stepId);
|
||||
return impl.getLongDescription(stepId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a localized String description of the step for the passed id,
|
||||
* which may be displayed in the UI of the wizard.
|
||||
*
|
||||
* @param id A step id among those returned by <code>getAllSteps()</code>
|
||||
*/
|
||||
*/
|
||||
public String getStepDescription(String id) {
|
||||
return impl.getStepDescription(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user has clicked the finish button. This method
|
||||
* Called when the user has clicked the finish button. This method
|
||||
* computes whatever the result of the wizard is.
|
||||
*
|
||||
* @param settings The complete set of key-value pairs gathered by the
|
||||
* various panels as the user proceeded through the wizard
|
||||
* various panels as the user proceeded through the wizard
|
||||
*
|
||||
* @return An implementation-dependent object that is the outcome of
|
||||
* the wizard. May be null. Special return values are instances of
|
||||
* DeferredWizardResult and Summary which will affect the behavior of
|
||||
* the UI.
|
||||
*/
|
||||
* the wizard. May be null. Special return values are instances of
|
||||
* DeferredWizardResult and Summary which will affect the behavior of
|
||||
* the UI.
|
||||
*/
|
||||
public Object finish(Map settings) throws WizardException {
|
||||
return impl.finish(settings);
|
||||
}
|
||||
@@ -225,20 +240,24 @@ public final class Wizard {
|
||||
/**
|
||||
* Called when the user has clicked the Cancel button in the wizard UI
|
||||
* or otherwise closed the UI component without completing the wizard.
|
||||
* @param settings The (possibly incomplete) set of key-value pairs gathered by the
|
||||
* various panels as the user proceeded through the wizard
|
||||
*
|
||||
* @param settings The (possibly incomplete) set of key-value pairs gathered
|
||||
* by the
|
||||
* various panels as the user proceeded through the wizard
|
||||
*
|
||||
* @return true if the UI may indeed be closed, false if closing should
|
||||
* not be permitted
|
||||
*/
|
||||
public boolean cancel (Map settings) {
|
||||
* not be permitted
|
||||
*/
|
||||
public boolean cancel(Map settings) {
|
||||
return impl.cancel(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title of the Wizard that should be displayed in its dialog
|
||||
* titlebar (if any).
|
||||
*
|
||||
* @return A localized string
|
||||
*/
|
||||
*/
|
||||
public String getTitle() {
|
||||
return impl.getTitle();
|
||||
}
|
||||
@@ -246,8 +265,9 @@ public final class Wizard {
|
||||
/**
|
||||
* Determine if the wizard is busy doing work in a background thread and
|
||||
* all navigation controls should be disabled.
|
||||
*
|
||||
* @return whether or not the wizard is busy
|
||||
*/
|
||||
*/
|
||||
public boolean isBusy() {
|
||||
return impl.isBusy();
|
||||
}
|
||||
@@ -255,23 +275,27 @@ public final class Wizard {
|
||||
/**
|
||||
* Get the navigation mode, which determines the enablement state of
|
||||
* the Next and Finish buttons.
|
||||
*
|
||||
* @return one of the constants <code>MODE_CAN_CONTINUE</code>,
|
||||
* <code>MODE_CAN_FINISH</code>, or <code>MODE_CAN_CONTINUE_OR_FINISH</code>.
|
||||
*/
|
||||
* <code>MODE_CAN_FINISH</code>, or
|
||||
* <code>MODE_CAN_CONTINUE_OR_FINISH</code>.
|
||||
*/
|
||||
public int getForwardNavigationMode() {
|
||||
return impl.getForwardNavigationMode();
|
||||
}
|
||||
|
||||
private volatile boolean listeningToImpl = false;
|
||||
private final List listeners = Collections.synchronizedList (
|
||||
new LinkedList());
|
||||
private final List listeners = Collections.synchronizedList(
|
||||
new LinkedList());
|
||||
|
||||
private WizardObserver l = null;
|
||||
|
||||
/**
|
||||
* Add a WizardObserver that will be notified of navigability and step
|
||||
* changes.
|
||||
*
|
||||
* @param observer A WizardObserver
|
||||
*/
|
||||
*/
|
||||
public void addWizardObserver(WizardObserver observer) {
|
||||
listeners.add(observer);
|
||||
if (!listeningToImpl) {
|
||||
@@ -283,11 +307,12 @@ public final class Wizard {
|
||||
|
||||
/**
|
||||
* Remove a WizardObserver.
|
||||
*
|
||||
* @param observer A WizardObserver
|
||||
*/
|
||||
*/
|
||||
public void removeWizardObserver(WizardObserver observer) {
|
||||
listeners.remove(observer);
|
||||
if (listeningToImpl && listeners.size() == 0) {
|
||||
if (listeningToImpl && listeners.isEmpty()) {
|
||||
impl.removeWizardObserver(l);
|
||||
l = null;
|
||||
listeningToImpl = false;
|
||||
@@ -295,28 +320,26 @@ public final class Wizard {
|
||||
}
|
||||
|
||||
private class ImplL implements WizardObserver {
|
||||
|
||||
public void stepsChanged(Wizard wizard) {
|
||||
WizardObserver[] l = (WizardObserver[]) listeners.toArray(
|
||||
new WizardObserver[listeners.size()]);
|
||||
for (int i = 0; i < l.length; i++) {
|
||||
l[i].stepsChanged(Wizard.this);
|
||||
}
|
||||
new WizardObserver[listeners.size()]);
|
||||
for (WizardObserver l1 : l)
|
||||
l1.stepsChanged(Wizard.this);
|
||||
}
|
||||
|
||||
public void navigabilityChanged(Wizard wizard) {
|
||||
WizardObserver[] l = (WizardObserver[]) listeners.toArray(
|
||||
new WizardObserver[listeners.size()]);
|
||||
for (int i = 0; i < l.length; i++) {
|
||||
l[i].navigabilityChanged(Wizard.this);
|
||||
}
|
||||
new WizardObserver[listeners.size()]);
|
||||
for (WizardObserver l1 : l)
|
||||
l1.navigabilityChanged(Wizard.this);
|
||||
}
|
||||
|
||||
public void selectionChanged(Wizard wizard) {
|
||||
WizardObserver[] l = (WizardObserver[]) listeners.toArray(
|
||||
new WizardObserver[listeners.size()]);
|
||||
for (int i = 0; i < l.length; i++) {
|
||||
l[i].selectionChanged(Wizard.this);
|
||||
}
|
||||
new WizardObserver[listeners.size()]);
|
||||
for (WizardObserver l1 : l)
|
||||
l1.selectionChanged(Wizard.this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,41 +347,40 @@ public final class Wizard {
|
||||
return impl.hashCode() * 17;
|
||||
}
|
||||
|
||||
public boolean equals (Object o) {
|
||||
if (o == this) {
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
} else if (o instanceof Wizard) {
|
||||
return impl.equals (((Wizard)o).impl);
|
||||
} else {
|
||||
else if (o instanceof Wizard)
|
||||
return impl.equals(((Wizard) o).impl);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to WizardDisplayer.showWizard()
|
||||
*/
|
||||
public void show () {
|
||||
*/
|
||||
public void show() {
|
||||
WizardDisplayer.showWizard(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to WizardDisplayer.showWizard()
|
||||
*/
|
||||
public Object show (Wizard wizard, Action help) {
|
||||
return WizardDisplayer.showWizard (wizard, help);
|
||||
*/
|
||||
public Object show(Wizard wizard, Action help) {
|
||||
return WizardDisplayer.showWizard(wizard, help);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to WizardDisplayer.showWizard()
|
||||
*/
|
||||
public Object show (Wizard wizard, Rectangle r) {
|
||||
return WizardDisplayer.showWizard (wizard, r);
|
||||
*/
|
||||
public Object show(Wizard wizard, Rectangle r) {
|
||||
return WizardDisplayer.showWizard(wizard, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to WizardDisplayer.showWizard()
|
||||
*/
|
||||
public Object show (Wizard wizard, Rectangle r, Action help) {
|
||||
return WizardDisplayer.showWizard (wizard, r, help, null);
|
||||
*/
|
||||
public Object show(Wizard wizard, Rectangle r, Action help) {
|
||||
return WizardDisplayer.showWizard(wizard, r, help, null);
|
||||
}
|
||||
}
|
||||
+295
-327
File diff suppressed because it is too large.
Load diff
+47
-39
@@ -5,8 +5,7 @@ compliance with the License.
|
||||
or http://www.netbeans.org/cddl.txt.
|
||||
When distributing Covered Code, include this CDDL Header Notice in each file
|
||||
and include the License file at http://www.netbeans.org/cddl.txt.
|
||||
*/
|
||||
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -14,76 +13,85 @@ import java.util.Map;
|
||||
/**
|
||||
* This is an optional interface for panels that want to be notified when
|
||||
* the next and back buttons are pressed.
|
||||
*
|
||||
*
|
||||
* The WizardPanelProvider is NOT required to create panels that implement
|
||||
* this interface.
|
||||
*
|
||||
*
|
||||
* Each of these methods returns a WizardPanelNavResult that can be used to
|
||||
* indicate PROCEED or REMAIN_ON_PAGE.
|
||||
*
|
||||
*
|
||||
* The result can also be an instance of a subclass of WizardPanelNavResult
|
||||
* that implements the <code>start</code> method to use a background thread
|
||||
* to determine if the next page can be shown.
|
||||
*
|
||||
* to determine if the next page can be shown.
|
||||
*
|
||||
* @author stanley@stanleyknutson.com
|
||||
*/
|
||||
public interface WizardPanel
|
||||
{
|
||||
public interface WizardPanel {
|
||||
|
||||
/**
|
||||
* This method is invoked when the "next" button has been pushed,
|
||||
* to do a final validation of input (such as doing a database login).
|
||||
*
|
||||
* If this method return false, then the "next" button will not change the
|
||||
* displayed panel. Presumably some error will have been shown to the user.
|
||||
*
|
||||
*
|
||||
* If this method return false, then the "next" button will not change the
|
||||
* displayed panel. Presumably some error will have been shown to the user.
|
||||
*
|
||||
* @param stepName
|
||||
* @param settings
|
||||
* @param wizard
|
||||
*
|
||||
* @return WizardPanelNavResult.PROCEED if the "next" button should proceed,
|
||||
* WizardPanelNavResult.REMAIN_ON_PAGE if "next" should not proceed,
|
||||
* or a instance of subclass of WizardPanelResult that will do some background computation
|
||||
* and call the progress.finished method with one of those constants
|
||||
* (or call progress.failed with the error message)
|
||||
* WizardPanelNavResult.REMAIN_ON_PAGE if "next" should not proceed,
|
||||
* or a instance of subclass of WizardPanelResult that will do some
|
||||
* background computation
|
||||
* and call the progress.finished method with one of those constants
|
||||
* (or call progress.failed with the error message)
|
||||
*/
|
||||
public WizardPanelNavResult allowNext (String stepName, Map settings, Wizard wizard);
|
||||
public WizardPanelNavResult allowNext(String stepName, Map settings, Wizard wizard);
|
||||
|
||||
/**
|
||||
* This method is invoked when the "back" button has been pushed,
|
||||
* to discard any data from the setings that will not been needed and for which the
|
||||
* to discard any data from the setings that will not been needed and for
|
||||
* which the
|
||||
* normal "just hide that data" is not the desired behavior.
|
||||
* (See MergeMap for discussion of the "hide the data" behavior)
|
||||
*
|
||||
* If this method return false, then the "next" button will not change the
|
||||
* displayed panel. Presumably some error will have been shown to the user.
|
||||
*
|
||||
*
|
||||
* If this method return false, then the "next" button will not change the
|
||||
* displayed panel. Presumably some error will have been shown to the user.
|
||||
*
|
||||
* @param stepName
|
||||
* @param settings
|
||||
* @param wizard
|
||||
*
|
||||
* @return WizardPanelNavResult.PROCEED if the "back" button should proceed,
|
||||
* WizardPanelNavResult.REMAIN_ON_PAGE if "back" should not proceed,
|
||||
* or a instance of subclass of WizardPanelResult that will do some background computation
|
||||
* and call the progress.finished method with one of those constants.
|
||||
* (or call progress.failed with the error message)
|
||||
* WizardPanelNavResult.REMAIN_ON_PAGE if "back" should not proceed,
|
||||
* or a instance of subclass of WizardPanelResult that will do some
|
||||
* background computation
|
||||
* and call the progress.finished method with one of those constants.
|
||||
* (or call progress.failed with the error message)
|
||||
*/
|
||||
public WizardPanelNavResult allowBack (String stepName, Map settings, Wizard wizard);
|
||||
public WizardPanelNavResult allowBack(String stepName, Map settings, Wizard wizard);
|
||||
|
||||
/**
|
||||
* This method is invoked when the "finish" button has been pushed,
|
||||
* to allow veto of the finish action BEFORE the wizard finish method is invoked.
|
||||
*
|
||||
* If this method return false, then the "finish" button will have no effect.
|
||||
* to allow veto of the finish action BEFORE the wizard finish method is
|
||||
* invoked.
|
||||
*
|
||||
* If this method return false, then the "finish" button will have no
|
||||
* effect.
|
||||
* Presumably some error will have been shown to the user.
|
||||
*
|
||||
*
|
||||
* @param stepName
|
||||
* @param settings
|
||||
* @param wizard
|
||||
* @return WizardPanelNavResult.PROCEED if the "finish" button should proceed,
|
||||
* WizardPanelNavResult.REMAIN_ON_PAGE if "finish" should not proceed,
|
||||
* or a instance of subclass of WizardPanelResult that will do some background computation
|
||||
* and call the progress.finished method with one of those constants.
|
||||
* (or call progress.failed with the error message)
|
||||
*
|
||||
* @return WizardPanelNavResult.PROCEED if the "finish" button should
|
||||
* proceed,
|
||||
* WizardPanelNavResult.REMAIN_ON_PAGE if "finish" should not proceed,
|
||||
* or a instance of subclass of WizardPanelResult that will do some
|
||||
* background computation
|
||||
* and call the progress.finished method with one of those constants.
|
||||
* (or call progress.failed with the error message)
|
||||
*/
|
||||
public WizardPanelNavResult allowFinish (String stepName, Map settings, Wizard wizard);
|
||||
public WizardPanelNavResult allowFinish(String stepName, Map settings, Wizard wizard);
|
||||
|
||||
}
|
||||
|
||||
+24
-41
@@ -5,23 +5,22 @@ compliance with the License.
|
||||
or http://www.netbeans.org/cddl.txt.
|
||||
When distributing Covered Code, include this CDDL Header Notice in each file
|
||||
and include the License file at http://www.netbeans.org/cddl.txt.
|
||||
*/
|
||||
|
||||
*/
|
||||
package org.jackhuang.hellominecraft.utils.views.wizard.spi;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Result class for the methods in WizardPanel.
|
||||
*
|
||||
*
|
||||
* For immediate action, one of the two constantants PROCEED or REMAIN_ON_PAGE
|
||||
* should be returned. Otherwise an instance of a subclass should be returned
|
||||
* should be returned. Otherwise an instance of a subclass should be returned
|
||||
* that computes a Boolean result.
|
||||
*
|
||||
*
|
||||
* @author stanley@stanleyknutson.com
|
||||
*/
|
||||
public abstract class WizardPanelNavResult extends DeferredWizardResult
|
||||
{
|
||||
public abstract class WizardPanelNavResult extends DeferredWizardResult {
|
||||
|
||||
/**
|
||||
* value for procced to next step in the wizard.
|
||||
*/
|
||||
@@ -31,59 +30,43 @@ public abstract class WizardPanelNavResult extends DeferredWizardResult
|
||||
*/
|
||||
public static final WizardPanelNavResult REMAIN_ON_PAGE = new WPNRimmediate(false);
|
||||
|
||||
public WizardPanelNavResult(boolean useBusy) {
|
||||
super (false, useBusy);
|
||||
private WizardPanelNavResult() {
|
||||
super();
|
||||
}
|
||||
|
||||
public WizardPanelNavResult(boolean useBusy, boolean canAbort) {
|
||||
super (canAbort, useBusy);
|
||||
}
|
||||
|
||||
public WizardPanelNavResult() {
|
||||
super (false, false);
|
||||
}
|
||||
|
||||
public boolean isDeferredComputation()
|
||||
{
|
||||
public boolean isDeferredComputation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* internal class for the constants only
|
||||
*/
|
||||
private final static class WPNRimmediate extends WizardPanelNavResult
|
||||
{
|
||||
private final static class WPNRimmediate extends WizardPanelNavResult {
|
||||
|
||||
boolean value;
|
||||
|
||||
WPNRimmediate (boolean v)
|
||||
{
|
||||
|
||||
WPNRimmediate(boolean v) {
|
||||
value = v;
|
||||
}
|
||||
public boolean isDeferredComputation()
|
||||
{
|
||||
|
||||
public boolean isDeferredComputation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean equals (Object o)
|
||||
{
|
||||
if (o instanceof WPNRimmediate && ((WPNRimmediate)o).value == value)
|
||||
{
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof WPNRimmediate && ((WPNRimmediate) o).value == value)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
|
||||
public int hashCode() {
|
||||
return value ? 1 : 2;
|
||||
}
|
||||
|
||||
public void start(Map settings, ResultProgressHandle progress)
|
||||
{
|
||||
|
||||
public void start(Map settings, ResultProgressHandle progress) {
|
||||
// Should never get here, this is supposed to be immediate!
|
||||
throw new RuntimeException("Immediate result was called as deferral!");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* ARMOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.ARM;
|
||||
|
||||
/**
|
||||
* BCJ filter for little endian ARM instructions.
|
||||
*/
|
||||
public class ARMOptions extends BCJOptions {
|
||||
|
||||
private static final int ALIGNMENT = 4;
|
||||
|
||||
public ARMOptions() {
|
||||
super(ALIGNMENT);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new SimpleOutputStream(out, new ARM(true, startOffset));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new SimpleInputStream(in, new ARM(false, startOffset));
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new BCJEncoder(this, BCJCoder.ARM_FILTER_ID);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* ARMThumbOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.ARMThumb;
|
||||
|
||||
/**
|
||||
* BCJ filter for little endian ARM-Thumb instructions.
|
||||
*/
|
||||
public class ARMThumbOptions extends BCJOptions {
|
||||
|
||||
private static final int ALIGNMENT = 2;
|
||||
|
||||
public ARMThumbOptions() {
|
||||
super(ALIGNMENT);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new SimpleOutputStream(out, new ARMThumb(true, startOffset));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new SimpleInputStream(in, new ARMThumb(false, startOffset));
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new BCJEncoder(this, BCJCoder.ARMTHUMB_FILTER_ID);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* BCJCoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
abstract class BCJCoder implements FilterCoder {
|
||||
|
||||
public static final long X86_FILTER_ID = 0x04;
|
||||
public static final long POWERPC_FILTER_ID = 0x05;
|
||||
public static final long IA64_FILTER_ID = 0x06;
|
||||
public static final long ARM_FILTER_ID = 0x07;
|
||||
public static final long ARMTHUMB_FILTER_ID = 0x08;
|
||||
public static final long SPARC_FILTER_ID = 0x09;
|
||||
|
||||
public static boolean isBCJFilterID(long filterID) {
|
||||
return filterID >= 0x04 && filterID <= 0x09;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changesSize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nonLastOK() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lastOK() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* BCJDecoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.*;
|
||||
|
||||
class BCJDecoder extends BCJCoder implements FilterDecoder {
|
||||
|
||||
private final long filterID;
|
||||
private final int startOffset;
|
||||
|
||||
BCJDecoder(long filterID, byte[] props)
|
||||
throws UnsupportedOptionsException {
|
||||
assert isBCJFilterID(filterID);
|
||||
this.filterID = filterID;
|
||||
|
||||
switch (props.length) {
|
||||
case 0:
|
||||
startOffset = 0;
|
||||
break;
|
||||
case 4:
|
||||
int n = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
n |= (props[i] & 0xFF) << (i * 8);
|
||||
startOffset = n;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported BCJ filter properties");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemoryUsage() {
|
||||
return SimpleInputStream.getMemoryUsage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
SimpleFilter simpleFilter = null;
|
||||
|
||||
if (filterID == X86_FILTER_ID)
|
||||
simpleFilter = new X86(false, startOffset);
|
||||
else if (filterID == POWERPC_FILTER_ID)
|
||||
simpleFilter = new PowerPC(false, startOffset);
|
||||
else if (filterID == IA64_FILTER_ID)
|
||||
simpleFilter = new IA64(false, startOffset);
|
||||
else if (filterID == ARM_FILTER_ID)
|
||||
simpleFilter = new ARM(false, startOffset);
|
||||
else if (filterID == ARMTHUMB_FILTER_ID)
|
||||
simpleFilter = new ARMThumb(false, startOffset);
|
||||
else if (filterID == SPARC_FILTER_ID)
|
||||
simpleFilter = new SPARC(false, startOffset);
|
||||
else
|
||||
assert false;
|
||||
|
||||
return new SimpleInputStream(in, simpleFilter);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* BCJEncoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
class BCJEncoder extends BCJCoder implements FilterEncoder {
|
||||
|
||||
private final BCJOptions options;
|
||||
private final long filterID;
|
||||
private final byte[] props;
|
||||
|
||||
BCJEncoder(BCJOptions options, long filterID) {
|
||||
assert isBCJFilterID(filterID);
|
||||
int startOffset = options.getStartOffset();
|
||||
|
||||
if (startOffset == 0)
|
||||
props = new byte[0];
|
||||
else {
|
||||
props = new byte[4];
|
||||
for (int i = 0; i < 4; ++i)
|
||||
props[i] = (byte) (startOffset >>> (i * 8));
|
||||
}
|
||||
|
||||
this.filterID = filterID;
|
||||
this.options = (BCJOptions) options.clone();
|
||||
}
|
||||
|
||||
public long getFilterID() {
|
||||
return filterID;
|
||||
}
|
||||
|
||||
public byte[] getFilterProps() {
|
||||
return props;
|
||||
}
|
||||
|
||||
public boolean supportsFlushing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return options.getOutputStream(out);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* BCJOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
abstract class BCJOptions extends FilterOptions {
|
||||
|
||||
private final int alignment;
|
||||
int startOffset = 0;
|
||||
|
||||
BCJOptions(int alignment) {
|
||||
this.alignment = alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the start offset for the address conversions. Normally this is
|
||||
* useless so you shouldn't use this function. The default value is
|
||||
* <code>0</code>.
|
||||
*/
|
||||
public void setStartOffset(int startOffset)
|
||||
throws UnsupportedOptionsException {
|
||||
if ((startOffset & (alignment - 1)) != 0)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Start offset must be a multiple of " + alignment);
|
||||
|
||||
this.startOffset = startOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start offset.
|
||||
*/
|
||||
public int getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
public int getEncoderMemoryUsage() {
|
||||
return SimpleOutputStream.getMemoryUsage();
|
||||
}
|
||||
|
||||
public int getDecoderMemoryUsage() {
|
||||
return SimpleInputStream.getMemoryUsage();
|
||||
}
|
||||
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
assert false;
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
/*
|
||||
* BlockInputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import org.tukaani.xz.common.DecoderUtil;
|
||||
import org.tukaani.xz.check.Check;
|
||||
|
||||
class BlockInputStream extends InputStream {
|
||||
|
||||
private final DataInputStream inData;
|
||||
private final CountingInputStream inCounted;
|
||||
private InputStream filterChain;
|
||||
private final Check check;
|
||||
|
||||
private long uncompressedSizeInHeader = -1;
|
||||
private long compressedSizeInHeader = -1;
|
||||
private long compressedSizeLimit;
|
||||
private final int headerSize;
|
||||
private long uncompressedSize = 0;
|
||||
private boolean endReached = false;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
public BlockInputStream(InputStream in, Check check, int memoryLimit,
|
||||
long unpaddedSizeInIndex,
|
||||
long uncompressedSizeInIndex)
|
||||
throws IOException, IndexIndicatorException {
|
||||
this.check = check;
|
||||
inData = new DataInputStream(in);
|
||||
|
||||
byte[] buf = new byte[DecoderUtil.BLOCK_HEADER_SIZE_MAX];
|
||||
|
||||
// Block Header Size or Index Indicator
|
||||
inData.readFully(buf, 0, 1);
|
||||
|
||||
// See if this begins the Index field.
|
||||
if (buf[0] == 0x00)
|
||||
throw new IndexIndicatorException();
|
||||
|
||||
// Read the rest of the Block Header.
|
||||
headerSize = 4 * ((buf[0] & 0xFF) + 1);
|
||||
inData.readFully(buf, 1, headerSize - 1);
|
||||
|
||||
// Validate the CRC32.
|
||||
if (!DecoderUtil.isCRC32Valid(buf, 0, headerSize - 4, headerSize - 4))
|
||||
throw new CorruptedInputException("XZ Block Header is corrupt");
|
||||
|
||||
// Check for reserved bits in Block Flags.
|
||||
if ((buf[1] & 0x3C) != 0)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported options in XZ Block Header");
|
||||
|
||||
// Memory for the Filter Flags field
|
||||
int filterCount = (buf[1] & 0x03) + 1;
|
||||
long[] filterIDs = new long[filterCount];
|
||||
byte[][] filterProps = new byte[filterCount][];
|
||||
|
||||
// Use a stream to parse the fields after the Block Flags field.
|
||||
// Exclude the CRC32 field at the end.
|
||||
ByteArrayInputStream bufStream = new ByteArrayInputStream(
|
||||
buf, 2, headerSize - 6);
|
||||
|
||||
try {
|
||||
// Set the maximum valid compressed size. This is overriden
|
||||
// by the value from the Compressed Size field if it is present.
|
||||
compressedSizeLimit = (DecoderUtil.VLI_MAX & ~3)
|
||||
- headerSize - check.getSize();
|
||||
|
||||
// Decode and validate Compressed Size if the relevant flag
|
||||
// is set in Block Flags.
|
||||
if ((buf[1] & 0x40) != 0x00) {
|
||||
compressedSizeInHeader = DecoderUtil.decodeVLI(bufStream);
|
||||
|
||||
if (compressedSizeInHeader == 0
|
||||
|| compressedSizeInHeader > compressedSizeLimit)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
compressedSizeLimit = compressedSizeInHeader;
|
||||
}
|
||||
|
||||
// Decode Uncompressed Size if the relevant flag is set
|
||||
// in Block Flags.
|
||||
if ((buf[1] & 0x80) != 0x00)
|
||||
uncompressedSizeInHeader = DecoderUtil.decodeVLI(bufStream);
|
||||
|
||||
// Decode Filter Flags.
|
||||
for (int i = 0; i < filterCount; ++i) {
|
||||
filterIDs[i] = DecoderUtil.decodeVLI(bufStream);
|
||||
|
||||
long filterPropsSize = DecoderUtil.decodeVLI(bufStream);
|
||||
if (filterPropsSize > bufStream.available())
|
||||
throw new CorruptedInputException();
|
||||
|
||||
filterProps[i] = new byte[(int) filterPropsSize];
|
||||
bufStream.read(filterProps[i]);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new CorruptedInputException("XZ Block Header is corrupt");
|
||||
}
|
||||
|
||||
// Check that the remaining bytes are zero.
|
||||
for (int i = bufStream.available(); i > 0; --i)
|
||||
if (bufStream.read() != 0x00)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported options in XZ Block Header");
|
||||
|
||||
// Validate the Blcok Header against the Index when doing
|
||||
// random access reading.
|
||||
if (unpaddedSizeInIndex != -1) {
|
||||
// Compressed Data must be at least one byte, so if Block Header
|
||||
// and Check alone take as much or more space than the size
|
||||
// stored in the Index, the file is corrupt.
|
||||
int headerAndCheckSize = headerSize + check.getSize();
|
||||
if (headerAndCheckSize >= unpaddedSizeInIndex)
|
||||
throw new CorruptedInputException(
|
||||
"XZ Index does not match a Block Header");
|
||||
|
||||
// The compressed size calculated from Unpadded Size must
|
||||
// match the value stored in the Compressed Size field in
|
||||
// the Block Header.
|
||||
long compressedSizeFromIndex
|
||||
= unpaddedSizeInIndex - headerAndCheckSize;
|
||||
if (compressedSizeFromIndex > compressedSizeLimit
|
||||
|| (compressedSizeInHeader != -1
|
||||
&& compressedSizeInHeader != compressedSizeFromIndex))
|
||||
throw new CorruptedInputException(
|
||||
"XZ Index does not match a Block Header");
|
||||
|
||||
// The uncompressed size stored in the Index must match
|
||||
// the value stored in the Uncompressed Size field in
|
||||
// the Block Header.
|
||||
if (uncompressedSizeInHeader != -1
|
||||
&& uncompressedSizeInHeader != uncompressedSizeInIndex)
|
||||
throw new CorruptedInputException(
|
||||
"XZ Index does not match a Block Header");
|
||||
|
||||
// For further validation, pretend that the values from the Index
|
||||
// were stored in the Block Header.
|
||||
compressedSizeLimit = compressedSizeFromIndex;
|
||||
compressedSizeInHeader = compressedSizeFromIndex;
|
||||
uncompressedSizeInHeader = uncompressedSizeInIndex;
|
||||
}
|
||||
|
||||
// Check if the Filter IDs are supported, decode
|
||||
// the Filter Properties, and check that they are
|
||||
// supported by this decoder implementation.
|
||||
FilterDecoder[] filters = new FilterDecoder[filterIDs.length];
|
||||
|
||||
for (int i = 0; i < filters.length; ++i)
|
||||
if (filterIDs[i] == LZMA2Coder.FILTER_ID)
|
||||
filters[i] = new LZMA2Decoder(filterProps[i]);
|
||||
|
||||
else if (filterIDs[i] == DeltaCoder.FILTER_ID)
|
||||
filters[i] = new DeltaDecoder(filterProps[i]);
|
||||
|
||||
else if (BCJDecoder.isBCJFilterID(filterIDs[i]))
|
||||
filters[i] = new BCJDecoder(filterIDs[i], filterProps[i]);
|
||||
|
||||
else
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unknown Filter ID " + filterIDs[i]);
|
||||
|
||||
RawCoder.validate(filters);
|
||||
|
||||
// Check the memory usage limit.
|
||||
if (memoryLimit >= 0) {
|
||||
int memoryNeeded = 0;
|
||||
for (int i = 0; i < filters.length; ++i)
|
||||
memoryNeeded += filters[i].getMemoryUsage();
|
||||
|
||||
if (memoryNeeded > memoryLimit)
|
||||
throw new MemoryLimitException(memoryNeeded, memoryLimit);
|
||||
}
|
||||
|
||||
// Use an input size counter to calculate
|
||||
// the size of the Compressed Data field.
|
||||
inCounted = new CountingInputStream(in);
|
||||
|
||||
// Initialize the filter chain.
|
||||
filterChain = inCounted;
|
||||
for (int i = filters.length - 1; i >= 0; --i)
|
||||
filterChain = filters[i].getInputStream(filterChain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (endReached)
|
||||
return -1;
|
||||
|
||||
int ret = filterChain.read(buf, off, len);
|
||||
|
||||
if (ret > 0) {
|
||||
check.update(buf, off, ret);
|
||||
uncompressedSize += ret;
|
||||
|
||||
// Catch invalid values.
|
||||
long compressedSize = inCounted.getSize();
|
||||
if (compressedSize < 0
|
||||
|| compressedSize > compressedSizeLimit
|
||||
|| uncompressedSize < 0
|
||||
|| (uncompressedSizeInHeader != -1
|
||||
&& uncompressedSize > uncompressedSizeInHeader))
|
||||
throw new CorruptedInputException();
|
||||
|
||||
// Check the Block integrity as soon as possible:
|
||||
// - The filter chain shouldn't return less than requested
|
||||
// unless it hit the end of the input.
|
||||
// - If the uncompressed size is known, we know when there
|
||||
// shouldn't be more data coming. We still need to read
|
||||
// one byte to let the filter chain catch errors and to
|
||||
// let it read end of payload marker(s).
|
||||
if (ret < len || uncompressedSize == uncompressedSizeInHeader) {
|
||||
if (filterChain.read() != -1)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
validate();
|
||||
endReached = true;
|
||||
}
|
||||
} else if (ret == -1) {
|
||||
validate();
|
||||
endReached = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void validate() throws IOException {
|
||||
long compressedSize = inCounted.getSize();
|
||||
|
||||
// Validate Compressed Size and Uncompressed Size if they were
|
||||
// present in Block Header.
|
||||
if ((compressedSizeInHeader != -1
|
||||
&& compressedSizeInHeader != compressedSize)
|
||||
|| (uncompressedSizeInHeader != -1
|
||||
&& uncompressedSizeInHeader != uncompressedSize))
|
||||
throw new CorruptedInputException();
|
||||
|
||||
// Block Padding bytes must be zeros.
|
||||
while ((compressedSize++ & 3) != 0)
|
||||
if (inData.readUnsignedByte() != 0x00)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
// Validate the integrity check.
|
||||
byte[] storedCheck = new byte[check.getSize()];
|
||||
inData.readFully(storedCheck);
|
||||
if (!Arrays.equals(check.finish(), storedCheck))
|
||||
throw new CorruptedInputException("Integrity check ("
|
||||
+ check.getName() + ") does not match");
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return filterChain.available();
|
||||
}
|
||||
|
||||
public long getUnpaddedSize() {
|
||||
return headerSize + inCounted.getSize() + check.getSize();
|
||||
}
|
||||
|
||||
public long getUncompressedSize() {
|
||||
return uncompressedSize;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* BlockOutputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.common.EncoderUtil;
|
||||
import org.tukaani.xz.check.Check;
|
||||
|
||||
class BlockOutputStream extends FinishableOutputStream {
|
||||
|
||||
private final OutputStream out;
|
||||
private final CountingOutputStream outCounted;
|
||||
private FinishableOutputStream filterChain;
|
||||
private final Check check;
|
||||
|
||||
private final int headerSize;
|
||||
private final long compressedSizeLimit;
|
||||
private long uncompressedSize = 0;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
public BlockOutputStream(OutputStream out, FilterEncoder[] filters,
|
||||
Check check) throws IOException {
|
||||
this.out = out;
|
||||
this.check = check;
|
||||
|
||||
// Initialize the filter chain.
|
||||
outCounted = new CountingOutputStream(out);
|
||||
filterChain = outCounted;
|
||||
for (int i = filters.length - 1; i >= 0; --i)
|
||||
filterChain = filters[i].getOutputStream(filterChain);
|
||||
|
||||
// Prepare to encode the Block Header field.
|
||||
ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
|
||||
|
||||
// Write a dummy Block Header Size field. The real value is written
|
||||
// once everything else except CRC32 has been written.
|
||||
bufStream.write(0x00);
|
||||
|
||||
// Write Block Flags. Storing Compressed Size or Uncompressed Size
|
||||
// isn't supported for now.
|
||||
bufStream.write(filters.length - 1);
|
||||
|
||||
// List of Filter Flags
|
||||
for (int i = 0; i < filters.length; ++i) {
|
||||
EncoderUtil.encodeVLI(bufStream, filters[i].getFilterID());
|
||||
byte[] filterProps = filters[i].getFilterProps();
|
||||
EncoderUtil.encodeVLI(bufStream, filterProps.length);
|
||||
bufStream.write(filterProps);
|
||||
}
|
||||
|
||||
// Header Padding
|
||||
while ((bufStream.size() & 3) != 0)
|
||||
bufStream.write(0x00);
|
||||
|
||||
byte[] buf = bufStream.toByteArray();
|
||||
|
||||
// Total size of the Block Header: Take the size of the CRC32 field
|
||||
// into account.
|
||||
headerSize = buf.length + 4;
|
||||
|
||||
// This is just a sanity check.
|
||||
if (headerSize > EncoderUtil.BLOCK_HEADER_SIZE_MAX)
|
||||
throw new UnsupportedOptionsException();
|
||||
|
||||
// Block Header Size
|
||||
buf[0] = (byte) (buf.length / 4);
|
||||
|
||||
// Write the Block Header field to the output stream.
|
||||
out.write(buf);
|
||||
EncoderUtil.writeCRC32(out, buf);
|
||||
|
||||
// Calculate the maximum allowed size of the Compressed Data field.
|
||||
// It is hard to exceed it so this is mostly to be pedantic.
|
||||
compressedSizeLimit = (EncoderUtil.VLI_MAX & ~3)
|
||||
- headerSize - check.getSize();
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
tempBuf[0] = (byte) b;
|
||||
write(tempBuf, 0, 1);
|
||||
}
|
||||
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
filterChain.write(buf, off, len);
|
||||
check.update(buf, off, len);
|
||||
uncompressedSize += len;
|
||||
validate();
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
filterChain.flush();
|
||||
validate();
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
// Finish the Compressed Data field.
|
||||
filterChain.finish();
|
||||
validate();
|
||||
|
||||
// Block Padding
|
||||
for (long i = outCounted.getSize(); (i & 3) != 0; ++i)
|
||||
out.write(0x00);
|
||||
|
||||
// Check
|
||||
out.write(check.finish());
|
||||
}
|
||||
|
||||
private void validate() throws IOException {
|
||||
long compressedSize = outCounted.getSize();
|
||||
|
||||
// It is very hard to trigger this exception.
|
||||
// This is just to be pedantic.
|
||||
if (compressedSize < 0 || compressedSize > compressedSizeLimit
|
||||
|| uncompressedSize < 0)
|
||||
throw new XZIOException("XZ Stream has grown too big");
|
||||
}
|
||||
|
||||
public long getUnpaddedSize() {
|
||||
return headerSize + outCounted.getSize() + check.getSize();
|
||||
}
|
||||
|
||||
public long getUncompressedSize() {
|
||||
return uncompressedSize;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* CorruptedInputException
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
/**
|
||||
* Thrown when the compressed input data is corrupt. However, it is possible
|
||||
* that some or all of the data already read from the input stream was corrupt
|
||||
* too.
|
||||
*/
|
||||
public class CorruptedInputException extends XZIOException {
|
||||
|
||||
private static final long serialVersionUID = 3L;
|
||||
|
||||
/**
|
||||
* Creates a new CorruptedInputException with the default error detail
|
||||
* message.
|
||||
*/
|
||||
public CorruptedInputException() {
|
||||
super("Compressed data is corrupt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CorruptedInputException with the specified error detail
|
||||
* message.
|
||||
*
|
||||
* @param s error detail message
|
||||
*/
|
||||
public CorruptedInputException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* CountingInputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Counts the number of bytes read from an input stream.
|
||||
*/
|
||||
class CountingInputStream extends FilterInputStream {
|
||||
|
||||
private long size = 0;
|
||||
|
||||
public CountingInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
int ret = in.read();
|
||||
if (ret != -1 && size >= 0)
|
||||
++size;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int ret = in.read(b, off, len);
|
||||
if (ret > 0 && size >= 0)
|
||||
size += ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* CountingOutputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Counts the number of bytes written to an output stream.
|
||||
* <p>
|
||||
* The <code>finish</code> method does nothing.
|
||||
* This is <code>FinishableOutputStream</code> instead
|
||||
* of <code>OutputStream</code> solely because it allows
|
||||
* using this as the output stream for a chain of raw filters.
|
||||
*/
|
||||
class CountingOutputStream extends FinishableOutputStream {
|
||||
|
||||
private final OutputStream out;
|
||||
private long size = 0;
|
||||
|
||||
public CountingOutputStream(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
out.write(b);
|
||||
if (size >= 0)
|
||||
++size;
|
||||
}
|
||||
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
out.write(b, off, len);
|
||||
if (size >= 0)
|
||||
size += len;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* DeltaCoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
abstract class DeltaCoder implements FilterCoder {
|
||||
|
||||
public static final long FILTER_ID = 0x03;
|
||||
|
||||
public boolean changesSize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean nonLastOK() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean lastOK() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* DeltaDecoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
class DeltaDecoder extends DeltaCoder implements FilterDecoder {
|
||||
|
||||
private final int distance;
|
||||
|
||||
DeltaDecoder(byte[] props) throws UnsupportedOptionsException {
|
||||
if (props.length != 1)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported Delta filter properties");
|
||||
|
||||
distance = (props[0] & 0xFF) + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemoryUsage() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new DeltaInputStream(in, distance);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* DeltaEncoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
class DeltaEncoder extends DeltaCoder implements FilterEncoder {
|
||||
|
||||
private final DeltaOptions options;
|
||||
private final byte[] props = new byte[1];
|
||||
|
||||
DeltaEncoder(DeltaOptions options) {
|
||||
props[0] = (byte) (options.getDistance() - 1);
|
||||
this.options = (DeltaOptions) options.clone();
|
||||
}
|
||||
|
||||
public long getFilterID() {
|
||||
return FILTER_ID;
|
||||
}
|
||||
|
||||
public byte[] getFilterProps() {
|
||||
return props;
|
||||
}
|
||||
|
||||
public boolean supportsFlushing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return options.getOutputStream(out);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* DeltaInputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.delta.DeltaDecoder;
|
||||
|
||||
/**
|
||||
* Decodes raw Delta-filtered data (no XZ headers).
|
||||
* <p>
|
||||
* The delta filter doesn't change the size of the data and thus it cannot have
|
||||
* an end-of-payload marker. It will simply decode until its input stream
|
||||
* indicates end of input.
|
||||
*/
|
||||
public class DeltaInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Smallest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MIN = 1;
|
||||
|
||||
/**
|
||||
* Largest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MAX = 256;
|
||||
|
||||
private InputStream in;
|
||||
private final DeltaDecoder delta;
|
||||
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
/**
|
||||
* Creates a new Delta decoder with the given delta calculation distance.
|
||||
*
|
||||
* @param in input stream from which Delta filtered data is read
|
||||
*
|
||||
* @param distance delta calculation distance, must be in the range
|
||||
* [<code>DISTANCE_MIN</code>, <code>DISTANCE_MAX</code>]
|
||||
*/
|
||||
public DeltaInputStream(InputStream in, int distance) {
|
||||
// Check for null because otherwise null isn't detect
|
||||
// in this constructor.
|
||||
if (in == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.in = in;
|
||||
this.delta = new DeltaDecoder(distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the next byte from this input stream.
|
||||
*
|
||||
* @return the next decoded byte, or <code>-1</code> to indicate the end of
|
||||
* input on the input stream <code>in</code>
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode into an array of bytes.
|
||||
* <p>
|
||||
* This calls <code>in.read(buf, off, len)</code> and defilters the returned
|
||||
* data.
|
||||
*
|
||||
* @param buf target buffer for decoded data
|
||||
* @param off start offset in <code>buf</code>
|
||||
* @param len maximum number of bytes to read
|
||||
*
|
||||
* @return number of bytes read, or <code>-1</code> to indicate the end of
|
||||
* the input stream <code>in</code>
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws IOException may be thrown by underlaying input stream
|
||||
* <code>in</code>
|
||||
*/
|
||||
@Override
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
int size;
|
||||
try {
|
||||
size = in.read(buf, off, len);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (size == -1)
|
||||
return -1;
|
||||
|
||||
delta.decode(buf, off, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>in.available()</code>.
|
||||
*
|
||||
* @return the value returned by <code>in.available()</code>
|
||||
*/
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return in.available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and calls <code>in.close()</code>. If the stream was
|
||||
* already closed, this does nothing.
|
||||
*
|
||||
* @throws IOException if thrown by <code>in.close()</code>
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* DeltaOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Delta filter options. The Delta filter can be used only as a non-last filter
|
||||
* in the chain, for example Delta + LZMA2.
|
||||
* <p>
|
||||
* Currently only simple byte-wise delta is supported. The only option is the
|
||||
* delta distance, which you should set to match your data. It's not possible to
|
||||
* provide a generic default value for it.
|
||||
* <p>
|
||||
* For example, with distance = 2 and eight-byte input A1 B1 A2 B3 A3 B5 A4 B7,
|
||||
* the output will be A1 B1 01 02 01 02 01 02.
|
||||
* <p>
|
||||
* The Delta filter can be good with uncompressed bitmap images. It can also
|
||||
* help with PCM audio, although special-purpose compressors like FLAC will give
|
||||
* much smaller result at much better compression speed.
|
||||
*/
|
||||
public class DeltaOptions extends FilterOptions {
|
||||
|
||||
/**
|
||||
* Smallest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MIN = 1;
|
||||
|
||||
/**
|
||||
* Largest supported delta calculation distance.
|
||||
*/
|
||||
public static final int DISTANCE_MAX = 256;
|
||||
|
||||
private int distance = DISTANCE_MIN;
|
||||
|
||||
/**
|
||||
* Creates new Delta options and sets the delta distance to 1 byte.
|
||||
*/
|
||||
public DeltaOptions() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new Delta options and sets the distance to the given value.
|
||||
*/
|
||||
public DeltaOptions(int distance) throws UnsupportedOptionsException {
|
||||
setDistance(distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delta distance in bytes. The new distance must be in the range
|
||||
* [DISTANCE_MIN, DISTANCE_MAX].
|
||||
*/
|
||||
public void setDistance(int distance) throws UnsupportedOptionsException {
|
||||
if (distance < DISTANCE_MIN || distance > DISTANCE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Delta distance must be in the range [" + DISTANCE_MIN
|
||||
+ ", " + DISTANCE_MAX + "]: " + distance);
|
||||
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the delta distance.
|
||||
*/
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEncoderMemoryUsage() {
|
||||
return DeltaOutputStream.getMemoryUsage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new DeltaOutputStream(out, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecoderMemoryUsage() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new DeltaInputStream(in, distance);
|
||||
}
|
||||
|
||||
@Override
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new DeltaEncoder(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
assert false;
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* DeltaOutputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.delta.DeltaEncoder;
|
||||
|
||||
class DeltaOutputStream extends FinishableOutputStream {
|
||||
|
||||
private static final int FILTER_BUF_SIZE = 4096;
|
||||
|
||||
private FinishableOutputStream out;
|
||||
private final DeltaEncoder delta;
|
||||
private final byte[] filterBuf = new byte[FILTER_BUF_SIZE];
|
||||
|
||||
private boolean finished = false;
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
static int getMemoryUsage() {
|
||||
return 1 + FILTER_BUF_SIZE / 1024;
|
||||
}
|
||||
|
||||
DeltaOutputStream(FinishableOutputStream out, DeltaOptions options) {
|
||||
this.out = out;
|
||||
delta = new DeltaEncoder(options.getDistance());
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
tempBuf[0] = (byte) b;
|
||||
write(tempBuf, 0, 1);
|
||||
}
|
||||
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished");
|
||||
|
||||
try {
|
||||
while (len > FILTER_BUF_SIZE) {
|
||||
delta.encode(buf, off, FILTER_BUF_SIZE, filterBuf);
|
||||
out.write(filterBuf);
|
||||
off += FILTER_BUF_SIZE;
|
||||
len -= FILTER_BUF_SIZE;
|
||||
}
|
||||
|
||||
delta.encode(buf, off, len, filterBuf);
|
||||
out.write(filterBuf, 0, len);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
if (!finished) {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
try {
|
||||
out.finish();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
if (exception == null)
|
||||
exception = e;
|
||||
}
|
||||
|
||||
out = null;
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* FilterCoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
interface FilterCoder {
|
||||
|
||||
boolean changesSize();
|
||||
|
||||
boolean nonLastOK();
|
||||
|
||||
boolean lastOK();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* FilterDecoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
interface FilterDecoder extends FilterCoder {
|
||||
|
||||
int getMemoryUsage();
|
||||
|
||||
InputStream getInputStream(InputStream in);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* FilterEncoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
interface FilterEncoder extends FilterCoder {
|
||||
|
||||
long getFilterID();
|
||||
|
||||
byte[] getFilterProps();
|
||||
|
||||
boolean supportsFlushing();
|
||||
|
||||
FinishableOutputStream getOutputStream(FinishableOutputStream out);
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* FilterOptions
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Base class for filter-specific options classes.
|
||||
*/
|
||||
public abstract class FilterOptions implements Cloneable {
|
||||
|
||||
/**
|
||||
* Gets how much memory the encoder will need with the given filter chain.
|
||||
* This function simply calls <code>getEncoderMemoryUsage()</code> for every
|
||||
* filter in the array and returns the sum of the returned values.
|
||||
*/
|
||||
public static int getEncoderMemoryUsage(FilterOptions[] options) {
|
||||
int m = 0;
|
||||
|
||||
for (int i = 0; i < options.length; ++i)
|
||||
m += options[i].getEncoderMemoryUsage();
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets how much memory the decoder will need with the given filter chain.
|
||||
* This function simply calls <code>getDecoderMemoryUsage()</code> for every
|
||||
* filter in the array and returns the sum of the returned values.
|
||||
*/
|
||||
public static int getDecoderMemoryUsage(FilterOptions[] options) {
|
||||
int m = 0;
|
||||
|
||||
for (int i = 0; i < options.length; ++i)
|
||||
m += options[i].getDecoderMemoryUsage();
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets how much memory the encoder will need with these options.
|
||||
*/
|
||||
public abstract int getEncoderMemoryUsage();
|
||||
|
||||
/**
|
||||
* Gets a raw (no XZ headers) encoder output stream using these options. Raw
|
||||
* streams are an advanced feature. In most cases you want to store the
|
||||
* compressed data in the .xz container format instead of using a raw
|
||||
* stream. To use this filter in a .xz file, pass this object to
|
||||
* XZOutputStream.
|
||||
*/
|
||||
public abstract FinishableOutputStream getOutputStream(
|
||||
FinishableOutputStream out);
|
||||
|
||||
/**
|
||||
* Gets how much memory the decoder will need to decompress the data that
|
||||
* was encoded with these options.
|
||||
*/
|
||||
public abstract int getDecoderMemoryUsage();
|
||||
|
||||
/**
|
||||
* Gets a raw (no XZ headers) decoder input stream using these options.
|
||||
*/
|
||||
public abstract InputStream getInputStream(InputStream in)
|
||||
throws IOException;
|
||||
|
||||
abstract FilterEncoder getFilterEncoder();
|
||||
|
||||
FilterOptions() {
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* FinishableOutputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Output stream that supports finishing without closing
|
||||
* the underlying stream.
|
||||
*/
|
||||
public abstract class FinishableOutputStream extends OutputStream {
|
||||
|
||||
/**
|
||||
* Finish the stream without closing the underlying stream.
|
||||
* No more data may be written to the stream after finishing.
|
||||
* <p>
|
||||
* The <code>finish</code> method of <code>FinishableOutputStream</code>
|
||||
* does nothing. Subclasses should override it if they need finishing
|
||||
* support, which is the case, for example, with compressors.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void finish() throws IOException {
|
||||
}
|
||||
;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* FinishableWrapperOutputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Wraps an output stream to a finishable output stream for use with
|
||||
* raw encoders. This is not needed for XZ compression and thus most
|
||||
* people will never need this.
|
||||
*/
|
||||
public class FinishableWrapperOutputStream extends FinishableOutputStream {
|
||||
|
||||
/**
|
||||
* The {@link java.io.OutputStream OutputStream} that has been
|
||||
* wrapped into a FinishableWrapperOutputStream.
|
||||
*/
|
||||
protected OutputStream out;
|
||||
|
||||
/**
|
||||
* Creates a new output stream which support finishing.
|
||||
* The <code>finish()</code> method will do nothing.
|
||||
*/
|
||||
public FinishableWrapperOutputStream(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link java.io.OutputStream#write(int) out.write(b)}.
|
||||
*/
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
out.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link java.io.OutputStream#write(byte[]) out.write(buf)}.
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] buf) throws IOException {
|
||||
out.write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link java.io.OutputStream#write(byte[],int,int)
|
||||
* out.write(buf, off, len)}.
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
out.write(buf, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link java.io.OutputStream#flush() out.flush()}.
|
||||
*/
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link java.io.OutputStream#close() out.close()}.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* IA64Options
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.IA64;
|
||||
|
||||
/**
|
||||
* BCJ filter for Itanium (IA-64) instructions.
|
||||
*/
|
||||
public class IA64Options extends BCJOptions {
|
||||
|
||||
private static final int ALIGNMENT = 16;
|
||||
|
||||
public IA64Options() {
|
||||
super(ALIGNMENT);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new SimpleOutputStream(out, new IA64(true, startOffset));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new SimpleInputStream(in, new IA64(false, startOffset));
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new BCJEncoder(this, BCJCoder.IA64_FILTER_ID);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* IndexIndicatorException
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
class IndexIndicatorException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* LZMA2Coder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
abstract class LZMA2Coder implements FilterCoder {
|
||||
|
||||
public static final long FILTER_ID = 0x21;
|
||||
|
||||
public boolean changesSize() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean nonLastOK() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean lastOK() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* LZMA2Decoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
class LZMA2Decoder extends LZMA2Coder implements FilterDecoder {
|
||||
|
||||
private int dictSize;
|
||||
|
||||
LZMA2Decoder(byte[] props) throws UnsupportedOptionsException {
|
||||
// Up to 1.5 GiB dictionary is supported. The bigger ones
|
||||
// are too big for int.
|
||||
if (props.length != 1 || (props[0] & 0xFF) > 37)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported LZMA2 properties");
|
||||
|
||||
dictSize = 2 | (props[0] & 1);
|
||||
dictSize <<= (props[0] >>> 1) + 11;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemoryUsage() {
|
||||
return LZMA2InputStream.getMemoryUsage(dictSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new LZMA2InputStream(in, dictSize);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* LZMA2Encoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import org.tukaani.xz.lzma.LZMAEncoder;
|
||||
|
||||
class LZMA2Encoder extends LZMA2Coder implements FilterEncoder {
|
||||
|
||||
private final LZMA2Options options;
|
||||
private final byte[] props = new byte[1];
|
||||
|
||||
LZMA2Encoder(LZMA2Options options) {
|
||||
if (options.getPresetDict() != null)
|
||||
throw new IllegalArgumentException(
|
||||
"XZ doesn't support a preset dictionary for now");
|
||||
|
||||
if (options.getMode() == LZMA2Options.MODE_UNCOMPRESSED)
|
||||
props[0] = (byte) 0;
|
||||
else {
|
||||
int d = Math.max(options.getDictSize(),
|
||||
LZMA2Options.DICT_SIZE_MIN);
|
||||
props[0] = (byte) (LZMAEncoder.getDistSlot(d - 1) - 23);
|
||||
}
|
||||
|
||||
// Make a private copy so that the caller is free to change its copy.
|
||||
this.options = (LZMA2Options) options.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFilterID() {
|
||||
return FILTER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getFilterProps() {
|
||||
return props;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFlushing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return options.getOutputStream(out);
|
||||
}
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* LZMA2InputStream
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZDecoder;
|
||||
import org.tukaani.xz.rangecoder.RangeDecoderFromBuffer;
|
||||
import org.tukaani.xz.lzma.LZMADecoder;
|
||||
|
||||
/**
|
||||
* Decompresses a raw LZMA2 stream (no XZ headers).
|
||||
*/
|
||||
public class LZMA2InputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Smallest valid LZMA2 dictionary size.
|
||||
* <p>
|
||||
* Very tiny dictionaries would be a performance problem, so the minimum is
|
||||
* 4 KiB.
|
||||
*/
|
||||
public static final int DICT_SIZE_MIN = 4096;
|
||||
|
||||
/**
|
||||
* Largest dictionary size supported by this implementation.
|
||||
* <p>
|
||||
* The LZMA2 algorithm allows dictionaries up to one byte less than 4 GiB.
|
||||
* This implementation supports only 16 bytes less than 2 GiB for raw LZMA2
|
||||
* streams, and for .xz files the maximum is 1.5 GiB. This limitation is due
|
||||
* to Java using signed 32-bit integers for array indexing. The limitation
|
||||
* shouldn't matter much in practice since so huge dictionaries are not
|
||||
* normally used.
|
||||
*/
|
||||
public static final int DICT_SIZE_MAX = Integer.MAX_VALUE & ~15;
|
||||
|
||||
private static final int COMPRESSED_SIZE_MAX = 1 << 16;
|
||||
|
||||
private DataInputStream in;
|
||||
|
||||
private final LZDecoder lz;
|
||||
private final RangeDecoderFromBuffer rc
|
||||
= new RangeDecoderFromBuffer(COMPRESSED_SIZE_MAX);
|
||||
private LZMADecoder lzma;
|
||||
|
||||
private int uncompressedSize = 0;
|
||||
private boolean isLZMAChunk;
|
||||
|
||||
private boolean needDictReset = true;
|
||||
private boolean needProps = true;
|
||||
private boolean endReached = false;
|
||||
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
/**
|
||||
* Gets approximate decompressor memory requirements as kibibytes for the
|
||||
* given dictionary size.
|
||||
*
|
||||
* @param dictSize LZMA2 dictionary size as bytes, must be in the range
|
||||
* [<code>DICT_SIZE_MIN</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @return approximate memory requirements as kibibytes (KiB)
|
||||
*/
|
||||
public static int getMemoryUsage(int dictSize) {
|
||||
// The base state is around 30-40 KiB (probabilities etc.),
|
||||
// range decoder needs COMPRESSED_SIZE_MAX bytes for buffering,
|
||||
// and LZ decoder needs a dictionary buffer.
|
||||
return 40 + COMPRESSED_SIZE_MAX / 1024 + getDictSize(dictSize) / 1024;
|
||||
}
|
||||
|
||||
private static int getDictSize(int dictSize) {
|
||||
if (dictSize < DICT_SIZE_MIN || dictSize > DICT_SIZE_MAX)
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported dictionary size " + dictSize);
|
||||
|
||||
// Round dictionary size upward to a multiple of 16. This way LZMA
|
||||
// can use LZDecoder.getPos() for calculating LZMA's posMask.
|
||||
// Note that this check is needed only for raw LZMA2 streams; it is
|
||||
// redundant with .xz.
|
||||
return (dictSize + 15) & ~15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA2 data from
|
||||
* <code>in</code>.
|
||||
* <p>
|
||||
* The caller needs to know the dictionary size used when compressing; the
|
||||
* dictionary size isn't stored as part of a raw LZMA2 stream.
|
||||
* <p>
|
||||
* Specifying a too small dictionary size will prevent decompressing the
|
||||
* stream. Specifying a too big dictionary is waste of memory but
|
||||
* decompression will work.
|
||||
* <p>
|
||||
* There is no need to specify a dictionary bigger than the uncompressed
|
||||
* size of the data even if a bigger dictionary was used when compressing.
|
||||
* If you know the uncompressed size of the data, this might allow saving
|
||||
* some memory.
|
||||
*
|
||||
* @param in input stream from which LZMA2-compressed data is read
|
||||
*
|
||||
* @param dictSize LZMA2 dictionary size as bytes, must be in the range
|
||||
* [<code>DICT_SIZE_MIN</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*/
|
||||
public LZMA2InputStream(InputStream in, int dictSize) {
|
||||
this(in, dictSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new LZMA2 decompressor using a preset dictionary.
|
||||
* <p>
|
||||
* This is like <code>LZMA2InputStream(InputStream, int)</code> except that
|
||||
* the dictionary may be initialized using a preset dictionary. If a preset
|
||||
* dictionary was used when compressing the data, the same preset dictionary
|
||||
* must be provided when decompressing.
|
||||
*
|
||||
* @param in input stream from which LZMA2-compressed data is read
|
||||
*
|
||||
* @param dictSize LZMA2 dictionary size as bytes, must be in the range
|
||||
* [<code>DICT_SIZE_MIN</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param presetDict preset dictionary or <code>null</code> to use no preset
|
||||
* dictionary
|
||||
*/
|
||||
public LZMA2InputStream(InputStream in, int dictSize, byte[] presetDict) {
|
||||
// Check for null because otherwise null isn't detect
|
||||
// in this constructor.
|
||||
if (in == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.in = new DataInputStream(in);
|
||||
this.lz = new LZDecoder(getDictSize(dictSize), presetDict);
|
||||
|
||||
if (presetDict != null && presetDict.length > 0)
|
||||
needDictReset = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses the next byte from this input stream.
|
||||
* <p>
|
||||
* Reading lots of data with <code>read()</code> from this input stream may
|
||||
* be inefficient. Wrap it in <code>java.io.BufferedInputStream</code> if
|
||||
* you need to read lots of data one byte at a time.
|
||||
*
|
||||
* @return the next decompressed byte, or <code>-1</code> to indicate the
|
||||
* end of the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses into an array of bytes.
|
||||
* <p>
|
||||
* If <code>len</code> is zero, no bytes are read and <code>0</code> is
|
||||
* returned. Otherwise this will block until <code>len</code> bytes have
|
||||
* been decompressed, the end of the LZMA2 stream is reached, or an
|
||||
* exception is thrown.
|
||||
*
|
||||
* @param buf target buffer for uncompressed data
|
||||
* @param off start offset in <code>buf</code>
|
||||
* @param len maximum number of uncompressed bytes to read
|
||||
*
|
||||
* @return number of bytes read, or <code>-1</code> to indicate the end of
|
||||
* the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (endReached)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
int size = 0;
|
||||
|
||||
while (len > 0) {
|
||||
if (uncompressedSize == 0) {
|
||||
decodeChunkHeader();
|
||||
if (endReached)
|
||||
return size == 0 ? -1 : size;
|
||||
}
|
||||
|
||||
int copySizeMax = Math.min(uncompressedSize, len);
|
||||
|
||||
if (!isLZMAChunk)
|
||||
lz.copyUncompressed(in, copySizeMax);
|
||||
else {
|
||||
lz.setLimit(copySizeMax);
|
||||
lzma.decode();
|
||||
if (!rc.isInBufferOK())
|
||||
throw new CorruptedInputException();
|
||||
}
|
||||
|
||||
int copiedSize = lz.flush(buf, off);
|
||||
off += copiedSize;
|
||||
len -= copiedSize;
|
||||
size += copiedSize;
|
||||
uncompressedSize -= copiedSize;
|
||||
|
||||
if (uncompressedSize == 0)
|
||||
if (!rc.isFinished() || lz.hasPending())
|
||||
throw new CorruptedInputException();
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeChunkHeader() throws IOException {
|
||||
int control = in.readUnsignedByte();
|
||||
|
||||
if (control == 0x00) {
|
||||
endReached = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (control >= 0xE0 || control == 0x01) {
|
||||
needProps = true;
|
||||
needDictReset = false;
|
||||
lz.reset();
|
||||
} else if (needDictReset)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
if (control >= 0x80) {
|
||||
isLZMAChunk = true;
|
||||
|
||||
uncompressedSize = (control & 0x1F) << 16;
|
||||
uncompressedSize += in.readUnsignedShort() + 1;
|
||||
|
||||
int compressedSize = in.readUnsignedShort() + 1;
|
||||
|
||||
if (control >= 0xC0) {
|
||||
needProps = false;
|
||||
decodeProps();
|
||||
|
||||
} else if (needProps)
|
||||
throw new CorruptedInputException();
|
||||
else if (control >= 0xA0)
|
||||
lzma.reset();
|
||||
|
||||
rc.prepareInputBuffer(in, compressedSize);
|
||||
|
||||
} else if (control > 0x02)
|
||||
throw new CorruptedInputException();
|
||||
else {
|
||||
isLZMAChunk = false;
|
||||
uncompressedSize = in.readUnsignedShort() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeProps() throws IOException {
|
||||
int props = in.readUnsignedByte();
|
||||
|
||||
if (props > (4 * 5 + 4) * 9 + 8)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
int pb = props / (9 * 5);
|
||||
props -= pb * 9 * 5;
|
||||
int lp = props / 9;
|
||||
int lc = props - lp * 9;
|
||||
|
||||
if (lc + lp > 4)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
lzma = new LZMADecoder(lz, rc, lc, lp, pb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of uncompressed bytes that can be read without
|
||||
* blocking. The value is returned with an assumption that the compressed
|
||||
* input data will be valid. If the compressed data is corrupt,
|
||||
* <code>CorruptedInputException</code> may get thrown before the number of
|
||||
* bytes claimed to be available have been read from this input stream.
|
||||
* <p>
|
||||
* In LZMA2InputStream, the return value will be non-zero when the
|
||||
* decompressor is in the middle of an LZMA2 chunk. The return value will
|
||||
* then be the number of uncompressed bytes remaining from that chunk.
|
||||
*
|
||||
* @return the number of uncompressed bytes that can be read without
|
||||
* blocking
|
||||
*/
|
||||
public int available() throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
return uncompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and calls <code>in.close()</code>. If the stream was
|
||||
* already closed, this does nothing.
|
||||
*
|
||||
* @throws IOException if thrown by <code>in.close()</code>
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,562 +0,0 @@
|
||||
/*
|
||||
* LZMA2Options
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZEncoder;
|
||||
import org.tukaani.xz.lzma.LZMAEncoder;
|
||||
|
||||
/**
|
||||
* LZMA2 compression options.
|
||||
* <p>
|
||||
* While this allows setting the LZMA2 compression options in detail, often you
|
||||
* only need <code>LZMA2Options()</code> or <code>LZMA2Options(int)</code>.
|
||||
*/
|
||||
public class LZMA2Options extends FilterOptions {
|
||||
|
||||
/**
|
||||
* Minimum valid compression preset level is 0.
|
||||
*/
|
||||
public static final int PRESET_MIN = 0;
|
||||
|
||||
/**
|
||||
* Maximum valid compression preset level is 9.
|
||||
*/
|
||||
public static final int PRESET_MAX = 9;
|
||||
|
||||
/**
|
||||
* Default compression preset level is 6.
|
||||
*/
|
||||
public static final int PRESET_DEFAULT = 6;
|
||||
|
||||
/**
|
||||
* Minimum dictionary size is 4 KiB.
|
||||
*/
|
||||
public static final int DICT_SIZE_MIN = 4096;
|
||||
|
||||
/**
|
||||
* Maximum dictionary size for compression is 768 MiB.
|
||||
* <p>
|
||||
* The decompressor supports bigger dictionaries, up to almost 2 GiB. With
|
||||
* HC4 the encoder would support dictionaries bigger than 768 MiB. The 768
|
||||
* MiB limit comes from the current implementation of BT4 where we would
|
||||
* otherwise hit the limits of signed ints in array indexing.
|
||||
* <p>
|
||||
* If you really need bigger dictionary for decompression, use
|
||||
* {@link LZMA2InputStream} directly.
|
||||
*/
|
||||
public static final int DICT_SIZE_MAX = 768 << 20;
|
||||
|
||||
/**
|
||||
* The default dictionary size is 8 MiB.
|
||||
*/
|
||||
public static final int DICT_SIZE_DEFAULT = 8 << 20;
|
||||
|
||||
/**
|
||||
* Maximum value for lc + lp is 4.
|
||||
*/
|
||||
public static final int LC_LP_MAX = 4;
|
||||
|
||||
/**
|
||||
* The default number of literal context bits is 3.
|
||||
*/
|
||||
public static final int LC_DEFAULT = 3;
|
||||
|
||||
/**
|
||||
* The default number of literal position bits is 0.
|
||||
*/
|
||||
public static final int LP_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Maximum value for pb is 4.
|
||||
*/
|
||||
public static final int PB_MAX = 4;
|
||||
|
||||
/**
|
||||
* The default number of position bits is 2.
|
||||
*/
|
||||
public static final int PB_DEFAULT = 2;
|
||||
|
||||
/**
|
||||
* Compression mode: uncompressed. The data is wrapped into a LZMA2 stream
|
||||
* without compression.
|
||||
*/
|
||||
public static final int MODE_UNCOMPRESSED = 0;
|
||||
|
||||
/**
|
||||
* Compression mode: fast. This is usually combined with a hash chain match
|
||||
* finder.
|
||||
*/
|
||||
public static final int MODE_FAST = LZMAEncoder.MODE_FAST;
|
||||
|
||||
/**
|
||||
* Compression mode: normal. This is usually combined with a binary tree
|
||||
* match finder.
|
||||
*/
|
||||
public static final int MODE_NORMAL = LZMAEncoder.MODE_NORMAL;
|
||||
|
||||
/**
|
||||
* Minimum value for <code>niceLen</code> is 8.
|
||||
*/
|
||||
public static final int NICE_LEN_MIN = 8;
|
||||
|
||||
/**
|
||||
* Maximum value for <code>niceLen</code> is 273.
|
||||
*/
|
||||
public static final int NICE_LEN_MAX = 273;
|
||||
|
||||
/**
|
||||
* Match finder: Hash Chain 2-3-4
|
||||
*/
|
||||
public static final int MF_HC4 = LZEncoder.MF_HC4;
|
||||
|
||||
/**
|
||||
* Match finder: Binary tree 2-3-4
|
||||
*/
|
||||
public static final int MF_BT4 = LZEncoder.MF_BT4;
|
||||
|
||||
private static final int[] presetToDictSize = {
|
||||
1 << 18, 1 << 20, 1 << 21, 1 << 22, 1 << 22,
|
||||
1 << 23, 1 << 23, 1 << 24, 1 << 25, 1 << 26 };
|
||||
|
||||
private static final int[] presetToDepthLimit = { 4, 8, 24, 48 };
|
||||
|
||||
private int dictSize;
|
||||
private byte[] presetDict = null;
|
||||
private int lc;
|
||||
private int lp;
|
||||
private int pb;
|
||||
private int mode;
|
||||
private int niceLen;
|
||||
private int mf;
|
||||
private int depthLimit;
|
||||
|
||||
/**
|
||||
* Creates new LZMA2 options and sets them to the default values. This is
|
||||
* equivalent to <code>LZMA2Options(PRESET_DEFAULT)</code>.
|
||||
*/
|
||||
public LZMA2Options() {
|
||||
try {
|
||||
setPreset(PRESET_DEFAULT);
|
||||
} catch (UnsupportedOptionsException e) {
|
||||
assert false;
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new LZMA2 options and sets them to the given preset.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>preset</code> is not supported
|
||||
*/
|
||||
public LZMA2Options(int preset) throws UnsupportedOptionsException {
|
||||
setPreset(preset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new LZMA2 options and sets them to the given custom values.
|
||||
*
|
||||
* @throws UnsupportedOptionsException unsupported options were specified
|
||||
*/
|
||||
public LZMA2Options(int dictSize, int lc, int lp, int pb, int mode,
|
||||
int niceLen, int mf, int depthLimit)
|
||||
throws UnsupportedOptionsException {
|
||||
setDictSize(dictSize);
|
||||
setLcLp(lc, lp);
|
||||
setPb(pb);
|
||||
setMode(mode);
|
||||
setNiceLen(niceLen);
|
||||
setMatchFinder(mf);
|
||||
setDepthLimit(depthLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression options to the given preset.
|
||||
* <p>
|
||||
* The presets 0-3 are fast presets with medium compression. The presets 4-6
|
||||
* are fairly slow presets with high compression. The default preset
|
||||
* (<code>PRESET_DEFAULT</code>) is 6.
|
||||
* <p>
|
||||
* The presets 7-9 are like the preset 6 but use bigger dictionaries and
|
||||
* have higher compressor and decompressor memory requirements. Unless the
|
||||
* uncompressed size of the file exceeds 8 MiB, 16 MiB, or
|
||||
* 32 MiB, it is waste of memory to use the presets 7, 8, or 9,
|
||||
* respectively.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>preset</code> is not supported
|
||||
*/
|
||||
public void setPreset(int preset) throws UnsupportedOptionsException {
|
||||
if (preset < 0 || preset > 9)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported preset: " + preset);
|
||||
|
||||
lc = LC_DEFAULT;
|
||||
lp = LP_DEFAULT;
|
||||
pb = PB_DEFAULT;
|
||||
dictSize = presetToDictSize[preset];
|
||||
|
||||
if (preset <= 3) {
|
||||
mode = MODE_FAST;
|
||||
mf = MF_HC4;
|
||||
niceLen = preset <= 1 ? 128 : NICE_LEN_MAX;
|
||||
depthLimit = presetToDepthLimit[preset];
|
||||
} else {
|
||||
mode = MODE_NORMAL;
|
||||
mf = MF_BT4;
|
||||
niceLen = (preset == 4) ? 16 : (preset == 5) ? 32 : 64;
|
||||
depthLimit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dictionary size in bytes.
|
||||
* <p>
|
||||
* The dictionary (or history buffer) holds the most recently seen
|
||||
* uncompressed data. Bigger dictionary usually means better compression.
|
||||
* However, using a dictioanary bigger than the size of the uncompressed
|
||||
* data is waste of memory.
|
||||
* <p>
|
||||
* Any value in the range [DICT_SIZE_MIN, DICT_SIZE_MAX] is valid, but sizes
|
||||
* of 2^n and 2^n + 2^(n-1) bytes are somewhat recommended.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>dictSize</code> is not
|
||||
* supported
|
||||
*/
|
||||
public void setDictSize(int dictSize) throws UnsupportedOptionsException {
|
||||
if (dictSize < DICT_SIZE_MIN)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA2 dictionary size must be at least 4 KiB: "
|
||||
+ dictSize + " B");
|
||||
|
||||
if (dictSize > DICT_SIZE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA2 dictionary size must not exceed "
|
||||
+ (DICT_SIZE_MAX >> 20) + " MiB: " + dictSize + " B");
|
||||
|
||||
this.dictSize = dictSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dictionary size in bytes.
|
||||
*/
|
||||
public int getDictSize() {
|
||||
return dictSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a preset dictionary. Use null to disable the use of a preset
|
||||
* dictionary. By default there is no preset dictionary.
|
||||
* <p>
|
||||
* <b>The .xz format doesn't support a preset dictionary for now. Do not set
|
||||
* a preset dictionary unless you use raw LZMA2.</b>
|
||||
* <p>
|
||||
* Preset dictionary can be useful when compressing many similar, relatively
|
||||
* small chunks of data independently from each other. A preset dictionary
|
||||
* should contain typical strings that occur in the files being compressed.
|
||||
* The most probable strings should be near the end of the preset
|
||||
* dictionary. The preset dictionary used for compression is also needed for
|
||||
* decompression.
|
||||
*/
|
||||
public void setPresetDict(byte[] presetDict) {
|
||||
this.presetDict = presetDict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preset dictionary.
|
||||
*/
|
||||
public byte[] getPresetDict() {
|
||||
return presetDict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of literal context bits and literal position bits.
|
||||
* <p>
|
||||
* The sum of <code>lc</code> and <code>lp</code> is limited to 4. Trying to
|
||||
* exceed it will throw an exception. This function lets you change both at
|
||||
* the same time.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>lc</code> and <code>lp</code>
|
||||
* are invalid
|
||||
*/
|
||||
public void setLcLp(int lc, int lp) throws UnsupportedOptionsException {
|
||||
if (lc < 0 || lp < 0 || lc > LC_LP_MAX || lp > LC_LP_MAX
|
||||
|| lc + lp > LC_LP_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"lc + lp must not exceed " + LC_LP_MAX + ": "
|
||||
+ lc + " + " + lp);
|
||||
|
||||
this.lc = lc;
|
||||
this.lp = lp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of literal context bits.
|
||||
* <p>
|
||||
* All bytes that cannot be encoded as matches are encoded as literals. That
|
||||
* is, literals are simply 8-bit bytes that are encoded one at a time.
|
||||
* <p>
|
||||
* The literal coding makes an assumption that the highest <code>lc</code>
|
||||
* bits of the previous uncompressed byte correlate with the next byte. For
|
||||
* example, in typical English text, an upper-case letter is often followed
|
||||
* by a lower-case letter, and a lower-case letter is usually followed by
|
||||
* another lower-case letter. In the US-ASCII character set, the highest
|
||||
* three bits are 010 for upper-case letters and 011 for lower-case letters.
|
||||
* When <code>lc</code> is at least 3, the literal coding can take advantage
|
||||
* of this property in the uncompressed data.
|
||||
* <p>
|
||||
* The default value (3) is usually good. If you want maximum compression,
|
||||
* try <code>setLc(4)</code>. Sometimes it helps a little, and sometimes it
|
||||
* makes compression worse. If it makes it worse, test for example
|
||||
* <code>setLc(2)</code> too.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>lc</code> is invalid, or the
|
||||
* sum of <code>lc</code> and <code>lp</code> exceed LC_LP_MAX
|
||||
*/
|
||||
public void setLc(int lc) throws UnsupportedOptionsException {
|
||||
setLcLp(lc, lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of literal position bits.
|
||||
* <p>
|
||||
* This affets what kind of alignment in the uncompressed data is assumed
|
||||
* when encoding literals. See {@link #setPb(int) setPb} for more
|
||||
* information about alignment.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>lp</code> is invalid, or the
|
||||
* sum of <code>lc</code> and <code>lp</code> exceed LC_LP_MAX
|
||||
*/
|
||||
public void setLp(int lp) throws UnsupportedOptionsException {
|
||||
setLcLp(lc, lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of literal context bits.
|
||||
*/
|
||||
public int getLc() {
|
||||
return lc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of literal position bits.
|
||||
*/
|
||||
public int getLp() {
|
||||
return lp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of position bits.
|
||||
* <p>
|
||||
* This affects what kind of alignment in the uncompressed data is assumed
|
||||
* in general. The default (2) means four-byte alignment (2^<code>pb</code>
|
||||
* = 2^2 = 4), which is often a good choice when there's no better guess.
|
||||
* <p>
|
||||
* When the alignment is known, setting the number of position bits
|
||||
* accordingly may reduce the file size a little. For example with text
|
||||
* files having one-byte alignment (US-ASCII, ISO-8859-*, UTF-8), using
|
||||
* <code>setPb(0)</code> can improve compression slightly. For UTF-16 text,
|
||||
* <code>setPb(1)</code> is a good choice. If the alignment is an odd number
|
||||
* like 3 bytes, <code>setPb(0)</code> might be the best choice.
|
||||
* <p>
|
||||
* Even though the assumed alignment can be adjusted with <code>setPb</code>
|
||||
* and <code>setLp</code>, LZMA2 still slightly favors 16-byte alignment. It
|
||||
* might be worth taking into account when designing file formats that are
|
||||
* likely to be often compressed with LZMA2.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>pb</code> is invalid
|
||||
*/
|
||||
public void setPb(int pb) throws UnsupportedOptionsException {
|
||||
if (pb < 0 || pb > PB_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"pb must not exceed " + PB_MAX + ": " + pb);
|
||||
|
||||
this.pb = pb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of position bits.
|
||||
*/
|
||||
public int getPb() {
|
||||
return pb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression mode.
|
||||
* <p>
|
||||
* This specifies the method to analyze the data produced by a match finder.
|
||||
* The default is <code>MODE_FAST</code> for presets 0-3 and
|
||||
* <code>MODE_NORMAL</code> for presets 4-9.
|
||||
* <p>
|
||||
* Usually <code>MODE_FAST</code> is used with Hash Chain match finders and
|
||||
* <code>MODE_NORMAL</code> with Binary Tree match finders. This is also
|
||||
* what the presets do.
|
||||
* <p>
|
||||
* The special mode <code>MODE_UNCOMPRESSED</code> doesn't try to compress
|
||||
* the data at all (and doesn't use a match finder) and will simply wrap it
|
||||
* in uncompressed LZMA2 chunks.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>mode</code> is not supported
|
||||
*/
|
||||
public void setMode(int mode) throws UnsupportedOptionsException {
|
||||
if (mode < MODE_UNCOMPRESSED || mode > MODE_NORMAL)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported compression mode: " + mode);
|
||||
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compression mode.
|
||||
*/
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nice length of matches. Once a match of at least
|
||||
* <code>niceLen</code> bytes is found, the algorithm stops looking for
|
||||
* better matches. Higher values tend to give better compression at the
|
||||
* expense of speed. The default depends on the preset.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>niceLen</code> is invalid
|
||||
*/
|
||||
public void setNiceLen(int niceLen) throws UnsupportedOptionsException {
|
||||
if (niceLen < NICE_LEN_MIN)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Minimum nice length of matches is "
|
||||
+ NICE_LEN_MIN + " bytes: " + niceLen);
|
||||
|
||||
if (niceLen > NICE_LEN_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Maximum nice length of matches is " + NICE_LEN_MAX
|
||||
+ ": " + niceLen);
|
||||
|
||||
this.niceLen = niceLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nice length of matches.
|
||||
*/
|
||||
public int getNiceLen() {
|
||||
return niceLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the match finder type.
|
||||
* <p>
|
||||
* Match finder has a major effect on compression speed, memory usage, and
|
||||
* compression ratio. Usually Hash Chain match finders are faster than
|
||||
* Binary Tree match finders. The default depends on the preset: 0-3 use
|
||||
* <code>MF_HC4</code> and 4-9 use <code>MF_BT4</code>.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>mf</code> is not supported
|
||||
*/
|
||||
public void setMatchFinder(int mf) throws UnsupportedOptionsException {
|
||||
if (mf != MF_HC4 && mf != MF_BT4)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported match finder: " + mf);
|
||||
|
||||
this.mf = mf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the match finder type.
|
||||
*/
|
||||
public int getMatchFinder() {
|
||||
return mf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the match finder search depth limit.
|
||||
* <p>
|
||||
* The default is a special value of <code>0</code> which indicates that the
|
||||
* depth limit should be automatically calculated by the selected match
|
||||
* finder from the nice length of matches.
|
||||
* <p>
|
||||
* Reasonable depth limit for Hash Chain match finders is 4-100 and 16-1000
|
||||
* for Binary Tree match finders. Using very high values can make the
|
||||
* compressor extremely slow with some files. Avoid settings higher than
|
||||
* 1000 unless you are prepared to interrupt the compression in case it is
|
||||
* taking far too long.
|
||||
*
|
||||
* @throws UnsupportedOptionsException <code>depthLimit</code> is invalid
|
||||
*/
|
||||
public void setDepthLimit(int depthLimit)
|
||||
throws UnsupportedOptionsException {
|
||||
if (depthLimit < 0)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Depth limit cannot be negative: " + depthLimit);
|
||||
|
||||
this.depthLimit = depthLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the match finder search depth limit.
|
||||
*/
|
||||
public int getDepthLimit() {
|
||||
return depthLimit;
|
||||
}
|
||||
|
||||
public int getEncoderMemoryUsage() {
|
||||
return (mode == MODE_UNCOMPRESSED)
|
||||
? UncompressedLZMA2OutputStream.getMemoryUsage()
|
||||
: LZMA2OutputStream.getMemoryUsage(this);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
if (mode == MODE_UNCOMPRESSED)
|
||||
return new UncompressedLZMA2OutputStream(out);
|
||||
|
||||
return new LZMA2OutputStream(out, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets how much memory the LZMA2 decoder will need to decompress the data
|
||||
* that was encoded with these options and stored in a .xz file.
|
||||
* <p>
|
||||
* The returned value may bigger than the value returned by a direct call to
|
||||
* {@link LZMA2InputStream#getMemoryUsage(int)} if the dictionary size is
|
||||
* not 2^n or 2^n + 2^(n-1) bytes. This is because the .xz headers
|
||||
* store the dictionary size in such a format and other values are rounded
|
||||
* up to the next such value. Such rounding is harmess except it might waste
|
||||
* some memory if an unsual dictionary size is used.
|
||||
* <p>
|
||||
* If you use raw LZMA2 streams and unusual dictioanary size, call
|
||||
* {@link LZMA2InputStream#getMemoryUsage} directly to get raw decoder
|
||||
* memory requirements.
|
||||
*/
|
||||
public int getDecoderMemoryUsage() {
|
||||
// Round the dictionary size up to the next 2^n or 2^n + 2^(n-1).
|
||||
int d = dictSize - 1;
|
||||
d |= d >>> 2;
|
||||
d |= d >>> 3;
|
||||
d |= d >>> 4;
|
||||
d |= d >>> 8;
|
||||
d |= d >>> 16;
|
||||
return LZMA2InputStream.getMemoryUsage(d + 1);
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) throws IOException {
|
||||
return new LZMA2InputStream(in, dictSize);
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new LZMA2Encoder(this);
|
||||
}
|
||||
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
assert false;
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
* LZMA2OutputStream
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZEncoder;
|
||||
import org.tukaani.xz.rangecoder.RangeEncoder;
|
||||
import org.tukaani.xz.lzma.LZMAEncoder;
|
||||
|
||||
class LZMA2OutputStream extends FinishableOutputStream {
|
||||
|
||||
static final int COMPRESSED_SIZE_MAX = 64 << 10;
|
||||
|
||||
private FinishableOutputStream out;
|
||||
private final DataOutputStream outData;
|
||||
|
||||
private final LZEncoder lz;
|
||||
private final RangeEncoder rc;
|
||||
private final LZMAEncoder lzma;
|
||||
|
||||
private final int props; // Cannot change props on the fly for now.
|
||||
private boolean dictResetNeeded = true;
|
||||
private boolean stateResetNeeded = true;
|
||||
private boolean propsNeeded = true;
|
||||
|
||||
private int pendingSize = 0;
|
||||
private boolean finished = false;
|
||||
private IOException exception = null;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
private static int getExtraSizeBefore(int dictSize) {
|
||||
return COMPRESSED_SIZE_MAX > dictSize
|
||||
? COMPRESSED_SIZE_MAX - dictSize : 0;
|
||||
}
|
||||
|
||||
static int getMemoryUsage(LZMA2Options options) {
|
||||
// 64 KiB buffer for the range encoder + a little extra + LZMAEncoder
|
||||
int dictSize = options.getDictSize();
|
||||
int extraSizeBefore = getExtraSizeBefore(dictSize);
|
||||
return 70 + LZMAEncoder.getMemoryUsage(options.getMode(),
|
||||
dictSize, extraSizeBefore,
|
||||
options.getMatchFinder());
|
||||
}
|
||||
|
||||
LZMA2OutputStream(FinishableOutputStream out, LZMA2Options options) {
|
||||
if (out == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
this.out = out;
|
||||
outData = new DataOutputStream(out);
|
||||
rc = new RangeEncoder(COMPRESSED_SIZE_MAX);
|
||||
|
||||
int dictSize = options.getDictSize();
|
||||
int extraSizeBefore = getExtraSizeBefore(dictSize);
|
||||
lzma = LZMAEncoder.getInstance(rc,
|
||||
options.getLc(), options.getLp(), options.getPb(),
|
||||
options.getMode(),
|
||||
dictSize, extraSizeBefore, options.getNiceLen(),
|
||||
options.getMatchFinder(), options.getDepthLimit());
|
||||
|
||||
lz = lzma.getLZEncoder();
|
||||
|
||||
byte[] presetDict = options.getPresetDict();
|
||||
if (presetDict != null && presetDict.length > 0) {
|
||||
lz.setPresetDict(dictSize, presetDict);
|
||||
dictResetNeeded = false;
|
||||
}
|
||||
|
||||
props = (options.getPb() * 5 + options.getLp()) * 9 + options.getLc();
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
tempBuf[0] = (byte) b;
|
||||
write(tempBuf, 0, 1);
|
||||
}
|
||||
|
||||
public void write(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
while (len > 0) {
|
||||
int used = lz.fillWindow(buf, off, len);
|
||||
off += used;
|
||||
len -= used;
|
||||
pendingSize += used;
|
||||
|
||||
if (lzma.encodeForLZMA2())
|
||||
writeChunk();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeChunk() throws IOException {
|
||||
int compressedSize = rc.finish();
|
||||
int uncompressedSize = lzma.getUncompressedSize();
|
||||
|
||||
assert compressedSize > 0 : compressedSize;
|
||||
assert uncompressedSize > 0 : uncompressedSize;
|
||||
|
||||
// +2 because the header of a compressed chunk is 2 bytes
|
||||
// bigger than the header of an uncompressed chunk.
|
||||
if (compressedSize + 2 < uncompressedSize)
|
||||
writeLZMA(uncompressedSize, compressedSize);
|
||||
else {
|
||||
lzma.reset();
|
||||
uncompressedSize = lzma.getUncompressedSize();
|
||||
assert uncompressedSize > 0 : uncompressedSize;
|
||||
writeUncompressed(uncompressedSize);
|
||||
}
|
||||
|
||||
pendingSize -= uncompressedSize;
|
||||
lzma.resetUncompressedSize();
|
||||
rc.reset();
|
||||
}
|
||||
|
||||
private void writeLZMA(int uncompressedSize, int compressedSize)
|
||||
throws IOException {
|
||||
int control;
|
||||
|
||||
if (propsNeeded)
|
||||
if (dictResetNeeded)
|
||||
control = 0x80 + (3 << 5);
|
||||
else
|
||||
control = 0x80 + (2 << 5);
|
||||
else if (stateResetNeeded)
|
||||
control = 0x80 + (1 << 5);
|
||||
else
|
||||
control = 0x80;
|
||||
|
||||
control |= (uncompressedSize - 1) >>> 16;
|
||||
outData.writeByte(control);
|
||||
|
||||
outData.writeShort(uncompressedSize - 1);
|
||||
outData.writeShort(compressedSize - 1);
|
||||
|
||||
if (propsNeeded)
|
||||
outData.writeByte(props);
|
||||
|
||||
rc.write(out);
|
||||
|
||||
propsNeeded = false;
|
||||
stateResetNeeded = false;
|
||||
dictResetNeeded = false;
|
||||
}
|
||||
|
||||
private void writeUncompressed(int uncompressedSize) throws IOException {
|
||||
while (uncompressedSize > 0) {
|
||||
int chunkSize = Math.min(uncompressedSize, COMPRESSED_SIZE_MAX);
|
||||
outData.writeByte(dictResetNeeded ? 0x01 : 0x02);
|
||||
outData.writeShort(chunkSize - 1);
|
||||
lz.copyUncompressed(out, uncompressedSize, chunkSize);
|
||||
uncompressedSize -= chunkSize;
|
||||
dictResetNeeded = false;
|
||||
}
|
||||
|
||||
stateResetNeeded = true;
|
||||
}
|
||||
|
||||
private void writeEndMarker() throws IOException {
|
||||
assert !finished;
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
lz.setFinishing();
|
||||
|
||||
try {
|
||||
while (pendingSize > 0) {
|
||||
lzma.encodeForLZMA2();
|
||||
writeChunk();
|
||||
}
|
||||
|
||||
out.write(0x00);
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (finished)
|
||||
throw new XZIOException("Stream finished or closed");
|
||||
|
||||
try {
|
||||
lz.setFlushing();
|
||||
|
||||
while (pendingSize > 0) {
|
||||
lzma.encodeForLZMA2();
|
||||
writeChunk();
|
||||
}
|
||||
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() throws IOException {
|
||||
if (!finished) {
|
||||
writeEndMarker();
|
||||
|
||||
try {
|
||||
out.finish();
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (out != null) {
|
||||
if (!finished)
|
||||
try {
|
||||
writeEndMarker();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
if (exception == null)
|
||||
exception = e;
|
||||
}
|
||||
|
||||
out = null;
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
@@ -1,548 +0,0 @@
|
||||
/*
|
||||
* LZMAInputStream
|
||||
*
|
||||
* Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
* Igor Pavlov <http://7-zip.org/>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import org.tukaani.xz.lz.LZDecoder;
|
||||
import org.tukaani.xz.rangecoder.RangeDecoderFromStream;
|
||||
import org.tukaani.xz.lzma.LZMADecoder;
|
||||
|
||||
/**
|
||||
* Decompresses legacy .lzma files and raw LZMA streams (no .lzma header).
|
||||
* <p>
|
||||
* <b>IMPORTANT:</b> In contrast to other classes in this package, this class
|
||||
* reads data from its input stream one byte at a time. If the input stream is
|
||||
* for example {@link java.io.FileInputStream}, wrapping it into
|
||||
* {@link java.io.BufferedInputStream} tends to improve performance a lot. This
|
||||
* is not automatically done by this class because there may be use cases where
|
||||
* it is desired that this class won't read any bytes past the end of the LZMA
|
||||
* stream.
|
||||
* <p>
|
||||
* Even when using <code>BufferedInputStream</code>, the performance tends to be
|
||||
* worse (maybe 10-20 % slower) than with {@link LZMA2InputStream} or
|
||||
* {@link XZInputStream} (when the .xz file contains LZMA2-compressed data).
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public class LZMAInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Largest dictionary size supported by this implementation.
|
||||
* <p>
|
||||
* LZMA allows dictionaries up to one byte less than 4 GiB. This
|
||||
* implementation supports only 16 bytes less than 2 GiB. This limitation is
|
||||
* due to Java using signed 32-bit integers for array indexing. The
|
||||
* limitation shouldn't matter much in practice since so huge dictionaries
|
||||
* are not normally used.
|
||||
*/
|
||||
public static final int DICT_SIZE_MAX = Integer.MAX_VALUE & ~15;
|
||||
|
||||
private InputStream in;
|
||||
private LZDecoder lz;
|
||||
private RangeDecoderFromStream rc;
|
||||
private LZMADecoder lzma;
|
||||
|
||||
private boolean endReached = false;
|
||||
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
/**
|
||||
* Number of uncompressed bytes left to be decompressed, or -1 if the end
|
||||
* marker is used.
|
||||
*/
|
||||
private long remainingSize;
|
||||
|
||||
private IOException exception = null;
|
||||
|
||||
/**
|
||||
* Gets approximate decompressor memory requirements as kibibytes for the
|
||||
* given dictionary size and LZMA properties byte (lc, lp, and pb).
|
||||
*
|
||||
* @param dictSize LZMA dictionary size as bytes, should be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param propsByte LZMA properties byte that encodes the values of lc, lp,
|
||||
* and pb
|
||||
*
|
||||
* @return approximate memory requirements as kibibytes (KiB)
|
||||
*
|
||||
* @throws UnsupportedOptionsException if <code>dictSize</code> is outside
|
||||
* the range [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @throws CorruptedInputException if <code>propsByte</code> is invalid
|
||||
*/
|
||||
public static int getMemoryUsage(int dictSize, byte propsByte)
|
||||
throws UnsupportedOptionsException, CorruptedInputException {
|
||||
if (dictSize < 0 || dictSize > DICT_SIZE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA dictionary is too big for this implementation");
|
||||
|
||||
int props = propsByte & 0xFF;
|
||||
if (props > (4 * 5 + 4) * 9 + 8)
|
||||
throw new CorruptedInputException("Invalid LZMA properties byte");
|
||||
|
||||
props %= 9 * 5;
|
||||
int lp = props / 9;
|
||||
int lc = props - lp * 9;
|
||||
|
||||
return getMemoryUsage(dictSize, lc, lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets approximate decompressor memory requirements as kibibytes for the
|
||||
* given dictionary size, lc, and lp. Note that pb isn't needed.
|
||||
*
|
||||
* @param dictSize LZMA dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param lc number of literal context bits, must be in the range [0,
|
||||
* 8]
|
||||
*
|
||||
* @param lp number of literal position bits, must be in the range [0,
|
||||
* 4]
|
||||
*
|
||||
* @return approximate memory requirements as kibibytes (KiB)
|
||||
*/
|
||||
public static int getMemoryUsage(int dictSize, int lc, int lp) {
|
||||
if (lc < 0 || lc > 8 || lp < 0 || lp > 4)
|
||||
throw new IllegalArgumentException("Invalid lc or lp");
|
||||
|
||||
// Probability variables have the type "short". There are
|
||||
// 0x300 (768) probability variables in each literal subcoder.
|
||||
// The number of literal subcoders is 2^(lc + lp).
|
||||
//
|
||||
// Roughly 10 KiB for the base state + LZ decoder's dictionary buffer
|
||||
// + sizeof(short) * number probability variables per literal subcoder
|
||||
// * number of literal subcoders
|
||||
return 10 + getDictSize(dictSize) / 1024
|
||||
+ ((2 * 0x300) << (lc + lp)) / 1024;
|
||||
}
|
||||
|
||||
private static int getDictSize(int dictSize) {
|
||||
if (dictSize < 0 || dictSize > DICT_SIZE_MAX)
|
||||
throw new IllegalArgumentException(
|
||||
"LZMA dictionary is too big for this implementation");
|
||||
|
||||
// For performance reasons, use a 4 KiB dictionary if something
|
||||
// smaller was requested. It's a rare situation and the performance
|
||||
// difference isn't huge, and it starts to matter mostly when the
|
||||
// dictionary is just a few bytes. But we need to handle the special
|
||||
// case of dictSize == 0 anyway, which is an allowed value but in
|
||||
// practice means one-byte dictionary.
|
||||
//
|
||||
// Note that using a dictionary bigger than specified in the headers
|
||||
// can hide errors if there is a reference to data beyond the original
|
||||
// dictionary size but is still within 4 KiB.
|
||||
if (dictSize < 4096)
|
||||
dictSize = 4096;
|
||||
|
||||
// Round dictionary size upward to a multiple of 16. This way LZMA
|
||||
// can use LZDecoder.getPos() for calculating LZMA's posMask.
|
||||
return (dictSize + 15) & ~15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new .lzma file format decompressor without a memory usage
|
||||
* limit.
|
||||
*
|
||||
* @param in input stream from which .lzma data is read; it might be a good
|
||||
* idea to wrap it in <code>BufferedInputStream</code>, see the note at the
|
||||
* top of this page
|
||||
*
|
||||
* @throws CorruptedInputException file is corrupt or perhaps not in the
|
||||
* .lzma format at all
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
* @throws EOFException file is truncated or perhaps not in
|
||||
* the .lzma format
|
||||
* at all
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in) throws IOException {
|
||||
this(in, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new .lzma file format decompressor with an optional memory
|
||||
* usage limit.
|
||||
*
|
||||
* @param in input stream from which .lzma data is read; it might
|
||||
* be a good
|
||||
* idea to wrap it in <code>BufferedInputStream</code>, see the note at the
|
||||
* top of this page
|
||||
*
|
||||
* @param memoryLimit memory usage limit in kibibytes (KiB) or
|
||||
* <code>-1</code> to impose no memory usage limit
|
||||
*
|
||||
* @throws CorruptedInputException file is corrupt or perhaps not in the
|
||||
* .lzma format at all
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
* @throws MemoryLimitException memory usage limit was exceeded
|
||||
*
|
||||
* @throws EOFException file is truncated or perhaps not in
|
||||
* the .lzma format
|
||||
* at all
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, int memoryLimit)
|
||||
throws IOException {
|
||||
DataInputStream inData = new DataInputStream(in);
|
||||
|
||||
// Properties byte (lc, lp, and pb)
|
||||
byte propsByte = inData.readByte();
|
||||
|
||||
// Dictionary size is an unsigned 32-bit little endian integer.
|
||||
int dictSize = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
dictSize |= inData.readUnsignedByte() << (8 * i);
|
||||
|
||||
// Uncompressed size is an unsigned 64-bit little endian integer.
|
||||
// The maximum 64-bit value is a special case (becomes -1 here)
|
||||
// which indicates that the end marker is used instead of knowing
|
||||
// the uncompressed size beforehand.
|
||||
long uncompSize = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
uncompSize |= (long) inData.readUnsignedByte() << (8 * i);
|
||||
|
||||
// Check the memory usage limit.
|
||||
int memoryNeeded = getMemoryUsage(dictSize, propsByte);
|
||||
if (memoryLimit != -1 && memoryNeeded > memoryLimit)
|
||||
throw new MemoryLimitException(memoryNeeded, memoryLimit);
|
||||
|
||||
initialize(in, uncompSize, propsByte, dictSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA data (no .lzma
|
||||
* header) from <code>in</code>.
|
||||
* <p>
|
||||
* The caller needs to know if the "end of payload marker (EOPM)" alias "end
|
||||
* of stream marker (EOS marker)" alias "end marker" present. If the end
|
||||
* marker isn't used, the caller must know the exact uncompressed size of
|
||||
* the stream.
|
||||
* <p>
|
||||
* The caller also needs to provide the LZMA properties byte that encodes
|
||||
* the number of literal context bits (lc), literal position bits (lp), and
|
||||
* position bits (pb).
|
||||
* <p>
|
||||
* The dictionary size used when compressing is also needed. Specifying a
|
||||
* too small dictionary size will prevent decompressing the stream.
|
||||
* Specifying a too big dictionary is waste of memory but decompression will
|
||||
* work.
|
||||
* <p>
|
||||
* There is no need to specify a dictionary bigger than the uncompressed
|
||||
* size of the data even if a bigger dictionary was used when compressing.
|
||||
* If you know the uncompressed size of the data, this might allow saving
|
||||
* some memory.
|
||||
*
|
||||
* @param in input stream from which compressed data is read
|
||||
*
|
||||
* @param uncompSize uncompressed size of the LZMA stream or -1 if the end
|
||||
* marker is used in the LZMA stream
|
||||
*
|
||||
* @param propsByte LZMA properties byte that has the encoded values for
|
||||
* literal context bits (lc), literal position bits (lp), and position bits
|
||||
* (pb)
|
||||
*
|
||||
* @param dictSize dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @throws CorruptedInputException if <code>propsByte</code> is invalid
|
||||
* or
|
||||
* the first input byte is not 0x00
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
*
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, long uncompSize, byte propsByte,
|
||||
int dictSize) throws IOException {
|
||||
initialize(in, uncompSize, propsByte, dictSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA data (no .lzma
|
||||
* header) from <code>in</code> optionally with a preset dictionary.
|
||||
*
|
||||
* @param in input stream from which LZMA-compressed data is read
|
||||
*
|
||||
* @param uncompSize uncompressed size of the LZMA stream or -1 if the end
|
||||
* marker is used in the LZMA stream
|
||||
*
|
||||
* @param propsByte LZMA properties byte that has the encoded values for
|
||||
* literal context bits (lc), literal position bits (lp), and position bits
|
||||
* (pb)
|
||||
*
|
||||
* @param dictSize dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param presetDict preset dictionary or <code>null</code> to use no preset
|
||||
* dictionary
|
||||
*
|
||||
* @throws CorruptedInputException if <code>propsByte</code> is invalid
|
||||
* or
|
||||
* the first input byte is not 0x00
|
||||
*
|
||||
* @throws UnsupportedOptionsException dictionary size or uncompressed size
|
||||
* is too big for this implementation
|
||||
*
|
||||
* @throws EOFException file is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, long uncompSize, byte propsByte,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
initialize(in, uncompSize, propsByte, dictSize, presetDict);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream that decompresses raw LZMA data (no .lzma
|
||||
* header) from <code>in</code> optionally with a preset dictionary.
|
||||
*
|
||||
* @param in input stream from which LZMA-compressed data is read
|
||||
*
|
||||
* @param uncompSize uncompressed size of the LZMA stream or -1 if the end
|
||||
* marker is used in the LZMA stream
|
||||
*
|
||||
* @param lc number of literal context bits, must be in the range
|
||||
* [0, 8]
|
||||
*
|
||||
* @param lp number of literal position bits, must be in the range
|
||||
* [0, 4]
|
||||
*
|
||||
* @param pb number position bits, must be in the range [0, 4]
|
||||
*
|
||||
* @param dictSize dictionary size as bytes, must be in the range
|
||||
* [<code>0</code>, <code>DICT_SIZE_MAX</code>]
|
||||
*
|
||||
* @param presetDict preset dictionary or <code>null</code> to use no preset
|
||||
* dictionary
|
||||
*
|
||||
* @throws CorruptedInputException if the first input byte is not 0x00
|
||||
*
|
||||
* @throws EOFException file is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public LZMAInputStream(InputStream in, long uncompSize,
|
||||
int lc, int lp, int pb,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
initialize(in, uncompSize, lc, lp, pb, dictSize, presetDict);
|
||||
}
|
||||
|
||||
private void initialize(InputStream in, long uncompSize, byte propsByte,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
// Validate the uncompressed size since the other "initialize" throws
|
||||
// IllegalArgumentException if uncompSize < -1.
|
||||
if (uncompSize < -1)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Uncompressed size is too big");
|
||||
|
||||
// Decode the properties byte. In contrast to LZMA2, there is no
|
||||
// limit of lc + lp <= 4.
|
||||
int props = propsByte & 0xFF;
|
||||
if (props > (4 * 5 + 4) * 9 + 8)
|
||||
throw new CorruptedInputException("Invalid LZMA properties byte");
|
||||
|
||||
int pb = props / (9 * 5);
|
||||
props -= pb * 9 * 5;
|
||||
int lp = props / 9;
|
||||
int lc = props - lp * 9;
|
||||
|
||||
// Validate the dictionary size since the other "initialize" throws
|
||||
// IllegalArgumentException if dictSize is not supported.
|
||||
if (dictSize < 0 || dictSize > DICT_SIZE_MAX)
|
||||
throw new UnsupportedOptionsException(
|
||||
"LZMA dictionary is too big for this implementation");
|
||||
|
||||
initialize(in, uncompSize, lc, lp, pb, dictSize, presetDict);
|
||||
}
|
||||
|
||||
private void initialize(InputStream in, long uncompSize,
|
||||
int lc, int lp, int pb,
|
||||
int dictSize, byte[] presetDict)
|
||||
throws IOException {
|
||||
// getDictSize validates dictSize and gives a message in
|
||||
// the exception too, so skip validating dictSize here.
|
||||
if (uncompSize < -1 || lc < 0 || lc > 8 || lp < 0 || lp > 4
|
||||
|| pb < 0 || pb > 4)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
this.in = in;
|
||||
|
||||
// If uncompressed size is known, use it to avoid wasting memory for
|
||||
// a uselessly large dictionary buffer.
|
||||
dictSize = getDictSize(dictSize);
|
||||
if (uncompSize >= 0 && dictSize > uncompSize)
|
||||
dictSize = getDictSize((int) uncompSize);
|
||||
|
||||
lz = new LZDecoder(getDictSize(dictSize), presetDict);
|
||||
rc = new RangeDecoderFromStream(in);
|
||||
lzma = new LZMADecoder(lz, rc, lc, lp, pb);
|
||||
remainingSize = uncompSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses the next byte from this input stream.
|
||||
* <p>
|
||||
* Reading lots of data with <code>read()</code> from this input stream may
|
||||
* be inefficient. Wrap it in <code>java.io.BufferedInputStream</code> if
|
||||
* you need to read lots of data one byte at a time.
|
||||
*
|
||||
* @return the next decompressed byte, or <code>-1</code> to indicate the
|
||||
* end of the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses into an array of bytes.
|
||||
* <p>
|
||||
* If <code>len</code> is zero, no bytes are read and <code>0</code> is
|
||||
* returned. Otherwise this will block until <code>len</code> bytes have
|
||||
* been decompressed, the end of the LZMA stream is reached, or an exception
|
||||
* is thrown.
|
||||
*
|
||||
* @param buf target buffer for uncompressed data
|
||||
* @param off start offset in <code>buf</code>
|
||||
* @param len maximum number of uncompressed bytes to read
|
||||
*
|
||||
* @return number of bytes read, or <code>-1</code> to indicate the end of
|
||||
* the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws EOFException compressed input is truncated or corrupt
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (endReached)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
int size = 0;
|
||||
|
||||
while (len > 0) {
|
||||
// If uncompressed size is known and thus no end marker will
|
||||
// be present, set the limit so that the uncompressed size
|
||||
// won't be exceeded.
|
||||
int copySizeMax = len;
|
||||
if (remainingSize >= 0 && remainingSize < len)
|
||||
copySizeMax = (int) remainingSize;
|
||||
|
||||
lz.setLimit(copySizeMax);
|
||||
|
||||
// Decode into the dictionary buffer.
|
||||
try {
|
||||
lzma.decode();
|
||||
} catch (CorruptedInputException e) {
|
||||
// The end marker is encoded with a LZMA symbol that
|
||||
// indicates maximum match distance. This is larger
|
||||
// than any supported dictionary and thus causes
|
||||
// CorruptedInputException from LZDecoder.repeat.
|
||||
if (remainingSize != -1 || !lzma.endMarkerDetected())
|
||||
throw e;
|
||||
|
||||
endReached = true;
|
||||
|
||||
// The exception makes lzma.decode() miss the last range
|
||||
// decoder normalization, so do it here. This might
|
||||
// cause an IOException if it needs to read a byte
|
||||
// from the input stream.
|
||||
rc.normalize();
|
||||
}
|
||||
|
||||
// Copy from the dictionary to buf.
|
||||
int copiedSize = lz.flush(buf, off);
|
||||
off += copiedSize;
|
||||
len -= copiedSize;
|
||||
size += copiedSize;
|
||||
|
||||
if (remainingSize >= 0) {
|
||||
// Update the number of bytes left to be decompressed.
|
||||
remainingSize -= copiedSize;
|
||||
assert remainingSize >= 0;
|
||||
|
||||
if (remainingSize == 0)
|
||||
endReached = true;
|
||||
}
|
||||
|
||||
if (endReached) {
|
||||
// Checking these helps a lot when catching corrupt
|
||||
// or truncated .lzma files. LZMA Utils doesn't do
|
||||
// the first check and thus it accepts many invalid
|
||||
// files that this implementation and XZ Utils don't.
|
||||
if (!rc.isFinished() || lz.hasPending())
|
||||
throw new CorruptedInputException();
|
||||
|
||||
return size == 0 ? -1 : size;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and calls <code>in.close()</code>. If the stream was
|
||||
* already closed, this does nothing.
|
||||
*
|
||||
* @throws IOException if thrown by <code>in.close()</code>
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* MemoryLimitException
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
/**
|
||||
* Thrown when the memory usage limit given to the XZ decompressor would be
|
||||
* exceeded.
|
||||
* <p>
|
||||
* The amount of memory required and the memory usage limit are included in the
|
||||
* error detail message in human readable format.
|
||||
*/
|
||||
public class MemoryLimitException extends XZIOException {
|
||||
|
||||
private static final long serialVersionUID = 3L;
|
||||
|
||||
private final int memoryNeeded;
|
||||
private final int memoryLimit;
|
||||
|
||||
/**
|
||||
* Creates a new MemoryLimitException.
|
||||
* <p>
|
||||
* The amount of memory needed and the memory usage limit are included in
|
||||
* the error detail message.
|
||||
*
|
||||
* @param memoryNeeded amount of memory needed as kibibytes (KiB)
|
||||
* @param memoryLimit specified memory usage limit as kibibytes (KiB)
|
||||
*/
|
||||
public MemoryLimitException(int memoryNeeded, int memoryLimit) {
|
||||
super("" + memoryNeeded + " KiB of memory would be needed; limit was "
|
||||
+ memoryLimit + " KiB");
|
||||
|
||||
this.memoryNeeded = memoryNeeded;
|
||||
this.memoryLimit = memoryLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets how much memory is required to decompress the data.
|
||||
*
|
||||
* @return amount of memory needed as kibibytes (KiB)
|
||||
*/
|
||||
public int getMemoryNeeded() {
|
||||
return memoryNeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets what the memory usage limit was at the time the exception was
|
||||
* created.
|
||||
*
|
||||
* @return memory usage limit as kibibytes (KiB)
|
||||
*/
|
||||
public int getMemoryLimit() {
|
||||
return memoryLimit;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* PowerPCOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.PowerPC;
|
||||
|
||||
/**
|
||||
* BCJ filter for big endian PowerPC instructions.
|
||||
*/
|
||||
public class PowerPCOptions extends BCJOptions {
|
||||
|
||||
private static final int ALIGNMENT = 4;
|
||||
|
||||
public PowerPCOptions() {
|
||||
super(ALIGNMENT);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new SimpleOutputStream(out, new PowerPC(true, startOffset));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new SimpleInputStream(in, new PowerPC(false, startOffset));
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new BCJEncoder(this, BCJCoder.POWERPC_FILTER_ID);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* RawCoder
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
class RawCoder {
|
||||
|
||||
static void validate(FilterCoder[] filters)
|
||||
throws UnsupportedOptionsException {
|
||||
for (int i = 0; i < filters.length - 1; ++i)
|
||||
if (!filters[i].nonLastOK())
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported XZ filter chain");
|
||||
|
||||
if (!filters[filters.length - 1].lastOK())
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported XZ filter chain");
|
||||
|
||||
int changesSizeCount = 0;
|
||||
for (int i = 0; i < filters.length; ++i)
|
||||
if (filters[i].changesSize())
|
||||
++changesSizeCount;
|
||||
|
||||
if (changesSizeCount > 3)
|
||||
throw new UnsupportedOptionsException(
|
||||
"Unsupported XZ filter chain");
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* SPARCOptions
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.tukaani.xz.simple.SPARC;
|
||||
|
||||
/**
|
||||
* BCJ filter for SPARC.
|
||||
*/
|
||||
public class SPARCOptions extends BCJOptions {
|
||||
|
||||
private static final int ALIGNMENT = 4;
|
||||
|
||||
public SPARCOptions() {
|
||||
super(ALIGNMENT);
|
||||
}
|
||||
|
||||
public FinishableOutputStream getOutputStream(FinishableOutputStream out) {
|
||||
return new SimpleOutputStream(out, new SPARC(true, startOffset));
|
||||
}
|
||||
|
||||
public InputStream getInputStream(InputStream in) {
|
||||
return new SimpleInputStream(in, new SPARC(false, startOffset));
|
||||
}
|
||||
|
||||
FilterEncoder getFilterEncoder() {
|
||||
return new BCJEncoder(this, BCJCoder.SPARC_FILTER_ID);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* SeekableFileInputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.IOException;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Wraps a {@link java.io.RandomAccessFile RandomAccessFile} in a
|
||||
* SeekableInputStream.
|
||||
*/
|
||||
public class SeekableFileInputStream extends SeekableInputStream {
|
||||
|
||||
/**
|
||||
* The RandomAccessFile that has been wrapped into a
|
||||
* SeekableFileInputStream.
|
||||
*/
|
||||
protected RandomAccessFile randomAccessFile;
|
||||
|
||||
/**
|
||||
* Creates a new seekable input stream that reads from the specified file.
|
||||
*/
|
||||
public SeekableFileInputStream(File file) throws FileNotFoundException {
|
||||
randomAccessFile = new RandomAccessFile(file, "r");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new seekable input stream that reads from a file with the
|
||||
* specified name.
|
||||
*/
|
||||
public SeekableFileInputStream(String name) throws FileNotFoundException {
|
||||
randomAccessFile = new RandomAccessFile(name, "r");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new seekable input stream from an existing
|
||||
* <code>RandomAccessFile</code> object.
|
||||
*/
|
||||
public SeekableFileInputStream(RandomAccessFile randomAccessFile) {
|
||||
this.randomAccessFile = randomAccessFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link RandomAccessFile#read() randomAccessFile.read()}.
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return randomAccessFile.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link RandomAccessFile#read(byte[]) randomAccessFile.read(buf)}.
|
||||
*/
|
||||
public int read(byte[] buf) throws IOException {
|
||||
return randomAccessFile.read(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls null null null null null null null null null null null {@link RandomAccessFile#read(byte[],int,int)
|
||||
* randomAccessFile.read(buf, off, len)}.
|
||||
*/
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
return randomAccessFile.read(buf, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link RandomAccessFile#close() randomAccessFile.close()}.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
randomAccessFile.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link RandomAccessFile#length() randomAccessFile.length()}.
|
||||
*/
|
||||
public long length() throws IOException {
|
||||
return randomAccessFile.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link RandomAccessFile#getFilePointer()
|
||||
* randomAccessFile.getFilePointer()}.
|
||||
*/
|
||||
public long position() throws IOException {
|
||||
return randomAccessFile.getFilePointer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link RandomAccessFile#seek(long) randomAccessFile.seek(long)}.
|
||||
*/
|
||||
public void seek(long pos) throws IOException {
|
||||
randomAccessFile.seek(pos);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* SeekableInputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Input stream with random access support.
|
||||
*/
|
||||
public abstract class SeekableInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Seeks <code>n</code> bytes forward in this stream.
|
||||
* <p>
|
||||
* This will not seek past the end of the file. If the current position is
|
||||
* already at or past the end of the file, this doesn't seek at all and
|
||||
* returns <code>0</code>. Otherwise, if skipping <code>n</code> bytes would
|
||||
* cause the position to exceed the stream size, this will do equivalent of
|
||||
* <code>seek(length())</code> and the return value will be adjusted
|
||||
* accordingly.
|
||||
* <p>
|
||||
* If <code>n</code> is negative, the position isn't changed and the return
|
||||
* value is <code>0</code>. It doesn't seek backward because it would
|
||||
* conflict with the specification of
|
||||
* {@link java.io.InputStream#skip(long) InputStream.skip}.
|
||||
*
|
||||
* @return <code>0</code> if <code>n</code> is negative, less than
|
||||
* <code>n</code> if skipping <code>n</code> bytes would seek past the end
|
||||
* of the file, <code>n</code> otherwise
|
||||
*
|
||||
* @throws IOException might be thrown by {@link #seek(long)}
|
||||
*/
|
||||
public long skip(long n) throws IOException {
|
||||
if (n <= 0)
|
||||
return 0;
|
||||
|
||||
long size = length();
|
||||
long pos = position();
|
||||
if (pos >= size)
|
||||
return 0;
|
||||
|
||||
if (size - pos < n)
|
||||
n = size - pos;
|
||||
|
||||
seek(pos + n);
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the stream.
|
||||
*/
|
||||
public abstract long length() throws IOException;
|
||||
|
||||
/**
|
||||
* Gets the current position in the stream.
|
||||
*/
|
||||
public abstract long position() throws IOException;
|
||||
|
||||
/**
|
||||
* Seeks to the specified absolute position in the stream.
|
||||
* <p>
|
||||
* Seeking past the end of the file should be supported by the subclasses
|
||||
* unless there is a good reason to do otherwise. If one has seeked past the
|
||||
* end of the stream, <code>read</code> will return <code>-1</code> to
|
||||
* indicate end of stream.
|
||||
*
|
||||
* @param pos new read position in the stream
|
||||
*
|
||||
* @throws IOException if <code>pos</code> is negative or if a
|
||||
* stream-specific I/O error occurs
|
||||
*/
|
||||
public abstract void seek(long pos) throws IOException;
|
||||
}
|
||||
@@ -1,877 +0,0 @@
|
||||
/*
|
||||
* SeekableXZInputStream
|
||||
*
|
||||
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
||||
*
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
package org.tukaani.xz;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.EOFException;
|
||||
import org.tukaani.xz.common.DecoderUtil;
|
||||
import org.tukaani.xz.common.StreamFlags;
|
||||
import org.tukaani.xz.check.Check;
|
||||
import org.tukaani.xz.index.IndexDecoder;
|
||||
import org.tukaani.xz.index.BlockInfo;
|
||||
|
||||
/**
|
||||
* Decompresses a .xz file in random access mode. This supports decompressing
|
||||
* concatenated .xz files.
|
||||
* <p>
|
||||
* Each .xz file consist of one or more Streams. Each Stream consist of zero or
|
||||
* more Blocks. Each Stream contains an Index of Streams' Blocks. The Indexes
|
||||
* from all Streams are loaded in RAM by a constructor of this class. A typical
|
||||
* .xz file has only one Stream, and parsing its Index will need only three or
|
||||
* four seeks.
|
||||
* <p>
|
||||
* To make random access possible, the data in a .xz file must be splitted into
|
||||
* multiple Blocks of reasonable size. Decompression can only start at a Block
|
||||
* boundary. When seeking to an uncompressed position that is not at a Block
|
||||
* boundary, decompression starts at the beginning of the Block and throws away
|
||||
* data until the target position is reached. Thus, smaller Blocks mean faster
|
||||
* seeks to arbitrary uncompressed positions. On the other hand, smaller Blocks
|
||||
* mean worse compression. So one has to make a compromise between random access
|
||||
* speed and compression ratio.
|
||||
* <p>
|
||||
* Implementation note: This class uses linear search to locate the correct
|
||||
* Stream from the data structures in RAM. It was the simplest to implement and
|
||||
* should be fine as long as there aren't too many Streams. The correct Block
|
||||
* inside a Stream is located using binary search and thus is fast even with a
|
||||
* huge number of Blocks.
|
||||
*
|
||||
* <h4>Memory usage</h4>
|
||||
* <p>
|
||||
* The amount of memory needed for the Indexes is taken into account when
|
||||
* checking the memory usage limit. Each Stream is calculated to need at least
|
||||
* 1 KiB of memory and each Block 16 bytes of memory, rounded up to the
|
||||
* next kibibyte. So unless the file has a huge number of Streams or Blocks,
|
||||
* these don't take significant amount of memory.
|
||||
*
|
||||
* <h4>Creating random-accessible .xz files</h4>
|
||||
* <p>
|
||||
* When using {@link XZOutputStream}, a new Block can be started by calling its
|
||||
* {@link XZOutputStream#endBlock() endBlock} method. If you know that the
|
||||
* decompressor will only need to seek to certain uncompressed positions, it can
|
||||
* be a good idea to start a new Block at (some of) these positions (and only at
|
||||
* these positions to get better compression ratio).
|
||||
* <p>
|
||||
* liblzma in XZ Utils supports starting a new Block with
|
||||
* <code>LZMA_FULL_FLUSH</code>. XZ Utils 5.1.1alpha added threaded compression
|
||||
* which creates multi-Block .xz files. XZ Utils 5.1.1alpha also added the
|
||||
* option <code>--block-size=SIZE</code> to the xz command line tool. XZ Utils
|
||||
* 5.1.2alpha added a partial implementation of <code>--block-list=SIZES</code>
|
||||
* which allows specifying sizes of individual Blocks.
|
||||
*
|
||||
* @see SeekableFileInputStream
|
||||
* @see XZInputStream
|
||||
* @see XZOutputStream
|
||||
*/
|
||||
public class SeekableXZInputStream extends SeekableInputStream {
|
||||
|
||||
/**
|
||||
* The input stream containing XZ compressed data.
|
||||
*/
|
||||
private SeekableInputStream in;
|
||||
|
||||
/**
|
||||
* Memory usage limit after the memory usage of the IndexDecoders have been
|
||||
* substracted.
|
||||
*/
|
||||
private final int memoryLimit;
|
||||
|
||||
/**
|
||||
* Memory usage of the IndexDecoders.
|
||||
* <code>memoryLimit + indexMemoryUsage</code> equals the original memory
|
||||
* usage limit that was passed to the constructor.
|
||||
*/
|
||||
private int indexMemoryUsage = 0;
|
||||
|
||||
/**
|
||||
* List of IndexDecoders, one for each Stream in the file. The list is in
|
||||
* reverse order: The first element is the last Stream in the file.
|
||||
*/
|
||||
private final ArrayList streams = new ArrayList();
|
||||
|
||||
/**
|
||||
* Bitmask of all Check IDs seen.
|
||||
*/
|
||||
private int checkTypes = 0;
|
||||
|
||||
/**
|
||||
* Uncompressed size of the file (all Streams).
|
||||
*/
|
||||
private long uncompressedSize = 0;
|
||||
|
||||
/**
|
||||
* Uncompressed size of the largest XZ Block in the file.
|
||||
*/
|
||||
private long largestBlockSize = 0;
|
||||
|
||||
/**
|
||||
* Number of XZ Blocks in the file.
|
||||
*/
|
||||
private int blockCount = 0;
|
||||
|
||||
/**
|
||||
* Size and position information about the current Block. If there are no
|
||||
* Blocks, all values will be <code>-1</code>.
|
||||
*/
|
||||
private final BlockInfo curBlockInfo;
|
||||
|
||||
/**
|
||||
* Temporary (and cached) information about the Block whose information is
|
||||
* queried via <code>getBlockPos</code> and related functions.
|
||||
*/
|
||||
private final BlockInfo queriedBlockInfo;
|
||||
|
||||
/**
|
||||
* Integrity Check in the current XZ Stream. The constructor leaves this to
|
||||
* point to the Check of the first Stream.
|
||||
*/
|
||||
private Check check;
|
||||
|
||||
/**
|
||||
* Decoder of the current XZ Block, if any.
|
||||
*/
|
||||
private BlockInputStream blockDecoder = null;
|
||||
|
||||
/**
|
||||
* Current uncompressed position.
|
||||
*/
|
||||
private long curPos = 0;
|
||||
|
||||
/**
|
||||
* Target position for seeking.
|
||||
*/
|
||||
private long seekPos;
|
||||
|
||||
/**
|
||||
* True when <code>seek(long)</code> has been called but the actual seeking
|
||||
* hasn't been done yet.
|
||||
*/
|
||||
private boolean seekNeeded = false;
|
||||
|
||||
/**
|
||||
* True when end of the file was reached. This can be cleared by calling
|
||||
* <code>seek(long)</code>.
|
||||
*/
|
||||
private boolean endReached = false;
|
||||
|
||||
/**
|
||||
* Pending exception from an earlier error.
|
||||
*/
|
||||
private IOException exception = null;
|
||||
|
||||
/**
|
||||
* Temporary buffer for read(). This avoids reallocating memory on every
|
||||
* read() call.
|
||||
*/
|
||||
private final byte[] tempBuf = new byte[1];
|
||||
|
||||
/**
|
||||
* Creates a new seekable XZ decompressor without a memory usage limit.
|
||||
*
|
||||
* @param in seekable input stream containing one or more XZ Streams; the
|
||||
* whole input stream is used
|
||||
*
|
||||
* @throws XZFormatException input is not in the XZ format
|
||||
*
|
||||
* @throws CorruptedInputException XZ data is corrupt or truncated
|
||||
*
|
||||
* @throws UnsupportedOptionsException XZ headers seem valid but they
|
||||
* specify options not supported by this implementation
|
||||
*
|
||||
* @throws EOFException less than 6 bytes of input was
|
||||
* available from
|
||||
* <code>in</code>, or (unlikely) the size of the underlying stream got
|
||||
* smaller while this was reading from it
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public SeekableXZInputStream(SeekableInputStream in)
|
||||
throws IOException {
|
||||
this(in, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new seekable XZ decomporessor with an optional memory usage
|
||||
* limit.
|
||||
*
|
||||
* @param in seekable input stream containing one or more XZ
|
||||
* Streams; the
|
||||
* whole input stream is used
|
||||
*
|
||||
* @param memoryLimit memory usage limit in kibibytes (KiB) or
|
||||
* <code>-1</code> to impose no memory usage limit
|
||||
*
|
||||
* @throws XZFormatException input is not in the XZ format
|
||||
*
|
||||
* @throws CorruptedInputException XZ data is corrupt or truncated
|
||||
*
|
||||
* @throws UnsupportedOptionsException XZ headers seem valid but they
|
||||
* specify options not supported by this implementation
|
||||
*
|
||||
* @throws MemoryLimitException decoded XZ Indexes would need more
|
||||
* memory
|
||||
* than allowed by the memory usage limit
|
||||
*
|
||||
* @throws EOFException less than 6 bytes of input was
|
||||
* available from
|
||||
* <code>in</code>, or (unlikely) the size of the underlying stream got
|
||||
* smaller while this was reading from it
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public SeekableXZInputStream(SeekableInputStream in, int memoryLimit)
|
||||
throws IOException {
|
||||
this.in = in;
|
||||
DataInputStream inData = new DataInputStream(in);
|
||||
|
||||
// Check the magic bytes in the beginning of the file.
|
||||
{
|
||||
in.seek(0);
|
||||
byte[] buf = new byte[XZ.HEADER_MAGIC.length];
|
||||
inData.readFully(buf);
|
||||
if (!Arrays.equals(buf, XZ.HEADER_MAGIC))
|
||||
throw new XZFormatException();
|
||||
}
|
||||
|
||||
// Get the file size and verify that it is a multiple of 4 bytes.
|
||||
long pos = in.length();
|
||||
if ((pos & 3) != 0)
|
||||
throw new CorruptedInputException(
|
||||
"XZ file size is not a multiple of 4 bytes");
|
||||
|
||||
// Parse the headers starting from the end of the file.
|
||||
byte[] buf = new byte[DecoderUtil.STREAM_HEADER_SIZE];
|
||||
long streamPadding = 0;
|
||||
|
||||
while (pos > 0) {
|
||||
if (pos < DecoderUtil.STREAM_HEADER_SIZE)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
// Read the potential Stream Footer.
|
||||
in.seek(pos - DecoderUtil.STREAM_HEADER_SIZE);
|
||||
inData.readFully(buf);
|
||||
|
||||
// Skip Stream Padding four bytes at a time.
|
||||
// Skipping more at once would be faster,
|
||||
// but usually there isn't much Stream Padding.
|
||||
if (buf[8] == 0x00 && buf[9] == 0x00 && buf[10] == 0x00
|
||||
&& buf[11] == 0x00) {
|
||||
streamPadding += 4;
|
||||
pos -= 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's not Stream Padding. Update pos.
|
||||
pos -= DecoderUtil.STREAM_HEADER_SIZE;
|
||||
|
||||
// Decode the Stream Footer and check if Backward Size
|
||||
// looks reasonable.
|
||||
StreamFlags streamFooter = DecoderUtil.decodeStreamFooter(buf);
|
||||
if (streamFooter.backwardSize >= pos)
|
||||
throw new CorruptedInputException(
|
||||
"Backward Size in XZ Stream Footer is too big");
|
||||
|
||||
// Check that the Check ID is supported. Store it in case this
|
||||
// is the first Stream in the file.
|
||||
check = Check.getInstance(streamFooter.checkType);
|
||||
|
||||
// Remember which Check IDs have been seen.
|
||||
checkTypes |= 1 << streamFooter.checkType;
|
||||
|
||||
// Seek to the beginning of the Index.
|
||||
in.seek(pos - streamFooter.backwardSize);
|
||||
|
||||
// Decode the Index field.
|
||||
IndexDecoder index;
|
||||
try {
|
||||
index = new IndexDecoder(in, streamFooter, streamPadding,
|
||||
memoryLimit);
|
||||
} catch (MemoryLimitException e) {
|
||||
// IndexDecoder doesn't know how much memory we had
|
||||
// already needed so we need to recreate the exception.
|
||||
assert memoryLimit >= 0;
|
||||
throw new MemoryLimitException(
|
||||
e.getMemoryNeeded() + indexMemoryUsage,
|
||||
memoryLimit + indexMemoryUsage);
|
||||
}
|
||||
|
||||
// Update the memory usage and limit counters.
|
||||
indexMemoryUsage += index.getMemoryUsage();
|
||||
if (memoryLimit >= 0) {
|
||||
memoryLimit -= index.getMemoryUsage();
|
||||
assert memoryLimit >= 0;
|
||||
}
|
||||
|
||||
// Remember the uncompressed size of the largest Block.
|
||||
if (largestBlockSize < index.getLargestBlockSize())
|
||||
largestBlockSize = index.getLargestBlockSize();
|
||||
|
||||
// Calculate the offset to the beginning of this XZ Stream and
|
||||
// check that it looks sane.
|
||||
long off = index.getStreamSize() - DecoderUtil.STREAM_HEADER_SIZE;
|
||||
if (pos < off)
|
||||
throw new CorruptedInputException("XZ Index indicates "
|
||||
+ "too big compressed size for the XZ Stream");
|
||||
|
||||
// Seek to the beginning of this Stream.
|
||||
pos -= off;
|
||||
in.seek(pos);
|
||||
|
||||
// Decode the Stream Header.
|
||||
inData.readFully(buf);
|
||||
StreamFlags streamHeader = DecoderUtil.decodeStreamHeader(buf);
|
||||
|
||||
// Verify that the Stream Header matches the Stream Footer.
|
||||
if (!DecoderUtil.areStreamFlagsEqual(streamHeader, streamFooter))
|
||||
throw new CorruptedInputException(
|
||||
"XZ Stream Footer does not match Stream Header");
|
||||
|
||||
// Update the total uncompressed size of the file and check that
|
||||
// it doesn't overflow.
|
||||
uncompressedSize += index.getUncompressedSize();
|
||||
if (uncompressedSize < 0)
|
||||
throw new UnsupportedOptionsException("XZ file is too big");
|
||||
|
||||
// Update the Block count and check that it fits into an int.
|
||||
blockCount += index.getRecordCount();
|
||||
if (blockCount < 0)
|
||||
throw new UnsupportedOptionsException(
|
||||
"XZ file has over " + Integer.MAX_VALUE + " Blocks");
|
||||
|
||||
// Add this Stream to the list of Streams.
|
||||
streams.add(index);
|
||||
|
||||
// Reset to be ready to parse the next Stream.
|
||||
streamPadding = 0;
|
||||
}
|
||||
|
||||
assert pos == 0;
|
||||
|
||||
// Save it now that indexMemoryUsage has been substracted from it.
|
||||
this.memoryLimit = memoryLimit;
|
||||
|
||||
// Store the relative offsets of the Streams. This way we don't
|
||||
// need to recalculate them in this class when seeking; the
|
||||
// IndexDecoder instances will handle them.
|
||||
IndexDecoder prev = (IndexDecoder) streams.get(streams.size() - 1);
|
||||
for (int i = streams.size() - 2; i >= 0; --i) {
|
||||
IndexDecoder cur = (IndexDecoder) streams.get(i);
|
||||
cur.setOffsets(prev);
|
||||
prev = cur;
|
||||
}
|
||||
|
||||
// Initialize curBlockInfo to point to the first Stream.
|
||||
// The blockNumber will be left to -1 so that .hasNext()
|
||||
// and .setNext() work to get the first Block when starting
|
||||
// to decompress from the beginning of the file.
|
||||
IndexDecoder first = (IndexDecoder) streams.get(streams.size() - 1);
|
||||
curBlockInfo = new BlockInfo(first);
|
||||
|
||||
// queriedBlockInfo needs to be allocated too. The Stream used for
|
||||
// initialization doesn't matter though.
|
||||
queriedBlockInfo = new BlockInfo(first);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the types of integrity checks used in the .xz file. Multiple checks
|
||||
* are possible only if there are multiple concatenated XZ Streams.
|
||||
* <p>
|
||||
* The returned value has a bit set for every check type that is present.
|
||||
* For example, if CRC64 and SHA-256 were used, the return value is <code>(1 << XZ.CHECK_CRC64)
|
||||
* | (1 << XZ.CHECK_SHA256)</code>.
|
||||
*/
|
||||
public int getCheckTypes() {
|
||||
return checkTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of memory in kibibytes (KiB) used by the data structures
|
||||
* needed to locate the XZ Blocks. This is usually useless information but
|
||||
* since it is calculated for memory usage limit anyway, it is nice to make
|
||||
* it available to too.
|
||||
*/
|
||||
public int getIndexMemoryUsage() {
|
||||
return indexMemoryUsage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uncompressed size of the largest XZ Block in bytes. This can be
|
||||
* useful if you want to check that the file doesn't have huge XZ Blocks
|
||||
* which could make seeking to arbitrary offsets very slow. Note that huge
|
||||
* Blocks don't automatically mean that seeking would be slow, for example,
|
||||
* seeking to the beginning of any Block is always fast.
|
||||
*/
|
||||
public long getLargestBlockSize() {
|
||||
return largestBlockSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of Streams in the .xz file.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public int getStreamCount() {
|
||||
return streams.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of Blocks in the .xz file.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public int getBlockCount() {
|
||||
return blockCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uncompressed start position of the given Block.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if
|
||||
* <code>blockNumber < 0</code> or
|
||||
* <code>blockNumber >= getBlockCount()</code>.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public long getBlockPos(int blockNumber) {
|
||||
locateBlockByNumber(queriedBlockInfo, blockNumber);
|
||||
return queriedBlockInfo.uncompressedOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uncompressed size of the given Block.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if
|
||||
* <code>blockNumber < 0</code> or
|
||||
* <code>blockNumber >= getBlockCount()</code>.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public long getBlockSize(int blockNumber) {
|
||||
locateBlockByNumber(queriedBlockInfo, blockNumber);
|
||||
return queriedBlockInfo.uncompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position where the given compressed Block starts in the
|
||||
* underlying .xz file. This information is rarely useful to the users of
|
||||
* this class.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if
|
||||
* <code>blockNumber < 0</code> or
|
||||
* <code>blockNumber >= getBlockCount()</code>.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public long getBlockCompPos(int blockNumber) {
|
||||
locateBlockByNumber(queriedBlockInfo, blockNumber);
|
||||
return queriedBlockInfo.compressedOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compressed size of the given Block. This together with the
|
||||
* uncompressed size can be used to calculate the compression ratio of the
|
||||
* specific Block.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if
|
||||
* <code>blockNumber < 0</code> or
|
||||
* <code>blockNumber >= getBlockCount()</code>.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public long getBlockCompSize(int blockNumber) {
|
||||
locateBlockByNumber(queriedBlockInfo, blockNumber);
|
||||
return (queriedBlockInfo.unpaddedSize + 3) & ~3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets integrity check type (Check ID) of the given Block.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if
|
||||
* <code>blockNumber < 0</code> or
|
||||
* <code>blockNumber >= getBlockCount()</code>.
|
||||
*
|
||||
* @see #getCheckTypes()
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public int getBlockCheckType(int blockNumber) {
|
||||
locateBlockByNumber(queriedBlockInfo, blockNumber);
|
||||
return queriedBlockInfo.getCheckType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of the Block that contains the byte at the given
|
||||
* uncompressed position.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if <code>pos < 0</code> or
|
||||
* <code>pos >= length()</code>.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public int getBlockNumber(long pos) {
|
||||
locateBlockByPos(queriedBlockInfo, pos);
|
||||
return queriedBlockInfo.blockNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses the next byte from this input stream.
|
||||
*
|
||||
* @return the next decompressed byte, or <code>-1</code> to indicate the
|
||||
* end of the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
* @throws UnsupportedOptionsException
|
||||
* @throws MemoryLimitException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return read(tempBuf, 0, 1) == -1 ? -1 : (tempBuf[0] & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses into an array of bytes.
|
||||
* <p>
|
||||
* If <code>len</code> is zero, no bytes are read and <code>0</code> is
|
||||
* returned. Otherwise this will try to decompress <code>len</code> bytes of
|
||||
* uncompressed data. Less than <code>len</code> bytes may be read only in
|
||||
* the following situations:
|
||||
* <ul>
|
||||
* <li>The end of the compressed data was reached successfully.</li>
|
||||
* <li>An error is detected after at least one but less than
|
||||
* <code>len</code> bytes have already been successfully decompressed. The
|
||||
* next call with non-zero <code>len</code> will immediately throw the
|
||||
* pending exception.</li>
|
||||
* <li>An exception is thrown.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param buf target buffer for uncompressed data
|
||||
* @param off start offset in <code>buf</code>
|
||||
* @param len maximum number of uncompressed bytes to read
|
||||
*
|
||||
* @return number of bytes read, or <code>-1</code> to indicate the end of
|
||||
* the compressed stream
|
||||
*
|
||||
* @throws CorruptedInputException
|
||||
* @throws UnsupportedOptionsException
|
||||
* @throws MemoryLimitException
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*
|
||||
* @throws IOException may be thrown by <code>in</code>
|
||||
*/
|
||||
public int read(byte[] buf, int off, int len) throws IOException {
|
||||
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
int size = 0;
|
||||
|
||||
try {
|
||||
if (seekNeeded)
|
||||
seek();
|
||||
|
||||
if (endReached)
|
||||
return -1;
|
||||
|
||||
while (len > 0) {
|
||||
if (blockDecoder == null) {
|
||||
seek();
|
||||
if (endReached)
|
||||
break;
|
||||
}
|
||||
|
||||
int ret = blockDecoder.read(buf, off, len);
|
||||
|
||||
if (ret > 0) {
|
||||
curPos += ret;
|
||||
size += ret;
|
||||
off += ret;
|
||||
len -= ret;
|
||||
} else if (ret == -1)
|
||||
blockDecoder = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// We know that the file isn't simply truncated because we could
|
||||
// parse the Indexes in the constructor. So convert EOFException
|
||||
// to CorruptedInputException.
|
||||
if (e instanceof EOFException)
|
||||
e = new CorruptedInputException();
|
||||
|
||||
exception = e;
|
||||
if (size == 0)
|
||||
throw e;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of uncompressed bytes that can be read without
|
||||
* blocking. The value is returned with an assumption that the compressed
|
||||
* input data will be valid. If the compressed data is corrupt,
|
||||
* <code>CorruptedInputException</code> may get thrown before the number of
|
||||
* bytes claimed to be available have been read from this input stream.
|
||||
*
|
||||
* @return the number of uncompressed bytes that can be read without
|
||||
* blocking
|
||||
*/
|
||||
public int available() throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (exception != null)
|
||||
throw exception;
|
||||
|
||||
if (endReached || seekNeeded || blockDecoder == null)
|
||||
return 0;
|
||||
|
||||
return blockDecoder.available();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and calls <code>in.close()</code>. If the stream was
|
||||
* already closed, this does nothing.
|
||||
*
|
||||
* @throws IOException if thrown by <code>in.close()</code>
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} finally {
|
||||
in = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uncompressed size of this input stream. If there are multiple XZ
|
||||
* Streams, the total uncompressed size of all XZ Streams is returned.
|
||||
*/
|
||||
public long length() {
|
||||
return uncompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current uncompressed position in this input stream.
|
||||
*
|
||||
* @throws XZIOException if the stream has been closed
|
||||
*/
|
||||
public long position() throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
return seekNeeded ? seekPos : curPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to the specified absolute uncompressed position in the stream. This
|
||||
* only stores the new position, so this function itself is always very
|
||||
* fast. The actual seek is done when <code>read</code> is called to read at
|
||||
* least one byte.
|
||||
* <p>
|
||||
* Seeking past the end of the stream is possible. In that case
|
||||
* <code>read</code> will return <code>-1</code> to indicate the end of the
|
||||
* stream.
|
||||
*
|
||||
* @param pos new uncompressed read position
|
||||
*
|
||||
* @throws XZIOException if <code>pos</code> is negative, or if stream has
|
||||
* been closed
|
||||
*/
|
||||
public void seek(long pos) throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (pos < 0)
|
||||
throw new XZIOException("Negative seek position: " + pos);
|
||||
|
||||
seekPos = pos;
|
||||
seekNeeded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to the beginning of the given XZ Block.
|
||||
*
|
||||
* @throws XZIOException if <code>blockNumber < 0</code> or
|
||||
* <code>blockNumber >= getBlockCount()</code>, or if stream
|
||||
* has been closed
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public void seekToBlock(int blockNumber) throws IOException {
|
||||
if (in == null)
|
||||
throw new XZIOException("Stream closed");
|
||||
|
||||
if (blockNumber < 0 || blockNumber >= blockCount)
|
||||
throw new XZIOException("Invalid XZ Block number: " + blockNumber);
|
||||
|
||||
// This is a bit silly implementation. Here we locate the uncompressed
|
||||
// offset of the specified Block, then when doing the actual seek in
|
||||
// seek(), we need to find the Block number based on seekPos.
|
||||
seekPos = getBlockPos(blockNumber);
|
||||
seekNeeded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual seeking. This is also called when <code>read</code> needs
|
||||
* a new Block to decode.
|
||||
*/
|
||||
private void seek() throws IOException {
|
||||
// If seek(long) wasn't called, we simply need to get the next Block
|
||||
// from the same Stream. If there are no more Blocks in this Stream,
|
||||
// then we behave as if seek(long) had been called.
|
||||
if (!seekNeeded) {
|
||||
if (curBlockInfo.hasNext()) {
|
||||
curBlockInfo.setNext();
|
||||
initBlockDecoder();
|
||||
return;
|
||||
}
|
||||
|
||||
seekPos = curPos;
|
||||
}
|
||||
|
||||
seekNeeded = false;
|
||||
|
||||
// Check if we are seeking to or past the end of the file.
|
||||
if (seekPos >= uncompressedSize) {
|
||||
curPos = seekPos;
|
||||
blockDecoder = null;
|
||||
endReached = true;
|
||||
return;
|
||||
}
|
||||
|
||||
endReached = false;
|
||||
|
||||
// Locate the Block that contains the uncompressed target position.
|
||||
locateBlockByPos(curBlockInfo, seekPos);
|
||||
|
||||
// Seek in the underlying stream and create a new Block decoder
|
||||
// only if really needed. We can skip it if the current position
|
||||
// is already in the correct Block and the target position hasn't
|
||||
// been decompressed yet.
|
||||
//
|
||||
// NOTE: If curPos points to the beginning of this Block, it's
|
||||
// because it was left there after decompressing an earlier Block.
|
||||
// In that case, decoding of the current Block hasn't been started
|
||||
// yet. (Decoding of a Block won't be started until at least one
|
||||
// byte will also be read from it.)
|
||||
if (!(curPos > curBlockInfo.uncompressedOffset && curPos <= seekPos)) {
|
||||
// Seek to the beginning of the Block.
|
||||
in.seek(curBlockInfo.compressedOffset);
|
||||
|
||||
// Since it is possible that this Block is from a different
|
||||
// Stream than the previous Block, initialize a new Check.
|
||||
check = Check.getInstance(curBlockInfo.getCheckType());
|
||||
|
||||
// Create a new Block decoder.
|
||||
initBlockDecoder();
|
||||
curPos = curBlockInfo.uncompressedOffset;
|
||||
}
|
||||
|
||||
// If the target wasn't at a Block boundary, decompress and throw
|
||||
// away data to reach the target position.
|
||||
if (seekPos > curPos) {
|
||||
// NOTE: The "if" below is there just in case. In this situation,
|
||||
// blockDecoder.skip will always skip the requested amount
|
||||
// or throw an exception.
|
||||
long skipAmount = seekPos - curPos;
|
||||
if (blockDecoder.skip(skipAmount) != skipAmount)
|
||||
throw new CorruptedInputException();
|
||||
|
||||
curPos = seekPos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the Block that contains the given uncompressed position.
|
||||
*/
|
||||
private void locateBlockByPos(BlockInfo info, long pos) {
|
||||
if (pos < 0 || pos >= uncompressedSize)
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid uncompressed position: " + pos);
|
||||
|
||||
// Locate the Stream that contains the target position.
|
||||
IndexDecoder index;
|
||||
for (int i = 0;; ++i) {
|
||||
index = (IndexDecoder) streams.get(i);
|
||||
if (index.hasUncompressedOffset(pos))
|
||||
break;
|
||||
}
|
||||
|
||||
// Locate the Block from the Stream that contains the target position.
|
||||
index.locateBlock(info, pos);
|
||||
|
||||
assert (info.compressedOffset & 3) == 0;
|
||||
assert info.uncompressedSize > 0;
|
||||
assert pos >= info.uncompressedOffset;
|
||||
assert pos < info.uncompressedOffset + info.uncompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the given Block and stores information about it to
|
||||
* <code>info</code>.
|
||||
*/
|
||||
private void locateBlockByNumber(BlockInfo info, int blockNumber) {
|
||||
// Validate.
|
||||
if (blockNumber < 0 || blockNumber >= blockCount)
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Invalid XZ Block number: " + blockNumber);
|
||||
|
||||
// Skip the search if info already points to the correct Block.
|
||||
if (info.blockNumber == blockNumber)
|
||||
return;
|
||||
|
||||
// Search the Stream that contains the given Block and then
|
||||
// search the Block from that Stream.
|
||||
for (int i = 0;; ++i) {
|
||||
IndexDecoder index = (IndexDecoder) streams.get(i);
|
||||
if (index.hasRecord(blockNumber)) {
|
||||
index.setBlockInfo(info, blockNumber);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new BlockInputStream. This is a helper function for
|
||||
* <code>seek()</code>.
|
||||
*/
|
||||
private void initBlockDecoder() throws IOException {
|
||||
try {
|
||||
// Set it to null first so that GC can collect it if memory
|
||||
// runs tight when initializing a new BlockInputStream.
|
||||
blockDecoder = null;
|
||||
blockDecoder = new BlockInputStream(in, check, memoryLimit,
|
||||
curBlockInfo.unpaddedSize, curBlockInfo.uncompressedSize);
|
||||
} catch (MemoryLimitException e) {
|
||||
// BlockInputStream doesn't know how much memory we had
|
||||
// already needed so we need to recreate the exception.
|
||||
assert memoryLimit >= 0;
|
||||
throw new MemoryLimitException(
|
||||
e.getMemoryNeeded() + indexMemoryUsage,
|
||||
memoryLimit + indexMemoryUsage);
|
||||
} catch (IndexIndicatorException e) {
|
||||
// It cannot be Index so the file must be corrupt.
|
||||
throw new CorruptedInputException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 254 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user