package modmanager.controller;
import modmanager.business.modactions.ActionEditFileFindAll;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.io.StreamException;
import com.mallardsoft.tuple.Pair;
import com.mallardsoft.tuple.Tuple;
import org.apache.log4j.Logger;
import modmanager.business.ManagerOptions;
import modmanager.business.Mod;
import modmanager.business.ModsOutOfDateReminder;
import modmanager.exceptions.*;
import modmanager.gui.l10n.L10n;
import modmanager.utility.OS;
import modmanager.utility.XML;
import modmanager.utility.ZIP;
import modmanager.utility.FileUtils;
import modmanager.utility.Game;
import modmanager.utility.SplashScreenMain;
import modmanager.utility.update.UpdateReturn;
import modmanager.utility.update.UpdateThread;
import java.nio.channels.FileLockInterruptionException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Observable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.zip.ZipException;
import java.security.InvalidParameterException;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import modmanager.business.modactions.Action;
import modmanager.business.modactions.ActionApplyAfter;
import modmanager.business.modactions.ActionApplyBefore;
import modmanager.business.modactions.ActionCopyFile;
import modmanager.business.modactions.ActionEditFile;
import modmanager.business.modactions.ActionEditFileActions;
import modmanager.business.modactions.ActionEditFileDelete;
import modmanager.business.modactions.ActionEditFileFind;
import modmanager.business.modactions.ActionEditFileFindUp;
import modmanager.business.modactions.ActionEditFileInsert;
import modmanager.business.modactions.ActionEditFileReplace;
import modmanager.business.modactions.ActionIncompatibility;
import modmanager.business.modactions.ActionRequirement;
/**
* Implementation of the core functionality of HoN modification manager. This class is
* the 'model' part of the MVC framework used for creating manager GUI. After any updates
* that should result in UI changes (such as new mod added) it should call updateNotify
* method which will notify observers to refresh. This class should never directly call
* view or controller classes.
*
* @author Shirkit
*/
public class Manager extends Observable {
private static Manager instance = null;
private HashMap<Mod, HashMap<String, String>> deps;
private HashSet<HashMap<String, String>> cons;
private HashMap<Mod, HashMap<String, String>> after;
private HashMap<Mod, HashMap<String, String>> before;
private ArrayList<String> resources0FolderTree;
private static Logger logger = Logger.getLogger(Manager.class.getPackage().getName());
/**
* It's private since only one isntance of the controller is allowed to exist.
*/
private Manager() {
// Deps, After, and Before are all Map of Mod and ArrayList of Tuple, this way the key can query the mods requested and the value is the list
//
// Cons is an Arraylist of sets, this way each set has only 2 mods (should be),
// then by checking for all items in the list to find the sets contains the mod, one can pinpoint the incompatible mods too
deps = new HashMap<Mod, HashMap<String, String>>();
cons = new HashSet<HashMap<String, String>>();
after = new HashMap<Mod, HashMap<String, String>>();
before = new HashMap<Mod, HashMap<String, String>>();
resources0FolderTree = new ArrayList<String>();
resources0FolderTree.add("buildings");
resources0FolderTree.add("core" + File.separator + "cursors");
resources0FolderTree.add("core" + File.separator + "fonts");
resources0FolderTree.add("core" + File.separator + "materials");
resources0FolderTree.add("core" + File.separator + "null");
resources0FolderTree.add("core" + File.separator + "post");
resources0FolderTree.add("heroes");
resources0FolderTree.add("items");
resources0FolderTree.add("music");
resources0FolderTree.add("npcs");
resources0FolderTree.add("scripts");
resources0FolderTree.add("shared");
resources0FolderTree.add("stringtables");
resources0FolderTree.add("tools");
resources0FolderTree.add("tools");
resources0FolderTree.add("triggers");
resources0FolderTree.add("ui");
resources0FolderTree.add("world");
}
/**
* This method is used to get the running instance of the Manager class.
* @return the instance.
* @see get()
*/
public static Manager getInstance() {
if (instance == null) {
instance = new Manager();
}
return instance;
}
public ArrayList<String> getResources0FolderTree() {
return resources0FolderTree;
}
/**
* This should be called after adding all the honmod files to build and initialize the arrays
*/
public void buildGraphs() {
ArrayList<Mod> mods = ManagerOptions.getInstance().getMods();
// Now building the graph
for (int i = 0; i < mods.size(); i++) {
if (mods.get(i).getActions() != null) {
for (int j = 0; j < mods.get(i).getActions().size(); j++) {
// ApplyAfter
if (mods.get(i).getActions().get(j).getClass() == ActionApplyAfter.class) {
if (!after.containsKey(mods.get(i))) {
after.put(mods.get(i), new HashMap<String, String>());
}
after.get(mods.get(i)).put(((ActionApplyAfter) mods.get(i).getActions().get(j)).getName(), ((ActionApplyAfter) mods.get(i).getActions().get(j)).getVersion());
// ApplyBefore
} else if (mods.get(i).getActions().get(j).getClass() == ActionApplyBefore.class) {
if (!before.containsKey(mods.get(i))) {
before.put(mods.get(i), new HashMap<String, String>());
}
before.get(mods.get(i)).put(((ActionApplyBefore) mods.get(i).getActions().get(j)).getName(), ((ActionApplyBefore) mods.get(i).getActions().get(j)).getVersion());
// ApplyIncompatibility
} else if (mods.get(i).getActions().get(j).getClass() == ActionIncompatibility.class) {
HashMap<String, String> mapping = new HashMap<String, String>();
mapping.put(mods.get(i).getName(), mods.get(i).getVersion());
mapping.put(((ActionIncompatibility) mods.get(i).getActions().get(j)).getName(), ((ActionIncompatibility) mods.get(i).getActions().get(j)).getVersion());
cons.add(mapping);
// ApplyRequirement
} else if (mods.get(i).getActions().get(j).getClass() == ActionRequirement.class) {
if (!deps.containsKey(mods.get(i))) {
deps.put(mods.get(i), new HashMap<String, String>());
}
deps.get(mods.get(i)).put(((ActionRequirement) mods.get(i).getActions().get(j)).getName(), ((ActionRequirement) mods.get(i).getActions().get(j)).getVersion());
}
}
}
}
}
private void doSaveOptions() throws IOException {
// TODO: Change path of managerOptions.xml
String name = FileUtils.getManagerPerpetualFolder() + File.separator + ManagerOptions.OPTIONS_FILENAME;
File f = new File(name);
f.setReadable(true);
f.setWritable(true);
ManagerOptions.getInstance().saveOptions(f);
}
/**
* This method saves the ManagerOptions attributes in a file. The file is located in the same folder of the Manager.
* The filename can be get in the ManagerOptions.
* @throws IOException if a random I/O exception happened.
*
*/
public void saveOptions() throws IOException {
doSaveOptions();
logger.info("Options saved. Path=" + FileUtils.getManagerPerpetualFolder() + File.separator + ManagerOptions.OPTIONS_FILENAME);
}
/**
* This method saves the ManagerOptions attributes in a file but without adding a logging info. The file is located in the same folder of the Manager.
* This method's existence is just for not spam the saving Column size change thing. If the user begins to change the colum's Widht, it was spamming infinite Saving log stuff.
* The filename can be get in the ManagerOptions.
* @throws IOException if a random I/O exception happened.
*
*/
public void saveOptionsNoLog() throws IOException {
doSaveOptions();
}
/**
* Not using it currently
* check update the path of the Hon or Mod folder according to the string passed in
* and prompt the user for input if the designate functions have failed.
* @deprecated no sense on this method.
*/
public String check(String name) {
String path = "";
if (name.equalsIgnoreCase("HoN folder")) {
path = Game.findHonFolder();
} else if (name.equalsIgnoreCase("Mod folder")) {
path = Game.findModFolder(Game.findHonFolder());
}
if (path == null || path.isEmpty()) {
JFileChooser fc = new JFileChooser();
fc.setAcceptAllFileFilterUsed(false);
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (OS.isMac()) {
fc.setCurrentDirectory(new File("/Applications"));
}
fc.setMultiSelectionEnabled(false);
fc.showOpenDialog(null);
if (fc.getSelectedFile() != null) {
path = fc.getSelectedFile().getAbsolutePath();
} else {
path = null;
}
}
return path;
}
/**
* This method runs the ManagerOptions.loadOptions method to load the options located in a file.
*/
public void loadOptions() {
try {
ManagerOptions.getInstance().loadOptions();
ManagerOptions.getInstance().setNoOptionsFile(false);
logger.info("Options loaded.");
} catch (FileNotFoundException e) {
logger.error("Failed loading options file.", e);
ManagerOptions.getInstance().setGamePath(Game.findHonFolder());
logger.info("HoN folder set to=" + ManagerOptions.getInstance().getGamePath());
ManagerOptions.getInstance().setModPath(Game.findModFolder(Game.findHonFolder()));
logger.info("Mods folder set to=" + ManagerOptions.getInstance().getModPath());
} catch (StreamException e) {
logger.error("Failed loading options file.", e);
ManagerOptions.getInstance().setGamePath(Game.findHonFolder());
logger.info("HoN folder set to=" + ManagerOptions.getInstance().getGamePath());
ManagerOptions.getInstance().setModPath(Game.findModFolder(Game.findHonFolder()));
logger.info("Mods folder set to=" + ManagerOptions.getInstance().getModPath());
}
logger.info("MAN: finished loading options.");
}
/**
* Adds a Mod to the list of mods. This adds the mod to the Model list of mods.
* @param Mod to be added.
*/
private void addMod(Mod mod) {
// Disable the mod to make sure it is consistent
ManagerOptions.getInstance().addMod(mod, false);
}
boolean firstLoad = false;
/**
* This method returns possible Honmod files inside a target folder.
* @param targetFolder
* @return
*/
public File[] listHonmodFiles(String targetFolder) {
FileFilter fileFilter = new FileFilter() {
public boolean accept(File file) {
String fileName = file.getName();
if ((!file.isDirectory())
&& /* Filter out directories */ (!fileName.startsWith("."))
&& /* Filter out hidden files and current dir */ ((fileName.endsWith(".honmod")) || (fileName.endsWith(".zip")))) /* Filter only .honmod files */ {
return true;
} else {
return false;
}
}
};
return new File(targetFolder).listFiles(fileFilter);
}
/**
* Load all mods from the mods folder (set in Model) and put them into the Model array of mods.
*/
public ArrayList<ArrayList<Pair<String, String>>> loadMods(boolean developerMode) throws IOException {
ManagerOptions.getInstance().getMods().clear();
// Get mod files from the directory
File[] files = listHonmodFiles(ManagerOptions.getInstance().getModPath());
// Exit if no file is found
if (files == null || files.length == 0) {
return new ArrayList<ArrayList<Pair<String, String>>>();
}
// Go through all the mods and load them
ArrayList<Pair<String, String>> stream = new ArrayList<Pair<String, String>>();
ArrayList<Pair<String, String>> notfound = new ArrayList<Pair<String, String>>();
ArrayList<Pair<String, String>> zip = new ArrayList<Pair<String, String>>();
ArrayList<Pair<String, String>> duplicate = new ArrayList<Pair<String, String>>();
ArrayList<ArrayList<Pair<String, String>>> problems = new ArrayList<ArrayList<Pair<String, String>>>();
if (SplashScreenMain.getInstance().isSplashScreenActive()) {
SplashScreenMain.getInstance().setProgressMax(files.length);
}
String devMod = ManagerOptions.getInstance().getDevelopingMod();
if (developerMode && devMod != null && !devMod.isEmpty()) {
File[] filesTemp = new File[files.length + 1];
System.arraycopy(files, 0, filesTemp, 1, files.length);
filesTemp[0] = new File(devMod);
files = filesTemp;
}
for (int i = 0; i < files.length; i++) {
try {
//logger.error("Adding file - " + files[i].getName() + " from loadMods().");
//ManagerCtrl.getGUI().showMessage(L10n.getString("error.loadmodfile").replace("#mod#", files[i].getName()), "TESTING", JOptionPane.ERROR_MESSAGE);
addHonmod(files[i], false);
if (SplashScreenMain.getInstance().isSplashScreenActive()) {
SplashScreenMain.getInstance().setProgress("" + i + "/" + files.length, i);
}
} catch (ModStreamException e) {
logger.error("StreamException from loadMods(): file - " + files[i].getName() + " - is corrupted.", e);
stream.addAll(e.getMods());
//ManagerCtrl.getGUI().showMessage(L10n.getString("error.loadmodfile").replace("#mod#", files[i].getName()), "error.loadmodfile.title", JOptionPane.ERROR_MESSAGE);
} catch (ModNotFoundException e) {
logger.error("FileNotFoundException from loadMods(): file - " + files[i].getName() + " - is corrupted.", e);
notfound.addAll(e.getMods());
//ManagerCtrl.getGUI().showMessage(L10n.getString("error.loadmodfile").replace("#mod#", files[i].getName()), "error.loadmodfile.title", JOptionPane.ERROR_MESSAGE);
} catch (ModDuplicateException e) {
logger.error("ModDuplicateException from loadMods().", e);
duplicate.addAll(e.getMods());
} catch (ConversionException e) {
logger.error("Conversion from loadMods(): file - " + files[i].getName() + " - is corrupted.", e);
} catch (ModZipException e) {
logger.error("ZipException from loadsMods(): file - " + files[i].getName() + " - is corrupted.", e);
zip.addAll(e.getMods());
}
}
try {
// Load the reminder and enable it by default. If the user chosen to disable it, future methods will overide this and disable it.
Mod moodr = ModsOutOfDateReminder.getMod();
moodr.setIcon(new javax.swing.ImageIcon(getClass().getResource("/modmanager/gui/resources/icon.png")));
moodr.setChangelog(null);
moodr.setPath(null);
addMod(moodr);
} catch (Exception e) {
logger.error("Failed to load Mods Out of Date Reminder", e);
}
problems.add(stream);
problems.add(notfound);
problems.add(zip);
problems.add(duplicate);
return problems;
}
/**
* This function is used internally from the GUI itself automatically when launch to initiate existing mods.
* @param honmod is the file (.honmod) to be add.
* @param copy flag to indicate whether to copy the file to mods folder
* @throws FileNotFoundException if the file wasn't found.
* @throws IOException if a random I/O exception has happened.
*/
public void addHonmod(File honmod, boolean copy) throws ModNotFoundException, ModStreamException, IOException, ModZipException, ModDuplicateException {
ArrayList<Pair<String, String>> list = new ArrayList<Pair<String, String>>();
if (!honmod.exists()) {
list.add(Tuple.from(honmod.getName(), "notfound"));
throw new ModNotFoundException(list);
}
String xml = null;
try {
if (honmod.isFile()) {
xml = new String(ZIP.getFile(honmod, Mod.MOD_FILENAME), "UTF-8");
} else {
// Directory
xml = FileUtils.loadFile(new File(honmod, Mod.MOD_FILENAME), "UTF-8");
}
} catch (ZipException ex) {
list.add(Tuple.from(honmod.getName(), "zip"));
logger.error(ex);
throw new ModZipException(list);
} catch (FileNotFoundException ex) {
list.add(Tuple.from(honmod.getName(), "zip"));
logger.error(ex);
throw new ModZipException(list);
}
Mod m = null;
try {
m = XML.xmlToMod(xml);
} catch (StreamException ex) {
list.add(Tuple.from(honmod.getName(), "stream"));
throw new ModStreamException(list);
}
if (honmod.getName().endsWith(".zip")) {
honmod.setWritable(true);
honmod.renameTo(new File(honmod.getParentFile(), honmod.getName().replace(".zip", ".honmod")));
}
m.setPath(honmod.getAbsolutePath());
if (getMod(m.getName(), m.getVersion()) != null) {
list.add(Tuple.from(new File(getMod(m.getName(), m.getVersion()).getPath()).getName(), "duplicate"));
list.add(Tuple.from(honmod.getName(), "duplicate"));
throw new ModDuplicateException(list);
}
Icon icon;
try {
if (honmod.isFile()) {
icon = new ImageIcon(ZIP.getFile(honmod, Mod.ICON_FILENAME));
} else {
icon = new ImageIcon(honmod.getAbsolutePath() + File.separator + Mod.ICON_FILENAME);
}
} catch (FileNotFoundException e) {
icon = new javax.swing.ImageIcon(getClass().getResource("/modmanager/gui/resources/icon.png"));
}
String changelog = null;
try {
if (honmod.isFile()) {
changelog = new String(ZIP.getFile(honmod, Mod.CHANGELOG_FILENAME));
} else {
changelog = FileUtils.loadFile(new File(honmod, Mod.CHANGELOG_FILENAME), null);
}
} catch (IOException e) {
changelog = null;
}
m.setChangelog(changelog);
m.setIcon(icon);
logger.info("Mod file opened. Mod name: " + m.getName());
if (copy && !(new File(ManagerOptions.getInstance().getModPath() + File.separator + honmod.getName()).exists())) {
// Copy the honmod file to mods directory
logger.info("Mod file copied to mods folder");
File f = new File(ManagerOptions.getInstance().getModPath());
f.mkdirs();
FileUtils.copyFile(honmod, new File(f, honmod.getName()));
logger.info("Mod file copied to mods older");
m.setPath(f.getAbsolutePath() + File.separator + honmod.getName());
}
addMod(m);
}
/**
* Open specified website in the default browser. This method is using java
* Desktop API and therefore requires Java 1.6. Also, this operation might not
* be supported on all platforms.
*
* @param url url of the website to open
* @return true on success, false in case the operation is not supported on this platform
*/
public boolean openWebsite(String url) {
if (!java.awt.Desktop.isDesktopSupported()) {
logger.info("Opening websites is not supported");
return false;
}
java.awt.Desktop desktop = java.awt.Desktop.getDesktop();
if (!desktop.isSupported(java.awt.Desktop.Action.BROWSE)) {
logger.info("Opening websites is not supported");
return false;
}
try {
java.net.URI uri = new java.net.URI(url);
desktop.browse(uri);
} catch (Exception e) {
logger.error("Unable to open website: " + e.getMessage());
return false;
}
return true;
}
/**
* Open folder containing mods. The folder is opened using OS specific explorer
* and therefore might not be supported on all platforms. This operation uses java
* Desktop API and requires Java 1.6
*
* @return 0 on succuess, -1 in case the operation is not supported on this platform
*/
public int openModFolder() {
if (!java.awt.Desktop.isDesktopSupported()) {
logger.info("Opening local folders is not supported");
return -1;
}
java.awt.Desktop desktop = java.awt.Desktop.getDesktop();
try {
desktop.open(new File(ManagerOptions.getInstance().getModPath()));
} catch (Exception e) {
logger.error("Unable to open local folder: " + e.getMessage());
return -1;
}
return 0;
}
/**
* Get mod at specified index
*
* @param index index of the mod in the list of mods
* @return mod at the given index
* @throws IndexOutOfBoundsException in case index does not exist in the list of mods
* @deprecated This method doesn't make sense and it should be avoided.
*/
public Mod getMod(int index) throws IndexOutOfBoundsException {
return (Mod) ManagerOptions.getInstance().getMods().get(index);
}
/**
* This function returns the mod from the arraylist mods given it's name and version.
* @param name of the mod.
* @param version Version or a version expression of the mod. Examples: "1.1", "1.1-1.5", "*-1.6" or "*". A null string or no lenght will be assumed as any version.
* @return the found Mod or null if isn't found.
*/
public Mod getMod(String name, String version) {
return ManagerOptions.getInstance().getMod(name, version);
}
/**
* This method updates the given mods. It handles all exceptions that can exist, and take the needed actions to complete the task,
* without needing any external influence.
* @param mods to be updated.
* @return a instance of a UpdateReturn containing the result of the method. Updated, failed and already up-to-date mods can be easily found there.
* @throws StreamException This exception is thrown in a serius error.
* @throws ModVersionUnsatisfiedException
* @throws ModNotEnabledException
*/
public UpdateReturn updateMod(ArrayList<Mod> mods) {
// Prepare the pool
ExecutorService pool = Executors.newCachedThreadPool();
Iterator<Mod> it = mods.iterator();
HashSet<Future<UpdateThread>> temp = new HashSet<Future<UpdateThread>>();
// Submit to the pool and fires the threads
while (it.hasNext()) {
Mod tempMod = it.next();
temp.add(pool.submit(new UpdateThread(tempMod)));
logger.info("Started update on: " + tempMod.getName() + " - " + tempMod.getVersion());
}
// Transfer from the pool to a result array
HashSet<Future<UpdateThread>> result = new HashSet<Future<UpdateThread>>();
while (temp.size() != result.size()) {
Iterator<Future<UpdateThread>> ite = temp.iterator();
while (ite.hasNext()) {
Future<UpdateThread> ff = ite.next();
if (!result.contains(ff) && ff.isDone()) {
result.add(ff);
//logger.info("Finished " + result.size() + " out of " + temp.size() + " URL check");
int[] ints = new int[2];
ints[0] = result.size();
ints[1] = temp.size();
setChanged();
notifyObservers(ints);
}
}
}
// Prepare for results
Iterator<Future<UpdateThread>> ite = result.iterator();
UpdateReturn returnValue = new UpdateReturn();
while (ite.hasNext()) {
// Retrieve the UpdateThread
Future<UpdateThread> ff = ite.next();
try {
// Get the mod
UpdateThread mod = (UpdateThread) ff.get();
File file = mod.getFile();
// If file == null, then it means that it didn't needed to download anything, and the mods is up-to-date.
if (file != null) {
new File(mod.getMod().getPath()).setWritable(true);
FileUtils.copyFile(file, mod.getMod().getPath());
Mod newMod = null;
String olderVersion = mod.getMod().getVersion();
try {
newMod = XML.xmlToMod(new String(ZIP.getFile(file, Mod.MOD_FILENAME)));
} catch (StreamException ex) {
logger.info("StreamException: Failed to update: " + mod.getMod().getName(), ex);
returnValue.addModFailed(mod.getMod(), ex);
} catch (ZipException ex) {
logger.info("ZipException: Failed to update: " + mod.getMod().getName(), ex);
returnValue.addModFailed(mod.getMod(), ex);
}
if (newMod != null) {
newMod.setPath(mod.getMod().getPath());
Mod oldMod = getMod(mod.getMod().getName(), olderVersion);
boolean wasEnabled = oldMod.isEnabled();
HashSet<Mod> gotDisable = new HashSet<Mod>();
gotDisable.add(oldMod);
while (!gotDisable.isEmpty()) {
Iterator<Mod> iter = gotDisable.iterator();
while (iter.hasNext()) {
try {
Mod next = iter.next();
disableMod(next);
// If he got under this, so disabling was successfull.
gotDisable.remove(next);
} catch (ModEnabledException ex) {
// Couldn't disable, we need who didn't let disable him
Iterator<Pair<String, String>> itera = ex.getDeps().iterator();
while (itera.hasNext()) {
Pair<String, String> pair = itera.next();
if (!gotDisable.contains(getMod(Tuple.get1(pair), Tuple.get2(pair)))) {
gotDisable.add(getMod(Tuple.get1(pair), Tuple.get2(pair)));
}
}
}
}
}
oldMod.copy(newMod);
if (wasEnabled) {
try {
enableMod(newMod, false);
} catch (Exception ex) {
// Couldn't enable mod, just log it
logger.error("Could not enable mod " + newMod.getName());
}
}
returnValue.addUpdated(mod.getMod(), olderVersion);
logger.info(mod.getMod().getName() + " was updated to " + newMod.getVersion() + " from " + olderVersion);
}
} else {
logger.info(mod.getMod().getName() + " is up-to-date");
returnValue.addUpToDate(mod.getMod());
}
} catch (SecurityException ex) {
logger.info("Couldn't write on the file.");
} catch (InterruptedException ex) {
// Nothing can get here
} catch (ExecutionException ex) {
try {
UpdateModException ex2 = (UpdateModException) ex.getCause();
logger.info("Failed to update: " + ex2.getMod().getName() + " - " + ex2.getCause().getClass() + " - " + ex2.getCause().getMessage());
returnValue.addModFailed(ex2.getMod(), (Exception) ex2.getCause());
} catch (ClassCastException ex3) {
logger.info(ex.getCause());
}
} catch (FileNotFoundException ex) {
// Can't get here
} catch (IOException ex) {
logger.error("Random I/O Exception happened", ex);
// Random IO Exception
}
}
pool.shutdown();
return returnValue;
}
private void checkdiff(Mod mod) throws ModSameNameDifferentVersionsException {
HashSet<Pair<String, String>> modDiffEx = new HashSet<Pair<String, String>>();
Enumeration e = Collections.enumeration(ManagerOptions.getInstance().getMods());
while (e.hasMoreElements()) {
Mod m = (Mod) e.nextElement();
if (m.getName().equalsIgnoreCase(mod.getName()) && m.isEnabled() && !m.getVersion().equalsIgnoreCase(mod.getVersion())) {
modDiffEx.add(Tuple.from(m.getName(), m.getVersion()));
}
}
if (!modDiffEx.isEmpty()) {
throw new ModSameNameDifferentVersionsException(modDiffEx);
}
}
/**
* This function checks to see if all dependencies of a given mod are satisfied. If a dependency isn't satisfied, throws exceptions.
* @param mod to be checked which is guaranteed to be disabled.
* @throws ModNotEnabledException if the mod given by parameter requires another mod to be enabled.
*/
private void checkdeps(Mod mod) throws ModNotEnabledException, ModEnabledException, ModVersionUnsatisfiedException {
// From disableMod
if (mod.isEnabled()) {
Iterator it = deps.entrySet().iterator();
HashSet<Pair<String, String>> modEnabledEx = new HashSet<Pair<String, String>>();
Pair<String, String> match = Tuple.from(mod.getName(), mod.getVersion());
while (it.hasNext()) {
// Make sure all mods depending on mod is disabled first
Map.Entry entry = (Map.Entry) it.next();
Mod m = (Mod) entry.getKey();
if (m.isEnabled() && ((HashMap<String, String>) entry.getValue()).containsKey(mod.getName()) && compareModsVersions(mod.getVersion(), ((HashMap<String, String>) entry.getValue()).get(mod.getName()))) {
modEnabledEx.add(Tuple.from(m.getName(), m.getVersion()));
}
}
if (!modEnabledEx.isEmpty()) {
throw new ModEnabledException(modEnabledEx);
}
// From enableMod
} else if (deps.containsKey(mod)) {
Iterator it = deps.get(mod).entrySet().iterator();
HashSet<Pair<String, String>> modDisabledEx = new HashSet<Pair<String, String>>();
HashSet<Pair<String, String>> modUnsatisfiedEx = new HashSet<Pair<String, String>>();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
// Make sure all dep exist and enabled
ArrayList<Mod> allModWithName = ManagerOptions.getInstance().getModsWithName((String) entry.getKey());
if (allModWithName.isEmpty()) {
modDisabledEx.add(Tuple.from((String) entry.getKey(), (String) entry.getValue()));
} else {
// Make sure all exist and enabled mods are satisfied
Enumeration e = Collections.enumeration(allModWithName);
while (e.hasMoreElements()) {
Mod m = (Mod) e.nextElement();
if (!m.isEnabled() && compareModsVersions(m.getVersion(), (String) entry.getValue())) {
modDisabledEx.add(Tuple.from(m.getName(), m.getVersion()));
}
if (m.isEnabled() && !compareModsVersions(m.getVersion(), (String) entry.getValue())) {
modUnsatisfiedEx.add(Tuple.from(m.getName(), m.getVersion()));
}
}
}
}
if (!modDisabledEx.isEmpty()) {
throw new ModNotEnabledException(modDisabledEx);
}
// We place ModNotEnabledException higher priority than ModVersionUnsatisfied
if (!modUnsatisfiedEx.isEmpty()) {
throw new ModVersionUnsatisfiedException(modUnsatisfiedEx);
}
}
}
/**
* This function checks to see if there is any conflict by the given mod with other enabled mods.
* @param mod to be checked.
* @throws ModEnabledException if another mod that is already enabled has a conflict with the mod given by parameter.
*/
private void checkcons(Mod mod) throws ModConflictException {
Iterator it = cons.iterator();
HashSet<Pair<String, String>> list = new HashSet<Pair<String, String>>();
while (it.hasNext()) {
HashMap<String, String> mapping = (HashMap<String, String>) it.next();
// If this mapping has this mod name
if (mapping.containsKey(mod.getName())) {
// Then it is probably saying this mod mod
if (compareModsVersions(mod.getVersion(), mapping.get(mod.getName()))) {
Iterator itt = mapping.entrySet().iterator();
while (itt.hasNext()) {
Map.Entry entry = (Map.Entry) itt.next();
if (!entry.getKey().equals(mod.getName())) {
Enumeration modsConflict = Collections.enumeration(ManagerOptions.getInstance().getModsWithName((String) entry.getKey()));
while (modsConflict.hasMoreElements()) {
Mod compare = (Mod) modsConflict.nextElement();
if (compareModsVersions(compare.getVersion(), (String) entry.getValue()) && compare.isEnabled()) {
list.add(Tuple.from((String) entry.getKey(), (String) entry.getValue()));
}
}
}
}
}
}
}
if (!list.isEmpty()) {
throw new ModConflictException(list);
}
}
/**
* This function trys to enable the mod with the name given. Throws exceptions if didn't no success while enabling the mod.
* ignoreGameVersion should be always false, unless the user especifically says so.
* @param name of the mod
* @throws ModEnabledException if a mod was enabled and caused an incompatibility with the Mod that is being tryied to apply.
* @throws ModNotEnabledException if a mod that was required by this mod wasn't enabled.
* @throws NoSuchElementException if the mod doesn't exist
* @throws ModVersionMissmatchException if the mod's version is imcompatible with the game version.
* @throws NullPointerException if there is a problem with the game path (maybe the path was not set in the game class,
* or hon.exe wasn't found, or happened a random I/O error).
* @throws FileNotFoundException if the Hon.exe file wasn't found
* @throws IOException if a random I/O Exception happened.
* @throws IllegalArgumentException if a mod used a invalid parameter to compare the mods version.
*/
public void enableMod(Mod m, boolean ignoreGameVersion) throws ModConflictException, ModVersionUnsatisfiedException, ModNotEnabledException, NoSuchElementException, ModVersionMissmatchException, NullPointerException, FileNotFoundException, IllegalArgumentException, IOException, ModSameNameDifferentVersionsException {
if (!m.isEnabled()) {
if (!ignoreGameVersion) {
if (m.getAppVersion() != null) {
if (!m.getAppVersion().contains("-") && !m.getAppVersion().contains("*")) {
if (!compareModsVersions(Game.getInstance().getVersion(), m.getAppVersion() + ".*")) {
throw new ModVersionMissmatchException(m.getName(), m.getVersion(), m.getAppVersion());
}
} else if (m.getAppVersion().contains("*") && !m.getAppVersion().contains("-")) {
if (!compareModsVersions(Game.getInstance().getVersion(), "-" + m.getAppVersion())) {
throw new ModVersionMissmatchException(m.getName(), m.getVersion(), m.getAppVersion());
}
} else {
if (!compareModsVersions(Game.getInstance().getVersion(), m.getAppVersion())) {
throw new ModVersionMissmatchException(m.getName(), m.getVersion(), m.getAppVersion());
}
}
}
}
checkdiff(m);
checkcons(m);
try {
checkdeps(m);
} catch (ModEnabledException e) {
// Exception never thrown
}
// enable it
ManagerOptions.getInstance().getMods().get(ManagerOptions.getInstance().getMods().indexOf(m)).enable();
m.enable();
}
}
/**
* Tries to disable a mod given by it's name. Throws exception if an error occurred .
* @param m name of the mod.
* @throws ModEnabledException if another mod is enabled and requires the given by parameter mod to continue enabled.
*/
public void disableMod(Mod m) throws ModEnabledException {
if (m.isEnabled()) {
try {
checkdeps(m);
} catch (ModNotEnabledException ex) {
// Never thrown
} catch (ModVersionUnsatisfiedException ex) {
// Never thrown
}
// disable it
ManagerOptions.getInstance().getMods().get(ManagerOptions.getInstance().getMods().indexOf(m)).disable();
}
}
public ArrayList<Mod> depSort(ArrayList<Mod> list) {
ArrayList<Mod> ulayer = new ArrayList<Mod>();
ArrayList<Mod> dlayer = new ArrayList<Mod>();
Enumeration e = Collections.enumeration(list);
while (e.hasMoreElements()) {
Mod m = (Mod) e.nextElement();
if (deps.containsKey(m)) {
Iterator it = deps.get(m).entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Mod mod = ManagerOptions.getInstance().getMod((String) entry.getKey(), (String) entry.getValue());
if (mod != null && !ulayer.contains(mod) && mod.isEnabled()) {
ulayer.add(mod);
}
}
}
}
e = Collections.enumeration(list);
while (e.hasMoreElements()) {
Mod m = (Mod) e.nextElement();
if (!ulayer.contains(m)) {
dlayer.add(m);
}
}
if (ulayer.isEmpty()) {
return dlayer;
}
ulayer = depSort(ulayer);
ulayer.addAll(dlayer);
return ulayer;
}
public ArrayList<Mod> afterSort(ArrayList<Mod> list) {
Enumeration e = Collections.enumeration(list);
while (e.hasMoreElements()) {
Mod m = (Mod) e.nextElement();
if (after.containsKey(m)) {
Iterator it = after.get(m).entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Mod mod = ManagerOptions.getInstance().getMod((String) entry.getKey(), (String) entry.getValue());
if (mod != null && mod.isEnabled()) {
if (list.indexOf(mod) >= list.indexOf(m)) {
// Swap
int j = list.indexOf(mod);
int x = list.indexOf(m);
list.set(j, m);
list.set(x, mod);
}
}
}
}
}
return list;
}
public ArrayList<Mod> beforeSort(ArrayList<Mod> list) {
Enumeration e = Collections.enumeration(list);
while (e.hasMoreElements()) {
Mod m = (Mod) e.nextElement();
if (before.containsKey(m)) {
Iterator it = before.get(m).entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Mod mod = ManagerOptions.getInstance().getMod((String) entry.getKey(), (String) entry.getValue());
if (mod != null && mod.isEnabled()) {
if (list.indexOf(mod) <= list.indexOf(m)) {
// Swap
int j = list.indexOf(mod);
int x = list.indexOf(m);
list.set(j, m);
list.set(x, mod);
}
}
}
}
}
return list;
}
public ArrayList<Mod> newSort(ArrayList<Mod> mods) {
boolean changed = false;
Mod[] sorted = new Mod[mods.size()];
Iterator<Mod> it = mods.iterator();
for (int i = 0; i < sorted.length; i++) {
sorted[i] = mods.get(i);
}
it = mods.iterator();
while (it.hasNext()) {
Mod mod = it.next();
if (deps.containsKey(mod)) {
Iterator<Entry<String, String>> iterator1 = deps.get(mod).entrySet().iterator();
while (iterator1.hasNext()) {
Entry<String, String> entry = iterator1.next();
Mod m = ManagerOptions.getInstance().getMod(entry.getKey(), entry.getValue());
if (m != null && m.isEnabled()) {
int actual = indexOf(sorted, mod);
int target = indexOf(sorted, m);
if (actual < target) {
changed = true;
for (int i = actual; i < target; i++) {
Mod x = sorted[i + 1];
sorted[i + 1] = sorted[i];
sorted[i] = x;
}
}
}
}
}
if (after.containsKey(mod)) {
Iterator<Entry<String, String>> iterator1 = after.get(mod).entrySet().iterator();
while (iterator1.hasNext()) {
Entry<String, String> entry = iterator1.next();
Mod m = ManagerOptions.getInstance().getMod(entry.getKey(), entry.getValue());
if (m != null && m.isEnabled()) {
int actual = indexOf(sorted, mod);
int target = indexOf(sorted, m);
if (actual < target) {
changed = true;
for (int i = actual; i < target; i++) {
Mod x = sorted[i + 1];
sorted[i + 1] = sorted[i];
sorted[i] = x;
}
}
}
}
}
if (before.containsKey(mod)) {
Iterator<Entry<String, String>> iterator1 = before.get(mod).entrySet().iterator();
while (iterator1.hasNext()) {
Entry<String, String> entry = iterator1.next();
Mod m = ManagerOptions.getInstance().getMod(entry.getKey(), entry.getValue());
if (m != null && m.isEnabled()) {
int actual = indexOf(sorted, mod);
int target = indexOf(sorted, m);
if (actual > target) {
changed = true;
for (int i = actual; i > target; i--) {
Mod x = sorted[i - 1];
sorted[i - 1] = sorted[i];
sorted[i] = x;
}
}
}
}
}
}
ArrayList<Mod> returnn = new ArrayList<Mod>();
returnn.addAll(Arrays.asList(sorted));
if (changed) {
returnn = newSort(returnn);
}
return returnn;
}
private int indexOf(Mod[] list, Mod m) {
for (int i = 0; i < list.length; i++) {
if (list[i] == m) {
return i;
}
}
return -1;
}
/**
*
* @return
* @throws IOException
*/
public ArrayList<Mod> sortMods() throws IOException {
ArrayList<Mod> left = new ArrayList<Mod>();
// Filling left queue with unordered list of mods to apply
for (int i = 0; i < ManagerOptions.getInstance().getMods().size(); i++) {
if (ManagerOptions.getInstance().getMods().get(i).isEnabled()) {
left.add(ManagerOptions.getInstance().getMods().get(i));
}
}
try {
left = newSort(left);
} catch (Exception e) {
logger.error("Failed to use the new sorting method, attemping to use the old one");
left = beforeSort(afterSort(depSort(left)));
}
return left;
}
public int getApplyIterationsCount() {
int total = 3; // First three that handles the file deletion/creation
for (Mod m : ManagerOptions.getInstance().getMods()) {
if (m.isEnabled()) {
for (Action a : m.getActions()) {
total++;
if (a.getClass().equals(ActionEditFile.class)) {
ActionEditFile ac = (ActionEditFile) a;
if (ac.getActions() != null) {
for (ActionEditFileActions b : ac.getActions()) {
total++;
}
}
}
}
}
}
return total;
}
// TODO: CHANGE THIS COMMENT FOR FUTURE IMPLEMENTATION OF SEMANTHICS AND LOGICAL WARNINS AND ERRORS PARSING.
/**
* Tries to apply the currently enabled mods. They can be found in the Model class.
* @param developerMode If true, the Manager will output the current mods to a folder tree in the HoN/game folder puting the files inside. If not, it will generate the resources999.s2z file. This sould be true for 'Developer Mode'.
* @throws IOException if a random I/O error happened.
* @throws UnknowModActionException if a unkown Action was found. Actions that aren't know by the program can't be applied.
* @throws NothingSelectedModActionException if a action tried to do a action that involves a string, but no string was selected.
* @throws StringNotFoundModActionException if a search for a string was made, but that string wasn't found. Probally,
* imcompatibility or a error by the mod's author.
* @throws InvalidModActionParameterException if a action had a invalid parameter. Only the position of actions 'insert' and 'find' can throw this exception.
* @throws SecurityException if the Manager couldn't do a action because of security business.
* @throws FileLockInterruptionException if the Manager couldn't open the resources999.s2z file.
*/
public void applyMods(boolean developerMode, boolean deleteFolderTree) throws IOException, UnknowModActionException, NothingSelectedModActionException, StringNotFoundModActionException, InvalidModActionParameterException, SecurityException, FileLockInterruptionException, ModFileNotFoundException {
ArrayList<Mod> applyOrder = sortMods();
File tempFolder = FileUtils.generateTempFolder(true);
logger.info("Started mod applying. Folder=" + tempFolder.getAbsolutePath() + ". Game version=" + Game.getInstance().getVersion());
Enumeration<Mod> list = Collections.enumeration(applyOrder);
int counted[] = new int[]{0};
while (list.hasMoreElements()) {
Mod mod = list.nextElement();
// --------------- Status bar update
setChanged();
notifyObservers(mod.getName());
// --------------- Status bar update
logger.info("Applying Mod=" + mod.getName() + " | Version=" + mod.getVersion());
Action lastAction = null;
Action beforeLastAction = null;
for (int j = 0; j < mod.getActions().size(); j++) {
// --------------- Progress bar update
counted[0]++;
setChanged();
notifyObservers(counted);
// --------------- Progress bar update
Action action = mod.getActions().get(j);
String resources0 = ManagerOptions.getInstance().getGamePath() + File.separator + "game" + File.separator + "resources0.s2z";
if (!new File(resources0).exists()) {
throw new FileNotFoundException(resources0);
}
if (action.getClass().equals(ActionCopyFile.class)) {
ActionCopyFile copyfile = (ActionCopyFile) action;
beforeLastAction = lastAction;
lastAction = copyfile;
if (!isValidCondition(action)) {
// condition isn't valid, can't apply
// No need to throw execption, since if condition isn't valid, this action won't be applied
} else {
// if path2 is not specified, path1 is copied
String toCopy;
if (copyfile.getSource() == null || copyfile.getSource().isEmpty() || copyfile.getSource().equals("")) {
toCopy = copyfile.getName();
} else {
// path2 is copied and renamed to path1
toCopy = copyfile.getSource();
}
File temp = new File(tempFolder.getAbsolutePath() + File.separator + copyfile.getName());
if (temp.exists()) {
if (copyfile.overwrite() == -1) {
throw new InvalidModActionParameterException(mod.getName(), mod.getVersion(), (Action) copyfile);
}
if (copyfile.overwrite() == 0) {
// Don't overwrite, do nothing
} else if (copyfile.overwrite() == 1) {
// Overwrite if newer
if (ZIP.getLastModified(new File(mod.getPath()), toCopy) > temp.lastModified()) {
if (temp.delete() && temp.createNewFile()) {
if (!copyfile.getFromResource()) {
FileUtils.writeFile(ZIP.getFile(new File(mod.getPath()), toCopy), temp);
} else {
FileUtils.writeFile(ZIP.getFile(new File(resources0), toCopy), temp);
}
} else {
throw new SecurityException(temp.getAbsolutePath());
}
}
} else if (copyfile.overwrite() == 2) {
// overwrite file
if (temp.delete() && temp.createNewFile()) {
if (!copyfile.getFromResource()) {
FileUtils.writeFile(ZIP.getFile(new File(mod.getPath()), toCopy), temp);
} else {
FileUtils.writeFile(ZIP.getFile(new File(resources0), toCopy), temp);
}
} else {
throw new SecurityException(temp.getAbsolutePath());
}
}
} else {
// if temporary file doesn't exists
if (!temp.getParentFile().exists() && !temp.getParentFile().mkdirs()) {
throw new SecurityException(temp.getAbsolutePath());
}
if (!copyfile.getFromResource()) {
FileUtils.writeFile(ZIP.getFile(new File(mod.getPath()), toCopy), temp);
} else {
FileUtils.writeFile(ZIP.getFile(new File(resources0), toCopy), temp);
}
}
if (!copyfile.getFromResource()) {
temp.setLastModified(ZIP.getLastModified(new File(mod.getPath()), toCopy));
} else {
temp.setLastModified(ZIP.getLastModified(new File(resources0), toCopy));
}
}
} else if (action.getClass().equals(ActionEditFile.class)) {
ActionEditFile editfile = (ActionEditFile) action;
beforeLastAction = lastAction;
lastAction = editfile;
if (!isValidCondition(action)) {
// --------------- Progress bar update
for (int i = 0; editfile.getActions() != null && i < editfile.getActions().size(); i++) {
counted[0]++;
setChanged();
notifyObservers(counted);
}
// --------------- Progress bar update
// condition isn't valid, can't apply
// No need to throw execption, since if condition isn't valid, this action won't be applied
} else {
// the selection is stored here
int cursor[] = new int[]{0};
int cursor2[] = new int[]{0};
// Check for file
File f = new File(tempFolder.getAbsolutePath() + File.separator + editfile.getName());
String afterEdit = "";
if (f.exists()) {
// Load file from temp folder. If any other mod changes the file, it's actions won't be lost.
afterEdit = FileUtils.loadFile(f, "UTF-8");
} else {
// Load file from resources0.s2z if no other mod edited this file
try {
afterEdit = new String(ZIP.getFile(new File(resources0), editfile.getName()), "UTF-8");
} catch (FileNotFoundException e) {
throw new ModFileNotFoundException(mod.getName(), mod.getVersion(), e.getLocalizedMessage(), action, mod);
}
}
if (editfile.getActions() != null && editfile.getActions().size() > 0) {
for (int k = 0; k < editfile.getActions().size(); k++) {
// --------------- Progress bar update
counted[0]++;
setChanged();
notifyObservers(counted);
// --------------- Progress bar update
ActionEditFileActions editFileAction = editfile.getActions().get(k);
// Delete Action
if (editFileAction.getClass().equals(ActionEditFileDelete.class)) {
beforeLastAction = lastAction;
lastAction = (ActionEditFileDelete) editFileAction;
for (int i = 0; i < cursor.length; i++) {
afterEdit = afterEdit.substring(0, cursor[i]) + afterEdit.substring(cursor2[i]);
int lenght = cursor2[i] - cursor[i];
cursor2[i] = cursor[i];
for (int l = i + 1; l < cursor.length; l++) {
cursor[l] = cursor[l] - (lenght);
cursor2[l] = cursor2[l] - (lenght);
}
}
// Find Action
} else if (editFileAction.getClass().equals(ActionEditFileFind.class)) {
ActionEditFileFind find = (ActionEditFileFind) editFileAction;
if (beforeLastAction != null && beforeLastAction.getClass().equals(ActionEditFileFindAll.class)) {
cursor = new int[]{0};
cursor2 = new int[]{0};
} else {
cursor = new int[]{cursor[0]};
cursor2 = new int[]{cursor2[0]};
}
beforeLastAction = lastAction;
lastAction = find;
if (find.getContent() == null || find.getContent().isEmpty()) {
if (find.isPositionAtEnd()) {
cursor[0] = afterEdit.length();
cursor2[0] = cursor[0];
} else if (find.isPositionAtStart()) {
cursor[0] = 0;
cursor2[0] = 0;
} else {
try {
cursor[0] = cursor[0] + Integer.parseInt(find.getPosition());
cursor2[0] = cursor[0];
} catch (NumberFormatException e) {
// it isn't a valid number or word, can't apply
throw new InvalidModActionParameterException(mod.getName(), mod.getVersion(), (Action) find);
}
}
} else {
cursor[0] = afterEdit.toLowerCase().indexOf(find.getContent().toLowerCase(), cursor2[0]);
if (cursor[0] == -1) {
// couldn't find the string, can't apply
throw new StringNotFoundModActionException(mod.getName(), mod.getVersion(), (Action) find, find.getContent(), mod);
}
cursor2[0] = cursor[0] + find.getContent().length();
}
// FindUp Action
} else if (editFileAction.getClass().equals(ActionEditFileFindUp.class)) {
ActionEditFileFindUp findup = (ActionEditFileFindUp) editFileAction;
if (beforeLastAction != null && beforeLastAction.getClass().equals(ActionEditFileFindAll.class)) {
cursor = new int[]{0};
cursor2 = new int[]{0};
} else {
cursor = new int[]{cursor[0]};
cursor2 = new int[]{cursor2[0]};
}
beforeLastAction = lastAction;
lastAction = findup;
cursor[0] = afterEdit.toLowerCase().lastIndexOf(findup.getContent().toLowerCase(), cursor2[0]);
if (cursor[0] == -1) {
// couldn't find the string, can't apply
throw new StringNotFoundModActionException(mod.getName(), mod.getVersion(), (Action) findup, findup.getContent(), mod);
}
cursor2[0] = cursor[0] + findup.getContent().length();
// FindAll Action
} else if (editFileAction.getClass().equals(ActionEditFileFindAll.class)) {
ActionEditFileFindAll findall = (ActionEditFileFindAll) editFileAction;
beforeLastAction = lastAction;
lastAction = findall;
// Preparation
ArrayList<Integer> firstPosition = new ArrayList<Integer>();
ArrayList<Integer> lastPosition = new ArrayList<Integer>();
int index = -1;
int lastIndex = 0;
while ((index = afterEdit.toLowerCase().indexOf(findall.getContent().toLowerCase(), lastIndex)) != -1) {
firstPosition.add(index);
lastPosition.add(index + findall.getContent().length());
lastIndex = index + findall.getContent().length();
}
if (!firstPosition.isEmpty()) {
// no string was found, can't apply
// Insert into the array
cursor = new int[firstPosition.size()];
cursor2 = new int[firstPosition.size()];
for (int i = 0; i < cursor.length; i++) {
cursor[i] = firstPosition.get(i);
cursor2[i] = lastPosition.get(i);
}
//throw new StringNotFoundModActionException(mod.getName(), mod.getVersion(), (Action) findall, findall.getContent(), mod);
} else {
cursor = new int[0];
cursor2 = new int[0];
}
// Insert Action
} else if (editFileAction.getClass().equals(ActionEditFileInsert.class)) {
ActionEditFileInsert insert = (ActionEditFileInsert) editFileAction;
beforeLastAction = lastAction;
lastAction = insert;
for (int i = 0; i < cursor.length; i++) {
if (insert.isPositionAfter()) {
afterEdit = afterEdit.substring(0, cursor2[i]) + insert.getContent() + afterEdit.substring(cursor2[i]);
cursor[i] = cursor2[i];
cursor2[i] = cursor2[i] + insert.getContent().length();
for (int l = i + 1; l < cursor.length; l++) {
cursor[l] = cursor[l] + insert.getContent().length();
cursor2[l] = cursor2[l] + insert.getContent().length();
}
} else if (insert.isPositionBefore()) {
afterEdit = afterEdit.substring(0, cursor[i]) + insert.getContent() + afterEdit.substring(cursor[i]);
//cursor[i] = cursor2[i];
cursor2[i] = cursor[i] + insert.getContent().length();
for (int l = i + 1; l < cursor.length; l++) {
cursor[l] = cursor[l] + insert.getContent().length();
cursor2[l] = cursor2[l] + insert.getContent().length();
}
} else {
// position is invalid, can't apply
throw new InvalidModActionParameterException(mod.getName(), mod.getVersion(), (Action) insert);
}
}
// Replace Action
} else if (editFileAction.getClass().equals(ActionEditFileReplace.class)) {
ActionEditFileReplace replace = (ActionEditFileReplace) editFileAction;
beforeLastAction = lastAction;
lastAction = replace;
for (int i = 0; i < cursor.length; i++) {
afterEdit = afterEdit.substring(0, cursor[i]) + replace.getContent() + afterEdit.substring(cursor2[i]);
int difference = replace.getContent().length() - (cursor2[i] - cursor[i]);
cursor2[i] = cursor[i] + replace.getContent().length();
for (int l = i + 1; l < cursor.length; l++) {
cursor[l] = cursor[l] + difference;
cursor2[l] = cursor2[l] + difference;
}
}
} else {
// Unknow action, can't apply
throw new UnknowModActionException(editFileAction.getClass().getName(), mod.getName());
}
}
}
File temp = new File(tempFolder.getAbsolutePath() + File.separator + editfile.getName().replace("\\", "/"));
File folder = new File(temp.getAbsolutePath().replace(temp.getName(), "") + File.separator);
if (!folder.getAbsolutePath().equalsIgnoreCase(tempFolder.getAbsolutePath())) {
if (!folder.exists()) {
if (!folder.mkdirs()) {
// Can't crete folders to path
throw new SecurityException(folder.getAbsolutePath());
}
}
}
// Write String afterEdit to a file
// Special case: S2's Lua parser won't accept files with BOM header
if (editfile.getName().endsWith(".lua")) {
FileUtils.writeFile(afterEdit.getBytes("UTF-8"), temp);
} else {
FileUtils.writeFileWithBom(afterEdit.getBytes("UTF-8"), temp);
}
}
// ApplyAfter, ApplyBefore, Incompatibility, Requirement Action
} else if (action.getClass().equals(ActionApplyAfter.class) || action.getClass().equals(ActionApplyBefore.class)
|| action.getClass().equals(ActionIncompatibility.class) || action.getClass().equals(ActionRequirement.class)) {
// nothing to do
} else {
// Unknow action, can't apply
throw new UnknowModActionException(action.getClass().getName(), mod.getName());
}
}
}
// --------------- Status bar update
setChanged();
notifyObservers(L10n.getString("status.compressingfiles"));
// --------------- Status bar update
// --------------- Progress bar update
counted[0]++;
setChanged();
notifyObservers(counted);
// --------------- Progress bar update
String dest = "";
// This should probably be fixed for getGamePath() to work for all
// platforms, if it doesn't already.
// Penn: getGamePath should work, now users are prompted for inputing correct game path when starting up in the beginning.
// Penn: Also are you sure about linux the same as windows?
if (OS.isWindows() || OS.isLinux()) {
dest = ManagerOptions.getInstance().getGamePath() + File.separator + "game" + File.separator + "resources999.s2z";
} else if (OS.isMac()) {
dest = System.getProperty("user.home") + "/Library/Application Support/Heroes of Newerth/game/resources999.s2z";
}
File targetZip = new File(dest);
if (targetZip.exists()) {
targetZip.setReadable(true);
targetZip.setWritable(true);
if (!targetZip.delete()) {
throw new FileLockInterruptionException();
}
}
// --------------- Progress bar update
counted[0]++;
setChanged();
notifyObservers(counted);
// --------------- Progress bar update
if (deleteFolderTree) {
deleteFolderTree();
}
if (!applyOrder.isEmpty()) {
if (!developerMode) {
String comment = "All-In Hon ModManager Output\n\nGame Version:" + Game.getInstance().getVersion() + "\n\nApplied Mods:";
for (Iterator<Mod> it = applyOrder.iterator(); it.hasNext();) {
Mod mod = it.next();
comment += "\n" + mod.getName() + " (v" + mod.getVersion() + ")";
}
ZIP.createZIP(tempFolder.getAbsolutePath(), targetZip.getAbsolutePath(), comment);
} else {
if (OS.isMac()) {
FileUtils.copyFolderToFolder(tempFolder, new File(System.getProperty("user.home") + "/Library/Application Support/Heroes of Newerth/game"));
} else if (OS.isWindows() || OS.isLinux()) {
FileUtils.copyFolderToFolder(tempFolder, new File(ManagerOptions.getInstance().getGamePath() + File.separator + "game"));
}
Iterator<String> it = resources0FolderTree.iterator();
while (it.hasNext()) {
File folder = null;
if (OS.isMac()) {
folder = new File(System.getProperty("user.home") + "/Library/Application Support/Heroes of Newerth/game" + File.separator + it.next());
} else if (OS.isWindows() || OS.isLinux()) {
folder = new File(ManagerOptions.getInstance().getGamePath() + File.separator + "game" + File.separator + it.next());
}
if (folder.exists() && folder.isDirectory()) {
File warningFile = new File(folder, "! FILES AND FOLDERS HERE WILL BE DELETED ON NEXT APPLY");
warningFile.createNewFile();
}
}
}
} else {
targetZip.createNewFile();
}
ManagerOptions.getInstance().setAppliedMods(new HashSet<Mod>(applyOrder));
// --------------- Progress bar update
counted[0]++;
setChanged();
notifyObservers(counted);
// --------------- Progress bar update
saveOptions();
}
/**
* Unapplies all currently enabled mods. After that, the method calls the saveOptions().
* @throws SecurityException if a security issue happened, and the action couldn't be completed.
* @throws IOException if a random I/O exception happened.
*/
public void unapplyMods(boolean deleteFolderTree) throws SecurityException, IOException {
ManagerOptions.getInstance().getAppliedMods().clear();
Iterator<Mod> i = ManagerOptions.getInstance().getMods().iterator();
while (i.hasNext()) {
i.next().disable();
}
deleteFolderTree();
try {
applyMods(false, deleteFolderTree);
} catch (IOException ex) {
throw ex;
} catch (UnknowModActionException ex) {
} catch (ModFileNotFoundException ex) {
} catch (NothingSelectedModActionException ex) {
} catch (StringNotFoundModActionException ex) {
} catch (InvalidModActionParameterException ex) {
} catch (SecurityException ex) {
throw ex;
}
try {
saveOptions();
} catch (IOException ex) {
throw ex;
}
}
private void deleteFolderTree() {
Iterator<String> it = resources0FolderTree.iterator();
while (it.hasNext()) {
File folder = new File(ManagerOptions.getInstance().getGamePath() + File.separator + "game" + File.separator + it.next());
if (folder.exists() && folder.isDirectory()) {
if (!FileUtils.deleteDir(folder)) {
// TODO: Need some handling?
}
}
}
}
public boolean hasUnappliedMods() {
Iterator<Mod> it = ManagerOptions.getInstance().getMods().iterator();
while (it.hasNext()) {
Mod mod = it.next();
if ((mod.isEnabled() && !ManagerOptions.getInstance().getAppliedMods().contains(mod)) || (!mod.isEnabled() && ManagerOptions.getInstance().getAppliedMods().contains(mod))) {
return true;
}
}
return false;
}
/**
* Compares the singleVersion of a Mod and another expressionVersion. Letters are ignored (they are removed before the test) and commas (,) are replaced by dots (.)
* @param singleVersion is the base version to be compared of. For example, a Mod's version go in here ('1.3', '3.2.57').
* @param expressionVersion generally you put the ApplyAfter, ApplyBefore, ConditionVersion here ('1.35-*'). This can be a singleVersion like paramether too.
* @return true if the mods have the same expressionVersion OR the singleVersion is in the range of the passed String expressionVersion. False otherwise.
* @throws InvalidParameterException if can't compare the versions for some reason (out of format).
*/
public boolean compareModsVersions(String singleVersion, String expressionVersion) throws InvalidParameterException {
boolean result = false;
if (expressionVersion == null || expressionVersion.isEmpty()) {
expressionVersion = "*";
}
if (singleVersion == null || singleVersion.isEmpty()) {
singleVersion = "*";
}
for (int i = 0; i < expressionVersion.length(); i++) {
if (Character.isLetter(expressionVersion.charAt(i))) {
expressionVersion = expressionVersion.replaceFirst(Character.toString(expressionVersion.charAt(i)), "");
}
}
for (int i = 0; i < singleVersion.length(); i++) {
if (Character.isLetter(singleVersion.charAt(i))) {
singleVersion = singleVersion.replaceFirst(Character.toString(singleVersion.charAt(i)), "");
}
}
expressionVersion = expressionVersion.replace(",", ".");
singleVersion = singleVersion.replace(",", ".");
if (expressionVersion.equals("*-*") || expressionVersion.equals("*") || expressionVersion.equals(singleVersion) || singleVersion.equals("*-*") || singleVersion.equals("*")) {
result = true;
} else if (expressionVersion.contains("-")) {
int check = 0;
String vEx1 = expressionVersion.substring(0, expressionVersion.indexOf("-"));
if (vEx1 == null || vEx1.isEmpty()) {
vEx1 = "*";
}
String vEx2 = expressionVersion.substring(expressionVersion.indexOf("-") + 1, expressionVersion.length());
if (vEx2 == null || vEx2.isEmpty()) {
vEx2 = "*";
}
result = checkVersion(vEx1, singleVersion) && checkVersion(singleVersion, vEx2);
} else {
result = singleVersion.equals(expressionVersion);
}
return result;
}
/**
* ??
* @param action
* @return
*/
private boolean isValidCondition(Action action) {
String condition = null;
if (action.getClass().equals(ActionEditFile.class)) {
ActionEditFile editfile = (ActionEditFile) action;
condition = editfile.getCondition();
} else if (action.getClass().equals(ActionCopyFile.class)) {
ActionCopyFile copyfile = (ActionCopyFile) action;
condition = copyfile.getCondition();
}
if (condition == null || condition.isEmpty()) {
return true;
}
return isValidCondition(condition);
}
/**
* findNextExpression returns the next expression from the given StringTokenizer
* @param st
* @return
*/
private String findNextExpression(String previous, StringTokenizer st) {
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (token.equalsIgnoreCase("\'")) {
String mod = token;
while (st.hasMoreTokens()) {
String next = st.nextToken();
if (next.equalsIgnoreCase("\'")) {
mod += next;
break;
}
mod += next;
}
return mod;
} else if (token.equalsIgnoreCase("(")) {
String cond = "";
boolean done = false;
int level = 0;
while (!done && st.hasMoreTokens()) {
String next = st.nextToken();
if (next.equalsIgnoreCase("(")) {
level++;
} else if (next.equalsIgnoreCase(")")) {
if (level == 0 && !done) {
break;
} else {
level--;
}
}
cond += next;
}
return cond;
} else if (token.equalsIgnoreCase(" ")) {
continue;
} else {
String ret = "";
boolean added = false;
while (st.hasMoreTokens()) {
ret += token;
added = true;
try {
token = st.nextToken();
added = false;
} catch (Exception e) {
break;
}
}
if (!added) {
ret += token;
}
return ret;
}
}
return "";
}
/**
* checkVersion checks to see if first argument <= second argument is true
* @param lower
* @param higher
* @return
*/
public boolean checkVersion(String lower, String higher) {
// boolean ret = true;
if (lower.equalsIgnoreCase("*") || higher.equalsIgnoreCase("*")) {
return true;
}
StringTokenizer lowst = new StringTokenizer(lower, ".", false);
StringTokenizer highst = new StringTokenizer(higher, ".", false);
while (lowst.hasMoreTokens() && highst.hasMoreTokens()) {
String firsttk = lowst.nextToken();
String secondtk = highst.nextToken();
if (firsttk.contains("*") || secondtk.contains("*")) {
return true;
}
int first = Integer.parseInt(firsttk);
int second = Integer.parseInt(secondtk);
if (first < second) {
return true;
} else if (first > second) {
return false;
} else if (first == second) {
continue;
}
/*
if (ret) {
ret = (first <= second);
} else if (!ret) {
return ret;
}
*/
}
if (lowst.hasMoreTokens()) {
return false;
}
return true;
}
/**
* validVesion checks to see if the version for m is within the condition set by version on the other parameter
* in development
* @param m
* @param version
* @return
*/
public boolean validVersion(Mod m, String version) throws NumberFormatException {
String target = m.getVersion().trim();
if (version.contains("-")) {
String low, high;
low = version.substring(1, version.indexOf("-")).trim();
high = version.substring(version.indexOf("-") + 1).trim();
// checkVersion checks to see if first argument <= second argument is true
return checkVersion(low, target) && checkVersion(target, high);
} else if (version.isEmpty()) {
return true;
} else {
String compare = version.trim();
return compare.equalsIgnoreCase(target);
}
}
/**
* isValidCondition evaluates the condition string and return the result of it
* Looks like it's working now, should work ;)
* @param condition
* @return
*/
public boolean isValidCondition(String condition) {
boolean valid = true;
StringTokenizer st = new StringTokenizer(condition, "\' ()", true);
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (token.equalsIgnoreCase("\'")) {
String mod = "";
while (st.hasMoreTokens()) {
String next = st.nextToken();
if (next.equalsIgnoreCase("\'")) {
break;
}
mod += next;
}
try {
String version = "";
if (mod.endsWith("]")) {
version = mod.substring(mod.indexOf('[') + 1, mod.length() - 1);
mod = mod.substring(0, mod.indexOf('['));
}
Mod m = getMod(mod, version);
try {
valid = (m.isEnabled() && validVersion(m, version));
} catch (Exception e) {
valid = false;
}
} catch (NoSuchElementException e) {
valid = false;
}
} else if (token.equalsIgnoreCase(" ")) {
continue;
} else if (token.equalsIgnoreCase("(")) {
String cond = "";
boolean done = false;
int level = 0;
while (!done && st.hasMoreTokens()) {
String next = st.nextToken();
if (next.equalsIgnoreCase("(")) {
level++;
} else if (next.equalsIgnoreCase(")")) {
if (level == 0 && !done) {
break;
} else {
level--;
}
}
cond += next;
}
return isValidCondition(cond);
} else if (token.equalsIgnoreCase(")")) {
return false;
} else {
String next = findNextExpression(token, st);
if (token.equalsIgnoreCase("not")) {
valid = !isValidCondition(next);
} else if (token.equalsIgnoreCase("and")) {
boolean compare = isValidCondition(next);
valid = (valid && compare);
} else if (token.equalsIgnoreCase("or")) {
boolean compare = isValidCondition(next);
valid = (valid || compare);
} else {
String mod = token + " " + next;
}
}
}
return valid;
}
}