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