author | mkos |
Sun, 30 Dec 2012 00:00:00 +0100 | |
changeset 22678 | ac1ea46be942 |
parent 12009 | 4abb694f273a |
permissions | -rw-r--r-- |
12009 | 1 |
/* |
22678
ac1ea46be942
8029237: Update copyright year to match last edit in jaxws repository for 2012
mkos
parents:
12009
diff
changeset
|
2 |
* Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. |
12009 | 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.io.StringWriter; |
|
29 |
import java.util.HashMap; |
|
30 |
import java.util.HashSet; |
|
31 |
import java.util.Map; |
|
32 |
import java.util.Set; |
|
33 |
import java.util.Stack; |
|
34 |
||
35 |
import com.sun.codemodel.internal.JCodeModel; |
|
36 |
import com.sun.codemodel.internal.JJavaName; |
|
37 |
import com.sun.codemodel.internal.JPackage; |
|
38 |
import com.sun.codemodel.internal.util.JavadocEscapeWriter; |
|
39 |
import com.sun.istack.internal.NotNull; |
|
40 |
import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo; |
|
41 |
import com.sun.tools.internal.xjc.model.CClassInfo; |
|
42 |
import com.sun.tools.internal.xjc.model.CClassInfoParent; |
|
43 |
import com.sun.tools.internal.xjc.model.CElement; |
|
44 |
import com.sun.tools.internal.xjc.model.CElementInfo; |
|
45 |
import com.sun.tools.internal.xjc.model.CTypeInfo; |
|
46 |
import com.sun.tools.internal.xjc.model.TypeUse; |
|
47 |
import com.sun.tools.internal.xjc.model.CClass; |
|
48 |
import com.sun.tools.internal.xjc.model.CNonElement; |
|
49 |
import com.sun.tools.internal.xjc.reader.Ring; |
|
50 |
import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIProperty; |
|
51 |
import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding; |
|
52 |
import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.LocalScoping; |
|
53 |
import com.sun.xml.internal.bind.v2.WellKnownNamespace; |
|
54 |
import com.sun.xml.internal.xsom.XSComplexType; |
|
55 |
import com.sun.xml.internal.xsom.XSComponent; |
|
56 |
import com.sun.xml.internal.xsom.XSDeclaration; |
|
57 |
import com.sun.xml.internal.xsom.XSElementDecl; |
|
58 |
import com.sun.xml.internal.xsom.XSSchema; |
|
59 |
import com.sun.xml.internal.xsom.XSSchemaSet; |
|
60 |
import com.sun.xml.internal.xsom.XSSimpleType; |
|
61 |
import com.sun.xml.internal.xsom.XSType; |
|
62 |
import com.sun.xml.internal.xsom.impl.util.SchemaWriter; |
|
63 |
import com.sun.xml.internal.xsom.util.ComponentNameFunction; |
|
64 |
||
65 |
import 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 |
*/ |
|
78 |
public 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 <tt>sc</tt>. 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</code>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 |
} |