All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.celldynamics.quimp.Serializer Maven / Gradle / Ivy

Go to download

QuimP software, a set of plugins for ImageJ to quantify spatio-temporal patterns of fluorescently labeled proteins in the cortex of moving cells.

The newest version!
package com.github.celldynamics.quimp;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.celldynamics.quimp.filesystem.IQuimpSerialize;
import com.github.celldynamics.quimp.filesystem.versions.IQconfOlderConverter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;

/**
 * Support saving and loading wrapped class to/from JSON file or string.
 * 
 * 

The Serializer class wraps provided objects and converts it to Gson together with itself. * Serializer adds fields like wrapped class name and versioning data (@link {@link QuimpVersion}) * to JSON. * *

Restored object is constructed using its constructor. If JSON file does not contain variable * available in class being restored, it will have the value assigned in constructor or null. GSon * overrides variables after they have been created in normal process of object building. Check * {@link #fromReader(Reader)} for details. * *

This serializer accepts only classes derived from IQuimpSerialize interface. Saved class is * packed in top level structure that contains version of software and wrapped class name. Exemplary * use case: SerializerTest#testLoad_1() * *

There is option to skip call afterSerialzie() or beforeSerialzie() method on class restoring * or saving To do so set {@link #doAfterSerialize} to false or {@link #doBeforeSerialize} * *

Serializer supports Since, Until tags from GSon library. User can write his own * converters executed if specified condition is met. Serializer compares version of callee tool * (provided in Serializer constructor) with trigger version returned by converter * {@link IQconfOlderConverter} and executes conversion provided by it. *

* Important: Until and Since tags are resolved using version of QCONF provided from callee * on json saving or by version read from QCONF file on its loading. Version read from JSON is also * used to decide whether apply converter or not but on load only. *

* * @author p.baniukiewicz * @param class type to be serialised * @see http://stackoverflow.com/questions/14139437/java-type-generic-as-argument-for-gson * @see com.github.celldynamics.quimp.Serializer#registerInstanceCreator(Class, Object) * @see #registerConverter(IQconfOlderConverter) */ public class Serializer implements ParameterizedType { /** * The Constant LOGGER. */ static final Logger LOGGER = LoggerFactory.getLogger(Serializer.class.getName()); /** * The gson builder. */ public transient GsonBuilder gsonBuilder; private transient Type type; /** * Indicates if afterSerialze should be called. */ protected transient boolean doAfterSerialize; /** * Indicates if {@link IQuimpSerialize#beforeSerialize()} should be called. * *

Rather for tests as {@link IQuimpSerialize#beforeSerialize()} is always required. */ protected transient boolean doBeforeSerialize; /** * Name of wrapped class, decoded from object. */ public String className; /** * Version and other information passed to serializer. Since(17.0202) */ public QuimpVersion timeStamp; /** * Date when file has been created. */ public String createdOn; /** * Wrapped object being serialized. */ public T obj; /** * Version stored in QCONF file loaded by Serialiser. * *

If class is serialised (saved) it contains version provided with constructor. This version * is * provided to GSon on loading json */ private transient Double qconfVersionToLoad; /** * Version provided form callee. * *

This version is provided to GSon on saving json. */ private transient Double qconfVersionToSave; /** * List of format converters called on every load when certain condition is met. */ private transient ArrayList> converters = new ArrayList<>(); /** * Default constructor used for restoring object. * *

Template T can not be restored during runtime thus the type of wrapped object is not known * for GSon. This is why this type must be passed explicitly to Serializer. * * @param type class type * @param version Version of framework this class is called from. */ public Serializer(final Type type, final QuimpVersion version) { doAfterSerialize = true; // by default use afterSerialize methods to restore object state doBeforeSerialize = true; gsonBuilder = new GsonBuilder(); obj = null; this.timeStamp = version; this.type = type; // fill date of creation createdOn = getCurrentDate(); } /** * Constructor used for saving wrapped class. * * @param obj Object being saved * @param version Version of framework this class is called from. */ public Serializer(final T obj, final QuimpVersion version) { doAfterSerialize = true; // by default use afterSerialize methods to restore object state doBeforeSerialize = true; gsonBuilder = new GsonBuilder(); this.type = obj.getClass(); this.obj = obj; className = obj.getClass().getSimpleName(); this.timeStamp = version; // set it as callee version if we will save json (json version is read on load only) this.qconfVersionToSave = convertStringVersion(version.getVersion()); // fill date of creation createdOn = getCurrentDate(); } /** * Save wrapped object passed in constructor as JSON file. * *

Calls {@link IQuimpSerialize#beforeSerialize()} before save. * * @param filename Name of file * @throws FileNotFoundException if problem with saving * @see com.github.celldynamics.quimp.Serializer#setPretty() * @see com.github.celldynamics.quimp.Serializer#Serializer(IQuimpSerialize, QuimpVersion) * @see com.github.celldynamics.quimp.Serializer#toString() */ public void save(final String filename) throws FileNotFoundException { String str; str = toString(); // produce json LOGGER.debug("Saving at: " + filename); PrintWriter f; f = new PrintWriter(new File(filename)); f.print(str); f.close(); } /** * Load GSon file. * * @param filename to load * @return Serializer object * @throws IOException when file can not be found * @throws JsonSyntaxException on wrong syntax * @throws JsonIOException on wrong syntax * @throws Exception any other case * @see #load(File) */ public Serializer load(final String filename) throws IOException, JsonSyntaxException, JsonIOException, Exception { File file = new File(filename); return load(file); } /** * Load wrapped object from JSON file. * *

Calls {@link IQuimpSerialize#afterSerialize()} after load. The general steps taken on GSon * load are as follows: * *

*

* * @param filename to load * @return Serialiser object * @throws IOException when file can not be found * @throws JsonSyntaxException on wrong syntax * @throws JsonIOException on wrong syntax * @throws Exception any other case * @see #fromReader(Reader) */ public Serializer load(final File filename) throws IOException, JsonSyntaxException, JsonIOException, Exception { LOGGER.debug("Loading from: " + filename.getPath()); // gather version from JSON FileReader vr = new FileReader(filename); qconfVersionToLoad = getQconfVersion(vr); vr.close(); // on duplicate to avoid problems with moving pointer FileReader f = new FileReader(filename); return fromReader(f); } /** * Restore wrapped object from JSON string. * * @param json string with json * @return Serialise object * @throws JsonSyntaxException on wrong syntax * @throws JsonIOException on wrong syntax * @throws Exception any other case * @see #fromReader(Reader) */ public Serializer fromString(final String json) throws JsonSyntaxException, JsonIOException, Exception { LOGGER.debug("Reading from string"); // gather version from JSON Reader vr = new StringReader(json); qconfVersionToLoad = getQconfVersion(vr); vr.close(); // on duplicate to avoid problems with moving pointer Reader reader = new StringReader(json); return fromReader(reader); } /** * Restore wrapped object from JSON string. * * @param reader reader that provides JSon string * @see #load(File) * * @return New instance of loaded object packed in Serializer class. returned instance has * proper (no nulls or empty strings) fields: className, createdOn, version * (and its subfields, obj) * @throws Exception from afterSerialize() method (specific to wrapped object) * @throws IOException when file can not be read * @throws JsonSyntaxException on bad file or when class has not been restored correctly * @throws JsonIOException This exception is raised when Gson was unable to read an input stream * or write to on */ public Serializer fromReader(final Reader reader) throws JsonSyntaxException, JsonIOException, Exception { // warn user if newer config is load to older QuimP if (qconfVersionToLoad > convertStringVersion(timeStamp.getVersion())) { LOGGER.info("You are trying to load config file which is in newer version" + " than software you are using. (" + qconfVersionToLoad + " vs " + convertStringVersion(timeStamp.getVersion()) + ")"); } // set version to load (read from file) gsonBuilder.setVersion(qconfVersionToLoad); Gson gson = gsonBuilder.create(); Serializer localref; localref = gson.fromJson(reader, this); verify(localref); // verification of correctness and conversion to current format if (doAfterSerialize) { localref.obj.afterSerialize(); } return localref; } /** * Perform basic verification of loaded file. * *

It verifies rather on general level for fields added by Serializer itself. More detailed * verification related to serialized class should be performed after full restoration of * wrapped object. * * @param localref object to verify * @throws JsonSyntaxException on bad file or when class has not been restored correctly */ private void verify(Serializer localref) throws JsonSyntaxException { // basic verification of loaded file, check whether some fields have reasonable values try { if (localref.obj == null || localref.className.isEmpty() || localref.createdOn.isEmpty()) { throw new JsonSyntaxException("Can not map loaded gson to class. Is it proper file?"); } convert(localref); } catch (NullPointerException | IllegalArgumentException | QuimpException np) { throw new JsonSyntaxException("Can not map loaded gson to class. Is it proper file?", np); } } /** * This method is called on load and goes through registered converters executing them. * *

Perform conversions from older version to current (newer). * * @param localref restored object * @throws QuimpException on problems with conversion * @see #registerConverter(IQconfOlderConverter) */ private void convert(Serializer localref) throws QuimpException { if (converters.isEmpty()) { return; // no converters registered } for (IQconfOlderConverter converter : converters) { // compare version loaded from file. If read version from file is smaller than returned // by converter - execute conversion if (converter.executeForLowerThan() > qconfVersionToLoad) { converter.upgradeFromOld(localref); } } } /** * This method register format converter on list of converters. * *

Registered converters are called on every object deserialisation in order that they were * registered. Converter is run when version of tool is higher than version of converter. * * @param converter converter * @see IQconfOlderConverter * @see #convert(Serializer) */ public void registerConverter(IQconfOlderConverter converter) { converters.add(converter); } /** * Convert wrapped class to JSON representation together with Serializer wrapper * *

Calls com.github.celldynamics.quimp.IQuimpSerialize.beforeSerialize() before conversion * * @return JSON string * @see com.github.celldynamics.quimp.Serializer#setPretty() */ @Override public String toString() { // set version to save (read from calee) gsonBuilder.setVersion(qconfVersionToSave); Gson gson = gsonBuilder.create(); if (obj != null && doBeforeSerialize == true) { obj.beforeSerialize(); } return gson.toJson(this); } /** * Get current date included in saved file. * * @return Formatted string with date. */ public String getCurrentDate() { Date dateNow = new Date(); SimpleDateFormat df = new SimpleDateFormat("E yyyy.MM.dd 'at' HH:mm:ss a zzz"); return df.format(dateNow); } /** * Dump object. * * @param obj object to dump (must be packed in Serializer already) * @param filename filename * @param savePretty true if use pretty format * @throws FileNotFoundException on saving problem * @see #jsonDump(Object, String, boolean) * @deprecated It does not support GSon versioning */ @Deprecated static void jsonDump(final Object obj, final String filename, boolean savePretty) throws FileNotFoundException { File file = new File(filename); Serializer.jsonDump(obj, file, savePretty); } /** * Performs pure dump of provided object without packing it into super class * *

Warning * *

This method does not call beforeSerialize(). It must be called explicitly before dumping. * Can be used for saving already packed objects * * @param obj to dump * @param filename to be saved under * @param savePretty if \a true use pretty format * @throws FileNotFoundException when file can not be created * @deprecated It does not support GSon versioning */ @Deprecated static void jsonDump(final Object obj, final File filename, boolean savePretty) throws FileNotFoundException { GsonBuilder gsonBuilder = new GsonBuilder(); if (savePretty) { gsonBuilder.setPrettyPrinting(); } Gson gson = gsonBuilder.create(); if (obj != null) { String str = gson.toJson(obj); PrintWriter f; f = new PrintWriter(filename); f.print(str); f.close(); } } /** * Sets pretty JSON formatting on save operation. * * @see com.github.celldynamics.quimp.Serializer#toString() * @see com.github.celldynamics.quimp.Serializer#save(String) */ public void setPretty() { gsonBuilder.setPrettyPrinting(); } /** * Read QuimP version from QCONF file. * *

It does not deserialize JSON, just plain string reading from file. * * @param reader reader that delivers string * @return Version string encoded as double. Any -SNAPSHOT suffix is removed. Return 0.0 on * error. * @throws JsonSyntaxException on version read error */ public Double getQconfVersion(Reader reader) { // key to look for final String versionKey = "\"version\""; char[] buf = new char[256]; try { reader.read(buf); } catch (IOException e) { throw new JsonSyntaxException("JSON string can not be read", e); } String sbuf = new String(buf); LOGGER.trace("Header: " + sbuf); int pos = sbuf.indexOf(versionKey); if (pos < 0) { throw new JsonSyntaxException("JSON file does not contain version tag"); } pos = sbuf.indexOf("\"", pos + versionKey.length()); int pos2 = sbuf.indexOf("\"", pos + 1); String version = sbuf.substring(pos + 1, pos2); Double ret = convertStringVersion(version); return ret; } /** * Convert string in format a.b.c-SNAPSHOT to double a.bc * * @param ver String version to convert * @return Double representation of version string * @throws JsonSyntaxException on wrong conversions (due to e.g. wrong wile read or bad * structure) */ private Double convertStringVersion(String ver) { String ret; try { // remove "" and other stuff ret = ver.replaceAll("([ \",]|-SNAPSHOT)", ""); int dotcount = ret.length() - ret.replace(".", "").length(); if (dotcount > 2) { throw new JsonSyntaxException("Format of version string must follow rule major.minor.inc"); } if (dotcount == 2) { int seconddotpos = ret.lastIndexOf('.'); ret = ret.substring(0, seconddotpos) + ret.substring(seconddotpos + 1); } return new Double(ret); } catch (NumberFormatException ex) { throw new JsonSyntaxException("Version string could not be converted to number", ex); } } /** * Register constructor for wrapped class. * *

It may be necessary during loading JSON file if wrapped class needs some parameters to * restore its state on com.github.celldynamics.quimp.IQuimpSerialize.afterSerialize() call and * those * parameters are passed in constructor. * * @param type Type of class * @param typeAdapter Wrapped object builder that implements InstanceCreator interface. * @see com.github.celldynamics.quimp.filesystem.IQuimpSerialize#afterSerialize() * @see https://github.com/google/gson/blob/master/UserGuide.md#TOC-InstanceCreator-for-a-Parameterized-Type */ public void registerInstanceCreator(Class type, Object typeAdapter) { gsonBuilder.registerTypeAdapter(type, typeAdapter); } /* * (non-Javadoc) * * @see java.lang.reflect.ParameterizedType#getActualTypeArguments() */ @Override public Type[] getActualTypeArguments() { return new Type[] { type }; } /* * (non-Javadoc) * * @see java.lang.reflect.ParameterizedType#getRawType() */ @Override public Type getRawType() { return Serializer.class; } /* * (non-Javadoc) * * @see java.lang.reflect.ParameterizedType#getOwnerType() */ @Override public Type getOwnerType() { return null; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy