001: /*
002: * Copyright (c) 2003-2005, Dennis M. Sosnoski All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer. Redistributions in binary
009: * form must reproduce the above copyright notice, this list of conditions and
010: * the following disclaimer in the documentation and/or other materials provided
011: * with the distribution. Neither the name of JiBX nor the names of its
012: * contributors may be used to endorse or promote products derived from this
013: * software without specific prior written permission.
014: *
015: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
016: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
017: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
018: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
019: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
020: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
021: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
022: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
023: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
024: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
025: * POSSIBILITY OF SUCH DAMAGE.
026: */
027:
028: package org.jibx.binding.classes;
029:
030: import java.io.File;
031: import java.util.HashMap;
032:
033: import org.apache.bcel.Constants;
034: import org.apache.bcel.classfile.Utility;
035: import org.apache.bcel.generic.Type;
036:
037: import org.jibx.binding.def.BindingDefinition;
038: import org.jibx.runtime.JiBXException;
039:
040: /**
041: * Bound class handler. Each instance controls and organizes information for a
042: * class included in one or more binding definitions.
043: *
044: * @author Dennis M. Sosnoski
045: * @version 1.0
046: */
047:
048: public class BoundClass {
049: //
050: // Constants and such related to code generation.
051:
052: /** Class used for code munging when no specific class available. */
053: private static final String GENERIC_MUNGE_CLASS = BindingDefinition.GENERATE_PREFIX
054: + "MungeAdapter";
055:
056: /** Prefix used for access methods. */
057: private static final String ACCESS_PREFIX = BindingDefinition.GENERATE_PREFIX
058: + "access_";
059:
060: /** Empty argument type array. */
061: private static final Type[] EMPTY_TYPE_ARGS = {};
062:
063: //
064: // Static data.
065:
066: /**
067: * Map from bound class name (or bound and munged combination) to binding
068: * information.
069: */
070: private static HashMap s_nameMap;
071:
072: /** Package of first modifiable class. */
073: private static String s_modifyPackage;
074:
075: /** Root for package of first modifiable class. */
076: private static File s_modifyRoot;
077:
078: /** Class used for code generation proxy with unmodifiable classes. */
079: private static MungedClass s_genericMunge;
080:
081: //
082: // Actual instance data.
083:
084: /** Bound class file information. */
085: private final ClassFile m_boundClass;
086:
087: /** Class receiving code generated for target class. */
088: private final MungedClass m_mungedClass;
089:
090: /**
091: * Map from field or method to load access method (lazy create,
092: * <code>null</code> if not used).
093: */
094: private HashMap m_loadMap;
095:
096: /**
097: * Map from field or method to store access method (lazy create,
098: * <code>null</code> if not used).
099: */
100: private HashMap m_storeMap;
101:
102: /**
103: * Constructor.
104: *
105: * @param bound target class file information
106: * @param munge class file for class hosting generated code
107: */
108:
109: private BoundClass(ClassFile bound, MungedClass munge) {
110: m_boundClass = bound;
111: m_mungedClass = munge;
112: }
113:
114: /**
115: * Get bound class file information.
116: *
117: * @return class file information for bound class
118: */
119:
120: public ClassFile getClassFile() {
121: return m_boundClass;
122: }
123:
124: /**
125: * Get bound class file name.
126: *
127: * @return name of bound class
128: */
129:
130: public String getClassName() {
131: return m_boundClass.getName();
132: }
133:
134: /**
135: * Get munged class file information.
136: *
137: * @return class file information for class being modified
138: */
139:
140: public ClassFile getMungedFile() {
141: return m_mungedClass.getClassFile();
142: }
143:
144: /**
145: * Check if class being changed directly.
146: *
147: * @return <code>true</code> if bound class is being modified,
148: * <code>false</code> if using a surrogate
149: */
150:
151: public boolean isDirectAccess() {
152: return m_boundClass == m_mungedClass.getClassFile();
153: }
154:
155: /**
156: * Get load access method for member of this class. If the access method
157: * does not already exist it's created by this call. If the access method
158: * does exist but without access from the context class, the access
159: * permission on the method is broadened (from package to protected or
160: * public, or from protected to public).
161: *
162: * @param item field or method to be accessed
163: * @param from context class from which access is required
164: * @return the item itself if it's accessible from the required context, an
165: * access method that is accessible if the item is not itself
166: * @throws JiBXException on configuration error
167: */
168:
169: public ClassItem getLoadMethod(ClassItem item, ClassFile from)
170: throws JiBXException {
171:
172: // initialize tracking information for access methods if first time
173: if (m_loadMap == null) {
174: m_loadMap = new HashMap();
175: }
176:
177: // check if a new access method needed
178: BindingMethod method = (BindingMethod) m_loadMap.get(item);
179: if (method == null) {
180:
181: // set up for constructing new method
182: String name = ACCESS_PREFIX + "load_" + item.getName();
183: ClassFile cf = item.getClassFile();
184: Type type = Type.getType(Utility.getSignature(item
185: .getTypeName()));
186: MethodBuilder mb = new ExceptionMethodBuilder(name, type,
187: EMPTY_TYPE_ARGS, cf, (short) 0);
188:
189: // add the actual access method code
190: mb.appendLoadLocal(0);
191: if (item.isMethod()) {
192: mb.addMethodExceptions(item);
193: mb.appendCall(item);
194: } else {
195: mb.appendGetField(item);
196: }
197: mb.appendReturn(type);
198:
199: // track unique instance of this method
200: method = m_mungedClass.getUniqueMethod(mb, true);
201: m_loadMap.put(item, method);
202: }
203:
204: // make sure method is accessible
205: method.makeAccessible(from);
206: return method.getItem();
207: }
208:
209: /**
210: * Get store access method for member of this class. If the access method
211: * does not already exist it's created by this call. If the access method
212: * does exist but without access from the context class, the access
213: * permission on the method is broadened (from package to protected or
214: * public, or from protected to public).
215: *
216: * @param item field or method to be accessed
217: * @param from context class from which access is required
218: * @return the item itself if it's accessible from the required context, an
219: * access method that is accessible if the item is not itself
220: * @throws JiBXException on configuration error
221: */
222:
223: public ClassItem getStoreMethod(ClassItem item, ClassFile from)
224: throws JiBXException {
225:
226: // initialize tracking information for access methods if first time
227: if (m_storeMap == null) {
228: m_storeMap = new HashMap();
229: }
230:
231: // check if a new access method needed
232: BindingMethod method = (BindingMethod) m_storeMap.get(item);
233: if (method == null) {
234:
235: // set up for constructing new method
236: String name = ACCESS_PREFIX + "store_" + item.getName();
237: ClassFile cf = item.getClassFile();
238: Type type;
239: if (item.isMethod()) {
240: String sig = item.getSignature();
241: int start = sig.indexOf('(');
242: int end = sig.indexOf(')');
243: type = Type.getType(sig.substring(start + 1, end));
244: } else {
245: type = Type.getType(Utility.getSignature(item
246: .getTypeName()));
247: }
248: MethodBuilder mb = new ExceptionMethodBuilder(name,
249: Type.VOID, new Type[] { type }, cf, (short) 0);
250:
251: // add the actual access method code
252: mb.appendLoadLocal(0);
253: mb.appendLoadLocal(1);
254: if (item.isMethod()) {
255: mb.addMethodExceptions(item);
256: mb.appendCall(item);
257: } else {
258: mb.appendPutField(item);
259: }
260: mb.appendReturn();
261:
262: // track unique instance of this method
263: method = m_mungedClass.getUniqueMethod(mb, true);
264: m_storeMap.put(item, method);
265: }
266:
267: // make sure method is accessible
268: method.makeAccessible(from);
269: return method.getItem();
270: }
271:
272: /**
273: * Get unique method. Just delegates to the modified class handling, with
274: * unique suffix appended to method name.
275: *
276: * @param builder method to be defined
277: * @return defined method item
278: * @throws JiBXException on configuration error
279: */
280:
281: public BindingMethod getUniqueMethod(MethodBuilder builder)
282: throws JiBXException {
283: return m_mungedClass.getUniqueMethod(builder, true);
284: }
285:
286: /**
287: * Get unique method. Just delegates to the modified class handling. The
288: * supplied name is used without change.
289: *
290: * @param builder method to be defined
291: * @return defined method item
292: * @throws JiBXException on configuration error
293: */
294:
295: public BindingMethod getUniqueNamed(MethodBuilder builder)
296: throws JiBXException {
297: return m_mungedClass.getUniqueMethod(builder, false);
298: }
299:
300: /**
301: * Add binding factory to class. Makes sure that there's no surrogate class
302: * for code generation, then delegates to the modified class handling.
303: *
304: * @param fact binding factory name
305: */
306:
307: public void addFactory(String fact) {
308: if (isDirectAccess()) {
309: m_mungedClass.addFactory(fact);
310: } else {
311: throw new IllegalStateException(
312: "Internal error: not directly modifiable class");
313: }
314: }
315:
316: /**
317: * Generate factory list. Makes sure that there's no surrogate class for
318: * code generation, then delegates to the modified class handling.
319: *
320: * @throws JiBXException on configuration error
321: */
322:
323: public void setFactoryList() throws JiBXException {
324: if (isDirectAccess()) {
325: m_mungedClass.setFactoryList();
326: } else {
327: throw new IllegalStateException(
328: "Internal error: not directly modifiable class");
329: }
330: }
331:
332: /**
333: * Create binding information for class. This creates the combination of
334: * bound class and (if different) munged class and adds it to the internal
335: * tables.
336: *
337: * @param key text identifier for this bound class and munged class
338: * combination
339: * @param bound class information for bound class
340: * @param munge information for surrogate class receiving generated code, or
341: * <code>null</code> if no separate class
342: * @return binding information for class
343: */
344:
345: private static BoundClass createInstance(String key,
346: ClassFile bound, MungedClass munge) {
347: BoundClass inst = new BoundClass(bound, munge);
348: s_nameMap.put(key, inst);
349: return inst;
350: }
351:
352: /**
353: * Find or create binding information for class. If the combination of bound
354: * class and munged class already exists it's returned directly, otherwise
355: * it's created and returned.
356: *
357: * @param bound class information for bound class
358: * @param munge information for surrogate class receiving generated code
359: * @return binding information for class
360: */
361:
362: private static BoundClass findOrCreateInstance(ClassFile bound,
363: MungedClass munge) {
364: String key = bound.getName() + ':'
365: + munge.getClassFile().getName();
366: BoundClass inst = (BoundClass) s_nameMap.get(key);
367: if (inst == null) {
368: inst = createInstance(key, bound, munge);
369: }
370: return inst;
371: }
372:
373: /**
374: * Get binding information for class. This finds the class in which code
375: * generation for the target class takes place. Normally this class will be
376: * the target class itself, but in cases where the target class is not
377: * modifiable an alternate class will be used. This can take two forms. If
378: * the context class is provided and it is a subclass of the target class,
379: * code for the target class is instead added to the context class. If there
380: * is no context class, or if the context class is not a subclass of the
381: * target class, a unique catch-all class is used.
382: *
383: * @param cf bound class information
384: * @param context context class for code generation, or <code>null</code>
385: * if no context
386: * @return binding information for class
387: * @throws JiBXException on configuration error
388: */
389:
390: public static BoundClass getInstance(ClassFile cf,
391: BoundClass context) throws JiBXException {
392:
393: // check if new instance needed for this class
394: BoundClass inst = (BoundClass) s_nameMap.get(cf.getName());
395: if (inst == null) {
396:
397: // load the basic class information and check for modifiable
398: if (!cf.isInterface() && cf.isModifiable()) {
399:
400: // return instance directly
401: inst = createInstance(cf.getName(), cf, MungedClass
402: .getInstance(cf));
403:
404: } else {
405:
406: // see if the context class is a subclass
407: if (context != null
408: && context.getClassFile().isSuperclass(
409: cf.getName())) {
410:
411: // find or create munge with subclass as surrogate
412: inst = findOrCreateInstance(cf,
413: context.m_mungedClass);
414:
415: } else {
416:
417: // use catch-all munge class as surrogate for all else
418: if (s_genericMunge == null) {
419: String mname;
420: if (s_modifyPackage == null) {
421: mname = GENERIC_MUNGE_CLASS;
422: MungedClass
423: .checkDirectory(s_modifyRoot, "");
424: } else {
425: mname = s_modifyPackage + '.'
426: + GENERIC_MUNGE_CLASS;
427: MungedClass.checkDirectory(s_modifyRoot,
428: s_modifyPackage);
429: }
430: ClassFile base = ClassCache
431: .getClassFile("java.lang.Object");
432: int acc = Constants.ACC_PUBLIC
433: | Constants.ACC_ABSTRACT;
434: ClassFile gen = new ClassFile(mname,
435: s_modifyRoot, base, acc, new String[0]);
436: gen.addDefaultConstructor();
437: s_genericMunge = MungedClass.getInstance(gen);
438: MungedClass.delayedAddUnique(gen);
439: }
440: inst = findOrCreateInstance(cf, s_genericMunge);
441:
442: }
443: }
444: }
445: return inst;
446: }
447:
448: /**
449: * Get binding information for class. This version takes a fully-qualified
450: * class name, calling the paired method if necessary to create a new
451: * instance.
452: *
453: * @param name fully qualified name of bound class
454: * @param context context class for code generation, or <code>null</code>
455: * if no context
456: * @return binding information for class
457: * @throws JiBXException on configuration error
458: */
459:
460: public static BoundClass getInstance(String name, BoundClass context)
461: throws JiBXException {
462:
463: // check if new instance needed for this class
464: BoundClass inst = (BoundClass) s_nameMap.get(name);
465: if (inst == null) {
466: ClassFile cf = ClassCache.getClassFile(name);
467: return getInstance(cf, context);
468: }
469: return inst;
470: }
471:
472: /**
473: * Discard cached information and reset in preparation for a new binding
474: * run.
475: */
476:
477: public static void reset() {
478: s_nameMap = new HashMap();
479: s_modifyPackage = null;
480: s_modifyRoot = null;
481: s_genericMunge = null;
482: }
483:
484: /**
485: * Discard cached information and reset in preparation for a new binding
486: * run.
487: */
488:
489: public static void setModify(File root, String pkg) {
490: s_modifyRoot = root;
491: s_modifyPackage = pkg;
492: if (s_modifyPackage.length() == 0) {
493: s_modifyPackage = null;
494: }
495: }
496:
497: /**
498: * Derive generated class name for bound class. This generates a JiBX class
499: * name from the name of this class, using the supplied prefix and suffix
500: * information. The derived class name is always in the same package as the
501: * munged class for this class.
502: *
503: * @param prefix generated class name prefix
504: * @param suffix generated class name suffix
505: * @return derived class name
506: */
507:
508: public String deriveClassName(String prefix, String suffix) {
509: String pack = m_mungedClass.getClassFile().getPackage();
510: if (pack.length() > 0) {
511: pack += '.';
512: }
513: String tname = m_boundClass.getName();
514: int split = tname.lastIndexOf('.');
515: if (split >= 0) {
516: tname = tname.substring(split + 1);
517: }
518: return pack + prefix + tname + suffix;
519: }
520: }
|