/*
* 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;
}
}