View Javadoc
1   /*
2    * Copyright (c) 1997, 2011, 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 <tt>sc</tt>. 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</code>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 }