Java tutorial
/******************************************************************************************************************* * Authors: SanAndreasP * Copyright: SanAndreasP * License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International * http://creativecommons.org/licenses/by-nc-sa/4.0/ *******************************************************************************************************************/ package de.sanandrew.core.manpack.managers; import com.google.common.collect.Maps; import com.google.gson.*; import de.sanandrew.core.manpack.mod.ConfigurationManager; import de.sanandrew.core.manpack.mod.ModCntManPack; import de.sanandrew.core.manpack.util.MutableString; import de.sanandrew.core.manpack.util.javatuples.Triplet; import net.minecraft.util.EnumChatFormatting; import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.logging.log4j.Level; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * */ public class SAPUpdateManager { public static final List<Triplet<SAPUpdateManager, MutableBoolean, MutableString>> UPD_MANAGERS = new ArrayList<>(); public static final Map<Integer, Boolean> IS_IN_RENDER_QUEUE = Maps.newHashMap(); private boolean checkedForUpdate = false; private Version version; private String modName; private URL updURL; private String modInfoURL; private File modPackedJar; private UpdateFile updInfo = new UpdateFile(); private final int mgrId; public UpdateDownloader downloader; public static synchronized void setChecked(int mgrId) { UPD_MANAGERS.get(mgrId).getValue1().setTrue(); } public static synchronized void setHasUpdate(int mgrId, String version) { setChecked(mgrId); UPD_MANAGERS.get(mgrId).getValue2().set(version); } public static void setInRenderQueue(int mgrId) { IS_IN_RENDER_QUEUE.put(mgrId, true); } private SAPUpdateManager(String modName, Version version, String updateURL, String modURL, File modJar) { this.modName = modName; this.version = version.clone(); this.modInfoURL = modURL; this.modPackedJar = modJar; try { this.updURL = new URL(updateURL); this.updURL.toURI(); // check validity } catch (MalformedURLException | NullPointerException | URISyntaxException e) { this.updURL = null; ModCntManPack.UPD_LOG.log(Level.WARN, "The URL to the mod version file is invalid!"); e.printStackTrace(); } this.mgrId = UPD_MANAGERS.size(); } public static SAPUpdateManager createUpdateManager(String modName, Version version, String updateURL, String modURL, File modJar) { SAPUpdateManager updMgr = new SAPUpdateManager(modName, version, updateURL, modURL, modJar); if (updMgr.updURL != null) { UPD_MANAGERS.add(Triplet.with(updMgr, new MutableBoolean(false), new MutableString(""))); IS_IN_RENDER_QUEUE.put(updMgr.mgrId, false); } return updMgr; } private void check() { Runnable threadProcessor = new Runnable() { @Override public void run() { try { ModCntManPack.UPD_LOG.printf(Level.INFO, "Checking for %s update", SAPUpdateManager.this.getModName()); if (SAPUpdateManager.this.getUpdateURL() == null) { throw new MalformedURLException("[NULL]"); } Gson gson = new GsonBuilder() .registerTypeAdapter(UpdateFile.class, new AnnotatedDeserializer<UpdateFile>()) .create(); try (BufferedReader in = new BufferedReader( new InputStreamReader(SAPUpdateManager.this.getUpdateURL().openStream()))) { SAPUpdateManager.this.updInfo = gson.fromJson(in, UpdateFile.class); } catch (IOException | JsonSyntaxException ex) { ModCntManPack.UPD_LOG.printf(Level.WARN, "Check for Update failed!", ex); } if (SAPUpdateManager.this.updInfo.version == null || SAPUpdateManager.this.updInfo.version.length() < 1) { SAPUpdateManager.setChecked(SAPUpdateManager.this.getId()); return; } Version webVersion = SAPUpdateManager.this.updInfo.getVersionInst(); SAPUpdateManager.this.updInfo.version = webVersion.toString(); // reformat the version number to the format major.minor.revision Version currVersion = SAPUpdateManager.this.getVersion(); String currModName = SAPUpdateManager.this.getModName(); if (webVersion.versionType != EnumVersionType.RELEASE) { if (ConfigurationManager.subscribeToUnstable || currVersion.versionType != EnumVersionType.RELEASE) { if (webVersion.versionType.ordinal() > currVersion.versionType.ordinal()) { switch (webVersion.versionType) { case BETA: ModCntManPack.UPD_LOG.printf(Level.INFO, "A beta for %s is available: %s", currModName, webVersion); break; case RELEASECANDIDATE: ModCntManPack.UPD_LOG.printf(Level.INFO, "A release candidate for %s is available: %s", currModName, webVersion); break; default: ModCntManPack.UPD_LOG.printf(Level.INFO, "No new update for %s is available.", currModName); SAPUpdateManager.setChecked(SAPUpdateManager.this.getId()); return; } SAPUpdateManager.setHasUpdate(SAPUpdateManager.this.mgrId, webVersion.toString()); return; } else if (webVersion.preVersionNr > currVersion.preVersionNr) { switch (webVersion.versionType) { case ALPHA: ModCntManPack.UPD_LOG.printf(Level.INFO, "A new alpha for %s is out: %s", currModName, webVersion); break; case BETA: ModCntManPack.UPD_LOG.printf(Level.INFO, "A new beta for %s is out: %s", currModName, webVersion); break; case RELEASECANDIDATE: ModCntManPack.UPD_LOG.printf(Level.INFO, "A new release candidate for %s is out: %s", currModName, webVersion); break; default: ModCntManPack.UPD_LOG.printf(Level.INFO, "No new update for %s is available.", currModName); SAPUpdateManager.setChecked(SAPUpdateManager.this.getId()); return; } SAPUpdateManager.setHasUpdate(SAPUpdateManager.this.mgrId, webVersion.toString()); return; } } } else { if (webVersion.major > currVersion.major) { ModCntManPack.UPD_LOG.printf(Level.INFO, "New major update for %s is out: %s", currModName, webVersion); SAPUpdateManager.setHasUpdate(SAPUpdateManager.this.mgrId, webVersion.toString()); return; } else if (webVersion.major == currVersion.major) { if (webVersion.minor > currVersion.minor) { ModCntManPack.UPD_LOG.printf(Level.INFO, "New minor update for %s is out: %s", currModName, webVersion); SAPUpdateManager.setHasUpdate(SAPUpdateManager.this.mgrId, webVersion.toString()); return; } else if (webVersion.minor == currVersion.minor) { if (webVersion.revision > currVersion.revision) { ModCntManPack.UPD_LOG.printf(Level.INFO, "New bugfix update for %s is out: %s", currModName, webVersion); SAPUpdateManager.setHasUpdate(SAPUpdateManager.this.mgrId, webVersion.toString()); return; } else if (webVersion.revision == currVersion.revision && currVersion.versionType != EnumVersionType.RELEASE) { ModCntManPack.UPD_LOG.printf(Level.INFO, "A stable release for %s is available: %s", currModName, webVersion); SAPUpdateManager.setHasUpdate(SAPUpdateManager.this.mgrId, webVersion.toString()); return; } } } } ModCntManPack.UPD_LOG.printf(Level.INFO, "No new update for %s is available.", currModName); } catch (IOException ioex) { ModCntManPack.UPD_LOG.printf(Level.WARN, String.format("Update Check for %s failed!", SAPUpdateManager.this.modName), ioex); } SAPUpdateManager.setChecked(SAPUpdateManager.this.getId()); } }; new Thread(threadProcessor, "SAPUpdateThread").start(); } public void checkForUpdate() { if (!this.checkedForUpdate) { this.check(); this.checkedForUpdate = true; } } public Version getVersion() { return this.version; } public String getModName() { return this.modName; } public URL getUpdateURL() { return this.updURL; } public String getModInfoURL() { return this.modInfoURL; } public File getModJar() { return this.modPackedJar; } public boolean isModJarValid() { return this.modPackedJar != null && this.modPackedJar.isFile() && this.modPackedJar.getName().endsWith(".jar"); } public int getId() { return this.mgrId; } public UpdateFile getUpdateInfo() { return this.updInfo; } public EnumUpdateSeverity getVersionDiffSeverity() { if (this.updInfo.severityOverride != null && this.updInfo.severityOverride.length() > 0) { return EnumUpdateSeverity.valueOf(this.updInfo.severityOverride); } Version updVersion = new Version(this.updInfo.version); if (updVersion.major > this.version.major) { return EnumUpdateSeverity.SEVERE; } else if (updVersion.major == this.version.major) { if (updVersion.minor >= this.version.minor + 4) { return EnumUpdateSeverity.SEVERE; } else if (updVersion.minor > this.version.minor) { return EnumUpdateSeverity.MAJOR; } else if (updVersion.minor == this.version.minor) { if (updVersion.revision >= this.version.revision + 8) { return EnumUpdateSeverity.SEVERE; } else if (updVersion.revision >= this.version.revision + 4) { return EnumUpdateSeverity.MAJOR; } else if (updVersion.revision > this.version.revision) { return EnumUpdateSeverity.MINOR; } } } return EnumUpdateSeverity.UNKNOWN; } public void runUpdate() { URL dl = this.updInfo.getDownload(); if (dl != null) { this.downloader = new UpdateDownloader(dl, this.modPackedJar); } } public static class UpdateFile { @JsonRequired public String version; public String downloadUrl; public String description; public String severityOverride; public String[] changelog; public UpdateFile() { } public URL getDownload() { try { URL dl = new URL(this.downloadUrl); dl.toURI(); return dl; } catch (MalformedURLException | URISyntaxException e) { return null; } } public Version getVersionInst() { return new Version(version); } } public enum EnumUpdateSeverity { MINOR(EnumChatFormatting.GREEN), MAJOR(EnumChatFormatting.YELLOW), SEVERE(EnumChatFormatting.RED), UNKNOWN( EnumChatFormatting.WHITE); public final EnumChatFormatting format; EnumUpdateSeverity(EnumChatFormatting formatting) { this.format = formatting; } } public static final class Version implements Cloneable { public final int revision; public final int minor; public final int major; public final EnumVersionType versionType; public final int preVersionNr; private static final Pattern[] VERSION_PATTERNS = new Pattern[] { // {1.7.10-}1.0.0{-beta{.2}} Pattern.compile( "(\\d+.\\d+[\\.|_]\\d+-)?(?<major>\\d+)\\.(?<minor>\\d+)[\\.|_](?<revision>\\d+)(-(?<prType>alpha|beta|rc)(\\.(?<prNr>\\d+))?)?") }; public Version(int majorNr, int minorNr, int revisionNr) { this.major = majorNr; this.minor = minorNr; this.revision = revisionNr; this.versionType = EnumVersionType.RELEASE; this.preVersionNr = 0; } public Version(int majorNr, int minorNr, int revisionNr, EnumVersionType type, int preVersionNr) { this.major = majorNr; this.minor = minorNr; this.revision = revisionNr; this.versionType = type; this.preVersionNr = preVersionNr; } public Version(String version) { if (version != null) { Matcher matcher; for (Pattern verPattern : VERSION_PATTERNS) { matcher = verPattern.matcher(version); if (matcher.find()) { this.major = Integer.valueOf(matcher.group("major")); this.minor = Integer.valueOf(matcher.group("minor")); this.revision = Integer.valueOf(matcher.group("revision")); if (matcher.group("prType") != null) { String prType = matcher.group("prType"); switch (prType) { case "alpha": this.versionType = EnumVersionType.ALPHA; break; case "beta": this.versionType = EnumVersionType.BETA; break; case "rc": this.versionType = EnumVersionType.RELEASECANDIDATE; break; default: this.versionType = EnumVersionType.UNKNOWN; } this.preVersionNr = matcher.group("prNr") != null ? Integer.valueOf(matcher.group("prNr")) : 1; } else { this.versionType = EnumVersionType.RELEASE; this.preVersionNr = 0; } return; } } } this.major = -1; this.minor = -1; this.revision = -1; this.versionType = EnumVersionType.UNKNOWN; this.preVersionNr = -1; // FMLLog.log(ModCntManPack.UPD_LOG, Level.WARN, // "Version Number for the mod %s could not be compiled! The version number %s does not have the required formatting!", // this.modName, version); } @Override public String toString() { if (this.versionType == EnumVersionType.RELEASE) { return String.format("%d.%d.%d", this.major, this.minor, this.revision); } else { return String.format("%d.%d.%d-%s.%d", this.major, this.minor, this.revision, this.versionType, this.preVersionNr); } } @Override public Version clone() { return new Version(this.major, this.minor, this.revision, this.versionType, this.preVersionNr); } } public enum EnumVersionType { ALPHA("alpha"), BETA("beta"), RELEASECANDIDATE("rc"), RELEASE, UNKNOWN; private final String versionStr; EnumVersionType() { this.versionStr = this.name(); } EnumVersionType(String verStr) { this.versionStr = verStr; } @Override public String toString() { return this.versionStr; } } /** * code from http://stackoverflow.com/a/14245807 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface JsonRequired { } static class AnnotatedDeserializer<T> implements JsonDeserializer<T> { public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { T pojo = new Gson().fromJson(je, type); Field[] fields = pojo.getClass().getDeclaredFields(); for (Field f : fields) { if (f.getAnnotation(JsonRequired.class) != null) { try { f.setAccessible(true); if (f.get(pojo) == null) { throw new JsonParseException("Missing field in JSON: " + f.getName()); } } catch (IllegalArgumentException | IllegalAccessException ex) { ModCntManPack.UPD_LOG.log(Level.WARN, ex, null); } } } return pojo; } } }