GenericXStreamService.java

// @formatter:off
/*******************************************************************************
 *
 * This file is part of JMad.
 * 
 * Copyright (c) 2008-2011, CERN. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 ******************************************************************************/
// @formatter:on

package cern.accsoft.steering.jmad.util.xml;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.Map;

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

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.xml.XppDriver;

public abstract class GenericXStreamService<T> implements PersistenceService<T> {

	private static final Logger LOGGER = LoggerFactory.getLogger(GenericXStreamService.class);

	/** the original xstream xml-converter (singleton) */
	private final XStream xStream;
	private final HierarchicalStreamDriver hierarchicalStreamDriver = new XppDriver();

	public GenericXStreamService() {
		super();
		this.xStream = createXStream();
	}

	/**
	 * This method must be implemented by subclass and has to return a properly
	 * configured XStream object.
	 * 
	 * @return a fully configured xstream object
	 */
	protected abstract XStream createXStream();

	/**
	 * Has to return the class which can be saved by this service. This is used for
	 * checking and producing an error message, if an object cannot be saved.
	 * 
	 * @return The class which can be saved by this persistence service
	 */
	protected abstract Class<? extends T> getSaveableClass();

	public synchronized final File save(T object, File destFile, Map<?, ?> context) throws PersistenceServiceException {
		if (!(getSaveableClass().isInstance(object))) {
			LOGGER.error(
					"can only save model definitions of type '" + getSaveableClass().getCanonicalName() + "' to file.");
			return null;
		}

		File file = ensureCorrectFileExtension(destFile);

		try {
			FileWriter writer = new FileWriter(file);
			writer.write(toXML(object, context));
			writer.close();
		} catch (Exception ex) {
			throw new PersistenceServiceException("Error writing Object [" + object.toString() + "] of Class ["
					+ object.getClass().getCanonicalName() + "] to XmlFile [" + file.getAbsolutePath() + "]", ex);
		}

		return file;
	}

	@Override
	public synchronized final File save(T object, File destFile) throws PersistenceServiceException {
		return save(object, destFile, null);
	}

	/**
	 * adds the correct extension for jmad-model files to the file name if not
	 * already there.
	 * 
	 * @param file
	 *            the file which shall be a correct jmad xml file
	 * @return the file
	 */
	private File ensureCorrectFileExtension(File file) {
		if (isCorrectFileName(file.getName())) {
			return file;
		} else {
			return new File(file.getAbsolutePath() + getFileExtension());
		}
	}

	/**
	 * determines if the given name is a correct file name
	 * 
	 * @param fileName
	 *            the file name to check
	 * @return true if it is an xml file name, false if not
	 */
	@Override
	public final boolean isCorrectFileName(String fileName) {
		return fileName.toLowerCase().endsWith(getFileExtension());
	}

	private String toXML(T object, Map<?, ?> context) {
		DataHolder dataHolder = xStream.newDataHolder();
		if (context != null) {
			context.entrySet().forEach(e -> dataHolder.put(e.getKey(), e.getValue()));
		}
		StringWriter writer = new StringWriter();
		xStream.marshal(object, hierarchicalStreamDriver.createWriter(writer), dataHolder);
		return writer.toString();
	}

	public void save(T object, OutputStream outStream, Map<?, ?> context) throws PersistenceServiceException {
		try {
			OutputStreamWriter writer = new OutputStreamWriter(outStream);
			writer.write(toXML(object, context));
			writer.flush();
		} catch (IOException e) {
			throw new PersistenceServiceException("Error writing Object [" + object.toString() + "] of Class ["
					+ object.getClass().getCanonicalName() + "] to output stream.", e);
		}
	}

	@Override
	public void save(T object, OutputStream outStream) throws PersistenceServiceException {
		save(object, outStream, null);
	}

	@Override
	@SuppressWarnings("unchecked")
	public synchronized T load(File srcFile) throws PersistenceServiceException {

		T retVal = null;
		try {
			retVal = (T) xStream.fromXML(new FileReader(srcFile));
		} catch (Exception ex) {
			throw new PersistenceServiceException("Error loading Object from file [" + srcFile.getAbsolutePath() + "]",
					ex);
		}

		return retVal;
	}

	@Override
	@SuppressWarnings("unchecked")
	public synchronized T load(InputStream inputStream) throws PersistenceServiceException {
		T retVal = null;
		try {
			retVal = (T) xStream.fromXML(new InputStreamReader(inputStream));
		} catch (Exception ex) {
			throw new PersistenceServiceException("Error loading Object from Xml stream", ex);
		}
		return retVal;
	}

	@Override
	@SuppressWarnings("unchecked")
	public synchronized T clone(T object) throws PersistenceServiceException {

		T retVal = null;
		try {
			retVal = (T) xStream.fromXML(xStream.toXML(object));
		} catch (Exception ex) {

			String className = object.getClass().getCanonicalName();

			throw new PersistenceServiceException("Error cloning Object of Class [" + className + "]", ex);
		}

		return retVal;
	}

}