diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b26060e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+#IDE
+.idea/
+*.iml
+/.settings
+/bin
+/.classpath
+/.project
+
+#Gradle
+build/
+.gradle
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7d65bf1
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'java'
+
+sourceCompatibility = '1.8'
+targetCompatibility = '1.8'
+compileJava.options.encoding = 'UTF-8'
+version = '1.0'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'commons-io:commons-io:2.4'
+ compile 'net.sf.jopt-simple:jopt-simple:4.5'
+}
+
+jar {
+ from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
+ manifest {
+ attributes("Main-Class": "org.ultramine.bootstrap.Main")
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..3c7abdf
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..eb67457
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Apr 24 12:13:23 VLAT 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-2.10-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..5390861
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'ultramine_bootstrap'
+
diff --git a/src/main/java/org/ultramine/bootstrap/Constants.java b/src/main/java/org/ultramine/bootstrap/Constants.java
new file mode 100644
index 0000000..fb0da10
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/Constants.java
@@ -0,0 +1,8 @@
+package org.ultramine.bootstrap;
+
+public class Constants
+{
+ public static final String UM_REPO = "http://maven.ultramine.ru";
+ public static final String UM_CORE_GROUP = "org.ultramine.core";
+ public static final String UM_CORE_NAME = "ultramine_core-1.7.10-server";
+}
diff --git a/src/main/java/org/ultramine/bootstrap/Main.java b/src/main/java/org/ultramine/bootstrap/Main.java
new file mode 100644
index 0000000..a0d69b3
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/Main.java
@@ -0,0 +1,135 @@
+package org.ultramine.bootstrap;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+import org.ultramine.bootstrap.maven.MavenDependency;
+import org.ultramine.bootstrap.exceptions.ApplicationErrorException;
+import org.ultramine.bootstrap.task.DependencyResolver;
+import org.ultramine.bootstrap.task.ScriptCreator;
+import org.ultramine.bootstrap.util.I18n;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Locale;
+import java.util.Set;
+
+public class Main
+{
+ public static void main(String[] args)
+ {
+ System.out.println(I18n.tlt("stage.start"));
+ OptionParser opt = new OptionParser();
+ opt.accepts("help", "Show the help").forHelp();
+// opt.acceptsAll(Arrays.asList("v", "minecraft"), "Version of minecraft (1.7.10)")
+// .withRequiredArg()
+// .ofType(String.class)
+// .defaultsTo("1.7.10")
+// .describedAs("version");
+ OptionSpec optLang = opt.accepts("lang", "Language")
+ .withRequiredArg()
+ .ofType(String.class)
+ .defaultsTo(Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry())
+ .describedAs("lang");
+ opt.accepts("stacktrace", "Print detailed stacktrace on application error");
+ OptionSpec optDir = opt.accepts("dir", "Server directory")
+ .withRequiredArg()
+ .ofType(File.class)
+ .defaultsTo(new File("."))
+ .describedAs("file");
+// opt.accepts("addliburl", "Add extra libs by URL")
+// .withRequiredArg()
+// .ofType(URL.class)
+// .describedAs("lib url");
+ OptionSpec optAddLib = opt.accepts("addlib", "Add extra libs from maven repo (gradle format)")
+ .withRequiredArg()
+ .ofType(String.class)
+ .describedAs("maven lib");
+ OptionSpec optAddRepo = opt.accepts("addrepo", "Add extra maven repository URL")
+ .withRequiredArg()
+ .ofType(URL.class)
+ .describedAs("repo URL");
+ opt.accepts("install", "Install ultramine server");
+ opt.accepts("update", "Update ultramine server");
+ opt.accepts("forge", "Make forge-compatible directory mappings");
+ opt.accepts("beta", "Use beta channel");
+ OptionSpec optVersion = opt.accepts("version", "ultramine core version")
+ .withRequiredArg()
+ .ofType(String.class)
+ .describedAs("version");
+
+ OptionSpec optXMX = opt.accepts("xmx", "Max heap size for server (-Xmx)")
+ .withRequiredArg()
+ .ofType(String.class)
+ .defaultsTo("2048m")
+ .describedAs("max memory");
+ OptionSpec optXMS = opt.accepts("xms", "Start heap size for server (-Xms)")
+ .withRequiredArg()
+ .ofType(String.class)
+ .defaultsTo("2048m")
+ .describedAs("start memory");
+
+ OptionSpec optTerminal = opt.accepts("terminal", "Terminal (console) mode")
+ .withRequiredArg()
+ .ofType(String.class)
+ .defaultsTo("jline")
+ .describedAs("jline/ansi/default/raw");
+
+ OptionSet options;
+
+ try {
+ options = opt.parse(args);
+ } catch (joptsimple.OptionException e) {
+ throw new RuntimeException("Fail to parse command line", e);
+ }
+
+ I18n.select(optLang.value(options));
+
+ if (options.has("help")) {
+ try {
+ opt.printHelpOn(System.out);
+ System.exit(0);
+ } catch (IOException e) {
+ throw new RuntimeException("Fail print help", e);
+ }
+ }
+
+ try
+ {
+ File dir = optDir.value(options);
+ Set deps = DependencyResolver.load(
+ dir,
+ optVersion.value(options),
+ options.has("install") || options.has("update"),
+ options.has("beta"),
+ optAddRepo.values(options),
+ optAddLib.values(options)
+ );
+ ScriptCreator.create(
+ dir,
+ deps,
+ optXMX.value(options),
+ optXMS.value(options),
+ optTerminal.value(options),
+ options.has("forge")
+ );
+ System.out.println(I18n.tlt("stage.finished"));
+ }
+ catch(ApplicationErrorException e)
+ {
+ System.err.println(e.getTranslatedMessage());
+ if(options.has("stacktrace"))
+ e.printStackTrace();
+ else
+ System.err.println(I18n.tlt("hint.stacktrace"));
+ System.exit(1);
+ }
+ catch(RuntimeException e)
+ {
+ e.printStackTrace();
+ }
+
+ System.exit(0);
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/UMCoreVersionsRetriever.java b/src/main/java/org/ultramine/bootstrap/UMCoreVersionsRetriever.java
new file mode 100644
index 0000000..4f44a30
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/UMCoreVersionsRetriever.java
@@ -0,0 +1,33 @@
+package org.ultramine.bootstrap;
+
+import org.ultramine.bootstrap.maven.MavenMetadata;
+import org.ultramine.bootstrap.versioning.DefaultArtifactVersion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UMCoreVersionsRetriever
+{
+ private final List betaVersions;
+ private final List stableVersions;
+
+ public UMCoreVersionsRetriever()
+ {
+ List versions = MavenMetadata.loadFromProject(Constants.UM_REPO, Constants.UM_CORE_GROUP, Constants.UM_CORE_NAME).getVersions();
+
+ betaVersions = new ArrayList<>(versions.size());
+ stableVersions = new ArrayList<>(versions.size());
+ for(DefaultArtifactVersion version : versions)
+ (version.getLabel().contains("beta") ? betaVersions : stableVersions).add(version);
+ }
+
+ public List getBetaVersions()
+ {
+ return betaVersions;
+ }
+
+ public List getStableVersions()
+ {
+ return stableVersions;
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/deps/AbstractDownloadable.java b/src/main/java/org/ultramine/bootstrap/deps/AbstractDownloadable.java
new file mode 100644
index 0000000..a08aaf5
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/deps/AbstractDownloadable.java
@@ -0,0 +1,65 @@
+package org.ultramine.bootstrap.deps;
+
+import org.apache.commons.io.IOUtils;
+import org.ultramine.bootstrap.deps.IDownloadable;
+import org.ultramine.bootstrap.util.HashUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+
+public abstract class AbstractDownloadable implements IDownloadable
+{
+ protected File outputDir;
+ protected File checkSumsDir;
+
+ public void setOutputDir(File outputDir)
+ {
+ this.outputDir = outputDir;
+ }
+
+ public File getOutputDir()
+ {
+ return this.outputDir;
+ }
+
+ public void setCheckSumsDir(File checkSumsDir)
+ {
+ this.checkSumsDir = checkSumsDir;
+ }
+
+ protected static String copyAndDigest(InputStream in, OutputStream out) throws IOException
+ {
+ MessageDigest digest = HashUtil.getSHA1();
+ byte[] buffer = new byte[0x10000];
+ try
+ {
+ for(int read = in.read(buffer); read > 0; read = in.read(buffer))
+ {
+ digest.update(buffer, 0, read);
+ out.write(buffer, 0, read);
+ }
+ }
+ finally
+ {
+ IOUtils.closeQuietly(in);
+ IOUtils.closeQuietly(out);
+ }
+
+ return HashUtil.byteArray2Hex(digest.digest());
+ }
+
+ protected static void ensureFileWritable(File target)
+ {
+ if(target.getParentFile() != null && !target.getParentFile().isDirectory())
+ {
+ if(!target.getParentFile().mkdirs() && !target.getParentFile().isDirectory())
+ throw new RuntimeException("Could not create directory " + target.getParentFile());
+ }
+
+ if(target.isFile() && !target.canWrite())
+ throw new RuntimeException("Do not have write permissions for " + target + " - aborting!");
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/deps/IDependency.java b/src/main/java/org/ultramine/bootstrap/deps/IDependency.java
new file mode 100644
index 0000000..fa6c035
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/deps/IDependency.java
@@ -0,0 +1,5 @@
+package org.ultramine.bootstrap.deps;
+
+public interface IDependency
+{
+}
diff --git a/src/main/java/org/ultramine/bootstrap/deps/IDownloadable.java b/src/main/java/org/ultramine/bootstrap/deps/IDownloadable.java
new file mode 100644
index 0000000..6ab6631
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/deps/IDownloadable.java
@@ -0,0 +1,15 @@
+package org.ultramine.bootstrap.deps;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface IDownloadable
+{
+ void setOutputDir(File outputDir);
+
+ File getOutputDir();
+
+ void setCheckSumsDir(File checkSumsDir);
+
+ void download() throws IOException;
+}
diff --git a/src/main/java/org/ultramine/bootstrap/deps/IRepository.java b/src/main/java/org/ultramine/bootstrap/deps/IRepository.java
new file mode 100644
index 0000000..ee3abb0
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/deps/IRepository.java
@@ -0,0 +1,24 @@
+package org.ultramine.bootstrap.deps;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface IRepository
+{
+ InputStream resolve(String path) throws IOException;
+
+ default String resolveCheckSum(String path) throws IOException
+ {
+ InputStream inp = resolve(path + ".sha1");
+ if(inp == null)
+ inp = resolve(path + ".sha");
+ if(inp == null)
+ return null;
+ String sum = IOUtils.toString(inp).trim();
+ if(sum.length() > 40)
+ sum = sum.substring(0, 40);
+ return sum;
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/exceptions/ApplicationErrorException.java b/src/main/java/org/ultramine/bootstrap/exceptions/ApplicationErrorException.java
new file mode 100644
index 0000000..51c14a8
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/exceptions/ApplicationErrorException.java
@@ -0,0 +1,24 @@
+package org.ultramine.bootstrap.exceptions;
+
+public class ApplicationErrorException extends TranslatableMessageException
+{
+ public ApplicationErrorException(String format)
+ {
+ super(format);
+ }
+
+ public ApplicationErrorException(String format, Object... args)
+ {
+ super(format, args);
+ }
+
+ public ApplicationErrorException(Throwable cause, String format)
+ {
+ super(cause, format);
+ }
+
+ public ApplicationErrorException(Throwable cause, String format, Object... args)
+ {
+ super(cause, format, args);
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/exceptions/TranslatableMessageException.java b/src/main/java/org/ultramine/bootstrap/exceptions/TranslatableMessageException.java
new file mode 100644
index 0000000..13b4133
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/exceptions/TranslatableMessageException.java
@@ -0,0 +1,40 @@
+package org.ultramine.bootstrap.exceptions;
+
+import org.ultramine.bootstrap.util.I18n;
+
+public class TranslatableMessageException extends RuntimeException
+{
+ private final String format;
+ private Object[] args;
+
+ public TranslatableMessageException(String format)
+ {
+ super(I18n.tlt(format));
+ this.format = format;
+ }
+
+ public TranslatableMessageException(String format, Object... args)
+ {
+ super(I18n.tlt(format, args));
+ this.format = format;
+ this.args = args;
+ }
+
+ public TranslatableMessageException(Throwable cause, String format)
+ {
+ super(I18n.tlt(format), cause);
+ this.format = format;
+ }
+
+ public TranslatableMessageException(Throwable cause, String format, Object... args)
+ {
+ super(I18n.tlt(format, args), cause);
+ this.format = format;
+ this.args = args;
+ }
+
+ public String getTranslatedMessage()
+ {
+ return args == null ? I18n.tlt(format) : I18n.tlt(format, args);
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/maven/MavenDependency.java b/src/main/java/org/ultramine/bootstrap/maven/MavenDependency.java
new file mode 100644
index 0000000..480fec1
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/maven/MavenDependency.java
@@ -0,0 +1,108 @@
+package org.ultramine.bootstrap.maven;
+
+import org.ultramine.bootstrap.deps.IDependency;
+import org.ultramine.bootstrap.deps.IRepository;
+import org.ultramine.bootstrap.deps.IDownloadable;
+
+import java.util.List;
+import java.util.Objects;
+
+public class MavenDependency implements IDependency
+{
+ public final String group;
+ public final String artifactName;
+ public final String version;
+
+ public MavenDependency(String group, String artifactName, String version)
+ {
+ this.group = Objects.requireNonNull(group);
+ this.artifactName = Objects.requireNonNull(artifactName);
+ this.version = Objects.requireNonNull(version);
+ }
+
+ public MavenDependency(String name)
+ {
+ String[] parts = name.split(":", 3);
+ this.group = parts[0];
+ this.artifactName = parts[1];
+ this.version = parts[2];
+ }
+
+ public String getGroup()
+ {
+ return group;
+ }
+
+ public String getArtifactName()
+ {
+ return artifactName;
+ }
+
+ public String getVersion()
+ {
+ return version;
+ }
+
+ public String getArtifactBaseDir()
+ {
+ return group.replace('.', '/') + "/" + artifactName + "/" + version;
+ }
+
+ public String getArtifactFilename(String classifier, String extension)
+ {
+ return artifactName + "-" + version + (classifier == null || classifier.isEmpty() ? "" : "-" + classifier) + "." + extension;
+ }
+
+ public String getArtifactPath()
+ {
+ return getArtifactPath(null);
+ }
+
+ public String getArtifactPath(String classifier)
+ {
+ return getArtifactBaseDir() + "/" + getArtifactFilename(classifier);
+ }
+
+ public String getArtifactPath(String classifier, String extension)
+ {
+ return getArtifactBaseDir() + "/" + getArtifactFilename(classifier, extension);
+ }
+
+ public String getArtifactFilename()
+ {
+ return getArtifactFilename(null);
+ }
+
+ public String getArtifactFilename(String classifier)
+ {
+ return getArtifactFilename(classifier, "jar");
+ }
+
+ public IDownloadable resolve(List repositories)
+ {
+ return new MavenDownloadable(repositories, this);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if(this == o) return true;
+ if(o == null || getClass() != o.getClass()) return false;
+ MavenDependency that = (MavenDependency) o;
+ return Objects.equals(group, that.group) &&
+ Objects.equals(artifactName, that.artifactName) &&
+ Objects.equals(version, that.version);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(group, artifactName, version);
+ }
+
+ @Override
+ public String toString()
+ {
+ return group+':'+ artifactName +':'+version;
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/maven/MavenDownloadable.java b/src/main/java/org/ultramine/bootstrap/maven/MavenDownloadable.java
new file mode 100644
index 0000000..215a8f0
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/maven/MavenDownloadable.java
@@ -0,0 +1,78 @@
+package org.ultramine.bootstrap.maven;
+
+import org.apache.commons.io.FileUtils;
+import org.ultramine.bootstrap.deps.IRepository;
+import org.ultramine.bootstrap.deps.AbstractDownloadable;
+import org.ultramine.bootstrap.exceptions.ApplicationErrorException;
+import org.ultramine.bootstrap.util.HashUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.List;
+
+public class MavenDownloadable extends AbstractDownloadable
+{
+ private final List repositories;
+ private final MavenDependency dependency;
+
+ public MavenDownloadable(List repositories, MavenDependency dependency)
+ {
+ this.repositories = repositories;
+ this.dependency = dependency;
+ }
+
+ @Override
+ public void download() throws IOException
+ {
+ String artifactName = dependency.getArtifactFilename();
+ File output = new File(outputDir, artifactName);
+ File checkSumFile = new File(checkSumsDir, artifactName+".sha1");
+ if(output.isFile() && checkSumFile.isFile())
+ {
+ if(HashUtil.sha1Str(output).equals(FileUtils.readFileToString(checkSumFile)))
+ return;
+ }
+ ensureFileWritable(output);
+ ensureFileWritable(checkSumFile);
+ String path = dependency.getArtifactPath();
+ for(IRepository repo : repositories)
+ if(tryDownload(output, checkSumFile, path, repo))
+ return;
+ throw new FileNotFoundException("Maven dependency not found in any repositories: "+dependency);
+ }
+
+ private boolean tryDownload(File output, File checkSumFile, String path, IRepository repo) throws IOException
+ {
+ try
+ {
+ InputStream resolved = repo.resolve(path);
+ if(resolved == null)
+ return false;
+ System.out.println("Downloading " + dependency.getArtifactName() + " from " + repo + "/" + path);
+ String computedCheckSum = copyAndDigest(resolved, new FileOutputStream(output));
+ String loadedCheckSum = repo.resolveCheckSum(path);
+ if(loadedCheckSum != null && !loadedCheckSum.equals(computedCheckSum))
+ throw new RuntimeException("CheckSums failed for " + dependency + "("+computedCheckSum + " != " + loadedCheckSum + ")");
+ FileUtils.writeStringToFile(checkSumFile, computedCheckSum);
+ return true;
+ } catch (UnknownHostException e)
+ {
+ throw new ApplicationErrorException(e, "error.unavailable.host", e.getMessage());
+ } catch (SocketTimeoutException | ConnectException e)
+ {
+ throw new ApplicationErrorException(e, "error.unavailable.maven", repo.toString());
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return dependency.toString();
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/maven/MavenLocalRepository.java b/src/main/java/org/ultramine/bootstrap/maven/MavenLocalRepository.java
new file mode 100644
index 0000000..20f0ba2
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/maven/MavenLocalRepository.java
@@ -0,0 +1,33 @@
+package org.ultramine.bootstrap.maven;
+
+import org.ultramine.bootstrap.deps.IRepository;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class MavenLocalRepository implements IRepository
+{
+ private final File directory;
+
+ public MavenLocalRepository(File directory)
+ {
+ this.directory = directory;
+ }
+
+ @Override
+ public InputStream resolve(String path) throws IOException
+ {
+ File file = new File(directory, path.replace("/", File.separator));
+ if(!file.isFile())
+ return null;
+ return new FileInputStream(file);
+ }
+
+ @Override
+ public String toString()
+ {
+ return directory.getAbsolutePath();
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/maven/MavenMetadata.java b/src/main/java/org/ultramine/bootstrap/maven/MavenMetadata.java
new file mode 100644
index 0000000..ffb590e
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/maven/MavenMetadata.java
@@ -0,0 +1,58 @@
+package org.ultramine.bootstrap.maven;
+
+import org.ultramine.bootstrap.exceptions.ApplicationErrorException;
+import org.ultramine.bootstrap.versioning.DefaultArtifactVersion;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MavenMetadata
+{
+ private final List versions;
+
+ private MavenMetadata(String xmlUrl)
+ {
+ Document doc;
+ try
+ {
+ doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlUrl);
+ }
+ catch (Exception e)
+ {
+ throw new ApplicationErrorException(e, "error.mavenmetadata", xmlUrl);
+ }
+ NodeList listNodes = doc.getElementsByTagName("version");
+
+ versions = new ArrayList<>(listNodes.getLength());
+ for(int i = 0, s = listNodes.getLength(); i < s; i++)
+ {
+ String version = listNodes.item(i).getTextContent();
+ versions.add(new DefaultArtifactVersion(version, version));
+ }
+ Collections.sort(versions);
+ }
+
+ public List getVersions()
+ {
+ return versions;
+ }
+
+ public static MavenMetadata loadFromXML(String xmlUrl)
+ {
+ return new MavenMetadata(xmlUrl);
+ }
+
+ public static MavenMetadata loadFromProject(String projectUrl)
+ {
+ return loadFromXML(projectUrl + (projectUrl.endsWith("/") ? "" : "/") + "maven-metadata.xml");
+ }
+
+ public static MavenMetadata loadFromProject(String repoUrl, String group, String project)
+ {
+ return loadFromProject(repoUrl + "/" + group.replace('.', '/') + "/" + project);
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/maven/MavenRemoteRepository.java b/src/main/java/org/ultramine/bootstrap/maven/MavenRemoteRepository.java
new file mode 100644
index 0000000..f04f602
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/maven/MavenRemoteRepository.java
@@ -0,0 +1,52 @@
+package org.ultramine.bootstrap.maven;
+
+import org.ultramine.bootstrap.deps.IRepository;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class MavenRemoteRepository implements IRepository
+{
+ private final String url;
+
+ public MavenRemoteRepository(String url)
+ {
+ this.url = url;
+ }
+
+ public String getURL()
+ {
+ return url;
+ }
+
+ @Override
+ public InputStream resolve(String path) throws IOException
+ {
+ HttpURLConnection conn = makeConnection(new URL(getURL() + "/" + path));
+ int status = conn.getResponseCode();
+ if(status / 100 != 2)
+ return null;
+ return conn.getInputStream();
+ }
+
+ protected HttpURLConnection makeConnection(URL url) throws IOException
+ {
+ HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+ connection.setUseCaches(false);
+ connection.setDefaultUseCaches(false);
+ connection.setRequestProperty("Cache-Control", "no-store,max-age=0,no-cache");
+ connection.setRequestProperty("Expires", "0");
+ connection.setRequestProperty("Pragma", "no-cache");
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(30000);
+ return connection;
+ }
+
+ @Override
+ public String toString()
+ {
+ return url;
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/maven/ProjectObjectModel.java b/src/main/java/org/ultramine/bootstrap/maven/ProjectObjectModel.java
new file mode 100644
index 0000000..b9cc9ea
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/maven/ProjectObjectModel.java
@@ -0,0 +1,69 @@
+package org.ultramine.bootstrap.maven;
+
+import org.ultramine.bootstrap.exceptions.ApplicationErrorException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProjectObjectModel
+{
+ private final List runtimeDependencies;
+
+ private ProjectObjectModel(String pomUrl)
+ {
+ Document doc;
+ try
+ {
+ doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pomUrl);
+ }
+ catch (Exception e)
+ {
+ throw new ApplicationErrorException(e, "error.pom", pomUrl);
+ }
+ NodeList listNodes = doc.getElementsByTagName("dependency");
+
+ runtimeDependencies = new ArrayList<>(listNodes.getLength());
+ for(int i = 0, s = listNodes.getLength(); i < s; i++)
+ {
+ NodeList dependency = listNodes.item(i).getChildNodes();
+ String group = null;
+ String artifact = null;
+ String version = null;
+ String scope = null;
+ for(int i1 = 0, s1 = dependency.getLength(); i1 < s1; i1++)
+ {
+ Node nd = dependency.item(i1);
+ String name = nd.getNodeName();
+ String val = nd.getTextContent();
+ switch(name)
+ {
+ case "groupId": group = val; break;
+ case "artifactId": artifact = val; break;
+ case "version": version = val; break;
+ case "scope": scope = val; break;
+ }
+ }
+ if("runtime".equals(scope) || "compile".equals(scope))
+ runtimeDependencies.add(new MavenDependency(group, artifact, version));
+ }
+ }
+
+ public List getRuntimeDependencies()
+ {
+ return runtimeDependencies;
+ }
+
+ public static ProjectObjectModel loadFromXML(String pomUrl)
+ {
+ return new ProjectObjectModel(pomUrl);
+ }
+
+ public static ProjectObjectModel loadFromArtifact(String repoUrl, String group, String project, String version)
+ {
+ return loadFromXML(repoUrl + "/" + group.replace('.', '/') + "/" + project + "/" + version + "/" + project + "-" + version + ".pom");
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/task/DependencyResolver.java b/src/main/java/org/ultramine/bootstrap/task/DependencyResolver.java
new file mode 100644
index 0000000..bbf444a
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/task/DependencyResolver.java
@@ -0,0 +1,164 @@
+package org.ultramine.bootstrap.task;
+
+import org.apache.commons.io.FileUtils;
+import org.ultramine.bootstrap.Constants;
+import org.ultramine.bootstrap.util.I18n;
+import org.ultramine.bootstrap.UMCoreVersionsRetriever;
+import org.ultramine.bootstrap.util.UmSslUtil;
+import org.ultramine.bootstrap.deps.IRepository;
+import org.ultramine.bootstrap.maven.MavenDependency;
+import org.ultramine.bootstrap.maven.MavenLocalRepository;
+import org.ultramine.bootstrap.maven.MavenRemoteRepository;
+import org.ultramine.bootstrap.deps.IDownloadable;
+import org.ultramine.bootstrap.exceptions.ApplicationErrorException;
+import org.ultramine.bootstrap.maven.ProjectObjectModel;
+import org.ultramine.bootstrap.versioning.DefaultArtifactVersion;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class DependencyResolver
+{
+ public static Set load(File dir, String selVersion, boolean update, boolean beta, List extraRepos, List extraLibs)
+ {
+ UmSslUtil.checkOrInstall();
+ File libraryDir = new File(dir, "libraries");
+ File checkSumsDir = new File(libraryDir, "checksums");
+
+ List repositories = new ArrayList<>();
+ addMavenLocalIfExists(repositories, findMavenLocal());
+ addMavenLocalIfExists(repositories, findMinecraftLibs());
+ repositories.addAll(Arrays.asList(
+ new MavenRemoteRepository("https://repo1.maven.org/maven2"),
+ new MavenRemoteRepository("https://oss.sonatype.org/content/repositories/snapshots"),
+ new MavenRemoteRepository("http://files.minecraftforge.net/maven"),
+ new MavenRemoteRepository("https://libraries.minecraft.net"),
+ new MavenRemoteRepository("http"+(UmSslUtil.isUseHttps() ? "s" : "")+"://maven.ultramine.ru")
+ ));
+
+ for(URL url : extraRepos)
+ try {
+ repositories.add(url.getProtocol().equals("file") ? new MavenLocalRepository(new File(url.toURI())) : new MavenRemoteRepository(url.toString()));
+ } catch(URISyntaxException e) {
+ throw new ApplicationErrorException(e, "error.addrepo.file", url.toString());
+ }
+
+ System.out.println(I18n.tlt("stage.versions"));
+ String targetVersion = selVersion;
+ if(targetVersion == null)
+ {
+ List versions = null;
+ if(!update)
+ versions = retrieveLocalVersions(dir);
+ if(versions == null || versions.size() == 0)
+ {
+ UMCoreVersionsRetriever retr = new UMCoreVersionsRetriever();
+ versions = beta ? retr.getBetaVersions() : retr.getStableVersions();
+ }
+
+ targetVersion = versions.get(versions.size()-1).getLabel();
+ }
+
+ MavenDependency umCoreDep = new MavenDependency(Constants.UM_CORE_GROUP, Constants.UM_CORE_NAME, targetVersion);
+ File umCoreFile = new File(dir, umCoreDep.getArtifactFilename());
+
+ Set dependencies = new HashSet<>();
+ dependencies.addAll(ProjectObjectModel.loadFromArtifact(Constants.UM_REPO, Constants.UM_CORE_GROUP, Constants.UM_CORE_NAME, targetVersion)
+ .getRuntimeDependencies());
+ dependencies.addAll(extraLibs.stream().map(MavenDependency::new).collect(Collectors.toList()));
+
+
+ List toDownload = dependencies.stream().map(d -> d.resolve(repositories)).collect(Collectors.toList());
+ IDownloadable umCoreDownloadable = umCoreDep.resolve(repositories);
+ umCoreDownloadable.setOutputDir(dir);
+ toDownload.add(umCoreDownloadable);
+ System.out.println(I18n.tlt("stage.downloading"));
+ toDownload.parallelStream().forEach(d -> {
+ try
+ {
+ d.setCheckSumsDir(checkSumsDir);
+ if(d.getOutputDir() == null)
+ d.setOutputDir(libraryDir);
+ d.download();
+ } catch (IOException e) {
+ throw new ApplicationErrorException(e, "error.download.dependency", d.toString(), e.toString());
+ }
+ });
+
+ try
+ {
+ File symlink = new File(dir, Constants.UM_CORE_NAME+"-latest.jar");
+ if(symlink.exists())
+ FileUtils.forceDelete(symlink);
+ try {
+ Files.createSymbolicLink(new File(dir, Constants.UM_CORE_NAME + "-latest.jar").toPath(), umCoreFile.toPath());
+ } catch(IOException e) {
+ FileUtils.copyFile(umCoreFile, symlink);
+ }
+ }
+ catch(IOException e)
+ {
+ throw new ApplicationErrorException(e, "error.write.file", Constants.UM_CORE_NAME+"-latest.jar", e.getMessage());
+ }
+
+ return dependencies;
+ }
+
+ private static List retrieveLocalVersions(File dir)
+ {
+ return Stream.of(dir.list((d, name) -> name.startsWith(Constants.UM_CORE_NAME)))
+ .map(s -> s.substring(Constants.UM_CORE_NAME.length()+1, s.length()-4))
+ .map(s -> new DefaultArtifactVersion(s, s)).sorted().collect(Collectors.toList());
+ }
+
+ private static void addMavenLocalIfExists(List list, File repo)
+ {
+ if(repo != null && repo.exists())
+ list.add(new MavenLocalRepository(repo));
+ }
+
+ private static File findMavenLocal()
+ {
+ String home = System.getProperty("user.home");
+ if(home != null)
+ return new File(home, ".m2"+File.separator+"repository");
+ return null;
+ }
+
+ private static File findMinecraftLibs()
+ {
+ return getFromAppdata(".minecraft"+File.separator+"libraries");
+ }
+
+ private static File getFromAppdata(String name)
+ {
+ String osName = System.getProperty("os.name").toLowerCase();
+ String home = System.getProperty("user.home", ".");
+ File dir;
+ if(osName.contains("linux") || osName.contains("unix"))
+ dir = new File(home, name);
+ else if(osName.contains("win"))
+ {
+ String appdata = System.getenv("APPDATA");
+ if(appdata != null)
+ dir = new File(appdata, name);
+ else
+ dir = new File(home, name);
+ }
+ else if(osName.contains("mac"))
+ dir = new File(home, "Library/Application Support/" + name);
+ else
+ dir = new File(home, name);
+ return dir;
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/task/ScriptCreator.java b/src/main/java/org/ultramine/bootstrap/task/ScriptCreator.java
new file mode 100644
index 0000000..1d0a12c
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/task/ScriptCreator.java
@@ -0,0 +1,139 @@
+package org.ultramine.bootstrap.task;
+
+import org.apache.commons.io.FileUtils;
+import org.ultramine.bootstrap.Constants;
+import org.ultramine.bootstrap.util.I18n;
+import org.ultramine.bootstrap.maven.MavenDependency;
+import org.ultramine.bootstrap.exceptions.ApplicationErrorException;
+import org.ultramine.bootstrap.util.Resources;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Set;
+
+public class ScriptCreator
+{
+ public static void create(File dir, Set dependencies, String parXmx, String parXms, String parTerminal, boolean forge)
+ {
+ System.out.println(I18n.tlt("stage.scripts"));
+ long xmx = resolveMemory(parXmx);
+ long xms = resolveMemory(parXms);
+ long xmn = Math.min(xmx / 4, 1024L*1024*1024);
+ if(xms > xmx)
+ xms = xmx;
+ if(xmx < 32*1024*1024)
+ throw new ApplicationErrorException("Maximum heap size (-Xmx) is too low: %s", parXmx);
+
+ boolean win = System.getProperty("os.name").toLowerCase().contains("win");
+ try(BufferedWriter writer = new BufferedWriter(new FileWriter(
+ new File(dir, "ultramine_server_run_line."+(win ? "bat" : "sh")))))
+ {
+ if(!win)
+ {
+ writer.write("#!/bin/sh");
+ writer.newLine();
+ }
+ writer.write(Resources.getAsString("/assets/script/java_command_line.txt")
+ .replace("{xms}", formatMemory(xmx))
+ .replace("{xmx}", formatMemory(xms))
+ .replace("{xmn}", formatMemory(xmn))
+
+ .replace("{terminal}", parTerminal)
+
+ .replace("{vanilla_dir}", forge ? "." : "storage")
+ .replace("{worlds_dir}", forge ? "." : "worlds")
+
+ .replace("{classpath}", buildClassPath(dependencies))
+ .replace('\\', win ? '^' : '\\')
+ .replace("#", win ? "::" : "#")
+// .replace("<", win ? "%=" : "`#")
+// .replace(">", win ? "=%" : "`")
+ );
+ writer.newLine();
+ }
+ catch(IOException e)
+ {
+ throw new ApplicationErrorException(e, "error.write.file", "ultramine_server_run_line."+(win ? "bat" : "sh"), e.getMessage());
+ }
+
+ try
+ {
+ FileUtils.writeStringToFile(new File(dir, "start."+(win ? "bat" : "sh")),
+ Resources.getAsString("/assets/script/"+(win ? "windows/start.bat" : "shell/start.sh")));
+ }
+ catch(IOException e)
+ {
+ throw new ApplicationErrorException(e, "error.write.file", "start."+(win ? "bat" : "sh"), e.getMessage());
+ }
+
+ if(forge)
+ {
+ try
+ {
+ File serverYml = new File(dir, "settings"+File.separator+"server.yml");
+ if(!serverYml.exists())
+ {
+ String confStr = Resources.getAsString("/assets/server.yml");
+ confStr = confStr.replace("{split-world-dirs}", "false");
+ Properties defs = new Properties();
+ defs.load(Resources.getAsStream("/assets/server.properties"));
+ Properties props = new Properties(defs);
+ File propsFile = new File(dir, "server.properties");
+ if(propsFile.exists())
+ try(FileInputStream inp = new FileInputStream(propsFile)) {
+ props.load(inp);
+ }
+ for(Object key : defs.keySet())
+ confStr = confStr.replace("{"+key+"}", props.getProperty(key.toString()));
+ FileUtils.writeStringToFile(serverYml, confStr);
+ }
+ }
+ catch(IOException e)
+ {
+ throw new ApplicationErrorException(e, "error.write.file", "settings/server.yml", e.getMessage());
+ }
+ }
+ }
+
+ /** @return number in bytes */
+ private static long resolveMemory(String s)
+ {
+ try
+ {
+ return Long.parseLong(s);
+ }
+ catch (NumberFormatException e1)
+ {
+ long val = Long.parseLong(s.substring(0, s.length() - 1));
+ char mod = Character.toLowerCase(s.charAt(s.length()-1));
+ if(mod != 'k' && mod != 'm' && mod != 'g')
+ throw new IllegalArgumentException(s);
+ return val * (mod == 'k' ? 1024 : mod == 'm' ? 1024*1024 : 1024*1024*1024);
+ }
+ }
+
+ private static String formatMemory(long bytes)
+ {
+ return (bytes / (1024*1024)) + "m";
+ }
+
+ private static String buildClassPath(Set dependencies)
+ {
+ StringBuilder sb = new StringBuilder();
+ String separator = System.getProperty("path.separator");
+
+ sb.append(Constants.UM_CORE_NAME+"-latest.jar");
+ for(MavenDependency dep : dependencies)
+ {
+ sb.append(separator);
+ sb.append("libraries/");
+ sb.append(dep.getArtifactFilename());
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/util/HashUtil.java b/src/main/java/org/ultramine/bootstrap/util/HashUtil.java
new file mode 100644
index 0000000..8a1b966
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/util/HashUtil.java
@@ -0,0 +1,68 @@
+package org.ultramine.bootstrap.util;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class HashUtil
+{
+ private static final char[] CHARS = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
+
+ public static MessageDigest getSHA1()
+ {
+ try
+ {
+ return MessageDigest.getInstance("SHA-1");
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException("SHA-1 not found", e);
+ }
+ }
+
+ public static String sha1Str(File file)
+ {
+ return byteArray2Hex(calculateHash(getSHA1(), file));
+ }
+
+ public static byte[] calculateHash(MessageDigest alg, File file)
+ {
+ DigestInputStream dis = null;
+ try
+ {
+ dis = new DigestInputStream(new FileInputStream(file), alg);
+
+ byte[] buff = new byte[4096];
+ do {} while(dis.read(buff) != -1);
+
+ return alg.digest();
+ }
+ catch(IOException e)
+ {
+ alg.reset();
+ return new byte[0];
+ }
+ finally
+ {
+ IOUtils.closeQuietly(dis);
+ }
+ }
+
+ public static String byteArray2Hex(byte[] hash)
+ {
+ StringBuilder sb = new StringBuilder(hash.length*2);
+ for(int i = 0; i < hash.length; i++)
+ {
+ byte b = hash[i];
+ sb.append(CHARS[(b & 0xff) >> 4]);
+ sb.append(CHARS[b & 15]);
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/util/I18n.java b/src/main/java/org/ultramine/bootstrap/util/I18n.java
new file mode 100644
index 0000000..5e73754
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/util/I18n.java
@@ -0,0 +1,46 @@
+package org.ultramine.bootstrap.util;
+
+import org.ultramine.bootstrap.util.Resources;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class I18n
+{
+ private static final Properties enUs = loadLang("en_US");
+ private static Properties selected = enUs;
+
+ public static void select(String locale)
+ {
+ Properties props = locale.equals("en_US") ? enUs : loadLang(locale);
+ selected = props != null ? props : enUs;
+ }
+
+ private static Properties loadLang(String locale)
+ {
+ InputStream inp = Resources.getAsStream("/assets/lang/"+locale+".lang");
+ if(inp == null)
+ return null;
+ try
+ {
+ Properties props = enUs != null ? new Properties(enUs) : new Properties();
+ props.load(inp);
+ return props;
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String tlt(String key)
+ {
+ return selected.getProperty(key, key);
+ }
+
+ public static String tlt(String key, Object... args)
+ {
+ return String.format(tlt(key), args);
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/util/Resources.java b/src/main/java/org/ultramine/bootstrap/util/Resources.java
new file mode 100644
index 0000000..f859002
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/util/Resources.java
@@ -0,0 +1,34 @@
+package org.ultramine.bootstrap.util;
+
+import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class Resources
+{
+ public static InputStream getAsStream(String path)
+ {
+ return Resources.class.getResourceAsStream(path);
+ }
+
+ public static String getAsString(String path)
+ {
+ InputStream is = getAsStream(path);
+ if(is == null)
+ throw new RuntimeException("Requested resource not found: " + path);
+ try
+ {
+ return IOUtils.toString(is, Charsets.UTF_8);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException("Failed to load resource: " + path, e);
+ }
+ finally
+ {
+ IOUtils.closeQuietly(is);
+ }
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/util/UmSslUtil.java b/src/main/java/org/ultramine/bootstrap/util/UmSslUtil.java
new file mode 100644
index 0000000..49ed417
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/util/UmSslUtil.java
@@ -0,0 +1,72 @@
+package org.ultramine.bootstrap.util;
+
+import org.ultramine.bootstrap.util.Resources;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+public class UmSslUtil
+{
+ private static boolean useHttps = true;
+
+ public static boolean isUseHttps()
+ {
+ return useHttps;
+ }
+
+ public static void checkOrInstall()
+ {
+ //TODO connection caching issues
+// try
+// {
+// new URL("https://maven.ultramine.ru").openConnection().getInputStream().close();
+// }
+// catch (SSLHandshakeException e)
+// {
+ installDSTRootCA();
+// }
+// catch(IOException e)
+// {
+// throw new ApplicationErrorException(e, "error.unavailable.maven", "maven.ultramine.ru");
+// }
+ }
+
+ private static void installDSTRootCA()
+ {
+ try
+ {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ Path ksPath = Paths.get(System.getProperty("java.home"),
+ "lib", "security", "cacerts");
+ keyStore.load(Files.newInputStream(ksPath),
+ "changeit".toCharArray());
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ try(InputStream caInput = new BufferedInputStream( Resources.getAsStream("/assets/crt/DSTRootCAX3.pem")))
+ {
+ Certificate crt = cf.generateCertificate(caInput);
+ keyStore.setCertificateEntry("DSTRootCAX3", crt);
+ }
+
+ TrustManagerFactory tmf = TrustManagerFactory
+ .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(keyStore);
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, tmf.getTrustManagers(), null);
+ SSLContext.setDefault(sslContext);
+ }
+ catch (Throwable e)
+ {
+ useHttps = false;
+ }
+ }
+}
diff --git a/src/main/java/org/ultramine/bootstrap/versioning/ArtifactVersion.java b/src/main/java/org/ultramine/bootstrap/versioning/ArtifactVersion.java
new file mode 100644
index 0000000..7ef90f8
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/versioning/ArtifactVersion.java
@@ -0,0 +1,50 @@
+/*
+ * Forge Mod Loader
+ * Copyright (c) 2012-2013 cpw.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser Public License v2.1
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Contributors:
+ * cpw - implementation
+ */
+
+package org.ultramine.bootstrap.versioning;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Describes an artifactName version in terms of its components, converts it to/from a string and
+ * compares two versions.
+ *
+ * @author Brett Porter
+ */
+public interface ArtifactVersion
+ extends Comparable
+{
+ String getLabel();
+
+ String getVersionString();
+
+ boolean containsVersion(ArtifactVersion source);
+
+ String getRangeString();
+}
\ No newline at end of file
diff --git a/src/main/java/org/ultramine/bootstrap/versioning/ComparableVersion.java b/src/main/java/org/ultramine/bootstrap/versioning/ComparableVersion.java
new file mode 100644
index 0000000..587edf2
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/versioning/ComparableVersion.java
@@ -0,0 +1,500 @@
+/*
+ * Forge Mod Loader
+ * Copyright (c) 2012-2013 cpw.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser Public License v2.1
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Contributors:
+ * cpw - implementation
+ */
+
+package org.ultramine.bootstrap.versioning;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Stack;
+
+/**
+ * Generic implementation of version comparison.
+ *
+ * Features:
+ *
+ * - mixing of '
-
' (dash) and '.
' (dot) separators,
+ * - transition between characters and digits also constitutes a separator:
+ *
1.0alpha1 => [1, 0, alpha, 1]
+ * - unlimited number of version components,
+ * - version components in the text can be digits or strings,
+ * - strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering.
+ * Well-known qualifiers (case insensitive) are:
+ * snapshot
+ * alpha
or a
+ * beta
or b
+ * milestone
or m
+ * rc
or cr
+ * (the empty string)
or ga
or final
+ * sp
+ *
+ * Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive),
+ *
+ * - a dash usually precedes a qualifier, and is always less important than something preceded with a dot.
+ *
+ *
+ * @see "Versioning" on Maven Wiki
+ * @author Kenney Westerhof
+ * @author Hervé Boutemy
+ */
+public class ComparableVersion
+ implements Comparable
+{
+ private String value;
+
+ private String canonical;
+
+ private ListItem items;
+
+ private interface Item
+ {
+ final int INTEGER_ITEM = 0;
+ final int STRING_ITEM = 1;
+ final int LIST_ITEM = 2;
+
+ int compareTo(Item item);
+
+ int getType();
+
+ boolean isNull();
+ }
+
+ /**
+ * Represents a numeric item in the version item list.
+ */
+ private static class IntegerItem
+ implements Item
+ {
+ private static final BigInteger BigInteger_ZERO = new BigInteger( "0" );
+
+ private final BigInteger value;
+
+ public static final IntegerItem ZERO = new IntegerItem();
+
+ private IntegerItem()
+ {
+ this.value = BigInteger_ZERO;
+ }
+
+ public IntegerItem( String str )
+ {
+ this.value = new BigInteger( str );
+ }
+
+ @Override
+ public int getType()
+ {
+ return INTEGER_ITEM;
+ }
+
+ @Override
+ public boolean isNull()
+ {
+ return BigInteger_ZERO.equals( value );
+ }
+
+ @Override
+ public int compareTo( Item item )
+ {
+ if ( item == null )
+ {
+ return BigInteger_ZERO.equals( value ) ? 0 : 1; // 1.0 == 1, 1.1 > 1
+ }
+
+ switch ( item.getType() )
+ {
+ case INTEGER_ITEM:
+ return value.compareTo( ( (IntegerItem) item ).value );
+
+ case STRING_ITEM:
+ return 1; // 1.1 > 1-sp
+
+ case LIST_ITEM:
+ return 1; // 1.1 > 1-1
+
+ default:
+ throw new RuntimeException( "invalid item: " + item.getClass() );
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return value.toString();
+ }
+ }
+
+ /**
+ * Represents a string in the version item list, usually a qualifier.
+ */
+ private static class StringItem
+ implements Item
+ {
+ private static final String[] QUALIFIERS = { "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" };
+
+ private static final List _QUALIFIERS = Arrays.asList( QUALIFIERS );
+
+ private static final Properties ALIASES = new Properties();
+ static
+ {
+ ALIASES.put( "ga", "" );
+ ALIASES.put( "final", "" );
+ ALIASES.put( "cr", "rc" );
+ }
+
+ /**
+ * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes
+ * the version older than one without a qualifier, or more recent.
+ */
+ private static final String RELEASE_VERSION_INDEX = String.valueOf( _QUALIFIERS.indexOf( "" ) );
+
+ private String value;
+
+ public StringItem( String value, boolean followedByDigit )
+ {
+ if ( followedByDigit && value.length() == 1 )
+ {
+ // a1 = alpha-1, b1 = beta-1, m1 = milestone-1
+ switch ( value.charAt( 0 ) )
+ {
+ case 'a':
+ value = "alpha";
+ break;
+ case 'b':
+ value = "beta";
+ break;
+ case 'm':
+ value = "milestone";
+ break;
+ }
+ }
+ this.value = ALIASES.getProperty( value , value );
+ }
+
+ @Override
+ public int getType()
+ {
+ return STRING_ITEM;
+ }
+
+ @Override
+ public boolean isNull()
+ {
+ return ( comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ) == 0 );
+ }
+
+ /**
+ * Returns a comparable value for a qualifier.
+ *
+ * This method takes into account the ordering of known qualifiers then unknown qualifiers with lexical ordering.
+ *
+ * just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1
+ * or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character,
+ * so this is still fast. If more characters are needed then it requires a lexical sort anyway.
+ *
+ * @param qualifier
+ * @return an equivalent value that can be used with lexical comparison
+ */
+ public static String comparableQualifier( String qualifier )
+ {
+ int i = _QUALIFIERS.indexOf( qualifier );
+
+ return i == -1 ? ( _QUALIFIERS.size() + "-" + qualifier ) : String.valueOf( i );
+ }
+
+ @Override
+ public int compareTo( Item item )
+ {
+ if ( item == null )
+ {
+ // 1-rc < 1, 1-ga > 1
+ return comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX );
+ }
+ switch ( item.getType() )
+ {
+ case INTEGER_ITEM:
+ return -1; // 1.any < 1.1 ?
+
+ case STRING_ITEM:
+ return comparableQualifier( value ).compareTo( comparableQualifier( ( (StringItem) item ).value ) );
+
+ case LIST_ITEM:
+ return -1; // 1.any < 1-1
+
+ default:
+ throw new RuntimeException( "invalid item: " + item.getClass() );
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return value;
+ }
+ }
+
+ /**
+ * Represents a version list item. This class is used both for the global item list and for sub-lists (which start
+ * with '-(number)' in the version specification).
+ */
+ private static class ListItem
+ extends ArrayList-
+ implements Item
+ {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public int getType()
+ {
+ return LIST_ITEM;
+ }
+
+ @Override
+ public boolean isNull()
+ {
+ return ( size() == 0 );
+ }
+
+ void normalize()
+ {
+ for( ListIterator
- iterator = listIterator( size() ); iterator.hasPrevious(); )
+ {
+ Item item = iterator.previous();
+ if ( item.isNull() )
+ {
+ iterator.remove(); // remove null trailing items: 0, "", empty list
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ @Override
+ public int compareTo( Item item )
+ {
+ if ( item == null )
+ {
+ if ( size() == 0 )
+ {
+ return 0; // 1-0 = 1- (normalize) = 1
+ }
+ Item first = get( 0 );
+ return first.compareTo( null );
+ }
+ switch ( item.getType() )
+ {
+ case INTEGER_ITEM:
+ return -1; // 1-1 < 1.0.x
+
+ case STRING_ITEM:
+ return 1; // 1-1 > 1-sp
+
+ case LIST_ITEM:
+ Iterator
- left = iterator();
+ Iterator
- right = ( (ListItem) item ).iterator();
+
+ while ( left.hasNext() || right.hasNext() )
+ {
+ Item l = left.hasNext() ? left.next() : null;
+ Item r = right.hasNext() ? right.next() : null;
+
+ // if this is shorter, then invert the compare and mul with -1
+ int result = l == null ? -1 * r.compareTo( l ) : l.compareTo( r );
+
+ if ( result != 0 )
+ {
+ return result;
+ }
+ }
+
+ return 0;
+
+ default:
+ throw new RuntimeException( "invalid item: " + item.getClass() );
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder buffer = new StringBuilder( "(" );
+ for( Iterator
- iter = iterator(); iter.hasNext(); )
+ {
+ buffer.append( iter.next() );
+ if ( iter.hasNext() )
+ {
+ buffer.append( ',' );
+ }
+ }
+ buffer.append( ')' );
+ return buffer.toString();
+ }
+ }
+
+ public ComparableVersion( String version )
+ {
+ parseVersion( version );
+ }
+
+ public final void parseVersion( String version )
+ {
+ this.value = version;
+
+ items = new ListItem();
+
+ version = version.toLowerCase( Locale.ENGLISH );
+
+ ListItem list = items;
+
+ Stack
- stack = new Stack
- ();
+ stack.push( list );
+
+ boolean isDigit = false;
+
+ int startIndex = 0;
+
+ for ( int i = 0; i < version.length(); i++ )
+ {
+ char c = version.charAt( i );
+
+ if ( c == '.' )
+ {
+ if ( i == startIndex )
+ {
+ list.add( IntegerItem.ZERO );
+ }
+ else
+ {
+ list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
+ }
+ startIndex = i + 1;
+ }
+ else if ( c == '-' )
+ {
+ if ( i == startIndex )
+ {
+ list.add( IntegerItem.ZERO );
+ }
+ else
+ {
+ list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
+ }
+ startIndex = i + 1;
+
+ if ( isDigit )
+ {
+ list.normalize(); // 1.0-* = 1-*
+
+ if ( ( i + 1 < version.length() ) && Character.isDigit( version.charAt( i + 1 ) ) )
+ {
+ // new ListItem only if previous were digits and new char is a digit,
+ // ie need to differentiate only 1.1 from 1-1
+ list.add( list = new ListItem() );
+
+ stack.push( list );
+ }
+ }
+ }
+ else if ( Character.isDigit( c ) )
+ {
+ if ( !isDigit && i > startIndex )
+ {
+ list.add( new StringItem( version.substring( startIndex, i ), true ) );
+ startIndex = i;
+ }
+
+ isDigit = true;
+ }
+ else
+ {
+ if ( isDigit && i > startIndex )
+ {
+ list.add( parseItem( true, version.substring( startIndex, i ) ) );
+ startIndex = i;
+ }
+
+ isDigit = false;
+ }
+ }
+
+ if ( version.length() > startIndex )
+ {
+ list.add( parseItem( isDigit, version.substring( startIndex ) ) );
+ }
+
+ while ( !stack.isEmpty() )
+ {
+ list = (ListItem) stack.pop();
+ list.normalize();
+ }
+
+ canonical = items.toString();
+ }
+
+ private static Item parseItem( boolean isDigit, String buf )
+ {
+ return isDigit ? new IntegerItem( buf ) : new StringItem( buf, false );
+ }
+
+ @Override
+ public int compareTo( ComparableVersion o )
+ {
+ return items.compareTo( o.items );
+ }
+
+ @Override
+ public String toString()
+ {
+ return value;
+ }
+
+ @Override
+ public boolean equals( Object o )
+ {
+ return ( o instanceof ComparableVersion ) && canonical.equals( ( (ComparableVersion) o ).canonical );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return canonical.hashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ultramine/bootstrap/versioning/DefaultArtifactVersion.java b/src/main/java/org/ultramine/bootstrap/versioning/DefaultArtifactVersion.java
new file mode 100644
index 0000000..ded634a
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/versioning/DefaultArtifactVersion.java
@@ -0,0 +1,105 @@
+/*
+ * Forge Mod Loader
+ * Copyright (c) 2012-2013 cpw.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser Public License v2.1
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Contributors:
+ * cpw - implementation
+ */
+
+package org.ultramine.bootstrap.versioning;
+
+public class DefaultArtifactVersion implements ArtifactVersion
+{
+ private ComparableVersion comparableVersion;
+ private String label;
+ private boolean unbounded;
+ private VersionRange range;
+
+ public DefaultArtifactVersion(String versionNumber)
+ {
+ comparableVersion = new ComparableVersion(versionNumber);
+ range = VersionRange.createFromVersion(versionNumber, this);
+ }
+
+ public DefaultArtifactVersion(String label, VersionRange range)
+ {
+ this.label = label;
+ this.range = range;
+ }
+ public DefaultArtifactVersion(String label, String version)
+ {
+ this(version);
+ this.label = label;
+ }
+
+ public DefaultArtifactVersion(String string, boolean unbounded)
+ {
+ this.label = string;
+ this.unbounded = true;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ return ((DefaultArtifactVersion)obj).containsVersion(this);
+ }
+
+ @Override
+ public int compareTo(ArtifactVersion o)
+ {
+ return unbounded ? 0 : this.comparableVersion.compareTo(((DefaultArtifactVersion)o).comparableVersion);
+ }
+
+ @Override
+ public String getLabel()
+ {
+ return label;
+ }
+
+ @Override
+ public boolean containsVersion(ArtifactVersion source)
+ {
+ if (!source.getLabel().equals(getLabel()))
+ {
+ return false;
+ }
+ if (unbounded)
+ {
+ return true;
+ }
+ if (range != null)
+ {
+ return range.containsVersion(source);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public String getVersionString()
+ {
+ return comparableVersion == null ? "unknown" : comparableVersion.toString();
+ }
+
+ @Override
+ public String getRangeString()
+ {
+ return range == null ? "any" : range.toString();
+ }
+ @Override
+ public String toString()
+ {
+ return label == null ? comparableVersion.toString() : label + ( unbounded ? "" : "@" + range);
+ }
+
+ public VersionRange getRange()
+ {
+ return range;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ultramine/bootstrap/versioning/InvalidVersionSpecificationException.java b/src/main/java/org/ultramine/bootstrap/versioning/InvalidVersionSpecificationException.java
new file mode 100644
index 0000000..333fd70
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/versioning/InvalidVersionSpecificationException.java
@@ -0,0 +1,47 @@
+/*
+ * Forge Mod Loader
+ * Copyright (c) 2012-2013 cpw.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser Public License v2.1
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Contributors:
+ * cpw - implementation
+ */
+
+package org.ultramine.bootstrap.versioning;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Occurs when a version is invalid.
+ *
+ * @author Brett Porter
+ */
+public class InvalidVersionSpecificationException extends Exception
+{
+ private static final long serialVersionUID = 1L;
+
+ public InvalidVersionSpecificationException( String message )
+ {
+ super( message );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ultramine/bootstrap/versioning/Restriction.java b/src/main/java/org/ultramine/bootstrap/versioning/Restriction.java
new file mode 100644
index 0000000..0baf78e
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/versioning/Restriction.java
@@ -0,0 +1,212 @@
+/*
+ * Forge Mod Loader
+ * Copyright (c) 2012-2013 cpw.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser Public License v2.1
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Contributors:
+ * cpw - implementation
+ */
+
+package org.ultramine.bootstrap.versioning;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Describes a restriction in versioning.
+ *
+ * @author Brett Porter
+ */
+public class Restriction
+{
+ private final ArtifactVersion lowerBound;
+
+ private final boolean lowerBoundInclusive;
+
+ private final ArtifactVersion upperBound;
+
+ private final boolean upperBoundInclusive;
+
+ public static final Restriction EVERYTHING = new Restriction( null, false, null, false );
+
+ public Restriction( ArtifactVersion lowerBound, boolean lowerBoundInclusive, ArtifactVersion upperBound,
+ boolean upperBoundInclusive )
+ {
+ this.lowerBound = lowerBound;
+ this.lowerBoundInclusive = lowerBoundInclusive;
+ this.upperBound = upperBound;
+ this.upperBoundInclusive = upperBoundInclusive;
+ }
+
+ public ArtifactVersion getLowerBound()
+ {
+ return lowerBound;
+ }
+
+ public boolean isLowerBoundInclusive()
+ {
+ return lowerBoundInclusive;
+ }
+
+ public ArtifactVersion getUpperBound()
+ {
+ return upperBound;
+ }
+
+ public boolean isUpperBoundInclusive()
+ {
+ return upperBoundInclusive;
+ }
+
+ public boolean containsVersion( ArtifactVersion version )
+ {
+ if ( lowerBound != null )
+ {
+ int comparison = lowerBound.compareTo( version );
+
+ if ( ( comparison == 0 ) && !lowerBoundInclusive )
+ {
+ return false;
+ }
+ if ( comparison > 0 )
+ {
+ return false;
+ }
+ }
+ if ( upperBound != null )
+ {
+ int comparison = upperBound.compareTo( version );
+
+ if ( ( comparison == 0 ) && !upperBoundInclusive )
+ {
+ return false;
+ }
+ if ( comparison < 0 )
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = 13;
+
+ if ( lowerBound == null )
+ {
+ result += 1;
+ }
+ else
+ {
+ result += lowerBound.hashCode();
+ }
+
+ result *= lowerBoundInclusive ? 1 : 2;
+
+ if ( upperBound == null )
+ {
+ result -= 3;
+ }
+ else
+ {
+ result -= upperBound.hashCode();
+ }
+
+ result *= upperBoundInclusive ? 2 : 3;
+
+ return result;
+ }
+
+ @Override
+ public boolean equals( Object other )
+ {
+ if ( this == other )
+ {
+ return true;
+ }
+
+ if ( !( other instanceof Restriction ) )
+ {
+ return false;
+ }
+
+ Restriction restriction = (Restriction) other;
+ if ( lowerBound != null )
+ {
+ if ( !lowerBound.equals( restriction.lowerBound ) )
+ {
+ return false;
+ }
+ }
+ else if ( restriction.lowerBound != null )
+ {
+ return false;
+ }
+
+ if ( lowerBoundInclusive != restriction.lowerBoundInclusive )
+ {
+ return false;
+ }
+
+ if ( upperBound != null )
+ {
+ if ( !upperBound.equals( restriction.upperBound ) )
+ {
+ return false;
+ }
+ }
+ else if ( restriction.upperBound != null )
+ {
+ return false;
+ }
+
+ if ( upperBoundInclusive != restriction.upperBoundInclusive )
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append( isLowerBoundInclusive() ? "[" : "(" );
+ if ( getLowerBound() != null )
+ {
+ buf.append( getLowerBound().toString() );
+ }
+ buf.append( "," );
+ if ( getUpperBound() != null )
+ {
+ buf.append( getUpperBound().toString() );
+ }
+ buf.append( isUpperBoundInclusive() ? "]" : ")" );
+
+ return buf.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ultramine/bootstrap/versioning/VersionParser.java b/src/main/java/org/ultramine/bootstrap/versioning/VersionParser.java
new file mode 100644
index 0000000..81d24e2
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/versioning/VersionParser.java
@@ -0,0 +1,66 @@
+/*
+ * Forge Mod Loader
+ * Copyright (c) 2012-2013 cpw.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser Public License v2.1
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Contributors:
+ * cpw - implementation
+ */
+
+package org.ultramine.bootstrap.versioning;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Parses version strings according to the specification here:
+ * http://docs.codehaus.org/display/MAVEN/Versioning
+ * and allows for comparison of versions based on that document.
+ * Bounded version specifications are defined as
+ * http://maven.apache.org/plugins/maven-enforcer-plugin/rules/versionRanges.html
+ *
+ * Borrows heavily from maven version range management code
+ *
+ * @author cpw
+ *
+ */
+public class VersionParser
+{
+ public static ArtifactVersion parseVersionReference(String labelledRef)
+ {
+ if (labelledRef == null || labelledRef.isEmpty())
+ {
+ throw new RuntimeException(String.format("Empty reference %s", labelledRef));
+ }
+ List parts = Arrays.asList(labelledRef.split("@"));
+ if (parts.size()>2)
+ {
+ throw new RuntimeException(String.format("Invalid versioned reference %s", labelledRef));
+ }
+ if (parts.size()==1)
+ {
+ return new DefaultArtifactVersion(parts.get(0), true);
+ }
+ return new DefaultArtifactVersion(parts.get(0),parseRange(parts.get(1)));
+ }
+
+ public static boolean satisfies(ArtifactVersion target, ArtifactVersion source)
+ {
+ return target.containsVersion(source);
+ }
+
+ public static VersionRange parseRange(String range)
+ {
+ try
+ {
+ return VersionRange.createFromVersionSpec(range);
+ }
+ catch (InvalidVersionSpecificationException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/ultramine/bootstrap/versioning/VersionRange.java b/src/main/java/org/ultramine/bootstrap/versioning/VersionRange.java
new file mode 100644
index 0000000..8d0a34c
--- /dev/null
+++ b/src/main/java/org/ultramine/bootstrap/versioning/VersionRange.java
@@ -0,0 +1,575 @@
+/*
+ * Forge Mod Loader
+ * Copyright (c) 2012-2013 cpw.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser Public License v2.1
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Contributors:
+ * cpw - implementation
+ */
+
+package org.ultramine.bootstrap.versioning;
+/*
+ * Modifications by cpw under LGPL 2.1 or later
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Construct a version range from a specification.
+ *
+ * @author Brett Porter
+ */
+public class VersionRange
+{
+ private final ArtifactVersion recommendedVersion;
+
+ private final List restrictions;
+
+ private VersionRange( ArtifactVersion recommendedVersion,
+ List restrictions )
+ {
+ this.recommendedVersion = recommendedVersion;
+ this.restrictions = restrictions;
+ }
+
+ public ArtifactVersion getRecommendedVersion()
+ {
+ return recommendedVersion;
+ }
+
+ public List getRestrictions()
+ {
+ return restrictions;
+ }
+
+ public VersionRange cloneOf()
+ {
+ List copiedRestrictions = null;
+
+ if ( restrictions != null )
+ {
+ copiedRestrictions = new ArrayList();
+
+ if ( !restrictions.isEmpty() )
+ {
+ copiedRestrictions.addAll( restrictions );
+ }
+ }
+
+ return new VersionRange( recommendedVersion, copiedRestrictions );
+ }
+
+ /**
+ * Factory method, for custom versioning schemes
+ * @param version version
+ * @param restrictions restriction list
+ * @return a new version range
+ */
+ public static VersionRange newRange(ArtifactVersion version, List restrictions)
+ {
+ return new VersionRange(version, restrictions);
+ }
+ /**
+ * Create a version range from a string representation
+ *
+ * Some spec examples are
+ *
+ * 1.0
Version 1.0
+ * [1.0,2.0)
Versions 1.0 (included) to 2.0 (not included)
+ * [1.0,2.0]
Versions 1.0 to 2.0 (both included)
+ * [1.5,)
Versions 1.5 and higher
+ * (,1.0],[1.2,)
Versions up to 1.0 (included) and 1.2 or higher
+ *
+ *
+ * @param spec string representation of a version or version range
+ * @return a new {@link VersionRange} object that represents the spec
+ * @throws InvalidVersionSpecificationException
+ *
+ */
+ public static VersionRange createFromVersionSpec( String spec )
+ throws InvalidVersionSpecificationException
+ {
+ if ( spec == null )
+ {
+ return null;
+ }
+
+ List restrictions = new ArrayList();
+ String process = spec;
+ ArtifactVersion version = null;
+ ArtifactVersion upperBound = null;
+ ArtifactVersion lowerBound = null;
+
+ while ( process.startsWith( "[" ) || process.startsWith( "(" ) )
+ {
+ int index1 = process.indexOf( ")" );
+ int index2 = process.indexOf( "]" );
+
+ int index = index2;
+ if ( index2 < 0 || index1 < index2 )
+ {
+ if ( index1 >= 0 )
+ {
+ index = index1;
+ }
+ }
+
+ if ( index < 0 )
+ {
+ throw new InvalidVersionSpecificationException( "Unbounded range: " + spec );
+ }
+
+ Restriction restriction = parseRestriction( process.substring( 0, index + 1 ) );
+ if ( lowerBound == null )
+ {
+ lowerBound = restriction.getLowerBound();
+ }
+ if ( upperBound != null )
+ {
+ if ( restriction.getLowerBound() == null || restriction.getLowerBound().compareTo( upperBound ) < 0 )
+ {
+ throw new InvalidVersionSpecificationException( "Ranges overlap: " + spec );
+ }
+ }
+ restrictions.add( restriction );
+ upperBound = restriction.getUpperBound();
+
+ process = process.substring( index + 1 ).trim();
+
+ if ( process.length() > 0 && process.startsWith( "," ) )
+ {
+ process = process.substring( 1 ).trim();
+ }
+ }
+
+ if ( process.length() > 0 )
+ {
+ if ( restrictions.size() > 0 )
+ {
+ throw new InvalidVersionSpecificationException(
+ "Only fully-qualified sets allowed in multiple set scenario: " + spec );
+ }
+ else
+ {
+ version = new DefaultArtifactVersion( process );
+ restrictions.add( Restriction.EVERYTHING );
+ }
+ }
+
+ return new VersionRange( version, restrictions );
+ }
+
+ private static Restriction parseRestriction( String spec )
+ throws InvalidVersionSpecificationException
+ {
+ boolean lowerBoundInclusive = spec.startsWith( "[" );
+ boolean upperBoundInclusive = spec.endsWith( "]" );
+
+ String process = spec.substring( 1, spec.length() - 1 ).trim();
+
+ Restriction restriction;
+
+ int index = process.indexOf( "," );
+
+ if ( index < 0 )
+ {
+ if ( !lowerBoundInclusive || !upperBoundInclusive )
+ {
+ throw new InvalidVersionSpecificationException( "Single version must be surrounded by []: " + spec );
+ }
+
+ ArtifactVersion version = new DefaultArtifactVersion( process );
+
+ restriction = new Restriction( version, lowerBoundInclusive, version, upperBoundInclusive );
+ }
+ else
+ {
+ String lowerBound = process.substring( 0, index ).trim();
+ String upperBound = process.substring( index + 1 ).trim();
+ if ( lowerBound.equals( upperBound ) )
+ {
+ throw new InvalidVersionSpecificationException( "Range cannot have identical boundaries: " + spec );
+ }
+
+ ArtifactVersion lowerVersion = null;
+ if ( lowerBound.length() > 0 )
+ {
+ lowerVersion = new DefaultArtifactVersion( lowerBound );
+ }
+ ArtifactVersion upperVersion = null;
+ if ( upperBound.length() > 0 )
+ {
+ upperVersion = new DefaultArtifactVersion( upperBound );
+ }
+
+ if ( upperVersion != null && lowerVersion != null && upperVersion.compareTo( lowerVersion ) < 0 )
+ {
+ throw new InvalidVersionSpecificationException( "Range defies version ordering: " + spec );
+ }
+
+ restriction = new Restriction( lowerVersion, lowerBoundInclusive, upperVersion, upperBoundInclusive );
+ }
+
+ return restriction;
+ }
+
+ public static VersionRange createFromVersion( String version , ArtifactVersion existing)
+ {
+ List restrictions = Collections.emptyList();
+ if (existing == null)
+ {
+ existing = new DefaultArtifactVersion( version );
+ }
+ return new VersionRange(existing , restrictions );
+ }
+
+ /**
+ * Creates and returns a new VersionRange
that is a restriction of this
+ * version range and the specified version range.
+ *
+ * Note: Precedence is given to the recommended version from this version range over the
+ * recommended version from the specified version range.
+ *
+ *
+ * @param restriction the VersionRange
that will be used to restrict this version
+ * range.
+ * @return the VersionRange
that is a restriction of this version range and the
+ * specified version range.
+ *
+ * The restrictions of the returned version range will be an intersection of the restrictions
+ * of this version range and the specified version range if both version ranges have
+ * restrictions. Otherwise, the restrictions on the returned range will be empty.
+ *
+ *
+ * The recommended version of the returned version range will be the recommended version of
+ * this version range, provided that ranges falls within the intersected restrictions. If
+ * the restrictions are empty, this version range's recommended version is used if it is not
+ * null
. If it is null
, the specified version range's recommended
+ * version is used (provided it is non-null
). If no recommended version can be
+ * obtained, the returned version range's recommended version is set to null
.
+ *
+ * @throws NullPointerException if the specified VersionRange
is
+ * null
.
+ */
+ public VersionRange restrict( VersionRange restriction )
+ {
+ List r1 = this.restrictions;
+ List r2 = restriction.restrictions;
+ List restrictions;
+
+ if ( r1.isEmpty() || r2.isEmpty() )
+ {
+ restrictions = Collections.emptyList();
+ }
+ else
+ {
+ restrictions = intersection( r1, r2 );
+ }
+
+ ArtifactVersion version = null;
+ if ( restrictions.size() > 0 )
+ {
+ for ( Restriction r : restrictions )
+ {
+ if ( recommendedVersion != null && r.containsVersion( recommendedVersion ) )
+ {
+ // if we find the original, use that
+ version = recommendedVersion;
+ break;
+ }
+ else if ( version == null && restriction.getRecommendedVersion() != null
+ && r.containsVersion( restriction.getRecommendedVersion() ) )
+ {
+ // use this if we can, but prefer the original if possible
+ version = restriction.getRecommendedVersion();
+ }
+ }
+ }
+ // Either the original or the specified version ranges have no restrictions
+ else if ( recommendedVersion != null )
+ {
+ // Use the original recommended version since it exists
+ version = recommendedVersion;
+ }
+ else if ( restriction.recommendedVersion != null )
+ {
+ // Use the recommended version from the specified VersionRange since there is no
+ // original recommended version
+ version = restriction.recommendedVersion;
+ }
+/* TODO: should throw this immediately, but need artifactName
+ else
+ {
+ throw new OverConstrainedVersionException( "Restricting incompatible version ranges" );
+ }
+*/
+
+ return new VersionRange( version, restrictions );
+ }
+
+ private List intersection( List r1, List r2 )
+ {
+ List restrictions = new ArrayList( r1.size() + r2.size() );
+ Iterator i1 = r1.iterator();
+ Iterator i2 = r2.iterator();
+ Restriction res1 = i1.next();
+ Restriction res2 = i2.next();
+
+ boolean done = false;
+ while ( !done )
+ {
+ if ( res1.getLowerBound() == null || res2.getUpperBound() == null
+ || res1.getLowerBound().compareTo( res2.getUpperBound() ) <= 0 )
+ {
+ if ( res1.getUpperBound() == null || res2.getLowerBound() == null
+ || res1.getUpperBound().compareTo( res2.getLowerBound() ) >= 0 )
+ {
+ ArtifactVersion lower;
+ ArtifactVersion upper;
+ boolean lowerInclusive;
+ boolean upperInclusive;
+
+ // overlaps
+ if ( res1.getLowerBound() == null )
+ {
+ lower = res2.getLowerBound();
+ lowerInclusive = res2.isLowerBoundInclusive();
+ }
+ else if ( res2.getLowerBound() == null )
+ {
+ lower = res1.getLowerBound();
+ lowerInclusive = res1.isLowerBoundInclusive();
+ }
+ else
+ {
+ int comparison = res1.getLowerBound().compareTo( res2.getLowerBound() );
+ if ( comparison < 0 )
+ {
+ lower = res2.getLowerBound();
+ lowerInclusive = res2.isLowerBoundInclusive();
+ }
+ else if ( comparison == 0 )
+ {
+ lower = res1.getLowerBound();
+ lowerInclusive = res1.isLowerBoundInclusive() && res2.isLowerBoundInclusive();
+ }
+ else
+ {
+ lower = res1.getLowerBound();
+ lowerInclusive = res1.isLowerBoundInclusive();
+ }
+ }
+
+ if ( res1.getUpperBound() == null )
+ {
+ upper = res2.getUpperBound();
+ upperInclusive = res2.isUpperBoundInclusive();
+ }
+ else if ( res2.getUpperBound() == null )
+ {
+ upper = res1.getUpperBound();
+ upperInclusive = res1.isUpperBoundInclusive();
+ }
+ else
+ {
+ int comparison = res1.getUpperBound().compareTo( res2.getUpperBound() );
+ if ( comparison < 0 )
+ {
+ upper = res1.getUpperBound();
+ upperInclusive = res1.isUpperBoundInclusive();
+ }
+ else if ( comparison == 0 )
+ {
+ upper = res1.getUpperBound();
+ upperInclusive = res1.isUpperBoundInclusive() && res2.isUpperBoundInclusive();
+ }
+ else
+ {
+ upper = res2.getUpperBound();
+ upperInclusive = res2.isUpperBoundInclusive();
+ }
+ }
+
+ // don't add if they are equal and one is not inclusive
+ if ( lower == null || upper == null || lower.compareTo( upper ) != 0 )
+ {
+ restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) );
+ }
+ else if ( lowerInclusive && upperInclusive )
+ {
+ restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) );
+ }
+
+ //noinspection ObjectEquality
+ if ( upper == res2.getUpperBound() )
+ {
+ // advance res2
+ if ( i2.hasNext() )
+ {
+ res2 = i2.next();
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ else
+ {
+ // advance res1
+ if ( i1.hasNext() )
+ {
+ res1 = i1.next();
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ }
+ else
+ {
+ // move on to next in r1
+ if ( i1.hasNext() )
+ {
+ res1 = i1.next();
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ }
+ else
+ {
+ // move on to next in r2
+ if ( i2.hasNext() )
+ {
+ res2 = i2.next();
+ }
+ else
+ {
+ done = true;
+ }
+ }
+ }
+
+ return restrictions;
+ }
+
+ @Override
+ public String toString()
+ {
+ if ( recommendedVersion != null )
+ {
+ return recommendedVersion.getVersionString();
+ }
+ else
+ {
+ return restrictions.stream().map(Restriction::toString).collect(Collectors.joining(","));
+ }
+ }
+
+ public ArtifactVersion matchVersion( List versions )
+ {
+ // TODO: could be more efficient by sorting the list and then moving along the restrictions in order?
+
+ ArtifactVersion matched = null;
+ for ( ArtifactVersion version : versions )
+ {
+ if ( containsVersion( version ) )
+ {
+ // valid - check if it is greater than the currently matched version
+ if ( matched == null || version.compareTo( matched ) > 0 )
+ {
+ matched = version;
+ }
+ }
+ }
+ return matched;
+ }
+
+ public boolean containsVersion( ArtifactVersion version )
+ {
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( version ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasRestrictions()
+ {
+ return !restrictions.isEmpty() && recommendedVersion == null;
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ {
+ return true;
+ }
+ if ( !( obj instanceof VersionRange ) )
+ {
+ return false;
+ }
+ VersionRange other = (VersionRange) obj;
+
+ boolean equals =
+ recommendedVersion == other.recommendedVersion
+ || ( ( recommendedVersion != null ) && recommendedVersion.equals( other.recommendedVersion ) );
+ equals &=
+ restrictions == other.restrictions
+ || ( ( restrictions != null ) && restrictions.equals( other.restrictions ) );
+ return equals;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 31 * hash + ( recommendedVersion == null ? 0 : recommendedVersion.hashCode() );
+ hash = 31 * hash + ( restrictions == null ? 0 : restrictions.hashCode() );
+ return hash;
+ }
+
+ public boolean isUnboundedAbove()
+ {
+ return restrictions.size() == 1 && restrictions.get(0).getUpperBound() == null && !restrictions.get(0).isUpperBoundInclusive();
+ }
+
+ public String getLowerBoundString()
+ {
+ return restrictions.size() == 1 ? restrictions.get(0).getLowerBound().getVersionString() : "";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/crt/DSTRootCAX3.pem b/src/main/resources/assets/crt/DSTRootCAX3.pem
new file mode 100644
index 0000000..b2e43c9
--- /dev/null
+++ b/src/main/resources/assets/crt/DSTRootCAX3.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
+PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
+Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
+rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
+OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
+xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
+7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
+aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
+SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
+ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
+AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
+R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
+JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
+Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
diff --git a/src/main/resources/assets/lang/en_US.lang b/src/main/resources/assets/lang/en_US.lang
new file mode 100644
index 0000000..7a3630d
--- /dev/null
+++ b/src/main/resources/assets/lang/en_US.lang
@@ -0,0 +1,15 @@
+stage.start=Bootstrap for UltraMine Core 1.7.10
+stage.versions=Retrieving versions
+stage.downloading=Checking & downloading dependencies
+stage.scripts=Creating scripts
+stage.finished=Bootstrap finished. Run ./start.sh (or start.bat) to run ultramine server
+
+error.mavenmetadata=Failed to retriever maven metadata from %s
+error.pom=Failed to retriever pom xml from %s
+error.addrepo.file=Failed to add repository: path is corrupted: %s
+error.unavailable.host=Failed to resolve hostname (%s); check your network connection
+error.unavailable.maven=Maven server (%s) is currently unavailable; check your network connection
+error.download.dependency=Failed to download dependency: %s (%s)
+error.write.file=Error write file %s: %s
+
+hint.stacktrace=Run with --stacktrace for more information
diff --git a/src/main/resources/assets/lang/ru_RU.lang b/src/main/resources/assets/lang/ru_RU.lang
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/main/resources/assets/lang/ru_RU.lang
diff --git a/src/main/resources/assets/script/java_command_line.txt b/src/main/resources/assets/script/java_command_line.txt
new file mode 100644
index 0000000..ced75b2
--- /dev/null
+++ b/src/main/resources/assets/script/java_command_line.txt
@@ -0,0 +1,16 @@
+# Use start.sh to start minecraft server, don't use this file
+
+java \
+-Xms{xms} \
+-Xmx{xmx} \
+-Xmn{xmn} \
+-XX:+UseParallelGC \
+-XX:+UseTLAB \
+-XX:+AggressiveOpts \
+-XX:+UseFastEmptyMethods \
+-XX:+UseFastAccessorMethods \
+-Dfile.encoding=utf8 \
+-Dorg.ultramine.terminal={terminal} \
+-Dorg.ultramine.dirs.vanilla={vanilla_dir} \
+-Dorg.ultramine.dirs.worlds={worlds_dir} \
+-cp "{classpath}" cpw.mods.fml.relauncher.ServerLaunchWrapper
\ No newline at end of file
diff --git a/src/main/resources/assets/script/shell/start.sh b/src/main/resources/assets/script/shell/start.sh
new file mode 100644
index 0000000..e4f222a
--- /dev/null
+++ b/src/main/resources/assets/script/shell/start.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+while :
+do
+ ./ultramine_server_run_line.sh
+ echo "waiting before restarting"
+ sleep 10
+done
\ No newline at end of file
diff --git a/src/main/resources/assets/script/windows/start.bat b/src/main/resources/assets/script/windows/start.bat
new file mode 100644
index 0000000..3bd412c
--- /dev/null
+++ b/src/main/resources/assets/script/windows/start.bat
@@ -0,0 +1,5 @@
+:loop
+ultramine_server_run_line.bat
+echo waiting before restarting
+timeout 10
+goto loop
\ No newline at end of file
diff --git a/src/main/resources/assets/server.properties b/src/main/resources/assets/server.properties
new file mode 100644
index 0000000..181cad3
--- /dev/null
+++ b/src/main/resources/assets/server.properties
@@ -0,0 +1,34 @@
+#Minecraft server properties
+#http://server.properties/owner.html
+generator-settings=
+op-permission-level=4
+allow-nether=true
+level-name=world
+enable-query=false
+query-port=25565
+allow-flight=false
+announce-player-achievements=true
+server-port=25565
+level-type=DEFAULT
+enable-rcon=false
+rcon-port=25565
+level-seed=
+force-gamemode=false
+server-ip=
+max-build-height=256
+spawn-npcs=true
+white-list=false
+spawn-animals=true
+snooper-enabled=true
+online-mode=true
+resource-pack=
+pvp=true
+difficulty=1
+enable-command-block=false
+gamemode=0
+player-idle-timeout=0
+max-players=20
+spawn-monsters=true
+generate-structures=true
+view-distance=10
+motd=A Minecraft Server
\ No newline at end of file
diff --git a/src/main/resources/assets/server.yml b/src/main/resources/assets/server.yml
new file mode 100644
index 0000000..11d34d1
--- /dev/null
+++ b/src/main/resources/assets/server.yml
@@ -0,0 +1,72 @@
+listen:
+ minecraft:
+ serverIP: '{server-ip}'
+ port: {server-port}
+ query:
+ enabled: {enable-query}
+ port: {query-port}
+ rcon:
+ enabled: {enable-rcon}
+ port: {rcon-port}
+ password: ''
+ whitelist: null
+settings:
+ authorization:
+ onlineMode: {online-mode}
+ player:
+ playerIdleTimeout: {player-idle-timeout}
+ gamemode: 0
+ maxPlayers: {max-players}
+ forceGamemode: false
+ whiteList: {white-list}
+ other:
+ snooperEnabled: {snooper-enabled}
+ hardcore: false
+ resourcePack: ''
+ enableCommandBlock: {enable-command-block}
+ splitWorldDirs: {split-world-dirs}
+ recipeCacheEnabled: true
+ spawnLocations:
+ firstSpawn: spawn
+ deathSpawn: spawn
+ respawnOnBed: true
+ teleportation:
+ cooldown: 60
+ delay: 5
+ interWorldHome: true
+ interWorldWarp: true
+ messages:
+ announcePlayerAchievements: {announce-player-achievements}
+ motd: {motd}
+ watchdogThread:
+ timeout: 120
+ restart: true
+ inSQLServerStorage:
+ enabled: false
+ database: global
+ tablePrefix: mc_
+ security:
+ allowFlight: false
+ checkBreakSpeed: true
+tools:
+ autobroadcast:
+ enabled: false
+ intervalSeconds: 600
+ messages: []
+ showAllMessages: false
+ autoDebugInfo:
+ enabled: false
+ intervalSeconds: 600
+ autobackup:
+ enabled: false
+ interval: 60
+ maxBackups: 10
+ maxDirSize: 50000
+ worlds: null
+ notifyPlayers: true
+ warpProtection: []
+ economy:
+ startBalance: 30.0
+databases: {}
+vanilla:
+ unresolved: {}