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: MappingTool.java 7273 2007-10-04 15:15:24Z wguttmn $ 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: 7273 $ $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