Fix #467
This commit is contained in:
@@ -296,7 +296,7 @@ public final class LauncherHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
|
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
|
||||||
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0.51");
|
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0_51");
|
||||||
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
|
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
|
||||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
||||||
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
|
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import javafx.scene.layout.StackPane;
|
|||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
|
import org.jackhuang.hmcl.game.Version;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
@@ -49,6 +50,7 @@ import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
|||||||
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
import org.jackhuang.hmcl.util.javafx.MultiStepBinding;
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -130,7 +132,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
|||||||
HMCLGameRepository repository = profile.getRepository();
|
HMCLGameRepository repository = profile.getRepository();
|
||||||
List<Node> children = repository.getVersions().parallelStream()
|
List<Node> children = repository.getVersions().parallelStream()
|
||||||
.filter(version -> !version.isHidden())
|
.filter(version -> !version.isHidden())
|
||||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
.sorted(Comparator.comparing(Version::getReleaseTime).thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||||
.map(version -> {
|
.map(version -> {
|
||||||
StackPane pane = new StackPane();
|
StackPane pane = new StackPane();
|
||||||
GameItem item = new GameItem(profile, version.getId());
|
GameItem item = new GameItem(profile, version.getId());
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import javafx.scene.control.Control;
|
|||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import org.jackhuang.hmcl.event.EventBus;
|
import org.jackhuang.hmcl.event.EventBus;
|
||||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
|
||||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
|
import org.jackhuang.hmcl.game.Version;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
|||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ public class GameList extends Control implements DecoratorPage {
|
|||||||
toggleGroup.getProperties().put("ReferenceHolder", listenerHolder);
|
toggleGroup.getProperties().put("ReferenceHolder", listenerHolder);
|
||||||
List<GameListItem> children = repository.getVersions().parallelStream()
|
List<GameListItem> children = repository.getVersions().parallelStream()
|
||||||
.filter(version -> !version.isHidden())
|
.filter(version -> !version.isHidden())
|
||||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
.sorted(Comparator.comparing(Version::getReleaseTime).thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||||
.map(version -> new GameListItem(toggleGroup, profile, version.getId()))
|
.map(version -> new GameListItem(toggleGroup, profile, version.getId()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
JFXUtilities.runInFX(() -> {
|
JFXUtilities.runInFX(() -> {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import javafx.stage.FileChooser;
|
|||||||
import org.jackhuang.hmcl.game.World;
|
import org.jackhuang.hmcl.game.World;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||||
import org.jackhuang.hmcl.util.versioning.IntVersionNumber;
|
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -67,7 +66,7 @@ public class WorldListItem extends Control {
|
|||||||
|
|
||||||
public void manageDatapacks() {
|
public void manageDatapacks() {
|
||||||
if (world.getGameVersion() == null || // old game will not write game version to level.dat
|
if (world.getGameVersion() == null || // old game will not write game version to level.dat
|
||||||
(IntVersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version
|
(VersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version
|
||||||
&& VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
&& VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||||
Controllers.dialog(i18n("world.datapack.1_13"));
|
Controllers.dialog(i18n("world.datapack.1_13"));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
|||||||
versions.clear();
|
versions.clear();
|
||||||
|
|
||||||
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
|
for (Map.Entry<String, int[]> entry : root.getGameVersions().entrySet()) {
|
||||||
Optional<String> gameVersion = VersionNumber.parseVersion(entry.getKey());
|
String gameVersion = VersionNumber.normalize(entry.getKey());
|
||||||
if (!gameVersion.isPresent())
|
|
||||||
continue;
|
|
||||||
for (int v : entry.getValue()) {
|
for (int v : entry.getValue()) {
|
||||||
ForgeVersion version = root.getNumber().get(v);
|
ForgeVersion version = root.getNumber().get(v);
|
||||||
if (version == null)
|
if (version == null)
|
||||||
@@ -84,7 +82,7 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
|||||||
|
|
||||||
if (jar == null)
|
if (jar == null)
|
||||||
continue;
|
continue;
|
||||||
versions.put(gameVersion.get(), new ForgeRemoteVersion(
|
versions.put(gameVersion, new ForgeRemoteVersion(
|
||||||
version.getGameVersion(), version.getVersion(), jar
|
version.getGameVersion(), version.getVersion(), jar
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,11 +71,10 @@ public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemot
|
|||||||
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
|
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
|
||||||
String gameVersion = entry.getKey();
|
String gameVersion = entry.getKey();
|
||||||
LiteLoaderGameVersions liteLoader = entry.getValue();
|
LiteLoaderGameVersions liteLoader = entry.getValue();
|
||||||
Optional<String> gg = VersionNumber.parseVersion(gameVersion);
|
|
||||||
if (!gg.isPresent())
|
String gg = VersionNumber.normalize(gameVersion);
|
||||||
continue;
|
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
|
||||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
|
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
|
||||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
|
|||||||
@@ -71,11 +71,10 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
|||||||
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
|
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
|
||||||
String gameVersion = entry.getKey();
|
String gameVersion = entry.getKey();
|
||||||
LiteLoaderGameVersions liteLoader = entry.getValue();
|
LiteLoaderGameVersions liteLoader = entry.getValue();
|
||||||
Optional<String> gg = VersionNumber.parseVersion(gameVersion);
|
|
||||||
if (!gg.isPresent())
|
String gg = VersionNumber.normalize(gameVersion);
|
||||||
continue;
|
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
|
||||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
|
doBranch(gg, gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
|
||||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
|
|||||||
@@ -70,8 +70,9 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
|
|||||||
|
|
||||||
if (StringUtils.isBlank(element.getGameVersion()))
|
if (StringUtils.isBlank(element.getGameVersion()))
|
||||||
continue;
|
continue;
|
||||||
VersionNumber.parseVersion(element.getGameVersion())
|
|
||||||
.ifPresent(gameVersion -> versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror, isPre)));
|
String gameVersion = VersionNumber.normalize(element.getGameVersion());
|
||||||
|
versions.put(gameVersion, new OptiFineRemoteVersion(gameVersion, version, () -> mirror, isPre));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ public final class ModManager {
|
|||||||
File modsDirectory = new File(repository.getRunDirectory(id), "mods");
|
File modsDirectory = new File(repository.getRunDirectory(id), "mods");
|
||||||
Consumer<File> puter = modFile -> Lang.ignoringException(() -> modCache.put(id, ModInfo.fromFile(modFile)));
|
Consumer<File> puter = modFile -> Lang.ignoringException(() -> modCache.put(id, ModInfo.fromFile(modFile)));
|
||||||
Optional.ofNullable(modsDirectory.listFiles()).map(Arrays::stream).ifPresent(files -> files.forEach(modFile -> {
|
Optional.ofNullable(modsDirectory.listFiles()).map(Arrays::stream).ifPresent(files -> files.forEach(modFile -> {
|
||||||
if (modFile.isDirectory() && VersionNumber.parseVersion(modFile.getName()).isPresent())
|
if (modFile.isDirectory() && VersionNumber.isIntVersionNumber(modFile.getName())) {
|
||||||
|
// If the folder name is game version, forge will search mod in this subdirectory
|
||||||
Optional.ofNullable(modFile.listFiles()).map(Arrays::stream).ifPresent(x -> x.forEach(puter));
|
Optional.ofNullable(modFile.listFiles()).map(Arrays::stream).ifPresent(x -> x.forEach(puter));
|
||||||
else
|
} else {
|
||||||
puter.accept(modFile);
|
puter.accept(modFile);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public final class JavaVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public VersionNumber getVersionNumber() {
|
public VersionNumber getVersionNumber() {
|
||||||
return VersionNumber.asVersion(longVersion.replace('_', '.'));
|
return VersionNumber.asVersion(longVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.util.versioning;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a version string contains '-', a {@link ComposedVersionNumber}
|
|
||||||
* will be generated.
|
|
||||||
*
|
|
||||||
* Formats like 1.7.10-OptiFine, 1.12.2-Forge
|
|
||||||
*
|
|
||||||
* @author huangyuhui
|
|
||||||
*/
|
|
||||||
public final class ComposedVersionNumber extends VersionNumber {
|
|
||||||
List<VersionNumber> composed;
|
|
||||||
|
|
||||||
public static boolean isComposedVersionNumber(String version) {
|
|
||||||
return version.contains("-");
|
|
||||||
}
|
|
||||||
|
|
||||||
ComposedVersionNumber(String version) {
|
|
||||||
composed = Arrays.stream(version.split("-"))
|
|
||||||
.map(VersionNumber::asVersion)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return composed.stream().map(VersionNumber::toString).collect(Collectors.joining("-"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.util.versioning;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a version string formats x.x.x.x, a {@code IntVersionNumber}
|
|
||||||
* will be generated.
|
|
||||||
*
|
|
||||||
* @author huangyuhui
|
|
||||||
*/
|
|
||||||
public final class IntVersionNumber extends VersionNumber {
|
|
||||||
|
|
||||||
final List<Integer> version;
|
|
||||||
|
|
||||||
public static boolean isIntVersionNumber(String version) {
|
|
||||||
if (version.chars().noneMatch(ch -> ch != '.' && (ch < '0' || ch > '9'))
|
|
||||||
&& !version.contains("..") && StringUtils.isNotBlank(version)) {
|
|
||||||
String[] arr = version.split("\\.");
|
|
||||||
for (String str : arr)
|
|
||||||
if (str.length() > 9)
|
|
||||||
// Numbers which are larger than 1e9 cannot be stored as integer.
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IntVersionNumber(String version) {
|
|
||||||
if (!isIntVersionNumber(version))
|
|
||||||
throw new IllegalArgumentException("The version " + version + " is malformed, only dots and digits are allowed.");
|
|
||||||
|
|
||||||
List<Integer> versions = Arrays.stream(version.split("\\."))
|
|
||||||
.map(Integer::parseInt)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
while (!versions.isEmpty() && versions.get(versions.size() - 1) == 0)
|
|
||||||
versions.remove(versions.size() - 1);
|
|
||||||
|
|
||||||
this.version = versions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get(int index) {
|
|
||||||
return version.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return version.stream().map(Object::toString).collect(Collectors.joining("."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.util.versioning;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a version string contains alphabets, a {@code StringVersionNumber}
|
|
||||||
* will be constructed.
|
|
||||||
*
|
|
||||||
* @author huangyuhui
|
|
||||||
*/
|
|
||||||
public final class StringVersionNumber extends VersionNumber {
|
|
||||||
|
|
||||||
private final String version;
|
|
||||||
|
|
||||||
StringVersionNumber(String version) {
|
|
||||||
Objects.requireNonNull(version);
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,108 +1,339 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.util.versioning;
|
package org.jackhuang.hmcl.util.versioning;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.math.BigInteger;
|
||||||
import java.util.Optional;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The formatted version number represents a version string.
|
* Copied from org.apache.maven.artifact.versioning.ComparableVersion
|
||||||
*
|
* Apache License 2.0
|
||||||
* @author huangyuhui
|
|
||||||
*/
|
*/
|
||||||
public abstract class VersionNumber implements Comparable<VersionNumber> {
|
public class VersionNumber implements Comparable<VersionNumber> {
|
||||||
|
|
||||||
public static VersionNumber asVersion(String version) {
|
public static VersionNumber asVersion(String version) {
|
||||||
Objects.requireNonNull(version);
|
Objects.requireNonNull(version);
|
||||||
if (ComposedVersionNumber.isComposedVersionNumber(version))
|
return new VersionNumber(version);
|
||||||
return new ComposedVersionNumber(version);
|
|
||||||
else if (IntVersionNumber.isIntVersionNumber(version))
|
|
||||||
return new IntVersionNumber(version);
|
|
||||||
else
|
|
||||||
return new StringVersionNumber(version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<String> parseVersion(String str) {
|
public static String normalize(String str) {
|
||||||
if (IntVersionNumber.isIntVersionNumber(str))
|
return new VersionNumber(str).getCanonical();
|
||||||
return Optional.of(new IntVersionNumber(str).toString());
|
}
|
||||||
else
|
|
||||||
return Optional.empty();
|
public static boolean isIntVersionNumber(String version) {
|
||||||
|
if (version.chars().noneMatch(ch -> ch != '.' && (ch < '0' || ch > '9'))
|
||||||
|
&& !version.contains("..") && StringUtils.isNotBlank(version)) {
|
||||||
|
String[] arr = version.split("\\.");
|
||||||
|
for (String str : arr)
|
||||||
|
if (str.length() > 9)
|
||||||
|
// Numbers which are larger than 1e9 cannot be stored as integer.
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private String canonical;
|
||||||
|
private ListItem items;
|
||||||
|
|
||||||
|
private interface Item {
|
||||||
|
int INTEGER_ITEM = 0;
|
||||||
|
int STRING_ITEM = 1;
|
||||||
|
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 final BigInteger value;
|
||||||
|
|
||||||
|
public static final IntegerItem ZERO = new IntegerItem();
|
||||||
|
|
||||||
|
private IntegerItem() {
|
||||||
|
this.value = BigInteger.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegerItem(String str) {
|
||||||
|
this.value = new BigInteger(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return INTEGER_ITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNull() {
|
||||||
|
return BigInteger.ZERO.equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a string in the version item list, usually a qualifier.
|
||||||
|
*/
|
||||||
|
private static class StringItem
|
||||||
|
implements Item {
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
StringItem(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return STRING_ITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNull() {
|
||||||
|
return value.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo(Item item) {
|
||||||
|
if (item == null) {
|
||||||
|
// 1-string > 1
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
switch (item.getType()) {
|
||||||
|
case INTEGER_ITEM:
|
||||||
|
return -1; // 1.any < 1.1 ?
|
||||||
|
|
||||||
|
case STRING_ITEM:
|
||||||
|
return value.compareTo(((StringItem) item).value);
|
||||||
|
|
||||||
|
case LIST_ITEM:
|
||||||
|
return -1; // 1.any < 1-1
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("invalid item: " + item.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Item>
|
||||||
|
implements Item {
|
||||||
|
Character separator;
|
||||||
|
|
||||||
|
public ListItem() {}
|
||||||
|
|
||||||
|
public ListItem(char separator) {
|
||||||
|
this.separator = separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return LIST_ITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNull() {
|
||||||
|
return (size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void normalize() {
|
||||||
|
for (int i = size() - 1; i >= 0; i--) {
|
||||||
|
Item lastItem = get(i);
|
||||||
|
|
||||||
|
if (lastItem.isNull()) {
|
||||||
|
// remove null trailing items: 0, "", empty list
|
||||||
|
remove(i);
|
||||||
|
} else if (!(lastItem instanceof ListItem)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Item> left = iterator();
|
||||||
|
Iterator<Item> 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 ? (r == null ? 0 : -1 * r.compareTo(l)) : l.compareTo(r);
|
||||||
|
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("invalid item: " + item.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
for (Item item : this) {
|
||||||
|
if (buffer.length() > 0) {
|
||||||
|
if (!(item instanceof ListItem))
|
||||||
|
buffer.append('.');
|
||||||
|
}
|
||||||
|
buffer.append(item);
|
||||||
|
}
|
||||||
|
if (separator != null)
|
||||||
|
return separator + buffer.toString();
|
||||||
|
else
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VersionNumber(String version) {
|
||||||
|
parseVersion(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseVersion(String version) {
|
||||||
|
this.value = version;
|
||||||
|
|
||||||
|
items = new ListItem();
|
||||||
|
|
||||||
|
version = version.toLowerCase(Locale.ENGLISH);
|
||||||
|
|
||||||
|
ListItem list = items;
|
||||||
|
|
||||||
|
Stack<Item> 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(version.substring(startIndex, i)));
|
||||||
|
}
|
||||||
|
startIndex = i + 1;
|
||||||
|
} else if ("!\"#$%&'()*+,-/:;<=>?@[\\]^_`{|}~".indexOf(c) != -1) {
|
||||||
|
if (i == startIndex) {
|
||||||
|
list.add(IntegerItem.ZERO);
|
||||||
|
} else {
|
||||||
|
list.add(parseItem(version.substring(startIndex, i)));
|
||||||
|
}
|
||||||
|
startIndex = i + 1;
|
||||||
|
|
||||||
|
list.add(list = new ListItem(c));
|
||||||
|
stack.push(list);
|
||||||
|
} else if (Character.isDigit(c)) {
|
||||||
|
if (!isDigit && i > startIndex) {
|
||||||
|
list.add(parseItem(version.substring(startIndex, i)));
|
||||||
|
startIndex = i;
|
||||||
|
|
||||||
|
list.add(list = new ListItem());
|
||||||
|
stack.push(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDigit = true;
|
||||||
|
} else {
|
||||||
|
if (isDigit && i > startIndex) {
|
||||||
|
list.add(parseItem(version.substring(startIndex, i)));
|
||||||
|
startIndex = i;
|
||||||
|
|
||||||
|
list.add(list = new ListItem());
|
||||||
|
stack.push(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDigit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version.length() > startIndex) {
|
||||||
|
list.add(parseItem(version.substring(startIndex)));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!stack.isEmpty()) {
|
||||||
|
list = (ListItem) stack.pop();
|
||||||
|
list.normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
canonical = items.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Item parseItem(String buf) {
|
||||||
|
return buf.chars().allMatch(Character::isDigit) ? new IntegerItem(buf) : new StringItem(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(VersionNumber o) {
|
public int compareTo(VersionNumber o) {
|
||||||
return COMPARATOR.compare(this, o);
|
return items.compareTo(o.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object another) {
|
public String toString() {
|
||||||
return another instanceof VersionNumber && this.toString().equals(another.toString());
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCanonical() {
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof VersionNumber && canonical.equals(((VersionNumber) o).canonical);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return toString().hashCode();
|
return canonical.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T extends Comparable<T>> int compareTo(List<T> a, List<T> b) {
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < a.size() && i < b.size(); ++i) {
|
|
||||||
int res = a.get(i).compareTo(b.get(i));
|
|
||||||
if (res != 0)
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
if (i < a.size()) return 1;
|
|
||||||
else if (i < b.size()) return -1;
|
|
||||||
else return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Comparator<VersionNumber> COMPARATOR = new Comparator<VersionNumber>() {
|
|
||||||
@Override
|
|
||||||
public int compare(VersionNumber a, VersionNumber b) {
|
|
||||||
if (a == null || b == null)
|
|
||||||
return 0;
|
|
||||||
else {
|
|
||||||
if (a instanceof ComposedVersionNumber) {
|
|
||||||
if (b instanceof ComposedVersionNumber)
|
|
||||||
return compareTo(((ComposedVersionNumber) a).composed, ((ComposedVersionNumber) b).composed);
|
|
||||||
else
|
|
||||||
return compare(((ComposedVersionNumber) a).composed.get(0), b);
|
|
||||||
} else if (a instanceof IntVersionNumber) {
|
|
||||||
if (b instanceof ComposedVersionNumber)
|
|
||||||
return -compare(b, a);
|
|
||||||
else if (b instanceof IntVersionNumber)
|
|
||||||
return compareTo(((IntVersionNumber) a).version, ((IntVersionNumber) b).version);
|
|
||||||
else if (b instanceof StringVersionNumber)
|
|
||||||
return a.toString().compareTo(b.toString());
|
|
||||||
} else if (a instanceof StringVersionNumber) {
|
|
||||||
if (b instanceof ComposedVersionNumber)
|
|
||||||
return -compare(b, a);
|
|
||||||
else if (b instanceof StringVersionNumber)
|
|
||||||
return a.toString().compareTo(b.toString());
|
|
||||||
else if (b instanceof IntVersionNumber)
|
|
||||||
return a.toString().compareTo(b.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unrecognized VersionNumber " + a + " and " + b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package org.jackhuang.hmcl.util;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class VersionNumberTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCanonical() {
|
||||||
|
VersionNumber u, v;
|
||||||
|
|
||||||
|
v = VersionNumber.asVersion("3.2.0.0");
|
||||||
|
Assert.assertEquals("3.2", v.getCanonical());
|
||||||
|
|
||||||
|
v = VersionNumber.asVersion("3.2.0.0-5");
|
||||||
|
Assert.assertEquals("3.2-5", v.getCanonical());
|
||||||
|
|
||||||
|
v = VersionNumber.asVersion("3.2.0.0-0");
|
||||||
|
Assert.assertEquals("3.2", v.getCanonical());
|
||||||
|
|
||||||
|
v = VersionNumber.asVersion("3.2--------");
|
||||||
|
Assert.assertEquals("3.2", v.getCanonical());
|
||||||
|
|
||||||
|
v = VersionNumber.asVersion("1.7.2$%%^@&snapshot-3.1.1");
|
||||||
|
Assert.assertEquals("1.7.2$%%^@&snapshot-3.1.1", v.getCanonical());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparator() {
|
||||||
|
VersionNumber u, v;
|
||||||
|
|
||||||
|
u = VersionNumber.asVersion("1.7.10forge1614_FTBInfinity");
|
||||||
|
v = VersionNumber.asVersion("1.12.2");
|
||||||
|
Assert.assertTrue(u.compareTo(v) < 0);
|
||||||
|
|
||||||
|
u = VersionNumber.asVersion("1.8.0_51");
|
||||||
|
v = VersionNumber.asVersion("1.8.0.51");
|
||||||
|
Assert.assertTrue(u.compareTo(v) < 0);
|
||||||
|
|
||||||
|
u = VersionNumber.asVersion("1.8.0_151");
|
||||||
|
v = VersionNumber.asVersion("1.8.0_77");
|
||||||
|
Assert.assertTrue(u.compareTo(v) > 0);
|
||||||
|
|
||||||
|
u = VersionNumber.asVersion("1.6.0_22");
|
||||||
|
v = VersionNumber.asVersion("1.8.0_11");
|
||||||
|
Assert.assertTrue(u.compareTo(v) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSorting() {
|
||||||
|
List<String> input = Arrays.asList(
|
||||||
|
"1.10",
|
||||||
|
"1.10.2",
|
||||||
|
"1.10.2-All the Mods",
|
||||||
|
"1.10.2-AOE",
|
||||||
|
"1.10.2-AOE-1.1.5",
|
||||||
|
"1.10.2-forge2511-Age_of_Progression",
|
||||||
|
"1.10.2-forge2511-AOE-1.1.2",
|
||||||
|
"1.10.2-forge2511-ATM-E",
|
||||||
|
"1.10.2-forge2511-simple_life_2",
|
||||||
|
"1.10.2-forge2511_bxztest",
|
||||||
|
"1.10.2-forge2511_Farming_Valley",
|
||||||
|
"1.10.2-forge2511简单生活BXZ",
|
||||||
|
"1.10.2-FTB_Beyond",
|
||||||
|
"1.10.2-LiteLoader1.10.2",
|
||||||
|
"1.12.2",
|
||||||
|
"1.12.2_Modern_Skyblock-3.4.2",
|
||||||
|
"1.13.1",
|
||||||
|
"1.6.4",
|
||||||
|
"1.6.4-Forge9.11.1.1345",
|
||||||
|
"1.7.10",
|
||||||
|
"1.7.10-1614",
|
||||||
|
"1.7.10-1614-test",
|
||||||
|
"1.7.10-F1614-L",
|
||||||
|
"1.7.10-FL1614_04",
|
||||||
|
"1.7.10-Forge10.13.4.1614-1.7.10",
|
||||||
|
"1.7.10-Forge1614",
|
||||||
|
"1.7.10-Forge1614.1",
|
||||||
|
"1.7.10Agrarian_Skies_2",
|
||||||
|
"1.7.10forge1614test",
|
||||||
|
"1.7.10forge1614_ATlauncher",
|
||||||
|
"1.7.10forge1614_FTBInfinity",
|
||||||
|
"1.7.10Forge1614_FTBInfinity-2.6.0",
|
||||||
|
"1.7.10Forge1614_FTBInfinity-3.0.1",
|
||||||
|
"1.7.10forge1614_FTBInfinity_server",
|
||||||
|
"1.8",
|
||||||
|
"1.8-forge1577",
|
||||||
|
"1.8.9",
|
||||||
|
"1.8.9-forge1902",
|
||||||
|
"1.9");
|
||||||
|
input.sort(Comparator.comparing(VersionNumber::asVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user