package de.tobiyas.racesandclasses.standalonegui.data; import java.io.File; import java.io.FileFilter; import java.lang.annotation.AnnotationFormatError; import java.net.URL; import java.net.URLClassLoader; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Pattern; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import de.tobiyas.racesandclasses.standalonegui.data.option.TraitConfigOption; import de.tobiyas.racesandclasses.standalonegui.data.option.TraitGuiConfigParser; import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitInfos; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.Trait; import de.tobiyas.util.collections.CaseInsenesitveMap; import de.tobiyas.util.config.YAMLConfigExtended; import de.tobiyas.util.file.FileUtils; public class GuiLoader { //TODO remove this later. //It's just to not load it all the time! static{ File ownMCRoot = new File("D:\\Bastelecke\\1.8.7"); File ownBukkit = new File(ownMCRoot, "spigot.jar"); File ownRaC = new File(new File(ownMCRoot, "plugins"), "RacesAndClasses"); if(ownBukkit.exists()) lastSelectedBukkitFile = ownBukkit; if(ownRaC.exists()) lastSelectedBaseFile = ownRaC; } /** * The Base file to use. */ private static File baseFile; /** * The Base file to use. */ private static File lastSelectedBaseFile; /** * The Bukkit file to reopen. */ private static File lastSelectedBukkitFile; /** * The Path to CB / bukkit / Spigot. */ private static ClassLoader bukkitLoader; /** * The Set of classes to use. */ private static final List<GuiClass> classes = new LinkedList<GuiClass>(); /** * The Set of races to use. */ private static final List<GuiRace> races = new LinkedList<GuiRace>(); /** * The Map for the Traits -> Classes. */ private static final Map<String, java.lang.Class<? extends Trait>> traitMap = new CaseInsenesitveMap<java.lang.Class<? extends Trait>>(); /** * Loads the Classes from the Current Directory. * * @return the set of loaded Classes. */ public static List<GuiClass> getLoadedClasses(){ Collections.sort(classes); return classes; } /** * Loads the Races from the Current Directory. * * @return the set of loaded Races. */ public static List<GuiRace> getLoadedRaces(){ Collections.sort(races); return races; } public static void removeRace(GuiRace race){ races.remove(race); } public static void addRace(GuiRace race){ races.add(race); } public static void removeClass(GuiClass clazz){ classes.remove(clazz); } public static void addClass(GuiClass clazz){ classes.add(clazz); } /** * Opens an Dir-Selection. */ public static void openBaseFileSelection(){ //select Bukkit / CB / Spigot file. if(bukkitLoader == null){ JOptionPane.showMessageDialog(null, "Select a CraftBukkit / Spigot distribution."); JFileChooser fileChooser = new JFileChooser(); if(lastSelectedBukkitFile != null) fileChooser.setSelectedFile(lastSelectedBukkitFile); //only allow .jar files. fileChooser.setFileFilter(new javax.swing.filechooser.FileFilter() { @Override public String getDescription() { return "jar"; } @Override public boolean accept(File f) { if(f.isDirectory()) return true; return f.getName().endsWith(".jar"); } }); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); fileChooser.setDialogTitle("Select a CraftBukkit / Spigot distribution"); int result = fileChooser.showOpenDialog(null); //check if abort or exit. if(result == JFileChooser.CANCEL_OPTION) return; if(result == JFileChooser.ERROR_OPTION) return; if(result == JFileChooser.APPROVE_OPTION) { File selected = fileChooser.getSelectedFile(); lastSelectedBukkitFile = selected; if(selected == null || !selected.getName().endsWith(".jar")){ JOptionPane.showMessageDialog(null, "You need to select a CraftBukkit / Spigot distribution."); return; } try{ ClassLoader loader = URLClassLoader.newInstance( new URL[] { selected.toURI().toURL() }, GuiLoader.class.getClassLoader() ); Class<?> clazz = Class.forName("org.bukkit.Bukkit", true, loader); if(clazz == null) { JOptionPane.showMessageDialog(null, "Could not load the File you selected."); return; } bukkitLoader = loader; }catch(Throwable exp){ exp.printStackTrace(); JOptionPane.showMessageDialog(null, "You need to select a CraftBukkit / Spigot distribution."); return; } } } //select RaC Folder. JOptionPane.showMessageDialog(null, "Select your RacesAndClasses folder."); JFileChooser fileChooser = new JFileChooser(); if(lastSelectedBaseFile != null) fileChooser.setSelectedFile(lastSelectedBaseFile); fileChooser.setDialogTitle("Select your RacesAndClasses Folder"); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int result = fileChooser.showOpenDialog(null); //check if abort or exit. if(result == JFileChooser.CANCEL_OPTION) return; if(result == JFileChooser.ERROR_OPTION) return; if(result == JFileChooser.APPROVE_OPTION) { File selected = fileChooser.getSelectedFile(); lastSelectedBaseFile = selected; if(selected == null || !selected.getName().equalsIgnoreCase("RacesAndClasses")){ JOptionPane.showMessageDialog(null, "The folder has to be 'RacesAndClasses' base directory."); return; } baseFile = selected; reloadEverything(); }; } /** * Reloads everything from the Base-Dir. */ private static void reloadEverything() { if(baseFile == null) return; reloadTraits(); File racesDir = new File(baseFile, "races"); File classesDir = new File(baseFile, "classes"); Set<YAMLConfigExtended> racesConfigs = new HashSet<YAMLConfigExtended>(); for(File raceFile : racesDir.listFiles()){ if(!raceFile.getName().endsWith(".yml")) continue; racesConfigs.add(new YAMLConfigExtended(raceFile).load()); } Set<YAMLConfigExtended> classesConfigs = new HashSet<YAMLConfigExtended>(); for(File classFile : classesDir.listFiles()){ if(!classFile.getName().endsWith(".yml")) continue; classesConfigs.add(new YAMLConfigExtended(classFile).load()); } //do the configs. for(YAMLConfigExtended raceConfig : racesConfigs){ for(String root : raceConfig.getRootChildren()){ //read ALL the Race. String displayName = raceConfig.getString(root + ".config.name", root); String manaBonus = raceConfig.getString(root + ".config.manabonus", "+0"); String tag = raceConfig.getString(root + ".config.tag", ""); String armor = raceConfig.getString(root + ".config.armor", ""); Set<GuiTrait> loadTraits = loadTraits(raceConfig, root); GuiRace race = new GuiRace(raceConfig, displayName, root, tag, manaBonus, armor, loadTraits); races.add(race); } } for(YAMLConfigExtended classConfig : classesConfigs){ for(String root : classConfig.getRootChildren()){ String displayName = classConfig.getString(root + ".config.name", root); String manaBonus = classConfig.getString(root + ".config.manabonus", "+0"); String tag = classConfig.getString(root + ".config.tag", ""); String armor = classConfig.getString(root + ".config.armor", ""); Set<GuiTrait> loadTraits = loadTraits(classConfig, root); GuiClass clazz = new GuiClass(classConfig, displayName, root, tag, manaBonus, armor, loadTraits); classes.add(clazz); } } } /** * loads the List of Traits from the Config. * * @param raceConfig to load from. * @param name to load * * @return the set of Traits to load. */ private static Set<GuiTrait> loadTraits(YAMLConfigExtended config, String name) { Set<String> names = config.getChildren(name + ".traits"); Set<GuiTrait> traits = new HashSet<GuiTrait>(); for(String traitName : names){ GuiTrait trait = loadTrait(config, name + ".traits", traitName); if(trait != null) traits.add(trait); } return traits; } /** * Loads the Config from the Path. * * @param config to load from * @param path to load from * @param traitPath the Path name to use. * * @return the loaded Trait. */ private static GuiTrait loadTrait(YAMLConfigExtended config, String path, String traitPath){ if(config == null || path == null || path.isEmpty()) return null; if(!config.contains(path)) return null; String traitName = traitPath; if(traitPath.contains("#")) traitName = traitName.split(Pattern.quote("#"))[0]; traitName = config.getString(path + "." + traitPath + ".trait", traitName); java.lang.Class<? extends Trait> traitClass = traitMap.get(traitName); if(traitClass == null) return null; GuiTrait trait = TraitGuiConfigParser.generateEmptyConfig(traitClass); for(TraitConfigOption option : trait.getTraitConfigurationNeeded()){ if(config.contains(path + "." + traitPath + "." + option.getName())){ String value = config.get(path + "." + traitPath + "." + option.getName()).toString(); option.setCreated(true); option.valueSelected(value); } } return trait; } /** * Reloads all Traits. */ private static void reloadTraits(){ traitMap.clear(); FileFilter filter = new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().endsWith(".jar"); } }; Set<File> files = FileUtils.getAllFiles(new File(baseFile, "traits"), filter); for(File file : files){ loadExternalTrait(file); } } /** * Loads the traits from the System. */ @SuppressWarnings("unchecked") private static void loadExternalTrait(File file){ try{ //old but working: //TODO replace by new Classloader with no leaking of memory URLClassLoader clazzLoader = URLClassLoader.newInstance(new URL[]{file.toURI().toURL()}, bukkitLoader); JarFile jarFile = new JarFile(file); Enumeration<JarEntry> entries = jarFile.entries(); Set<java.lang.Class<Trait>> clazzArray = new HashSet<java.lang.Class<Trait>>(); while (entries.hasMoreElements()) { JarEntry element = entries.nextElement(); if (element.getName().endsWith(".class")) { try { java.lang.Class<?> clazz = clazzLoader.loadClass(element.getName().replaceAll(".class", "").replaceAll("/", ".")); if(clazz != null){ if(Trait.class.isAssignableFrom(clazz)){ clazzArray.add((java.lang.Class<Trait>) clazz); } } } catch (Throwable e) { System.out.println("Could not load Java Class: " + element.getName() + ". In: " + jarFile.getName()); continue; } } } boolean hasClass = false; for(java.lang.Class<? extends Trait> clazz : clazzArray){ try{ if (clazz != null) { boolean isPresent = clazz.getMethod("importTrait").isAnnotationPresent(TraitInfos.class); if(isPresent){ TraitInfos annotation = clazz.getMethod("importTrait").getAnnotation(TraitInfos.class); hasClass = true; String name = annotation.traitName(); traitMap.put(name, clazz); }else{ throw new AnnotationFormatError("Annotation: Import could not be found for class: " + clazz); } } }catch(AnnotationFormatError e){ System.out.println(e.getLocalizedMessage()); } } jarFile.close(); if(!hasClass){ throw new AnnotationFormatError("Annotation: Import could not be found for file: " + file.getName()); } }catch (NoClassDefFoundError e) { String message = "Unable to load " + file.getName() + ". Probably it was written for a previous Races version!"; System.out.println(message); return; } catch (AnnotationFormatError e){ System.out.println(e.getLocalizedMessage()); } catch (Throwable e) { String message = "The trait " + file.getName() + " failed to load for an unknown reason."; System.out.println(message); e.printStackTrace(); } } /** * Saves everything! */ public static void save() { for(GuiRace race : races){ race.save(); } for(GuiClass clazz : classes){ clazz.save(); } } }