/**
* This code is part of Project Waterloo from King's College London
*
*
* Copyright King's College London 2012-
*
* @author Malcolm Lidierth, King's College London [Contact]
*
* Project Waterloo is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Project Waterloo is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
*/
package kcl.waterloo.xml;
import java.awt.BasicStroke;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.geom.Rectangle2D;
import java.beans.*;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import javax.swing.SwingWorker;
import kcl.waterloo.actions.GJEventManager;
import kcl.waterloo.defaults.GJDefaults;
import kcl.waterloo.graphics.AxisLink;
import kcl.waterloo.graphics.GJAbstractGraph;
import kcl.waterloo.graphics.GJAbstractGraphContainer;
import kcl.waterloo.graphics.GJGraphInterface;
import kcl.waterloo.graphics.GJRoi;
import kcl.waterloo.graphics.data.GJXYSeries;
import kcl.waterloo.graphics.plots2D.GJAbstractPlot;
import kcl.waterloo.widget.GJButton;
import kcl.waterloo.graphics.transforms.GJDataTransformInterface;
import kcl.waterloo.graphics.transforms.Services;
import kcl.waterloo.logging.CommonLogger;
import kcl.waterloo.plotmodel2D.GJCyclicArrayList;
import kcl.waterloo.swing.GCFrame;
import kcl.waterloo.swing.GCGridContainer;
import kcl.waterloo.swing.GCTabbedGridContainer;
/**
* A static library used to serialize all Waterloo graphics to an XML file.
*
* Uses the standard java.bean.util.XMLEncoder.
*
* Note:
*
* The singleton instance of GJDefaults is set as the owner of the XMLEncoder.
* The defaults are written to each XML file together with the appropriate
* setting for the data vector class used by plots.
*
* The matching XML decoder routine needs to save and restore the default
* settings when de-serializing the generated XML file (the supplied GJDecoder
* does this).
*
* Any changes here need to be tested for backwards compatibility.
*
* The GJDefaults also set the verbosity of exception logging: 0 (quiet) to 2
* (verbose). Exceptions are also logged to the file (verbosely) Exceptions are
* expected: e.g. when non-serializable listeners are used. They do not
* (generally) represent errors.
*
* @author Malcolm Lidierth, King's College London [Contact]
*/
public class GJEncoder {
/**
* Data written to XML files as a header
*/
static final class Header {
static final String attribution = "Waterloo Scientific Graphics: Copyright King's College London 2012-";
static final String versionString = "kcl.waterloo.xml.GJEncoder/GJDecoder version:";
static final Long majorVersion = 1L;
static final Long minorVersion = 0L;
}
/**
* Stores exceptions thrown during the last call to the {@code save}
* function as an ArrayList.
*/
static final ArrayList exceptionLog = new ArrayList();
public static final LinkedHashMap customDelegates = new LinkedHashMap();
private static final CommonLogger logger = new CommonLogger(GJEncoder.class);
/**
* Enumerated type for supported compression modes. Presently none or gzip.
*/
public enum CompressionMode {
none, gzip
}
/**
* The current compression mode.
*/
private static CompressionMode compression = CompressionMode.gzip;
/**
* Non-instantiable class providing a static library.
*/
private GJEncoder() {
}
/**
* Static method for saving Waterloo graphics objects. Presently supported
* objects on input are GCFrames, GCPanels, graph containers and graphs.
*
* @param fileName fully qualified file name as a String. Compression will
* be used if the file extension is ".gz". Otherwise, compression will be
* applied if it has been selected as the default by a call to
* setCompression.
* @param comp the component to save
*/
public static void save(final String fileName, final Object comp) {
SwingWorker worker = new SwingWorker() {
@Override
protected Void doInBackground() {
saveOnThread(fileName, comp);
//XMLChecker.check(fileNameCopy);
return null;
}
};
worker.execute();
}
public static void saveOnEDT(final String fileName, final Object comp) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
saveOnThread(fileName, comp);
}
});
}
public static void saveOnThread(final String fileName, final Object comp) {
final String fileNameCopy = setExtension(fileName);
if (comp instanceof GCFrame) {
GCFrame f = (GCFrame) comp;
if (f.getContentPane() instanceof GCGridContainer) {
gcPanelEncoder(fileNameCopy, f.getContentPane());
} else if (f.getContentPane() instanceof GCTabbedGridContainer) {
gcPanelEncoder(fileNameCopy, f.getContentPane());
} else {
gcPanelEncoder(fileNameCopy, f.getContentPane());
}
} else if (comp instanceof GCGridContainer) {
gcPanelEncoder(fileNameCopy, (GCGridContainer) comp);
} else if (comp instanceof GCTabbedGridContainer) {
gcPanelEncoder(fileNameCopy, (GCTabbedGridContainer) comp);
} else if (comp instanceof GJAbstractGraphContainer) {
containerEncoder(fileNameCopy, (GJAbstractGraphContainer) comp);
} else if (comp instanceof GJAbstractGraph) {
graphEncoder(fileNameCopy, (GJAbstractGraph) comp);
} else if (comp instanceof GJAbstractPlot) {
// Added at 1.1RC2 - support saving plots with no Swing hierarchy
GJAbstractPlot p = (GJAbstractPlot) comp;
final OutputStream out = createOutputStream(fileName);
final XMLEncoder e = new XMLEncoder(out);
logger.debug("Writing to ".concat(fileName));
writeHeader(e);
GJGraphInterface gr = p.getParentGraph();
p.setParentGraph(null);
e.writeObject(p);
p.setParentGraph(gr);
doClose(e);
try {
out.close();
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
Integer debugLevel = (Integer) GJDefaults.getMap2().get("DeveloperMode");
if (debugLevel.intValue() > 0) {
int sz = exceptionLog.size();
if (sz > 0) {
logger.warn("Debug Info: file log size = " + exceptionLog.size());
}
}
}
/**
* Creates an OutputStream for use by the encoder.
*
* @param fileName the name of the file
* @return the OutputStream
*/
private static OutputStream createOutputStream(String fileName) {
BufferedOutputStream buffer;
fileName = setExtension(fileName);
try {
buffer = new BufferedOutputStream(new java.io.FileOutputStream(fileName));
if (fileName.contains(".gz")) {
return new GZIPOutputStream(buffer);
} else {
return buffer;
}
} catch (FileNotFoundException ex) {
logger.error(ex.getMessage());
} catch (IOException ex) {
logger.error(ex.getMessage());
}
return null;
}
private static void doClose(XMLEncoder e) {
e.writeObject(exceptionLog);
e.close();
}
/**
* Sets the compression mode to be used by all output streams subsequently
* created using createOutputStream.
*
* @param mode the CompressionMode
*/
public static void setCompressionMode(CompressionMode mode) {
compression = mode;
}
/**
* Returns the current compression mode.
*
* @return the CompressionMode
*/
public static CompressionMode getCompressionMode() {
return compression;
}
/**
* Sets the compression mode to be used by all output streams subsequently
* created using createOutputStream. If set true, gzip compression mode will
* be used. If set false, no compression will be used.
*
* @param flag true to activate compression
*/
public static void setCompression(boolean flag) {
if (flag) {
compression = CompressionMode.gzip;
} else {
compression = CompressionMode.none;
}
}
/**
* Returns true if the compression mode is anything other than
* {@code CompressionMode.none}
*
* @return the flag
*/
public static boolean getCompression() {
switch (compression) {
case none:
return false;
default:
return true;
}
}
/**
* Initializes an XMLEncoder by installing the required persistence
* delegates, initializes the XMLEncoder's owner to the GJDefaults singleton
* instance, writes the header to the XML file, and saves the present
* default settings in preparation for later deserialization. Used
* internally but can be called if creating a file long-hand.
*
* Note that users can supplement/replace the persistence delegates on the
* XMLEncoder instance after calling this method.
*
* @param e the XMLEncoder instance
*/
private static void writeHeader(XMLEncoder e) {
Thread.currentThread().setContextClassLoader(GJEncoder.class.getClassLoader());
exceptionLog.clear();
addDelegates(e);
e.setOwner(GJDefaults.getInstance());
e.setExceptionListener(exHandler);
e.writeObject(Header.attribution);
e.writeObject(Header.versionString);
e.writeObject(Header.majorVersion);
e.writeObject(Header.minorVersion);
e.writeObject("Created: " + new java.util.Date());
e.writeObject(kcl.waterloo.util.Version.getVersion().toString());
e.writeObject(e.getOwner());
}
public static void setContext(XMLEncoder e) {
Thread.currentThread().setContextClassLoader(GJEncoder.class.getClassLoader());
}
private static void gcPanelEncoder(String fileName, final Container comp) {
final OutputStream out = createOutputStream(fileName);
final XMLEncoder e = new XMLEncoder(out);
writeHeader(e);
e.writeObject(createComponentHeader(comp));
e.writeObject(comp);
doClose(e);
try {
out.close();
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
private static void containerEncoder(String fileName, final GJAbstractGraphContainer comp) {
final OutputStream out = createOutputStream(fileName);
final XMLEncoder e = new XMLEncoder(out);
writeHeader(e);
containerEncoder(e, comp);
doClose(e);
try {
out.close();
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
private static void containerEncoder(XMLEncoder e, GJAbstractGraphContainer comp) {
e.writeObject(createComponentHeader(comp));
localWrite(e, comp);
}
private static void graphEncoder(String fileName, GJAbstractGraph graph) {
final OutputStream out = createOutputStream(fileName);
final XMLEncoder e = new XMLEncoder(out);
writeHeader(e);
graphEncoder(e, graph);
doClose(e);
try {
out.close();
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
private static void graphEncoder(XMLEncoder e, GJGraphInterface graph) {
e.writeObject(createComponentHeader((Component) graph));
localWrite(e, graph);
}
private static void localWrite(XMLEncoder e, Object o) {
e.writeObject(o);
}
private static LinkedHashMap createComponentHeader(Component c) {
LinkedHashMap map = new LinkedHashMap();
map.put("Dimension", c.getSize());
return map;
}
public static ArrayList getExceptionLog() {
return exceptionLog;
}
/**
* Static method that adds the required persistence delegates to an
* XMLEncoder instance.
*
* @param e the XMLEncoder
*/
public static void addDelegates(XMLEncoder e) {
// Waterloo delegates
e.setPersistenceDelegate(GJCyclicArrayList.class, e.getPersistenceDelegate(List.class));
e.setPersistenceDelegate(GradientPaint.class, new DefaultPersistenceDelegate(
new String[]{"point1", "color1", "point2", "color2"}));
e.setPersistenceDelegate(GJXYSeries.class, new DefaultPersistenceDelegate(
new String[]{"x", "y"}));
LinkedHashMap trs = Services.getAvailable();
GJDataTransformInterface[] arr = new GJDataTransformInterface[trs.size()];
arr = trs.values().toArray(arr);
for (GJDataTransformInterface arr1 : arr) {
e.setPersistenceDelegate(arr1.getClass(), transformInterfaceDelegate);
}
e.setPersistenceDelegate(GJRoi.class, new DefaultPersistenceDelegate(
new String[]{"axesRectangle"}));
e.setPersistenceDelegate(GJButton.class, new DefaultPersistenceDelegate(
new String[]{"fileName"}));
// AWT delegates
e.setPersistenceDelegate(Rectangle2D.Double.class, new DefaultPersistenceDelegate(
new String[]{"x", "y", "width", "height"}));
e.setPersistenceDelegate(BasicStroke.class, new DefaultPersistenceDelegate() {
@Override
protected Expression instantiate(Object oldInstance, Encoder out) {
BasicStroke old = (BasicStroke) oldInstance;
return new Expression(oldInstance, oldInstance.getClass(), "new",
new Object[]{old.getLineWidth(),
old.getEndCap(),
old.getLineJoin(),
old.getMiterLimit(),
old.getDashArray(),
old.getDashPhase()
});
}
});
e.setPersistenceDelegate(GJEventManager.class, new DefaultPersistenceDelegate() {
@Override
protected Expression instantiate(Object oldInstance, Encoder out) {
return new Expression(oldInstance, GJEventManager.class, "getInstance", null);
}
});
e.setPersistenceDelegate(BigDecimal.class, new DefaultPersistenceDelegate() {
@Override
protected Expression instantiate(Object oldInstance, Encoder out) {
BigDecimal bd = (BigDecimal) oldInstance;
return new Expression(oldInstance, oldInstance.getClass(),
"new",
new Object[]{bd.toString()});
}
});
e.setPersistenceDelegate(AxisLink.class, new DefaultPersistenceDelegate() {
@Override
protected Expression instantiate(Object oldInstance, Encoder out) {
AxisLink bd = (AxisLink) oldInstance;
return new Expression(oldInstance, oldInstance.getClass(),
"new",
new Object[]{bd.getPairedTarget(), bd.getPairing()});
}
});
e.setPersistenceDelegate(GJDefaults.class, new DefaultPersistenceDelegate() {
/**
* IMPORTANT: This causes the defaults to be changed to match those
* on file during de-serialization. The decoder must therefore save
* then restore the user defaults after reading a file.
*
* CAUTION: Changing this could break backwards compatibility.
*/
@Override
protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) {
Object o = ((XMLEncoder) out).getOwner();
Object[] arr = new Object[]{GJDefaults.getMap()};
// The decoder will invoke copyEntries on the singleton instance of GJDefaults
// during de-serialization
out.writeStatement(new Statement(o, "copyEntries", arr));
}
});
for (Class clzz : customDelegates.keySet()) {
e.setPersistenceDelegate(clzz, customDelegates.get(clzz));
}
}
private static String setExtension(String fileName) {
boolean requestFileCompress;
if (fileName.contains(".kclf") && !fileName.contains(".gz")) {
requestFileCompress = false;
} else if (fileName.contains(".gz")) {
requestFileCompress = true;
} else {
requestFileCompress = getCompression();
}
fileName = fileName.replaceAll(".gz", "");
int dotPos = fileName.lastIndexOf(".kclf");
if (dotPos < 0) {
fileName = fileName.concat(".kclf");
}
if (requestFileCompress) {
dotPos = fileName.lastIndexOf(".gz");
if (dotPos < 0) {
fileName = fileName.concat(".gz");
}
}
return fileName;
}
/**
* Exception log. Note that exceptions can be expected if non-serializable
* components have been added by users to graphs. This will not always
* represent a bug.
*/
private static ExceptionListener exHandler = new ExceptionListener() {
@Override
public void exceptionThrown(Exception excptn) {
String message = excptn.getMessage();
if (message.contains("Listener") || message.contains("AncestorNotifier")) {
exceptionLog.add(excptn.getCause().toString() + " " + message);
}
Integer debugLevel = (Integer) GJDefaults.getMap2().get("DeveloperMode");
switch (debugLevel.intValue()) {
case 0:
break;
case 1:
if (!(message.contains("Listener") || message.contains("AncestorNotifier")
|| message.contains("GraphMouseHandler") || message.contains("GraphCycler"))) {
logger.error(excptn.getMessage());
}
break;
case 2:
logger.error(excptn.getMessage());
break;
}
}
};
/**
* Delegate for all data transforms
*/
private static final PersistenceDelegate transformInterfaceDelegate = new PersistenceDelegate() {
@Override
protected Expression instantiate(Object oldInstance, Encoder out) {
return new Expression(oldInstance, oldInstance.getClass(), "getInstance", null);
}
};
/**
* Used to save Waterloo graphics housed in a MATLAB figure.
*
* @param fileName - the file name.
* @param compList - a list of GJGraphContainers.
* @param pos - the pixel positions of those in the MATLAB figure.
* @return a SwingWorker instance that saves the data on a
* background thread.
*/
public static SwingWorker createForMATLAB(final String fileName, final Component[] compList, final ArrayList pos) {
SwingWorker worker = new SwingWorker() {
@Override
protected Void doInBackground() {
OutputStream out = createOutputStream(fileName);
XMLEncoder e = new XMLEncoder(out);
writeHeader(e);
for (int k = compList.length - 1; k >= 0; k--) {
e.writeObject(new Separator());
LinkedHashMap map = new LinkedHashMap();
double[] positions = pos.get(k);
map.put("Bounds", new Rectangle2D.Double(positions[0], positions[1], positions[2], positions[3]));
e.writeObject(map);
containerEncoder(e, (GJAbstractGraphContainer) compList[k]);
}
doClose(e);
try {
out.close();
} catch (IOException ex) {
logger.error(ex.getMessage());
}
return null;
}
};
worker.execute();
return worker;
}
}
Ads help maintain this website.