001: /*
002: Copyright (c) 2003-2006, 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.io.File;
032: import java.io.FileFilter;
033: import java.io.IOException;
034: import java.util.ArrayList;
035: import java.util.HashMap;
036:
037: import org.apache.bcel.Constants;
038:
039: import org.jibx.binding.def.BindingDefinition;
040: import org.jibx.runtime.JiBXException;
041:
042: /**
043: * Modifiable class handler. Each instance controls changes to a particular
044: * class modified by one or more binding definitions. As methods are generated
045: * they're checked for uniqueness. If an already-generated method is found with
046: * the same characteristics (including byte code) as the one being generated,
047: * the already-generated is used instead.
048: *
049: * @author Dennis M. Sosnoski
050: * @version 1.0
051: */
052:
053: public class MungedClass {
054: //
055: // Constants and such related to code generation.
056:
057: /** Empty class file array. */
058: private static final ClassFile[] EMPTY_CLASSFILE_ARRAY = {};
059:
060: /** Name for list of binding factory names. */
061: private static final String BINDING_LISTNAME = BindingDefinition.GENERATE_PREFIX
062: + "bindingList";
063:
064: /** Name and signature for generated methods without standard prefix. */
065: private static final String[] EXTRA_METHODS_MATCHES = { "marshal",
066: "(Lorg/jibx/runtime/IMarshallingContext;)V", "unmarshal",
067: "(Lorg/jibx/runtime/IUnmarshallingContext;)V" };
068:
069: //
070: // Static data.
071:
072: /** Munged class information. */
073: private static ArrayList s_classes;
074:
075: /** Map from generated class to binding information. */
076: private static HashMap s_classMap;
077:
078: /** Map of directories already checked for JiBX classes. */
079: private static HashMap s_directories;
080:
081: /** Map from class name to binding information. */
082: private static HashMap s_nameMap;
083:
084: /** Munged classes to be unique-added at end of binding. */
085: private static ArrayList s_pendingClasses;
086:
087: //
088: // Actual instance data.
089:
090: /** Munged class file information. */
091: private ClassFile m_classFile;
092:
093: /** Map from method byte code and signature to method item. */
094: private HashMap m_methodMap;
095:
096: /** Existing binding methods in class. */
097: private ExistingMethod[] m_existingMethods;
098:
099: /** List of factory names for this class. */
100: private String m_factoryList;
101:
102: /**
103: * Constructor. This sets up for modifying a class with added methods.
104: *
105: * @param cf owning class file information
106: */
107:
108: private MungedClass(ClassFile cf) {
109:
110: // initialize basic class information
111: m_classFile = cf;
112: m_methodMap = new HashMap();
113:
114: // get information for existing binding methods in this class
115: ExistingMethod[] exists = cf.getBindingMethods(
116: BindingDefinition.GENERATE_PREFIX,
117: EXTRA_METHODS_MATCHES);
118: if (exists != null) {
119: for (int i = 0; i < exists.length; i++) {
120: m_methodMap.put(exists[i], exists[i]);
121: }
122: }
123: m_existingMethods = exists;
124: }
125:
126: /**
127: * Get munged class file information.
128: *
129: * @return class file information for bound class
130: */
131:
132: /*package*/ClassFile getClassFile() {
133: return m_classFile;
134: }
135:
136: /**
137: * Delete pre-existing binding methods that are no longer needed.
138: *
139: * @throws JiBXException on configuration error
140: */
141:
142: private void purgeUnusedMethods() throws JiBXException {
143: if (m_existingMethods != null) {
144: for (int i = 0; i < m_existingMethods.length; i++) {
145: ExistingMethod method = m_existingMethods[i];
146: if (!method.isUsed()) {
147: method.delete();
148: }
149: }
150: }
151: }
152:
153: /**
154: * Get unique method. If a method matching the byte code of the supplied
155: * method has already been defined the existing method is returned.
156: * Otherwise the method is added to the definitions. If necessary, a number
157: * suffix is appended to the method name to prevent conflicts with existing
158: * names.
159: *
160: * @param builder method to be defined
161: * @param suffix append name suffix to assure uniqueness flag
162: * @return defined method item
163: * @throws JiBXException on configuration error
164: */
165:
166: /*package*/BindingMethod getUniqueMethod(MethodBuilder builder,
167: boolean suffix) throws JiBXException {
168:
169: // try to find already added method with same characteristics
170: if (builder.getClassFile() != m_classFile) {
171: throw new IllegalStateException(
172: "Internal error: wrong class for call");
173: }
174: builder.codeComplete(suffix);
175: BindingMethod method = (BindingMethod) m_methodMap.get(builder);
176: if (method == null) {
177: // System.out.println("No match found for method " +
178: // builder.getClassFile().getName()+ '.' + builder.getName() +
179: // "; adding method");
180:
181: // create as new method
182: builder.addMethod();
183: m_methodMap.put(builder, builder);
184: return builder;
185:
186: } else if (method instanceof ExistingMethod) {
187: ((ExistingMethod) method).setUsed();
188: }
189: // System.out.println("Found " + method.getClassFile().getName()+
190: // '.' + method.getName() + " as match for " + builder.getName());
191: return method;
192: }
193:
194: /**
195: * Get unique generated support class. Allows tracking of all generated
196: * classes for common handling with the bound classes. Each generated
197: * support class is associated with a particular bound class.
198: *
199: * @param cf generated class file
200: * @return unique class file information
201: */
202:
203: public static ClassFile getUniqueSupportClass(ClassFile cf) {
204: cf.codeComplete();
205: Object value = s_classMap.get(cf);
206: if (value == null) {
207: s_classes.add(cf);
208: s_classMap.put(cf, cf);
209: // System.out.println("Added class " + cf.getName());
210: return cf;
211: } else {
212: ClassFile prior = (ClassFile) value;
213: prior.incrementUseCount();
214: // System.out.println("Matched class " + cf.getName() + " with " +
215: // prior.getName());
216: return prior;
217: }
218: }
219:
220: /**
221: * Check directory for JiBX generated files. Scans through all class files
222: * in the target directory and loads any that start with the JiBX
223: * identifier string.
224: *
225: * @param root class path root for directory
226: * @param pack package relative to root directory
227: * @throws JiBXException on configuration error
228: */
229:
230: /*package*/static void checkDirectory(File root, String pack)
231: throws JiBXException {
232: try {
233: File directory = new File(root, pack.replace('.',
234: File.separatorChar));
235: String cpath = directory.getCanonicalPath();
236: if (s_directories.get(cpath) == null) {
237: File[] matches = new File(cpath)
238: .listFiles(new JiBXFilter());
239: for (int i = 0; i < matches.length; i++) {
240: File file = matches[i];
241: String name = file.getName();
242: int split = name.indexOf('.');
243: if (split >= 0) {
244: name = name.substring(0, split);
245: }
246: if (pack.length() > 0) {
247: name = pack + '.' + name;
248: }
249: ClassFile cf = ClassCache.getClassFile(name);
250: s_classes.add(cf);
251: s_classMap.put(cf, cf);
252: }
253: s_directories.put(cpath, cpath);
254: }
255: } catch (IOException ex) {
256: throw new JiBXException("Error loading class file", ex);
257: }
258: }
259:
260: /**
261: * Add binding factory to class. The binding factories are accumulated as
262: * a delimited string during generation, then dumped to a static field in
263: * the class at the end.
264: *
265: * @param fact binding factory name
266: */
267:
268: /*package*/void addFactory(String fact) {
269: String match = "|" + fact + "|";
270: if (m_factoryList == null) {
271: m_factoryList = match;
272: } else if (m_factoryList.indexOf(match) < 0) {
273: m_factoryList = m_factoryList + fact + "|";
274: }
275: }
276:
277: /**
278: * Generate factory list. Adds or replaces the existing static array of
279: * factories in the class.
280: *
281: * @throws JiBXException on configuration error
282: */
283:
284: /*package*/void setFactoryList() throws JiBXException {
285: if (m_factoryList != null) {
286: short access = Constants.ACC_PUBLIC | Constants.ACC_FINAL
287: | Constants.ACC_STATIC;
288: m_classFile.updateField("java.lang.String",
289: BINDING_LISTNAME, access, m_factoryList);
290: }
291: }
292:
293: /**
294: * Get modification tracking information for class.
295: *
296: * @param cf information for class to be modified (must be writable)
297: * @return binding information for class
298: * @throws JiBXException on configuration error
299: */
300:
301: /*package*/static MungedClass getInstance(ClassFile cf)
302: throws JiBXException {
303: MungedClass inst = (MungedClass) s_nameMap.get(cf.getName());
304: if (inst == null) {
305: inst = new MungedClass(cf);
306: s_nameMap.put(cf.getName(), inst);
307: if (cf.isComplete()) {
308: if (s_classMap.get(cf) == null) {
309: s_classes.add(inst);
310: s_classMap.put(cf, cf);
311: } else {
312: throw new IllegalStateException(
313: "Existing class conflicts with load");
314: }
315: String pack = cf.getPackage();
316: checkDirectory(cf.getRoot(), pack);
317: }
318: }
319: inst.m_classFile.incrementUseCount();
320: return inst;
321: }
322:
323: /**
324: * Add unique support class at end of binding process. This allows a class
325: * to be constructed in steps during handling of one or more bindings, with
326: * the class finished and checked for uniqueness only after all bindings
327: * have been handled. The actual add of the class is done during the
328: * {@link #fixChanges(boolean)} handling.
329: *
330: * @param cf class file to be added as unique support class at end of
331: * binding
332: */
333:
334: public static void delayedAddUnique(ClassFile cf) {
335: s_pendingClasses.add(cf);
336: }
337:
338: /**
339: * Add class file to set modified.
340: *
341: * @param cf
342: */
343: public static void addModifiedClass(ClassFile cf) {
344: s_classes.add(cf);
345: }
346:
347: /**
348: * Finalize changes to modified class files.
349: *
350: * @param write replace original class files with modified versions
351: * @return three-way array of class files, for written, unmodified, and
352: * deleted
353: * @throws JiBXException on write error
354: */
355: public static ClassFile[][] fixChanges(boolean write)
356: throws JiBXException {
357: try {
358: for (int i = 0; i < s_pendingClasses.size(); i++) {
359: getUniqueSupportClass((ClassFile) s_pendingClasses
360: .get(i));
361: }
362: ArrayList writes = new ArrayList();
363: ArrayList keeps = new ArrayList();
364: ArrayList deletes = new ArrayList();
365: for (int i = 0; i < s_classes.size(); i++) {
366: Object obj = s_classes.get(i);
367: ClassFile cf;
368: if (obj instanceof MungedClass) {
369: MungedClass inst = (MungedClass) obj;
370: inst.purgeUnusedMethods();
371: inst.setFactoryList();
372: cf = inst.getClassFile();
373: } else {
374: cf = (ClassFile) obj;
375: }
376: if (cf.isModified()) {
377: if (write) {
378: cf.writeFile();
379: }
380: writes.add(cf);
381: // System.out.println("Wrote file " + cf.getName());
382: } else if (cf.getUseCount() > 0) {
383: keeps.add(cf);
384: // System.out.println("Kept file " + cf.getName());
385: } else {
386: cf.delete();
387: deletes.add(cf);
388: // System.out.println("Deleted file " + cf.getName());
389: }
390: }
391: ClassFile[][] results = new ClassFile[3][];
392: results[0] = (ClassFile[]) writes
393: .toArray(EMPTY_CLASSFILE_ARRAY);
394: results[1] = (ClassFile[]) keeps
395: .toArray(EMPTY_CLASSFILE_ARRAY);
396: results[2] = (ClassFile[]) deletes
397: .toArray(EMPTY_CLASSFILE_ARRAY);
398: return results;
399: } catch (IOException ex) {
400: throw new JiBXException("Error writing to file", ex);
401: }
402: }
403:
404: /**
405: * Filter for class files generated by JiBX.
406: */
407: private static class JiBXFilter implements FileFilter {
408: public boolean accept(File file) {
409: String name = file.getName();
410: return name.startsWith(BindingDefinition.GENERATE_PREFIX)
411: && name.endsWith(".class");
412: }
413: }
414:
415: /**
416: * Discard cached information and reset in preparation for a new binding
417: * run.
418: */
419: public static void reset() {
420: s_classes = new ArrayList();
421: s_classMap = new HashMap();
422: s_directories = new HashMap();
423: s_nameMap = new HashMap();
424: s_pendingClasses = new ArrayList();
425: }
426: }
|