1/*
2 * Copyright (c) 1997, 2015, 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
26package com.sun.tools.internal.xjc.reader.xmlschema;
27
28import java.util.ArrayList;
29import java.util.HashMap;
30import java.util.List;
31import java.util.Map;
32import java.util.Set;
33
34import javax.xml.namespace.QName;
35import javax.xml.transform.Transformer;
36import javax.xml.transform.TransformerConfigurationException;
37import javax.xml.transform.TransformerFactory;
38
39import com.sun.codemodel.internal.JCodeModel;
40import com.sun.codemodel.internal.fmt.JTextFile;
41import com.sun.istack.internal.NotNull;
42import com.sun.istack.internal.Nullable;
43import com.sun.tools.internal.xjc.ErrorReceiver;
44import com.sun.tools.internal.xjc.Options;
45import com.sun.tools.internal.xjc.Plugin;
46import com.sun.tools.internal.xjc.generator.bean.field.FieldRendererFactory;
47import com.sun.tools.internal.xjc.model.CClassInfoParent;
48import com.sun.tools.internal.xjc.model.Model;
49import com.sun.tools.internal.xjc.reader.ModelChecker;
50import com.sun.tools.internal.xjc.reader.Ring;
51import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDeclaration;
52import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDom;
53import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIGlobalBinding;
54import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding;
55import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISerializable;
56import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BindInfo;
57import com.sun.tools.internal.xjc.util.CodeModelClassFactory;
58import com.sun.tools.internal.xjc.util.ErrorReceiverFilter;
59import com.sun.xml.internal.bind.api.impl.NameConverter;
60import com.sun.xml.internal.bind.v2.util.XmlFactory;
61import com.sun.xml.internal.xsom.XSAnnotation;
62import com.sun.xml.internal.xsom.XSAttributeUse;
63import com.sun.xml.internal.xsom.XSComponent;
64import com.sun.xml.internal.xsom.XSDeclaration;
65import com.sun.xml.internal.xsom.XSParticle;
66import com.sun.xml.internal.xsom.XSSchema;
67import com.sun.xml.internal.xsom.XSSchemaSet;
68import com.sun.xml.internal.xsom.XSSimpleType;
69import com.sun.xml.internal.xsom.XSTerm;
70import com.sun.xml.internal.xsom.XSType;
71import com.sun.xml.internal.xsom.XSWildcard;
72import com.sun.xml.internal.xsom.util.XSFinder;
73
74import org.xml.sax.Locator;
75
76/**
77 * Root of the XML Schema binder.
78 *
79 * <div><img src="doc-files/binding_chart.png" alt=""></div>
80 *
81 * @author Kohsuke Kawaguchi
82 */
83public 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 "{@code 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}
569