package simple.escp.data; import javax.json.JsonObject; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; /** * A factory class to create <code>DataSource</code>. */ @SuppressWarnings("unchecked") public abstract class DataSources { private static final Logger LOG = Logger.getLogger("simple.escp"); private static EmptyDataSource emptyDataSource = new EmptyDataSource(); public static final List<DataSourceEntry> DATA_SOURCES; static { List<DataSourceEntry> newDataSource = new ArrayList<>(); newDataSource.add(new DataSourceEntry(Object.class, BeanDataSource.class)); newDataSource.add(new DataSourceEntry(Map.class, MapDataSource.class)); newDataSource.add(new DataSourceEntry(String.class, JsonDataSource.class)); newDataSource.add(new DataSourceEntry(JsonObject.class, JsonDataSource.class)); DATA_SOURCES = newDataSource; } /** * Register a custom data source so that <code>DataSources</code> can build the custom data source from * provided value. * * @param supportedType class of object that can be handled by this data source. * @param dataSourceType an implementation of <code>DataSource</code> that will be created by this entry. */ public static void register(Class supportedType, Class dataSourceType) { DATA_SOURCES.add(new DataSourceEntry(supportedType, dataSourceType)); } /** * Unregister a custom data source. * * @param dataSourceType an implementation of <code>DataSource</code> that will be created by this entry. */ public static void unregister(Class dataSourceType) { for (DataSourceEntry entry : DATA_SOURCES.toArray(new DataSourceEntry[0])) { if (entry.getDataSourceType().equals(dataSourceType)) { DATA_SOURCES.remove(entry); } } } /** * Create a new <code>DataSource</code> from an <code>Object</code>. This method will select the * appropriate <code>DataSouce</code> based on the class type of <code>Object</code>. * * @param object the data source value. Passing <code>null</code> value will always return * an instance of <code>EmptyDataSource</code>. * @return an implementation of <code>DataSource</code>. */ public static DataSource from(Object object) { if (object == null) { return emptyDataSource; } for (int i = DATA_SOURCES.size() - 1; i >= 0; i--) { DataSourceEntry dataSourceEntry = DATA_SOURCES.get(i); if (dataSourceEntry.support(object)) { return dataSourceEntry.create(object); } } LOG.severe("Can't create data source for [" + object + "] class [" + object.getClass() + "]"); throw new UnsupportedOperationException("No data source available for [" + object + "] class [" + object.getClass() + "]"); } /** * Create an array of <code>DataSource</code> based on an array of <code>Object</code>. This method will * select the appropriate <code>DataSource</code> based on the class type of <code>Object</code>. * * @param objects an array of data source values. * @return an array of implementation of <code>DataSource</code> in order of object's array. */ public static DataSource[] from(Object[] objects) { List<DataSource> sources = new ArrayList<>(); for (Object o : objects) { sources.add(from(o)); } return sources.toArray(new DataSource[0]); } /** * Create an array of <code>DataSource</code> from a <code>Map</code> and JavaBean object. * * @param map the <code>Map</code> that contains value. This will be the data source with the first priority. * @param object the object that contains value. * @return an array of implementation of <code>DataSource</code>. */ public static DataSource[] from(Map map, Object object) { return from(new Object[] {map, object}); } /** * Internal class used by <code>DataSource</code>. */ public static class DataSourceEntry { private Class supportedType; private Class dataSourceType; /** * Create new instance of <code>DataSourceEntry</code>. * * @param supportedType class of object that can be handled by this data source. * @param dataSourceType an implementation of <code>DataSource</code> that will be created by this entry. */ public DataSourceEntry(Class supportedType, Class dataSourceType) { this.supportedType = supportedType; this.dataSourceType = dataSourceType; } /** * Get the supported value type (in <code>Class</code>) that is supported by this <code>DataSource</code>. * * @return a <code>Class</code> that represent the type of value that can be handled by this * <code>DataSource</code>. */ public Class getSupportedType() { return supportedType; } /** * Get the <code>DataSource</code> that will be created if a matching value was found. * * @return a <code>Class</code> that should be an implementation of <code>DataSource</code>. */ public Class getDataSourceType() { return dataSourceType; } /** * Determine if the <code>DataSource</code> can be used for this <code>object</code>. An <code>object</code> * is considered as supported if it is an instance of <code>supportedType</code> or an instance of * subclass of <code>supportedType</code>. * * @param object the object that contains value. * @return <code>true</code> if this object can be used as value source for <code>DataSource</code>. */ public boolean support(Object object) { return supportedType.isAssignableFrom(object.getClass()); } /** * Create a new instance of <code>DataSource</code> from an <code>object</code>. * * @param object the object that contains value. * @return an instance of <code>dataSourceType</code> constructed from <code>object</code>. */ public DataSource create(Object object) { if (!support(object)) { throw new UnsupportedOperationException("[" + object + "] type [" + object.getClass() + "] is not supported by [" + dataSourceType + "]"); } try { Constructor constructor = dataSourceType.getConstructor(supportedType); return (DataSource) constructor.newInstance(object); } catch (NoSuchMethodException e) { LOG.severe("Can't find constructor that accept [" + object.getClass().getName() + "] for [" + dataSourceType.getClass().getName()); throw new RuntimeException(e); } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { LOG.severe("Can't create data source for [" + object + "] class [" + object.getClass() + "]"); throw new RuntimeException(e); } } } }