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