package com.laytonsmith.core;
import com.laytonsmith.PureUtilities.Common.FileUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONValue;
/**
* This class performs a check to see if this is an upgrade, and if so,
* can be used to perform various upgrades. The upgrade log is updated with
* information about upgrade history, so upgrade actions are not performed
* unnecessarily.
*
* In general, this class is used in one of two ways. The first way is the ideal
* way, and should be always be preferred in all cases where it can be done. The second
* way is a universal fallback, for if the preferred method can't be done for
* whatever reason.
*
* Ideally, you will do feature detection, to see if an upgrade routine needs to
* be performed. This is done by checking the status of the existing setup, and if
* it is in a particular state (the old state), returning true from doRun, then
* running the actual upgrade task in the run method.
*
* Sometimes this is too difficult to do, or it would be ambiguous as to whether
* or not the upgrade has been run previously. In this case, you can check for
* breadcrumbs that may have been left in a previous run. If the breadcrumbs don't
* exist, then you still need to do some amount of detection to ensure that this
* isn't the first run, but other than that, you can assume that if the breadcrumb
* is missing, you should run the upgrade task. Then, at the end of the upgrade task,
* you must be sure to leave the breadcrumb. There are protected methods in
* {@link UpgradeTask} to assist with this, which manage actually persisting the
* breadcrumbs with the installation.
*
*/
public class UpgradeLog {
private final File logFile;
private final List<UpgradeTask> tasks;
private final List<Upgrade> upgrades = new ArrayList<Upgrade>();
/**
* Creates a new UpgradeLog object.
* @param logFile The location of the upgrade log. This doesn't need to yet
* exist, but it does need to be createable as a file.
*/
public UpgradeLog(File logFile) {
this.logFile = logFile;
this.tasks = new ArrayList<UpgradeTask>();
}
/**
* Adds an upgrade task. This task will be run only if the task's
* doRun() method returns true.
* @param task
*/
public void addUpgradeTask(UpgradeTask task) {
tasks.add(task);
task.that = this;
}
/**
* Runs the required upgrade tasks. Tasks are run sequentially, from oldest to newest
* version tasks, starting with the last version that was run.
* @throws java.io.IOException If the output log can't be written to.
*/
public void runTasks() throws IOException{
if(logFile.exists()){
List<Map<String, String>> jsonUpgrades = (List<Map<String, String>>)JSONValue.parse(FileUtil.read(logFile));
for(Map<String, String> m : jsonUpgrades){
upgrades.add(Upgrade.fromMap(m));
}
}
for(UpgradeTask task : tasks){
if(task.doRun()){
task.run();
}
}
List<Map<String, String>> jsonUpgrades = new ArrayList<Map<String, String>>();
for(Upgrade u : upgrades){
jsonUpgrades.add(u.toMap());
}
String newJSON = JSONValue.toJSONString(jsonUpgrades);
FileUtil.write(newJSON, logFile);
}
public static abstract class UpgradeTask implements Runnable {
UpgradeLog that = null;
/**
* Checks to see if a previous task has left a breadcrumb.
* See {@link UpgradeTask#leaveBreadcrumb(java.lang.String)} for
* more information about breadcrumbs.
* @param breadcrumb
* @return
*/
protected boolean hasBreadcrumb(String breadcrumb){
for(Upgrade u : that.upgrades){
if(u.breadcrumb.equals(breadcrumb)){
return true;
}
}
return false;
}
/**
* This method should return true if the associated upgrade task
* should be run, or false if not. If true is returned, run() will
* be called. If false, run() will not be called.
* @return
*/
public abstract boolean doRun();
/**
* Leaves a breadcrumb. A breadcrumb should be a unique
* name, which can be used by future upgrade detection
* algorithms to determine if this task has run or not.
* @param breadcrumb
*/
protected void leaveBreadcrumb(String breadcrumb){
Upgrade u = new Upgrade();
u.breadcrumb = breadcrumb;
u.upgradeTime = System.currentTimeMillis();
that.upgrades.add(u);
}
}
private static class Upgrade {
String breadcrumb;
long upgradeTime;
public static Upgrade fromMap(Map<String, String> map){
Upgrade u = new Upgrade();
u.breadcrumb = map.get("breadcrumb");
u.upgradeTime = Long.parseLong(map.get("upgradeTime"));
return u;
}
public Map<String, String> toMap(){
Map<String, String> map = new HashMap<String, String>();
map.put("breadcrumb", breadcrumb);
map.put("upgradeTime", Long.toString(upgradeTime));
return map;
}
}
}