1 /* 2 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.internal.xjc.reader.xmlschema; 27 28 import java.io.StringWriter; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.Map; 32 import java.util.Set; 33 import java.util.Stack; 34 35 import com.sun.codemodel.internal.JCodeModel; 36 import com.sun.codemodel.internal.JJavaName; 37 import com.sun.codemodel.internal.JPackage; 38 import com.sun.codemodel.internal.util.JavadocEscapeWriter; 39 import com.sun.istack.internal.NotNull; 40 import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo; 41 import com.sun.tools.internal.xjc.model.CClassInfo; 42 import com.sun.tools.internal.xjc.model.CClassInfoParent; 43 import com.sun.tools.internal.xjc.model.CElement; 44 import com.sun.tools.internal.xjc.model.CElementInfo; 45 import com.sun.tools.internal.xjc.model.CTypeInfo; 46 import com.sun.tools.internal.xjc.model.TypeUse; 47 import com.sun.tools.internal.xjc.model.CClass; 48 import com.sun.tools.internal.xjc.model.CNonElement; 49 import com.sun.tools.internal.xjc.reader.Ring; 50 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIProperty; 51 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding; 52 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.LocalScoping; 53 import com.sun.xml.internal.bind.v2.WellKnownNamespace; 54 import com.sun.xml.internal.xsom.XSComplexType; 55 import com.sun.xml.internal.xsom.XSComponent; 56 import com.sun.xml.internal.xsom.XSDeclaration; 57 import com.sun.xml.internal.xsom.XSElementDecl; 58 import com.sun.xml.internal.xsom.XSSchema; 59 import com.sun.xml.internal.xsom.XSSchemaSet; 60 import com.sun.xml.internal.xsom.XSSimpleType; 61 import com.sun.xml.internal.xsom.XSType; 62 import com.sun.xml.internal.xsom.impl.util.SchemaWriter; 63 import com.sun.xml.internal.xsom.util.ComponentNameFunction; 64 65 import org.xml.sax.Locator; 66 67 /** 68 * Manages association between {@link XSComponent}s and generated 69 * {@link CTypeInfo}s. 70 * 71 * <p> 72 * This class determines which component is mapped to (or is not mapped to) 73 * what types. 74 * 75 * @author 76 * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) 77 */ 78 public final class ClassSelector extends BindingComponent { 79 /** Center of owner classes. */ 80 private final BGMBuilder builder = Ring.get(BGMBuilder.class); 81 82 83 /** 84 * Map from XSComponents to {@link Binding}s. Keeps track of all 85 * content interfaces that are already built or being built. 86 */ 87 private final Map<XSComponent,Binding> bindMap = new HashMap<XSComponent,Binding>(); 88 89 /** 90 * UGLY HACK. 91 * <p> 92 * To avoid cyclic dependency between binding elements and types, 93 * we need additional markers that tell which elements are definitely not bound 94 * to a class. 95 * <p> 96 * the cyclic dependency is as follows: 97 * elements need to bind its types first, because otherwise it can't 98 * determine T of JAXBElement<T>. 99 * OTOH, types need to know whether its parent is bound to a class to decide 100 * which class name to use. 101 */ 102 /*package*/ final Map<XSComponent,CElementInfo> boundElements = new HashMap<XSComponent,CElementInfo>(); 103 104 /** 105 * A list of {@link Binding}s object that needs to be built. 106 */ 107 private final Stack<Binding> bindQueue = new Stack<Binding>(); 108 109 /** 110 * {@link CClassInfo}s that are already {@link Binding#build() built}. 111 */ 112 private final Set<CClassInfo> built = new HashSet<CClassInfo>(); 113 114 /** 115 * Object that determines components that are mapped 116 * to classes. 117 */ 118 private final ClassBinder classBinder; 119 120 /** 121 * {@link CClassInfoParent}s that determines where a new class 122 * should be created. 123 */ 124 private final Stack<CClassInfoParent> classScopes = new Stack<CClassInfoParent>(); 125 126 /** 127 * The component that is being bound to {@link #currentBean}. 128 */ 129 private XSComponent currentRoot; 130 /** 131 * The bean representation we are binding right now. 132 */ 133 private CClassInfo currentBean; 134 135 136 private final class Binding { 137 private final XSComponent sc; 138 private final CTypeInfo bean; 139 140 public Binding(XSComponent sc, CTypeInfo bean) { 141 this.sc = sc; 142 this.bean = bean; 143 } 144 145 void build() { 146 if(!(this.bean instanceof CClassInfo)) 147 return; // no need to "build" 148 149 CClassInfo bean = (CClassInfo)this.bean; 150 151 if(!built.add(bean)) 152 return; // already built 153 154 for( String reservedClassName : reservedClassNames ) { 155 if( bean.getName().equals(reservedClassName) ) { 156 getErrorReporter().error( sc.getLocator(), 157 Messages.ERR_RESERVED_CLASS_NAME, reservedClassName ); 158 break; 159 } 160 } 161 162 // if this schema component is an element declaration 163 // and it satisfies a set of conditions specified in the spec, 164 // this class will receive a constructor. 165 if(needValueConstructor(sc)) { 166 // TODO: fragile. There is no guarantee that the property name 167 // is in fact "value". 168 bean.addConstructor("value"); 169 } 170 171 if(bean.javadoc==null) 172 addSchemaFragmentJavadoc(bean,sc); 173 174 // build the body 175 if(builder.getGlobalBinding().getFlattenClasses()==LocalScoping.NESTED) 176 pushClassScope(bean); 177 else 178 pushClassScope(bean.parent()); 179 XSComponent oldRoot = currentRoot; 180 CClassInfo oldBean = currentBean; 181 currentRoot = sc; 182 currentBean = bean; 183 sc.visit(Ring.get(BindRed.class)); 184 currentBean = oldBean; 185 currentRoot = oldRoot; 186 popClassScope(); 187 188 // acknowledge property customization on this schema component, 189 // since it is OK to have a customization at the point of declaration 190 // even when no one is using it. 191 BIProperty prop = builder.getBindInfo(sc).get(BIProperty.class); 192 if(prop!=null) prop.markAsAcknowledged(); 193 } 194 } 195 196 197 // should be instanciated only from BGMBuilder. 198 public ClassSelector() { 199 classBinder = new Abstractifier(new DefaultClassBinder()); 200 Ring.add(ClassBinder.class,classBinder); 201 202 classScopes.push(null); // so that the getClassFactory method returns null 203 204 XSComplexType anyType = Ring.get(XSSchemaSet.class).getComplexType(WellKnownNamespace.XML_SCHEMA,"anyType"); 205 bindMap.put(anyType,new Binding(anyType,CBuiltinLeafInfo.ANYTYPE)); 206 } 207 208 /** Gets the current class scope. */ 209 public final CClassInfoParent getClassScope() { 210 assert !classScopes.isEmpty(); 211 return classScopes.peek(); 212 } 213 214 public final void pushClassScope( CClassInfoParent clsFctry ) { 215 assert clsFctry!=null; 216 classScopes.push(clsFctry); 217 } 218 219 public final void popClassScope() { 220 classScopes.pop(); 221 } 222 223 public XSComponent getCurrentRoot() { 224 return currentRoot; 225 } 226 227 public CClassInfo getCurrentBean() { 228 return currentBean; 229 } 230 231 /** 232 * Checks if the given component is bound to a class. 233 */ 234 public final CElement isBound( XSElementDecl x, XSComponent referer ) { 235 CElementInfo r = boundElements.get(x); 236 if(r!=null) 237 return r; 238 return bindToType(x,referer); 239 } 240 241 /** 242 * Checks if the given component is being mapped to a type. 243 * If so, build that type and return that object. 244 * If it is not being mapped to a type item, return null. 245 */ 246 public CTypeInfo bindToType( XSComponent sc, XSComponent referer ) { 247 return _bindToClass(sc,referer,false); 248 } 249 250 // 251 // some schema components are guaranteed to map to a particular CTypeInfo. 252 // the following versions capture those constraints in the signature 253 // and making the bindToType invocation more type safe. 254 // 255 256 public CElement bindToType( XSElementDecl e, XSComponent referer ) { 257 return (CElement)_bindToClass(e,referer,false); 258 } 259 260 public CClass bindToType( XSComplexType t, XSComponent referer, boolean cannotBeDelayed ) { 261 // this assumption that a complex type always binds to a ClassInfo 262 // does not hold for xs:anyType --- our current approach of handling 263 // this idiosynchracy is to make sure that xs:anyType doesn't use 264 // this codepath. 265 return (CClass)_bindToClass(t,referer,cannotBeDelayed); 266 } 267 268 public TypeUse bindToType( XSType t, XSComponent referer ) { 269 if(t instanceof XSSimpleType) { 270 return Ring.get(SimpleTypeBuilder.class).build((XSSimpleType)t); 271 } else 272 return (CNonElement)_bindToClass(t,referer,false); 273 } 274 275 /** 276 * The real meat of the "bindToType" code. 277 * 278 * @param cannotBeDelayed 279 * if the binding of the body of the class cannot be defered 280 * and needs to be done immediately. If the flag is false, 281 * the binding of the body will be done later, to avoid 282 * cyclic binding problem. 283 * @param referer 284 * The component that refers to {@code sc}. This can be null, 285 * if figuring out the referer is too hard, in which case 286 * the error message might be less user friendly. 287 */ 288 // TODO: consider getting rid of "cannotBeDelayed" 289 CTypeInfo _bindToClass( @NotNull XSComponent sc, XSComponent referer, boolean cannotBeDelayed ) { 290 // check if this class is already built. 291 if(!bindMap.containsKey(sc)) { 292 // craete a bind task 293 294 // if this is a global declaration, make sure they will be generated 295 // under a package. 296 boolean isGlobal = false; 297 if( sc instanceof XSDeclaration ) { 298 isGlobal = ((XSDeclaration)sc).isGlobal(); 299 if( isGlobal ) 300 pushClassScope( new CClassInfoParent.Package( 301 getPackage(((XSDeclaration)sc).getTargetNamespace())) ); 302 } 303 304 // otherwise check if this component should become a class. 305 CElement bean = sc.apply(classBinder); 306 307 if( isGlobal ) 308 popClassScope(); 309 310 if(bean==null) 311 return null; 312 313 // can this namespace generate a class? 314 if (bean instanceof CClassInfo) { 315 XSSchema os = sc.getOwnerSchema(); 316 BISchemaBinding sb = builder.getBindInfo(os).get(BISchemaBinding.class); 317 if(sb!=null && !sb.map) { 318 // nope 319 getErrorReporter().error(sc.getLocator(), 320 Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS, sc.apply( new ComponentNameFunction() ) ); 321 getErrorReporter().error(sb.getLocation(), 322 Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_MAP_FALSE, os.getTargetNamespace() ); 323 if(referer!=null) 324 getErrorReporter().error(referer.getLocator(), 325 Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_REFERER, referer.apply( new ComponentNameFunction() ) ); 326 } 327 } 328 329 330 queueBuild( sc, bean ); 331 } 332 333 Binding bind = bindMap.get(sc); 334 if( cannotBeDelayed ) 335 bind.build(); 336 337 return bind.bean; 338 } 339 340 /** 341 * Runs all the pending build tasks. 342 */ 343 public void executeTasks() { 344 while( bindQueue.size()!=0 ) 345 bindQueue.pop().build(); 346 } 347 348 349 350 351 352 353 354 355 /** 356 * Determines if the given component needs to have a value 357 * constructor (a constructor that takes a parmater.) on ObjectFactory. 358 */ 359 private boolean needValueConstructor( XSComponent sc ) { 360 if(!(sc instanceof XSElementDecl)) return false; 361 362 XSElementDecl decl = (XSElementDecl)sc; 363 if(!decl.getType().isSimpleType()) return false; 364 365 return true; 366 } 367 368 private static final String[] reservedClassNames = new String[]{"ObjectFactory"}; 369 370 public void queueBuild( XSComponent sc, CElement bean ) { 371 // it is an error if the same component is built twice, 372 // or the association is modified. 373 Binding b = new Binding(sc,bean); 374 bindQueue.push(b); 375 Binding old = bindMap.put(sc, b); 376 assert old==null || old.bean==bean; 377 } 378 379 380 /** 381 * Copies a schema fragment into the javadoc of the generated class. 382 */ 383 private void addSchemaFragmentJavadoc( CClassInfo bean, XSComponent sc ) { 384 385 // first, pick it up from <documentation> if any. 386 String doc = builder.getBindInfo(sc).getDocumentation(); 387 if(doc!=null) 388 append(bean, doc); 389 390 // then the description of where this component came from 391 Locator loc = sc.getLocator(); 392 String fileName = null; 393 if(loc!=null) { 394 fileName = loc.getPublicId(); 395 if(fileName==null) 396 fileName = loc.getSystemId(); 397 } 398 if(fileName==null) fileName=""; 399 400 String lineNumber=Messages.format( Messages.JAVADOC_LINE_UNKNOWN); 401 if(loc!=null && loc.getLineNumber()!=-1) 402 lineNumber = String.valueOf(loc.getLineNumber()); 403 404 String componentName = sc.apply( new ComponentNameFunction() ); 405 String jdoc = Messages.format( Messages.JAVADOC_HEADING, componentName, fileName, lineNumber ); 406 append(bean,jdoc); 407 408 // then schema fragment 409 StringWriter out = new StringWriter(); 410 out.write("<pre>\n"); 411 SchemaWriter sw = new SchemaWriter(new JavadocEscapeWriter(out)); 412 sc.visit(sw); 413 out.write("</pre>"); 414 append(bean,out.toString()); 415 } 416 417 private void append(CClassInfo bean, String doc) { 418 if(bean.javadoc==null) 419 bean.javadoc = doc+'\n'; 420 else 421 bean.javadoc += '\n'+doc+'\n'; 422 } 423 424 425 /** 426 * Set of package names that are tested (set of {@code String}s.) 427 * 428 * This set is used to avoid duplicating "incorrect package name" 429 * errors. 430 */ 431 private static Set<String> checkedPackageNames = new HashSet<String>(); 432 433 /** 434 * Gets the Java package to which classes from 435 * this namespace should go. 436 * 437 * <p> 438 * Usually, the getOuterClass method should be used 439 * to determine where to put a class. 440 */ 441 public JPackage getPackage(String targetNamespace) { 442 XSSchema s = Ring.get(XSSchemaSet.class).getSchema(targetNamespace); 443 444 BISchemaBinding sb = 445 builder.getBindInfo(s).get(BISchemaBinding.class); 446 if(sb!=null) sb.markAsAcknowledged(); 447 448 String name = null; 449 450 // "-p" takes precedence over everything else 451 if( builder.defaultPackage1 != null ) 452 name = builder.defaultPackage1; 453 454 // use the <jaxb:package> customization 455 if( name == null && sb!=null && sb.getPackageName()!=null ) 456 name = sb.getPackageName(); 457 458 // the JAX-RPC option goes below the <jaxb:package> 459 if( name == null && builder.defaultPackage2 != null ) 460 name = builder.defaultPackage2; 461 462 // generate the package name from the targetNamespace 463 if( name == null ) 464 name = builder.getNameConverter().toPackageName( targetNamespace ); 465 466 // hardcode a package name because the code doesn't compile 467 // if it generated into the default java package 468 if( name == null ) 469 name = "generated"; // the last resort 470 471 472 // check if the package name is a valid name. 473 if( checkedPackageNames.add(name) ) { 474 // this is the first time we hear about this package name. 475 if( !JJavaName.isJavaPackageName(name) ) 476 // TODO: s.getLocator() is not very helpful. 477 // ideally, we'd like to use the locator where this package name 478 // comes from. 479 getErrorReporter().error(s.getLocator(), 480 Messages.ERR_INCORRECT_PACKAGE_NAME, targetNamespace, name ); 481 } 482 483 return Ring.get(JCodeModel.class)._package(name); 484 } 485 }