001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036: package com.sun.tools.xjc.reader.xmlschema;
037:
038: import java.io.StringWriter;
039: import java.util.HashMap;
040: import java.util.HashSet;
041: import java.util.Map;
042: import java.util.Set;
043: import java.util.Stack;
044:
045: import com.sun.codemodel.JCodeModel;
046: import com.sun.codemodel.JJavaName;
047: import com.sun.codemodel.JPackage;
048: import com.sun.codemodel.util.JavadocEscapeWriter;
049: import com.sun.istack.NotNull;
050: import com.sun.tools.xjc.model.CBuiltinLeafInfo;
051: import com.sun.tools.xjc.model.CClassInfo;
052: import com.sun.tools.xjc.model.CClassInfoParent;
053: import com.sun.tools.xjc.model.CElement;
054: import com.sun.tools.xjc.model.CElementInfo;
055: import com.sun.tools.xjc.model.CTypeInfo;
056: import com.sun.tools.xjc.model.TypeUse;
057: import com.sun.tools.xjc.model.CClass;
058: import com.sun.tools.xjc.model.CNonElement;
059: import com.sun.tools.xjc.reader.Ring;
060: import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIProperty;
061: import com.sun.tools.xjc.reader.xmlschema.bindinfo.BISchemaBinding;
062: import com.sun.tools.xjc.reader.xmlschema.bindinfo.LocalScoping;
063: import com.sun.xml.bind.v2.WellKnownNamespace;
064: import com.sun.xml.xsom.XSComplexType;
065: import com.sun.xml.xsom.XSComponent;
066: import com.sun.xml.xsom.XSDeclaration;
067: import com.sun.xml.xsom.XSElementDecl;
068: import com.sun.xml.xsom.XSSchema;
069: import com.sun.xml.xsom.XSSchemaSet;
070: import com.sun.xml.xsom.XSSimpleType;
071: import com.sun.xml.xsom.XSType;
072: import com.sun.xml.xsom.impl.util.SchemaWriter;
073: import com.sun.xml.xsom.util.ComponentNameFunction;
074:
075: import org.xml.sax.Locator;
076:
077: /**
078: * Manages association between {@link XSComponent}s and generated
079: * {@link CTypeInfo}s.
080: *
081: * <p>
082: * This class determines which component is mapped to (or is not mapped to)
083: * what types.
084: *
085: * @author
086: * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
087: */
088: public final class ClassSelector extends BindingComponent {
089: /** Center of owner classes. */
090: private final BGMBuilder builder = Ring.get(BGMBuilder.class);
091:
092: /**
093: * Map from XSComponents to {@link Binding}s. Keeps track of all
094: * content interfaces that are already built or being built.
095: */
096: private final Map<XSComponent, Binding> bindMap = new HashMap<XSComponent, Binding>();
097:
098: /**
099: * UGLY HACK.
100: * <p>
101: * To avoid cyclic dependency between binding elements and types,
102: * we need additional markers that tell which elements are definitely not bound
103: * to a class.
104: * <p>
105: * the cyclic dependency is as follows:
106: * elements need to bind its types first, because otherwise it can't
107: * determine T of JAXBElement<T>.
108: * OTOH, types need to know whether its parent is bound to a class to decide
109: * which class name to use.
110: */
111: /*package*/final Map<XSComponent, CElementInfo> boundElements = new HashMap<XSComponent, CElementInfo>();
112:
113: /**
114: * A list of {@link Binding}s object that needs to be built.
115: */
116: private final Stack<Binding> bindQueue = new Stack<Binding>();
117:
118: /**
119: * {@link CClassInfo}s that are already {@link Binding#build() built}.
120: */
121: private final Set<CClassInfo> built = new HashSet<CClassInfo>();
122:
123: /**
124: * Object that determines components that are mapped
125: * to classes.
126: */
127: private final ClassBinder classBinder;
128:
129: /**
130: * {@link CClassInfoParent}s that determines where a new class
131: * should be created.
132: */
133: private final Stack<CClassInfoParent> classScopes = new Stack<CClassInfoParent>();
134:
135: /**
136: * The component that is being bound to {@link #currentBean}.
137: */
138: private XSComponent currentRoot;
139: /**
140: * The bean representation we are binding right now.
141: */
142: private CClassInfo currentBean;
143:
144: private final class Binding {
145: private final XSComponent sc;
146: private final CTypeInfo bean;
147:
148: public Binding(XSComponent sc, CTypeInfo bean) {
149: this .sc = sc;
150: this .bean = bean;
151: }
152:
153: void build() {
154: if (!(this .bean instanceof CClassInfo))
155: return; // no need to "build"
156:
157: CClassInfo bean = (CClassInfo) this .bean;
158:
159: if (!built.add(bean))
160: return; // already built
161:
162: for (String reservedClassName : reservedClassNames) {
163: if (bean.getName().equals(reservedClassName)) {
164: getErrorReporter().error(sc.getLocator(),
165: Messages.ERR_RESERVED_CLASS_NAME,
166: reservedClassName);
167: break;
168: }
169: }
170:
171: // if this schema component is an element declaration
172: // and it satisfies a set of conditions specified in the spec,
173: // this class will receive a constructor.
174: if (needValueConstructor(sc)) {
175: // TODO: fragile. There is no guarantee that the property name
176: // is in fact "value".
177: bean.addConstructor("value");
178: }
179:
180: if (bean.javadoc == null)
181: addSchemaFragmentJavadoc(bean, sc);
182:
183: // build the body
184: if (builder.getGlobalBinding().getFlattenClasses() == LocalScoping.NESTED)
185: pushClassScope(bean);
186: else
187: pushClassScope(bean.parent());
188: XSComponent oldRoot = currentRoot;
189: CClassInfo oldBean = currentBean;
190: currentRoot = sc;
191: currentBean = bean;
192: sc.visit(Ring.get(BindRed.class));
193: currentBean = oldBean;
194: currentRoot = oldRoot;
195: popClassScope();
196:
197: // acknowledge property customization on this schema component,
198: // since it is OK to have a customization at the point of declaration
199: // even when no one is using it.
200: BIProperty prop = builder.getBindInfo(sc).get(
201: BIProperty.class);
202: if (prop != null)
203: prop.markAsAcknowledged();
204: }
205: }
206:
207: // should be instanciated only from BGMBuilder.
208: public ClassSelector() {
209: classBinder = new Abstractifier(new DefaultClassBinder());
210: Ring.add(ClassBinder.class, classBinder);
211:
212: classScopes.push(null); // so that the getClassFactory method returns null
213:
214: XSComplexType anyType = Ring.get(XSSchemaSet.class)
215: .getComplexType(WellKnownNamespace.XML_SCHEMA,
216: "anyType");
217: bindMap.put(anyType, new Binding(anyType,
218: CBuiltinLeafInfo.ANYTYPE));
219: }
220:
221: /** Gets the current class scope. */
222: public final CClassInfoParent getClassScope() {
223: assert !classScopes.isEmpty();
224: return classScopes.peek();
225: }
226:
227: public final void pushClassScope(CClassInfoParent clsFctry) {
228: assert clsFctry != null;
229: classScopes.push(clsFctry);
230: }
231:
232: public final void popClassScope() {
233: classScopes.pop();
234: }
235:
236: public XSComponent getCurrentRoot() {
237: return currentRoot;
238: }
239:
240: public CClassInfo getCurrentBean() {
241: return currentBean;
242: }
243:
244: /**
245: * Checks if the given component is bound to a class.
246: */
247: public final CElement isBound(XSElementDecl x, XSComponent referer) {
248: CElementInfo r = boundElements.get(x);
249: if (r != null)
250: return r;
251: return bindToType(x, referer);
252: }
253:
254: /**
255: * Checks if the given component is being mapped to a type.
256: * If so, build that type and return that object.
257: * If it is not being mapped to a type item, return null.
258: */
259: public CTypeInfo bindToType(XSComponent sc, XSComponent referer) {
260: return _bindToClass(sc, referer, false);
261: }
262:
263: //
264: // some schema components are guaranteed to map to a particular CTypeInfo.
265: // the following versions capture those constraints in the signature
266: // and making the bindToType invocation more type safe.
267: //
268:
269: public CElement bindToType(XSElementDecl e, XSComponent referer) {
270: return (CElement) _bindToClass(e, referer, false);
271: }
272:
273: public CClass bindToType(XSComplexType t, XSComponent referer,
274: boolean cannotBeDelayed) {
275: // this assumption that a complex type always binds to a ClassInfo
276: // does not hold for xs:anyType --- our current approach of handling
277: // this idiosynchracy is to make sure that xs:anyType doesn't use
278: // this codepath.
279: return (CClass) _bindToClass(t, referer, cannotBeDelayed);
280: }
281:
282: public TypeUse bindToType(XSType t, XSComponent referer) {
283: if (t instanceof XSSimpleType) {
284: return Ring.get(SimpleTypeBuilder.class).build(
285: (XSSimpleType) t);
286: } else
287: return (CNonElement) _bindToClass(t, referer, false);
288: }
289:
290: /**
291: * The real meat of the "bindToType" code.
292: *
293: * @param cannotBeDelayed
294: * if the binding of the body of the class cannot be defered
295: * and needs to be done immediately. If the flag is false,
296: * the binding of the body will be done later, to avoid
297: * cyclic binding problem.
298: * @param referer
299: * The component that refers to <tt>sc</tt>. This can be null,
300: * if figuring out the referer is too hard, in which case
301: * the error message might be less user friendly.
302: */
303: // TODO: consider getting rid of "cannotBeDelayed"
304: CTypeInfo _bindToClass(@NotNull
305: XSComponent sc, XSComponent referer, boolean cannotBeDelayed) {
306: // check if this class is already built.
307: if (!bindMap.containsKey(sc)) {
308: // craete a bind task
309:
310: // if this is a global declaration, make sure they will be generated
311: // under a package.
312: boolean isGlobal = false;
313: if (sc instanceof XSDeclaration) {
314: isGlobal = ((XSDeclaration) sc).isGlobal();
315: if (isGlobal)
316: pushClassScope(new CClassInfoParent.Package(
317: getPackage(((XSDeclaration) sc)
318: .getTargetNamespace())));
319: }
320:
321: // otherwise check if this component should become a class.
322: CElement bean = sc.apply(classBinder);
323:
324: if (isGlobal)
325: popClassScope();
326:
327: if (bean == null)
328: return null;
329:
330: // can this namespace generate a class?
331: if (bean instanceof CClassInfo) {
332: XSSchema os = sc.getOwnerSchema();
333: BISchemaBinding sb = builder.getBindInfo(os).get(
334: BISchemaBinding.class);
335: if (sb != null && !sb.map) {
336: // nope
337: getErrorReporter()
338: .error(
339: sc.getLocator(),
340: Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS,
341: sc
342: .apply(new ComponentNameFunction()));
343: getErrorReporter()
344: .error(
345: sb.getLocation(),
346: Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_MAP_FALSE,
347: os.getTargetNamespace());
348: if (referer != null)
349: getErrorReporter()
350: .error(
351: referer.getLocator(),
352: Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_REFERER,
353: referer
354: .apply(new ComponentNameFunction()));
355: }
356: }
357:
358: queueBuild(sc, bean);
359: }
360:
361: Binding bind = bindMap.get(sc);
362: if (cannotBeDelayed)
363: bind.build();
364:
365: return bind.bean;
366: }
367:
368: /**
369: * Runs all the pending build tasks.
370: */
371: public void executeTasks() {
372: while (bindQueue.size() != 0)
373: bindQueue.pop().build();
374: }
375:
376: /**
377: * Determines if the given component needs to have a value
378: * constructor (a constructor that takes a parmater.) on ObjectFactory.
379: */
380: private boolean needValueConstructor(XSComponent sc) {
381: if (!(sc instanceof XSElementDecl))
382: return false;
383:
384: XSElementDecl decl = (XSElementDecl) sc;
385: if (!decl.getType().isSimpleType())
386: return false;
387:
388: return true;
389: }
390:
391: private static final String[] reservedClassNames = new String[] { "ObjectFactory" };
392:
393: public void queueBuild(XSComponent sc, CElement bean) {
394: // it is an error if the same component is built twice,
395: // or the association is modified.
396: Binding b = new Binding(sc, bean);
397: bindQueue.push(b);
398: Binding old = bindMap.put(sc, b);
399: assert old == null || old.bean == bean;
400: }
401:
402: /**
403: * Copies a schema fragment into the javadoc of the generated class.
404: */
405: private void addSchemaFragmentJavadoc(CClassInfo bean,
406: XSComponent sc) {
407:
408: // first, pick it up from <documentation> if any.
409: String doc = builder.getBindInfo(sc).getDocumentation();
410: if (doc != null)
411: append(bean, doc);
412:
413: // then the description of where this component came from
414: Locator loc = sc.getLocator();
415: String fileName = null;
416: if (loc != null) {
417: fileName = loc.getPublicId();
418: if (fileName == null)
419: fileName = loc.getSystemId();
420: }
421: if (fileName == null)
422: fileName = "";
423:
424: String lineNumber = Messages
425: .format(Messages.JAVADOC_LINE_UNKNOWN);
426: if (loc != null && loc.getLineNumber() != -1)
427: lineNumber = String.valueOf(loc.getLineNumber());
428:
429: String componentName = sc.apply(new ComponentNameFunction());
430: String jdoc = Messages.format(Messages.JAVADOC_HEADING,
431: componentName, fileName, lineNumber);
432: append(bean, jdoc);
433:
434: // then schema fragment
435: StringWriter out = new StringWriter();
436: out.write("<pre>\n");
437: SchemaWriter sw = new SchemaWriter(new JavadocEscapeWriter(out));
438: sc.visit(sw);
439: out.write("</pre>");
440: append(bean, out.toString());
441: }
442:
443: private void append(CClassInfo bean, String doc) {
444: if (bean.javadoc == null)
445: bean.javadoc = doc + '\n';
446: else
447: bean.javadoc += '\n' + doc + '\n';
448: }
449:
450: /**
451: * Set of package names that are tested (set of <code>String</code>s.)
452: *
453: * This set is used to avoid duplicating "incorrect package name"
454: * errors.
455: */
456: private static Set<String> checkedPackageNames = new HashSet<String>();
457:
458: /**
459: * Gets the Java package to which classes from
460: * this namespace should go.
461: *
462: * <p>
463: * Usually, the getOuterClass method should be used
464: * to determine where to put a class.
465: */
466: public JPackage getPackage(String targetNamespace) {
467: XSSchema s = Ring.get(XSSchemaSet.class).getSchema(
468: targetNamespace);
469:
470: BISchemaBinding sb = builder.getBindInfo(s).get(
471: BISchemaBinding.class);
472: if (sb != null)
473: sb.markAsAcknowledged();
474:
475: String name = null;
476:
477: // "-p" takes precedence over everything else
478: if (builder.defaultPackage1 != null)
479: name = builder.defaultPackage1;
480:
481: // use the <jaxb:package> customization
482: if (name == null && sb != null && sb.getPackageName() != null)
483: name = sb.getPackageName();
484:
485: // the JAX-RPC option goes below the <jaxb:package>
486: if (name == null && builder.defaultPackage2 != null)
487: name = builder.defaultPackage2;
488:
489: // generate the package name from the targetNamespace
490: if (name == null)
491: name = builder.getNameConverter().toPackageName(
492: targetNamespace);
493:
494: // hardcode a package name because the code doesn't compile
495: // if it generated into the default java package
496: if (name == null)
497: name = "generated"; // the last resort
498:
499: // check if the package name is a valid name.
500: if (checkedPackageNames.add(name)) {
501: // this is the first time we hear about this package name.
502: if (!JJavaName.isJavaPackageName(name))
503: // TODO: s.getLocator() is not very helpful.
504: // ideally, we'd like to use the locator where this package name
505: // comes from.
506: getErrorReporter().error(s.getLocator(),
507: Messages.ERR_INCORRECT_PACKAGE_NAME,
508: targetNamespace, name);
509: }
510:
511: return Ring.get(JCodeModel.class)._package(name);
512: }
513: }
|