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
26package com.sun.tools.internal.xjc.reader.xmlschema;
27
28import java.io.StringWriter;
29import java.util.HashMap;
30import java.util.HashSet;
31import java.util.Map;
32import java.util.Set;
33import java.util.Stack;
34
35import com.sun.codemodel.internal.JCodeModel;
36import com.sun.codemodel.internal.JJavaName;
37import com.sun.codemodel.internal.JPackage;
38import com.sun.codemodel.internal.util.JavadocEscapeWriter;
39import com.sun.istack.internal.NotNull;
40import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo;
41import com.sun.tools.internal.xjc.model.CClassInfo;
42import com.sun.tools.internal.xjc.model.CClassInfoParent;
43import com.sun.tools.internal.xjc.model.CElement;
44import com.sun.tools.internal.xjc.model.CElementInfo;
45import com.sun.tools.internal.xjc.model.CTypeInfo;
46import com.sun.tools.internal.xjc.model.TypeUse;
47import com.sun.tools.internal.xjc.model.CClass;
48import com.sun.tools.internal.xjc.model.CNonElement;
49import com.sun.tools.internal.xjc.reader.Ring;
50import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIProperty;
51import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding;
52import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.LocalScoping;
53import com.sun.xml.internal.bind.v2.WellKnownNamespace;
54import com.sun.xml.internal.xsom.XSComplexType;
55import com.sun.xml.internal.xsom.XSComponent;
56import com.sun.xml.internal.xsom.XSDeclaration;
57import com.sun.xml.internal.xsom.XSElementDecl;
58import com.sun.xml.internal.xsom.XSSchema;
59import com.sun.xml.internal.xsom.XSSchemaSet;
60import com.sun.xml.internal.xsom.XSSimpleType;
61import com.sun.xml.internal.xsom.XSType;
62import com.sun.xml.internal.xsom.impl.util.SchemaWriter;
63import com.sun.xml.internal.xsom.util.ComponentNameFunction;
64
65import 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 */
78public 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}
486