org.enerj.enhancer.MetaData.java Source code

Java tutorial

Introduction

Here is the source code for org.enerj.enhancer.MetaData.java

Source

/*******************************************************************************
 * Copyright 2000, 2006 Visual Systems Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License version 2
 * which accompanies this distribution in a file named "COPYING".
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *      
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *      
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *******************************************************************************/
// Ener-J Enhancer
// Copyright 2001, 2002 Visual Systems Corporation
// $Header: /cvsroot/ener-j/ener-j/src/org/enerj/enhancer/MetaData.java,v 1.20 2006/05/27 21:32:28 dsyrstad Exp $

package org.enerj.enhancer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.enerj.annotations.Persist;
import org.enerj.annotations.PersistenceAware;
import org.enerj.annotations.SchemaAnnotation;
import org.enerj.util.ClassUtil;
import org.enerj.util.asm.AnnotationNode;
import org.enerj.util.asm.ClassReflector;
import org.enerj.util.asm.FieldNode;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * Enhancer MetaData representation.
 *
 * @version $Id: MetaData.java,v 1.20 2006/05/27 21:32:28 dsyrstad Exp $
 * @author <a href="mailto:dsyrstad@ener-j.org">Dan Syrstad</a>
 */
class MetaData {
    private static final String sSchemaAnnotationDescr = Type.getDescriptor(SchemaAnnotation.class);
    private static final String sPersistDescr = Type.getDescriptor(Persist.class);
    private static final String sPersistenceAwareDescr = Type.getDescriptor(PersistenceAware.class);

    /** Primitive types followed by the corresponding DataInput/Output suffix to use
     * for reading and writing. These are used to load sPrimitiveTypesMap.
     */
    private static final String[] sPrimitiveTypes = { "B", "Byte", "Z", "Boolean", "C", "Char", "S", "Short", "I",
            "Int", "J", "Long", "F", "Float", "D", "Double", };

    /** Map keyed by primitive type JVM names with a value of the DataInput/Output read/write suffix */
    private static HashMap<String, String> sPrimitiveTypesMap = null;

    /** General defaults. The default is not persistable. */
    private ClassDef mDefaultClassDef = new ClassDef("*", ClassDef.TYPE_NOT_CAPABLE, new FieldDef[0]);

    /** Map of ClassDefs by class name explicitly specified in the metadata. Key is class name, value is ClassDef. */
    private HashMap<String, ClassDef> mClassDefMap = new HashMap<String, ClassDef>(1024);

    /** Map of ClassDefs by package name. These are package defaults. Key is pacakge name, value is ClassDef. */
    private HashMap<String, ClassDef> mPackageDefaultsMap = new HashMap<String, ClassDef>(64);

    /** Map of ClassDefs by package name. These are recursive package defaults. Key is pacakge name, value is ClassDef. */
    private HashMap<String, ClassDef> mRecursivePackageDefaultsMap = new HashMap<String, ClassDef>(64);

    /** Cache of classes that have been read to get annotations, etc. Key is class name, value is ClassReflector containing the class information. */
    private HashMap<String, ClassReflector> mClassReflectorCache = new HashMap<String, ClassReflector>(1024);

    /** Cache of direct supertypes of a class. Key is a fully qualified dotted class name, Value is a Set of class names. */
    private Map<String, Set<String>> mDirectSuperTypes = new HashMap<String, Set<String>>(1024);

    /** Cache of all supertypes of a class. Key is a fully qualified dotted class name, Value is a Set of class names. */
    private Map<String, Set<String>> mAllSuperTypes = new HashMap<String, Set<String>>(1024);

    /**
     * Construct a new MetaData model using the specified property files and
     * source path.
     *
     * @param aPropFileList the Ener-J property files names - a List of Strings.
     *
     * @throws MetaDataException if there is an error parsing a property file.
     */
    MetaData(List<String> aPropFileList) throws MetaDataException {
        // Initialize TypeInfo maps.
        if (sPrimitiveTypesMap == null) {
            sPrimitiveTypesMap = new HashMap<String, String>(sPrimitiveTypes.length / 2, 1.0F);
            for (int i = 0; i < sPrimitiveTypes.length; i += 2) {
                sPrimitiveTypesMap.put(sPrimitiveTypes[i], sPrimitiveTypes[i + 1]);
            }
        }

        for (String propFile : aPropFileList) {
            new ODMGMetaDataParser(propFile, this).parse();
        }
    }

    /**
     * Determines if the specified class name is enhanceable (defined in the
     * metadata to be persistable or persistence aware).
     *
     * @param aClassName the class name.
     * @param someClassBytecodes the class' bytecodes, if known. Otherwise null.
     *
     * @return true if it is enhanceable, else false 
     */
    boolean isClassEnhanceable(String aClassName, byte[] someClassBytecodes) throws MetaDataException {
        ClassDef classDef = getClassDef(aClassName, someClassBytecodes);
        int type = classDef.getPersistentType();

        return (type == ClassDef.TYPE_AWARE || type == ClassDef.TYPE_CAPABLE);
    }

    /**
     * Determines the specified class' enhancment type (defined in the
     * metadata to be persistable, persistence aware, or transient).
     *
     * @param aClassName the class name.
     * @param someClassBytecodes the class' bytecodes, if known. Otherwise null.
     *
     * @return non-zero if it is enhanceable, else zero. If it is enhancable, 1 is
     * returned if class is persistence capable, -1 if persistence aware.
     */
    int getClassEnhancementType(String aClassName, byte[] someClassBytecodes) throws MetaDataException {
        ClassDef classDef = getClassDef(aClassName, someClassBytecodes);
        int type = classDef.getPersistentType();

        return (type == ClassDef.TYPE_AWARE ? -1 : (type == ClassDef.TYPE_CAPABLE ? 1 : 0));
    }

    /**
     * Determines if the specified class name is only PersistentAware (defined in the
     * metadata to be persistence=aware).
     *
     * @param aClassName the class name.
     *
     * @return true if it is just PersistentAware, else false.
     */
    boolean isClassOnlyPersistentAware(String aClassName) throws MetaDataException {
        ClassDef classDef = getClassDef(aClassName);
        int type = classDef.getPersistentType();

        return (type == ClassDef.TYPE_AWARE);
    }

    /**
     * Converts a Java VM signature to a class name.
     *
     * @param aSignature the signature.
     *
     * @return the full class name with dot notation. If aSignature is
     *  not recognized, it is just returned.
     */
    static String convertSignatureToClassName(String aSignature) {
        String className = aSignature;

        // Fix "L{class};" forms. Must also change the '/'s to '.'s.
        if (className.charAt(0) == 'L' && className.charAt(className.length() - 1) == ';') {
            className = className.substring(1, className.length() - 2);
        }

        className = className.replace('/', '.');

        return className;
    }

    /**
     * Determines if the specified class name is a FCO (i.e., it 
     * implements Persistable). 
     * 
     * @param aClassName the class name. May be a dot notated class name (e.g., 
     * "java.lang.Object" or a Java VM signature.
     *
     * @return true if it is persistable, else false.
     */
    boolean isClassAFCO(String aClassName) throws MetaDataException {
        aClassName = convertSignatureToClassName(aClassName);
        return getClassDef(aClassName).getPersistentType() == ClassDef.TYPE_CAPABLE;
    }

    /**
     * Determines if the specified signature (e.g., "Z") denotes a primitive type.
     *
     * @param aSignature the Java VM signature to check.
     * 
     * @return true if it represents a primitive type.
     */
    static boolean isPrimitive(String aSignature) {
        return sPrimitiveTypesMap.containsKey(aSignature);
    }

    /**
     * Gets the corresponding DataInput/Output read/write method suffix for
     * aSignature.
     *
     * @param aSignature the Java VM signature to check. Must be a primitive type.
     * 
     * @return the suffix (e.g., "Byte"), or null if aSignature is not a primitive type.
     */
    static String getPrimitiveDataInOutSuffix(String aSignature) {
        return (String) sPrimitiveTypesMap.get(aSignature);
    }

    /**
     * Gets the overrides, if any, for a field as defined by the metadata.
     *
     * @param aClassName the name of the class that the field is contained in.
     * @param aFieldName the name of the field.
     *
     * @return a FieldDef declaring the overrides, or null if no overrides were
     *  specified.
     */
    FieldDef getFieldOverrides(String aClassName, String aFieldName) throws MetaDataException {
        ClassDef classDef = getClassDef(aClassName);
        // TODO Refactor to ClassDef
        FieldDef[] fieldDefs = classDef.getFieldDefs();
        for (int i = 0; fieldDefs != null && i < fieldDefs.length; i++) {
            if (fieldDefs[i].getName().equals(aFieldName)) {
                return fieldDefs[i];
            }
        }

        return null;
    }

    /**
     * Ensure that the field overrides for a class are actually defined on the class.
     *
     * @param aClassName the name of the class.
     * @param someFields a map from field name to Field of fields defined on the class.
     *
     * @throws  MetaDataException if a field override does not exist on the class or some other error occurs.
     */
    void validateFieldOverrides(String aClassName, Map<String, Field> someFields) throws MetaDataException {
        ClassDef classDef = getClassDef(aClassName);
        FieldDef[] fieldDefs = classDef.getFieldDefs();
        for (int overrideIdx = 0; fieldDefs != null && overrideIdx < fieldDefs.length; overrideIdx++) {
            String fieldOverrideName = fieldDefs[overrideIdx].getName();

            Field field = someFields.get(fieldOverrideName);
            if (field == null) {
                throw new EnhancerException("Metadata field override " + fieldOverrideName
                        + " is not an actual field on class " + aClassName, null);
            } else {
                // If override say transient=false, make sure the field is not declared static nor final because
                // these types are always non-persistent.
                int modifiers = field.getAccessModifiers();
                if (fieldDefs[overrideIdx].getPersistentOverride() == FieldDef.PERSISTENT_YES
                        && isStaticOrFinal(modifiers)) {
                    throw new EnhancerException("Metadata field override " + fieldOverrideName + " on class "
                            + aClassName + " incorrectly attempts to make a static or final field persistent.",
                            null);
                }
            }
        }
    }

    /**
     * Determines if a field is persistent. If the specified class is found 
     * in the source path, the metadata and/or Field definition is used
     * to determine persistability. If the class is not in the source path, the
     * database schema is used to determine persistability. 
     *<p>
     * EnerJ will attempt to persist all fields of a class that are not marked static, final,
     * transient, or are marked "transient=false" in the metadata. At runtime, 
     * if EnerJ tries to persist a field
     * and the field contains an object that is not an SCO nor a FCO, EnerJ throws 
     * org.odmg.ClassNotPersistenceCapableException.
     *
     * @param aClassName the fully qualified dotted class name that the field is contained in.
     * @param aFieldName the name of the field.
     * @param someModifiers The field's access modifiers as defined by ASM Opcodes.ACC_*.
     *
     * @return true if the field is persistent, else false.
     */
    boolean isFieldPersistent(String aClassName, String aFieldName, int someModifiers) throws MetaDataException {
        // If the _containing_ class of the field is not persistable, neither is the field.
        if (!isClassAFCO(aClassName)) {
            return false;
        }

        // Static or final fields are never persistent
        if (isStaticOrFinal(someModifiers)) {
            return false;
        }

        FieldDef fieldDef = getFieldOverrides(aClassName, aFieldName);

        if (fieldDef != null) {
            // If "transient=" was specified, use it to determine the override.
            int persistentOverride = fieldDef.getPersistentOverride();
            if (persistentOverride != FieldDef.PERSISTENT_USE_FIELDDEF) {
                return persistentOverride == FieldDef.PERSISTENT_YES;
            }
        }

        // Transient fields are not persistent, by default (if not overridden in metadata).
        return (someModifiers & Opcodes.ACC_TRANSIENT) != Opcodes.ACC_TRANSIENT;
    }

    /**
     * Determine if the access modifiers are static or final.
     * 
     * @param someModifiers the ASM Opcodes.ACC_* modifiers.
     * 
     * @return true if the modifiers represent static or final.
     */
    // TODO Refactor to ASMUtil
    private static boolean isStaticOrFinal(int someModifiers) {
        return (someModifiers & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC
                || (someModifiers & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL;
    }

    /**
     * Gets the ClassDef for a class.
     *
     * @param aClassName the class name.
     *
     * @return a ClassDef. Due to default ClassDefs, the class name may not 
     *  be accurate. If you need it to be accurate, clone the ClassDef and
     *  set the class name.
     *  
     *  @throws MetaDataException if an error occurs.
     */
    private ClassDef getClassDef(String aClassName) throws MetaDataException {
        return getClassDef(aClassName, null);
    }

    /**
     * Gets the ClassDef for a class.
     *
     * @param aClassName the class name.
     * @param someClassBytecodes the class' bytecodes, if known. Otherwise null.
     *
     * @return a ClassDef.
     *  
     * @throws MetaDataException if an error occurs.
     */
    private ClassDef getClassDef(String aClassName, byte[] someClassBytecodes) throws MetaDataException {
        // If we have an explict class def, return that. This can only come from global meta data
        // or from a previously cached getClassDef() resolution.
        ClassDef classDef = mClassDefMap.get(aClassName);
        if (classDef != null) {
            return classDef;
        }

        classDef = getUncachedClassDef(aClassName, someClassBytecodes);
        ClassDef cachableClassDef = classDef;
        String classDefName = classDef.getName();
        if (classDefName == null || !classDefName.equals(aClassName)) {
            cachableClassDef = (ClassDef) classDef.clone();
            cachableClassDef.setName(aClassName);
        }

        // Cache it for next time.
        mClassDefMap.put(aClassName, cachableClassDef);
        return cachableClassDef;
    }

    /**
     * Gets the uncached ClassDef for a class.
     *
     * @param aClassName the class name.
     * @param someClassBytecodes the class' bytecodes, if known. Otherwise null.
     *
     * @return a ClassDef.
     *  
     * @throws MetaDataException if an error occurs.
     */
    private ClassDef getUncachedClassDef(String aClassName, byte[] someClassBytecodes) throws MetaDataException {
        // Check global meta data.

        // Progressively work back thru package defs, and finally 
        // return the default def if no others are found.
        String packageName = getPackageName(aClassName);
        ClassDef classDef = mPackageDefaultsMap.get(packageName);
        if (classDef != null) {
            return classDef;
        }

        // Test recursive package defaults. If our packageName, or any part of it, matches one in 
        // the map, use the corresponding ClassDef. We search from most qualified package name to least
        // so that "com.xyz.abc.*" overrides "com.xyz.*".
        for (; packageName.length() > 0; packageName = getPackageName(packageName)) {
            classDef = mRecursivePackageDefaultsMap.get(packageName);
            if (classDef != null) {
                return classDef;
            }
        }

        // Look at class meta data or schema annotations. Schema annotations are taken over
        // meta data annotations because schema annotations are added as a result of static enhancement.
        classDef = getClassDefFromClassAnnotation(aClassName, someClassBytecodes);
        if (classDef != null) {
            return classDef;
        }

        // Check package annotations all of the way up the hierarchy.
        // If we find one, cache a ClassDef for the package-info class and one for the class.
        packageName = getPackageName(aClassName);
        for (; packageName.length() > 0; packageName = getPackageName(packageName)) {
            try {
                ClassReflector pkgInfo = getClassReflector(packageName + ".package-info", null);
                for (AnnotationNode anno : pkgInfo.getClassAnnotations()) {
                    if (anno.desc.equals(sPersistDescr)) {
                        Boolean persist = (Boolean) anno.getValue("value");
                        classDef = new ClassDef(aClassName,
                                (persist == null || persist) ? ClassDef.TYPE_CAPABLE : ClassDef.TYPE_NOT_CAPABLE,
                                null);
                        return classDef;
                    }
                }
            } catch (MetaDataException e) {
                // Ignore. Just try the next one.
            }
        }

        // Punt. Return the default definition.
        return mDefaultClassDef;
    }

    /**
     * Get the containing package name of the given class or package name.
     *
     * @param aName a dotted class or package name.
     * 
     * @return the containing package name, or an empty string for the default package. 
     */
    private String getPackageName(String aName) {
        String packageName = ""; // Default package.
        int lastDot = aName.lastIndexOf('.');
        if (lastDot >= 0) {
            packageName = aName.substring(0, lastDot);
        }

        return packageName;
    }

    /**
     * Attempts to get a ClassDef for the named class via annotations on the class.
     *
     * @param aClassName the class name.
     * @param someClassBytecodes the class' bytecodes, if known. Otherwise null.
     * 
     * @return the ClassDef, or null if no applicable annotations exist.
     * 
     * @throws MetaDataException if an error occurs.
     */
    private ClassDef getClassDefFromClassAnnotation(String aClassName, byte[] someClassBytecodes)
            throws MetaDataException {
        ClassReflector classInfo = getClassReflector(aClassName, someClassBytecodes);
        List<AnnotationNode> classAnnotations = classInfo.getClassAnnotations();

        for (AnnotationNode anno : classAnnotations) {
            // Do we have a SchemaAnnotation from a static enhancement?
            if (anno.desc.equals(sSchemaAnnotationDescr)) {
                // Yep, class was statically enhanced. Build a class def from the annotation.
                List<String> persistentFields = (List<String>) anno.getValue("persistentFieldNames"); //anno.getArray("persistentFieldNames", String.class);
                List<String> transientFields = (List<String>) anno.getValue("transientFieldNames"); //anno.getArray("transientFieldNames", String.class);
                FieldDef[] fieldDefs = new FieldDef[persistentFields.size() + transientFields.size()];
                int i = 0;
                for (String fieldName : persistentFields) {
                    fieldDefs[i++] = new FieldDef(fieldName, FieldDef.PERSISTENT_YES, null, null);
                }

                for (String fieldName : transientFields) {
                    fieldDefs[i++] = new FieldDef(fieldName, FieldDef.PERSISTENT_NO, null, null);
                }

                return new ClassDef(aClassName, ClassDef.TYPE_CAPABLE, fieldDefs);
            } else if (anno.desc.equals(sPersistDescr)) {
                // Persist anno. 
                Boolean persistent = (Boolean) anno.getValue("value");
                FieldDef[] fieldDefs = null;

                if (persistent == null || persistent) {
                    // Need to get field annos, if any. These are overrides.
                    List<FieldNode> fields = classInfo.getFields();
                    List<FieldDef> fieldDefList = new ArrayList<FieldDef>();
                    for (FieldNode field : fields) {
                        for (AnnotationNode fieldAnno : field.getAnnotations()) {
                            if (fieldAnno.desc.equals(sPersistDescr)) {
                                Boolean fieldPersistent = (Boolean) anno.getValue("value");
                                fieldDefList.add(new FieldDef(field.getName(),
                                        (fieldPersistent == null || fieldPersistent) ? FieldDef.PERSISTENT_YES
                                                : FieldDef.PERSISTENT_NO,
                                        "", ""));
                            }
                        }
                    }

                    fieldDefs = new FieldDef[fieldDefList.size()];
                    fieldDefList.toArray(fieldDefs);
                }

                return new ClassDef(aClassName,
                        (persistent == null || persistent) ? ClassDef.TYPE_CAPABLE : ClassDef.TYPE_NOT_CAPABLE,
                        fieldDefs);
            } else if (anno.desc.equals(sPersistenceAwareDescr)) {
                // PersistenceAware anno.
                Boolean persistenceAware = (Boolean) anno.getValue("value");
                return new ClassDef(aClassName, (persistenceAware == null || persistenceAware) ? ClassDef.TYPE_AWARE
                        : ClassDef.TYPE_NOT_CAPABLE, null);
            }
        }

        return null;
    }

    /**
     * Gets information about a class by reading its bytecodes. After a class
     * has been read once, the information is cached.  
     *
     * @param aClassName the class.
     * @param someClassBytecodes the class' bytecodes, if known. Otherwise null.
     * 
     * @return a ClassReflector which contains information about the class.
     * 
     * @throws MetaDataException if an error occurs.
     */
    private ClassReflector getClassReflector(String aClassName, byte[] someClassBytecodes)
            throws MetaDataException {
        // Is class already cached?
        ClassReflector classInfo = mClassReflectorCache.get(aClassName);
        if (classInfo != null) {
            return classInfo;
        }

        // Load class information using ASM. If so, cache it in mClassReflectorMap.
        try {
            if (someClassBytecodes == null) {
                classInfo = new ClassReflector(aClassName);
            } else {
                classInfo = new ClassReflector(someClassBytecodes);
            }

            mClassReflectorCache.put(aClassName, classInfo);
            return classInfo;
        } catch (ClassNotFoundException e) {
            throw new MetaDataException(e);
        }
    }

    /**
     * Recursively resolves all superclasses and superinterfaces of the specified class.
     * Intentionally attempts to avoid using the ClassLoader to reflect on these values. Classes
     * on the source path will be examined with ASM, others will be reflected with the ClassLoader. 
     * 
     * @param aClassName the fully qualified dotted class name to be resolved.
     * 
     * @return a Set<String> contains all super-types. Every class will contain at least one: java.lang.Object.
     *
     * @throws MetaDataException if an error occurs.
     */
    Set<String> resolveSuperTypes(String aClassName) throws MetaDataException {
        // Check to see if we already resolved this class.
        Set<String> cachedAllSuperTypes = mAllSuperTypes.get(aClassName);
        if (cachedAllSuperTypes != null) {
            return cachedAllSuperTypes;
        }

        Set<String> directSuperTypes = new HashSet<String>(16);
        cachedAllSuperTypes = new HashSet<String>(16);

        String directSuperClass;
        String[] directInterfaces;

        // TODO pass bytecodes into this.
        ClassReflector classReflector = getClassReflector(aClassName, null);
        directSuperClass = classReflector.getSuperClass();
        directInterfaces = classReflector.getSuperInterfaces();

        // Add direct supertype information. directSuperClass will only be null for java.lang.Object.
        if (directSuperClass != null) {
            directSuperTypes.add(directSuperClass);
            cachedAllSuperTypes.add(directSuperClass);
            cachedAllSuperTypes.addAll(resolveSuperTypes(directSuperClass));
        }

        for (String intf : directInterfaces) {
            directSuperTypes.add(intf);
            cachedAllSuperTypes.add(intf);
            cachedAllSuperTypes.addAll(resolveSuperTypes(intf));
        }

        // Cache the results.
        mDirectSuperTypes.put(aClassName, directSuperTypes);
        mAllSuperTypes.put(aClassName, cachedAllSuperTypes);

        return cachedAllSuperTypes;
    }

    /**
     * Sets the default ClassDef.
     *
     * @param aClassDef the ClassDef to become the default.
     */
    void setDefaultClassDef(ClassDef aClassDef) {
        mDefaultClassDef = aClassDef;
    }

    /**
     * Adds a default ClassDef for a package. Only classes below this package are 
     * treated by aClassDef.
     *
     * @param aPackageName the package name.
     * @param aClassDef the ClassDef to become the default for the package.
     */
    void addPackageDefaultClassDef(String aPackageName, ClassDef aClassDef) {
        mPackageDefaultsMap.put(aPackageName, aClassDef);
    }

    /**
     * Adds a default ClassDef for a recursive package. All classes in this package and
     * all sub-packages below it are treated by aClassDef. 
     *
     * @param aPackageName the package name.
     * @param aClassDef the ClassDef to become the default for the package.
     */
    void addRecursivePackageDefaultClassDef(String aPackageName, ClassDef aClassDef) {
        mRecursivePackageDefaultsMap.put(aPackageName, aClassDef);
    }

    /**
     * Adds a ClassDef.
     *
     * @param aClassName the class name.
     * @param aClassDef the ClassDef.
     */
    void addClassDef(String aClassName, ClassDef aClassDef) {
        mClassDefMap.put(aClassName, aClassDef);
    }
}