Java tutorial
/* * Copyright (c) 2004 - 2006, Jonathan Ross <jonross@alum.mit.edu> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package org.cauldron.data; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap; import org.apache.commons.digester.Digester; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.SAXException; import org.cauldron.Converter; import org.cauldron.DataException; import org.cauldron.Task; /** * <p> * Convenience class for input verification and data type conversion in * {@link Task Tasks}. * </p> * <p> * Note: type conversion in Cauldron does not use <code>ConvertUtils</code> in * Commons BeanUtils because we don't want to conflict with those definitions, * which are generally for working with configuration files and not with * application data. Also, we can do interface-based and superclass-based * conversions where BeanUtils cannot. * </p> * <p> * Once initialized from classpath resources, this class accepts no further * conversion configurations. Do not try to change this or you will have to * synchronize the type lookups and performance will plummet. * </p> */ public class Conversions { static { initialize(); } // Where to find the type conversion defaults private final static String CONVERTER_DEFAULTS = "org/cauldron/config/converters.xml"; // Where to find overrides of type converters private final static String CONVERTER_OVERRIDES = "META-INF/cauldron-converters.xml"; // Maps source+target type pairs to ConverterInfo private static Map byPair; // Maps target types to lists of ConverterInfo private static Map byTarget; private static Log log; /** * Perform conversion of an input to the target type, if possible. * * @param obj The object to convert * @param targetType The target type */ public static Object convert(Object obj, Class targetType) throws DataException { if (obj == null) return null; Class sourceType = obj.getClass(); if (sourceType == targetType) return obj; ConverterInfo info = new ConverterInfo(); info.fromClass = sourceType; info.toClass = targetType; ConverterInfo cv = (ConverterInfo) byPair.get(info); if (cv == null) cv = findConverter(sourceType, targetType); if (cv.converter != null) return cv.converter.convert(obj); else return null; } /** * Initialize a specific type converter for the source and target type. If * we cannot do the conversion, set a <code>null</code> {@link Converter} * for that source/target pair so we don't do this again (it's expensive; * it's also why {@link #byPair} is a {@link ConcurrentHashMap}. */ private static ConverterInfo findConverter(Class sourceType, Class targetType) { ConverterInfo cv = new ConverterInfo(); cv.fromClass = sourceType; cv.toClass = targetType; cv.converter = null; List list = (List) byTarget.get(targetType); if (list != null) for (Iterator it = list.iterator(); it.hasNext();) { ConverterInfo ocv = (ConverterInfo) it.next(); if (ocv.fromClass.isAssignableFrom(sourceType)) { cv.converter = ocv.converter; break; } } byPair.put(cv, cv); return cv; } /** * Initialize type conversion information. Read first from the internal * defaults, then from all overrides on the classpath. * * @throws DataException To wrap any exception that occurs while reading * resource files. */ private static synchronized void initialize() throws DataException { log = LogFactory.getLog(Conversions.class); byPair = new ConcurrentHashMap(); byTarget = new HashMap(); ClassLoader cl = Conversions.class.getClassLoader(); InputStream input = cl.getResourceAsStream(CONVERTER_DEFAULTS); if (input == null) throw new DataException("Internal error: resource " + CONVERTER_DEFAULTS + " not found"); try { getConversions(input); Enumeration e = cl.getResources(CONVERTER_OVERRIDES); while (e.hasMoreElements()) { URL url = (URL) e.nextElement(); input = url.openStream(); getConversions(input); } } catch (IOException e) { throw new DataException("I/O error reading type conversions", e); } catch (SAXException e) { throw new DataException("Parse error reading type conversions", e); } } /** * Read type converter information from an InputStream and enter it in * {@link #classConverters}. See org/cauldron/config/converters.xml * for file format. */ private static void getConversions(InputStream stream) throws IOException, SAXException { Digester digester = new Digester(); List cvlist = new ArrayList(); digester.push(cvlist); digester.addObjectCreate("converters/convert", ConverterConfig.class); digester.addSetProperties("converters/convert"); digester.addSetNext("converters/convert", "add"); try { digester.parse(stream); } finally { IOUtils.closeQuietly(stream); } for (Iterator cv = cvlist.iterator(); cv.hasNext();) ((ConverterConfig) cv.next()).apply(); } /** * Internal class that carries configuration info specified by a * <code><converter></code> element. */ public static class ConverterConfig { private String fromClass, toClass, withClass; public void setFrom(String klass) { fromClass = klass; } public void setTo(String klass) { toClass = klass; } public void setWith(String klass) { withClass = klass; } public String toString() { return fromClass + "," + toClass + "," + withClass; } public void apply() throws DataException { ConverterInfo cv = new ConverterInfo(); cv.fromClass = (Class) load(fromClass, false); cv.toClass = (Class) load(toClass, false); cv.converter = (Converter) load(withClass, true); List list = (List) byTarget.get(cv.toClass); if (list == null) { list = new ArrayList(); byTarget.put(cv.toClass, list); } list.add(cv); if (log.isDebugEnabled()) log.debug("Registered " + this); } } /** * Internal class used as a key/value entry in {@link #classConverters}. */ private static class ConverterInfo { Class fromClass, toClass; Converter converter; public boolean equals(Object obj) { if (obj == null || (!(obj instanceof ConverterInfo))) return false; if (obj == this) return true; ConverterInfo a = this, b = (ConverterInfo) obj; return a.fromClass == b.fromClass && a.toClass == b.toClass; } public int hashCode() { return fromClass.hashCode() ^ toClass.hashCode(); } } /** * Internal utility method for resolving and optionally instantiating * classes listed in the configuration file. * * @throws DataException If unable to load or instantiate a class. */ private static Object load(String className, boolean instantiate) throws DataException { ClassLoader cl = Conversions.class.getClassLoader(); Exception ex = null; try { Class klass = cl.loadClass(className); if (instantiate) return klass.newInstance(); else return klass; } catch (ClassNotFoundException e) { ex = e; } catch (InstantiationException e) { ex = e; } catch (IllegalAccessException e) { ex = e; } throw new DataException("Unable to load / instantiate " + className, ex); } }