0001: /*
0002: Copyright (c) 2003-2005, Dennis M. Sosnoski
0003: All rights reserved.
0004:
0005: Redistribution and use in source and binary forms, with or without modification,
0006: are permitted provided that the following conditions are met:
0007:
0008: * Redistributions of source code must retain the above copyright notice, this
0009: list of conditions and the following disclaimer.
0010: * Redistributions in binary form must reproduce the above copyright notice,
0011: this list of conditions and the following disclaimer in the documentation
0012: and/or other materials provided with the distribution.
0013: * Neither the name of JiBX nor the names of its contributors may be used
0014: to endorse or promote products derived from this software without specific
0015: prior written permission.
0016:
0017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
0018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
0021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
0024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0027: */
0028:
0029: package org.jibx.binding.classes;
0030:
0031: import java.io.BufferedOutputStream;
0032: import java.io.File;
0033: import java.io.FileInputStream;
0034: import java.io.FileOutputStream;
0035: import java.io.IOException;
0036: import java.io.InputStream;
0037: import java.io.OutputStream;
0038: import java.net.MalformedURLException;
0039: import java.net.URL;
0040: import java.net.URLClassLoader;
0041: import java.util.ArrayList;
0042: import java.util.Arrays;
0043: import java.util.HashMap;
0044:
0045: import org.apache.bcel.Constants;
0046: import org.apache.bcel.classfile.ClassParser;
0047: import org.apache.bcel.classfile.Code;
0048: import org.apache.bcel.classfile.CodeException;
0049: import org.apache.bcel.classfile.Constant;
0050: import org.apache.bcel.classfile.ConstantDouble;
0051: import org.apache.bcel.classfile.ConstantFloat;
0052: import org.apache.bcel.classfile.ConstantInteger;
0053: import org.apache.bcel.classfile.ConstantLong;
0054: import org.apache.bcel.classfile.ConstantPool;
0055: import org.apache.bcel.classfile.ConstantString;
0056: import org.apache.bcel.classfile.ConstantUtf8;
0057: import org.apache.bcel.classfile.ConstantValue;
0058: import org.apache.bcel.classfile.ExceptionTable;
0059: import org.apache.bcel.classfile.Field;
0060: import org.apache.bcel.classfile.FieldOrMethod;
0061: import org.apache.bcel.classfile.JavaClass;
0062: import org.apache.bcel.classfile.Method;
0063: import org.apache.bcel.classfile.Synthetic;
0064: import org.apache.bcel.classfile.Utility;
0065: import org.apache.bcel.generic.ClassGen;
0066: import org.apache.bcel.generic.ConstantPoolGen;
0067: import org.apache.bcel.generic.FieldGen;
0068: import org.apache.bcel.generic.Type;
0069: import org.apache.bcel.util.ClassPath;
0070: import org.jibx.runtime.JiBXException;
0071:
0072: /**
0073: * Class file information. Wraps the actual class file data as well as
0074: * associated management information.
0075: *
0076: * @author Dennis M. Sosnoski
0077: * @version 1.0
0078: */
0079:
0080: public class ClassFile {
0081: //
0082: // Constants for code generation.
0083:
0084: public static final int PRIVATE_ACCESS = 0;
0085: public static final int PACKAGE_ACCESS = 1;
0086: public static final int PROTECTED_ACCESS = 2;
0087: public static final int PUBLIC_ACCESS = 3;
0088:
0089: public static final int SYNTHETIC_ACCESS_FLAG = 0x1000;
0090:
0091: protected static final int PRIVATEFIELD_ACCESS = Constants.ACC_PRIVATE
0092: | SYNTHETIC_ACCESS_FLAG;
0093: protected static final ExistingMethod[] EMPTY_METHOD_ARRAY = {};
0094: protected static final byte[] EMPTY_BYTES = new byte[0];
0095:
0096: public static final ClassItem[] EMPTY_CLASS_ITEMS = new ClassItem[0];
0097:
0098: //
0099: // Class data.
0100:
0101: /** Singleton loader from classpath. */
0102: private static ClassPath s_loader;
0103:
0104: /** Direct class loader. */
0105: private static ClassLoader s_directLoader;
0106:
0107: //
0108: // Actual instance data.
0109:
0110: /** Fully qualified class name. */
0111: private String m_name;
0112:
0113: /** Signature for class as type. */
0114: private String m_signature;
0115:
0116: /** Class as type. */
0117: private Type m_type;
0118:
0119: /** Directory root for class. */
0120: private File m_root;
0121:
0122: /** Actual class file information. */
0123: private File m_file;
0124:
0125: /** Class in same package as superclass flag. */
0126: private boolean m_isSamePackage;
0127:
0128: /** File is writable flag. */
0129: private boolean m_isWritable;
0130:
0131: /** Super class of this class (set by caller, since it may require
0132: additional information to find the class file). */
0133: protected ClassFile m_super Class;
0134:
0135: /** Names of all interfaces directly implemented by this class. */
0136: protected String[] m_interfaceNames;
0137:
0138: /** Class files of interfaces extended by interface. */
0139: private ClassFile[] m_super Interfaces;
0140:
0141: /** All classes and interfaces of which this is an instance (lazy create,
0142: only if needed. */
0143: private String[] m_instanceOfs;
0144:
0145: /** All methods defined by this class or interface (lazy create, only if
0146: needed. */
0147: private Method[] m_methods;
0148:
0149: /** Base class information as loaded by BCEL. */
0150: private JavaClass m_curClass;
0151:
0152: /** Modified class generator (lazy create, only if needed). */
0153: private ClassGen m_genClass;
0154:
0155: /** Constant pool generator for modified class (lazy create, only if
0156: needed). */
0157: private ConstantPoolGen m_genPool;
0158:
0159: /** Instruction factory for modified class (lazy create, only if needed). */
0160: protected InstructionBuilder m_instBuilder;
0161:
0162: /** Map for method names with possibly generated suffixes (lazy create, only
0163: if needed). */
0164: private HashMap m_suffixMap;
0165:
0166: /** Map to class item information. */
0167: private HashMap m_itemMap;
0168:
0169: /** Flag for class modified. */
0170: private boolean m_isModified;
0171:
0172: /** Usage count for this class. */
0173: private int m_useCount;
0174:
0175: /** Hash code computation for class is current flag. */
0176: private boolean m_isHashCurrent;
0177:
0178: /** Cached hash code value for class. */
0179: private int m_hashCode;
0180:
0181: /** Depth of superclass hierarchy for class (lazy computation). */
0182: private int m_inheritDepth;
0183:
0184: /** Suffix number for making method names unique (lazy computation). */
0185: private int m_uniqueIndex;
0186:
0187: /** Added default constructor for class. */
0188: private ClassItem m_defaultConstructor;
0189:
0190: /**
0191: * Constructor for class file loaded from a stream. Loads the class data
0192: * and prepares it for use.
0193: *
0194: * @param name fully qualified class name
0195: * @param path class file path
0196: * @param ins input stream for class file data
0197: * @throws JiBXException if unable to load class file
0198: */
0199:
0200: public ClassFile(String name, String path, InputStream ins)
0201: throws JiBXException {
0202: init(name, path, ins);
0203: }
0204:
0205: /**
0206: * Constructor for preexisting class file. Loads the class data and
0207: * prepares it for use.
0208: *
0209: * @param name fully qualified class name
0210: * @param root directory root from class loading path list
0211: * @param file actual class file
0212: * @throws IOException if unable to open file
0213: * @throws JiBXException if error in reading class file
0214: */
0215:
0216: public ClassFile(String name, File root, File file)
0217: throws IOException, JiBXException {
0218: init(name, root.getPath(), new FileInputStream(file));
0219: m_root = root;
0220: m_file = file;
0221: m_isWritable = file.canWrite();
0222: }
0223:
0224: /**
0225: * Constructor for preexisting class file from classpath. Loads the class
0226: * data and prepares it for use.
0227: *
0228: * @param name fully qualified class name
0229: * @throws IOException if unable to open file
0230: * @throws JiBXException if error in reading class file
0231: */
0232:
0233: public ClassFile(String name) throws IOException, JiBXException {
0234:
0235: // try out class path first, then BCEL system path
0236: ClassPath.ClassFile cf = null;
0237: try {
0238: cf = s_loader.getClassFile(name);
0239: } catch (IOException ex) {
0240: try {
0241: cf = ClassPath.SYSTEM_CLASS_PATH.getClassFile(name);
0242: } catch (IOException ex1) { /* deliberately left empty */
0243: }
0244: }
0245: if (cf == null) {
0246: throw new JiBXException("Class " + name
0247: + " not found in any classpath");
0248: } else {
0249: init(name, cf.getPath(), cf.getInputStream());
0250: }
0251: }
0252:
0253: /**
0254: * Constructor for synthetic placeholder classfile with no backing class
0255: * data.
0256: *
0257: * @param name fully qualified class name
0258: * @param sig corresponding class signature
0259: */
0260:
0261: public ClassFile(String name, String sig) {
0262: m_name = name;
0263: m_signature = sig;
0264: m_type = Type.getType(sig);
0265: m_interfaceNames = new String[0];
0266: m_super Interfaces = new ClassFile[0];
0267: m_itemMap = new HashMap();
0268: }
0269:
0270: /**
0271: * Constructor for new class file. Initializes the class data and
0272: * prepares it for use.
0273: *
0274: * @param name fully qualified class name
0275: * @param root directory root from class loading path list
0276: * @param sclas superclass of new class
0277: * @param access access flags for class
0278: * @param impls array of interfaces implemented by new class
0279: * @throws JiBXException on error loading interface information
0280: */
0281:
0282: public ClassFile(String name, File root, ClassFile sclas,
0283: int access, String[] impls) throws JiBXException {
0284: String fname = name.replace('.', File.separatorChar) + ".class";
0285: File file = new File(root, fname);
0286: m_name = name;
0287: m_signature = Utility.getSignature(name);
0288: m_type = ClassItem.typeFromName(name);
0289: m_root = root;
0290: m_super Class = sclas;
0291: m_interfaceNames = impls;
0292: m_file = file;
0293: m_isWritable = true;
0294: m_genClass = new ClassGen(name, sclas.getName(), "", access
0295: | SYNTHETIC_ACCESS_FLAG, impls);
0296: m_genPool = m_genClass.getConstantPool();
0297: int index = m_genPool.addUtf8("Synthetic");
0298: m_genClass.addAttribute(new Synthetic(index, 0, EMPTY_BYTES,
0299: m_genPool.getConstantPool()));
0300: m_instBuilder = new InstructionBuilder(m_genClass, m_genPool);
0301: m_itemMap = new HashMap();
0302: initInterface();
0303: ClassCache.addClassFile(this );
0304: }
0305:
0306: /**
0307: * Internal initialization method. This is used to handle common
0308: * initialization for the constructors.
0309: *
0310: * @param name fully qualified class name
0311: * @param path class file path
0312: * @param ins input stream for class file data
0313: * @throws JiBXException if unable to load class file
0314: */
0315:
0316: private void init(String name, String path, InputStream ins)
0317: throws JiBXException {
0318: m_name = name;
0319: m_signature = Utility.getSignature(name);
0320: m_type = ClassItem.typeFromName(name);
0321: m_itemMap = new HashMap();
0322: if (path == null) {
0323: m_interfaceNames = new String[0];
0324: } else {
0325: String fname = name.replace('.', File.separatorChar)
0326: + ".class";
0327: ClassParser parser = new ClassParser(ins, fname);
0328: try {
0329: m_curClass = parser.parse();
0330: m_interfaceNames = m_curClass.getInterfaceNames();
0331: } catch (Exception ex) {
0332: throw new JiBXException("Error reading path " + path
0333: + " for class " + name);
0334: }
0335: }
0336: initInterface();
0337: }
0338:
0339: /**
0340: * Retrieve superinterfaces for an interface class. These are collected at
0341: * initialization so that we can support getting the full set of methods
0342: * later without worrying about throwing an exception.
0343: *
0344: * @throws JiBXException on error loading interface information
0345: */
0346: private void initInterface() throws JiBXException {
0347: if (isInterface() && m_interfaceNames.length > 0) {
0348: ClassFile[] super s = new ClassFile[m_interfaceNames.length];
0349: for (int i = 0; i < m_interfaceNames.length; i++) {
0350: super s[i] = ClassCache
0351: .getClassFile(m_interfaceNames[i]);
0352: }
0353: m_super Interfaces = super s;
0354: } else {
0355: m_super Interfaces = new ClassFile[0];
0356: }
0357: }
0358:
0359: /**
0360: * Check if class is an interface. This only checks existing classes,
0361: * assuming that no generated classes are interfaces.
0362: *
0363: * @return <code>true</code> if an interface, <code>false</code> if not
0364: */
0365:
0366: public boolean isInterface() {
0367: return m_curClass != null && m_curClass.isInterface();
0368: }
0369:
0370: /**
0371: * Check if class is abstract. This only checks existing classes,
0372: * assuming that no generated classes are abstract.
0373: *
0374: * @return <code>true</code> if an abstract class, <code>false</code> if not
0375: */
0376:
0377: public boolean isAbstract() {
0378: return m_curClass != null && m_curClass.isAbstract();
0379: }
0380:
0381: /**
0382: * Check if class is an array. This only checks existing classes,
0383: * assuming that no generated classes are arrays.
0384: *
0385: * @return <code>true</code> if an array class, <code>false</code> if not
0386: */
0387:
0388: public boolean isArray() {
0389: return m_name.endsWith("[]");
0390: }
0391:
0392: /**
0393: * Check if class is modifiable.
0394: *
0395: * @return <code>true</code> if class is modifiable, <code>false</code> if
0396: * not
0397: */
0398:
0399: public boolean isModifiable() {
0400: return m_isWritable && !isInterface();
0401: }
0402:
0403: /**
0404: * Get fully qualified class name.
0405: *
0406: * @return fully qualified name for class
0407: */
0408:
0409: public String getName() {
0410: return m_name;
0411: }
0412:
0413: /**
0414: * Get signature for class as type.
0415: *
0416: * @return signature for class used as type
0417: */
0418:
0419: public String getSignature() {
0420: return m_signature;
0421: }
0422:
0423: /**
0424: * Get class as type.
0425: *
0426: * @return class as type
0427: */
0428:
0429: public Type getType() {
0430: return m_type;
0431: }
0432:
0433: /**
0434: * Get package name.
0435: *
0436: * @return package name for class
0437: */
0438:
0439: public String getPackage() {
0440: int split = m_name.lastIndexOf('.');
0441: if (split >= 0) {
0442: return m_name.substring(0, split);
0443: } else {
0444: return "";
0445: }
0446: }
0447:
0448: /**
0449: * Get root directory for load path.
0450: *
0451: * @return root directory in path used for loading file
0452: */
0453:
0454: public File getRoot() {
0455: return m_root;
0456: }
0457:
0458: /**
0459: * Get actual file for class.
0460: *
0461: * @return file used for class
0462: */
0463:
0464: public File getFile() {
0465: return m_file;
0466: }
0467:
0468: /**
0469: * Get raw current class information.
0470: *
0471: * @return raw current class information
0472: */
0473:
0474: public JavaClass getRawClass() {
0475: if (m_curClass == null) {
0476: throw new IllegalStateException(
0477: "No loadable class information for " + m_name);
0478: } else {
0479: return m_curClass;
0480: }
0481: }
0482:
0483: /**
0484: * Get superclass name.
0485: *
0486: * @return fully qualified name of superclass
0487: */
0488:
0489: public String getSuperName() {
0490: if (m_curClass == null) {
0491: return null;
0492: } else {
0493: return m_curClass.getSuperclassName();
0494: }
0495: }
0496:
0497: /**
0498: * Set superclass information.
0499: *
0500: * @param sclas superclass information
0501: */
0502:
0503: public void setSuperFile(ClassFile sclas) {
0504: m_super Class = sclas;
0505: m_isSamePackage = getPackage().equals(sclas.getPackage());
0506: }
0507:
0508: /**
0509: * Get superclass information.
0510: *
0511: * @return super class information as loaded (<code>null</code> if no
0512: * superclass - java.lang.Object, interface, or primitive)
0513: */
0514:
0515: public ClassFile getSuperFile() {
0516: return m_super Class;
0517: }
0518:
0519: /**
0520: * Get names of all interfaces implemented by class.
0521: *
0522: * @return names of all interfaces implemented directly by class
0523: */
0524:
0525: public String[] getInterfaces() {
0526: return m_interfaceNames;
0527: }
0528:
0529: /**
0530: * Add interface to class. The interface is added to the class if not
0531: * already defined.
0532: *
0533: * @param intf fully qualified interface name
0534: * @return <code>true</code> if added, <code>false</code> if already present
0535: * @throws JiBXException on configuration error
0536: */
0537:
0538: public boolean addInterface(String intf) throws JiBXException {
0539: ClassGen gen = getClassGen();
0540: String[] intfs = gen.getInterfaceNames();
0541: for (int i = 0; i < intfs.length; i++) {
0542: if (intf.equals(intfs[i])) {
0543: return false;
0544: }
0545: }
0546: gen.addInterface(intf);
0547: m_isModified = true;
0548: m_instanceOfs = null;
0549: return true;
0550: }
0551:
0552: /**
0553: * Accumulate interface signatures recursively.
0554: *
0555: * @param intfs names of interfaces implemented
0556: * @param map map for interfaces already accumulated
0557: * @param accs accumulated interface names
0558: * @throws JiBXException if configuration error
0559: */
0560:
0561: protected void accumulateInterfaces(String[] intfs, HashMap map,
0562: ArrayList accs) throws JiBXException {
0563: for (int i = 0; i < intfs.length; i++) {
0564: String name = intfs[i];
0565: if (map.get(name) == null) {
0566: ClassFile cf = ClassCache.getClassFile(name);
0567: String sig = cf.getSignature();
0568: map.put(name, sig);
0569: accs.add(sig);
0570: String[] inherits = cf.m_curClass.getInterfaceNames();
0571: accumulateInterfaces(inherits, map, accs);
0572: }
0573: }
0574: }
0575:
0576: /**
0577: * Get signatures for all types of which instances of this type are
0578: * instances.
0579: *
0580: * @return all signatures suppored by instances
0581: * @throws JiBXException if configuration error
0582: */
0583:
0584: public String[] getInstanceSigs() throws JiBXException {
0585: if (m_instanceOfs == null) {
0586:
0587: // check for an array class
0588: String name = getName();
0589: if (name.endsWith("[]")) {
0590:
0591: // accumulate prefix by stripping suffixes
0592: String prefix = "";
0593: do {
0594: name = name.substring(0, name.length() - 2);
0595: prefix = prefix + '[';
0596: } while (name.endsWith("[]"));
0597:
0598: // check for a primitive base type on array
0599: String[] bsigs;
0600: if (ClassItem.isPrimitive(name)) {
0601: bsigs = new String[1];
0602: bsigs[0] = ClassItem.getPrimitiveSignature(name);
0603: } else {
0604: ClassFile bcf = ClassCache.getClassFile(name);
0605: bsigs = bcf.getInstanceSigs();
0606: }
0607:
0608: // derive array signatures from signatures for base type
0609: String[] asigs = new String[bsigs.length + 1];
0610: for (int i = 0; i < bsigs.length; i++) {
0611: asigs[i] = prefix + bsigs[i];
0612: }
0613: asigs[bsigs.length] = "Ljava/lang/Object;";
0614: m_instanceOfs = asigs;
0615:
0616: } else {
0617:
0618: // walk all classes and interfaces to find signatures
0619: HashMap map = new HashMap();
0620: ArrayList iofs = new ArrayList();
0621: ClassFile cur = this ;
0622: while (cur != null) {
0623: String sig = cur.getSignature();
0624: map.put(name, sig);
0625: iofs.add(sig);
0626: accumulateInterfaces(cur.getInterfaces(), map, iofs);
0627: cur = cur.getSuperFile();
0628: }
0629: String[] sigs = new String[iofs.size()];
0630: m_instanceOfs = (String[]) iofs.toArray(sigs);
0631:
0632: }
0633: }
0634: return m_instanceOfs;
0635: }
0636:
0637: /**
0638: * Check if class implements an interface.
0639: *
0640: * @param sig signature of interface to be checked
0641: * @return <code>true</code> if interface is implemented by class,
0642: * <code>false</code> if not
0643: * @throws JiBXException if configuration error
0644: */
0645:
0646: public boolean isImplements(String sig) throws JiBXException {
0647: String[] sigs = getInstanceSigs();
0648: for (int i = 0; i < sigs.length; i++) {
0649: if (sig.equals(sigs[i])) {
0650: return true;
0651: }
0652: }
0653: return false;
0654: }
0655:
0656: /**
0657: * Check if another class is a superclass of this one.
0658: *
0659: * @param name of superclass to be checked
0660: * @return <code>true</code> if named class is a superclass of this one,
0661: * <code>false</code> if not
0662: */
0663:
0664: public boolean isSuperclass(String name) {
0665: ClassFile cur = this ;
0666: while (cur != null) {
0667: if (cur.getName().equals(name)) {
0668: return true;
0669: } else {
0670: cur = cur.getSuperFile();
0671: }
0672: }
0673: return false;
0674: }
0675:
0676: /**
0677: * Get array of fields defined by class.
0678: *
0679: * @return array of fields defined by class
0680: */
0681: public ClassItem[] getFieldItems() {
0682: if (m_curClass == null) {
0683: return EMPTY_CLASS_ITEMS;
0684: } else {
0685: Field[] fields = m_curClass.getFields();
0686: ClassItem[] items = new ClassItem[fields.length];
0687: for (int i = 0; i < fields.length; i++) {
0688: Field field = fields[i];
0689: items[i] = new ClassItem(field.getName(), this , field);
0690: }
0691: return items;
0692: }
0693: }
0694:
0695: /**
0696: * Get internal information for field. This can only be used with
0697: * existing classes, and only checks for fields that are actually members
0698: * of the class (not superclasses).
0699: *
0700: * @param name field name
0701: * @return field information, or <code>null</code> if field not found
0702: */
0703:
0704: protected Field getDefinedField(String name) {
0705:
0706: // check for match to field name defined in class
0707: Field[] fields = m_curClass.getFields();
0708: for (int i = 0; i < fields.length; i++) {
0709: if (fields[i].getName().equals(name)) {
0710: return fields[i];
0711: }
0712: }
0713: return null;
0714: }
0715:
0716: /**
0717: * Get internal information for field. This can only be used with existing
0718: * classes. If the field is not found directly, superclasses are checked for
0719: * inherited fields matching the supplied name.
0720: *
0721: * @param name field name
0722: * @return field information, or <code>null</code> if field not found
0723: */
0724:
0725: protected Field getAccessibleField(String name) {
0726:
0727: // always return not found for unloadable class
0728: if (m_curClass == null) {
0729: return null;
0730: } else {
0731:
0732: // check for match to field name defined in class
0733: Field field = getDefinedField(name);
0734: if (field == null) {
0735:
0736: // try match to field inherited from superclass
0737: if (m_super Class != null) {
0738: field = m_super Class.getAccessibleField(name);
0739: if (field != null
0740: && (!m_isSamePackage || field.isPrivate())
0741: && !field.isPublic()
0742: && !field.isProtected()) {
0743: field = null;
0744: }
0745: }
0746:
0747: }
0748: return field;
0749: }
0750: }
0751:
0752: /**
0753: * Get information for field. This can only be used with existing classes,
0754: * and only checks for fields that are actually members of the class (not
0755: * superclasses).
0756: *
0757: * @param name field name
0758: * @return field information, or <code>null</code> if field not found
0759: */
0760:
0761: public ClassItem getDirectField(String name) {
0762: Field field = getAccessibleField(name);
0763: if (field == null) {
0764: return null;
0765: } else {
0766: ClassItem item = (ClassItem) m_itemMap.get(field);
0767: if (item == null) {
0768: item = new ClassItem(name, this , field);
0769: m_itemMap.put(field, item);
0770: }
0771: return item;
0772: }
0773: }
0774:
0775: /**
0776: * Get information for field. This can only be used with existing classes.
0777: * If the field is not found directly, superclasses are checked for
0778: * inherited fields matching the supplied name.
0779: *
0780: * @param name field name
0781: * @return field information
0782: * @throws JiBXException if field not found
0783: */
0784:
0785: public ClassItem getField(String name) throws JiBXException {
0786: Field field = getAccessibleField(name);
0787: if (field == null) {
0788: throw new JiBXException("Field " + name
0789: + " not found in class " + m_name);
0790: } else {
0791: ClassItem item = (ClassItem) m_itemMap.get(field);
0792: if (item == null) {
0793: item = new ClassItem(name, this , field);
0794: m_itemMap.put(field, item);
0795: }
0796: return item;
0797: }
0798: }
0799:
0800: /**
0801: * Get array of methods defined by class or interface. In the case of an
0802: * interface, this merges all methods from superinterfaces in the array
0803: * returned.
0804: *
0805: * @return array of methods defined by class
0806: */
0807: private Method[] getMethods() {
0808: if (m_methods == null) {
0809:
0810: // start with methods defined directly
0811: Method[] methods = m_curClass.getMethods();
0812: if (m_curClass.isInterface()
0813: && m_super Interfaces.length > 0) {
0814:
0815: // for interface extending other interfaces, merge methods
0816: ArrayList merges = new ArrayList();
0817: for (int i = 0; i < methods.length; i++) {
0818: merges.add(methods[i]);
0819: }
0820: for (int i = 0; i < m_super Interfaces.length; i++) {
0821: methods = m_super Interfaces[i].getMethods();
0822: for (int j = 0; j < methods.length; j++) {
0823: merges.add(methods[j]);
0824: }
0825: }
0826:
0827: // set merged array
0828: methods = (Method[]) merges.toArray(new Method[merges
0829: .size()]);
0830: }
0831:
0832: // cache the created method array
0833: m_methods = methods;
0834: }
0835: return m_methods;
0836: }
0837:
0838: /**
0839: * Get array of methods defined by class.
0840: *
0841: * @return array of methods defined by class
0842: */
0843: public ClassItem[] getMethodItems() {
0844: if (m_curClass == null) {
0845: return EMPTY_CLASS_ITEMS;
0846: } else {
0847: Method[] methods = getMethods();
0848: ClassItem[] items = new ClassItem[methods.length];
0849: for (int i = 0; i < methods.length; i++) {
0850: Method method = methods[i];
0851: items[i] = new ClassItem(method.getName(), this , method);
0852: }
0853: return items;
0854: }
0855: }
0856:
0857: /**
0858: * Get internal information for method without respect to potential trailing
0859: * arguments or return value. This can only be used with existing classes.
0860: * If the method is not found directly, superclasses are checked for
0861: * inherited methods matching the supplied name. This compares the supplied
0862: * partial signature against the actual method signature, and considers it
0863: * a match if the actual sigature starts with the supplied signature..
0864: *
0865: * @param name method name
0866: * @param sig partial method signature to be matched
0867: * @return method information, or <code>null</code> if method not found
0868: */
0869:
0870: protected Method getAccessibleMethod(String name, String sig) {
0871:
0872: // only check loadable classes
0873: if (m_curClass != null) {
0874:
0875: // check for match to method defined in class
0876: Method[] methods = getMethods();
0877: for (int i = 0; i < methods.length; i++) {
0878: Method method = methods[i];
0879: if (method.getName().equals(name)) {
0880: if (method.getSignature().startsWith(sig)) {
0881: return method;
0882: }
0883: }
0884: }
0885:
0886: // try match to method inherited from superclass
0887: if (m_super Class != null) {
0888: Method method = m_super Class.getAccessibleMethod(name,
0889: sig);
0890: if (method != null
0891: && ((m_isSamePackage && !method.isPrivate())
0892: || method.isPublic() || method
0893: .isProtected())) {
0894: return method;
0895: }
0896: }
0897:
0898: }
0899: return null;
0900: }
0901:
0902: /**
0903: * Get information for method without respect to potential trailing
0904: * arguments or return value. This can only be used with existing classes.
0905: * If the method is not found directly, superclasses are checked for
0906: * inherited methods matching the supplied name. This compares the supplied
0907: * partial signature against the actual method signature, and considers it
0908: * a match if the actual sigature starts with the supplied signature..
0909: *
0910: * @param name method name
0911: * @param sig partial method signature to be matched
0912: * @return method information, or <code>null</code> if method not found
0913: */
0914:
0915: public ClassItem getMethod(String name, String sig) {
0916: Method method = getAccessibleMethod(name, sig);
0917: if (method == null) {
0918: return null;
0919: } else {
0920: ClassItem item = (ClassItem) m_itemMap.get(method);
0921: if (item == null) {
0922: item = new ClassItem(name, this , method);
0923: m_itemMap.put(method, item);
0924: }
0925: return item;
0926: }
0927: }
0928:
0929: /**
0930: * Get information for method matching one of several possible signatures.
0931: * This can only be used with existing classes. If a match is not found
0932: * directly, superclasses are checked for inherited methods matching the
0933: * supplied name and signatures. The signature variations are checked in
0934: * the order supplied.
0935: *
0936: * @param name method name
0937: * @param sigs possible signatures for method (including return type)
0938: * @return method information, or <code>null</code> if method not found
0939: */
0940:
0941: public ClassItem getMethod(String name, String[] sigs) {
0942: Method method = null;
0943: for (int i = 0; method == null && i < sigs.length; i++) {
0944: method = getAccessibleMethod(name, sigs[i]);
0945: }
0946: if (method == null) {
0947: return null;
0948: } else {
0949: ClassItem item = (ClassItem) m_itemMap.get(method);
0950: if (item == null) {
0951: item = new ClassItem(name, this , method);
0952: m_itemMap.put(method, item);
0953: }
0954: return item;
0955: }
0956: }
0957:
0958: /**
0959: * Check for match to specified access level. This treats a field or method
0960: * as matching if the access level is the same as or more open than the
0961: * required level.
0962: *
0963: * @param item information for field or method to be checked
0964: * @param access required access level for match
0965: * @return <code>true</code> if access level match, <code>false</code> if
0966: * not
0967: */
0968:
0969: private static boolean matchAccess(FieldOrMethod item, int access) {
0970: if (item.isPublic()) {
0971: return true;
0972: } else if (item.isProtected()) {
0973: return access <= PROTECTED_ACCESS;
0974: } else if (item.isPrivate()) {
0975: return access == PRIVATE_ACCESS;
0976: } else {
0977: return access <= PACKAGE_ACCESS;
0978: }
0979: }
0980:
0981: /**
0982: * Check if one type is assignment compatible with another type. This is an
0983: * ugly replacement for apparently broken BCEL code.
0984: *
0985: * @param have type being checked
0986: * @param need type needed
0987: * @return <code>true</code> if compatible, <code>false</code> if not
0988: */
0989: private static boolean isAssignmentCompatible(Type have, Type need) {
0990: if (have.equals(need)) {
0991: return true;
0992: } else {
0993: try {
0994: return ClassItem.isAssignable(have.toString(), need
0995: .toString());
0996: } catch (JiBXException e) {
0997: throw new IllegalStateException(
0998: "Internal error: Unable to access data for "
0999: + have.toString() + " or "
1000: + need.toString() + ":\n"
1001: + e.getMessage());
1002: }
1003: }
1004: }
1005:
1006: /**
1007: * Get information for best matching method. This tries to find a method
1008: * which matches the specified name, return type, and argument types. If an
1009: * exact match is not found it looks for a method with a return type that
1010: * is extended or implemented by the specified type and arguments that are
1011: * extended or implemented by the specified types. This can only be used
1012: * with existing classes. If the method is not found directly, superclasses
1013: * are checked for inherited methods.
1014: *
1015: * @param name method name
1016: * @param access access level required for matching methods
1017: * @param ret return value type (<code>null</code> if indeterminant)
1018: * @param args argument value types
1019: * @return method information, or <code>null</code> if method not found
1020: */
1021:
1022: private Method getBestAccessibleMethod(String name, int access,
1023: Type ret, Type[] args) {
1024:
1025: // just fail for classes that aren't loadable
1026: if (m_curClass == null) {
1027: return null;
1028: }
1029:
1030: // check for match to method defined in class
1031: Method[] methods = getMethods();
1032: Method best = null;
1033: int diff = Integer.MAX_VALUE;
1034: for (int i = 0; i < methods.length; i++) {
1035: Method method = methods[i];
1036: if (method.getName().equals(name)
1037: && matchAccess(method, access)) {
1038:
1039: // make sure the return type is compatible
1040: boolean match = true;
1041: int ndiff = 0;
1042: if (ret != null) {
1043: Type type = method.getReturnType();
1044: match = isAssignmentCompatible(ret, type);
1045: }
1046: if (match) {
1047:
1048: // check closeness of argument types
1049: Type[] types = method.getArgumentTypes();
1050: if (args.length == types.length) {
1051: for (int j = 0; j < args.length; j++) {
1052: Type type = types[j];
1053: Type arg = args[j];
1054: if (!type.equals(arg)) {
1055: ndiff++;
1056: match = isAssignmentCompatible(arg,
1057: type);
1058: if (!match) {
1059: break;
1060: }
1061: }
1062: }
1063: } else {
1064: match = false;
1065: }
1066: }
1067: if (match && ndiff < diff) {
1068: best = method;
1069: }
1070: }
1071: }
1072: if (best != null) {
1073: return best;
1074: }
1075:
1076: // try methods inherited from superclass if no match found
1077: if (m_super Class != null) {
1078: if (access < PROTECTED_ACCESS) {
1079: if (m_isSamePackage) {
1080: access = PACKAGE_ACCESS;
1081: } else {
1082: access = PROTECTED_ACCESS;
1083: }
1084: }
1085: return m_super Class.getBestAccessibleMethod(name, access,
1086: ret, args);
1087: } else {
1088: return null;
1089: }
1090: }
1091:
1092: /**
1093: * Get information for best matching method. This tries to find a method
1094: * which matches the specified name, return type, and argument types. If an
1095: * exact match is not found it looks for a method with a return type that
1096: * is extended or implemented by the specified type and arguments that are
1097: * extended or implemented by the specified types. This can only be used
1098: * with existing classes. If the method is not found directly, superclasses
1099: * are checked for inherited methods.
1100: *
1101: * @param name method name
1102: * @param ret return value type (<code>null</code> if indeterminant)
1103: * @param args argument value types
1104: * @return method information, or <code>null</code> if method not found
1105: */
1106:
1107: public ClassItem getBestMethod(String name, String ret,
1108: String[] args) {
1109: Type rtype = null;
1110: if (ret != null) {
1111: rtype = ClassItem.typeFromName(ret);
1112: }
1113: Type[] atypes = new Type[args.length];
1114: for (int i = 0; i < args.length; i++) {
1115: atypes[i] = ClassItem.typeFromName(args[i]);
1116: }
1117: Method method = getBestAccessibleMethod(name, PRIVATE_ACCESS,
1118: rtype, atypes);
1119: if (method == null) {
1120: return null;
1121: }
1122: ClassItem item = (ClassItem) m_itemMap.get(method);
1123: if (item == null) {
1124: item = new ClassItem(name, this , method);
1125: m_itemMap.put(method, item);
1126: }
1127: return item;
1128: }
1129:
1130: /**
1131: * Get information for initializer. This can only be used with existing
1132: * classes. Only the class itself is checked for an initializer matching
1133: * the argument list signature.
1134: *
1135: * @param sig encoded argument list signature
1136: * @return method information, or <code>null</code> if method not found
1137: */
1138:
1139: public ClassItem getInitializerMethod(String sig) {
1140:
1141: // only check if loadable class
1142: if (m_curClass != null) {
1143:
1144: // check for match to method defined in class
1145: Method[] methods = getMethods();
1146: for (int i = 0; i < methods.length; i++) {
1147: Method method = methods[i];
1148: if (method.getName().equals("<init>")) {
1149: if (method.getSignature().startsWith(sig)) {
1150: ClassItem item = (ClassItem) m_itemMap
1151: .get(method);
1152: if (item == null) {
1153: item = new ClassItem("<init>", this , method);
1154: m_itemMap.put(method, item);
1155: }
1156: return item;
1157: }
1158: }
1159: }
1160:
1161: }
1162: return null;
1163: }
1164:
1165: /**
1166: * Get information for static method without respect to return value. This
1167: * can only be used with existing classes. Only the class itself is checked
1168: * for a method matching the supplied name and argument list signature.
1169: *
1170: * @param name method name
1171: * @param sig encoded argument list signature
1172: * @return method information, or <code>null</code> if method not found
1173: */
1174:
1175: public ClassItem getStaticMethod(String name, String sig) {
1176:
1177: // only check if loadable class
1178: if (m_curClass != null) {
1179:
1180: // check for match to method defined in class
1181: Method[] methods = getMethods();
1182: for (int i = 0; i < methods.length; i++) {
1183: Method method = methods[i];
1184: if (method.getName().equals(name) && method.isStatic()) {
1185: if (method.getSignature().startsWith(sig)) {
1186: ClassItem item = (ClassItem) m_itemMap
1187: .get(method);
1188: if (item == null) {
1189: item = new ClassItem(name, this , method);
1190: m_itemMap.put(method, item);
1191: }
1192: return item;
1193: }
1194: }
1195: }
1196:
1197: }
1198: return null;
1199: }
1200:
1201: /**
1202: * Get all binding methods currently defined in class. Binding methods are
1203: * generally identified by a supplied prefix, but additional methods
1204: * can be specified the the combination of exact name and signature. This
1205: * is a little kludgy, but necessary to handle the "marshal" method added
1206: * to mapped classes.
1207: *
1208: * @param prefix identifying prefix for binding methods
1209: * @param matches pairs of method name and signature to be matched as
1210: * exceptions to the prefix matching
1211: * @return existing binding methods
1212: */
1213:
1214: public ExistingMethod[] getBindingMethods(String prefix,
1215: String[] matches) {
1216:
1217: // return empty array if newly created class or unloadable class
1218: if (m_curClass == null) {
1219: return EMPTY_METHOD_ARRAY;
1220: }
1221:
1222: // check for binding methods defined in class
1223: Method[] methods = getMethods();
1224: int count = 0;
1225: for (int i = 0; i < methods.length; i++) {
1226: Method method = methods[i];
1227: String name = method.getName();
1228: if (name.startsWith(prefix)) {
1229: count++;
1230: } else {
1231: String sig = method.getSignature();
1232: for (int j = 0; j < matches.length; j += 2) {
1233: if (name.equals(matches[j])
1234: && sig.equals(matches[j + 1])) {
1235: count++;
1236: break;
1237: }
1238: }
1239: }
1240: }
1241:
1242: // generate array of methods found
1243: if (count == 0) {
1244: return EMPTY_METHOD_ARRAY;
1245: } else {
1246: ExistingMethod[] exists = new ExistingMethod[count];
1247: int fill = 0;
1248: for (int i = 0; i < methods.length; i++) {
1249: Method method = methods[i];
1250: String name = method.getName();
1251: boolean match = name.startsWith(prefix);
1252: if (!match) {
1253: String sig = method.getSignature();
1254: for (int j = 0; j < matches.length; j += 2) {
1255: if (name.equals(matches[j])
1256: && sig.equals(matches[j + 1])) {
1257: match = true;
1258: break;
1259: }
1260: }
1261: }
1262: if (match) {
1263: ClassItem item = (ClassItem) m_itemMap.get(method);
1264: if (item == null) {
1265: item = new ClassItem(name, this , method);
1266: m_itemMap.put(method, item);
1267: }
1268: exists[fill++] = new ExistingMethod(method, item,
1269: this );
1270: }
1271: }
1272: return exists;
1273: }
1274: }
1275:
1276: /**
1277: * Check accessible method. Check if a field or method in another class is
1278: * accessible from within this class.
1279: *
1280: * @param item field or method information
1281: * @return <code>true</code> if accessible, <code>false</code> if not
1282: */
1283:
1284: public boolean isAccessible(ClassItem item) {
1285: if (item.getClassFile() == this ) {
1286: return true;
1287: } else {
1288: int access = item.getAccessFlags();
1289: if ((access & Constants.ACC_PUBLIC) != 0) {
1290: return true;
1291: } else if ((access & Constants.ACC_PRIVATE) != 0) {
1292: return false;
1293: } else if (getPackage().equals(
1294: item.getClassFile().getPackage())) {
1295: return true;
1296: } else if ((access & Constants.ACC_PROTECTED) != 0) {
1297: ClassFile target = item.getClassFile();
1298: ClassFile ancestor = this ;
1299: while ((ancestor = ancestor.getSuperFile()) != null) {
1300: if (ancestor == target) {
1301: return true;
1302: }
1303: }
1304: return false;
1305: } else {
1306: return false;
1307: }
1308: }
1309: }
1310:
1311: /**
1312: * Get generator for modifying class.
1313: *
1314: * @return generator for class
1315: * @throws JiBXException if class not modifiable
1316: */
1317:
1318: private ClassGen getClassGen() throws JiBXException {
1319: if (m_genClass == null) {
1320: if (m_isWritable) {
1321: m_genClass = new ClassGen(m_curClass);
1322: m_genPool = m_genClass.getConstantPool();
1323: m_instBuilder = new InstructionBuilder(m_genClass,
1324: m_genPool);
1325: m_isHashCurrent = false;
1326: } else {
1327: throw new JiBXException("Cannot modify class " + m_name);
1328: }
1329: }
1330: return m_genClass;
1331: }
1332:
1333: /**
1334: * Get constant pool generator for modifying class.
1335: *
1336: * @return constant pool generator for class
1337: * @throws JiBXException if class not modifiable
1338: */
1339:
1340: public ConstantPoolGen getConstPoolGen() throws JiBXException {
1341: if (m_genPool == null) {
1342: getClassGen();
1343: }
1344: return m_genPool;
1345: }
1346:
1347: /**
1348: * Get instruction builder for modifying class.
1349: *
1350: * @return instruction builder for class
1351: * @throws JiBXException if class not modifiable
1352: */
1353:
1354: public InstructionBuilder getInstructionBuilder()
1355: throws JiBXException {
1356: if (m_instBuilder == null) {
1357: getClassGen();
1358: }
1359: return m_instBuilder;
1360: }
1361:
1362: /**
1363: * Add method to class.
1364: *
1365: * @param method method to be added
1366: * @return added method information
1367: * @throws JiBXException on error in adding method
1368: */
1369:
1370: public ClassItem addMethod(Method method) throws JiBXException {
1371: getClassGen().addMethod(method);
1372: setModified();
1373: String mname = method.getName();
1374: if (m_suffixMap != null && isSuffixName(mname)) {
1375: m_suffixMap.put(mname, method);
1376: }
1377: return new ClassItem(mname, this , method);
1378: }
1379:
1380: /**
1381: * Remove method from class.
1382: *
1383: * @param method method to be removed
1384: * @throws JiBXException on error in removing method
1385: */
1386:
1387: public void removeMethod(Method method) throws JiBXException {
1388: getClassGen().removeMethod(method);
1389: setModified();
1390: String mname = method.getName();
1391: if (m_suffixMap != null && isSuffixName(mname)) {
1392: m_suffixMap.remove(mname);
1393: }
1394: }
1395:
1396: /**
1397: * Add field to class with initial <code>String</code> value. If a field
1398: * with the same name already exists, it is overwritten.
1399: *
1400: * @param type fully qualified class name of field type
1401: * @param name field name
1402: * @param access access flags for field
1403: * @param init initial value for field
1404: * @return field information
1405: * @throws JiBXException if unable to add field
1406: */
1407:
1408: public ClassItem addField(String type, String name, int access,
1409: String init) throws JiBXException {
1410: deleteField(name);
1411: FieldGen fgen = new FieldGen(access, Type.getType(Utility
1412: .getSignature(type)), name, getConstPoolGen());
1413: fgen.setInitValue(init);
1414: Field field = fgen.getField();
1415: getClassGen().addField(field);
1416: m_isModified = true;
1417: m_isHashCurrent = false;
1418: return new ClassItem(name, this , field);
1419: }
1420:
1421: /**
1422: * Update class field with initial <code>String</code> value. If the field
1423: * already exists with the same characteristics it is left unchanged;
1424: * otherwise any existing field with the same name is overwritten.
1425: *
1426: * @param type fully qualified class name of field type
1427: * @param name field name
1428: * @param access access flags for field
1429: * @param init initial value for field
1430: * @return field information
1431: * @throws JiBXException if unable to add field
1432: */
1433:
1434: public ClassItem updateField(String type, String name, int access,
1435: String init) throws JiBXException {
1436:
1437: // first check for match with existing field
1438: Field[] fields = m_curClass.getFields();
1439: for (int i = 0; i < fields.length; i++) {
1440: Field field = fields[i];
1441: if (field.getName().equals(name)
1442: && field.getAccessFlags() == access) {
1443: String sig = field.getSignature();
1444: if (type.equals(Utility.signatureToString(sig, false))) {
1445: ConstantValue cval = field.getConstantValue();
1446: if (cval != null) {
1447: int index = cval.getConstantValueIndex();
1448: ConstantPool cp = m_curClass.getConstantPool();
1449: Constant cnst = cp.getConstant(index);
1450: if (cnst instanceof ConstantString) {
1451: Object value = ((ConstantString) cnst)
1452: .getConstantValue(cp);
1453: if (init.equals(value)) {
1454: return new ClassItem(name, this , field);
1455: }
1456: }
1457: }
1458: }
1459: }
1460: }
1461:
1462: // no exact match, so replace any existing field with same name
1463: deleteField(name);
1464: FieldGen fgen = new FieldGen(access, Type.getType(Utility
1465: .getSignature(type)), name, getConstPoolGen());
1466: fgen.setInitValue(init);
1467: Field field = fgen.getField();
1468: getClassGen().addField(field);
1469: m_isModified = true;
1470: m_isHashCurrent = false;
1471: return new ClassItem(name, this , field);
1472: }
1473:
1474: /**
1475: * Add field to class without initialization. If a field with the same name
1476: * already exists, it is overwritten.
1477: *
1478: * @param type fully qualified class name of field type
1479: * @param name field name
1480: * @param access access flags for field
1481: * @return field information
1482: * @throws JiBXException if unable to add field
1483: */
1484:
1485: public ClassItem addField(String type, String name, int access)
1486: throws JiBXException {
1487: deleteField(name);
1488: FieldGen fgen = new FieldGen(access, Type.getType(Utility
1489: .getSignature(type)), name, getConstPoolGen());
1490: Field field = fgen.getField();
1491: getClassGen().addField(field);
1492: m_isModified = true;
1493: m_isHashCurrent = false;
1494: return new ClassItem(name, this , field);
1495: }
1496:
1497: /**
1498: * Add private field to class without initialization. If a field
1499: * with the same name already exists, it is overwritten.
1500: *
1501: * @param type fully qualified class name of field type
1502: * @param name field name
1503: * @return field information
1504: * @throws JiBXException if unable to add field
1505: */
1506:
1507: public ClassItem addPrivateField(String type, String name)
1508: throws JiBXException {
1509: return addField(type, name, PRIVATEFIELD_ACCESS);
1510: }
1511:
1512: /**
1513: * Add default constructor to a class. The added default constructor just
1514: * calls the default constructor for the superclass. If the superclass
1515: * doesn't have a default constructor, this method is called recursively to
1516: * add one if possible.
1517: *
1518: * @return constructor information
1519: * @throws JiBXException if unable to add constructor
1520: */
1521:
1522: public ClassItem addDefaultConstructor() throws JiBXException {
1523: if (m_defaultConstructor == null) {
1524:
1525: // check for default constructor in superclass
1526: ClassItem cons = m_super Class.getInitializerMethod("()V");
1527: if (cons == null) {
1528: if (m_super Class.addDefaultConstructor() == null) {
1529: return null;
1530: }
1531: } else {
1532: cons.makeAccessible(this );
1533: }
1534:
1535: // add the public constructor method
1536: ExceptionMethodBuilder mb = new ExceptionMethodBuilder(
1537: "<init>", Type.VOID, new Type[0], this ,
1538: Constants.ACC_PUBLIC);
1539:
1540: // call the superclass constructor
1541: mb.appendLoadLocal(0);
1542: mb.appendCallInit(m_super Class.getName(), "()V");
1543:
1544: // finish with return
1545: mb.appendReturn();
1546: mb.codeComplete(false);
1547: m_defaultConstructor = mb.addMethod();
1548:
1549: }
1550: return m_defaultConstructor;
1551: }
1552:
1553: /**
1554: * Check if a method name matches the pattern for a generated unique suffix.
1555: *
1556: * @param name method name to be checked
1557: * @return <code>true</code> if name matches suffix pattern,
1558: * <code>false</code> if not
1559: */
1560:
1561: private static boolean isSuffixName(String name) {
1562: int last = name.length() - 1;
1563: for (int i = last; i > 0; i--) {
1564: char chr = name.charAt(i);
1565: if (chr == '_') {
1566: return i < last;
1567: } else if (!Character.isDigit(chr)) {
1568: break;
1569: }
1570: }
1571: return false;
1572: }
1573:
1574: /**
1575: * Make method name unique with generated suffix. The suffixed method name
1576: * is tracked so that it will not be used again.
1577: *
1578: * @param name base name before suffix is appended
1579: * @return name with unique suffix appended
1580: */
1581:
1582: public String makeUniqueMethodName(String name) {
1583:
1584: // check if map creation is needed
1585: if (m_suffixMap == null) {
1586: m_suffixMap = new HashMap();
1587: if (m_curClass != null) {
1588: Method[] methods = getMethods();
1589: for (int i = 0; i < methods.length; i++) {
1590: Method method = methods[i];
1591: String mname = method.getName();
1592: if (isSuffixName(mname)) {
1593: m_suffixMap.put(mname, method);
1594: }
1595: }
1596: }
1597: }
1598:
1599: // check if inheritance depth is needed
1600: if (m_inheritDepth == 0) {
1601: ClassFile cf = this ;
1602: while ((cf = cf.getSuperFile()) != null) {
1603: m_inheritDepth++;
1604: }
1605: }
1606:
1607: // generate suffix to make name unique, trying low values first
1608: while (true) {
1609: String uname = name + '_' + m_inheritDepth + '_'
1610: + m_uniqueIndex;
1611: if (m_suffixMap.get(uname) == null) {
1612: return uname;
1613: } else {
1614: m_uniqueIndex++;
1615: }
1616: }
1617: }
1618:
1619: /**
1620: * Delete field from class.
1621: *
1622: * @param name field name
1623: * @return <code>true</code> if field was present, <code>false</code> if not
1624: * @throws JiBXException if unable to delete field
1625: */
1626:
1627: public boolean deleteField(String name) throws JiBXException {
1628: ClassGen cg = getClassGen();
1629: Field field = cg.containsField(name);
1630: if (field == null) {
1631: return false;
1632: } else {
1633: cg.removeField(field);
1634: m_isModified = true;
1635: m_isHashCurrent = false;
1636: return true;
1637: }
1638: }
1639:
1640: /**
1641: * Get use count for class.
1642: *
1643: * @return use count for this class
1644: */
1645:
1646: public int getUseCount() {
1647: return m_useCount;
1648: }
1649:
1650: /**
1651: * Increment use count for class.
1652: *
1653: * @return use count (after increment)
1654: */
1655:
1656: public int incrementUseCount() {
1657: return ++m_useCount;
1658: }
1659:
1660: /**
1661: * Check if class has been modified.
1662: *
1663: * @return <code>true</code> if class is modified, <code>false</code> if not
1664: */
1665:
1666: public boolean isModified() {
1667: return m_isModified;
1668: }
1669:
1670: /**
1671: * Set class modified flag.
1672: */
1673:
1674: public void setModified() {
1675: m_isModified = true;
1676: MungedClass.addModifiedClass(this );
1677: }
1678:
1679: /**
1680: * Check if class is in complete state.
1681: *
1682: * @return <code>true</code> if class is complete, <code>false</code> if not
1683: */
1684:
1685: public boolean isComplete() {
1686: return m_genClass == null;
1687: }
1688:
1689: /**
1690: * Computes a hash code based on characteristics of the class. The
1691: * characteristics used in computing the hash code include the base class,
1692: * implemented interfaces, method names, field names, and package, but not
1693: * the actual class name or signature. The current static version of the
1694: * class is used for this computation, so if the class is being modified
1695: * {@link #codeComplete} should be called before this method. Note that this
1696: * is designed for use with the classes generated by JiBX, and is not
1697: * necessarily a good model for general usage.
1698: *
1699: * @return computed hash code value
1700: */
1701:
1702: protected int computeHashCode() {
1703:
1704: // start with basic characteristics of class
1705: int hash = getPackage().hashCode();
1706: ClassFile sfile = getSuperFile();
1707: if (sfile != null) {
1708: hash += sfile.getName().hashCode();
1709: }
1710: String[] intfs = getInterfaces();
1711: for (int i = 0; i < intfs.length; i++) {
1712: hash += intfs[i].hashCode();
1713: }
1714: hash += m_curClass.getAccessFlags();
1715:
1716: // include field and method names
1717: Field[] fields = m_curClass.getFields();
1718: for (int i = 0; i < fields.length; i++) {
1719: hash = hash * 49 + fields[i].getName().hashCode();
1720: }
1721: Method[] methods = m_curClass.getMethods();
1722: for (int i = 0; i < methods.length; i++) {
1723: hash = hash * 49 + methods[i].getName().hashCode();
1724: }
1725:
1726: // finish with constant table simple values (except name and signature)
1727: Constant[] cnsts = m_curClass.getConstantPool()
1728: .getConstantPool();
1729: for (int i = m_curClass.getClassNameIndex() + 1; i < cnsts.length; i++) {
1730: Constant cnst = cnsts[i];
1731: if (cnst != null) {
1732: int value = 0;
1733: switch (cnst.getTag()) {
1734: case Constants.CONSTANT_Double:
1735: value = (int) Double
1736: .doubleToRawLongBits(((ConstantDouble) cnst)
1737: .getBytes());
1738: break;
1739: case Constants.CONSTANT_Float:
1740: value = Float
1741: .floatToRawIntBits(((ConstantFloat) cnst)
1742: .getBytes());
1743: break;
1744: case Constants.CONSTANT_Integer:
1745: value = ((ConstantInteger) cnst).getBytes();
1746: break;
1747: case Constants.CONSTANT_Long:
1748: value = (int) ((ConstantLong) cnst).getBytes();
1749: break;
1750: case Constants.CONSTANT_Utf8:
1751: String text = ((ConstantUtf8) cnst).getBytes();
1752: if (!text.equals(m_signature)) {
1753: value = text.hashCode();
1754: }
1755: break;
1756: default:
1757: break;
1758: }
1759: hash = hash * 49 + value;
1760: }
1761: }
1762: // System.out.println("Hashed " + m_name + " to value " + hash);
1763: return hash;
1764: }
1765:
1766: /**
1767: * Finalize current modified state of class. This converts the modified
1768: * class state into a static form, then computes a hash code based on
1769: * characteristics of the class. If the class has not been modified it
1770: * just computes the hash code. Note that this won't initialize the array
1771: * of superinterfaces if used with an interface, but that shouldn't be a
1772: * problem since we don't create any interfaces.
1773: */
1774:
1775: public void codeComplete() {
1776: if (m_genClass != null) {
1777: m_curClass = m_genClass.getJavaClass();
1778: m_interfaceNames = m_curClass.getInterfaceNames();
1779: m_super Interfaces = new ClassFile[0];
1780: m_genClass = null;
1781: }
1782: }
1783:
1784: /**
1785: * Get hash code. This is based on most characteristics of the class,
1786: * including the actual methods, but excluding the class name. It is only
1787: * valid after the {@link #codeComplete} method is called.
1788: *
1789: * @return hash code based on code sequence
1790: */
1791:
1792: public int hashCode() {
1793: if (!m_isHashCurrent) {
1794: if (m_genClass != null) {
1795: throw new IllegalStateException(
1796: "Class still being constructed");
1797: }
1798: m_hashCode = computeHashCode();
1799: m_isHashCurrent = true;
1800: }
1801: return m_hashCode;
1802: }
1803:
1804: /**
1805: * Compare two field or method items to see if they're equal. This handles
1806: * only the comparisons that apply to both fields and methods. It does not
1807: * include comparing access flags, since these may be different due to
1808: * access requirements.
1809: *
1810: * @param a first field or method item
1811: * @param b second field or method item
1812: * @return <code>true</code> if the equal, <code>false</code> if not
1813: */
1814:
1815: public static boolean equalFieldOrMethods(FieldOrMethod a,
1816: FieldOrMethod b) {
1817: return a.getName().equals(b.getName())
1818: && a.getSignature().equals(b.getSignature());
1819: }
1820:
1821: /**
1822: * Compare two methods to see if they're equal. This checks only the details
1823: * of the exception handling and actual code, not the name or signature.
1824: *
1825: * @param a first method
1826: * @param b second method
1827: * @return <code>true</code> if the equal, <code>false</code> if not
1828: */
1829:
1830: public static boolean equalMethods(Method a, Method b) {
1831:
1832: // check the exceptions thrown by the method
1833: ExceptionTable etaba = a.getExceptionTable();
1834: ExceptionTable etabb = b.getExceptionTable();
1835: if (etaba != null && etabb != null) {
1836: String[] aexcepts = etaba.getExceptionNames();
1837: String[] bexcepts = etabb.getExceptionNames();
1838: if (!Arrays.equals(aexcepts, bexcepts)) {
1839: return false;
1840: }
1841: } else if (etaba != null || etabb != null) {
1842: return false;
1843: }
1844:
1845: // compare the exception handling details
1846: Code acode = a.getCode();
1847: Code bcode = b.getCode();
1848: CodeException[] acexs = acode.getExceptionTable();
1849: CodeException[] bcexs = bcode.getExceptionTable();
1850: if (acexs.length == bcexs.length) {
1851: for (int i = 0; i < acexs.length; i++) {
1852: CodeException acex = acexs[i];
1853: CodeException bcex = bcexs[i];
1854: if (acex.getCatchType() != bcex.getCatchType()
1855: || acex.getStartPC() != bcex.getStartPC()
1856: || acex.getEndPC() != bcex.getEndPC()
1857: || acex.getHandlerPC() != bcex.getHandlerPC()) {
1858: return false;
1859: }
1860: }
1861: }
1862:
1863: // finally compare the actual byte codes
1864: return Arrays.equals(acode.getCode(), bcode.getCode());
1865: }
1866:
1867: /**
1868: * Check if objects are equal. Compares first based on hash code, then on
1869: * the actual contents of the class, including package, implemented
1870: * interfaces, superclass, methods, and fields (but not the actual class
1871: * name). It is only valid after the {@link #codeComplete} method is called.
1872: *
1873: * @return <code>true</code> if equal objects, <code>false</code> if not
1874: */
1875:
1876: public boolean equals(Object obj) {
1877: if (obj instanceof ClassFile && obj.hashCode() == hashCode()) {
1878:
1879: // check basic details of the classes
1880: ClassFile comp = (ClassFile) obj;
1881: if (!org.jibx.runtime.Utility.isEqual(getPackage(), comp
1882: .getPackage())
1883: || getSuperFile() != comp.getSuperFile()
1884: || !Arrays.equals(getInterfaces(), comp
1885: .getInterfaces())) {
1886: return false;
1887: }
1888: JavaClass tjc = m_curClass;
1889: JavaClass cjc = comp.m_curClass;
1890: if (tjc.getAccessFlags() != cjc.getAccessFlags()) {
1891: return false;
1892: }
1893:
1894: // compare the defined fields
1895: Field[] tfields = tjc.getFields();
1896: Field[] cfields = cjc.getFields();
1897: if (tfields.length != cfields.length) {
1898: return false;
1899: }
1900: for (int i = 0; i < tfields.length; i++) {
1901: if (!equalFieldOrMethods(tfields[i], cfields[i])) {
1902: return false;
1903: }
1904: }
1905:
1906: // compare the defined methods
1907: Method[] tmethods = tjc.getMethods();
1908: Method[] cmethods = cjc.getMethods();
1909: if (tmethods.length != cmethods.length) {
1910: return false;
1911: }
1912: for (int i = 0; i < tmethods.length; i++) {
1913: Method tmethod = tmethods[i];
1914: Method cmethod = cmethods[i];
1915: if (!equalFieldOrMethods(tmethod, cmethod)
1916: || !equalMethods(tmethod, cmethod)) {
1917: return false;
1918: }
1919: }
1920:
1921: // finish with constant table values (correcting name and signature)
1922: Constant[] tcnsts = tjc.getConstantPool().getConstantPool();
1923: Constant[] ccnsts = cjc.getConstantPool().getConstantPool();
1924: if (tcnsts.length != ccnsts.length) {
1925: return false;
1926: }
1927: for (int i = tjc.getClassNameIndex() + 1; i < tcnsts.length; i++) {
1928: Constant tcnst = tcnsts[i];
1929: Constant ccnst = ccnsts[i];
1930: if (tcnst != null && ccnst != null) {
1931: int tag = tcnst.getTag();
1932: if (tag != ccnst.getTag()) {
1933: return false;
1934: }
1935: boolean equal = true;
1936: switch (tag) {
1937: case Constants.CONSTANT_Double:
1938: equal = ((ConstantDouble) tcnst).getBytes() == ((ConstantDouble) ccnst)
1939: .getBytes();
1940: break;
1941: case Constants.CONSTANT_Float:
1942: equal = ((ConstantFloat) tcnst).getBytes() == ((ConstantFloat) ccnst)
1943: .getBytes();
1944: break;
1945: case Constants.CONSTANT_Integer:
1946: equal = ((ConstantInteger) tcnst).getBytes() == ((ConstantInteger) ccnst)
1947: .getBytes();
1948: break;
1949: case Constants.CONSTANT_Long:
1950: equal = ((ConstantLong) tcnst).getBytes() == ((ConstantLong) ccnst)
1951: .getBytes();
1952: break;
1953: case Constants.CONSTANT_Utf8:
1954: String ttext = ((ConstantUtf8) tcnst)
1955: .getBytes();
1956: String ctext = ((ConstantUtf8) ccnst)
1957: .getBytes();
1958: if (ttext.equals(m_signature)) {
1959: equal = ctext.equals(comp.m_signature);
1960: } else {
1961: equal = ttext.equals(ctext);
1962: }
1963: break;
1964: default:
1965: break;
1966: }
1967: if (!equal) {
1968: return false;
1969: }
1970: } else if (tcnst != null || ccnst != null) {
1971: return false;
1972: }
1973: }
1974:
1975: // if nothing failed, the classes are equal
1976: return true;
1977:
1978: } else {
1979: return false;
1980: }
1981: }
1982:
1983: /**
1984: * Delete class file information. Deletes the class file for this class,
1985: * if it exists. Does nothing if no class file has been generated.
1986: */
1987:
1988: public void delete() {
1989: if (m_file.exists()) {
1990: m_file.delete();
1991: }
1992: }
1993:
1994: /**
1995: * Write out modified class information. Writes the modified class file to
1996: * an output stream.
1997: *
1998: * @param os output stream for writing modified class
1999: * @throws IOException if error writing to file
2000: */
2001:
2002: public void writeFile(OutputStream os) throws IOException {
2003: codeComplete();
2004: m_curClass.dump(new BufferedOutputStream(os));
2005: os.close();
2006: }
2007:
2008: /**
2009: * Write out modified class information. Writes the modified class file
2010: * back out to the original file. If the class file has not been modified,
2011: * the original file is kept unchanged.
2012: *
2013: * @throws IOException if error writing to file
2014: */
2015:
2016: public void writeFile() throws IOException {
2017: if (m_isModified) {
2018: OutputStream os = new FileOutputStream(m_file);
2019: writeFile(os);
2020: }
2021: }
2022:
2023: /**
2024: * Derive generated class name. This generates a JiBX class name from the
2025: * name of this class, using the supplied prefix and suffix information. The
2026: * derived class name is always in the same package as this class.
2027: *
2028: * @param prefix generated class name prefix
2029: * @param suffix generated class name suffix
2030: * @return derived class name
2031: */
2032:
2033: public String deriveClassName(String prefix, String suffix) {
2034: String pack = "";
2035: String tname = m_name;
2036: int split = tname.lastIndexOf('.');
2037: if (split >= 0) {
2038: pack = tname.substring(0, split + 1);
2039: tname = tname.substring(split + 1);
2040: }
2041: return pack + prefix + tname + suffix;
2042: }
2043:
2044: /**
2045: * Set class paths to be searched.
2046: *
2047: * @param paths ordered set of paths to be searched for class files
2048: */
2049:
2050: public static void setPaths(String[] paths) {
2051:
2052: // create full path string with separators for BCEL loader
2053: StringBuffer full = new StringBuffer();
2054: for (int i = 0; i < paths.length; i++) {
2055: if (i > 0) {
2056: full.append(File.pathSeparatorChar);
2057: }
2058: full.append(paths[i]);
2059: }
2060: s_loader = new ClassPath(full.toString());
2061:
2062: // create direct classloader for access to classes during binding
2063: URL[] urls = new URL[paths.length];
2064: try {
2065:
2066: // generate array of component file or directory URLs
2067: for (int i = 0; i < urls.length; i++) {
2068:
2069: // append trailing slash on directory paths
2070: String path = paths[i];
2071: int mark = path.lastIndexOf('/');
2072: if (path.indexOf('.', mark) < 0) {
2073: path = path + '/';
2074: }
2075: urls[i] = new URL("file:" + path);
2076: }
2077:
2078: // initialize classloader with full array of paths
2079: s_directLoader = new URLClassLoader(urls);
2080:
2081: } catch (MalformedURLException ex) {
2082: throw new IllegalArgumentException(
2083: "Error initializing classloading: "
2084: + ex.getMessage());
2085: }
2086: }
2087:
2088: /**
2089: * Try loading class from classpath.
2090: *
2091: * @param name fully qualified name of class to be loaded
2092: * @return loaded class, or <code>null</code> if not found
2093: */
2094:
2095: public static Class loadClass(String name) {
2096: try {
2097: return s_directLoader.loadClass(name);
2098: } catch (ClassNotFoundException ex) {
2099: return null;
2100: }
2101: }
2102: }
|