/* * Copyright (c) 2003-2008, Dennis M. Sosnoski All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. 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. Neither the name of JiBX nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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. */ package org.jibx.binding.classes; import java.io.File; import java.util.HashMap; import org.apache.bcel.Constants; import org.apache.bcel.classfile.Utility; import org.apache.bcel.generic.Type; import org.jibx.binding.def.BindingDefinition; import org.jibx.runtime.JiBXException; /** * Bound class handler. Each instance controls and organizes information for a * class included in one or more binding definitions. * * @author Dennis M. Sosnoski */ public class BoundClass { // // Constants and such related to code generation. /** Class used for code munging when no specific class available. */ private static final String GENERIC_MUNGE_CLASS = BindingDefinition.GENERATE_PREFIX + "MungeAdapter"; /** Prefix used for access methods. */ private static final String ACCESS_PREFIX = BindingDefinition.GENERATE_PREFIX + "access_"; /** Empty argument type array. */ private static final Type[] EMPTY_TYPE_ARGS = {}; // // Static data. /** * Map from bound class name (or bound and munged combination) to binding * information. */ private static HashMap s_nameMap; /** Package of first modifiable class. */ private static String s_modifyPackage; /** Root for package of first modifiable class. */ private static File s_modifyRoot; /** Class used for code generation proxy with unmodifiable classes. */ private static MungedClass s_genericMunge; // // Actual instance data. /** Bound class file information. */ private final ClassFile m_boundClass; /** Class receiving code generated for target class. */ private final MungedClass m_mungedClass; /** * Map from field or method to load access method (lazy create, * null if not used). */ private HashMap m_loadMap; /** * Map from field or method to store access method (lazy create, * null if not used). */ private HashMap m_storeMap; /** * Constructor. * * @param bound target class file information * @param munge class file for class hosting generated code */ private BoundClass(ClassFile bound, MungedClass munge) { m_boundClass = bound; m_mungedClass = munge; } /** * Get bound class file information. * * @return class file information for bound class */ public ClassFile getClassFile() { return m_boundClass; } /** * Get bound class file name. * * @return name of bound class */ public String getClassName() { return m_boundClass.getName(); } /** * Get munged class file information. * * @return class file information for class being modified */ public ClassFile getMungedFile() { return m_mungedClass.getClassFile(); } /** * Check if class being changed directly. * * @return true if bound class is being modified, * false if using a surrogate */ public boolean isDirectAccess() { return m_boundClass == m_mungedClass.getClassFile(); } /** * Get load access method for member of this class. If the access method * does not already exist it's created by this call. If the access method * does exist but without access from the context class, the access * permission on the method is broadened (from package to protected or * public, or from protected to public). * * @param item field or method to be accessed * @param from context class from which access is required * @return the item itself if it's accessible from the required context, an * access method that is accessible if the item is not itself * @throws JiBXException on configuration error */ public ClassItem getLoadMethod(ClassItem item, ClassFile from) throws JiBXException { // initialize tracking information for access methods if first time if (m_loadMap == null) { m_loadMap = new HashMap(); } // check if a new access method needed BindingMethod method = (BindingMethod)m_loadMap.get(item); if (method == null) { // set up for constructing new method String name = ACCESS_PREFIX + "load_" + item.getName(); ClassFile cf = item.getClassFile(); Type type = Type.getType(Utility.getSignature(item.getTypeName())); MethodBuilder mb = new ExceptionMethodBuilder(name, type, EMPTY_TYPE_ARGS, cf, (short)0); // add the actual access method code mb.appendLoadLocal(0); if (item.isMethod()) { mb.addMethodExceptions(item); mb.appendCall(item); } else { mb.appendGetField(item); } mb.appendReturn(type); // track unique instance of this method method = m_mungedClass.getUniqueMethod(mb, true); m_loadMap.put(item, method); } // make sure method is accessible method.makeAccessible(from); return method.getItem(); } /** * Get store access method for member of this class. If the access method * does not already exist it's created by this call. If the access method * does exist but without access from the context class, the access * permission on the method is broadened (from package to protected or * public, or from protected to public). * * @param item field or method to be accessed * @param from context class from which access is required * @return the item itself if it's accessible from the required context, an * access method that is accessible if the item is not itself * @throws JiBXException on configuration error */ public ClassItem getStoreMethod(ClassItem item, ClassFile from) throws JiBXException { // initialize tracking information for access methods if first time if (m_storeMap == null) { m_storeMap = new HashMap(); } // check if a new access method needed BindingMethod method = (BindingMethod)m_storeMap.get(item); if (method == null) { // set up for constructing new method String name = ACCESS_PREFIX + "store_" + item.getName(); ClassFile cf = item.getClassFile(); Type type; if (item.isMethod()) { String sig = item.getSignature(); int start = sig.indexOf('('); int end = sig.indexOf(')'); type = Type.getType(sig.substring(start + 1, end)); } else { type = Type.getType(Utility.getSignature(item.getTypeName())); } MethodBuilder mb = new ExceptionMethodBuilder(name, Type.VOID, new Type[] { type }, cf, (short)0); // add the actual access method code mb.appendLoadLocal(0); mb.appendLoadLocal(1); if (item.isMethod()) { mb.addMethodExceptions(item); mb.appendCall(item); } else { mb.appendPutField(item); } mb.appendReturn(); // track unique instance of this method method = m_mungedClass.getUniqueMethod(mb, true); m_storeMap.put(item, method); } // make sure method is accessible method.makeAccessible(from); return method.getItem(); } /** * Get unique method. Just delegates to the modified class handling, with * unique suffix appended to method name. * * @param builder method to be defined * @return defined method item * @throws JiBXException on configuration error */ public BindingMethod getUniqueMethod(MethodBuilder builder) throws JiBXException { return m_mungedClass.getUniqueMethod(builder, true); } /** * Get unique method. Just delegates to the modified class handling. The * supplied name is used without change. * * @param builder method to be defined * @return defined method item * @throws JiBXException on configuration error */ public BindingMethod getUniqueNamed(MethodBuilder builder) throws JiBXException { return m_mungedClass.getUniqueMethod(builder, false); } /** * Add binding factory to class. Makes sure that there's no surrogate class * for code generation, then delegates to the modified class handling. * * @param fact binding factory name */ public void addFactory(String fact) { if (isDirectAccess()) { m_mungedClass.addFactory(fact); } else { throw new IllegalStateException( "Internal error: not directly modifiable class"); } } /** * Generate factory list. Makes sure that there's no surrogate class for * code generation, then delegates to the modified class handling. * * @throws JiBXException on configuration error */ public void setFactoryList() throws JiBXException { if (isDirectAccess()) { m_mungedClass.setFactoryList(); } else { throw new IllegalStateException( "Internal error: not directly modifiable class"); } } /** * Create binding information for class. This creates the combination of * bound class and (if different) munged class and adds it to the internal * tables. * * @param key text identifier for this bound class and munged class * combination * @param bound class information for bound class * @param munge information for surrogate class receiving generated code, or * null if no separate class * @return binding information for class */ private static BoundClass createInstance(String key, ClassFile bound, MungedClass munge) { BoundClass inst = new BoundClass(bound, munge); s_nameMap.put(key, inst); return inst; } /** * Find or create binding information for class. If the combination of bound * class and munged class already exists it's returned directly, otherwise * it's created and returned. * * @param bound class information for bound class * @param munge information for surrogate class receiving generated code * @return binding information for class */ private static BoundClass findOrCreateInstance(ClassFile bound, MungedClass munge) { String key = bound.getName() + ':' + munge.getClassFile().getName(); BoundClass inst = (BoundClass)s_nameMap.get(key); if (inst == null) { inst = createInstance(key, bound, munge); } return inst; } /** * Get binding information for class. This finds the class in which code * generation for the target class takes place. Normally this class will be * the target class itself, but in cases where the target class is not * modifiable an alternate class will be used. This can take two forms. If * the context class is provided and it is a subclass of the target class, * code for the target class is instead added to the context class. If there * is no context class, or if the context class is not a subclass of the * target class, a unique catch-all class is used. * * @param cf bound class information * @param context context class for code generation, or null * if no context * @return binding information for class * @throws JiBXException on configuration error */ public static BoundClass getInstance(ClassFile cf, BoundClass context) throws JiBXException { // check if new instance needed for this class BoundClass inst = (BoundClass)s_nameMap.get(cf.getName()); if (inst == null) { // load the basic class information and check for modifiable if (!cf.isInterface() && cf.isModifiable()) { // return instance directly inst = createInstance(cf.getName(), cf, MungedClass .getInstance(cf)); } else { // see if the context class is a subclass if (context != null && context.getClassFile().isSuperclass(cf.getName())) { // find or create munge with subclass as surrogate inst = findOrCreateInstance(cf, context.m_mungedClass); } else { // use catch-all munge class as surrogate for all else if (s_genericMunge == null) { String mname; if (s_modifyPackage == null) { mname = GENERIC_MUNGE_CLASS; MungedClass.checkDirectory(s_modifyRoot, ""); } else { mname = s_modifyPackage + '.' + GENERIC_MUNGE_CLASS; MungedClass.checkDirectory(s_modifyRoot, s_modifyPackage); } ClassFile base = ClassCache .getClassFile("java.lang.Object"); int acc = Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT; ClassFile gen = new ClassFile(mname, s_modifyRoot, base, acc, new String[0]); gen.addDefaultConstructor(); s_genericMunge = MungedClass.getInstance(gen); MungedClass.delayedAddUnique(gen); } inst = findOrCreateInstance(cf, s_genericMunge); } } } return inst; } /** * Get binding information for class. This version takes a fully-qualified * class name, calling the paired method if necessary to create a new * instance. * * @param name fully qualified name of bound class * @param context context class for code generation, or null * if no context * @return binding information for class * @throws JiBXException on configuration error */ public static BoundClass getInstance(String name, BoundClass context) throws JiBXException { // check if new instance needed for this class BoundClass inst = (BoundClass)s_nameMap.get(name); if (inst == null) { ClassFile cf = ClassCache.getClassFile(name); return getInstance(cf, context); } return inst; } /** * Discard cached information and reset in preparation for a new binding * run. */ public static void reset() { s_nameMap = new HashMap(); s_modifyPackage = null; s_modifyRoot = null; s_genericMunge = null; } /** * Set override modification information. This allows the binding to * control directly the root directory and package for added classes. * * @param root classpath root directory for added classes * @param pkg package for added classes */ public static void setModify(File root, String pkg) { s_modifyRoot = root; s_modifyPackage = pkg; if (s_modifyPackage.length() == 0) { s_modifyPackage = null; } } /** * Derive generated class name for bound class. This generates a JiBX class * name from the name of this class, using the supplied prefix and suffix * information. The derived class name is always in the same package as the * munged class for this class. * * @param prefix generated class name prefix * @param suffix generated class name suffix * @return derived class name */ public String deriveClassName(String prefix, String suffix) { String pack = m_mungedClass.getClassFile().getPackage(); if (pack.length() > 0) { pack += '.'; } String tname = m_boundClass.getName(); int split = tname.lastIndexOf('.'); if (split >= 0) { tname = tname.substring(split + 1); } return pack + prefix + tname + suffix; } }