package ui.controller; import java.awt.Color; import java.awt.Point; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.archivers.ArchiveOutputStream; import org.apache.commons.compress.archivers.ArchiveStreamFactory; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.utils.IOUtils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import TypeAdapter.AbstractCpsObjectAdapter; import TypeAdapter.ColorAdapter; import TypeAdapter.PositionAdapter; import classes.AbstractCpsObject; import classes.Category; import classes.CpsEdge; import classes.CpsUpperNode; import classes.HolonElement; import classes.HolonObject; import classes.HolonSwitch; import classes.IdCounter; import classes.IdCounterElem; import classes.Position; import classes.TrackedDataSet; import ui.model.Model; import ui.view.StatisticGraph; import ui.view.StatisticGraphPanel; /** * Controller for Storage. * * @author Gruppe14 */ public class SaveController { public enum MODE { COMPLETE, PARTIAL, CATEGORY } public enum TYPE { CATEGORY, CANVAS } public enum EDGETYPE { CANVAS, CONNECTION, NODE, OLD, LAYER } public enum NUMTYPE { CATEGORY, OBJECT, ELEMENT, EDGE, CONNECTION, NODEEDGE, OLDEDGE, UNITGRAPH, STATSGRAPH } public enum GRAPHTYPE { SWITCH, ELEMENT } private Model model; private int nCat, nObj, nEle, nEdge, nConn, nNodeEdge, nOldEdge, nUnitGraph, nStatsGraph; /** * Constructor. * * @param model * the Model */ public SaveController(Model model) { this.model = model; } /** * Writes the current State of the Modelling into a JSON File which can be * loaded. * * @param path * the Path * * @throws IOException * exception */ public void writeSave(String path) throws IOException, ArchiveException { File dst = new File(path); File holonFile = File.createTempFile("tmp", ".json"); holonFile.deleteOnExit(); dst.delete(); OutputStream output = new FileOutputStream(dst); ArchiveOutputStream stream = new ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, output); initNumeration(); JsonObject file = new JsonObject(); initialize(MODE.COMPLETE, file); storeCategory(file); storeCanvas(file); storeStatistics(file); storeData(stream); FileWriter writer = new FileWriter(holonFile); writer.write(model.getGson().toJson(file)); writer.flush(); writer.close(); addFileToSave(holonFile, stream, false); stream.finish(); output.close(); } /** * Writes the Autosave File. * * @param path * the Path * @throws IOException * Exception */ public void writeAutosave(String path) throws IOException { initNumeration(); JsonObject file = new JsonObject(); initialize(MODE.PARTIAL, file); storeCanvas(file); FileWriter writer = new FileWriter(path); writer.write(model.getGson().toJson(file)); writer.flush(); writer.close(); } /** * Writes the Category File in Case of Changes * * @param path * @throws IOException */ public void writeCategory(String path) throws IOException { initNumeration(); JsonObject file = new JsonObject(); initialize(MODE.CATEGORY, file); storeCategory(file); FileWriter writer = new FileWriter(path); writer.write(model.getGson().toJson(file)); writer.flush(); writer.close(); } /** * Write needed default parameter into the JsonObject. Can be extended later * on * * @param mode * @param file */ private void initialize(MODE mode, JsonObject file) { // TODO Auto-generated method stub switch (mode) { case COMPLETE: file.add("MODE", new JsonPrimitive(mode.name())); file.add("IDCOUNTER", new JsonPrimitive(IdCounter.getCounter())); file.add("IDCOUNTERELEMENT", new JsonPrimitive(IdCounterElem.getCounter())); file.add("CANVAS_SIZE_X", new JsonPrimitive(model.getCanvasX())); file.add("CANVAS_SIZE_Y", new JsonPrimitive(model.getCanvasY())); break; case PARTIAL: file.add("MODE", new JsonPrimitive(mode.name())); file.add("IDCOUNTER", new JsonPrimitive(IdCounter.getCounter())); file.add("IDCOUNTERELEMENT", new JsonPrimitive(IdCounterElem.getCounter())); file.add("CANVAS_SIZE_X", new JsonPrimitive(model.getCanvasX())); file.add("CANVAS_SIZE_Y", new JsonPrimitive(model.getCanvasY())); break; case CATEGORY: file.add("MODE", new JsonPrimitive(mode.name())); default: break; } } /** * Store all Categories and Object into a Json File via Serialization * * @param file */ private void storeCategory(JsonObject file) { // TODO Auto-generated method stub // forall categories store them into the jsontree for (Category cat : model.getCategories()) { String key = "CATEGORY" + getNumerator(NUMTYPE.CATEGORY); file.add(key, new JsonPrimitive(cat.getName())); // forall object in the category store them into the jsontree for (AbstractCpsObject obj : cat.getObjects()) { file.add("CGOBJECT" + getNumerator(NUMTYPE.OBJECT), model.getGson().toJsonTree(obj, AbstractCpsObject.class)); // if its a holonobject add elements too if (obj instanceof HolonObject) elementsToJson(TYPE.CATEGORY, file, obj); } } } /** * Travers through all Objects via BFS and Stores everything relevant * * @param file */ private void storeCanvas(JsonObject file) { // TODO Auto-generated method stub ArrayDeque queue = new ArrayDeque<>(); AbstractCpsObject u = null; // put all objects into queue since there is not starting object for (AbstractCpsObject cps : model.getObjectsOnCanvas()) { queue.add(cps); } // while quene not empty while (!queue.isEmpty()) { // u = current node u = queue.pop(); // add currentnode into jsontree String key = "CVSOBJECT" + getNumerator(NUMTYPE.OBJECT); file.add(key, model.getGson().toJsonTree(u, AbstractCpsObject.class)); // and its connections too edgeToJson(EDGETYPE.CONNECTION, file, u.getId(), u.getConnections()); // if holonobject elements too if (u instanceof HolonObject) elementsToJson(TYPE.CANVAS, file, u); // if switch graphpoints too if (u instanceof HolonSwitch) if (((HolonSwitch) u).getGraphPoints().size() != 0) unitgraphToJson(GRAPHTYPE.SWITCH, file, u.getId(), ((HolonSwitch) u).getGraphPoints()); // if uppernode put all nodes inside the uppernode into queue if (u instanceof CpsUpperNode) { for (AbstractCpsObject adjacent : ((CpsUpperNode) u).getNodes()) { queue.add(adjacent); } // dont forget to add the nodeedges and oldedges edgeToJson(EDGETYPE.NODE, file, u.getId(), ((CpsUpperNode) u).getNodeEdges()); edgeToJson(EDGETYPE.OLD, file, u.getId(), ((CpsUpperNode) u).getOldEdges()); } } // lastly add canvasedges into json edgeToJson(EDGETYPE.CANVAS, file, 0, model.getEdgesOnCanvas()); } private void storeStatistics(JsonObject file) { trackedToJson(file); statisticgraphToJson(file); } /** * Save wanted Data * * @param stream * @throws IOException */ private void storeData(ArchiveOutputStream stream) throws IOException { // TODO Auto-generated method stub File images = new File(System.getProperty("user.home") + "/.config/HolonGUI/Images"); File background = new File(System.getProperty("user.home") + "/.config/HolonGUI/BackgroundImages"); addFilesToSave(images, stream); addFilesToSave(background, stream); } /** * Stores Category or Canvas Elements into Json * * @param type * @param gson * @param file * @param obj */ public void elementsToJson(TYPE type, JsonObject file, AbstractCpsObject obj) { // TODO Auto-generated method stub JsonObject temp = new JsonObject(); String key = null; // forall elements store them into json and include the id of the object for (HolonElement ele : ((HolonObject) obj).getElements()) { temp.add("properties", model.getGson().toJsonTree(ele)); temp.add("ID", new JsonPrimitive(obj.getId())); // switch for deciding the key switch (type) { case CANVAS: key = "CVSELEMENT" + getNumerator(NUMTYPE.ELEMENT); break; case CATEGORY: key = "CGELEMENT" + getNumerator(NUMTYPE.ELEMENT); break; default: break; } file.add(key, model.getGson().toJsonTree(temp)); // if there are gps add them into if (ele.getGraphPoints().size() != 0) unitgraphToJson(GRAPHTYPE.ELEMENT, file, ele.getId(), ele.getGraphPoints()); temp = new JsonObject(); } } /** * Put the UnitGraphs of Switches or Elements into Json * * @param ele */ public void unitgraphToJson(GRAPHTYPE type, JsonObject file, int id, LinkedList graph) { JsonObject temp = new JsonObject(); String key = null; // forall points add them for (int i = 0; i < graph.size(); i++) { temp.add("" + i, new JsonPrimitive(graph.get(i).x + ":" + graph.get(i).y)); } // decide key switch (type) { case SWITCH: key = "SWUNITGRAPH" + getNumerator(NUMTYPE.UNITGRAPH); break; case ELEMENT: key = "ELEUNITGRAPH" + getNumerator(NUMTYPE.UNITGRAPH); break; default: break; } // add id of element so it can be found again temp.add("ID", new JsonPrimitive(id)); file.add(key, model.getGson().toJsonTree(temp)); } /** * Canvas-Edge, Connections, Node-Edge and Old-Edges to json * * @param type * @param file * @param id * @param arr */ public void edgeToJson(EDGETYPE type, JsonObject file, int id, ArrayList arr) { // TODO Auto-generated method stub String k = null; boolean b = false; JsonObject temp = new JsonObject(); for (CpsEdge edge : arr) { // add properties and only the ids from a and b temp.add("properties", model.getGson().toJsonTree(edge)); temp.add("A", new JsonPrimitive(edge.getA().getId())); temp.add("B", new JsonPrimitive(edge.getB().getId())); // Key and occasionally the id of Uppernode switch (type) { case CANVAS: k = "CVSEDGE" + getNumerator(NUMTYPE.EDGE); break; case CONNECTION: k = "CONNEDGE" + getNumerator(NUMTYPE.CONNECTION); break; case NODE: temp.add("ID", new JsonPrimitive(id)); k = "NODEEDGE" + getNumerator(NUMTYPE.NODEEDGE); break; case OLD: temp.add("ID", new JsonPrimitive(id)); k = "OLDEDGE" + getNumerator(NUMTYPE.OLDEDGE); break; default: break; } // lookup if the CVS, NODE or OLDEDGE are also connections if (edge.getA().getConnections().contains(edge) && edge.getA().getConnections().contains(edge) && !type.equals(EDGETYPE.CONNECTION)) b = true; temp.add("connection", new JsonPrimitive(b)); file.add(k, model.getGson().toJsonTree(temp)); temp = new JsonObject(); } } private void trackedToJson(JsonObject file) { // TODO Auto-generated method stub JsonObject temp = new JsonObject(); String key = "TRACKED"; // forall points add them for (int i = 0; i < model.getTrackingObj().size(); i++) { temp.add("" + i, new JsonPrimitive(model.getTrackingObj().get(i).getId())); } file.add(key, model.getGson().toJsonTree(temp)); } private void statisticgraphToJson(JsonObject file) { // TODO Auto-generated method stub JsonObject temp = new JsonObject(); List keys = model.getGraphTable().keySet().stream().collect(Collectors.toCollection(ArrayList::new)); for (String k : keys) { JsonObject dataSet = new JsonObject(); for (int i = 0; i < model.getGraphTable().get(k).getStatGraph().getDataSets().size(); i++) { TrackedDataSet set = model.getGraphTable().get(k).getStatGraph().getDataSets().get(i); AbstractCpsObject cps = set.getCpsObject(); dataSet.add("ID", (cps == null ? null : new JsonPrimitive(cps.getId()))); dataSet.add("COLOR", model.getGson().toJsonTree(set.getColor(), Color.class)); dataSet.add("PROPERTY", new JsonPrimitive(set.getProperty())); temp.add("" + i, model.getGson().toJsonTree(dataSet)); dataSet = new JsonObject(); } temp.add("KEY", new JsonPrimitive(k)); file.add("STATSGRAPH" + getNumerator(NUMTYPE.STATSGRAPH), model.getGson().toJsonTree(temp)); temp = new JsonObject(); } } /** * Differs Between Single file or whole Directory to Store * * @param src * @param stream * @throws IOException */ private void addFilesToSave(File src, ArchiveOutputStream stream) throws IOException { if (!src.exists()) return; ArrayList files = new ArrayList<>(); files.addAll(Arrays.asList(src.listFiles())); for (File file : files) { if (file.isDirectory()) addFilesToSave(file, stream); else addFileToSave(file, stream, true); } } /** * Add a File into the Archive * * @param src * @param stream * @param dir * @throws IOException */ private void addFileToSave(File src, ArchiveOutputStream stream, boolean dir) throws IOException { String entryName = (dir == true ? src.getCanonicalPath() : src.getName()); entryName = checkOS(entryName); ZipArchiveEntry entry = new ZipArchiveEntry(entryName); stream.putArchiveEntry(entry); BufferedInputStream input = new BufferedInputStream(new FileInputStream(src)); IOUtils.copy(input, stream); input.close(); stream.closeArchiveEntry(); } private String checkOS(String entryName) { // TODO Auto-generated method stub String os = System.getProperty("os.name").toLowerCase(); String ret = entryName; String partition = null; if (os.contains("windows")) { partition = ret.substring(0, ret.indexOf(":") + 1); ret = ret.replace(System.getProperty("user.home") + "\\.config\\HolonGUI\\", ""); ret = ret.replace(partition, ""); } if (os.contains("mac")) { // dosmth ret = ret.replace(System.getProperty("user.home") + "/.config/HolonGUI/", ""); } if (os.contains("linux")) { // dosmth ret = ret.replace(System.getProperty("user.home") + "/.config/HolonGUI/", ""); } if (os.contains("solaris")) { // dosmth } return ret; } /** * Just initialize the Numerators for the Json Keys. Maybe bad Style.. */ public void initNumeration() { this.nCat = this.nObj = this.nEle = this.nEdge = this.nConn = this.nNodeEdge = this.nOldEdge = this.nStatsGraph = 0; } /** * Get the wanted numerator and increment it * * @param type * @return */ public int getNumerator(NUMTYPE type) { switch (type) { case CATEGORY: return nCat++; case OBJECT: return nObj++; case ELEMENT: return nEle++; case EDGE: return nEdge++; case CONNECTION: return nConn++; case NODEEDGE: return nNodeEdge++; case OLDEDGE: return nOldEdge++; case UNITGRAPH: return nUnitGraph++; case STATSGRAPH: return nStatsGraph++; default: break; } return -1; } }