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