001: /*
002: Copyright (c) 2003-2005, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.binding.classes;
030:
031: import java.util.HashMap;
032:
033: import org.apache.bcel.Constants;
034: import org.apache.bcel.classfile.Attribute;
035: import org.apache.bcel.classfile.ExceptionTable;
036: import org.apache.bcel.classfile.FieldOrMethod;
037: import org.apache.bcel.classfile.LocalVariableTable;
038: import org.apache.bcel.classfile.Method;
039: import org.apache.bcel.classfile.Utility;
040: import org.apache.bcel.generic.ArrayType;
041: import org.apache.bcel.generic.ObjectType;
042: import org.apache.bcel.generic.Type;
043: import org.jibx.runtime.JiBXException;
044:
045: /**
046: * Wrapper for field or method information. Provides the information needed
047: * for access to either existing or added methods in existing classes.
048: *
049: * @author Dennis M. Sosnoski
050: * @version 1.0
051: */
052:
053: public class ClassItem {
054: /** Empty array of strings. */
055: private static final String[] EMPTY_STRING_ARRAY = new String[0];
056:
057: /** Tag value for signature attribute. */
058: private static final byte SIGNATURE_ATTRIBUTE_TAG = 10;
059:
060: /** Map for primitive type signature variants. */
061: private static HashMap s_primitiveMap = new HashMap();
062:
063: /** Map from type name to BCEL type. */
064: private static HashMap s_typeMap = new HashMap();
065:
066: /** Map from method signature to array of argument types. */
067: private static HashMap s_signatureParamsMap = new HashMap();
068:
069: /** Map from method signature to return type. */
070: private static HashMap s_signatureTypeMap = new HashMap();
071:
072: static {
073: s_primitiveMap.put("boolean", new String[] { "Z", "I" });
074: s_primitiveMap.put("byte", new String[] { "B", "S", "I" });
075: s_primitiveMap.put("char", new String[] { "C", "I" });
076: s_primitiveMap.put("double", new String[] { "D" });
077: s_primitiveMap.put("float", new String[] { "F" });
078: s_primitiveMap.put("int", new String[] { "I" });
079: s_primitiveMap.put("long", new String[] { "J" });
080: s_primitiveMap.put("short", new String[] { "S", "I" });
081: s_primitiveMap.put("void", new String[] { "V" });
082: s_typeMap.put("boolean", Type.BOOLEAN);
083: s_typeMap.put("byte", Type.BYTE);
084: s_typeMap.put("char", Type.CHAR);
085: s_typeMap.put("double", Type.DOUBLE);
086: s_typeMap.put("float", Type.FLOAT);
087: s_typeMap.put("int", Type.INT);
088: s_typeMap.put("long", Type.LONG);
089: s_typeMap.put("short", Type.SHORT);
090: s_typeMap.put("void", Type.VOID);
091: }
092:
093: /** Owning class information. */
094: private ClassFile m_classFile;
095:
096: /** Item name. */
097: private String m_name;
098:
099: /** Encoded signature. */
100: private String m_signature;
101:
102: /** Fully qualified class name of item type. */
103: private String m_typeName;
104:
105: /** Argument types for method. */
106: private String[] m_argTypes;
107:
108: /** Wrapped existing item. */
109: private FieldOrMethod m_item;
110:
111: /**
112: * Constructor. Builds a wrapper for an item based on an existing field or
113: * method.
114: *
115: * @param name field or method name
116: * @param cf owning class information
117: * @param item field or method information
118: */
119:
120: public ClassItem(String name, ClassFile cf, FieldOrMethod item) {
121: m_classFile = cf;
122: m_name = name;
123: m_item = item;
124: m_signature = item.getSignature();
125: if (item instanceof Method) {
126: m_typeName = getTypeFromSignature(m_signature);
127: m_argTypes = getParametersFromSignature(m_signature);
128: } else {
129: m_typeName = Utility.signatureToString(m_signature, false);
130: }
131: }
132:
133: /**
134: * Get owning class information.
135: *
136: * @return owning class information
137: */
138:
139: public ClassFile getClassFile() {
140: return m_classFile;
141: }
142:
143: /**
144: * Get item name.
145: *
146: * @return item name
147: */
148:
149: public String getName() {
150: return m_name;
151: }
152:
153: /**
154: * Get item type as fully qualified class name.
155: *
156: * @return item type name
157: */
158:
159: public String getTypeName() {
160: return m_typeName;
161: }
162:
163: /**
164: * Get number of arguments for method.
165: *
166: * @return argument count for method, or zero if not a method
167: */
168:
169: public int getArgumentCount() {
170: if (m_item instanceof Method) {
171: return m_argTypes.length;
172: } else {
173: return 0;
174: }
175: }
176:
177: /**
178: * Get argument type as fully qualified class name.
179: *
180: * @param index argument number
181: * @return argument type name
182: */
183:
184: public String getArgumentType(int index) {
185: if (m_item instanceof Method) {
186: return m_argTypes[index];
187: } else {
188: throw new IllegalArgumentException(
189: "Internal error: not a method");
190: }
191: }
192:
193: /**
194: * Get method parameter name.
195: *
196: * @return parameter name
197: */
198:
199: public String getParameterName(int index) {
200: if (m_item instanceof Method) {
201: int position = index;
202: if (!isStatic()) {
203: position++;
204: }
205: LocalVariableTable vtab = ((Method) m_item)
206: .getLocalVariableTable();
207: if (vtab == null) {
208: return null;
209: } else {
210: return vtab.getLocalVariable(position).getName();
211: }
212: } else {
213: throw new IllegalArgumentException(
214: "Internal error: not a method");
215: }
216: }
217:
218: /**
219: * Get argument types as array of fully qualified class names.
220: *
221: * @return array of argument types
222: */
223:
224: public String[] getArgumentTypes() {
225: if (m_item instanceof Method) {
226: return m_argTypes;
227: } else {
228: return null;
229: }
230: }
231:
232: /**
233: * Get access flags.
234: *
235: * @return flags for access type of field or method
236: */
237:
238: public int getAccessFlags() {
239: return m_item.getAccessFlags();
240: }
241:
242: /**
243: * Set access flags.
244: *
245: * @param flags access flags for field or method
246: */
247:
248: public void setAccessFlags(int flags) {
249: m_item.setAccessFlags(flags);
250: m_classFile.setModified();
251: }
252:
253: /**
254: * Make accessible item. Check if this field or method is accessible from
255: * another class, and if not decreases the access restrictions to make it
256: * accessible.
257: *
258: * @param src class file for required access
259: * @throws JiBXException if cannot be accessed
260: */
261:
262: public void makeAccessible(ClassFile src) throws JiBXException {
263:
264: // no need to change if already public access
265: int access = getAccessFlags();
266: if ((access & Constants.ACC_PUBLIC) == 0) {
267:
268: // check for same package as most restrictive case
269: ClassFile dest = getClassFile();
270: if (dest.getPackage().equals(src.getPackage())) {
271: if ((access & Constants.ACC_PRIVATE) != 0) {
272: access = access - Constants.ACC_PRIVATE;
273: }
274: } else {
275:
276: // check if access is from a subclass of this method class
277: ClassFile ancestor = src;
278: while ((ancestor = ancestor.getSuperFile()) != null) {
279: if (ancestor == dest) {
280: break;
281: }
282: }
283:
284: // handle access adjustments based on subclass status
285: if (ancestor == null) {
286: int clear = Constants.ACC_PRIVATE
287: | Constants.ACC_PROTECTED;
288: access = (access & ~clear) | Constants.ACC_PUBLIC;
289: } else if ((access & Constants.ACC_PROTECTED) == 0) {
290: access = (access & ~Constants.ACC_PRIVATE)
291: | Constants.ACC_PROTECTED;
292: }
293: }
294:
295: // set new access flags
296: if (access != getAccessFlags()) {
297: if (dest.isModifiable()) {
298: setAccessFlags(access);
299: } else {
300: throw new JiBXException(
301: "Unable to change access permissions for "
302: + getName() + " in class "
303: + src.getName());
304: }
305: }
306: }
307: }
308:
309: /**
310: * Check if item is a static.
311: *
312: * @return <code>true</code> if a static, <code>false</code> if member
313: */
314:
315: public boolean isStatic() {
316: return (getAccessFlags() & Constants.ACC_STATIC) != 0;
317: }
318:
319: /**
320: * Get method signature.
321: *
322: * @return encoded method signature
323: */
324:
325: public String getSignature() {
326: return m_signature;
327: }
328:
329: /**
330: * Check if item is a method.
331: *
332: * @return <code>true</code> if a method, <code>false</code> if a field
333: */
334:
335: public boolean isMethod() {
336: return m_item == null || m_item instanceof Method;
337: }
338:
339: /**
340: * Check if item is an initializer.
341: *
342: * @return <code>true</code> if an initializer, <code>false</code> if a
343: * field or normal method
344: */
345:
346: public boolean isInitializer() {
347: return m_item != null && m_item.getName().equals("<init>");
348: }
349:
350: /**
351: * Get names of exceptions thrown by method.
352: *
353: * @return array of exceptions thrown by method, or <code>null</code> if
354: * a field
355: */
356:
357: public String[] getExceptions() {
358: if (m_item instanceof Method) {
359: ExceptionTable etab = ((Method) m_item).getExceptionTable();
360: if (etab != null) {
361: return etab.getExceptionNames();
362: } else {
363: return EMPTY_STRING_ARRAY;
364: }
365: }
366: return null;
367: }
368:
369: /**
370: * Get the generics signature information for item.
371: *
372: * @return generics signature (<code>null</code> if none)
373: */
374: public String getGenericsSignature() {
375: Attribute[] attrs = m_item.getAttributes();
376: for (int j = 0; j < attrs.length; j++) {
377: Attribute attr = attrs[j];
378: if (attr.getTag() == SIGNATURE_ATTRIBUTE_TAG) {
379: return attr.toString();
380: }
381: }
382: return null;
383: }
384:
385: /**
386: * Check if type name is a primitive.
387: *
388: * @return <code>true</code> if a primitive, <code>false</code> if not
389: */
390:
391: public static boolean isPrimitive(String type) {
392: return s_primitiveMap.get(type) != null;
393: }
394:
395: /**
396: * Get the signature for a primitive.
397: *
398: * @return signature for a primitive type
399: */
400:
401: public static String getPrimitiveSignature(String type) {
402: return ((String[]) s_primitiveMap.get(type))[0];
403: }
404:
405: /**
406: * Get parameter type names from method signature.
407: *
408: * @param sig method signature to be decoded
409: * @return array of argument type names
410: */
411:
412: public static String[] getParametersFromSignature(String sig) {
413: String[] types = (String[]) s_signatureParamsMap.get(sig);
414: if (types == null) {
415: types = Utility.methodSignatureArgumentTypes(sig, false);
416: s_signatureParamsMap.put(sig, types);
417: }
418: return types;
419: }
420:
421: /**
422: * Get return type names from method signature.
423: *
424: * @param sig method signature to be decoded
425: * @return return type name
426: */
427:
428: public static String getTypeFromSignature(String sig) {
429: String type = (String) s_signatureTypeMap.get(sig);
430: if (type == null) {
431: type = Utility.methodSignatureReturnType(sig, false);
432: s_signatureTypeMap.put(sig, type);
433: }
434: return type;
435: }
436:
437: /**
438: * Create type from name.
439: *
440: * @param name fully qualified type name
441: * @return corresponding type
442: */
443:
444: public static Type typeFromName(String name) {
445:
446: // first check for type already created
447: Type type = (Type) s_typeMap.get(name);
448: if (type == null) {
449:
450: // new type, strip off array dimensions
451: int dimen = 0;
452: String base = name;
453: while (base.endsWith("[]")) {
454: dimen++;
455: base = base.substring(0, base.length() - 2);
456: }
457:
458: // check for base type defined if array
459: if (dimen > 0) {
460: type = (Type) s_typeMap.get(base);
461: }
462:
463: // create and record base type if new
464: if (type == null) {
465: type = new ObjectType(base);
466: s_typeMap.put(base, type);
467: }
468:
469: // create and record array type
470: if (dimen > 0) {
471: type = new ArrayType(type, dimen);
472: s_typeMap.put(name, type);
473: }
474: }
475: return type;
476: }
477:
478: /**
479: * Get virtual method by fully qualified name. This splits the class
480: * name from the method name, finds the class, and then tries to find a
481: * matching method name in that class or a superclass.
482: *
483: * @param name fully qualified class and method name
484: * @param sigs possible method signatures
485: * @return information for the method, or <code>null</code> if not found
486: * @throws JiBXException if configuration error
487: */
488:
489: public static ClassItem findVirtualMethod(String name, String[] sigs)
490: throws JiBXException {
491:
492: // get the class containing the method
493: int split = name.lastIndexOf('.');
494: String cname = name.substring(0, split);
495: String mname = name.substring(split + 1);
496: ClassFile cf = ClassCache.getClassFile(cname);
497:
498: // find the method in class or superclass
499: for (int i = 0; i < sigs.length; i++) {
500: ClassItem method = cf.getMethod(mname, sigs[i]);
501: if (method != null) {
502: return method;
503: }
504: }
505: return null;
506: }
507:
508: /**
509: * Get static method by fully qualified name. This splits the class
510: * name from the method name, finds the class, and then tries to find a
511: * matching method name in that class.
512: *
513: * @param name fully qualified class and method name
514: * @param sigs possible method signatures
515: * @return information for the method, or <code>null</code> if not found
516: * @throws JiBXException if configuration error
517: */
518:
519: public static ClassItem findStaticMethod(String name, String[] sigs)
520: throws JiBXException {
521:
522: // get the class containing the method
523: int split = name.lastIndexOf('.');
524: String cname = name.substring(0, split);
525: String mname = name.substring(split + 1);
526: ClassFile cf = ClassCache.getClassFile(cname);
527:
528: // find the method in class or superclass
529: for (int i = 0; i < sigs.length; i++) {
530: ClassItem method = cf.getStaticMethod(mname, sigs[i]);
531: if (method != null) {
532: return method;
533: }
534: }
535: return null;
536: }
537:
538: /**
539: * Get all variant signatures for a fully qualified class name. The
540: * returned array gives all signatures (for interfaces or classes) which
541: * instances of the class can match.
542: *
543: * @param name fully qualified class name
544: * @return possible signature variations for instances of the class
545: * @throws JiBXException if configuration error
546: */
547:
548: public static String[] getSignatureVariants(String name)
549: throws JiBXException {
550: Object obj = s_primitiveMap.get(name);
551: if (obj == null) {
552: ClassFile cf = ClassCache.getClassFile(name);
553: return cf.getInstanceSigs();
554: } else {
555: return (String[]) obj;
556: }
557: }
558:
559: /**
560: * Check if a value of one type can be directly assigned to another type.
561: * This is basically the equivalent of the instanceof operator, but with
562: * application to primitive types as well as object types.
563: *
564: * @param from fully qualified class name of initial type
565: * @param to fully qualified class name of assignment type
566: * @return <code>true</code> if assignable, <code>false</code> if not
567: * @throws JiBXException if configuration error
568: */
569:
570: public static boolean isAssignable(String from, String to)
571: throws JiBXException {
572:
573: // always assignable if the two are the same
574: if (from.equals(to)) {
575: return true;
576: } else {
577:
578: // try direct lookup for primitive types
579: Object fobj = s_primitiveMap.get(from);
580: Object tobj = s_primitiveMap.get(to);
581: if (fobj == null && tobj == null) {
582:
583: // assignable if from type has to as a possible signature
584: ClassFile cf = ClassCache.getClassFile(from);
585: String[] sigs = cf.getInstanceSigs();
586: String match = Utility.getSignature(to);
587: for (int i = 0; i < sigs.length; i++) {
588: if (match.equals(sigs[i])) {
589: return true;
590: }
591: }
592: return false;
593:
594: } else if (fobj != null && tobj != null) {
595:
596: // assignable if from type has to as a possible signature
597: String[] fsigs = (String[]) fobj;
598: String[] tsigs = (String[]) tobj;
599: if (tsigs.length == 1) {
600: for (int i = 0; i < fsigs.length; i++) {
601: if (fsigs[i] == tsigs[0]) {
602: return true;
603: }
604: }
605: }
606: return false;
607:
608: } else {
609:
610: // primitive and object types never assignable
611: return false;
612:
613: }
614: }
615: }
616: }
|