/*-
*******************************************************************************
* Copyright (c) 2011, 2014 Diamond Light Source Ltd.
* 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:
* Matthew Gerring - initial API and implementation and/or initial documentation
*******************************************************************************/
package uk.ac.gda.util.beans;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.util.ArrayList;
import java.util.List;
/**
* Has a series of utilities for interacting with the beans and the
* xml files in which they are persisted.
* <p>
* The list of classes is either populated through a Spring instantiated instance of this class (server-side) or via the
* uk.ac.common.beans.factory extension point (RCP client-side).
*/
public class BeansFactory {
private static Class<? extends Object>[] CLASSES;
/**
* Check that CLASSES is init and throw an exception if it hasn't been yet. Using CLASSES in this file would
* generally through a {@link NullPointerException} anyway, this is just a more informative check that is less
* likely to be caught by over aggressive try/catchs that exist in this file.
*
* @throws NullPointerException
* thrown if CLASSES has not been initialised
*/
private static void checkInit() throws NullPointerException {
if (CLASSES == null)
throw new NullPointerException(
"BeansFactory.CLASSES is null, therefore BeansFactory has not been initialized properly");
}
/**
* Can inject classes from spring or with static method. The classes are needed to define which XML files have GDA
* bean files with them.
*/
public BeansFactory() {
}
/**
* Fast way to determine if XML file is a bean in the system which can be read by castor and control things on the
* server.
*
* @param beanFile
* @return true if bean file.
* @throws Exception
*/
public static boolean isBean(File beanFile) throws Exception {
checkInit();
for (int i = 0; i < CLASSES.length; i++) {
if (BeansFactory.isBean(beanFile, CLASSES[i]))
return true;
}
return false;
}
/**
* Find the first filename of an xml file persisting a bean of the given class
*
* @param folder
* @param clazz
* @return String - filename
* @throws Exception
*/
public static String getFirstFileName(File folder, Class<? extends Object> clazz) throws Exception {
final File[] fa = folder.listFiles();
for (int i = 0; i < fa.length; i++) {
if (BeansFactory.isBean(fa[i], clazz)) {
return fa[i].getName();
}
}
return null;
}
/**
* Checks if the XML file is a Castor persistence file of a bean in the given list
*
* @param beanFile
* @return true if file is a saved version of this bean
* @throws Exception
*/
public static boolean isBean(final File beanFile, final Class<? extends Object> beanClass) throws Exception {
final BufferedReader reader = new BufferedReader(new FileReader(beanFile));
return isBean(reader, beanClass);
}
public static boolean isBean(final InputStream bean, final Class<? extends Object> beanClass) throws Exception {
final BufferedReader reader = new BufferedReader(new InputStreamReader(bean, "UTF-8"));
return isBean(reader, beanClass);
}
private static boolean isBean(BufferedReader reader, Class<? extends Object> beanClass) throws Exception {
try {
@SuppressWarnings("unused")
final String titleLine = reader.readLine(); // unused.
final String tagLine = reader.readLine();
if (tagLine == null)
return false;
final String tagName = beanClass.getName().substring(beanClass.getName().lastIndexOf(".") + 1);
if (tagName == null)
return false;
if (tagLine.trim().equalsIgnoreCase("<" + tagName + ">")
|| tagLine.trim().equalsIgnoreCase("<" + tagName + "/>")) {
return true;
}
return false;
} finally {
reader.close();
}
}
public static Class<? extends Object>[] getClasses() {
checkInit();
return CLASSES;
}
public static void setClasses(Class<? extends Object>[] cLASSES) {
if (cLASSES == null)
throw new NullPointerException("cLASSES must be-non null to initialzied BeansFactory");
CLASSES = cLASSES;
}
/**
* Can be used to test if a given class is a class defined by the extension point.
*
* @param clazz
* @return true if class.
*/
public static boolean isClass(Class<? extends Object> clazz) {
checkInit();
for (int i = 0; i < CLASSES.length; i++) {
if (CLASSES[i].equals(clazz))
return true;
}
return false;
}
/**
* Deep copy using serialization. All objects in the graph must serialize to use this method or an exception will be
* thrown.
*
* @param fromBean
* @return deeply cloned bean
*/
public static <T> T deepClone(final T fromBean) throws Exception {
return deepClone(fromBean, fromBean.getClass().getClassLoader());
}
/**
* Creates a clone of any serializable object. Collections and arrays may be cloned if the entries are serializable.
* Caution super class members are not cloned if a super class is not serializable.
*/
private static <T> T deepClone(T toClone, final ClassLoader classLoader) throws Exception {
if (null == toClone)
return null;
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
ObjectOutputStream oOut = new ObjectOutputStream(bOut);
oOut.writeObject(toClone);
oOut.close();
ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
bOut.close();
ObjectInputStream oIn = new ObjectInputStream(bIn) {
/**
* What we are saying with this is that either the class loader or any of the beans added using extension
* points classloaders should be able to find the class.
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
try {
return Class.forName(desc.getName(), false, classLoader);
} catch (Exception ne) {
for (int i = 0; i < CLASSES.length; i++) {
try {
return CLASSES[i].getClassLoader().loadClass(desc.getName());
} catch (Exception e) {
continue;
}
}
}
return null;
}
};
bIn.close();
// the whole idea is to create a clone, therefore the readObject must
// be the same type in the toClone, hence of T
@SuppressWarnings("unchecked")
T copy = (T) oIn.readObject();
oIn.close();
return copy;
}
/**
* Creates a new list of cloned beans (deep).
*
* @param beans
* @return list of cloned beans.
* @throws Exception
*/
public static List<?> cloneBeans(final List<?> beans) throws Exception {
final List<Object> ret = new ArrayList<Object>(beans.size());
for (Object bean : beans)
ret.add(BeansFactory.deepClone(bean));
return ret;
}
}