org.cauldron.data.Conversions.java Source code

Java tutorial

Introduction

Here is the source code for org.cauldron.data.Conversions.java

Source

/*
 * 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>&lt;converter&gt;</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);
    }
}