/* * FilenameResolver.java * * Created on February 9, 2007, 4:44 PM */ package eug.shared; import at.jta.Key; import at.jta.Regor; import eug.parser.EUGFileIO; import java.io.File; import java.util.List; /** * Resolves filenames, given a main directory name and a mod name. * <p> * The default behavior is EU3-compatible: mod files are in a directory named * <code>getMainDirName() + getModPrefix() + getModName()</code>, and rules for * extending/replacing directories are in * <code>getMainDirName() + getModPrefix() + getModName() + ".mod"</code>. * <p> * To change to EU2-compatible behavior, use * {@link #setModFile(boolean) setModFile(false)} and * {@link #setModPrefix(String) setModPrefix("")}. * @author Michael Myers * @since EUGFile 1.02.01 */ public final class FilenameResolver { private static final String FILE_SEPARATOR = System.getProperty("file.separator"); private static final String defaultMainDir = "C:/Program Files/Paradox Interactive/Europa Universalis 3"; /** The base directory. */ private String mainDirName; private String modName; private String modPrefix = "mod/"; private String modDirName; private List<String> extended; private List<String> replaced; private boolean usingMod; /** true <=> main mod directory has a .mod file corresponding to each mod. */ private boolean modFile = true; /** * Creates a new instance of FilenameResolver, loading configuration from * the given file. * @param configFile a file containing at least the following two entries: * <ul> * <li><code>maindir = "<full path>"</code></li> * <li><code>moddir = "<name of mod>"</code></li> * </ul> */ public FilenameResolver(File configFile) { initDirNames(configFile); } /** * Creates a new instance of FilenameResolver with the given main directory * and no mod. * @param mainDirName the fully qualified name of the main directory. */ public FilenameResolver(String mainDirName) { setMainDirectory(mainDirName); setModName(""); } /** * Creates a new instance of FilenameResolver with the given main directory * and mod name. * @param mainDirName the fully qualified name of the main directory. * @param modName the unqualified name of the mod. */ public FilenameResolver(String mainDirName, String modName) { setMainDirectory(mainDirName); setModName(modName != null ? modName : ""); } private void initDirNames(File cfgFile) { GenericObject cfg = EUGFileIO.load(cfgFile); String mainDir = ""; String modDir = ""; if (cfg != null) { mainDir = cfg.getString("maindir"); modDir = cfg.getString("moddir"); } if(mainDir.equals("")) { try { if (System.getProperty("os.name").startsWith("Windows")) { //try to read the registry Regor regor = new Regor(); Key k = regor.openKey(Regor.HKEY_LOCAL_MACHINE, "SOFTWARE\\Paradox Interactive\\Europa Universalis III"); mainDir = new String(regor.readValue(k, "path")).replaceAll("\0", ""); } else { mainDir = defaultMainDir; } } catch (Exception e) { e.printStackTrace(); System.out.println("using default mod dir"); setMainDirectory(defaultMainDir); setModName(""); return; } } setMainDirectory(mainDir); setModName(modDir); } /** * Sets the main directory name to the given string. * @param dirName the main directory. * @throws NullPointerException if <code>dirName</code> is <code>null</code>. */ public void setMainDirectory(String dirName) { System.out.println("Set main directory to " + dirName); mainDirName = dirName; if (!(mainDirName.endsWith("/") || mainDirName.endsWith("\\"))) { mainDirName += FILE_SEPARATOR; } } /** * Sets the mod name to the given string. * @param modName the name of the mod. * @throws NullPointerException if <code>modName</code> is <code>null</code>. */ public void setModName(String modName) { this.modName = modName; if (modName.length() != 0) { usingMod = true; modDirName = mainDirName + modPrefix + modName + "/"; if (modFile) { GenericObject mod = EUGFileIO.load(mainDirName + modPrefix + modName + ".mod"); if (mod == null) { modFile = false; extended = null; replaced = null; } else { extended = mod.getStrings("extend"); replaced = mod.getStrings("replace"); } } } else { usingMod = false; modDirName = mainDirName; } } /** * Resolves the name of the given directory. * <p> * If no mod is being used, this simply returns the parameter. Otherwise, * the mod's .mod file will be checked to see if the directory is replaced. * If so, the mod path will be returned. * * @param dirName the name of the directory to resolve. * @return the path that the game will look in for the directory. This path * will end with the default file separator character. * @see resolveFilename(String) */ public String resolveDirectory(String dirName) { if (dirName.startsWith("/") || dirName.startsWith("\\")) dirName = dirName.substring(1); if (!(dirName.endsWith("/") || dirName.endsWith("\\"))) dirName += FILE_SEPARATOR; if (!usingMod) return mainDirName + dirName; String[] splitPath = splitParent(dirName); if (modFile) { if (replaced.contains(splitPath[0])) { return modDirName + dirName; } else if (extended.contains(splitPath[0])) { // XXX I don't think extending directories is done correctly if (new File(modDirName + dirName).exists()) { return modDirName + dirName; } else { return mainDirName + dirName; } } else { return mainDirName + dirName; } } else { if (new File(modDirName + dirName).exists()) return modDirName + dirName; else return mainDirName + dirName; } } /** * Lists all files in the given directory. If a mod is being used and the * directory is set to extend, files in both the original and the mod * directory are returned. * @param dirName the name of the directory to list files in. */ public File[] listFiles(String dirName) { if (dirName.startsWith("/") || dirName.startsWith("\\")) dirName = dirName.substring(1); if (!(dirName.endsWith("/") || dirName.endsWith("\\"))) dirName += FILE_SEPARATOR; if (!usingMod) return new File(mainDirName + dirName).listFiles(); String[] splitPath = splitParent(dirName); if (modFile) { if (replaced.contains(splitPath[0])) { return new File(modDirName + dirName).listFiles(); } else if (extended.contains(splitPath[0])) { java.util.List<File> ret = new java.util.ArrayList<File>(); for (File f : new File(mainDirName + dirName).listFiles()) { ret.add(f); } File moddedDir = new File(modDirName + dirName); if (moddedDir.exists()) { for (File f : moddedDir.listFiles()) { ret.add(f); } } return ret.toArray(new File[ret.size()]); } else { return new File(mainDirName + dirName).listFiles(); } } else { if (new File(modDirName + dirName).exists()) return new File(modDirName + dirName).listFiles(); else return new File(mainDirName + dirName).listFiles(); } } /** * Resolves the name of the given file. * <p> * If no mod is being used, this simply returns the parameter. Otherwise, * the mod's .mod file will be checked to see if the file's directory * (the part of the filename before the first '/' or '\' character) is * being replaced. If so, the mod path will be returned. * * @param filename the name of the file to resolve the path of. * @return the path that the game will look in for the file. * @see resolveDirectory(String) */ public String resolveFilename(String filename) { if (filename.startsWith("/") || filename.startsWith("\\")) filename = filename.substring(1); if (!usingMod) { return mainDirName + filename; } String[] splitPath = splitParent(filename); if (modFile) { // EU3-style mod if (replaced.contains(splitPath[0])) { // Case 1: Directory is replaced. // Return the file in the moddir, even if it doesn't exist. return modDirName + filename; } else if (extended.contains(splitPath[0])) { // Case 2: Directory is extended. // Check if the file exists in the moddir. if (new File(modDirName + filename).exists()) { // It does, so return it. return modDirName + filename; } else { // It doesn't, so return the file in the main dir. return mainDirName + filename; } } else { // Case 3: Directory is not modded. // Return the file in the main dir. return mainDirName + filename; } } else { // EU2-style mod if (new File(modDirName + filename).exists()) return modDirName + filename; else return mainDirName + filename; } } private static final String[] splitParent(String path) { int separatorIdx = path.indexOf('/'); if (separatorIdx < 0) { separatorIdx = path.indexOf('\\'); if (separatorIdx < 0) return new String[] {"", path}; } return new String[] { path.substring(0, separatorIdx), path.substring(separatorIdx) }; } // Inner classes to make the EU3 methods easier. // No longer used. // private static final class ProvFilter implements FilenameFilter { // private final String id; // public ProvFilter(int id) { // this.id = Integer.toString(id); // } // public boolean accept(File dir, String name) { // return name.startsWith(id) && // !name.endsWith("~") && // !Character.isDigit(name.charAt(id.length())); // } // } // // private static final class TagFilter implements FilenameFilter { // private final String tag; // public TagFilter(String tag) { // this.tag = tag.toUpperCase(); // } // public boolean accept(File dir, String name) { // return name.toUpperCase().startsWith(tag) && !name.endsWith("~"); // } // } /** * EU3-specific method to find the province history file for the given * province.<p> * Originally package-private, but became public upon moving to eug.shared. * @return the name of the province history file, or <code>null</code> if * the file could not be found. */ public String getProvinceHistoryFile(final int provId) { // Can't use resolveFilename, because we don't know what the file is // called. if (usingMod) { if (replaced.contains("history")) { // Case 1: History is replaced. // If there is a file in the mod's province history folder that // starts with the provId, return it. // Otherwise, return null. return getFile(modDirName + "history/provinces", Integer.toString(provId), true); } else if (extended.contains("history")) { // Case 2: History is extended. // If there is a file in the mod's province history folder that // starts with the provId, return it. // Otherwise, try to return the vanilla file. String filename = getFile(modDirName + "history/provinces", Integer.toString(provId), true); if (filename != null) return filename; else return getVanillaProvHistoryFile(provId); } else { // Case 3: History is not modded. // Try to return the vanilla file. return getVanillaProvHistoryFile(provId); } } else { // No mod. Try to return the vanilla file. return getVanillaProvHistoryFile(provId); } } private String getVanillaProvHistoryFile(final int provId) { return getFile(mainDirName + "history/provinces", Integer.toString(provId), true); } /** * EU3-specific method to find the country history file for the given * country. * @since EUGFile 1.04.00pre1 */ public String getCountryHistoryFile(final String tag) { if (usingMod) { if (replaced.contains("history")) { return getFile(modDirName + "history/countries", tag, false); } else if (extended.contains("history")) { String filename = getFile(modDirName + "history/countries", tag, false); if (filename != null) return filename; else return getVanillaCtryHistoryFile(tag); } else { return getVanillaCtryHistoryFile(tag); } } else { return getVanillaCtryHistoryFile(tag); } } private String getVanillaCtryHistoryFile(final String tag) { return getFile(mainDirName + "history/countries", tag, false); } private java.util.Map<String, String[]> directories = new java.util.HashMap<String, String[]>(); private String getFile(final String dirname, final String start, final boolean exactMatch) { String[] array = directories.get(dirname); if (array == null) { array = new File(dirname).list(); directories.put(dirname, array); } final int length = start.length(); for (String name : array) { if (name.substring(0, length).equalsIgnoreCase(start) && //!name.contains("~") && (!exactMatch || !Character.isLetterOrDigit(name.charAt(length)))) { return new File(dirname + File.separatorChar + name).getAbsolutePath(); } } return null; } /** * Directory information for province and country history files is cached, * to avoid calling <code>File.listFiles()</code> every time a file is * requested. This method clears the caches to force reloading of the * directory information. */ public void reset() { directories.clear(); } /** * Returns the prefix used between the main directory name and the mod name. * For EU3, this is "mod/". */ public String getModPrefix() { return modPrefix; } /** * Sets the prefix used between the main directory name and the mod name. * For EU3, this is "mod/". * <p> * For EU2 compatibility, set this to "" and set modFile to * <code>false</code>. * @param prefix the prefix that all mod directories start with. * @see #setModFile */ public void setModPrefix(String prefix) { modPrefix = prefix; setModName(modName); } /** * Returns the main directory name. This method is not generally useful; * {@link #getModDirName()} is more common. */ public String getMainDirName() { return mainDirName; } /** * Returns the fully qualified name of the mod directory, ending with a * file separator. Use this method when creating a new file, for example. * @since EUGFile 1.04.00pre1 */ public String getModDirName() { return modDirName; } /** * Returns the name of the mod, or "" if no mod is being used. * @return the name of the mod being used. * @since EUGFile 1.07.00pre1 */ public String getModName() { return modName; } /** * @return true if an EU3-style .mod file is being used. */ public boolean hasModFile() { return modFile; } public void setModFile(boolean modFile) { this.modFile = modFile; setModName(modName); } /** * @return <code>true</code> if the given directory is extended. * @since EUGFile 1.04.00pre1 */ public boolean isExtended(String directory) { return usingMod && extended.contains(directory); } /** * @return <code>true</code> if the given directory is replaced. * @since EUGFile 1.04.00pre1 */ public boolean isReplaced(String directory) { return usingMod && replaced.contains(directory); } }