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