|
1 /* |
|
2 * Copyright (c) 1997, 2011, 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 |
|
26 package com.sun.tools.internal.xjc.reader.xmlschema; |
|
27 |
|
28 import java.io.StringWriter; |
|
29 import java.math.BigInteger; |
|
30 import java.text.ParseException; |
|
31 import java.util.ArrayList; |
|
32 import java.util.Arrays; |
|
33 import java.util.Collections; |
|
34 import java.util.HashMap; |
|
35 import java.util.HashSet; |
|
36 import java.util.List; |
|
37 import java.util.Map; |
|
38 import java.util.Set; |
|
39 import java.util.Stack; |
|
40 |
|
41 import javax.activation.MimeTypeParseException; |
|
42 |
|
43 import com.sun.codemodel.internal.JJavaName; |
|
44 import com.sun.codemodel.internal.util.JavadocEscapeWriter; |
|
45 import com.sun.tools.internal.xjc.ErrorReceiver; |
|
46 import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo; |
|
47 import com.sun.tools.internal.xjc.model.CClassInfo; |
|
48 import com.sun.tools.internal.xjc.model.CClassInfoParent; |
|
49 import com.sun.tools.internal.xjc.model.CClassRef; |
|
50 import com.sun.tools.internal.xjc.model.CEnumConstant; |
|
51 import com.sun.tools.internal.xjc.model.CEnumLeafInfo; |
|
52 import com.sun.tools.internal.xjc.model.CNonElement; |
|
53 import com.sun.tools.internal.xjc.model.Model; |
|
54 import com.sun.tools.internal.xjc.model.TypeUse; |
|
55 import com.sun.tools.internal.xjc.model.TypeUseFactory; |
|
56 import com.sun.tools.internal.xjc.reader.Const; |
|
57 import com.sun.tools.internal.xjc.reader.Ring; |
|
58 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIConversion; |
|
59 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIEnum; |
|
60 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIEnumMember; |
|
61 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIProperty; |
|
62 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BindInfo; |
|
63 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.EnumMemberMode; |
|
64 import com.sun.tools.internal.xjc.util.MimeTypeRange; |
|
65 import com.sun.xml.internal.bind.DatatypeConverterImpl; |
|
66 import com.sun.xml.internal.bind.v2.WellKnownNamespace; |
|
67 import static com.sun.xml.internal.bind.v2.WellKnownNamespace.XML_MIME_URI; |
|
68 import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter; |
|
69 import com.sun.xml.internal.xsom.XSAttributeDecl; |
|
70 import com.sun.xml.internal.xsom.XSComplexType; |
|
71 import com.sun.xml.internal.xsom.XSComponent; |
|
72 import com.sun.xml.internal.xsom.XSElementDecl; |
|
73 import com.sun.xml.internal.xsom.XSFacet; |
|
74 import com.sun.xml.internal.xsom.XSListSimpleType; |
|
75 import com.sun.xml.internal.xsom.XSRestrictionSimpleType; |
|
76 import com.sun.xml.internal.xsom.XSSimpleType; |
|
77 import com.sun.xml.internal.xsom.XSUnionSimpleType; |
|
78 import com.sun.xml.internal.xsom.XSVariety; |
|
79 import com.sun.xml.internal.xsom.impl.util.SchemaWriter; |
|
80 import com.sun.xml.internal.xsom.visitor.XSSimpleTypeFunction; |
|
81 import com.sun.xml.internal.xsom.visitor.XSVisitor; |
|
82 |
|
83 import org.xml.sax.Locator; |
|
84 |
|
85 /** |
|
86 * Builds {@link TypeUse} from simple types. |
|
87 * |
|
88 * <p> |
|
89 * This code consists of two main portions. The {@link #compose(XSSimpleType)} method |
|
90 * and {@link #composer} forms an outer cycle, which gradually ascends the type |
|
91 * inheritance chain until it finds the suitable binding. When it does this |
|
92 * {@link #initiatingType} is set to the type which started binding, so that we can refer |
|
93 * to the actual constraint facets and such that are applicable on the type. |
|
94 * |
|
95 * <p> |
|
96 * For each intermediate type in the chain, the {@link #find(XSSimpleType)} method |
|
97 * is used to find the binding on that type, sine the outer loop is doing the ascending, |
|
98 * this method only sees if the current type has some binding available. |
|
99 * |
|
100 * <p> |
|
101 * There is at least one ugly code that you need to aware of |
|
102 * when you are modifying the code. See the documentation |
|
103 * about <a href="package.html#stref_cust"> |
|
104 * "simple type customization at the point of reference."</a> |
|
105 * |
|
106 * |
|
107 * @author |
|
108 * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) |
|
109 */ |
|
110 public final class SimpleTypeBuilder extends BindingComponent { |
|
111 |
|
112 protected final BGMBuilder builder = Ring.get(BGMBuilder.class); |
|
113 |
|
114 private final Model model = Ring.get(Model.class); |
|
115 |
|
116 /** |
|
117 * The component that is refering to the simple type |
|
118 * which we are building. This is ugly but necessary |
|
119 * to support the customization of simple types at |
|
120 * its point of reference. See my comment at the header |
|
121 * of this class for details. |
|
122 * |
|
123 * UGLY: Implemented as a Stack of XSComponent to fix a bug |
|
124 */ |
|
125 public final Stack<XSComponent> refererStack = new Stack<XSComponent>(); |
|
126 |
|
127 /** |
|
128 * Records what xmime:expectedContentTypes annotations we honored and processed, |
|
129 * so that we can later check if the user had these annotations in the places |
|
130 * where we didn't anticipate them. |
|
131 */ |
|
132 private final Set<XSComponent> acknowledgedXmimeContentTypes = new HashSet<XSComponent>(); |
|
133 |
|
134 /** |
|
135 * The type that was originally passed to this {@link SimpleTypeBuilder#build(XSSimpleType)}. |
|
136 * Never null. |
|
137 */ |
|
138 private XSSimpleType initiatingType; |
|
139 |
|
140 /** {@link TypeUse}s for the built-in types. Read-only. */ |
|
141 public static final Map<String,TypeUse> builtinConversions = new HashMap<String,TypeUse>(); |
|
142 |
|
143 |
|
144 /** |
|
145 * Entry point from outside. Builds a BGM type expression |
|
146 * from a simple type schema component. |
|
147 * |
|
148 * @param type |
|
149 * the simple type to be bound. |
|
150 */ |
|
151 public TypeUse build( XSSimpleType type ) { |
|
152 XSSimpleType oldi = initiatingType; |
|
153 this.initiatingType = type; |
|
154 |
|
155 TypeUse e = checkRefererCustomization(type); |
|
156 if(e==null) |
|
157 e = compose(type); |
|
158 |
|
159 initiatingType = oldi; |
|
160 |
|
161 return e; |
|
162 } |
|
163 |
|
164 /** |
|
165 * A version of the {@link #build(XSSimpleType)} method |
|
166 * used to bind the definition of a class generated from |
|
167 * the given simple type. |
|
168 */ |
|
169 public TypeUse buildDef( XSSimpleType type ) { |
|
170 XSSimpleType oldi = initiatingType; |
|
171 this.initiatingType = type; |
|
172 |
|
173 TypeUse e = type.apply(composer); |
|
174 |
|
175 initiatingType = oldi; |
|
176 |
|
177 return e; |
|
178 } |
|
179 |
|
180 |
|
181 /** |
|
182 * Returns a javaType customization specified to the referer, if present. |
|
183 * @return can be null. |
|
184 */ |
|
185 private BIConversion getRefererCustomization() { |
|
186 BindInfo info = builder.getBindInfo(getReferer()); |
|
187 BIProperty prop = info.get(BIProperty.class); |
|
188 if(prop==null) return null; |
|
189 return prop.getConv(); |
|
190 } |
|
191 |
|
192 public XSComponent getReferer() { |
|
193 return refererStack.peek(); |
|
194 } |
|
195 |
|
196 /** |
|
197 * Checks if the referer has a conversion customization or not. |
|
198 * If it does, use it to bind this simple type. Otherwise |
|
199 * return null; |
|
200 */ |
|
201 private TypeUse checkRefererCustomization( XSSimpleType type ) { |
|
202 |
|
203 // assertion check. referer must be set properly |
|
204 // before the build method is called. |
|
205 // since the handling of the simple type point-of-reference |
|
206 // customization is very error prone, it deserves a strict |
|
207 // assertion check. |
|
208 // UGLY CODE WARNING |
|
209 XSComponent top = getReferer(); |
|
210 |
|
211 if( top instanceof XSElementDecl ) { |
|
212 // if the parent is element type, its content type must be us. |
|
213 XSElementDecl eref = (XSElementDecl)top; |
|
214 assert eref.getType()==type; |
|
215 |
|
216 // for elements, you can't use <property>, |
|
217 // so we allow javaType to appear directly. |
|
218 BindInfo info = builder.getBindInfo(top); |
|
219 BIConversion conv = info.get(BIConversion.class); |
|
220 if(conv!=null) { |
|
221 conv.markAsAcknowledged(); |
|
222 // the conversion is given. |
|
223 return conv.getTypeUse(type); |
|
224 } |
|
225 detectJavaTypeCustomization(); |
|
226 } else |
|
227 if( top instanceof XSAttributeDecl ) { |
|
228 XSAttributeDecl aref = (XSAttributeDecl)top; |
|
229 assert aref.getType()==type; |
|
230 detectJavaTypeCustomization(); |
|
231 } else |
|
232 if( top instanceof XSComplexType ) { |
|
233 XSComplexType tref = (XSComplexType)top; |
|
234 assert tref.getBaseType()==type || tref.getContentType()==type; |
|
235 detectJavaTypeCustomization(); |
|
236 } else |
|
237 if( top == type ) { |
|
238 // this means the simple type is built by itself and |
|
239 // not because it's referenced by something. |
|
240 } else |
|
241 // unexpected referer type. |
|
242 assert false; |
|
243 |
|
244 // now we are certain that the referer is OK. |
|
245 // see if it has a conversion customization. |
|
246 BIConversion conv = getRefererCustomization(); |
|
247 if(conv!=null) { |
|
248 conv.markAsAcknowledged(); |
|
249 // the conversion is given. |
|
250 return conv.getTypeUse(type); |
|
251 } else |
|
252 // not found |
|
253 return null; |
|
254 } |
|
255 |
|
256 /** |
|
257 * Detect "javaType" customizations placed directly on simple types, rather |
|
258 * than being enclosed by "property" and "baseType" customizations (see |
|
259 * sec 6.8.1 of the spec). |
|
260 * |
|
261 * Report an error if any exist. |
|
262 */ |
|
263 private void detectJavaTypeCustomization() { |
|
264 BindInfo info = builder.getBindInfo(getReferer()); |
|
265 BIConversion conv = info.get(BIConversion.class); |
|
266 |
|
267 if( conv != null ) { |
|
268 // ack this conversion to prevent further error messages |
|
269 conv.markAsAcknowledged(); |
|
270 |
|
271 // report the error |
|
272 getErrorReporter().error( conv.getLocation(), |
|
273 Messages.ERR_UNNESTED_JAVATYPE_CUSTOMIZATION_ON_SIMPLETYPE ); |
|
274 } |
|
275 } |
|
276 |
|
277 /** |
|
278 * Recursively decend the type inheritance chain to find a binding. |
|
279 */ |
|
280 TypeUse compose( XSSimpleType t ) { |
|
281 TypeUse e = find(t); |
|
282 if(e!=null) return e; |
|
283 return t.apply(composer); |
|
284 } |
|
285 |
|
286 public final XSSimpleTypeFunction<TypeUse> composer = new XSSimpleTypeFunction<TypeUse>() { |
|
287 |
|
288 public TypeUse listSimpleType(XSListSimpleType type) { |
|
289 // bind item type individually and then compose them into a list |
|
290 // facets on the list shouldn't be taken account when binding item types, |
|
291 // so weed to call build(), not compose(). |
|
292 XSSimpleType itemType = type.getItemType(); |
|
293 refererStack.push(itemType); |
|
294 TypeUse tu = TypeUseFactory.makeCollection(build(type.getItemType())); |
|
295 refererStack.pop(); |
|
296 return tu; |
|
297 } |
|
298 |
|
299 public TypeUse unionSimpleType(XSUnionSimpleType type) { |
|
300 boolean isCollection = false; |
|
301 for( int i=0; i<type.getMemberSize(); i++ ) |
|
302 if(type.getMember(i).getVariety()==XSVariety.LIST || type.getMember(i).getVariety()==XSVariety.UNION) { |
|
303 isCollection = true; |
|
304 break; |
|
305 } |
|
306 |
|
307 TypeUse r = CBuiltinLeafInfo.STRING; |
|
308 if(isCollection) |
|
309 r = TypeUseFactory.makeCollection(r); |
|
310 return r; |
|
311 } |
|
312 |
|
313 public TypeUse restrictionSimpleType(XSRestrictionSimpleType type) { |
|
314 // just process the base type. |
|
315 return compose(type.getSimpleBaseType()); |
|
316 } |
|
317 }; |
|
318 |
|
319 |
|
320 /** |
|
321 * Checks if there's any binding available on the given type. |
|
322 * |
|
323 * @return |
|
324 * null if not (which causes the {@link #compose(XSSimpleType)} method |
|
325 * to do ascending. |
|
326 */ |
|
327 private TypeUse find( XSSimpleType type ) { |
|
328 TypeUse r; |
|
329 boolean noAutoEnum = false; |
|
330 |
|
331 // check for user specified conversion |
|
332 BindInfo info = builder.getBindInfo(type); |
|
333 BIConversion conv = info.get(BIConversion.class); |
|
334 |
|
335 if( conv!=null ) { |
|
336 // a conversion was found |
|
337 conv.markAsAcknowledged(); |
|
338 return conv.getTypeUse(type); |
|
339 } |
|
340 |
|
341 // look for enum customization, which is another user specified conversion |
|
342 BIEnum en = info.get(BIEnum.class); |
|
343 if( en!=null ) { |
|
344 en.markAsAcknowledged(); |
|
345 |
|
346 if(!en.isMapped()) { |
|
347 noAutoEnum = true; |
|
348 } else { |
|
349 // if an enum customization is specified, make sure |
|
350 // the type is OK |
|
351 if( !canBeMappedToTypeSafeEnum(type) ) { |
|
352 getErrorReporter().error( en.getLocation(), |
|
353 Messages.ERR_CANNOT_BE_TYPE_SAFE_ENUM ); |
|
354 getErrorReporter().error( type.getLocator(), |
|
355 Messages.ERR_CANNOT_BE_TYPE_SAFE_ENUM_LOCATION ); |
|
356 // recover by ignoring this customization |
|
357 return null; |
|
358 } |
|
359 |
|
360 // reference? |
|
361 if(en.ref!=null) { |
|
362 if(!JJavaName.isFullyQualifiedClassName(en.ref)) { |
|
363 Ring.get(ErrorReceiver.class).error( en.getLocation(), |
|
364 Messages.format(Messages.ERR_INCORRECT_CLASS_NAME, en.ref) ); |
|
365 // recover by ignoring @ref |
|
366 return null; |
|
367 } |
|
368 |
|
369 return new CClassRef(model, type, en, info.toCustomizationList() ); |
|
370 } |
|
371 |
|
372 // list and union cannot be mapped to a type-safe enum, |
|
373 // so in this stage we can safely cast it to XSRestrictionSimpleType |
|
374 return bindToTypeSafeEnum( (XSRestrictionSimpleType)type, |
|
375 en.className, en.javadoc, en.members, |
|
376 getEnumMemberMode().getModeWithEnum(), |
|
377 en.getLocation() ); |
|
378 } |
|
379 } |
|
380 |
|
381 |
|
382 // if the type is built in, look for the default binding |
|
383 if(type.getTargetNamespace().equals(WellKnownNamespace.XML_SCHEMA)) { |
|
384 String name = type.getName(); |
|
385 if(name!=null) { |
|
386 r = lookupBuiltin(name); |
|
387 if(r!=null) |
|
388 return r; |
|
389 } |
|
390 } |
|
391 |
|
392 // also check for swaRef |
|
393 if(type.getTargetNamespace().equals(WellKnownNamespace.SWA_URI)) { |
|
394 String name = type.getName(); |
|
395 if(name!=null && name.equals("swaRef")) |
|
396 return CBuiltinLeafInfo.STRING.makeAdapted(SwaRefAdapter.class,false); |
|
397 } |
|
398 |
|
399 |
|
400 // see if this type should be mapped to a type-safe enumeration by default. |
|
401 // if so, built a EnumXDucer from it and return it. |
|
402 if(type.isRestriction() && !noAutoEnum) { |
|
403 XSRestrictionSimpleType rst = type.asRestriction(); |
|
404 if(shouldBeMappedToTypeSafeEnumByDefault(rst)) { |
|
405 r = bindToTypeSafeEnum(rst,null,null, Collections.<String, BIEnumMember>emptyMap(), |
|
406 getEnumMemberMode(),null); |
|
407 if(r!=null) |
|
408 return r; |
|
409 } |
|
410 } |
|
411 |
|
412 return (CNonElement)getClassSelector()._bindToClass(type,null,false); |
|
413 } |
|
414 |
|
415 private static Set<XSRestrictionSimpleType> reportedEnumMemberSizeWarnings; |
|
416 |
|
417 /** |
|
418 * Returns true if a type-safe enum should be created from |
|
419 * the given simple type by default without an explicit <jaxb:enum> customization. |
|
420 */ |
|
421 private boolean shouldBeMappedToTypeSafeEnumByDefault( XSRestrictionSimpleType type ) { |
|
422 |
|
423 // if not, there will be a problem wrt the class name of this type safe enum type. |
|
424 if( type.isLocal() ) return false; |
|
425 |
|
426 // if redefined, we should map the new definition, not the old one. |
|
427 if( type.getRedefinedBy()!=null ) return false; |
|
428 |
|
429 List<XSFacet> facets = type.getDeclaredFacets(XSFacet.FACET_ENUMERATION); |
|
430 if( facets.isEmpty() ) |
|
431 // if the type itself doesn't have the enumeration facet, |
|
432 // it won't be mapped to a type-safe enum. |
|
433 return false; |
|
434 |
|
435 if(facets.size() > builder.getGlobalBinding().getDefaultEnumMemberSizeCap()) { |
|
436 // if there are too many facets, it's not very useful |
|
437 // produce warning when simple type is not mapped to enum |
|
438 // see issue https://jaxb.dev.java.net/issues/show_bug.cgi?id=711 |
|
439 |
|
440 if(reportedEnumMemberSizeWarnings == null) |
|
441 reportedEnumMemberSizeWarnings = new HashSet<XSRestrictionSimpleType>(); |
|
442 |
|
443 if(!reportedEnumMemberSizeWarnings.contains(type)) { |
|
444 getErrorReporter().warning(type.getLocator(), Messages.WARN_ENUM_MEMBER_SIZE_CAP, |
|
445 type.getName(), facets.size(), builder.getGlobalBinding().getDefaultEnumMemberSizeCap()); |
|
446 |
|
447 reportedEnumMemberSizeWarnings.add(type); |
|
448 } |
|
449 |
|
450 return false; |
|
451 } |
|
452 |
|
453 if( !canBeMappedToTypeSafeEnum(type) ) |
|
454 // we simply can't map this to an enumeration |
|
455 return false; |
|
456 |
|
457 // check for collisions among constant names. if a collision will happen, |
|
458 // don't try to bind it to an enum. |
|
459 |
|
460 // return true only when this type is derived from one of the "enum base type". |
|
461 for( XSSimpleType t = type; t!=null; t=t.getSimpleBaseType() ) |
|
462 if( t.isGlobal() && builder.getGlobalBinding().canBeMappedToTypeSafeEnum(t) ) |
|
463 return true; |
|
464 |
|
465 return false; |
|
466 } |
|
467 |
|
468 |
|
469 private static final Set<String> builtinTypeSafeEnumCapableTypes; |
|
470 |
|
471 static { |
|
472 Set<String> s = new HashSet<String>(); |
|
473 |
|
474 // see a bullet of 6.5.1 of the spec. |
|
475 String[] typeNames = new String[] { |
|
476 "string", "boolean", "float", "decimal", "double", "anyURI" |
|
477 }; |
|
478 s.addAll(Arrays.asList(typeNames)); |
|
479 |
|
480 builtinTypeSafeEnumCapableTypes = Collections.unmodifiableSet(s); |
|
481 } |
|
482 |
|
483 |
|
484 /** |
|
485 * Returns true if the given simple type can be mapped to a |
|
486 * type-safe enum class. |
|
487 * |
|
488 * <p> |
|
489 * JAXB spec places a restrictrion as to what type can be |
|
490 * mapped to a type-safe enum. This method enforces this |
|
491 * constraint. |
|
492 */ |
|
493 public static boolean canBeMappedToTypeSafeEnum( XSSimpleType type ) { |
|
494 do { |
|
495 if( WellKnownNamespace.XML_SCHEMA.equals(type.getTargetNamespace()) ) { |
|
496 // type must be derived from one of these types |
|
497 String localName = type.getName(); |
|
498 if( localName!=null ) { |
|
499 if( localName.equals("anySimpleType") ) |
|
500 return false; // catch all case |
|
501 if( localName.equals("ID") || localName.equals("IDREF") ) |
|
502 return false; // not ID/IDREF |
|
503 |
|
504 // other allowed list |
|
505 if( builtinTypeSafeEnumCapableTypes.contains(localName) ) |
|
506 return true; |
|
507 } |
|
508 } |
|
509 |
|
510 type = type.getSimpleBaseType(); |
|
511 } while( type!=null ); |
|
512 |
|
513 return false; |
|
514 } |
|
515 |
|
516 |
|
517 |
|
518 /** |
|
519 * Builds a type-safe enum conversion from a simple type |
|
520 * with enumeration facets. |
|
521 * |
|
522 * @param className |
|
523 * The class name of the type-safe enum. Or null to |
|
524 * create a default name. |
|
525 * @param javadoc |
|
526 * Additional javadoc that will be added at the beginning of the |
|
527 * class, or null if none is necessary. |
|
528 * @param members |
|
529 * A map from enumeration values (as String) to BIEnumMember objects. |
|
530 * if some of the value names need to be overrided. |
|
531 * Cannot be null, but the map may not contain entries |
|
532 * for all enumeration values. |
|
533 * @param loc |
|
534 * The source location where the above customizations are |
|
535 * specified, or null if none is available. |
|
536 */ |
|
537 private TypeUse bindToTypeSafeEnum( XSRestrictionSimpleType type, |
|
538 String className, String javadoc, Map<String,BIEnumMember> members, |
|
539 EnumMemberMode mode, Locator loc ) { |
|
540 |
|
541 if( loc==null ) // use the location of the simple type as the default |
|
542 loc = type.getLocator(); |
|
543 |
|
544 if( className==null ) { |
|
545 // infer the class name. For this to be possible, |
|
546 // the simple type must be a global one. |
|
547 if( !type.isGlobal() ) { |
|
548 getErrorReporter().error( loc, Messages.ERR_NO_ENUM_NAME_AVAILABLE ); |
|
549 // recover by returning a meaningless conversion |
|
550 return CBuiltinLeafInfo.STRING; |
|
551 } |
|
552 className = type.getName(); |
|
553 } |
|
554 |
|
555 // we apply name conversion in any case |
|
556 className = builder.deriveName(className,type); |
|
557 |
|
558 {// compute Javadoc |
|
559 StringWriter out = new StringWriter(); |
|
560 SchemaWriter sw = new SchemaWriter(new JavadocEscapeWriter(out)); |
|
561 type.visit((XSVisitor)sw); |
|
562 |
|
563 if(javadoc!=null) javadoc += "\n\n"; |
|
564 else javadoc = ""; |
|
565 |
|
566 javadoc += Messages.format( Messages.JAVADOC_HEADING, type.getName() ) |
|
567 +"\n<p>\n<pre>\n"+out.getBuffer()+"</pre>"; |
|
568 |
|
569 } |
|
570 |
|
571 // build base type |
|
572 refererStack.push(type.getSimpleBaseType()); |
|
573 TypeUse use = build(type.getSimpleBaseType()); |
|
574 refererStack.pop(); |
|
575 |
|
576 if(use.isCollection()) |
|
577 return null; // can't bind a list to enum constant |
|
578 |
|
579 CNonElement baseDt = use.getInfo(); // for now just ignore that case |
|
580 |
|
581 if(baseDt instanceof CClassInfo) |
|
582 return null; // can't bind to an enum if the base is a class, since we don't have the value constrctor |
|
583 |
|
584 // if the member names collide, re-generate numbered constant names. |
|
585 XSFacet[] errorRef = new XSFacet[1]; |
|
586 List<CEnumConstant> memberList = buildCEnumConstants(type, false, members, errorRef); |
|
587 if(memberList==null || checkMemberNameCollision(memberList)!=null) { |
|
588 switch(mode) { |
|
589 case SKIP: |
|
590 // abort |
|
591 return null; |
|
592 case ERROR: |
|
593 // error |
|
594 if(memberList==null) { |
|
595 getErrorReporter().error( errorRef[0].getLocator(), |
|
596 Messages.ERR_CANNOT_GENERATE_ENUM_NAME, |
|
597 errorRef[0].getValue() ); |
|
598 } else { |
|
599 CEnumConstant[] collision = checkMemberNameCollision(memberList); |
|
600 getErrorReporter().error( collision[0].getLocator(), |
|
601 Messages.ERR_ENUM_MEMBER_NAME_COLLISION, |
|
602 collision[0].getName() ); |
|
603 getErrorReporter().error( collision[1].getLocator(), |
|
604 Messages.ERR_ENUM_MEMBER_NAME_COLLISION_RELATED ); |
|
605 } |
|
606 return null; // recover from error |
|
607 case GENERATE: |
|
608 // generate |
|
609 memberList = buildCEnumConstants(type,true,members,null); |
|
610 break; |
|
611 } |
|
612 } |
|
613 if(memberList.isEmpty()) { |
|
614 getErrorReporter().error( loc, Messages.ERR_NO_ENUM_FACET ); |
|
615 return null; |
|
616 } |
|
617 |
|
618 // use the name of the simple type as the name of the class. |
|
619 CClassInfoParent scope; |
|
620 if(type.isGlobal()) |
|
621 scope = new CClassInfoParent.Package(getClassSelector().getPackage(type.getTargetNamespace())); |
|
622 else |
|
623 scope = getClassSelector().getClassScope(); |
|
624 CEnumLeafInfo xducer = new CEnumLeafInfo( model, BGMBuilder.getName(type), scope, |
|
625 className, baseDt, memberList, type, |
|
626 builder.getBindInfo(type).toCustomizationList(), loc ); |
|
627 xducer.javadoc = javadoc; |
|
628 |
|
629 BIConversion conv = new BIConversion.Static( type.getLocator(),xducer); |
|
630 conv.markAsAcknowledged(); |
|
631 |
|
632 // attach this new conversion object to this simple type |
|
633 // so that successive look up will use the same object. |
|
634 builder.getOrCreateBindInfo(type).addDecl(conv); |
|
635 |
|
636 return conv.getTypeUse(type); |
|
637 } |
|
638 |
|
639 /** |
|
640 * |
|
641 * @param errorRef |
|
642 * if constant names couldn't be generated, return a reference to that enum facet. |
|
643 * @return |
|
644 * null if unable to generate names for some of the constants. |
|
645 */ |
|
646 private List<CEnumConstant> buildCEnumConstants(XSRestrictionSimpleType type, boolean needsToGenerateMemberName, Map<String, BIEnumMember> members, XSFacet[] errorRef) { |
|
647 List<CEnumConstant> memberList = new ArrayList<CEnumConstant>(); |
|
648 int idx=1; |
|
649 Set<String> enums = new HashSet<String>(); // to avoid duplicates. See issue #366 |
|
650 |
|
651 for( XSFacet facet : type.getDeclaredFacets(XSFacet.FACET_ENUMERATION)) { |
|
652 String name=null; |
|
653 String mdoc=builder.getBindInfo(facet).getDocumentation(); |
|
654 |
|
655 if(!enums.add(facet.getValue().value)) |
|
656 continue; // ignore the 2nd occasion |
|
657 |
|
658 if( needsToGenerateMemberName ) { |
|
659 // generate names for all member names. |
|
660 // this will even override names specified by the user. that's crazy. |
|
661 name = "VALUE_"+(idx++); |
|
662 } else { |
|
663 String facetValue = facet.getValue().value; |
|
664 BIEnumMember mem = members.get(facetValue); |
|
665 if( mem==null ) |
|
666 // look at the one attached to the facet object |
|
667 mem = builder.getBindInfo(facet).get(BIEnumMember.class); |
|
668 |
|
669 if (mem!=null) { |
|
670 name = mem.name; |
|
671 if (mdoc != null) { |
|
672 mdoc = mem.javadoc; |
|
673 } |
|
674 } |
|
675 |
|
676 if(name==null) { |
|
677 StringBuilder sb = new StringBuilder(); |
|
678 for( int i=0; i<facetValue.length(); i++) { |
|
679 char ch = facetValue.charAt(i); |
|
680 if(Character.isJavaIdentifierPart(ch)) |
|
681 sb.append(ch); |
|
682 else |
|
683 sb.append('_'); |
|
684 } |
|
685 name = model.getNameConverter().toConstantName(sb.toString()); |
|
686 } |
|
687 } |
|
688 |
|
689 if(!JJavaName.isJavaIdentifier(name)) { |
|
690 if(errorRef!=null) errorRef[0] = facet; |
|
691 return null; // unable to generate a name |
|
692 } |
|
693 |
|
694 memberList.add(new CEnumConstant(name,mdoc,facet.getValue().value,facet,builder.getBindInfo(facet).toCustomizationList(),facet.getLocator())); |
|
695 } |
|
696 return memberList; |
|
697 } |
|
698 |
|
699 /** |
|
700 * Returns non-null if {@link CEnumConstant}s have name collisions among them. |
|
701 * |
|
702 * @return |
|
703 * if there's a collision, return two {@link CEnumConstant}s that collided. |
|
704 * otherwise return null. |
|
705 */ |
|
706 private CEnumConstant[] checkMemberNameCollision( List<CEnumConstant> memberList ) { |
|
707 Map<String,CEnumConstant> names = new HashMap<String,CEnumConstant>(); |
|
708 for (CEnumConstant c : memberList) { |
|
709 CEnumConstant old = names.put(c.getName(),c); |
|
710 if(old!=null) |
|
711 // collision detected |
|
712 return new CEnumConstant[]{old,c}; |
|
713 } |
|
714 return null; |
|
715 } |
|
716 |
|
717 |
|
718 |
|
719 private EnumMemberMode getEnumMemberMode() { |
|
720 return builder.getGlobalBinding().getEnumMemberMode(); |
|
721 } |
|
722 |
|
723 private TypeUse lookupBuiltin( String typeLocalName ) { |
|
724 if(typeLocalName.equals("integer") || typeLocalName.equals("long")) { |
|
725 /* |
|
726 attempt an optimization so that we can |
|
727 improve the binding for types like this: |
|
728 |
|
729 <simpleType> |
|
730 <restriciton baseType="integer"> |
|
731 <maxInclusive value="100" /> |
|
732 </ |
|
733 </ |
|
734 |
|
735 ... to int, not BigInteger. |
|
736 */ |
|
737 |
|
738 BigInteger xe = readFacet(XSFacet.FACET_MAXEXCLUSIVE,-1); |
|
739 BigInteger xi = readFacet(XSFacet.FACET_MAXINCLUSIVE,0); |
|
740 BigInteger max = min(xe,xi); // most restrictive one takes precedence |
|
741 |
|
742 if(max!=null) { |
|
743 BigInteger ne = readFacet(XSFacet.FACET_MINEXCLUSIVE,+1); |
|
744 BigInteger ni = readFacet(XSFacet.FACET_MININCLUSIVE,0); |
|
745 BigInteger min = max(ne,ni); |
|
746 |
|
747 if(min!=null) { |
|
748 if(min.compareTo(INT_MIN )>=0 && max.compareTo(INT_MAX )<=0) |
|
749 typeLocalName = "int"; |
|
750 else |
|
751 if(min.compareTo(LONG_MIN)>=0 && max.compareTo(LONG_MAX)<=0) |
|
752 typeLocalName = "long"; |
|
753 } |
|
754 } |
|
755 } else |
|
756 if(typeLocalName.equals("boolean") && isRestrictedTo0And1()) { |
|
757 // this is seen in the SOAP schema and too common to ignore |
|
758 return CBuiltinLeafInfo.BOOLEAN_ZERO_OR_ONE; |
|
759 } else |
|
760 if(typeLocalName.equals("base64Binary")) { |
|
761 return lookupBinaryTypeBinding(); |
|
762 } else |
|
763 if(typeLocalName.equals("anySimpleType")) { |
|
764 if(getReferer() instanceof XSAttributeDecl || getReferer() instanceof XSSimpleType) |
|
765 return CBuiltinLeafInfo.STRING; |
|
766 else |
|
767 return CBuiltinLeafInfo.ANYTYPE; |
|
768 } |
|
769 return builtinConversions.get(typeLocalName); |
|
770 } |
|
771 |
|
772 /** |
|
773 * Decides the way xs:base64Binary binds. |
|
774 * |
|
775 * This method checks the expected media type. |
|
776 */ |
|
777 private TypeUse lookupBinaryTypeBinding() { |
|
778 XSComponent referer = getReferer(); |
|
779 String emt = referer.getForeignAttribute(XML_MIME_URI, Const.EXPECTED_CONTENT_TYPES); |
|
780 if(emt!=null) { |
|
781 acknowledgedXmimeContentTypes.add(referer); |
|
782 try { |
|
783 // see http://www.xml.com/lpt/a/2004/07/21/dive.html |
|
784 List<MimeTypeRange> types = MimeTypeRange.parseRanges(emt); |
|
785 MimeTypeRange mt = MimeTypeRange.merge(types); |
|
786 |
|
787 // see spec table I-1 in appendix I section 2.1.1 for bindings |
|
788 if(mt.majorType.equalsIgnoreCase("image")) |
|
789 return CBuiltinLeafInfo.IMAGE.makeMimeTyped(mt.toMimeType()); |
|
790 |
|
791 if(( mt.majorType.equalsIgnoreCase("application") || mt.majorType.equalsIgnoreCase("text")) |
|
792 && isXml(mt.subType)) |
|
793 return CBuiltinLeafInfo.XML_SOURCE.makeMimeTyped(mt.toMimeType()); |
|
794 |
|
795 if((mt.majorType.equalsIgnoreCase("text") && (mt.subType.equalsIgnoreCase("plain")) )) { |
|
796 return CBuiltinLeafInfo.STRING.makeMimeTyped(mt.toMimeType()); |
|
797 } |
|
798 |
|
799 return CBuiltinLeafInfo.DATA_HANDLER.makeMimeTyped(mt.toMimeType()); |
|
800 } catch (ParseException e) { |
|
801 getErrorReporter().error( referer.getLocator(), |
|
802 Messages.format(Messages.ERR_ILLEGAL_EXPECTED_MIME_TYPE,emt, e.getMessage()) ); |
|
803 // recover by using the default |
|
804 } catch (MimeTypeParseException e) { |
|
805 getErrorReporter().error( referer.getLocator(), |
|
806 Messages.format(Messages.ERR_ILLEGAL_EXPECTED_MIME_TYPE,emt, e.getMessage()) ); |
|
807 } |
|
808 } |
|
809 // default |
|
810 return CBuiltinLeafInfo.BASE64_BYTE_ARRAY; |
|
811 } |
|
812 |
|
813 public boolean isAcknowledgedXmimeContentTypes(XSComponent c) { |
|
814 return acknowledgedXmimeContentTypes.contains(c); |
|
815 } |
|
816 |
|
817 /** |
|
818 * Returns true if the specified sub-type is an XML type. |
|
819 */ |
|
820 private boolean isXml(String subType) { |
|
821 return subType.equals("xml") || subType.endsWith("+xml"); |
|
822 } |
|
823 |
|
824 /** |
|
825 * Returns true if the {@link #initiatingType} is restricted |
|
826 * to '0' and '1'. This logic is not complete, but it at least |
|
827 * finds the such definition in SOAP @mustUnderstand. |
|
828 */ |
|
829 private boolean isRestrictedTo0And1() { |
|
830 XSFacet pattern = initiatingType.getFacet(XSFacet.FACET_PATTERN); |
|
831 if(pattern!=null) { |
|
832 String v = pattern.getValue().value; |
|
833 if(v.equals("0|1") || v.equals("1|0") || v.equals("\\d")) |
|
834 return true; |
|
835 } |
|
836 XSFacet enumf = initiatingType.getFacet(XSFacet.FACET_ENUMERATION); |
|
837 if(enumf!=null) { |
|
838 String v = enumf.getValue().value; |
|
839 if(v.equals("0") || v.equals("1")) |
|
840 return true; |
|
841 } |
|
842 return false; |
|
843 } |
|
844 |
|
845 private BigInteger readFacet(String facetName,int offset) { |
|
846 XSFacet me = initiatingType.getFacet(facetName); |
|
847 if(me==null) |
|
848 return null; |
|
849 BigInteger bi = DatatypeConverterImpl._parseInteger(me.getValue().value); |
|
850 if(offset!=0) |
|
851 bi = bi.add(BigInteger.valueOf(offset)); |
|
852 return bi; |
|
853 } |
|
854 |
|
855 private BigInteger min(BigInteger a, BigInteger b) { |
|
856 if(a==null) return b; |
|
857 if(b==null) return a; |
|
858 return a.min(b); |
|
859 } |
|
860 |
|
861 private BigInteger max(BigInteger a, BigInteger b) { |
|
862 if(a==null) return b; |
|
863 if(b==null) return a; |
|
864 return a.max(b); |
|
865 } |
|
866 |
|
867 private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE); |
|
868 private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); |
|
869 private static final BigInteger INT_MIN = BigInteger.valueOf(Integer.MIN_VALUE); |
|
870 private static final BigInteger INT_MAX = BigInteger.valueOf(Integer.MAX_VALUE); |
|
871 |
|
872 static { |
|
873 // list of datatypes which have built-in conversions. |
|
874 // note that although xs:token and xs:normalizedString are not |
|
875 // specified in the spec, they need to be here because they |
|
876 // have different whitespace normalization semantics. |
|
877 Map<String,TypeUse> m = builtinConversions; |
|
878 |
|
879 // TODO: this is so dumb |
|
880 m.put("string", CBuiltinLeafInfo.STRING); |
|
881 m.put("anyURI", CBuiltinLeafInfo.STRING); |
|
882 m.put("boolean", CBuiltinLeafInfo.BOOLEAN); |
|
883 // we'll also look at the expected media type, so don't just add this to the map |
|
884 // m.put("base64Binary", CBuiltinLeafInfo.BASE64_BYTE_ARRAY); |
|
885 m.put("hexBinary", CBuiltinLeafInfo.HEXBIN_BYTE_ARRAY); |
|
886 m.put("float", CBuiltinLeafInfo.FLOAT); |
|
887 m.put("decimal", CBuiltinLeafInfo.BIG_DECIMAL); |
|
888 m.put("integer", CBuiltinLeafInfo.BIG_INTEGER); |
|
889 m.put("long", CBuiltinLeafInfo.LONG); |
|
890 m.put("unsignedInt", CBuiltinLeafInfo.LONG); |
|
891 m.put("int", CBuiltinLeafInfo.INT); |
|
892 m.put("unsignedShort", CBuiltinLeafInfo.INT); |
|
893 m.put("short", CBuiltinLeafInfo.SHORT); |
|
894 m.put("unsignedByte", CBuiltinLeafInfo.SHORT); |
|
895 m.put("byte", CBuiltinLeafInfo.BYTE); |
|
896 m.put("double", CBuiltinLeafInfo.DOUBLE); |
|
897 m.put("QName", CBuiltinLeafInfo.QNAME); |
|
898 m.put("NOTATION", CBuiltinLeafInfo.QNAME); |
|
899 m.put("dateTime", CBuiltinLeafInfo.CALENDAR); |
|
900 m.put("date", CBuiltinLeafInfo.CALENDAR); |
|
901 m.put("time", CBuiltinLeafInfo.CALENDAR); |
|
902 m.put("gYearMonth", CBuiltinLeafInfo.CALENDAR); |
|
903 m.put("gYear", CBuiltinLeafInfo.CALENDAR); |
|
904 m.put("gMonthDay", CBuiltinLeafInfo.CALENDAR); |
|
905 m.put("gDay", CBuiltinLeafInfo.CALENDAR); |
|
906 m.put("gMonth", CBuiltinLeafInfo.CALENDAR); |
|
907 m.put("duration", CBuiltinLeafInfo.DURATION); |
|
908 m.put("token", CBuiltinLeafInfo.TOKEN); |
|
909 m.put("normalizedString",CBuiltinLeafInfo.NORMALIZED_STRING); |
|
910 m.put("ID", CBuiltinLeafInfo.ID); |
|
911 m.put("IDREF", CBuiltinLeafInfo.IDREF); |
|
912 // TODO: handling dateTime, time, and date type |
|
913 // String[] names = { |
|
914 // "date", "dateTime", "time", "hexBinary" }; |
|
915 } |
|
916 } |