Compare commits

...
7 Commits
Author SHA1 Message Date
huangyuhui 80e60d2753 change version to 2.3.5.9 2016-01-29 22:25:14 +08:00
huangyuhui ff0750f1f7 To make death, I removed xz processor 2016-01-29 22:23:50 +08:00
huangyuhui f8eec1b7d5 Each version has its own settings 2016-01-29 22:21:13 +08:00
huangyuhui d2162f31e4 Clean tukaani.xz 2016-01-26 20:54:52 +08:00
huangyuhui fee3e5251d Try to fix warnings by NetBeans 2016-01-26 20:17:05 +08:00
huangyuhui 409fc83819 To make death I removed RxJava 2016-01-26 19:50:26 +08:00
huangyuhui 54e14a25da choosing files for modpack 2016-01-24 19:54:02 +08:00
254 changed files with 2648 additions and 23826 deletions

No files matched your search

+1 -1
View File
@@ -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");
@@ -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
@@ -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".
@@ -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
@@ -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]));
}
};
}
}
@@ -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;
}
@@ -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;
@@ -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();
}
@@ -74,7 +74,8 @@ public class MinecraftModService extends IMinecraftModService {
}
}
Collections.sort(mods);
return modCache.put(id, mods);
modCache.put(id, mods);
return mods;
}
@Override
@@ -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" });
/**
* &lt; String, Boolean, Boolean &gt;: 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)
@@ -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();
}
@@ -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();
}
}
@@ -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" };
@@ -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;
}
}
@@ -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);
@@ -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/&lt;version&gt;/<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() {
@@ -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/&lt;version&gt;/<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();
}
}
@@ -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));
}
@@ -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() {
@@ -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(&quot;{key}&quot;)"/>
<ResourceString bundle="org/jackhuang/hellominecraft/launcher/I18N.properties" key="settings.run_directory" replaceFormat="C.i18n(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
<ResourceString bundle="org/jackhuang/hellominecraft/launcher/I18N.properties" key="advancedsettings.debug_mode" replaceFormat="C.i18n(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
@@ -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();
}
@@ -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());
}
@@ -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>
@@ -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
}
@@ -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="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JCheckBox" name="chkSave">
<Properties>
<Property name="text" type="java.lang.String" value="&#x5141;&#x8bb8;&#x5bfc;&#x51fa;&#x5b58;&#x6863;"/>
</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">
@@ -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;
@@ -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();
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
}
@@ -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;
}
@@ -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();
@@ -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"));
@@ -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
@@ -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;
@@ -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();
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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) {
@@ -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;
}
}
@@ -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;
}
@@ -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;
}
@@ -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);
}
}
@@ -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);
}
@@ -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&nbsp;MiB, 16&nbsp;MiB, or
* 32&nbsp;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&nbsp;+&nbsp;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&nbsp;+&nbsp;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&nbsp;% 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&nbsp;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&nbsp;&lt;&lt;&nbsp;XZ.CHECK_CRC64)
* | (1&nbsp;&lt;&lt;&nbsp;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&nbsp;&lt;&nbsp;0</code> or
* <code>blockNumber&nbsp;&gt;=&nbsp;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&nbsp;&lt;&nbsp;0</code> or
* <code>blockNumber&nbsp;&gt;=&nbsp;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&nbsp;&lt;&nbsp;0</code> or
* <code>blockNumber&nbsp;&gt;=&nbsp;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&nbsp;&lt;&nbsp;0</code> or
* <code>blockNumber&nbsp;&gt;=&nbsp;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&nbsp;&lt;&nbsp;0</code> or
* <code>blockNumber&nbsp;&gt;=&nbsp;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&nbsp;&lt;&nbsp;0</code> or
* <code>pos&nbsp;&gt;=&nbsp;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&nbsp;&lt;&nbsp;0</code> or
* <code>blockNumber&nbsp;&gt;=&nbsp;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