mirror of
https://bitbucket.org/projectswg/launcherupdater.git
synced 2026-01-15 21:04:24 -05:00
Initial commit
This commit is contained in:
7
.classpath
Normal file
7
.classpath
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-9"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/bin/
|
||||
/build/
|
||||
/.gradle/
|
||||
/.settings/
|
||||
/update/
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "PSWGCommon"]
|
||||
path = PSWGCommon
|
||||
url = git@bitbucket.org:projectswg/pswgcommon.git
|
||||
[submodule "PSWGCommonFX"]
|
||||
path = PSWGCommonFX
|
||||
url = git@bitbucket.org:projectswg/pswgcommonfx.git
|
||||
23
.project
Normal file
23
.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Launcher Updater</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
1
PSWGCommon
Submodule
1
PSWGCommon
Submodule
Submodule PSWGCommon added at 8505a996c3
1
PSWGCommonFX
Submodule
1
PSWGCommonFX
Submodule
Submodule PSWGCommonFX added at 32a80807c2
36
build.gradle
Normal file
36
build.gradle
Normal file
@@ -0,0 +1,36 @@
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'java'
|
||||
|
||||
mainClassName = 'com.projectswg.installer.LauncherUpdater'
|
||||
|
||||
manifest {
|
||||
attributes 'Main-Class': mainClassName
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ['src']
|
||||
includes ['**/*.java']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveName = "ProjectSWG.jar"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':PSWGCommon')
|
||||
compile project(':PSWGCommonFX')
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url 'https://plugins.gradle.org/m2/' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.0'
|
||||
}
|
||||
}
|
||||
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':PSWGCommon', ':PSWGCommonFX'
|
||||
89
src/com/projectswg/installer/LauncherUpdateGUI.java
Normal file
89
src/com/projectswg/installer/LauncherUpdateGUI.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
*
|
||||
* This file is part of ProjectSWG Launchpad.
|
||||
*
|
||||
* ProjectSWG Launchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ProjectSWG Launchpad 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with ProjectSWG Launchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.projectswg.installer;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class LauncherUpdateGUI extends Application {
|
||||
|
||||
private static final AtomicReference<LauncherUpdateGUI> INSTANCE = new AtomicReference<>(null);
|
||||
|
||||
private final ProgressBar progressBar;
|
||||
private final Label progressStatus;
|
||||
private final AtomicReference<Stage> primaryStage;
|
||||
|
||||
public LauncherUpdateGUI() {
|
||||
this.progressBar = new ProgressBar();
|
||||
this.progressStatus = new Label("");
|
||||
this.primaryStage = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
this.primaryStage.set(primaryStage);
|
||||
VBox root = new VBox();
|
||||
{
|
||||
VBox progress = new VBox();
|
||||
progress.setFillWidth(true);
|
||||
progress.setPrefWidth(700);
|
||||
progressBar.setMaxWidth(Double.MAX_VALUE);
|
||||
progressStatus.setMaxWidth(Double.MAX_VALUE);
|
||||
progress.getChildren().addAll(progressStatus, progressBar);
|
||||
root.getChildren().add(progress);
|
||||
}
|
||||
root.setFillWidth(false);
|
||||
root.setAlignment(Pos.CENTER);
|
||||
root.setPrefSize(786, 486);
|
||||
|
||||
primaryStage.setScene(new Scene(root));
|
||||
primaryStage.setTitle("Launcher Updater");
|
||||
primaryStage.show();
|
||||
LauncherUpdateGUI.INSTANCE.set(this);
|
||||
}
|
||||
|
||||
public void setProgress(double progress) {
|
||||
Platform.runLater(() -> progressBar.setProgress(progress));
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
Platform.runLater(() -> progressStatus.setText(status));
|
||||
}
|
||||
|
||||
public void launch(Application pswg) throws Exception {
|
||||
pswg.start(primaryStage.get());
|
||||
}
|
||||
|
||||
public void close() {
|
||||
primaryStage.get().close();
|
||||
}
|
||||
|
||||
public static LauncherUpdateGUI getInstance() {
|
||||
return INSTANCE.get();
|
||||
}
|
||||
|
||||
}
|
||||
94
src/com/projectswg/installer/LauncherUpdater.java
Normal file
94
src/com/projectswg/installer/LauncherUpdater.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
*
|
||||
* This file is part of ProjectSWG Launchpad.
|
||||
*
|
||||
* ProjectSWG Launchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ProjectSWG Launchpad 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with ProjectSWG Launchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.projectswg.installer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.control.IntentManager;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.common.debug.Log.LogLevel;
|
||||
import com.projectswg.common.debug.log_wrapper.ConsoleLogWrapper;
|
||||
import com.projectswg.common.javafx.ResourceUtilities;
|
||||
import com.projectswg.common.process.JarProcessBuilder;
|
||||
import com.projectswg.common.process.JarProcessBuilder.MemoryUnit;
|
||||
import com.projectswg.common.utilities.LocalUtilities;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
public class LauncherUpdater {
|
||||
|
||||
private static final String LOCAL_JAR_FILE = "Launcher.jar";
|
||||
|
||||
public static void main(String [] args) throws IOException {
|
||||
LocalUtilities.setApplicationName(".projectswg/launcher");
|
||||
ResourceUtilities.setPrimarySource(LauncherUpdater.class);
|
||||
Log.addWrapper(new ConsoleLogWrapper(LogLevel.VERBOSE));
|
||||
IntentManager.setInstance(new IntentManager(Runtime.getRuntime().availableProcessors()));
|
||||
|
||||
AtomicBoolean running = new AtomicBoolean(true);
|
||||
new Thread(() -> guiThread(args, running), "launcher-gui-thread").start();
|
||||
while (LauncherUpdateGUI.getInstance() == null) { // takes on average 500ms
|
||||
Delay.sleepMilli(50);
|
||||
}
|
||||
if (!running.get())
|
||||
return;
|
||||
if (!update(running)) {
|
||||
Log.w("Failed update.");
|
||||
Delay.sleepSeconds(5);
|
||||
}
|
||||
if (!running.get())
|
||||
return;
|
||||
launch();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static void guiThread(String [] args, AtomicBoolean running) {
|
||||
LauncherUpdateGUI.launch(LauncherUpdateGUI.class, args);
|
||||
// Hits this point if the updater was exited out of
|
||||
running.set(false);
|
||||
}
|
||||
|
||||
private static boolean update(AtomicBoolean running) {
|
||||
LauncherUpdateGUI gui = LauncherUpdateGUI.getInstance();
|
||||
return RemoteInstaller.install(running, (progress, status) -> {
|
||||
gui.setProgress(progress);
|
||||
gui.setStatus(status);
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean launch() {
|
||||
try {
|
||||
File updaterDirectory = LocalUtilities.getSubApplicationDirectory("updater");
|
||||
File java = new File(System.getProperty("java.home"), "bin/java");
|
||||
File src = new File(updaterDirectory, LOCAL_JAR_FILE);
|
||||
JarProcessBuilder builder = new JarProcessBuilder(java, src).setMemory(256, 256, MemoryUnit.MEGABYTES).inheritIO();
|
||||
Log.i("Launching jar... '%s'", src);
|
||||
Platform.runLater(LauncherUpdateGUI.getInstance()::close);
|
||||
builder.start().waitFor();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
304
src/com/projectswg/installer/RemoteInstaller.java
Normal file
304
src/com/projectswg/installer/RemoteInstaller.java
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
*
|
||||
* This file is part of ProjectSWG Launchpad.
|
||||
*
|
||||
* ProjectSWG Launchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ProjectSWG Launchpad 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with ProjectSWG Launchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.projectswg.installer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import com.projectswg.common.concurrency.Delay;
|
||||
import com.projectswg.common.debug.Assert;
|
||||
import com.projectswg.common.debug.Log;
|
||||
import com.projectswg.common.javafx.ResourceUtilities;
|
||||
import com.projectswg.common.utilities.LocalUtilities;
|
||||
import com.projectswg.installer.json.JSON;
|
||||
import com.projectswg.installer.json.JSONObject;
|
||||
|
||||
public class RemoteInstaller {
|
||||
|
||||
private static final String MANIFEST = "manifest.json";
|
||||
private static final String INSTALLER = "ProjectSWG.jar";
|
||||
private static final String LAUNCHER = "Launcher.jar";
|
||||
private static final String DATA = "data.zip";
|
||||
|
||||
private static final String REMOTE_URL = "patch1.projectswg.com";
|
||||
private static final String REMOTE_PATH = "launcher_patch";
|
||||
private static final String REMOTE_MANIFEST = REMOTE_PATH+'/'+MANIFEST;
|
||||
private static final String REMOTE_INSTALLER = REMOTE_PATH+'/'+INSTALLER;
|
||||
private static final String REMOTE_LAUNCHER = REMOTE_PATH+'/'+LAUNCHER;
|
||||
private static final String REMOTE_DATA = REMOTE_PATH+'/'+DATA;
|
||||
|
||||
private final InstallStatusCallback callback;
|
||||
private final File updateDirectory;
|
||||
private final AtomicReference<InstallStatus> status;
|
||||
private final AtomicReference<String> installerVersion;
|
||||
private final AtomicReference<String> launcherVersion;
|
||||
private final AtomicBoolean updateInstaller;
|
||||
private final AtomicBoolean updateLauncher;
|
||||
|
||||
private RemoteInstaller(InstallStatusCallback callback) {
|
||||
this.callback = callback;
|
||||
this.updateDirectory = LocalUtilities.getSubApplicationDirectory("updater");
|
||||
this.status = new AtomicReference<>(InstallStatus.CREATED);
|
||||
this.installerVersion = new AtomicReference<>(null);
|
||||
this.launcherVersion = new AtomicReference<>(null);
|
||||
this.updateInstaller = new AtomicBoolean(false);
|
||||
this.updateLauncher = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
private void initialize() throws InstallerException {
|
||||
verifyStatus(InstallStatus.CREATED, InstallStatus.INITIALIZED);
|
||||
try {
|
||||
File manifest = new File(updateDirectory, MANIFEST);
|
||||
if (!manifest.isFile()) {
|
||||
Log.d(" Manifest file missing");
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject obj = JSON.readObject(new FileInputStream(manifest), true);
|
||||
if (obj == null || !obj.containsKey("installerVersion") || !obj.containsKey("launcherVersion")) {
|
||||
Log.d(" Invalid local manifest file. %s", obj);
|
||||
return;
|
||||
}
|
||||
|
||||
installerVersion.set(obj.getString("installerVersion"));
|
||||
launcherVersion.set(obj.getString("launcherVersion"));
|
||||
Log.d(" Installer Version: %s", installerVersion.get());
|
||||
Log.d(" Launcher Version: %s", launcherVersion.get());
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
throw new InstallerException("initialize", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean testConnection() throws InstallerException {
|
||||
verifyStatus(InstallStatus.INITIALIZED, InstallStatus.CONNECTED);
|
||||
try {
|
||||
RemoteUtilities.testConnection(REMOTE_URL, REMOTE_MANIFEST);
|
||||
Log.d(" Valid connection.");
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void queryUpdates() throws InstallerException {
|
||||
verifyStatus(InstallStatus.CONNECTED, InstallStatus.QUERY_UPDATES);
|
||||
updateStatus(0, "Querying updates...");
|
||||
try {
|
||||
JSONObject obj = JSON.readObject(RemoteUtilities.fetch(REMOTE_URL, REMOTE_MANIFEST), true);
|
||||
if (obj == null || !obj.containsKey("installerVersion") || !obj.containsKey("launcherVersion"))
|
||||
throw new InstallerException("query updates", "Invalid remote manifest");
|
||||
|
||||
if (installerVersion.get() == null || launcherVersion.get() == null) {
|
||||
Log.d(" Downloading installer and launcher by default.");
|
||||
updateInstaller.set(true);
|
||||
updateLauncher.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
updateInstaller.set(installerVersion.get().compareTo(obj.getString("installerVersion")) < 0);
|
||||
updateLauncher.set(launcherVersion.get().compareTo(obj.getString("launcherVersion")) < 0);
|
||||
Log.d(" Update Installer: %b", updateInstaller.get());
|
||||
Log.d(" Update Launcher: %b", updateLauncher.get());
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
throw new InstallerException("query updates", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadUpdates() throws InstallerException {
|
||||
verifyStatus(InstallStatus.QUERY_UPDATES, InstallStatus.DOWNLOADING);
|
||||
if (updateInstaller.get()) {
|
||||
downloadInstallerUpdates();
|
||||
}
|
||||
if (updateLauncher.get()) {
|
||||
downloadLauncherUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadInstallerUpdates() throws InstallerException {
|
||||
updateStatus(.2, "Starting installer download...");
|
||||
try {
|
||||
Log.d(" Downloading installer...");
|
||||
RemoteUtilities.download(REMOTE_URL, REMOTE_INSTALLER, new File(ResourceUtilities.getSourceDirectory(), INSTALLER), (download, percent) -> updateStatus(.2+percent*0.8, "Downloading installer patch..."));
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
throw new InstallerException("downloading installer updates", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadLauncherUpdates() throws InstallerException {
|
||||
updateStatus(.2, "Starting launcher download...");
|
||||
try {
|
||||
Log.d(" Downloading launcher...");
|
||||
RemoteUtilities.download(REMOTE_URL, REMOTE_LAUNCHER, new File(updateDirectory, LAUNCHER), (download, percent) -> updateStatus(.2+percent*0.35, "Downloading launcher patch..."));
|
||||
Log.d(" Downloading data...");
|
||||
RemoteUtilities.download(REMOTE_URL, REMOTE_DATA, new File(updateDirectory, DATA), (download, percent) -> updateStatus(.55+percent*0.35, "Downloading data patch..."));
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
throw new InstallerException("downloading installer updates", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeDownload(AtomicBoolean running) throws InstallerException {
|
||||
try {
|
||||
RemoteUtilities.download(REMOTE_URL, REMOTE_MANIFEST, new File(updateDirectory, MANIFEST), null);
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
throw new InstallerException("complete download-update manifest", e);
|
||||
}
|
||||
if (updateLauncher.get()) {
|
||||
completeLauncherDownload();
|
||||
}
|
||||
if (updateInstaller.get()) {
|
||||
completeInstallerDownload(running);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeLauncherDownload() throws InstallerException {
|
||||
Log.d(" Unzipping data...");
|
||||
File data = new File(updateDirectory, DATA);
|
||||
byte[] buffer = new byte[16 * 1024];
|
||||
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(data))) {
|
||||
ZipEntry ze = zis.getNextEntry();
|
||||
while (ze != null) {
|
||||
String fileName = ze.getName();
|
||||
File newFile = new File(updateDirectory, fileName);
|
||||
if (ze.isDirectory()) {
|
||||
newFile.mkdirs();
|
||||
} else {
|
||||
Log.d(" Inflating %s...", newFile);
|
||||
try (FileOutputStream fos = new FileOutputStream(newFile)) {
|
||||
int len;
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
zis.closeEntry();
|
||||
ze = zis.getNextEntry();
|
||||
}
|
||||
// close last ZipEntry
|
||||
zis.closeEntry();
|
||||
zis.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(e);
|
||||
throw new InstallerException("unzipping data", e);
|
||||
} finally {
|
||||
data.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void completeInstallerDownload(AtomicBoolean running) throws InstallerException {
|
||||
Log.d(" Terminal operation. Waiting for user to restart launcher.");
|
||||
updateStatus(1, "Please restart the launcher to complete the update.");
|
||||
while (running.get()) { // Wait for user to restart the launcher
|
||||
Delay.sleepSeconds(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStatus(double progress, String status) {
|
||||
this.callback.update(progress, status);
|
||||
}
|
||||
|
||||
private void verifyStatus(InstallStatus oldStatus, InstallStatus newStatus) {
|
||||
Log.d("updateStatus(): %s -> %s", oldStatus, newStatus);
|
||||
Assert.test(status.compareAndSet(oldStatus, newStatus), "Unable to update status! Expected: " + oldStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe function to reinstall the application. All failures are handled
|
||||
* @param callback the callback for progress updates
|
||||
* @return TRUE if the launcher was successfully updated, FALSE otherwise
|
||||
*/
|
||||
public static boolean install(AtomicBoolean running, InstallStatusCallback callback) {
|
||||
try {
|
||||
RemoteInstaller installer = new RemoteInstaller(callback);
|
||||
if (!running.get())
|
||||
return true;
|
||||
installer.initialize();
|
||||
if (!running.get())
|
||||
return true;
|
||||
if (!installer.testConnection())
|
||||
return true;
|
||||
if (!running.get())
|
||||
return true;
|
||||
installer.queryUpdates();
|
||||
if (!running.get())
|
||||
return true;
|
||||
installer.downloadUpdates();
|
||||
if (!running.get())
|
||||
return true;
|
||||
installer.completeDownload(running);
|
||||
if (!running.get())
|
||||
return true;
|
||||
installer.verifyStatus(InstallStatus.DOWNLOADING, InstallStatus.DONE);
|
||||
return true;
|
||||
} catch (InstallerException e) {
|
||||
callback.update(-1, String.format("Failed '%s'. %s", e.getOperation(), e.getMessage()));
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
callback.update(-1, "Failed. " + t.getClass().getSimpleName() + ": " + t.getMessage());
|
||||
Log.e(t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface InstallStatusCallback {
|
||||
void update(double progress, String status);
|
||||
}
|
||||
|
||||
private static class InstallerException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String operation;
|
||||
|
||||
public InstallerException(String operation, String message) {
|
||||
super(message);
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public InstallerException(String operation, Throwable cause) {
|
||||
this(operation, cause.getClass().getSimpleName() + ": " + cause.getMessage());
|
||||
}
|
||||
|
||||
public String getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private enum InstallStatus {
|
||||
CREATED,
|
||||
INITIALIZED,
|
||||
CONNECTED,
|
||||
QUERY_UPDATES,
|
||||
DOWNLOADING,
|
||||
DONE
|
||||
}
|
||||
|
||||
}
|
||||
86
src/com/projectswg/installer/RemoteUtilities.java
Normal file
86
src/com/projectswg/installer/RemoteUtilities.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
*
|
||||
* This file is part of ProjectSWG Launchpad.
|
||||
*
|
||||
* ProjectSWG Launchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ProjectSWG Launchpad 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with ProjectSWG Launchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.projectswg.installer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class RemoteUtilities {
|
||||
|
||||
public static void testConnection(String host, String path) throws IOException {
|
||||
try (InputStream is = setupConnection(host, path, new AtomicLong())) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void download(String host, String path, File localPath, DownloadCallback callback) throws IOException {
|
||||
byte [] buffer = new byte[16 * 1024];
|
||||
AtomicLong expectedLength = new AtomicLong(-1);
|
||||
try (InputStream is = setupConnection(host, path, expectedLength)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(localPath)) {
|
||||
transfer(buffer, expectedLength.get(), is, fos, callback);
|
||||
fos.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String fetch(String host, String path) throws IOException {
|
||||
byte [] buffer = new byte[16 * 1024];
|
||||
AtomicLong expectedLength = new AtomicLong(-1);
|
||||
try (InputStream is = setupConnection(host, path, expectedLength)) {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
transfer(buffer, expectedLength.get(), is, baos, null);
|
||||
baos.flush();
|
||||
return baos.toString("ASCII");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void transfer(byte [] buffer, long expectedLength, InputStream is, OutputStream os, DownloadCallback callback) throws IOException {
|
||||
int bytesRead = 0;
|
||||
long downloaded = 0;
|
||||
while ((bytesRead = is.read(buffer)) > -1) {
|
||||
os.write(buffer, 0, bytesRead);
|
||||
downloaded += bytesRead;
|
||||
if (callback != null)
|
||||
callback.onDownloaded(downloaded, downloaded / (double) expectedLength);
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStream setupConnection(String host, String path, AtomicLong expectedLength) throws IOException {
|
||||
if (!path.startsWith("/"))
|
||||
path = '/' + path;
|
||||
URLConnection urlConnection = new URL("http", host, path).openConnection();
|
||||
urlConnection.connect();
|
||||
expectedLength.set(urlConnection.getContentLength());
|
||||
return urlConnection.getInputStream();
|
||||
}
|
||||
|
||||
public interface DownloadCallback {
|
||||
void onDownloaded(long downloaded, double percentage);
|
||||
}
|
||||
|
||||
}
|
||||
34
src/com/projectswg/installer/UpdaterResourceUtilities.java
Normal file
34
src/com/projectswg/installer/UpdaterResourceUtilities.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
*
|
||||
* This file is part of ProjectSWG Launchpad.
|
||||
*
|
||||
* ProjectSWG Launchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ProjectSWG Launchpad 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with ProjectSWG Launchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.projectswg.installer;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public class UpdaterResourceUtilities {
|
||||
|
||||
public static File getSourceDirectory() {
|
||||
try {
|
||||
return new File(UpdaterResourceUtilities.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile();
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
93
src/com/projectswg/installer/json/JSON.java
Normal file
93
src/com/projectswg/installer/json/JSON.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package com.projectswg.installer.json;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Provides convenience methods for stream operations that do automatic resource cleanup
|
||||
*/
|
||||
public class JSON {
|
||||
|
||||
/**
|
||||
* Opens a new JSONInputStream with the specified InputStream and reads a JSONObject. After
|
||||
* reading, the input streams are closed
|
||||
*
|
||||
* @param is the input stream to read from
|
||||
* @param printError TRUE if exception stack traces should be printed, FALSE otherwise
|
||||
* @return the JSONObject read from the stream, or null if there was an exception
|
||||
*/
|
||||
public static JSONObject readObject(InputStream is, boolean printError) {
|
||||
JSONInputStream in = new JSONInputStream(is);
|
||||
try {
|
||||
return in.readObject();
|
||||
} catch (IOException e) {
|
||||
if (printError)
|
||||
e.printStackTrace();
|
||||
} catch (JSONException e) {
|
||||
if (printError)
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
if (printError)
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new JSONInputStream with the specified string and reads a JSONObject
|
||||
*
|
||||
* @param str the string to read from
|
||||
* @param printError TRUE if exception stack traces should be printed, FALSE otherwise
|
||||
* @return the JSONObject read from the string, or null if there was an exception
|
||||
*/
|
||||
public static JSONObject readObject(String str, boolean printError) {
|
||||
return readObject(new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)), printError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new JSONInputStream with the specified InputStream and reads a JSONArray. After
|
||||
* reading, the input streams are closed
|
||||
*
|
||||
* @param is the input stream to read from
|
||||
* @param printError TRUE if exception stack traces should be printed, FALSE otherwise
|
||||
* @return the JSONArray read from the stream, or null if there was an exception
|
||||
*/
|
||||
public static JSONArray readArray(InputStream is, boolean printError) {
|
||||
JSONInputStream in = new JSONInputStream(is);
|
||||
try {
|
||||
return in.readArray();
|
||||
} catch (IOException e) {
|
||||
if (printError)
|
||||
e.printStackTrace();
|
||||
} catch (JSONException e) {
|
||||
if (printError)
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
if (printError)
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new JSONInputStream with the specified string and reads a JSONArray
|
||||
*
|
||||
* @param str the string to read from
|
||||
* @param printError TRUE if exception stack traces should be printed, FALSE otherwise
|
||||
* @return the JSONArray read from the string, or null if there was an exception
|
||||
*/
|
||||
public static JSONArray readArray(String str, boolean printError) {
|
||||
return readArray(new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)), printError);
|
||||
}
|
||||
|
||||
}
|
||||
332
src/com/projectswg/installer/json/JSONArray.java
Normal file
332
src/com/projectswg/installer/json/JSONArray.java
Normal file
@@ -0,0 +1,332 @@
|
||||
package com.projectswg.installer.json;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* This class contains a list of values, which can be one of the following types: JSONObject,
|
||||
* JSONArray, Number, Boolean, String, or null
|
||||
*/
|
||||
public class JSONArray implements List<Object>, Iterable<Object> {
|
||||
|
||||
private final ArrayList<Object> array; // Specifically ArrayList for null value support
|
||||
|
||||
public JSONArray() {
|
||||
array = new ArrayList<Object>();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return array.size();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
array.clear();
|
||||
}
|
||||
|
||||
public Object remove(int index) {
|
||||
if (index < 0 || index >= array.size())
|
||||
throw new IndexOutOfBoundsException("Specified index " + index + " is out of range! [0, " + size() + ")");
|
||||
return array.remove(index);
|
||||
}
|
||||
|
||||
public void add(int index, Object o) {
|
||||
if (o instanceof JSONObject || o instanceof JSONArray)
|
||||
array.add(index, o);
|
||||
else if (o instanceof Number || o instanceof Boolean)
|
||||
array.add(index, o);
|
||||
else if (o instanceof String)
|
||||
array.add(index, o);
|
||||
else if (o == null)
|
||||
array.add(index, null);
|
||||
else
|
||||
throw new IllegalArgumentException("Object must be of type: JSONObject, JSONArray, Number, Boolean, String, or null!");
|
||||
}
|
||||
|
||||
public boolean add(Object o) {
|
||||
if (o instanceof JSONObject)
|
||||
add((JSONObject) o);
|
||||
else if (o instanceof JSONArray)
|
||||
add((JSONArray) o);
|
||||
else if (o instanceof Number)
|
||||
add((Number) o);
|
||||
else if (o instanceof Boolean)
|
||||
add((Boolean) o);
|
||||
else if (o instanceof String)
|
||||
add((String) o);
|
||||
else if (o == null)
|
||||
addNull();
|
||||
else
|
||||
throw new IllegalArgumentException("Object must be of type: JSONObject, JSONArray, Number, Boolean, String, or null!");
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends Object> c) {
|
||||
ensureCapacity(size() + c.size());
|
||||
for (Object o : c) {
|
||||
add(o);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean addAll(int index, Collection<? extends Object> c) {
|
||||
ensureCapacity(size() + c.size());
|
||||
int i = index;
|
||||
for (Object o : c) {
|
||||
add(i++, o);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return array.containsAll(c);
|
||||
}
|
||||
|
||||
public void ensureCapacity(int minCapacity) {
|
||||
array.ensureCapacity(minCapacity);
|
||||
}
|
||||
|
||||
public int indexOf(Object o) {
|
||||
return array.indexOf(o);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return array.isEmpty();
|
||||
}
|
||||
|
||||
public int lastIndexOf(Object o) {
|
||||
return array.lastIndexOf(o);
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
return array.remove(o);
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return array.removeAll(c);
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return array.retainAll(c);
|
||||
}
|
||||
|
||||
public Object set(int index, Object element) {
|
||||
return array.set(index, element);
|
||||
}
|
||||
|
||||
public List<Object> subList(int fromIndex, int toIndex) {
|
||||
return array.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
return array.toArray();
|
||||
}
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return array.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<Object> listIterator() {
|
||||
return array.listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<Object> listIterator(int index) {
|
||||
return array.listIterator(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a JSONObject to the array
|
||||
*
|
||||
* @param obj the JSONObject to add
|
||||
*/
|
||||
public void add(JSONObject obj) {
|
||||
array.add(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a JSONArray to the array
|
||||
*
|
||||
* @param array the JSONArray to add
|
||||
*/
|
||||
public void add(JSONArray array) {
|
||||
this.array.add(array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number to the array
|
||||
*
|
||||
* @param n the number to add
|
||||
*/
|
||||
public void add(Number n) {
|
||||
array.add(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a boolean to the array
|
||||
*
|
||||
* @param b the boolean to add
|
||||
*/
|
||||
public void add(Boolean b) {
|
||||
array.add(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a string to the array
|
||||
*
|
||||
* @param str the string to add
|
||||
*/
|
||||
public void add(String str) {
|
||||
array.add(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a null value to the array
|
||||
*/
|
||||
public void addNull() {
|
||||
array.add(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object will always be one
|
||||
* of the supported JSON types: JSONObject, JSONArray, Number, Boolean, String, or null
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the object at the specified index
|
||||
*/
|
||||
public Object get(int index) {
|
||||
return array.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a
|
||||
* JSONObject
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the object at the specified index
|
||||
* @throws ClassCastException if the object is not a JSONObject
|
||||
*/
|
||||
public JSONObject getObject(int index) {
|
||||
return (JSONObject) get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a
|
||||
* JSONArray
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the array at the specified index
|
||||
* @throws ClassCastException if the object is not a JSONArray
|
||||
*/
|
||||
public JSONArray getArray(int index) {
|
||||
return (JSONArray) get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a int
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the int at the specified index
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
public int getInt(int index) {
|
||||
return ((Number) get(index)).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a
|
||||
* long
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the long at the specified index
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
public long getLong(int index) {
|
||||
return ((Number) get(index)).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a
|
||||
* float
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the float at the specified index
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
public float getFloat(int index) {
|
||||
return ((Number) get(index)).floatValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a
|
||||
* double
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the double at the specified index
|
||||
* @throws NullPointerException if the object is null
|
||||
*/
|
||||
public double getDouble(int index) {
|
||||
return ((Number) get(index)).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a
|
||||
* boolean
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the boolean at the specified index
|
||||
* @throws NullPointerException if the object is null
|
||||
* @throws ClassCastException if the object is not a boolean
|
||||
*/
|
||||
public boolean getBoolean(int index) {
|
||||
return (boolean) get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object at the specified index from the array. The returned object is casted to a
|
||||
* String
|
||||
*
|
||||
* @param index the index to retrieve over the interval [0, size())
|
||||
* @return the string at the specified index
|
||||
* @throws ClassCastException if the object is not a String
|
||||
*/
|
||||
public String getString(int index) {
|
||||
return (String) get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the specified object exists inside this array
|
||||
*
|
||||
* @param o the object to search for within the array
|
||||
* @return TRUE if the object exists, FALSE otherwise
|
||||
*/
|
||||
public boolean contains(Object o) {
|
||||
return array.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return array.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON string (RFC 4627) containing this array
|
||||
*
|
||||
* @return a JSON string compatible with RFC 4627
|
||||
*/
|
||||
public String toString() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new JSONOutputStream(baos).writeArray(this);
|
||||
} catch (IOException e) {
|
||||
return "Failed: " + e.getMessage();
|
||||
}
|
||||
return baos.toString();
|
||||
}
|
||||
|
||||
}
|
||||
11
src/com/projectswg/installer/json/JSONException.java
Normal file
11
src/com/projectswg/installer/json/JSONException.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.projectswg.installer.json;
|
||||
|
||||
public class JSONException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public JSONException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
296
src/com/projectswg/installer/json/JSONInputStream.java
Normal file
296
src/com/projectswg/installer/json/JSONInputStream.java
Normal file
@@ -0,0 +1,296 @@
|
||||
package com.projectswg.installer.json;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* This input stream will read RFC 4627 compatible JSON strings from either a string or an input
|
||||
* stream
|
||||
*/
|
||||
public class JSONInputStream extends InputStream {
|
||||
|
||||
private final InputStream is;
|
||||
private final byte[] buffer;
|
||||
private int bufferPos;
|
||||
private int bufferSize;
|
||||
private char peek;
|
||||
private boolean hasPeek;
|
||||
private boolean previousTokenString;
|
||||
|
||||
/**
|
||||
* Creates a new input stream around the specified string
|
||||
*
|
||||
* @param str a RFC 4627 JSON string
|
||||
*/
|
||||
public JSONInputStream(String str) {
|
||||
this(new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new input stream around the specified input stream
|
||||
*
|
||||
* @param is the input stream pointing to the RFC 4627 JSON string
|
||||
*/
|
||||
public JSONInputStream(InputStream is) {
|
||||
this.is = is;
|
||||
this.buffer = new byte[256];
|
||||
this.bufferPos = 0;
|
||||
this.bufferSize = 0;
|
||||
this.peek = 0;
|
||||
this.hasPeek = false;
|
||||
this.previousTokenString = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a JSONObject from the stream
|
||||
*
|
||||
* @return the read JSONObject
|
||||
* @throws IOException if there is an exception within the input stream
|
||||
* @throws JSONException if there is a JSON parsing error
|
||||
*/
|
||||
public JSONObject readObject() throws IOException, JSONException {
|
||||
readAssert(getNextToken().equals("{"), "JSONObject must start with '{'");
|
||||
return getNextObjectInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a JSONArray from the stream
|
||||
*
|
||||
* @return the read JSONArray
|
||||
* @throws IOException if there is an exception within the input stream
|
||||
* @throws JSONException if there is a JSON parsing error
|
||||
*/
|
||||
public JSONArray readArray() throws IOException, JSONException {
|
||||
readAssert(getNextToken().equals("["), "JSONArray must start with '['");
|
||||
return getNextArrayInternal();
|
||||
}
|
||||
|
||||
private JSONObject getNextObjectInternal() throws IOException, JSONException {
|
||||
JSONObject obj = new JSONObject();
|
||||
String token = getNextToken();
|
||||
while (!token.equals("}")) {
|
||||
String key = token;
|
||||
readAssert(getNextToken().equals(":"), "Attributes must be key-value pairs separated by ':'");
|
||||
token = getNextToken();
|
||||
if (previousTokenString)
|
||||
obj.put(key, token);
|
||||
else if (token.equals("null"))
|
||||
obj.putNull(key);
|
||||
else if (token.equals("false"))
|
||||
obj.put(key, false);
|
||||
else if (token.equals("true"))
|
||||
obj.put(key, true);
|
||||
else if (token.equals("["))
|
||||
obj.put(key, getNextArrayInternal());
|
||||
else if (token.equals("{"))
|
||||
obj.put(key, getNextObjectInternal());
|
||||
else {
|
||||
try {
|
||||
if (token.indexOf('.') != -1)
|
||||
obj.put(key, Double.valueOf(token));
|
||||
else
|
||||
obj.put(key, Long.valueOf(token));
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
token = getNextToken();
|
||||
if (!token.equals("}")) {
|
||||
readAssert(token.equals(","), "Attributes must be key-value pairs enumerated by ','");
|
||||
token = getNextToken();
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private JSONArray getNextArrayInternal() throws IOException, JSONException {
|
||||
JSONArray array = new JSONArray();
|
||||
String token = getNextToken();
|
||||
while (!token.equals("]")) {
|
||||
if (previousTokenString)
|
||||
array.add(token);
|
||||
else if (token.equals("null"))
|
||||
array.addNull();
|
||||
else if (token.equals("false"))
|
||||
array.add(false);
|
||||
else if (token.equals("true"))
|
||||
array.add(true);
|
||||
else if (token.equals("["))
|
||||
array.add(getNextArrayInternal());
|
||||
else if (token.equals("{"))
|
||||
array.add(getNextObjectInternal());
|
||||
else {
|
||||
try {
|
||||
if (token.indexOf('.') != -1)
|
||||
array.add(Double.valueOf(token));
|
||||
else
|
||||
array.add(Long.valueOf(token));
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
token = getNextToken();
|
||||
if (!token.equals("]")) {
|
||||
readAssert(token.equals(","), "Values must be enumerated by ','");
|
||||
token = getNextToken();
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private String getNextToken() throws IOException, JSONException {
|
||||
char c = ingestWhitespace();
|
||||
previousTokenString = false;
|
||||
if (c == 0)
|
||||
return "";
|
||||
if (c == '{' || c == '[' || c == '}' || c == ']' || c == ',' || c == ':') {
|
||||
return Character.toString(c);
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (c == '\"') {
|
||||
getNextTokenString(builder);
|
||||
previousTokenString = true;
|
||||
} else {
|
||||
builder.append(c);
|
||||
getNextTokenOther(builder);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void readAssert(boolean val, String message) throws JSONException {
|
||||
if (!val)
|
||||
throw new JSONException(message);
|
||||
}
|
||||
|
||||
private void getNextTokenString(StringBuilder builder) throws IOException, JSONException {
|
||||
char c = readChar();
|
||||
boolean prevEscape = false;
|
||||
while (c != '\"' || prevEscape) {
|
||||
if (c != '\\' || prevEscape)
|
||||
builder.append(c);
|
||||
prevEscape = (c == '\\' && !prevEscape);
|
||||
c = readChar();
|
||||
if (prevEscape) {
|
||||
switch (c) {
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case 'b':
|
||||
c = '\b';
|
||||
break;
|
||||
case 'u':
|
||||
c = (char) Integer.valueOf("" + readChar() + readChar() + readChar() + readChar(), 16).intValue();
|
||||
break;
|
||||
case '\"':
|
||||
case '\\':
|
||||
break;
|
||||
default:
|
||||
throw new JSONException("Unknown escaped character: " + c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getNextTokenOther(StringBuilder builder) throws IOException {
|
||||
while (isLetterOrNumber(peekChar())) {
|
||||
builder.append(readChar());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLetterOrNumber(char c) {
|
||||
return isUppercase(c) || isLowercase(c) || isDigit(c) || isExtraNumCharacter(c);
|
||||
}
|
||||
|
||||
private boolean isUppercase(char c) {
|
||||
return c >= 'A' && c <= 'Z';
|
||||
}
|
||||
|
||||
private boolean isLowercase(char c) {
|
||||
return c >= 'a' && c <= 'z';
|
||||
}
|
||||
|
||||
private boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private boolean isExtraNumCharacter(char c) {
|
||||
return c == '.' || c == '-' || c == 'E' || c == 'e' || c == '+';
|
||||
}
|
||||
|
||||
private boolean isWhitespace(char c) {
|
||||
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
|
||||
}
|
||||
|
||||
private char ingestWhitespace() throws IOException {
|
||||
char c;
|
||||
do {
|
||||
c = readChar();
|
||||
} while (isWhitespace(c));
|
||||
return (char) c;
|
||||
}
|
||||
|
||||
public char peekChar() throws IOException {
|
||||
if (hasPeek)
|
||||
return peek;
|
||||
peek = readChar();
|
||||
hasPeek = true;
|
||||
return peek;
|
||||
}
|
||||
|
||||
public char readChar() throws IOException {
|
||||
if (hasPeek) {
|
||||
hasPeek = false;
|
||||
return peek;
|
||||
}
|
||||
int r = read();
|
||||
if (r == -1)
|
||||
return 0;
|
||||
return (char) r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (bufferPos >= bufferSize) {
|
||||
bufferSize = read(buffer, 0, Math.min(buffer.length, available()));
|
||||
bufferPos = 0;
|
||||
if (bufferSize < 0)
|
||||
return -1;
|
||||
if (bufferSize == 0)
|
||||
return is.read();
|
||||
}
|
||||
return buffer[bufferPos++];
|
||||
}
|
||||
|
||||
public int read(byte[] b) throws IOException {
|
||||
return is.read(b);
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return is.read(b, off, len);
|
||||
}
|
||||
|
||||
public long skip(long n) throws IOException {
|
||||
return is.skip(n);
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return is.available();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
is.close();
|
||||
}
|
||||
|
||||
public void reset() throws IOException {
|
||||
is.reset();
|
||||
}
|
||||
|
||||
}
|
||||
317
src/com/projectswg/installer/json/JSONObject.java
Normal file
317
src/com/projectswg/installer/json/JSONObject.java
Normal file
@@ -0,0 +1,317 @@
|
||||
package com.projectswg.installer.json;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InvalidClassException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class contains key-value pairs where the key is a string and the value is one of the
|
||||
* following types: JSONObject, JSONArray, Number, Boolean, String, or null
|
||||
*/
|
||||
public class JSONObject implements Map<String, Object> {
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
public JSONObject() {
|
||||
attributes = new LinkedHashMap<String, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value pairs in the map
|
||||
*
|
||||
* @return the number of key-value pairs in the map
|
||||
*/
|
||||
public int size() {
|
||||
return attributes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal map of all key-value pairs
|
||||
*/
|
||||
public void clear() {
|
||||
attributes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the key-value pair from the map
|
||||
*
|
||||
* @param key the key to remove
|
||||
*/
|
||||
public Object remove(String key) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
return attributes.remove(key);
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
return attributes.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean containsValue(Object value) {
|
||||
return attributes.containsValue(value);
|
||||
}
|
||||
|
||||
public Object get(Object key) {
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return attributes.isEmpty();
|
||||
}
|
||||
|
||||
public Object put(String key, Object value) {
|
||||
if (value instanceof JSONObject || value instanceof JSONArray)
|
||||
return attributes.put(key, value);
|
||||
if (value instanceof Number || value instanceof Boolean)
|
||||
return attributes.put(key, value);
|
||||
if (value instanceof String)
|
||||
return attributes.put(key, value);
|
||||
if (value == null)
|
||||
return attributes.put(key, null);
|
||||
throw new IllegalArgumentException("Value must be of type: JSONObject, JSONArray, Number, Boolean, String, or null!");
|
||||
}
|
||||
|
||||
public void putAll(Map<? extends String, ? extends Object> m) {
|
||||
for (Entry<? extends String, ? extends Object> e : m.entrySet()) {
|
||||
put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public Object remove(Object key) {
|
||||
return attributes.remove(key);
|
||||
}
|
||||
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
return attributes.entrySet();
|
||||
}
|
||||
|
||||
public Set<String> keySet() {
|
||||
return attributes.keySet();
|
||||
}
|
||||
|
||||
public Collection<Object> values() {
|
||||
return attributes.values();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return attributes.equals(o);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return attributes.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a JSONObject into the map with the specified key
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @param value the value associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
*/
|
||||
public void put(String key, JSONObject value) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a JSONArray into the map with the specified key
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @param value the value associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
*/
|
||||
public void put(String key, JSONArray value) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a number into the map with the specified key
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @param value the value associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
*/
|
||||
public void put(String key, Number value) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a boolean into the map with the specified key
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @param value the value associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
*/
|
||||
public void put(String key, Boolean value) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a string into the map with the specified key
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @param value the value associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
*/
|
||||
public void put(String key, String value) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a null value into the map with the specified key
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @param value the value associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
*/
|
||||
public void putNull(String key) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
attributes.put(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value will always be a supported JSON
|
||||
* object: JSONObject, JSONArray, Number, Boolean, String, or null
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the value associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
*/
|
||||
public Object get(String key) {
|
||||
if (key == null)
|
||||
throw new NullPointerException("Key cannot be null!");
|
||||
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a JSONObject
|
||||
* internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the JSONObject associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
* @throws InvalidClassException if the object is not a JSONObject
|
||||
*/
|
||||
public JSONObject getObject(String key) {
|
||||
return (JSONObject) get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a JSONArray
|
||||
* internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the JSONArray associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
* @throws InvalidClassException if the object is not a JSONArray
|
||||
*/
|
||||
public JSONArray getArray(String key) {
|
||||
return (JSONArray) get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a int internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the int associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null or if the value is null
|
||||
*/
|
||||
public int getInt(String key) {
|
||||
return ((Number) get(key)).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a long internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the long associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null or if the value is null
|
||||
*/
|
||||
public long getLong(String key) {
|
||||
return ((Number) get(key)).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a float internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the float associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null or if the value is null
|
||||
*/
|
||||
public float getFloat(String key) {
|
||||
return ((Number) get(key)).floatValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a double internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the double associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null or if the value is null
|
||||
*/
|
||||
public double getDouble(String key) {
|
||||
return ((Number) get(key)).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a boolean internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the boolean associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null or if the value is null
|
||||
* @throws InvalidClassException if the object is not a boolean
|
||||
*/
|
||||
public boolean getBoolean(String key) {
|
||||
return (boolean) get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated with the specified key. The value is casted to a String internally
|
||||
*
|
||||
* @param key the key for the map
|
||||
* @return the String associated with the specified key
|
||||
* @throws NullPointerException if the specified key is null
|
||||
* @throws InvalidClassException if the object is not a String
|
||||
*/
|
||||
public String getString(String key) {
|
||||
return (String) get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON string (RFC 4627) containing this object
|
||||
*
|
||||
* @return a JSON string compatible with RFC 4627
|
||||
*/
|
||||
public String toString() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
new JSONOutputStream(baos).writeObject(this);
|
||||
} catch (IOException e) {
|
||||
return "Failed: " + e.getMessage();
|
||||
}
|
||||
return baos.toString();
|
||||
}
|
||||
|
||||
}
|
||||
202
src/com/projectswg/installer/json/JSONOutputStream.java
Normal file
202
src/com/projectswg/installer/json/JSONOutputStream.java
Normal file
@@ -0,0 +1,202 @@
|
||||
package com.projectswg.installer.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This output stream will write RFC 4627 JSON strings out
|
||||
*/
|
||||
public class JSONOutputStream extends OutputStream {
|
||||
|
||||
private final OutputStream os;
|
||||
private String indentation;
|
||||
private boolean compact;
|
||||
|
||||
/**
|
||||
* Wraps this JSON output stream around the specified output stream
|
||||
*
|
||||
* @param os the output stream to wrap
|
||||
*/
|
||||
public JSONOutputStream(OutputStream os) {
|
||||
this.os = os;
|
||||
this.indentation = " ";
|
||||
this.compact = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the indentation for each additional tab. This is not used if compact is set to true
|
||||
*
|
||||
* @param indentation the indentation for each tab
|
||||
* @see setCompact
|
||||
*/
|
||||
public void setIndentation(String indentation) {
|
||||
this.indentation = indentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mode to compact. If TRUE, there is no indentation or newlines
|
||||
*
|
||||
* @param compact TRUE to enable compact, FALSE otherwise
|
||||
*/
|
||||
public void setCompact(boolean compact) {
|
||||
this.compact = compact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified JSONObject to the output stream
|
||||
*
|
||||
* @param obj the JSONObject to write
|
||||
* @throws IOException if there is an I/O error
|
||||
*/
|
||||
public void writeObject(JSONObject obj) throws IOException {
|
||||
writeObject(obj, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified JSONArray to the output stream
|
||||
*
|
||||
* @param array the JSONArray to write
|
||||
* @throws IOException if there is an I/O error
|
||||
*/
|
||||
public void writeArray(JSONArray array) throws IOException {
|
||||
writeArray(array, 0);
|
||||
}
|
||||
|
||||
private void writeObject(JSONObject obj, int depth) throws IOException {
|
||||
write('{');
|
||||
if (!compact)
|
||||
write('\n');
|
||||
int i = 0;
|
||||
Set<String> keys = obj.keySet();
|
||||
for (String key : keys) {
|
||||
if (!compact)
|
||||
writeIndentation(depth + 1);
|
||||
writeStringSafe("\"" + escapeString(key) + "\": ");
|
||||
writeValue(obj.get(key), depth + 1);
|
||||
if (i + 1 < keys.size())
|
||||
write(',');
|
||||
if (!compact)
|
||||
write('\n');
|
||||
i++;
|
||||
}
|
||||
if (!compact)
|
||||
writeIndentation(depth);
|
||||
write('}');
|
||||
}
|
||||
|
||||
private void writeArray(JSONArray array, int depth) throws IOException {
|
||||
write('[');
|
||||
if (!compact)
|
||||
write('\n');
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
if (!compact)
|
||||
writeIndentation(depth + 1);
|
||||
Object o = array.get(i);
|
||||
writeValue(o, depth + 1);
|
||||
if (i + 1 < array.size())
|
||||
write(',');
|
||||
if (!compact)
|
||||
write('\n');
|
||||
}
|
||||
if (!compact)
|
||||
writeIndentation(depth);
|
||||
write(']');
|
||||
}
|
||||
|
||||
private void writeValue(Object o, int depth) throws IOException {
|
||||
if (o instanceof String) // String
|
||||
writeStringSafe("\"" + escapeString((String) o) + "\"");
|
||||
else if (o instanceof Number) // Number
|
||||
writeNumber((Number) o);
|
||||
else if (o instanceof Boolean) // Boolean
|
||||
writeString(o.toString());
|
||||
else if (o == null) // Null
|
||||
writeString("null");
|
||||
else if (o instanceof JSONObject) // Object
|
||||
writeObject(((JSONObject) o), depth);
|
||||
else if (o instanceof JSONArray) // Array
|
||||
writeArray(((JSONArray) o), depth);
|
||||
}
|
||||
|
||||
private void writeNumber(Number n) throws IOException {
|
||||
if (n instanceof Double && (Double.isNaN((Double) n) || Double.isInfinite((Double) n))) {
|
||||
write('0');
|
||||
return;
|
||||
}
|
||||
if (n instanceof Float && (Float.isNaN((Float) n) || Float.isInfinite((Float) n))) {
|
||||
write('0');
|
||||
return;
|
||||
}
|
||||
writeString(n.toString());
|
||||
}
|
||||
|
||||
private void writeIndentation(int depth) throws IOException {
|
||||
for (int i = 0; i < depth; ++i)
|
||||
writeString(indentation);
|
||||
}
|
||||
|
||||
private void writeString(String str) throws IOException {
|
||||
for (int i = 0; i < str.length(); ++i)
|
||||
write(str.charAt(i));
|
||||
}
|
||||
|
||||
private void writeStringSafe(String str) throws IOException {
|
||||
write(str.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private String escapeString(String str) {
|
||||
StringBuilder builder = new StringBuilder(str.length());
|
||||
char c;
|
||||
for (int i = 0; i < str.length(); ++i) {
|
||||
c = str.charAt(i);
|
||||
if (c == '\\' || c == '\"') {
|
||||
builder.append('\\');
|
||||
builder.append(c);
|
||||
} else if (c <= 0x1F) {
|
||||
builder.append('\\');
|
||||
switch (c) {
|
||||
case '\n':
|
||||
builder.append('n');
|
||||
break;
|
||||
case '\r':
|
||||
builder.append('r');
|
||||
break;
|
||||
case '\t':
|
||||
builder.append('t');
|
||||
break;
|
||||
case '\b':
|
||||
builder.append('b');
|
||||
break;
|
||||
default:
|
||||
builder.append(String.format("u%04X", (int) c));
|
||||
break;
|
||||
}
|
||||
} else
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
os.write(b);
|
||||
}
|
||||
|
||||
public void write(byte[] b) throws IOException {
|
||||
os.write(b);
|
||||
}
|
||||
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
os.write(b, off, len);
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
os.flush();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
os.close();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user