View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Intalio, Inc.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Intalio, Inc. Exolab is a registered
23   *    trademark of Intalio, Inc.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
42   *
43   * $Id$
44   */
45  package org.exolab.castor.tools;
46  
47  import java.io.File;
48  import java.io.FileWriter;
49  import java.io.PrintWriter;
50  import java.io.Writer;
51  import java.util.Enumeration;
52  import java.util.Hashtable;
53  import java.util.Properties;
54  
55  import org.castor.xml.BackwardCompatibilityContext;
56  import org.castor.xml.InternalContext;
57  import org.exolab.castor.mapping.FieldDescriptor;
58  import org.exolab.castor.mapping.MappingException;
59  import org.exolab.castor.mapping.loader.CollectionHandlers;
60  import org.exolab.castor.mapping.loader.Types;
61  import org.exolab.castor.mapping.xml.BindXml;
62  import org.exolab.castor.mapping.xml.ClassChoice;
63  import org.exolab.castor.mapping.xml.ClassMapping;
64  import org.exolab.castor.mapping.xml.FieldMapping;
65  import org.exolab.castor.mapping.xml.MapTo;
66  import org.exolab.castor.mapping.xml.MappingRoot;
67  import org.exolab.castor.mapping.xml.types.BindXmlNodeType;
68  import org.exolab.castor.mapping.xml.types.FieldMappingCollectionType;
69  import org.exolab.castor.util.CommandLineOptions;
70  import org.exolab.castor.util.dialog.ConsoleDialog;
71  import org.exolab.castor.xml.Marshaller;
72  import org.exolab.castor.xml.XMLClassDescriptor;
73  import org.exolab.castor.xml.XMLContext;
74  import org.exolab.castor.xml.XMLFieldDescriptor;
75  
76  /**
77   * A tool which uses the introspector to automatically
78   * create mappings for a given set of classes.
79   *
80   * @author <a href="arkin@intalio.com">Assaf Arkin</a>
81   * @author <a href="keith AT kvisco DOT com">Keith Visco</a>
82   * @version $Revision$ $Date: 2006-01-30 14:37:08 -0700 (Mon, 30 Jan 2006) $
83   */
84  public class MappingTool {
85      /** Used for checking field names to see if they begin with an underscore '_'. */
86      private static final String UNDERSCORE = "_";
87      
88      /** Hashtable of already generated mappings. */
89      private Hashtable _mappings;
90  
91      /**
92       * The internal MappingLoader to use for checking whether or not we can find
93       * the proper accessor methods.
94       */
95      private MappingToolMappingLoader _mappingLoader;
96  
97      /**
98       * Boolean to indicate that we should always perform introspection for each
99       * class even if a ClassDescriptor may exist.
100      */
101     private boolean _forceIntrospection = false;
102 
103     /**
104      * The XMLContext (mother of all dwelling).
105      */
106     private InternalContext _internalContext;
107 
108     /**
109      * Constructor, builds up the relations.
110      */
111     public MappingTool() {
112         super();
113     } // --MappingTool
114 
115     /**
116      * Command line method.
117      * 
118      * @param args
119      *            the command line parameters
120      */
121     public static void main(final String[] args) {
122         CommandLineOptions allOptions = new CommandLineOptions();
123 
124         // -- Input classname flag
125         allOptions.addFlag("i", "classname", "Sets the input class");
126 
127         // -- Output filename flag
128         String desc = "Sets the output mapping filename";
129         allOptions.addFlag("o", "filename", desc, true);
130 
131         // -- Force flag
132         desc = "Force overwriting of files.";
133         allOptions.addFlag("f", "", desc, true);
134 
135         // -- Help flag
136         desc = "Displays this help screen.";
137         allOptions.addFlag("h", "", desc, true);
138 
139         // -- Process the specified command line options
140         Properties options = allOptions.getOptions(args);
141 
142         // -- check for help option
143         if (options.getProperty("h") != null) {
144             PrintWriter pw = new PrintWriter(System.out, true);
145             allOptions.printHelp(pw);
146             pw.flush();
147             return;
148         }
149 
150         String classname = options.getProperty("i");
151         String mappingName = options.getProperty("o");
152         boolean force = (options.getProperty("f") != null);
153 
154         if (classname == null) {
155             PrintWriter pw = new PrintWriter(System.out, true);
156             allOptions.printUsage(pw);
157             pw.flush();
158             return;
159         }
160 
161         MappingTool tool;
162 
163         try {
164             XMLContext xmlContext = new XMLContext();
165             tool = xmlContext.createMappingTool();
166             tool.addClass(classname);
167 
168             Writer writer = null;
169 
170             if ((mappingName == null) || (mappingName.length() == 0)) {
171                 writer = new PrintWriter(System.out, true);
172             } else {
173                 File file = new File(mappingName);
174                 if (file.exists() && (!force)) {
175                     ConsoleDialog dialog = new ConsoleDialog();
176                     String message = "The file already exists. Do you wish " + "to overwrite '"
177                             + mappingName + "'?";
178                     if (!dialog.confirm(message)) {
179                         return;
180                     }
181                 }
182                 writer = new FileWriter(file);
183             }
184 
185             tool.write(writer);
186         } catch (Exception except) {
187             System.out.println(except);
188             except.printStackTrace();
189         }
190     } // -- main
191 
192     /**
193      * Adds the Class, specified by the given name, to the mapping file.
194      * 
195      * @param name
196      *            the name of the Class to add
197      * @throws MappingException
198      *             in case that the name is null or the Class can not be loaded
199      */
200     public void addClass(final String name) throws MappingException {
201         addClass(name, true);
202     } // -- addClass
203 
204     /**
205      * Adds the Class, specified by the given name, to the mapping file.
206      * 
207      * @param name
208      *            the name of the Class to add
209      * @param deep
210      *            a flag to indicate that recursive processing should take place
211      *            and all classes used by the given class should also be added
212      *            to the mapping file. This flag is true by default.
213      * @throws MappingException
214      *             in case that the name is null or the Class can not be loaded
215      */
216     public void addClass(final String name, final boolean deep) throws MappingException {
217         if (name == null) {
218             throw new MappingException("Cannot introspect a null class.");
219         }
220 
221         try {
222             addClass(Class.forName(name), deep);
223         } catch (ClassNotFoundException except) {
224             throw new MappingException(except);
225         }
226     } // -- addClass
227 
228     /**
229      * Adds the given Class to the mapping file.
230      * 
231      * @param cls
232      *            the Class to add
233      * @throws MappingException
234      *             in case that the name is null or the Class can not be loaded
235      */
236     public void addClass(final Class cls) throws MappingException {
237         addClass(cls, true);
238     } // -- addClass
239 
240     /**
241      * Adds the given Class to the mapping file. If the deep flag is true, all
242      * mappings for Classes used by the given Class will also be added to the
243      * mapping file.
244      * 
245      * @param cls
246      *            the Class to add
247      * @param deep
248      *            a flag to indicate that recursive processing should take place
249      *            and all classes used by the given class should also be added
250      *            to the mapping file. This flag is true by default.
251      * @throws MappingException
252      *             in case that the name is null or the Class can not be loaded
253      */
254     public void addClass(final Class cls, final boolean deep) throws MappingException {
255         if (cls == null) {
256             throw new MappingException("Cannot introspect a null class.");
257         }
258 
259         if (_mappings.get(cls) != null) {
260             return;
261         }
262 
263         if (cls.isArray()) {
264             Class cType = cls.getComponentType();
265             if (_mappings.get(cType) != null) {
266                 return;
267             }
268             if (Types.isSimpleType(cType)) {
269                 return;
270             }
271             // -- handle component type
272             addClass(cType);
273         }
274 
275         if (_forceIntrospection && (!Types.isConstructable(cls))) {
276             throw new MappingException("mapping.classNotConstructable", cls.getName());
277         }
278 
279         XMLClassDescriptor xmlClass;
280         FieldDescriptor[] fields;
281         ClassMapping classMap;
282         FieldMapping fieldMap;
283 
284         boolean introspected = false;
285         try {
286             if (_forceIntrospection) {
287                 xmlClass = _internalContext.getIntrospector().generateClassDescriptor(cls);
288                 introspected = true;
289             } else {
290                 xmlClass = (XMLClassDescriptor) _internalContext.getXMLClassDescriptorResolver().resolve(cls);
291                 introspected = _internalContext.getIntrospector().introspected(xmlClass);
292             }
293         } catch (Exception except) {
294             throw new MappingException(except);
295         }
296         classMap = new ClassMapping();
297         classMap.setName(cls.getName());
298         classMap.setDescription("Default mapping for class " + cls.getName());
299 
300         // -- prevent default access from showing up in the mapping
301         classMap.setAccess(null);
302 
303         // -- map-to
304         MapTo mapTo = new MapTo();
305         mapTo.setXml(xmlClass.getXMLName());
306         mapTo.setNsUri(xmlClass.getNameSpaceURI());
307         mapTo.setNsPrefix(xmlClass.getNameSpacePrefix());
308         classMap.setMapTo(mapTo);
309 
310         // -- add mapping to hashtable before processing
311         // -- fields so we can do recursive processing
312         _mappings.put(cls, classMap);
313 
314         fields = xmlClass.getFields();
315         for (int i = 0; i < fields.length; ++i) {
316             FieldDescriptor fdesc = fields[i];
317 
318             String fieldName = fdesc.getFieldName();
319 
320             boolean isContainer = false;
321             // -- check for collection wrapper
322             if (introspected && fieldName.startsWith("##container")) {
323                 fdesc = fdesc.getClassDescriptor().getFields()[0];
324                 fieldName = fdesc.getFieldName();
325                 isContainer = true;
326             }
327 
328             Class fieldType = fdesc.getFieldType();
329 
330             // -- check to make sure we can find the accessors...
331             // -- if we used introspection we don't need to
332             // -- enter this block...only when descriptors
333             // -- were generated using the source code generator
334             // -- or by hand.
335             if ((!introspected) && fieldName.startsWith(UNDERSCORE)) {
336                 // -- check to see if we need to remove underscore
337                 if (!_mappingLoader.canFindAccessors(cls, fieldName, fieldType)) {
338                     fieldName = fieldName.substring(1);
339                 }
340 
341                 // -- check to see if we need to remove "List" prefix
342                 // -- used by generated source code
343                 if (!_mappingLoader.canFindAccessors(cls, fieldName, fieldType)) {
344                     if (fieldName.endsWith("List")) {
345                         int len = fieldName.length() - 4;
346                         String tmpName = fieldName.substring(0, len);
347                         if (_mappingLoader.canFindAccessors(cls, tmpName, fieldType)) {
348                             fieldName = tmpName;
349                         }
350                     }
351                 }
352             }
353 
354             fieldMap = new FieldMapping();
355             fieldMap.setName(fieldName);
356 
357             // -- unwrap arrays of objects
358             boolean isArray = fieldType.isArray();
359             while (fieldType.isArray()) {
360                 fieldType = fieldType.getComponentType();
361             }
362 
363             // -- To prevent outputing of optional fields...check
364             // -- for value first before setting
365             if (fdesc.isRequired()) {
366                 fieldMap.setRequired(true);
367             }
368             if (fdesc.isTransient()) {
369                 fieldMap.setTransient(true);
370             }
371             if (fdesc.isMultivalued()) {
372                 // -- special case for collections
373                 if (isContainer) {
374                     // -- backwards than what you'd expect, but
375                     // -- if the collection had a "container" wrapper
376                     // -- then we specify container="false" in the
377                     // -- mapping file.
378                     fieldMap.setContainer(false);
379                 }
380 
381                 // -- try to guess collection type
382                 if (isArray) {
383                     fieldMap.setCollection(FieldMappingCollectionType.ARRAY);
384                 } else {
385                     // -- if the fieldType is the collection, then set
386                     // appropriate
387                     // -- collection type
388                     String colName = CollectionHandlers.getCollectionName(fieldType);
389                     if (colName != null) {
390                         fieldMap.setCollection(FieldMappingCollectionType.valueOf(colName));
391                         fieldType = Object.class;
392                     } else if (_mappingLoader.returnsArray(cls, fieldName, fieldType)) {
393                         // -- help maintain compatibility with generated
394                         // descriptors
395                         fieldMap.setCollection(FieldMappingCollectionType.ARRAY);
396                     } else {
397                         fieldMap.setCollection(FieldMappingCollectionType.ENUMERATE);
398                     }
399                 }
400             }
401 
402             // -- fieldType
403             fieldMap.setType(fieldType.getName());
404 
405             // -- handle XML Specific information
406             fieldMap.setBindXml(new BindXml());
407             fieldMap.getBindXml().setName(((XMLFieldDescriptor) fdesc).getXMLName());
408             fieldMap.getBindXml().setNode(
409                     BindXmlNodeType.valueOf(((XMLFieldDescriptor) fields[i]).getNodeType()
410                             .toString()));
411             if (classMap.getClassChoice() == null) {
412                 classMap.setClassChoice(new ClassChoice());
413             }
414             classMap.getClassChoice().addFieldMapping(fieldMap);
415 
416             if (deep) {
417                 if (_mappings.get(fieldType) != null) {
418                     continue;
419                 }
420                 if (Types.isSimpleType(fieldType)) {
421                     continue;
422                 }
423                 // -- recursive add needed classes
424                 addClass(fieldType);
425             }
426         }
427     } // -- addClass
428 
429     /**
430      * Enables or disables the forcing of introspection when a ClassDescriptor
431      * already exists. This is false by default.
432      * 
433      * @param force
434      *            when true will cause the MappingTool to always use
435      *            introspection regardless of whether or not a ClassDescriptor
436      *            exists for a given Class.
437      */
438     public void setForceIntrospection(final boolean force) {
439         _forceIntrospection = force;
440     } // -- setForceInstrospection
441 
442     /**
443      * Serializes the mapping to the given writer.
444      * 
445      * @param writer
446      *            the Writer to serialize the mapping to
447      * @throws MappingException if writing the mapping information fails
448      */
449     public void write(final Writer writer) throws MappingException {
450         Marshaller marshal;
451         MappingRoot mapping;
452         Enumeration enumeration;
453 
454         try {
455             mapping = new MappingRoot();
456             mapping.setDescription("Castor generated mapping file");
457             enumeration = _mappings.elements();
458             while (enumeration.hasMoreElements()) {
459                 mapping.addClassMapping((ClassMapping) enumeration.nextElement());
460             }
461             marshal = new Marshaller(writer);
462             marshal.setNamespaceMapping(null, "http://castor.exolab.org/");
463             marshal.setNamespaceMapping("cst", "http://castor.exolab.org/");
464             marshal.marshal(mapping);
465         } catch (Exception except) {
466             throw new MappingException(except);
467         }
468     } // -- write
469 
470     /**
471      * To set the XMLContext to be used.
472      * @param internalContext the XMLContext to be used
473      */
474     public void setInternalContext(final InternalContext internalContext) {
475         _internalContext = internalContext;
476         _mappings = new Hashtable();
477         _mappingLoader = new MappingToolMappingLoader(_internalContext.getJavaNaming());
478     }
479 } // -- MappingTool