org.royaldev.royalcommands.VUUpdater.java Source code

Java tutorial

Introduction

Here is the source code for org.royaldev.royalcommands.VUUpdater.java

Source

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.royaldev.royalcommands;

import org.apache.commons.lang.StringUtils;
import org.bukkit.plugin.Plugin;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Class that gets VU update data from the CurseForge API. {@link #getUpdateInfo(String)} is the main method of this
 * class.
 */
public final class VUUpdater {

    /**
     * Matches "PluginName vA.B.C (V1V2V3)" according to the spec.
     */
    private static final Pattern vuPattern = Pattern
            .compile("^(.+)\\s+v(\\d+(\\.\\d){2}).*\\((([0-9a-f]{2}){1,3})\\)$", Pattern.CASE_INSENSITIVE);

    /**
     * Checks for an update for the given plugin and plugin ID. This will check if the plugin version contains
     * "-SNAPSHOT" and set the updater to check for development versions if it does. See
     * {@link #checkForUpdate(org.bukkit.plugin.Plugin, String, boolean)} if that is not the behavior you desire.
     *
     * @param p        Plugin to check for an update for
     * @param pluginID ID of the plugin to get the information from. The ID comes from CurseForge.
     * @return {@link VUUpdater.UpdateStatus}
     */
    public static VUUpdater.UpdateStatus checkForUpdate(Plugin p, String pluginID) {
        final String pluginVersion = p.getDescription().getVersion();
        return VUUpdater.checkForUpdate(p, pluginID, pluginVersion.toUpperCase().contains("-SNAPSHOT"));
    }

    /**
     * Checks for an update for the given plugin and plugin ID.
     *
     * @param p        Plugin to check for an update for
     * @param pluginID ID of the plugin to get the information from. The ID comes from CurseForge.
     * @param useDev   Should development versions be checked?
     * @return {@link VUUpdater.UpdateStatus}
     */
    public static UpdateStatus checkForUpdate(Plugin p, String pluginID, boolean useDev) {
        final VUUpdateInfo vuui;
        try {
            vuui = VUUpdater.getUpdateInfo(pluginID);
        } catch (IOException e) {
            return UpdateStatus.ERROR;
        }
        final String pluginVersion = p.getDescription().getVersion();
        if (useDev && !pluginVersion.contains(vuui.getDevelopment()))
            return UpdateStatus.UPDATE_FOUND;
        if (!useDev && !pluginVersion.contains(vuui.getStable()))
            return UpdateStatus.UPDATE_FOUND;
        return UpdateStatus.NO_UPDATE;
    }

    /**
     * Gets the update information from the title of the latest file available from the CurseForge API.
     *
     * @param pluginID ID of the plugin to get the information from. The ID comes from CurseForge.
     * @return {@link org.royaldev.royalcommands.VUUpdater.VUUpdateInfo}
     * @throws IOException If any errors occur
     */
    public static VUUpdateInfo getUpdateInfo(String pluginID) throws IOException {
        final URLConnection conn = new URL("https://api.curseforge.com/servermods/files?projectIds=" + pluginID)
                .openConnection();
        conn.setConnectTimeout(5000);
        conn.setRequestProperty("User-Agent", "VU/1.0");
        conn.setDoOutput(true);
        final BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        final String response = reader.readLine();
        final JSONArray array = (JSONArray) JSONValue.parse(response);
        final Matcher m = VUUpdater.vuPattern
                .matcher((String) ((JSONObject) array.get(array.size() - 1)).get("name"));
        if (!m.matches())
            throw new IllegalArgumentException("No match found!");
        final byte[] vuBytes = VUUpdater.hexStringToByteArray(m.group(4));
        return new VUUpdateInfo(m.group(2), vuBytes);
    }

    /**
     * Turns a hexadecimal string into an array of bytes.
     *
     * @param s Hexadecimal string (like FF00FF00)
     * @return Byte array
     */
    private static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    /**
     * Possible statuses for the update checker to return.
     */
    public static enum UpdateStatus {
        /**
         * An error occurred getting the update information.
         */
        ERROR,
        /**
         * No update was found.
         */
        NO_UPDATE,
        /**
         * An update was found.
         */
        UPDATE_FOUND
    }

    /**
     * Class that holds and processes VU update information for plugins.
     */
    public static class VUUpdateInfo {

        /**
         * VU bytes in the title
         */
        private final byte[] vuBytes;
        /**
         * Version string.
         */
        private String stable = null, devel = null;

        /**
         * Processes VU update information based on the given parameters. After construction, {@link #getStable()} and
         * {@link #getDevelopment()} are available.
         *
         * @param versionOfFile Source version (version of the file with VU bytes on it). e.g. "1.0.0"
         * @param vuBytes       Array of VU bytes obtained from source file title
         * @throws IllegalArgumentException If the length of vuBytes is less than one.
         */
        public VUUpdateInfo(String versionOfFile, byte[] vuBytes) throws IllegalArgumentException {
            if (vuBytes.length < 1)
                throw new IllegalArgumentException("Not enough VU bytes");
            this.vuBytes = vuBytes;
            int vuForStb = 0, vuForDev = 0;
            if (this.hasFlag(VUFlag.VU_FOR_STABLE_FOLLOW)) {
                vuForStb = this.vuBytes[this.hasFlag(VUFlag.VU_FOR_DEV_FIRST) ? 2 : 1];
            }
            if (this.hasFlag(VUFlag.VU_FOR_DEV_FOLLOW)) {
                vuForDev = this.vuBytes[this.hasFlag(VUFlag.VU_FOR_DEV_FIRST) ? 1
                        : this.hasFlag(VUFlag.VU_FOR_STABLE_FOLLOW) ? 2 : 1];
            }
            this.stable = versionOfFile;
            this.devel = versionOfFile;
            if (vuForStb != 0)
                this.stable = vuToVersion(versionToVU(this.stable) + vuForStb);
            if (vuForDev != 0)
                this.devel = vuToVersion(versionToVU(this.devel) + vuForDev);
            if (this.hasFlag(VUFlag.STABLE_IS_SAME)) {
                this.stable = versionOfFile; // override previous values
            }
            if (this.hasFlag(VUFlag.DEV_IS_STABLE)) {
                this.devel = this.stable; // override previous values
            }
        }

        /**
         * Converts a version string to version units.
         * <p>
         * Converts the following:
         * </p>
         * <p>
         * <code>3.2.0</code> to <code>320</code><br/>
         * <code>10.2.0</code> to <code>1020</code>
         * </p>
         *
         * @param version Version string
         * @return Version units
         */
        private int versionToVU(String version) {
            version = version.replace(".", "");
            try {
                return Integer.parseInt(version);
            } catch (NumberFormatException ex) {
                return -1;
            }
        }

        /**
         * Converts version units to a version string.
         * <p>
         * Converts the following:
         * </p>
         * <p>
         * <code>320</code> to <code>3.2.0</code><br/>
         * <code>1020</code> to <code>10.2.0</code>
         * </p>
         *
         * @param vu Version units
         * @return Version string
         */
        private String vuToVersion(int vu) {
            String version = String.valueOf(vu).replaceAll(".(?!$)", "$0.");
            while (StringUtils.countMatches(version, ".") > 2)
                version = version.replaceFirst("\\.", "");
            return version;
        }

        /**
         * Gets the development version string obtained from the VU bytes.
         *
         * @return Development version string
         */
        public String getDevelopment() {
            return this.devel;
        }

        /**
         * Gets the stable version string obtained from the VU bytes.
         *
         * @return Stable version string
         */
        public String getStable() {
            return this.stable;
        }

        /**
         * Returns if the head byte has the given flag set.
         *
         * @param flag Flag to check
         * @return true if set, false if otherwise
         */
        public boolean hasFlag(VUFlag flag) {
            return (this.vuBytes[0] & (1 << flag.getBitNumber())) > 0;
        }

        /**
         * Flags possible to have set on the head byte of VU bytes.
         */
        private enum VUFlag {
            /**
             * Bit 7 of head VU byte.
             * <p/>
             * The stable version is the same as the source version if this is set.
             */
            STABLE_IS_SAME((byte) 7),
            /**
             * Bit 6 of head VU byte.
             * <p/>
             * The development version is the same as the stable version if this is set.
             */
            DEV_IS_STABLE((byte) 6),
            /**
             * Bit 5 of head VU byte.
             * <p/>
             * The VU for the development version will follow this byte if this is set.
             */
            VU_FOR_DEV_FOLLOW((byte) 5),
            /**
             * Bit 4 of head VU byte.
             * <p/>
             * The VU for the stable version will follow this byte if this is set.
             */
            VU_FOR_STABLE_FOLLOW((byte) 4),
            /**
             * Bit 3 of head VU byte.
             * <p/>
             * The VU for the development version will appear first after this byte if this is set.
             * <p/>
             * This should only be set if bit 3 or 4 are set.
             */
            VU_FOR_DEV_FIRST((byte) 3);

            /**
             * Number that would be set for this flag in the VU head byte.
             */
            private final byte bitNumber;

            /**
             * Constructs a VUFlag with the given bit number.
             *
             * @param bitNumber Bit number
             */
            VUFlag(byte bitNumber) {
                this.bitNumber = bitNumber;
            }

            /**
             * Gets the bit number that would be set for this flag.
             *
             * @return Bit number
             */
            public byte getBitNumber() {
                return bitNumber;
            }
        }

    }
}