org.objectstyle.cayenne.wocompat.EOModelHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.objectstyle.cayenne.wocompat.EOModelHelper.java

Source

/* ====================================================================
 * 
 * The ObjectStyle Group Software License, version 1.1
 * ObjectStyle Group - http://objectstyle.org/
 * 
 * Copyright (c) 2002-2004, Andrei (Andrus) Adamchik and individual authors
 * of the software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if any,
 *    must include the following acknowlegement:
 *    "This product includes software developed by independent contributors
 *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
 *    or promote products derived from this software without prior written
 *    permission. For written permission, email
 *    "andrus at objectstyle dot org".
 * 
 * 5. Products derived from this software may not be called "ObjectStyle"
 *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
 *    names without prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals and hosted on ObjectStyle Group web site.  For more
 * information on the ObjectStyle Group, please see
 * <http://objectstyle.org/>.
 */

package org.objectstyle.cayenne.wocompat;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.IteratorUtils;
import org.objectstyle.cayenne.map.DataMap;
import org.objectstyle.cayenne.util.ResourceLocator;
import org.objectstyle.cayenne.wocompat.parser.Parser;

/**
 * Helper class used by EOModelProcessor. EOModelHelper loads an EOModel from the
 * specified location and gives its users access to the untyped EOModel information.
 */
public class EOModelHelper {

    private static final ResourceLocator locator = new ResourceLocator();

    private Parser plistParser = new Parser();
    protected URL modelUrl;
    protected Map entityIndex;
    protected Map entityClassIndex;
    protected Map entityQueryIndex;
    protected Map entityClientClassIndex;
    protected DataMap dataMap;
    private Map prototypeValues;

    static {
        // configure locator
        locator.setSkipClasspath(false);
        locator.setSkipCurrentDirectory(false);
        locator.setSkipHomeDirectory(true);
        locator.setSkipAbsolutePath(false);
    }

    /**
     * Creates helper instance and tries to locate EOModel and load index file.
     */
    public EOModelHelper(String path) throws Exception {

        this.modelUrl = findModelUrl(path);
        this.dataMap = new DataMap(findModelName(path));

        // load index file
        List modelIndex = (List) loadModelIndex().get("entities");

        // load entity indices
        entityIndex = new HashMap();
        entityClassIndex = new HashMap();
        entityClientClassIndex = new HashMap();
        entityQueryIndex = new HashMap();

        Iterator it = modelIndex.iterator();
        while (it.hasNext()) {
            Map info = (Map) it.next();
            String name = (String) info.get("name");

            entityIndex.put(name, loadEntityIndex(name));
            entityQueryIndex.put(name, loadQueryIndex(name));
            entityClassIndex.put(name, info.get("className"));
            Map entityPlistMap = entityPListMap(name);

            // get client class information
            Map internalInfo = (Map) entityPlistMap.get("internalInfo");

            if (internalInfo != null) {
                String clientClassName = (String) internalInfo.get("_javaClientClassName");
                entityClientClassIndex.put(name, clientClassName);
            }
        }

        it = modelIndex.iterator();
        while (it.hasNext()) {
            Map info = (Map) it.next();
            String name = (String) info.get("name");
            Map entityPlistMap = entityPListMap(name);
            List classProperties = (List) entityPlistMap.get("classProperties");
            if (classProperties == null) {
                classProperties = Collections.EMPTY_LIST;
            }

            // get client class information
            Map internalInfo = (Map) entityPlistMap.get("internalInfo");

            List clientClassProperties = (internalInfo != null)
                    ? (List) internalInfo.get("_clientClassPropertyNames")
                    : null;

            // guard against no internal info and no client class properties
            if (clientClassProperties == null) {
                clientClassProperties = Collections.EMPTY_LIST;
            }

            // there is a bug in EOModeler it sometimes keeps outdated properties in
            // the client property list. This removes them
            clientClassProperties.retainAll(classProperties);

            // remove all properties from the entity properties that are already defined
            // in
            // a potential parent class.
            String parentEntity = (String) entityPlistMap.get("parent");
            while (parentEntity != null) {
                Map parentEntityPListMap = entityPListMap(parentEntity);
                List parentClassProps = (List) parentEntityPListMap.get("classProperties");
                classProperties.removeAll(parentClassProps);
                // get client class information of parent
                Map parentInternalInfo = (Map) parentEntityPListMap.get("internalInfo");

                if (parentInternalInfo != null) {
                    List parentClientClassProps = (List) parentInternalInfo.get("_clientClassPropertyNames");
                    clientClassProperties.removeAll(parentClientClassProps);
                }

                parentEntity = (String) parentEntityPListMap.get("parent");
            }

            // put back processed properties to the map
            entityPlistMap.put("classProperties", classProperties);
            // add client classes directly for easier access
            entityPlistMap.put("clientClassProperties", clientClassProperties);
        }
    }

    /**
     * Performs Objective C data types conversion to Java types.
     * 
     * @return String representation for Java type corresponding to String representation
     *         of Objective C type.
     * @deprecated Since 1.1 use {@link #javaTypeForEOModelerType(String,String)}to take
     *             "valueType" into account.
     */
    public String javaTypeForEOModelerType(String type) {
        return javaTypeForEOModelerType(type, null);
    }

    /**
     * Performs Objective C data types conversion to Java types.
     * 
     * @since 1.1
     * @return String representation for Java type corresponding to String representation
     *         of Objective C type.
     */
    public String javaTypeForEOModelerType(String valueClassName, String valueType) {
        if (valueClassName == null) {
            return null;
        }

        if (valueClassName.equals("NSString")) {
            return String.class.getName();
        }

        if (valueClassName.equals("NSNumber")) {
            Class numericClass = numericAttributeClass(valueType);
            return (numericClass != null) ? numericClass.getName() : Number.class.getName();
        }

        if (valueClassName.equals("NSCalendarDate"))
            return "java.sql.Timestamp";

        if (valueClassName.equals("NSDecimalNumber")) {
            Class numericClass = numericAttributeClass(valueType);
            return (numericClass != null) ? numericClass.getName() : BigDecimal.class.getName();
        }

        if (valueClassName.equals("NSData"))
            return "byte[]";

        // don't know what the class is mapped to...
        // do some minimum sanity check and use as is
        try {
            return Class.forName(valueClassName).getName();
        } catch (ClassNotFoundException aClassNotFoundException) {
            try {
                return Class.forName("java.lang." + valueClassName).getName();
            } catch (ClassNotFoundException anotherClassNotFoundException) {
                try {
                    return Class.forName("java.util." + valueClassName).getName();
                } catch (ClassNotFoundException yetAnotherClassNotFoundException) {
                    try {
                        return ClassLoader.getSystemClassLoader().loadClass(valueClassName).getName();
                    } catch (ClassNotFoundException e) {
                        // likely a custom class
                        return valueClassName;
                    }
                }
            }
        }
    }

    /**
     * @since 1.1
     */
    // TODO: create a lookup map, maybe XML-loaded...
    protected Class numericAttributeClass(String valueType) {
        if (valueType == null) {
            return null;
        } else if ("b".equals(valueType)) {
            return Byte.class;
        } else if ("s".equals(valueType)) {
            return Short.class;
        } else if ("i".equals(valueType)) {
            return Integer.class;
        } else if ("l".equals(valueType)) {
            return Long.class;
        } else if ("f".equals(valueType)) {
            return Float.class;
        } else if ("d".equals(valueType)) {
            return Double.class;
        } else if ("B".equals(valueType)) {
            return BigDecimal.class;
        } else if ("c".equals(valueType)) {
            return Boolean.class;
        } else {
            return null;
        }
    }

    /** Returns a DataMap associated with this helper. */
    public DataMap getDataMap() {
        return dataMap;
    }

    /** Returns EOModel location as URL. */
    public URL getModelUrl() {
        return modelUrl;
    }

    /** 
     * Returns an iterator of model names. 
     */
    public Iterator modelNames() {
        return entityClassIndex.keySet().iterator();
    }

    /**
     * Returns a list of model entity names.
     * 
     * @since 1.1
     */
    public List modelNamesAsList() {
        return new ArrayList(entityClassIndex.keySet());
    }

    public Map getPrototypeAttributeMapFor(String aPrototypeAttributeName) {
        if (prototypeValues == null) {

            Map eoPrototypesEntityMap = this.entityPListMap("EOPrototypes");

            // no prototypes
            if (eoPrototypesEntityMap == null) {
                prototypeValues = Collections.EMPTY_MAP;
            } else {
                List eoPrototypeAttributes = (List) eoPrototypesEntityMap.get("attributes");

                prototypeValues = new HashMap();
                Iterator it = eoPrototypeAttributes.iterator();
                while (it.hasNext()) {
                    Map attrMap = (Map) it.next();

                    String attrName = (String) attrMap.get("name");

                    // TODO: why are we copying the original map? can we just use it as is?
                    Map prototypeAttrMap = new HashMap();
                    prototypeValues.put(attrName, prototypeAttrMap);

                    prototypeAttrMap.put("name", attrMap.get("name"));
                    prototypeAttrMap.put("prototypeName", attrMap.get("prototypeName"));
                    prototypeAttrMap.put("columnName", attrMap.get("columnName"));
                    prototypeAttrMap.put("valueClassName", attrMap.get("valueClassName"));
                    prototypeAttrMap.put("width", attrMap.get("width"));
                    prototypeAttrMap.put("allowsNull", attrMap.get("allowsNull"));
                    prototypeAttrMap.put("scale", attrMap.get("scale"));
                    prototypeAttrMap.put("valueType", attrMap.get("valueType"));
                }
            }
        }

        Map aMap = (Map) prototypeValues.get(aPrototypeAttributeName);
        if (null == aMap)
            aMap = Collections.EMPTY_MAP;

        return aMap;
    }

    /**
     * @deprecated since 1.0.4 use {@link #entityPListMap(String)}.
     */
    public Map entityInfo(String entityName) {
        return entityPListMap(entityName);
    }

    /** Returns an info map for the entity called <code>entityName</code>. */
    public Map entityPListMap(String entityName) {
        return (Map) entityIndex.get(entityName);
    }

    /**
     * Returns the iterator over EOFetchSpecification names for a given entity.
     * 
     * @since 1.1
     */
    public Iterator queryNames(String entityName) {
        Map queryPlist = (Map) entityQueryIndex.get(entityName);
        if (queryPlist == null || queryPlist.isEmpty()) {
            return IteratorUtils.EMPTY_ITERATOR;
        }

        return queryPlist.keySet().iterator();
    }

    /**
     * Returns a map containing EOFetchSpecification information for entity name and query
     * name. Returns null if no such query is found.
     * 
     * @since 1.1
     */
    public Map queryPListMap(String entityName, String queryName) {
        Map queryPlist = (Map) entityQueryIndex.get(entityName);
        if (queryPlist == null || queryPlist.isEmpty()) {
            return null;
        }

        return (Map) queryPlist.get(queryName);
    }

    /**
     * @deprecated since 1.0.4 use {@link #entityClass(String, boolean)}.
     */
    public String entityClass(String entityName) {
        return entityClass(entityName, false);
    }

    public String entityClass(String entityName, boolean getClientClass) {
        if (getClientClass) {
            return (String) entityClientClassIndex.get(entityName);
        } else {
            return (String) entityClassIndex.get(entityName);
        }
    }

    /** Loads EOModel index and returns it as a map. */
    protected Map loadModelIndex() throws Exception {
        InputStream indexIn = openIndexStream();
        try {
            plistParser.ReInit(indexIn);
            return (Map) plistParser.propertyList();
        } finally {
            indexIn.close();
        }
    }

    /**
     * Loads EOEntity information and returns it as a map.
     */
    protected Map loadEntityIndex(String entityName) throws Exception {
        InputStream entIn = openEntityStream(entityName);
        try {
            plistParser.ReInit(entIn);
            return (Map) plistParser.propertyList();
        } finally {
            entIn.close();
        }
    }

    /**
     * Loads EOFetchSpecification information and returns it as a map.
     */
    protected Map loadQueryIndex(String entityName) throws Exception {
        InputStream queryIn = null;

        // catch file open exceptions since not all entities have query files....
        try {
            queryIn = openQueryStream(entityName);
        } catch (IOException ioex) {
            return Collections.EMPTY_MAP;
        }

        try {
            plistParser.ReInit(queryIn);
            return (Map) plistParser.propertyList();
        } finally {
            queryIn.close();
        }
    }

    /** Returns EOModel name based on its path. */
    protected String findModelName(String path) {
        // strip trailing slashes
        if (path.endsWith("/") || path.endsWith("\\")) {
            path = path.substring(0, path.length() - 1);
        }

        // strip path components
        int i1 = path.lastIndexOf("/");
        int i2 = path.lastIndexOf("\\");
        int i = (i1 > i2) ? i1 : i2;
        if (i >= 0) {
            path = path.substring(i + 1);
        }

        // strip .eomodeld suffix
        if (path.endsWith(".eomodeld")) {
            path = path.substring(0, path.length() - ".eomodeld".length());
        }

        return path;
    }

    /**
     * Returns a URL of the EOModel directory. Throws exception if it can't be found.
     */
    protected URL findModelUrl(String path) {
        if (!path.endsWith(".eomodeld")) {
            path += ".eomodeld";
        }

        URL base = locator.findDirectoryResource(path);
        if (base == null) {
            throw new IllegalArgumentException("Can't find EOModel: " + path);
        }
        return base;
    }

    /**
     * Returns InputStream to read an EOModel index file.
     */
    protected InputStream openIndexStream() throws Exception {
        return new URL(modelUrl, "index.eomodeld").openStream();
    }

    /**
     * Returns InputStream to read an EOEntity plist file.
     * 
     * @param entityName
     *            name of EOEntity to be loaded.
     * @return InputStream to read an EOEntity plist file or null if
     *         <code>entityname.plist</code> file can not be located.
     */
    protected InputStream openEntityStream(String entityName) throws Exception {
        return new URL(modelUrl, entityName + ".plist").openStream();
    }

    /**
     * Returns InputStream to read an EOFetchSpecification plist file.
     * 
     * @param entityName
     *            name of EOEntity to be loaded.
     * @return InputStream to read an EOEntity plist file or null if
     *         <code>entityname.plist</code> file can not be located.
     */
    protected InputStream openQueryStream(String entityName) throws Exception {
        return new URL(modelUrl, entityName + ".fspec").openStream();
    }
}