public final class AddonLoader {

    private final File addonsFolder;

    private HashMap<String, Addon> entireAddons;

    private LinkedList<Addon> lowLevelAddons;
    private LinkedList<Addon> midLevelAddons;
    private LinkedList<Addon> highLevelAddons;

    public AddonLoader(String addonsFolderPath) {
        this.addonsFolder = new File(addonsFolderPath);
        if (!addonsFolder.exists() || !addonsFolder.isDirectory()) {
            throw new IllegalArgumentException("path not exists or not a folder");
        }
        this.entireAddons = new HashMap<>();
        this.lowLevelAddons = new LinkedList<>();
        this.midLevelAddons = new LinkedList<>();
        this.highLevelAddons = new LinkedList<>();
    }

    /**
     * Load all addons from specified folder path. Add them to particular query
     * through splitting their priority.
     *
     * @throws ClassNotFoundException if there no class implement Addon
     * interface
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws MalformedURLException
     */
    public void loadFilesFromFolder() throws ClassNotFoundException, IllegalAccessException, InstantiationException, MalformedURLException {

        for (File listFile : addonsFolder.listFiles()) {
            Addon addon;
            try {
                JarFile addonFile = new JarFile(listFile);
                String mainClass = addonFile.getManifest().getMainAttributes().getValue("Addon-Class");
                if (StringUtils.isEmpty(mainClass)) {
                    Engine.getEngine().getLogger().error(String.format("No declared Addon-Class found in MANIFEST.MF in \'%s\'. Load gave up.", listFile.getPath()));
                }
                URLClassLoader loader = new URLClassLoader(new URL[]{listFile.toURI().toURL()});
                Class addonClass = loader.loadClass(mainClass);
                Object instance = addonClass.newInstance();
                if (!(instance instanceof Addon)) {
                    throw new ClassCastException("Specified class is not a instance of addon.");
                }
                addon = (Addon) instance;
            } catch (Exception e) {
                Engine.getEngine().getLogger().error(String.format("Can not load addon file%s", listFile.getPath()), e);
                continue;

            }

            //Check if the add which has same name had already loaded.
            if (entireAddons.containsKey(addon.getAddonName())) {
                if (addon.getAddonName().equals(entireAddons.get(addon.getAddonName()).getAddonName())
                        && (addon.getVersion() == entireAddons.get(addon.getAddonName()).getVersion())) {
                    continue;
                }
                //Thus get the latest version
                addon = this.selectProprietyAddon(addon, entireAddons.get(addon.getAddonName()));
            }

            this.entireAddons.put(addon.getAddonName(), addon);
            switch (addon.getPriority()) {
                case LOW: {
                    this.lowLevelAddons.add(addon);
                    break;
                }
                case MEDIUM: {
                    this.midLevelAddons.add(addon);
                    break;
                }
                case HIGH: {
                    this.highLevelAddons.add(addon);
                    break;
                }
            }
        }

    }

    /**
     * Sort addons through its priority.
     *
     * @return all addons sorted
     */
    private Addon[] getAddonsSortedByPriority() {
        LinkedList<Addon> sortedList = new LinkedList<>();
        sortedList.addAll(this.highLevelAddons);
        sortedList.addAll(this.midLevelAddons);
        sortedList.addAll(this.lowLevelAddons);
        return sortedList.toArray(new Addon[]{});
    }

    public void perLoadAllAddons() {
        for (Addon addon : this.getAddonsSortedByPriority()) {
            addon.preLoad();
        }
    }

    public void loadAllAddons() {
        for (Addon addon : this.getAddonsSortedByPriority()) {
            addon.load();
        }
    }

    public void postLoadAllAddons() {
        for (Addon addon : this.getAddonsSortedByPriority()) {
            addon.postLoad();
        }
    }

    /**
     * Receive particular addon by addon name.
     *
     * @param name Addon name specified
     * @return corresponding addon
     */
    public Addon getAddonByName(String name) {
        return this.entireAddons.get(name);
    }

    private Addon selectProprietyAddon(Addon addon1, Addon addon2) {
        if (!addon1.getAddonName().equals(addon2.getAddonName())) {
            throw new IllegalArgumentException("Addons name not same. This should not happen as normal, but it is a deadly error.");
        }

        if (addon1.getVersion() >= addon2.getVersion()) {
            return addon1;
        } else {
            return addon2;
        }
    }

} public interface AuthClient {

    public Boolean isAuthorized();

    public String getUserName();

    public Image getUserAvatar();

    public String getUUID();
    
    public Boolean auth(String userName,String password);
} public class DefaultLauncher implements Launcher {
    private TaskManager launchTaskManager;

    public DefaultLauncher() {
        this.launchTaskManager = new TaskManager();
        this.launchTaskManager.insertTask(new SynchronizedTask() {
            @Override
            public void run() {

            }

            @Override
            public String getName() {
                return null;
            }
        });
    }

    @Override
    public void launch() {
        this.launchTaskManager.executeTasks();
    }

    @Override
    public TaskManager getLaunchTaskManager() {
        return this.launchTaskManager;
    }
} public class DefaultUIControler implements UIController {

    MainFrame mainFrame;

    public DefaultUIControler()  {
        try {
            EventQueue.invokeAndWait(new Runnable() {
                
                @Override
                public void run() {
                    mainFrame = new MainFrame();
                }
            });
        } catch (InterruptedException | InvocationTargetException ex) {
            Engine.getEngine().getLogger().fatal("Cant start up main window", ex);
        }

    }

    @Override
    public JFrame getMainWindow() {
        return this.mainFrame;
    }

    @Override
    public JDialog getConfigDialog() {
        return null;
    }

    @Override
    public JDialog getAuthDialog() {
        return null;
    }

    @Override
    public void closeAllWindow() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
} public class Configuration implements IConfig {
    private File configFile;
    private HashMap<String, Object> map;
    
    @SuppressWarnings("unchecked")
    public Configuration(File configFile, String defaultFileName) {
	this.configFile = configFile;
	try {
	    if(!configFile.exists()) {
		Engine.getEngine().getLogger().debug(defaultFileName + " is missing, trying to create it");
                InputStream config = this.getClass().getClassLoader().getResourceAsStream("defaults/" + defaultFileName + ".yml");
                FileUtils.copyInputStreamToFile(config, configFile);
	    } else if (!(new Yaml().load(FileUtils.readFileToString(configFile)) instanceof HashMap)) {
                FileUtils.deleteQuietly(configFile);
                InputStream config = this.getClass().getClassLoader().getResourceAsStream("defaults/" + defaultFileName + ".yml");
                FileUtils.copyInputStreamToFile(config, configFile);
            }
	    String fileContent = FileUtils.readFileToString(configFile);
            this.map = (HashMap<String, Object>) (new Yaml().load(fileContent));
            if (this.map == null) {
                this.map = new HashMap<>();
            }
	} catch (Exception e) {
	    Engine.getEngine().getLogger().fatal("Can not pass " + defaultFileName + " file.", e);
	    new CrashReporter(e , "Can not pass " + defaultFileName + " file.").pack().save();
	    System.exit(-1);
	}
    }
    
    @Override
    public Object getObject(String key) {
	return map.get(key);
    }
    
    @Override
    public String getString(String key) {
	Object obj = map.get(key);
	if(!(obj instanceof String)) {
	    Engine.getEngine().getLogger().error("The value of the key \'" + key + "\' is not a string!");
	    return null;
	}
	return (String) obj;
    }
    
    @Override
    public Boolean getBoolean(String key) {
	Object obj = map.get(key);
	if(!(obj instanceof Boolean)) {
	    Engine.getEngine().getLogger().error("The value of the key \'" + key + "\' is not a boolean!");
	    return null;
	}
	return (Boolean) obj;
    }

    @Override
    public Integer getInteger(String key) {
	Object obj = map.get(key);
	if(!(obj instanceof Integer)) {
	    Engine.getEngine().getLogger().error("The value of the key \'" + key + "\' is not a integer!");
	    return null;
	}
	return (Integer) obj;
    }

    @Override
    public Double getDouble(String key) {
	Object obj = map.get(key);
	if(!(obj instanceof Double)) {
	    Engine.getEngine().getLogger().error("The value of the key \'" + key + "\' is not a double!");
	    return null;
	}
	return (Double) obj;
    }

    @Override
    public List getList(String key) {
	Object obj = map.get(key);
	if(!(obj instanceof List)) {
	    Engine.getEngine().getLogger().error("The value of the key \'" + key + "\' is not a list!");
	    return null;
	}
	return (List) obj;
    }

    @Override
    public void saveToFile() {
    }

    @Override
    public void setConfig(String key, Object obj) {
	this.map.put(key, obj);
    }
} public interface IConfig {
    public Object getObject(String key);
    public String getString(String key);
    public Boolean getBoolean(String key);
    public Integer getInteger(String key);
    public Double getDouble(String key);
    public List getList(String key);
    public void saveToFile();
    public void setConfig(String key, Object obj);
} public class DefaultI18n implements I18n {
    private final Map<String, Map<String, String>> map;
    private final Locale locale;
    private final String defaultLangFileName;
    
    public DefaultI18n() {
	this.map = new HashMap<String, Map<String, String>>();
	this.locale = Locale.getDefault();
	this.defaultLangFileName = "zh_CN";
	
	this.importLanguageData(this.getClass(), "/lang/", locale.toString());
    }
    
    /**
     * Import the language data to the map
     * 
     * @param clazz any class of the addon is OK
     * @param path the path of the folder what the language-files in it (MUST START WITH '/')
     */
    @Override
    public void importLanguageData(Class<? extends Object> clazz, String path, String locale) {
	InputStream stream = clazz.getResourceAsStream(path + "/" + locale + ".lang");
	if(stream == null) {
	    Engine.getEngine().getLogger().warn("Unsupported language \'" + locale
		    + "\', trying to set as defaults.");
	    stream = clazz.getResourceAsStream(path + "/" + defaultLangFileName + ".lang");
	    this.loadFromInputstream(stream, defaultLangFileName);
	} else {
	    this.loadFromInputstream(stream, locale);
	}
    }
    
    /**
     * Import the language data to the map
     * 
     * @param clazz any class of the addon is OK
     * @param path the path of the folder what the language-files in it
     */
    @Override
    public void importLanguageData(Class<? extends Object> clazz, String path) {
	this.importLanguageData(clazz, path, locale.toString());
    }
    
    /**
     * Import a language-data-map to the map
     * 
     * @param map the language-data-map
     * @param locale the program will use the map by the locale
     */
    @Override
    public void importLaunguaeDataByMap(Map<String, String> map, String locale) {
	if(this.map.get(locale) == null) {
	    this.map.put(locale, map);
	} else {
	    MapUtils.addMapToMap(map, this.map.get(locale));
	}
    }
    
    /**
     * Import a language-data-map to the map by current locale 
     * 
     * @param map the language-data-map
     */
    @Override
    public void importLaunguaeDataByMap(Map<String, String> map) {
	this.importLaunguaeDataByMap(map, locale.toString());
    }
    
    /**
     * Load lang-file from an InputStream
     * 
     * @param stream the InputStream
     * @param locale the locale
     * @throws RuntimeException when the InputStream is null
     */
    private void loadFromInputstream(InputStream stream, String locale) {
	if(stream == null) {
	    throw new RuntimeException("Missing language-file, it should not be happened, "
	    	+ "contact the author!");
	}
	try {
	    Map<String, String> temp = new HashMap<String, String>();
	    List<String> list = IOUtils.readLines(stream, "utf-8");
	    for(String line : list) {
		if(line.startsWith("#")) {
		    continue;
		}
		try {
		    String[] elements = line.split("=", 2);
		    temp.put(elements[0].trim(), elements[1].trim());
		} catch (Exception ex) {
		    ;
		}
	    }
	    if(map.get(locale) == null) {
		map.put(locale, temp);
	    } else {
		MapUtils.addMapToMap(temp, map.get(locale));
	    }
	} catch (IOException e) {
	    ;
	}
    }
    
    /**
     * Get the localized string by a key
     * 
     * @param key key of the localized string
     * @return the localized string (if it is missing, will return the key)
     */
    @Override
    public String getTranslation(String key) {
	String string = map.get(this.locale.toString()).get(key);
	return string == null ? key : string;
    }
    
    /**
     * Get the localized string by a key and the locale
     * 
     * @param key key of the localized string
     * @param locale the locale
     * @return the localized string (if it is missing, will return the key)
     */
    @Override
    public String getTranslationByLocale(String key, String locale) {
	String string = map.get(locale) == null ? null : map.get(locale).get(key);
	return string == null ? key : string;
    }

    /**
     * Get the current locale
     * 
     * @return the instance of current locale
     */
    @Override
    public Locale getCurrentLocale() {
	return this.locale;
    }
} public interface I18n {
    /**
     * Import the language data to the map
     * 
     * @param clazz any class of the addon is OK
     * @param path the path of the folder what the language-files in it
     * @param locale the locale
     */
    public void importLanguageData(Class<? extends Object> clazz, String path, String locale);
    
    /**
     * Import the language data to the map
     * 
     * @param clazz any class of the addon is OK
     * @param path the path of the folder what the language-files in it
     */
    public void importLanguageData(Class<? extends Object> clazz, String path);
    
    /**
     * Import a language-data-map to the map
     * 
     * @param map the language-data-map
     * @param locale the program will use the map by the locale
     */
    public void importLaunguaeDataByMap(Map<String, String> map, String locale);
    
    /**
     * Import a language-data-map to the map by current locale 
     * 
     * @param map the language-data-map
     */
    public void importLaunguaeDataByMap(Map<String, String> map);
    
    /**
     * Get the localized string by a key
     * 
     * @param key key of the localized string
     * @return the localized string (if it is missing, will return the key)
     */
    public String getTranslation(String key);
    
    /**
     * Get the localized string by a key and the locale
     * 
     * @param key key of the localized string
     * @param locale the locale
     * @return the localized string (if it is missing, will return the key)
     */
    public String getTranslationByLocale(String key, String locale);
    
    /**
     * Get the current locale
     * 
     * @return the instance of current locale
     */
    public Locale getCurrentLocale();
} public final class AssestsProvider {
    
} public class LibraryProvider {
    private static Log logger;
    static {
        logger=LogFactory.getLog(LibraryProvider.class);
    }
    
    private HashMap<String, Library> libraryQuery;
    private File libraryFolder;
    private File repoFile;
    private URL repoUrl;
    private String repoContent;


    public LibraryProvider(File libraryFolder,File repoFile, URL repoUrl) throws IOException {
        this.libraryQuery = new HashMap<>();
        this.libraryFolder = libraryFolder;
        this.repoUrl = repoUrl;
        this.repoFile=repoFile;


        //Try to download the latest version of librepo.json
        try {
            //Files.deleteIfExists(FileUtils.getFile(libraryFolder, "librepo.json").toPath());
            DownloadUtils.downloadToFile(repoUrl.toString(), FileUtils.getFile(libraryFolder, "librepo.json"));
        } catch (Exception e) {
            logger.warn(String.format("LibProvider:Can not fetch repo.json form %s", repoUrl));
        }
        File repoJsonFile = FileUtils.getFile(libraryFolder, "librepo.json");
        if (!repoJsonFile.exists()) {
            logger.warn("LibProvider:repo.json not exists, you have to connect to internet at first time.");
            throw new IOException("librepo.json not found");
        }

        JsonObject repoJsonObject = new Gson().fromJson(FileUtils.readFileToString(repoJsonFile), JsonObject.class);
        for (JsonElement lib : repoJsonObject.getAsJsonArray("libraries")) {
            String groupId = lib.getAsJsonObject().get("groupId").getAsString();
            String artifactId = lib.getAsJsonObject().get("artifactId").getAsString();
            String version = lib.getAsJsonObject().get("version").getAsString();
            String url = lib.getAsJsonObject().get("url").getAsString();

            Path inferPath = LibraryUtils.resolveLibPath(libraryFolder, groupId, artifactId, version);
            Library l;
            if (inferPath.toFile().exists()) {
                l = new Library(groupId, artifactId, version, url, inferPath.toFile());
            } else {
                l = new Library(groupId, artifactId, version, url, null);
            }
            this.libraryQuery.put(String.format("%s:%s:%s", groupId, artifactId, version), l);

        }

    }

    public File getLibrary(String groupId, String artifactId, String version) throws IOException {
        String libString = String.format("%s:%s:%s", groupId, artifactId, version);
        return this.getLibrary(libString);
    }

    public File getLibrary(String libString) throws IOException {
        if (this.libraryQuery.get(libString).getLibFile() == null||!this.libraryQuery.get(libString).getLibFile().exists()) {
            DownloadUtils.downloadToFile(
                    this.libraryQuery.get(libString).getDownloadURL(),
                    LibraryUtils.resolveLibPath(
                            this.libraryFolder,
                            this.libraryQuery.get(libString).getGroupID(),
                            this.libraryQuery.get(libString).getName(),
                            this.libraryQuery.get(libString).getVersion()
                    ).toFile()
            );
        }
        return this.libraryQuery.get(libString).getLibFile();
    }

} public final class NativeProvider {
    File nativeFolder;
    URL repoUrl;

    /**
     * @param nativeFolder
     * @param repoUrl
     */
    public NativeProvider(File nativeFolder, URL repoUrl) {
        this.nativeFolder = nativeFolder;
        this.repoUrl = repoUrl;
    }

    public Path getNativePath() {
        return this.nativeFolder.toPath();
}
} public class VersionProvider {
    
}
import java.io.File;
import java.net.URLDecoder;
import net.teamtf.launcher.addon.AddonLoader;
import net.teamtf.launcher.auth.AuthClient;
import net.teamtf.launcher.basis.DefaultLauncher;
import net.teamtf.launcher.basis.gui.DefaultUIControler;
import net.teamtf.launcher.configuration.Configuration;
import net.teamtf.launcher.configuration.IConfig;
import net.teamtf.launcher.localization.DefaultI18n;
import net.teamtf.launcher.localization.I18n;
import net.teamtf.launcher.provider.assests.AssestsProvider;
import net.teamtf.launcher.provider.library.LibraryProvider;
import net.teamtf.launcher.provider.natives.NativeProvider;
import net.teamtf.launcher.provider.version.VersionProvider;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
package net.teamtf.launcher.core;




/**
 * @Author Decker
 */
public class Engine {

    private static Engine instance;

    public static void initEngine() throws Exception {
        if (instance == null) {
            instance = new Engine();
        } else {
            throw new IllegalAccessException("Engine has already initialized");
        }
    }

    public static Engine getEngine() {
        return instance;
    }

    private final File gameDir;
    private final File launcherDir;
    private final Log logger;
    private final TLFileSystem fileSystem;
    private UIController uiController;
    private Launcher launcher;
    private I18n i18n;
    private final AddonLoader addonLoader;
    private final IConfig launcherConfiguration, gameConfiguration;
    private AuthClient authClient;
    private LibraryProvider libraryProvider;
    private AssestsProvider assestsProvider;
    private NativeProvider nativeProvider;
    private VersionProvider versionProvider;

    private Engine() throws Exception {
        this.launcherDir = new File(new File(URLDecoder.decode(Engine.class.getProtectionDomain().getCodeSource().getLocation().getPath(),
        	"UTF-8")).getParentFile().getCanonicalPath());

        this.logger = LogFactory.getLog("MainLogger");
        this.fileSystem = new TLFileSystem();
        
        this.launcherConfiguration = new Configuration(this.fileSystem.getLauncherConfigFile(), "launcherconfig");
        this.launcherConfiguration.setConfig("launcher.dir", this.launcherDir.getAbsolutePath());
        this.gameConfiguration = new Configuration(this.fileSystem.getGameConfigFile(), "gameconfig");
        
        this.gameDir = FileUtils.getFile(this.launcherConfiguration.getString("game.dir"));
        this.i18n = new DefaultI18n();
        this.logger.info(this.i18n.getTranslation("lang.welcome"));
        
        this.uiController = new DefaultUIControler();
        this.launcher = new DefaultLauncher();