/*******************************************************************************
* Copyright (c) 2009 the CHISEL group and contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Del Myers - initial API and implementation
*******************************************************************************/
package ca.uvic.chisel.javasketch.ui.internal.presentation.metadata;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Properties;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.ui.handlers.IHandlerService;
import ca.uvic.chisel.javasketch.IProgramSketch;
import ca.uvic.chisel.javasketch.SketchPlugin;
import ca.uvic.chisel.javasketch.data.model.IActivation;
import ca.uvic.chisel.javasketch.data.model.IThread;
import ca.uvic.chisel.javasketch.data.model.ITrace;
import ca.uvic.chisel.javasketch.data.model.ITraceModel;
import ca.uvic.chisel.javasketch.data.model.ITraceModelProxy;
import ca.uvic.chisel.javasketch.internal.DBProgramSketch;
import ca.uvic.chisel.javasketch.internal.ast.groups.ASTMessageGroupingTree;
/**
* A class that stores presentation information for a java sketch.
* @author Del Myers
*
*/
public class PresentationData {
private static HashMap<IProgramSketch, PresentationData> cache = new HashMap<IProgramSketch, PresentationData>();
private Properties annotations;
private IProgramSketch sketch;
private int connections;
private boolean isCompressingLoops = true;
private long timestamp;
private PresentationData(IProgramSketch sketch) {
this.sketch = sketch;
annotations = new Properties();
}
public static PresentationData connect(IProgramSketch sketch) {
if (sketch == null) return null;
PresentationData data;
synchronized (cache) {
data = cache.get(sketch);
if (data == null) {
data = new PresentationData(sketch);
cache.put(sketch, data);
}
}
data.connect();
return data;
}
private synchronized void connect() {
connections++;
if (connections == 1) {
try {
load();
} catch (Exception e) {
SketchPlugin.getDefault().log(e);
}
}
}
public synchronized void disconnect() {
connections--;
if (connections == 0) {
try {
save();
} catch (Exception e) {
SketchPlugin.getDefault().log(e);
}
remove();
}
}
protected void finalize() throws Throwable {
//save();
};
/**
*
*/
private void remove() {
synchronized (cache) {
cache.remove(getSketch());
}
}
/**
* Checks to see whether the given activation is expanded in the presentation.
* @param activation the activation to query.
*/
public synchronized boolean isExpanded(IActivation activation) {
ThreadData td = getThreadData(activation.getArrival().getThread());
return td.isExpanded(activation);
}
public synchronized ASTMessageGroupingTree getGroups(IActivation activation) {
ActivationData ad = getActivationData(activation);
return ad.getGroups();
}
public synchronized boolean isGroupVisible(IActivation activation, ASTMessageGroupingTree node) {
if (!isCompressingLoops) {
return true;
}
ActivationData ad = getActivationData(activation);
return ad.isGroupVisible(node);
}
public synchronized boolean isGroupEmpty(IActivation activation, ASTMessageGroupingTree node) {
ActivationData ad = getActivationData(activation);
return ad.isGroupEmpty(node);
}
/**
* Swaps out the visibility of one iteration of a loop for the one represented by the given node.
* Only one iteration of a loop is visible at a time when this method is used.
* @param activation
* @param node
*/
public synchronized void swapLoop(IActivation activation, ASTMessageGroupingTree node, boolean firstNonEmpty) {
ActivationData ad = getActivationData(activation);
ad.swapLoop(node, firstNonEmpty);
}
/**
* @param activation
* @return
*/
private ActivationData getActivationData(IActivation activation) {
ThreadData td = getThreadData(activation.getArrival().getThread());
return td.getActivationData(activation.getIdentifier());
}
/**
* @param thread
* @return
*/
private synchronized ThreadData getThreadData(IThread thread) {
validate();
return new ThreadData(this, thread.getID(), thread.getIdentifier());
}
/**
* @return the sketch
*/
public IProgramSketch getSketch() {
return sketch;
};
public synchronized void save() throws IOException {
File path = getPresentationPath();
if (path == null || ! path.exists()) return;
FileOutputStream output = new FileOutputStream(new File(path, "metadata"));
Properties metaProperties = new Properties();
metaProperties.put("isCompressingLoops", isCompressingLoops + "");
metaProperties.put("timestamp", timestamp + "");
try {
metaProperties.store(output, "");
} finally {
output.close();
}
output = new FileOutputStream(new File(path, "annotations"));
try {
annotations.store(output, "");
} finally {
output.close();
}
}
private synchronized void erase() {
File root = getPresentationPath();
LinkedList<File> pathsToDelete = new LinkedList<File>();
LinkedList<File> filesToDelete = new LinkedList<File>();
if (root.exists()) {
//delete all the files in the path
filesToDelete.add(root);
while (filesToDelete.size() > 0) {
File f = filesToDelete.removeFirst();
if (f.isDirectory()) {
pathsToDelete.addFirst(f);
for (File child : f.listFiles()) {
filesToDelete.addLast(child);
}
} else {
f.delete();
}
}
}
for (File path : pathsToDelete) {
path.delete();
}
}
private synchronized void load() throws IOException {
File f = getPresentationPath();
if (f.exists() && !f.isDirectory()) {
if (!f.delete()) {
throw new IOException("Could not create new presentation path");
}
}
if (!f.exists()) {
if (!f.mkdirs()) {
throw new IOException("Could not create new presentation path");
}
}
this.timestamp = System.currentTimeMillis();
if (f != null && f.exists()) {
File metadata = new File(f, "metadata");
if (!metadata.exists()) {
Properties props = new Properties();
props.put("isCompressingLoops", "true");
props.put("timestamp", "" + timestamp);
FileOutputStream os = new FileOutputStream(metadata);
try {
props.store(os, "");
} finally {
os.close();
}
}
File annotationsFile = new File(f, "annotations");
if (annotationsFile.exists()) {
FileInputStream fis = new FileInputStream(annotationsFile);
try {
annotations.load(fis);
} finally {
fis.close();
}
}
//load the metadata
Properties p = new Properties();
FileInputStream fis = new FileInputStream(metadata);
try {
p.load(fis);
} finally {
fis.close();
}
this.isCompressingLoops = Boolean.parseBoolean(p.getProperty("isCompressingLoops", "true"));
this.timestamp = Long.parseLong(p.getProperty("timestamp", "0"));
}
}
File getPresentationPath() {
if (sketch instanceof DBProgramSketch) {
URL tracePath = sketch.getTracePath();
try {
File path = new File(tracePath.toURI());
File f = new File(path, ".presentation");
return f;
} catch (URISyntaxException e) {
}
}
return null;
}
/**
* @param activationElement
* @param grouping
* @param b
*/
public synchronized void setGroupExpanded(IActivation activation,
ASTMessageGroupingTree grouping, boolean expanded) {
validate();
ActivationData ad = getActivationData(activation);
if (ad != null) {
ad.setGroupExpanded(grouping, expanded);
}
}
public void setAnnotation(ITraceModel element, String annotation) {
if (element != null && annotation != null) {
try {
IHandlerService hService =
(IHandlerService) SketchPlugin.getDefault().getWorkbench().getService(IHandlerService.class);
if (hService != null) {
hService.executeCommand("ca.uvic.chisel.javasketch.annotate", null);
}
} catch (ExecutionException e) {}
catch (NotDefinedException e) {}
catch (NotEnabledException e) {}
catch (NotHandledException e) {}
annotations.put(element.getIdentifier(), annotation);
}
}
public String getAnnotation(ITraceModel element) {
if (element != null) {
return annotations.getProperty(element.getIdentifier());
}
return null;
}
public String getAnnotation(String elementId) {
if (elementId != null) {
return annotations.getProperty(elementId);
}
return null;
}
public ITraceModelProxy[] getAnnotatedElements() {
LinkedList<ITraceModelProxy> proxies = new LinkedList<ITraceModelProxy>();
for (Object id : annotations.keySet()) {
ITrace trace = sketch.getTraceData();
ITraceModelProxy proxy = trace.getElement(id.toString());
if (proxy != null) {
proxies.add(proxy);
}
}
return proxies.toArray(new ITraceModelProxy[proxies.size()]);
}
/**
* @param element
* @param b
*/
public synchronized void setActivationExpanded(IActivation element, boolean expanded) {
validate();
try {
ThreadData td = getThreadData(element.getArrival().getThread());
if (td != null) {
td.setActivationExpanded(element, expanded);
}
} catch (NullPointerException e){}
}
/**
* @param thread
* @param rootActivation
*/
public synchronized void setThreadRoot(IThread thread, IActivation rootActivation) {
ThreadData td = getThreadData(thread);
td.setRoot(rootActivation);
}
/**
* @param activation
* @param node
* @return
*/
public synchronized boolean isGroupExpanded(IActivation activation,
ASTMessageGroupingTree node) {
ActivationData ad = getActivationData(activation);
return ad.isGroupExpanded(node);
}
/**
* Validates the presentation to check to see whether the trace has been reset. If it has, then
* the presentation is invalid, and all the visible activations must be redone.
* @throws IOException
*/
private void validate() {
Date date = getSketch().getTraceData().getDataTime();
if (date.after(new Date(timestamp))) {
erase();
try {
load();
} catch (IOException e) {
SketchPlugin.getDefault().log(e);
}
}
}
}