View Javadoc
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.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import javax.xml.namespace.QName;
35  import javax.xml.transform.Transformer;
36  import javax.xml.transform.TransformerConfigurationException;
37  import javax.xml.transform.TransformerFactory;
38  
39  import com.sun.codemodel.internal.JCodeModel;
40  import com.sun.codemodel.internal.fmt.JTextFile;
41  import com.sun.istack.internal.NotNull;
42  import com.sun.istack.internal.Nullable;
43  import com.sun.tools.internal.xjc.ErrorReceiver;
44  import com.sun.tools.internal.xjc.Options;
45  import com.sun.tools.internal.xjc.Plugin;
46  import com.sun.tools.internal.xjc.generator.bean.field.FieldRendererFactory;
47  import com.sun.tools.internal.xjc.model.CClassInfoParent;
48  import com.sun.tools.internal.xjc.model.Model;
49  import com.sun.tools.internal.xjc.reader.ModelChecker;
50  import com.sun.tools.internal.xjc.reader.Ring;
51  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDeclaration;
52  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDom;
53  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIGlobalBinding;
54  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding;
55  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISerializable;
56  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BindInfo;
57  import com.sun.tools.internal.xjc.util.CodeModelClassFactory;
58  import com.sun.tools.internal.xjc.util.ErrorReceiverFilter;
59  import com.sun.xml.internal.bind.api.impl.NameConverter;
60  import com.sun.xml.internal.bind.v2.util.XmlFactory;
61  import com.sun.xml.internal.xsom.XSAnnotation;
62  import com.sun.xml.internal.xsom.XSAttributeUse;
63  import com.sun.xml.internal.xsom.XSComponent;
64  import com.sun.xml.internal.xsom.XSDeclaration;
65  import com.sun.xml.internal.xsom.XSParticle;
66  import com.sun.xml.internal.xsom.XSSchema;
67  import com.sun.xml.internal.xsom.XSSchemaSet;
68  import com.sun.xml.internal.xsom.XSSimpleType;
69  import com.sun.xml.internal.xsom.XSTerm;
70  import com.sun.xml.internal.xsom.XSType;
71  import com.sun.xml.internal.xsom.XSWildcard;
72  import com.sun.xml.internal.xsom.util.XSFinder;
73  
74  import org.xml.sax.Locator;
75  
76  /**
77   * Root of the XML Schema binder.
78   *
79   * <div><img src="doc-files/binding_chart.png"/></div>
80   *
81   * @author Kohsuke Kawaguchi
82   */
83  public class BGMBuilder extends BindingComponent {
84  
85      /**
86       * Entry point.
87       */
88      public static Model build( XSSchemaSet _schemas, JCodeModel codeModel,
89              ErrorReceiver _errorReceiver, Options opts ) {
90          // set up a ring
91          final Ring old = Ring.begin();
92          try {
93              ErrorReceiverFilter ef = new ErrorReceiverFilter(_errorReceiver);
94  
95              Ring.add(XSSchemaSet.class,_schemas);
96              Ring.add(codeModel);
97              Model model = new Model(opts, codeModel, null/*set later*/, opts.classNameAllocator, _schemas);
98              Ring.add(model);
99              Ring.add(ErrorReceiver.class,ef);
100             Ring.add(CodeModelClassFactory.class,new CodeModelClassFactory(ef));
101 
102             BGMBuilder builder = new BGMBuilder(opts.defaultPackage,opts.defaultPackage2,
103                 opts.isExtensionMode(),opts.getFieldRendererFactory(), opts.activePlugins);
104             builder._build();
105 
106             if(ef.hadError())   return null;
107             else                return model;
108         } finally {
109             Ring.end(old);
110         }
111     }
112 
113 
114     /**
115      * True if the compiler is running in the extension mode
116      * (as opposed to the strict conformance mode.)
117      */
118     public final boolean inExtensionMode;
119 
120     /**
121      * If this is non-null, this package name takes over
122      * all the schema customizations.
123      */
124     public final String defaultPackage1;
125 
126     /**
127      * If this is non-null, this package name will be
128      * used when no customization is specified.
129      */
130     public final String defaultPackage2;
131 
132     private final BindGreen green = Ring.get(BindGreen.class);
133     private final BindPurple purple = Ring.get(BindPurple.class);
134 
135     public final Model model = Ring.get(Model.class);
136 
137     public final FieldRendererFactory fieldRendererFactory;
138 
139     /**
140      * Lazily computed {@link RefererFinder}.
141      *
142      * @see #getReferer
143      */
144     private RefererFinder refFinder;
145 
146     private List<Plugin> activePlugins;
147 
148     protected BGMBuilder(String defaultPackage1, String defaultPackage2,
149             boolean _inExtensionMode, FieldRendererFactory fieldRendererFactory,
150             List<Plugin> activePlugins) {
151         this.inExtensionMode = _inExtensionMode;
152         this.defaultPackage1 = defaultPackage1;
153         this.defaultPackage2 = defaultPackage2;
154         this.fieldRendererFactory = fieldRendererFactory;
155         this.activePlugins = activePlugins;
156         promoteGlobalBindings();
157     }
158 
159     private void _build() {
160         // do the binding
161         buildContents();
162         getClassSelector().executeTasks();
163 
164         // additional error check
165         // Reports unused customizations to the user as errors.
166         Ring.get(UnusedCustomizationChecker.class).run();
167 
168         Ring.get(ModelChecker.class).check();
169 
170         for( Plugin ma : activePlugins )
171             ma.postProcessModel(model, Ring.get(ErrorReceiver.class));
172 
173     }
174 
175 
176     /** List up all the global bindings. */
177     private void promoteGlobalBindings() {
178         // promote any global bindings in the schema
179         XSSchemaSet schemas = Ring.get(XSSchemaSet.class);
180 
181         for( XSSchema s : schemas.getSchemas() ) {
182             BindInfo bi = getBindInfo(s);
183 
184             // collect all global customizations
185             model.getCustomizations().addAll(bi.toCustomizationList());
186 
187             BIGlobalBinding gb = bi.get(BIGlobalBinding.class);
188             if(gb==null)
189                 continue;
190 
191             gb.markAsAcknowledged();
192 
193             if(globalBinding==null) {
194                 globalBinding = gb;
195             } else {
196                 if (!globalBinding.isEqual(gb)) { // see Issue 687 - this may happen with syntactically imported documents
197                     // acknowledge this customization and report an error
198                     // otherwise the user will see "customization is attached to a wrong place" error,
199                     // which is incorrect
200                     getErrorReporter().error( gb.getLocation(),
201                         Messages.ERR_MULTIPLE_GLOBAL_BINDINGS);
202                     getErrorReporter().error( globalBinding.getLocation(),
203                         Messages.ERR_MULTIPLE_GLOBAL_BINDINGS_OTHER);
204                 }
205             }
206         }
207 
208         if( globalBinding==null ) {
209             // no global customization is present.
210             // use the default one
211             globalBinding = new BIGlobalBinding();
212             BindInfo big = new BindInfo();
213             big.addDecl(globalBinding);
214             big.setOwner(this,null);
215         }
216 
217         // code generation mode
218         model.strategy = globalBinding.getCodeGenerationStrategy();
219         model.rootClass = globalBinding.getSuperClass();
220         model.rootInterface = globalBinding.getSuperInterface();
221 
222         particleBinder = globalBinding.isSimpleMode() ? new ExpressionParticleBinder() : new DefaultParticleBinder();
223 
224         // check XJC extensions and realize them
225         BISerializable serial = globalBinding.getSerializable();
226         if(serial!=null) {
227             model.serializable = true;
228             model.serialVersionUID = serial.uid;
229         }
230 
231         // obtain the name conversion mode
232         if (globalBinding.nameConverter!=null)
233             model.setNameConverter(globalBinding.nameConverter);
234 
235         // attach global conversions to the appropriate simple types
236         globalBinding.dispatchGlobalConversions(schemas);
237 
238         globalBinding.errorCheck();
239     }
240 
241     /**
242      * Global bindings.
243      *
244      * The empty global binding is set as the default, so that
245      * there will be no need to test if the value is null.
246      */
247     private BIGlobalBinding globalBinding;
248 
249     /**
250      * Gets the global bindings.
251      */
252     public @NotNull BIGlobalBinding getGlobalBinding() { return globalBinding; }
253 
254 
255     private ParticleBinder particleBinder;
256 
257     /**
258      * Gets the particle binder for this binding.
259      */
260     public @NotNull ParticleBinder getParticleBinder() { return particleBinder; }
261 
262 
263     /**
264      * Name converter that implements "XML->Java name conversion"
265      * as specified in the spec.
266      *
267      * This object abstracts the detail that we use different name
268      * conversion depending on the customization.
269      *
270      * <p>
271      * This object should be used to perform any name conversion
272      * needs, instead of the JJavaName class in CodeModel.
273      */
274     public NameConverter getNameConverter() { return model.getNameConverter(); }
275 
276     /** Fill-in the contents of each classes. */
277     private void buildContents() {
278         ClassSelector cs = getClassSelector();
279         SimpleTypeBuilder stb = Ring.get(SimpleTypeBuilder.class);
280 
281         for( XSSchema s : Ring.get(XSSchemaSet.class).getSchemas() ) {
282             BISchemaBinding sb = getBindInfo(s).get(BISchemaBinding.class);
283 
284             if(sb!=null && !sb.map) {
285                 sb.markAsAcknowledged();
286                 continue;       // no mapping for this package
287             }
288 
289             getClassSelector().pushClassScope( new CClassInfoParent.Package(
290                 getClassSelector().getPackage(s.getTargetNamespace())) );
291 
292             checkMultipleSchemaBindings(s);
293             processPackageJavadoc(s);
294             populate(s.getAttGroupDecls(),s);
295             populate(s.getAttributeDecls(),s);
296             populate(s.getElementDecls(),s);
297             populate(s.getModelGroupDecls(),s);
298 
299             // fill in typeUses
300             for (XSType t : s.getTypes().values()) {
301                 stb.refererStack.push(t);
302                 model.typeUses().put( getName(t), cs.bindToType(t,s) );
303                 stb.refererStack.pop();
304             }
305 
306             getClassSelector().popClassScope();
307         }
308     }
309 
310     /** Reports an error if there are more than one jaxb:schemaBindings customization. */
311     private void checkMultipleSchemaBindings( XSSchema schema ) {
312         ArrayList<Locator> locations = new ArrayList<Locator>();
313 
314         BindInfo bi = getBindInfo(schema);
315         for( BIDeclaration bid : bi ) {
316             if( bid.getName()==BISchemaBinding.NAME )
317                 locations.add( bid.getLocation() );
318         }
319         if(locations.size()<=1)    return; // OK
320 
321         // error
322         getErrorReporter().error( locations.get(0),
323             Messages.ERR_MULTIPLE_SCHEMA_BINDINGS,
324             schema.getTargetNamespace() );
325         for( int i=1; i<locations.size(); i++ )
326             getErrorReporter().error( (Locator)locations.get(i),
327                 Messages.ERR_MULTIPLE_SCHEMA_BINDINGS_LOCATION);
328     }
329 
330     /**
331      * Calls {@link ClassSelector} for each item in the iterator
332      * to populate class items if there is any.
333      */
334     private void populate( Map<String,? extends XSComponent> col, XSSchema schema ) {
335         ClassSelector cs = getClassSelector();
336         for( XSComponent sc : col.values() )
337             cs.bindToType(sc,schema);
338     }
339 
340     /**
341      * Generates <code>package.html</code> if the customization
342      * says so.
343      */
344     private void processPackageJavadoc( XSSchema s ) {
345         // look for the schema-wide customization
346         BISchemaBinding cust = getBindInfo(s).get(BISchemaBinding.class);
347         if(cust==null)      return; // not present
348 
349         cust.markAsAcknowledged();
350         if( cust.getJavadoc()==null )   return;     // no javadoc customization
351 
352         // produce a HTML file
353         JTextFile html = new JTextFile("package.html");
354         html.setContents(cust.getJavadoc());
355         getClassSelector().getPackage(s.getTargetNamespace()).addResourceFile(html);
356     }
357 
358 
359 
360 
361 
362 
363     /**
364      * Gets or creates the BindInfo object associated to a schema component.
365      *
366      * @return
367      *      Always return a non-null valid BindInfo object.
368      *      Even if no declaration was specified, this method creates
369      *      a new BindInfo so that new decls can be added.
370      */
371     public BindInfo getOrCreateBindInfo( XSComponent schemaComponent ) {
372 
373         BindInfo bi = _getBindInfoReadOnly(schemaComponent);
374         if(bi!=null)    return bi;
375 
376         // XSOM is read-only, so we cannot add new annotations.
377         // for components that didn't have annotations,
378         // we maintain an external map.
379         bi = new BindInfo();
380         bi.setOwner(this,schemaComponent);
381         externalBindInfos.put(schemaComponent,bi);
382         return bi;
383     }
384 
385 
386     /**
387      * Used as a constant instance to represent the empty {@link BindInfo}.
388      */
389     private final BindInfo emptyBindInfo = new BindInfo();
390 
391     /**
392      * Gets the BindInfo object associated to a schema component.
393      *
394      * @return
395      *      always return a valid {@link BindInfo} object. If none
396      *      is specified for the given component, a dummy empty BindInfo
397      *      will be returned.
398      */
399     public BindInfo getBindInfo( XSComponent schemaComponent ) {
400         BindInfo bi = _getBindInfoReadOnly(schemaComponent);
401         if(bi!=null)    return bi;
402         else            return emptyBindInfo;
403     }
404 
405     /**
406      * Gets the BindInfo object associated to a schema component.
407      *
408      * @return
409      *      null if no bind info is associated to this schema component.
410      */
411     private BindInfo _getBindInfoReadOnly( XSComponent schemaComponent ) {
412 
413         BindInfo bi = externalBindInfos.get(schemaComponent);
414         if(bi!=null)    return bi;
415 
416         XSAnnotation annon = schemaComponent.getAnnotation();
417         if(annon!=null) {
418             bi = (BindInfo)annon.getAnnotation();
419             if(bi!=null) {
420                 if(bi.getOwner()==null)
421                     bi.setOwner(this,schemaComponent);
422                 return bi;
423             }
424         }
425 
426         return null;
427     }
428 
429     /**
430      * A map that stores binding declarations augmented by XJC.
431      */
432     private final Map<XSComponent,BindInfo> externalBindInfos = new HashMap<XSComponent,BindInfo>();
433 
434     /**
435      * Gets the {@link BIDom} object that applies to the given particle.
436      */
437     protected final BIDom getLocalDomCustomization( XSParticle p ) {
438         if (p == null) {
439             return null;
440         }
441         BIDom dom = getBindInfo(p).get(BIDom.class);
442         if(dom!=null)  return dom;
443 
444         // if not, the term might have one.
445         dom = getBindInfo(p.getTerm()).get(BIDom.class);
446         if(dom!=null)  return dom;
447 
448         XSTerm t = p.getTerm();
449         // type could also have one, in case of the dom customization
450         if(t.isElementDecl())
451             return getBindInfo(t.asElementDecl().getType()).get(BIDom.class);
452         // similarly the model group in a model group definition may have one.
453         if(t.isModelGroupDecl())
454             return getBindInfo(t.asModelGroupDecl().getModelGroup()).get(BIDom.class);
455 
456         return null;
457     }
458 
459     /**
460      * Returns true if the component should be processed by purple.
461      */
462     private final XSFinder toPurple = new XSFinder() {
463         @Override
464         public Boolean attributeUse(XSAttributeUse use) {
465             // attribute use always maps to a property
466             return true;
467         }
468 
469         @Override
470         public Boolean simpleType(XSSimpleType xsSimpleType) {
471             // simple type always maps to a type, hence we should take purple
472             return true;
473         }
474 
475         @Override
476         public Boolean wildcard(XSWildcard xsWildcard) {
477             // attribute wildcards always maps to a property.
478             // element wildcards should have been processed with particle binders
479             return true;
480         }
481     };
482     /**
483      * If the component maps to a property, forwards to purple, otherwise to green.
484      *
485      * If the component is mapped to a type, this method needs to return true.
486      * See the chart at the class javadoc.
487      */
488     public void ying( XSComponent sc, @Nullable XSComponent referer ) {
489         if(sc.apply(toPurple)==true || getClassSelector().bindToType(sc,referer)!=null)
490             sc.visit(purple);
491         else
492             sc.visit(green);
493     }
494 
495     private Transformer identityTransformer;
496 
497     /**
498      * Gets the shared instance of the identity transformer.
499      */
500     public Transformer getIdentityTransformer() {
501         try {
502             if(identityTransformer==null) {
503                 TransformerFactory tf = XmlFactory.createTransformerFactory(model.options.disableXmlSecurity);
504                 identityTransformer = tf.newTransformer();
505             }
506             return identityTransformer;
507         } catch (TransformerConfigurationException e) {
508             throw new Error(e); // impossible
509         }
510     }
511 
512     /**
513      * Find all types that refer to the given complex type.
514      */
515     public Set<XSComponent> getReferer(XSType c) {
516         if(refFinder==null) {
517             refFinder = new RefererFinder();
518             refFinder.schemaSet(Ring.get(XSSchemaSet.class));
519         }
520         return refFinder.getReferer(c);
521     }
522 
523     /**
524      * Returns the QName of the declaration.
525      * @return null
526      *      if the declaration is anonymous.
527      */
528     public static QName getName(XSDeclaration decl) {
529         String local = decl.getName();
530         if(local==null) return null;
531         return new QName(decl.getTargetNamespace(),local);
532     }
533 
534     /**
535      * Derives a name from a schema component.
536      *
537      * This method handles prefix/suffix modification and
538      * XML-to-Java name conversion.
539      *
540      * @param name
541      *      The base name. This should be things like element names
542      *      or type names.
543      * @param comp
544      *      The component from which the base name was taken.
545      *      Used to determine how names are modified.
546      */
547     public String deriveName( String name, XSComponent comp ) {
548         XSSchema owner = comp.getOwnerSchema();
549 
550         name = getNameConverter().toClassName(name);
551 
552         if( owner!=null ) {
553             BISchemaBinding sb = getBindInfo(owner).get(BISchemaBinding.class);
554 
555             if(sb!=null)    name = sb.mangleClassName(name,comp);
556         }
557 
558         return name;
559     }
560 
561     public boolean isGenerateMixedExtensions() {
562         if (globalBinding != null) {
563             return globalBinding.isGenerateMixedExtensions();
564         }
565         return false;
566     }
567 
568 }