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.xml.internal.bind.v2.model.impl; |
|
27 |
||
28 |
import java.lang.annotation.Annotation; |
|
29 |
import java.lang.reflect.Method; |
|
30 |
import java.util.ArrayList; |
|
31 |
import java.util.Collection; |
|
32 |
import java.util.Collections; |
|
33 |
import java.util.Comparator; |
|
34 |
import java.util.HashMap; |
|
35 |
import java.util.HashSet; |
|
36 |
import java.util.LinkedHashMap; |
|
37 |
import java.util.List; |
|
38 |
import java.util.Map; |
|
39 |
import java.util.Set; |
|
40 |
import java.util.TreeSet; |
|
41 |
import java.util.AbstractList; |
|
42 |
||
43 |
import javax.xml.bind.annotation.XmlAccessOrder; |
|
44 |
import javax.xml.bind.annotation.XmlAccessType; |
|
45 |
import javax.xml.bind.annotation.XmlAccessorOrder; |
|
46 |
import javax.xml.bind.annotation.XmlAccessorType; |
|
47 |
import javax.xml.bind.annotation.XmlAnyAttribute; |
|
48 |
import javax.xml.bind.annotation.XmlAnyElement; |
|
49 |
import javax.xml.bind.annotation.XmlAttachmentRef; |
|
50 |
import javax.xml.bind.annotation.XmlAttribute; |
|
51 |
import javax.xml.bind.annotation.XmlElement; |
|
52 |
import javax.xml.bind.annotation.XmlElementRef; |
|
53 |
import javax.xml.bind.annotation.XmlElementRefs; |
|
54 |
import javax.xml.bind.annotation.XmlElementWrapper; |
|
55 |
import javax.xml.bind.annotation.XmlElements; |
|
56 |
import javax.xml.bind.annotation.XmlID; |
|
57 |
import javax.xml.bind.annotation.XmlIDREF; |
|
58 |
import javax.xml.bind.annotation.XmlInlineBinaryData; |
|
59 |
import javax.xml.bind.annotation.XmlList; |
|
60 |
import javax.xml.bind.annotation.XmlMimeType; |
|
61 |
import javax.xml.bind.annotation.XmlMixed; |
|
62 |
import javax.xml.bind.annotation.XmlRootElement; |
|
63 |
import javax.xml.bind.annotation.XmlSchemaType; |
|
64 |
import javax.xml.bind.annotation.XmlTransient; |
|
65 |
import javax.xml.bind.annotation.XmlType; |
|
66 |
import javax.xml.bind.annotation.XmlValue; |
|
67 |
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; |
|
68 |
import javax.xml.namespace.QName; |
|
69 |
||
70 |
import com.sun.istack.internal.FinalArrayList; |
|
71 |
import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf; |
|
72 |
import com.sun.xml.internal.bind.v2.model.annotation.Locatable; |
|
73 |
import com.sun.xml.internal.bind.v2.model.annotation.MethodLocatable; |
|
74 |
import com.sun.xml.internal.bind.v2.model.core.ClassInfo; |
|
75 |
import com.sun.xml.internal.bind.v2.model.core.Element; |
|
76 |
import com.sun.xml.internal.bind.v2.model.core.ID; |
|
77 |
import com.sun.xml.internal.bind.v2.model.core.NonElement; |
|
78 |
import com.sun.xml.internal.bind.v2.model.core.PropertyInfo; |
|
79 |
import com.sun.xml.internal.bind.v2.model.core.PropertyKind; |
|
80 |
import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo; |
|
81 |
import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException; |
|
82 |
import com.sun.xml.internal.bind.v2.runtime.Location; |
|
83 |
import com.sun.xml.internal.bind.v2.util.EditDistance; |
|
84 |
||
85 |
||
86 |
/** |
|
87 |
* A part of the {@link ClassInfo} that doesn't depend on a particular |
|
88 |
* reflection library. |
|
89 |
* |
|
90 |
* @author Kohsuke Kawaguchi (kk@kohsuke.org) |
|
91 |
*/ |
|
92 |
public class ClassInfoImpl<T,C,F,M> extends TypeInfoImpl<T,C,F,M> |
|
93 |
implements ClassInfo<T,C>, Element<T,C> { |
|
94 |
||
95 |
protected final C clazz; |
|
96 |
||
97 |
/** |
|
98 |
* @see #getElementName() |
|
99 |
*/ |
|
100 |
private final QName elementName; |
|
101 |
||
102 |
/** |
|
103 |
* @see #getTypeName() |
|
104 |
*/ |
|
105 |
private final QName typeName; |
|
106 |
||
107 |
/** |
|
108 |
* Lazily created. |
|
109 |
* |
|
110 |
* @see #getProperties() |
|
111 |
*/ |
|
112 |
private FinalArrayList<PropertyInfoImpl<T,C,F,M>> properties; |
|
113 |
||
114 |
/** |
|
115 |
* The property order. |
|
116 |
* |
|
117 |
* null if unordered. {@link #DEFAULT_ORDER} if ordered but the order is defaulted |
|
118 |
* |
|
119 |
* @see #isOrdered() |
|
120 |
*/ |
|
121 |
private /*final*/ String[] propOrder; |
|
122 |
||
123 |
/** |
|
124 |
* Lazily computed. |
|
125 |
* |
|
126 |
* To avoid the cyclic references of the form C1 --base--> C2 --property--> C1. |
|
127 |
*/ |
|
128 |
private ClassInfoImpl<T,C,F,M> baseClass; |
|
129 |
||
130 |
private boolean baseClassComputed = false; |
|
131 |
||
132 |
private boolean hasSubClasses = false; |
|
133 |
||
134 |
/** |
|
135 |
* If this class has a declared (not inherited) attribute wildcard, keep the reference |
|
136 |
* to it. |
|
137 |
* |
|
138 |
* This parameter is initialized at the construction time and never change. |
|
139 |
*/ |
|
140 |
protected /*final*/ PropertySeed<T,C,F,M> attributeWildcard; |
|
141 |
||
142 |
||
143 |
/** |
|
144 |
* @see #getFactoryMethod() |
|
145 |
*/ |
|
146 |
private M factoryMethod = null; |
|
147 |
||
148 |
ClassInfoImpl(ModelBuilder<T,C,F,M> builder, Locatable upstream, C clazz) { |
|
149 |
super(builder,upstream); |
|
150 |
this.clazz = clazz; |
|
151 |
assert clazz!=null; |
|
152 |
||
153 |
// compute the element name |
|
154 |
elementName = parseElementName(clazz); |
|
155 |
||
156 |
// compute the type name |
|
157 |
XmlType t = reader().getClassAnnotation(XmlType.class,clazz,this); |
|
158 |
typeName = parseTypeName(clazz,t); |
|
159 |
||
160 |
if(t!=null) { |
|
161 |
String[] propOrder = t.propOrder(); |
|
162 |
if(propOrder.length==0) |
|
163 |
this.propOrder = null; // unordered |
|
164 |
else { |
|
165 |
if(propOrder[0].length()==0) |
|
166 |
this.propOrder = DEFAULT_ORDER; |
|
167 |
else |
|
168 |
this.propOrder = propOrder; |
|
169 |
} |
|
170 |
} else { |
|
171 |
propOrder = DEFAULT_ORDER; |
|
172 |
} |
|
173 |
||
174 |
// obtain XmlAccessorOrder and set proporder (XmlAccessorOrder can be defined for whole package) |
|
175 |
// (<xs:all> vs <xs:sequence>) |
|
176 |
XmlAccessorOrder xao = reader().getPackageAnnotation(XmlAccessorOrder.class, clazz, this); |
|
177 |
if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) { |
|
178 |
propOrder = null; |
|
179 |
} |
|
180 |
||
181 |
// obtain XmlAccessorOrder and set proporder (<xs:all> vs <xs:sequence>) |
|
182 |
xao = reader().getClassAnnotation(XmlAccessorOrder.class, clazz, this); |
|
183 |
if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) { |
|
184 |
propOrder = null; |
|
185 |
} |
|
186 |
||
187 |
if(nav().isInterface(clazz)) { |
|
188 |
builder.reportError(new IllegalAnnotationException( |
|
189 |
Messages.CANT_HANDLE_INTERFACE.format(nav().getClassName(clazz)), this )); |
|
190 |
} |
|
191 |
||
192 |
// the class must have the default constructor |
|
193 |
if (!hasFactoryConstructor(t)){ |
|
194 |
if(!nav().hasDefaultConstructor(clazz)){ |
|
195 |
if(nav().isInnerClass(clazz)) { |
|
196 |
builder.reportError(new IllegalAnnotationException( |
|
197 |
Messages.CANT_HANDLE_INNER_CLASS.format(nav().getClassName(clazz)), this )); |
|
198 |
} else if (elementName != null) { |
|
199 |
builder.reportError(new IllegalAnnotationException( |
|
200 |
Messages.NO_DEFAULT_CONSTRUCTOR.format(nav().getClassName(clazz)), this )); |
|
201 |
} |
|
202 |
} |
|
203 |
} |
|
204 |
} |
|
205 |
||
206 |
public ClassInfoImpl<T,C,F,M> getBaseClass() { |
|
207 |
if (!baseClassComputed) { |
|
208 |
// compute the base class |
|
209 |
C s = nav().getSuperClass(clazz); |
|
210 |
if(s==null || s==nav().asDecl(Object.class)) { |
|
211 |
baseClass = null; |
|
212 |
} else { |
|
213 |
NonElement<T,C> b = builder.getClassInfo(s, true, this); |
|
214 |
if(b instanceof ClassInfoImpl) { |
|
215 |
baseClass = (ClassInfoImpl<T,C,F,M>) b; |
|
216 |
baseClass.hasSubClasses = true; |
|
217 |
} else { |
|
218 |
baseClass = null; |
|
219 |
} |
|
220 |
} |
|
221 |
baseClassComputed = true; |
|
222 |
} |
|
223 |
return baseClass; |
|
224 |
} |
|
225 |
||
226 |
/** |
|
227 |
* {@inheritDoc} |
|
228 |
* |
|
229 |
* The substitution hierarchy is the same as the inheritance hierarchy. |
|
230 |
*/ |
|
231 |
public final Element<T,C> getSubstitutionHead() { |
|
232 |
ClassInfoImpl<T,C,F,M> c = getBaseClass(); |
|
233 |
while(c!=null && !c.isElement()) |
|
234 |
c = c.getBaseClass(); |
|
235 |
return c; |
|
236 |
} |
|
237 |
||
238 |
public final C getClazz() { |
|
239 |
return clazz; |
|
240 |
} |
|
241 |
||
242 |
/** |
|
243 |
* When a bean binds to an element, it's always through {@link XmlRootElement}, |
|
244 |
* so this method always return null. |
|
245 |
* |
|
246 |
* @deprecated |
|
247 |
* you shouldn't be invoking this method on {@link ClassInfoImpl}. |
|
248 |
*/ |
|
249 |
public ClassInfoImpl<T,C,F,M> getScope() { |
|
250 |
return null; |
|
251 |
} |
|
252 |
||
253 |
public final T getType() { |
|
254 |
return nav().use(clazz); |
|
255 |
} |
|
256 |
||
257 |
/** |
|
258 |
* A {@link ClassInfo} can be referenced by {@link XmlIDREF} if |
|
259 |
* it has an ID property. |
|
260 |
*/ |
|
261 |
public boolean canBeReferencedByIDREF() { |
|
262 |
for (PropertyInfo<T,C> p : getProperties()) { |
|
263 |
if(p.id()== ID.ID) |
|
264 |
return true; |
|
265 |
} |
|
266 |
ClassInfoImpl<T,C,F,M> base = getBaseClass(); |
|
267 |
if(base!=null) |
|
268 |
return base.canBeReferencedByIDREF(); |
|
269 |
else |
|
270 |
return false; |
|
271 |
} |
|
272 |
||
273 |
public final String getName() { |
|
274 |
return nav().getClassName(clazz); |
|
275 |
} |
|
276 |
||
277 |
public <A extends Annotation> A readAnnotation(Class<A> a) { |
|
278 |
return reader().getClassAnnotation(a,clazz,this); |
|
279 |
} |
|
280 |
||
281 |
public Element<T,C> asElement() { |
|
282 |
if(isElement()) |
|
283 |
return this; |
|
284 |
else |
|
285 |
return null; |
|
286 |
} |
|
287 |
||
288 |
public List<? extends PropertyInfo<T,C>> getProperties() { |
|
289 |
if(properties!=null) return properties; |
|
290 |
||
291 |
// check the access type first |
|
292 |
XmlAccessType at = getAccessType(); |
|
293 |
||
294 |
properties = new FinalArrayList<PropertyInfoImpl<T,C,F,M>>(); |
|
295 |
||
296 |
findFieldProperties(clazz,at); |
|
297 |
||
298 |
findGetterSetterProperties(at); |
|
299 |
||
300 |
if(propOrder==DEFAULT_ORDER || propOrder==null) { |
|
301 |
XmlAccessOrder ao = getAccessorOrder(); |
|
302 |
if(ao==XmlAccessOrder.ALPHABETICAL) |
|
303 |
Collections.sort(properties); |
|
304 |
} else { |
|
305 |
//sort them as specified |
|
306 |
PropertySorter sorter = new PropertySorter(); |
|
307 |
for (PropertyInfoImpl p : properties) { |
|
308 |
sorter.checkedGet(p); // have it check for errors |
|
309 |
} |
|
310 |
Collections.sort(properties,sorter); |
|
311 |
sorter.checkUnusedProperties(); |
|
312 |
} |
|
313 |
||
314 |
{// additional error checks |
|
315 |
PropertyInfoImpl vp=null; // existing value property |
|
316 |
PropertyInfoImpl ep=null; // existing element property |
|
317 |
||
318 |
for (PropertyInfoImpl p : properties) { |
|
319 |
switch(p.kind()) { |
|
320 |
case ELEMENT: |
|
321 |
case REFERENCE: |
|
322 |
case MAP: |
|
323 |
ep = p; |
|
324 |
break; |
|
325 |
case VALUE: |
|
326 |
if(vp!=null) { |
|
327 |
// can't have multiple value properties. |
|
328 |
builder.reportError(new IllegalAnnotationException( |
|
329 |
Messages.MULTIPLE_VALUE_PROPERTY.format(), |
|
330 |
vp, p )); |
|
331 |
} |
|
332 |
if(getBaseClass()!=null) { |
|
333 |
builder.reportError(new IllegalAnnotationException( |
|
334 |
Messages.XMLVALUE_IN_DERIVED_TYPE.format(), p )); |
|
335 |
} |
|
336 |
vp = p; |
|
337 |
break; |
|
338 |
case ATTRIBUTE: |
|
339 |
break; // noop |
|
340 |
default: |
|
341 |
assert false; |
|
342 |
} |
|
343 |
} |
|
344 |
||
345 |
if(ep!=null && vp!=null) { |
|
346 |
// can't have element and value property at the same time |
|
347 |
builder.reportError(new IllegalAnnotationException( |
|
348 |
Messages.ELEMENT_AND_VALUE_PROPERTY.format(), |
|
349 |
vp, ep |
|
350 |
)); |
|
351 |
} |
|
352 |
} |
|
353 |
||
354 |
return properties; |
|
355 |
} |
|
356 |
||
357 |
private void findFieldProperties(C c, XmlAccessType at) { |
|
358 |
||
359 |
// always find properties from the super class first |
|
360 |
C sc = nav().getSuperClass(c); |
|
361 |
if (shouldRecurseSuperClass(sc)) { |
|
362 |
findFieldProperties(sc,at); |
|
363 |
} |
|
364 |
||
365 |
for( F f : nav().getDeclaredFields(c) ) { |
|
366 |
Annotation[] annotations = reader().getAllFieldAnnotations(f,this); |
|
367 |
boolean isDummy = reader().hasFieldAnnotation(OverrideAnnotationOf.class, f); |
|
368 |
||
369 |
if( nav().isTransient(f) ) { |
|
370 |
// it's an error for transient field to have any binding annotation |
|
371 |
if(hasJAXBAnnotation(annotations)) |
|
372 |
builder.reportError(new IllegalAnnotationException( |
|
373 |
Messages.TRANSIENT_FIELD_NOT_BINDABLE.format(nav().getFieldName(f)), |
|
374 |
getSomeJAXBAnnotation(annotations))); |
|
375 |
} else |
|
376 |
if( nav().isStaticField(f) ) { |
|
377 |
// static fields are bound only when there's explicit annotation. |
|
378 |
if(hasJAXBAnnotation(annotations)) |
|
379 |
addProperty(createFieldSeed(f),annotations, false); |
|
380 |
} else { |
|
381 |
if(at==XmlAccessType.FIELD |
|
382 |
||(at==XmlAccessType.PUBLIC_MEMBER && nav().isPublicField(f)) |
|
383 |
|| hasJAXBAnnotation(annotations)) { |
|
384 |
if (isDummy) { |
|
385 |
ClassInfo<T, C> top = getBaseClass(); |
|
386 |
while ((top != null) && (top.getProperty("content") == null)) { |
|
387 |
top = top.getBaseClass(); |
|
388 |
} |
|
389 |
DummyPropertyInfo prop = (DummyPropertyInfo) top.getProperty("content"); |
|
390 |
PropertySeed seed = createFieldSeed(f); |
|
391 |
((DummyPropertyInfo)prop).addType(createReferenceProperty(seed)); |
|
392 |
} else { |
|
393 |
addProperty(createFieldSeed(f), annotations, false); |
|
394 |
} |
|
395 |
} |
|
396 |
checkFieldXmlLocation(f); |
|
397 |
} |
|
398 |
} |
|
399 |
} |
|
400 |
||
401 |
public final boolean hasValueProperty() { |
|
402 |
ClassInfoImpl<T, C, F, M> bc = getBaseClass(); |
|
403 |
if(bc!=null && bc.hasValueProperty()) |
|
404 |
return true; |
|
405 |
||
406 |
for (PropertyInfo p : getProperties()) { |
|
407 |
if (p instanceof ValuePropertyInfo) return true; |
|
408 |
} |
|
409 |
||
410 |
return false; |
|
411 |
} |
|
412 |
||
413 |
public PropertyInfo<T,C> getProperty(String name) { |
|
414 |
for( PropertyInfo<T,C> p: getProperties() ) { |
|
415 |
if(p.getName().equals(name)) |
|
416 |
return p; |
|
417 |
} |
|
418 |
return null; |
|
419 |
} |
|
420 |
||
421 |
/** |
|
422 |
* This hook is used by {@link RuntimeClassInfoImpl} to look for {@link XmlLocation}. |
|
423 |
*/ |
|
424 |
protected void checkFieldXmlLocation(F f) { |
|
425 |
} |
|
426 |
||
427 |
/** |
|
428 |
* Gets an annotation that are allowed on both class and type. |
|
429 |
*/ |
|
430 |
private <T extends Annotation> T getClassOrPackageAnnotation(Class<T> type) { |
|
431 |
T t = reader().getClassAnnotation(type,clazz,this); |
|
432 |
if(t!=null) |
|
433 |
return t; |
|
434 |
// defaults to the package level |
|
435 |
return reader().getPackageAnnotation(type,clazz,this); |
|
436 |
} |
|
437 |
||
438 |
/** |
|
439 |
* Computes the {@link XmlAccessType} on this class by looking at {@link XmlAccessorType} |
|
440 |
* annotations. |
|
441 |
*/ |
|
442 |
private XmlAccessType getAccessType() { |
|
443 |
XmlAccessorType xat = getClassOrPackageAnnotation(XmlAccessorType.class); |
|
444 |
if(xat!=null) |
|
445 |
return xat.value(); |
|
446 |
else |
|
447 |
return XmlAccessType.PUBLIC_MEMBER; |
|
448 |
} |
|
449 |
||
450 |
/** |
|
451 |
* Gets the accessor order for this class by consulting {@link XmlAccessorOrder}. |
|
452 |
*/ |
|
453 |
private XmlAccessOrder getAccessorOrder() { |
|
454 |
XmlAccessorOrder xao = getClassOrPackageAnnotation(XmlAccessorOrder.class); |
|
455 |
if(xao!=null) |
|
456 |
return xao.value(); |
|
457 |
else |
|
458 |
return XmlAccessOrder.UNDEFINED; |
|
459 |
} |
|
460 |
||
461 |
/** |
|
462 |
* Compares orders among {@link PropertyInfoImpl} according to {@link ClassInfoImpl#propOrder}. |
|
463 |
* |
|
464 |
* <p> |
|
465 |
* extends {@link HashMap} to save memory. |
|
466 |
*/ |
|
467 |
private final class PropertySorter extends HashMap<String,Integer> implements Comparator<PropertyInfoImpl> { |
|
468 |
/** |
|
469 |
* Mark property names that are used, so that we can report unused property names in the propOrder array. |
|
470 |
*/ |
|
471 |
PropertyInfoImpl[] used = new PropertyInfoImpl[propOrder.length]; |
|
472 |
||
473 |
/** |
|
474 |
* If any name collides, it will be added to this set. |
|
475 |
* This is used to avoid repeating the same error message. |
|
476 |
*/ |
|
477 |
private Set<String> collidedNames; |
|
478 |
||
479 |
PropertySorter() { |
|
480 |
super(propOrder.length); |
|
481 |
for( String name : propOrder ) |
|
482 |
if(put(name,size())!=null) { |
|
483 |
// two properties with the same name |
|
484 |
builder.reportError(new IllegalAnnotationException( |
|
485 |
Messages.DUPLICATE_ENTRY_IN_PROP_ORDER.format(name),ClassInfoImpl.this)); |
|
486 |
} |
|
487 |
} |
|
488 |
||
489 |
public int compare(PropertyInfoImpl o1, PropertyInfoImpl o2) { |
|
490 |
int lhs = checkedGet(o1); |
|
491 |
int rhs = checkedGet(o2); |
|
492 |
||
493 |
return lhs-rhs; |
|
494 |
} |
|
495 |
||
496 |
private int checkedGet(PropertyInfoImpl p) { |
|
497 |
Integer i = get(p.getName()); |
|
498 |
if(i==null) { |
|
499 |
// missing |
|
500 |
if (p.kind().isOrdered) |
|
501 |
builder.reportError(new IllegalAnnotationException( |
|
502 |
Messages.PROPERTY_MISSING_FROM_ORDER.format(p.getName()),p)); |
|
503 |
||
504 |
// give it an order to recover from an error |
|
505 |
i = size(); |
|
506 |
put(p.getName(),i); |
|
507 |
} |
|
508 |
||
509 |
// mark the used field |
|
510 |
int ii = i; |
|
511 |
if(ii<used.length) { |
|
512 |
if(used[ii]!=null && used[ii]!=p) { |
|
513 |
if(collidedNames==null) collidedNames = new HashSet<String>(); |
|
514 |
||
515 |
if(collidedNames.add(p.getName())) |
|
516 |
// report the error only on the first time |
|
517 |
builder.reportError(new IllegalAnnotationException( |
|
518 |
Messages.DUPLICATE_PROPERTIES.format(p.getName()),p,used[ii])); |
|
519 |
} |
|
520 |
used[ii] = p; |
|
521 |
} |
|
522 |
||
523 |
return i; |
|
524 |
} |
|
525 |
||
526 |
/** |
|
527 |
* Report errors for unused propOrder entries. |
|
528 |
*/ |
|
529 |
public void checkUnusedProperties() { |
|
530 |
for( int i=0; i<used.length; i++ ) |
|
531 |
if(used[i]==null) { |
|
532 |
String unusedName = propOrder[i]; |
|
533 |
String nearest = EditDistance.findNearest(unusedName, new AbstractList<String>() { |
|
534 |
public String get(int index) { |
|
535 |
return properties.get(index).getName(); |
|
536 |
} |
|
537 |
||
538 |
public int size() { |
|
539 |
return properties.size(); |
|
540 |
} |
|
541 |
}); |
|
542 |
boolean isOverriding = (i > (properties.size()-1)) ? false : properties.get(i).hasAnnotation(OverrideAnnotationOf.class); |
|
543 |
if (!isOverriding) { |
|
544 |
builder.reportError(new IllegalAnnotationException( |
|
545 |
Messages.PROPERTY_ORDER_CONTAINS_UNUSED_ENTRY.format(unusedName,nearest),ClassInfoImpl.this)); |
|
546 |
} |
|
547 |
} |
|
548 |
} |
|
549 |
} |
|
550 |
||
551 |
public boolean hasProperties() { |
|
552 |
return !properties.isEmpty(); |
|
553 |
} |
|
554 |
||
555 |
||
556 |
/** |
|
557 |
* Picks the first non-null argument, or null if all arguments are null. |
|
558 |
*/ |
|
559 |
private static <T> T pickOne( T... args ) { |
|
560 |
for( T arg : args ) |
|
561 |
if(arg!=null) |
|
562 |
return arg; |
|
563 |
return null; |
|
564 |
} |
|
565 |
||
566 |
private static <T> List<T> makeSet( T... args ) { |
|
567 |
List<T> l = new FinalArrayList<T>(); |
|
568 |
for( T arg : args ) |
|
569 |
if(arg!=null) l.add(arg); |
|
570 |
return l; |
|
571 |
} |
|
572 |
||
573 |
private static final class ConflictException extends Exception { |
|
574 |
final List<Annotation> annotations; |
|
575 |
||
576 |
public ConflictException(List<Annotation> one) { |
|
577 |
this.annotations = one; |
|
578 |
} |
|
579 |
} |
|
580 |
||
581 |
private static final class DuplicateException extends Exception { |
|
582 |
final Annotation a1,a2; |
|
583 |
public DuplicateException(Annotation a1, Annotation a2) { |
|
584 |
this.a1 = a1; |
|
585 |
this.a2 = a2; |
|
586 |
} |
|
587 |
} |
|
588 |
||
589 |
/** |
|
590 |
* Represents 6 groups of secondary annotations |
|
591 |
*/ |
|
592 |
private static enum SecondaryAnnotation { |
|
593 |
JAVA_TYPE (0x01, XmlJavaTypeAdapter.class), |
|
594 |
ID_IDREF (0x02, XmlID.class, XmlIDREF.class), |
|
595 |
BINARY (0x04, XmlInlineBinaryData.class, XmlMimeType.class, XmlAttachmentRef.class), |
|
596 |
ELEMENT_WRAPPER (0x08, XmlElementWrapper.class), |
|
597 |
LIST (0x10, XmlList.class), |
|
598 |
SCHEMA_TYPE (0x20, XmlSchemaType.class); |
|
599 |
||
600 |
/** |
|
601 |
* Each constant gets an unique bit mask so that the presence/absence |
|
602 |
* of them can be represented in a single byte. |
|
603 |
*/ |
|
604 |
final int bitMask; |
|
605 |
/** |
|
606 |
* List of annotations that belong to this member. |
|
607 |
*/ |
|
608 |
final Class<? extends Annotation>[] members; |
|
609 |
||
610 |
SecondaryAnnotation(int bitMask, Class<? extends Annotation>... members) { |
|
611 |
this.bitMask = bitMask; |
|
612 |
this.members = members; |
|
613 |
} |
|
614 |
} |
|
615 |
||
616 |
private static final SecondaryAnnotation[] SECONDARY_ANNOTATIONS = SecondaryAnnotation.values(); |
|
617 |
||
618 |
/** |
|
619 |
* Represents 7 groups of properties. |
|
620 |
* |
|
621 |
* Each instance is also responsible for rejecting annotations |
|
622 |
* that are not allowed on that kind. |
|
623 |
*/ |
|
624 |
private static enum PropertyGroup { |
|
625 |
TRANSIENT (false,false,false,false,false,false), |
|
626 |
ANY_ATTRIBUTE (true, false,false,false,false,false), |
|
627 |
ATTRIBUTE (true, true, true, false,true, true ), |
|
628 |
VALUE (true, true, true, false,true, true ), |
|
629 |
ELEMENT (true, true, true, true, true, true ), |
|
630 |
ELEMENT_REF (true, false,false,true, false,false), |
|
631 |
MAP (false,false,false,true, false,false); |
|
632 |
||
633 |
/** |
|
634 |
* Bit mask that represents secondary annotations that are allowed on this group. |
|
635 |
* |
|
636 |
* T = not allowed, F = allowed |
|
637 |
*/ |
|
638 |
final int allowedsecondaryAnnotations; |
|
639 |
||
640 |
PropertyGroup(boolean... bits) { |
|
641 |
int mask = 0; |
|
642 |
assert bits.length==SECONDARY_ANNOTATIONS.length; |
|
643 |
for( int i=0; i<bits.length; i++ ) { |
|
644 |
if(bits[i]) |
|
645 |
mask |= SECONDARY_ANNOTATIONS[i].bitMask; |
|
646 |
} |
|
647 |
allowedsecondaryAnnotations = ~mask; |
|
648 |
} |
|
649 |
||
650 |
boolean allows(SecondaryAnnotation a) { |
|
651 |
return (allowedsecondaryAnnotations&a.bitMask)==0; |
|
652 |
} |
|
653 |
} |
|
654 |
||
655 |
private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0]; |
|
656 |
||
657 |
/** |
|
658 |
* All the annotations in JAXB to their internal index. |
|
659 |
*/ |
|
660 |
private static final HashMap<Class,Integer> ANNOTATION_NUMBER_MAP = new HashMap<Class,Integer>(); |
|
661 |
static { |
|
662 |
Class[] annotations = { |
|
663 |
XmlTransient.class, // 0 |
|
664 |
XmlAnyAttribute.class, // 1 |
|
665 |
XmlAttribute.class, // 2 |
|
666 |
XmlValue.class, // 3 |
|
667 |
XmlElement.class, // 4 |
|
668 |
XmlElements.class, // 5 |
|
669 |
XmlElementRef.class, // 6 |
|
670 |
XmlElementRefs.class, // 7 |
|
671 |
XmlAnyElement.class, // 8 |
|
672 |
XmlMixed.class, // 9 |
|
673 |
OverrideAnnotationOf.class,// 10 |
|
674 |
}; |
|
675 |
||
676 |
HashMap<Class,Integer> m = ANNOTATION_NUMBER_MAP; |
|
677 |
||
678 |
// characterizing annotations |
|
679 |
for( Class c : annotations ) |
|
680 |
m.put(c, m.size() ); |
|
681 |
||
682 |
// secondary annotations |
|
683 |
int index = 20; |
|
684 |
for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) { |
|
685 |
for( Class member : sa.members ) |
|
686 |
m.put(member,index); |
|
687 |
index++; |
|
688 |
} |
|
689 |
} |
|
690 |
||
691 |
private void checkConflict(Annotation a, Annotation b) throws DuplicateException { |
|
692 |
assert b!=null; |
|
693 |
if(a!=null) |
|
694 |
throw new DuplicateException(a,b); |
|
695 |
} |
|
696 |
||
697 |
/** |
|
698 |
* Called only from {@link #getProperties()}. |
|
699 |
* |
|
700 |
* <p> |
|
701 |
* This is where we decide the type of the property and checks for annotations |
|
702 |
* that are not allowed. |
|
703 |
* |
|
704 |
* @param annotations |
|
705 |
* all annotations on this property. It's the same as |
|
706 |
* {@code seed.readAllAnnotation()}, but taken as a parameter |
|
707 |
* because the caller should know it already. |
|
708 |
*/ |
|
709 |
private void addProperty( PropertySeed<T,C,F,M> seed, Annotation[] annotations, boolean dummy ) { |
|
710 |
// since typically there's a very few annotations on a method, |
|
711 |
// this runs faster than checking for each annotation via readAnnotation(A) |
|
712 |
||
713 |
||
714 |
// characterizing annotations. these annotations (or lack thereof) decides |
|
715 |
// the kind of the property it goes to. |
|
716 |
// I wish I could use an array... |
|
717 |
XmlTransient t = null; |
|
718 |
XmlAnyAttribute aa = null; |
|
719 |
XmlAttribute a = null; |
|
720 |
XmlValue v = null; |
|
721 |
XmlElement e1 = null; |
|
722 |
XmlElements e2 = null; |
|
723 |
XmlElementRef r1 = null; |
|
724 |
XmlElementRefs r2 = null; |
|
725 |
XmlAnyElement xae = null; |
|
726 |
XmlMixed mx = null; |
|
727 |
OverrideAnnotationOf ov = null; |
|
728 |
||
729 |
// encountered secondary annotations are accumulated into a bit mask |
|
730 |
int secondaryAnnotations = 0; |
|
731 |
||
732 |
try { |
|
733 |
for( Annotation ann : annotations ) { |
|
734 |
Integer index = ANNOTATION_NUMBER_MAP.get(ann.annotationType()); |
|
735 |
if(index==null) continue; |
|
736 |
switch(index) { |
|
737 |
case 0: checkConflict(t ,ann); t = (XmlTransient) ann; break; |
|
738 |
case 1: checkConflict(aa ,ann); aa = (XmlAnyAttribute) ann; break; |
|
739 |
case 2: checkConflict(a ,ann); a = (XmlAttribute) ann; break; |
|
740 |
case 3: checkConflict(v ,ann); v = (XmlValue) ann; break; |
|
741 |
case 4: checkConflict(e1 ,ann); e1 = (XmlElement) ann; break; |
|
742 |
case 5: checkConflict(e2 ,ann); e2 = (XmlElements) ann; break; |
|
743 |
case 6: checkConflict(r1 ,ann); r1 = (XmlElementRef) ann; break; |
|
744 |
case 7: checkConflict(r2 ,ann); r2 = (XmlElementRefs) ann; break; |
|
745 |
case 8: checkConflict(xae,ann); xae = (XmlAnyElement) ann; break; |
|
746 |
case 9: checkConflict(mx, ann); mx = (XmlMixed) ann; break; |
|
747 |
case 10: checkConflict(ov, ann); ov = (OverrideAnnotationOf) ann; break; |
|
748 |
default: |
|
749 |
// secondary annotations |
|
750 |
secondaryAnnotations |= (1<<(index-20)); |
|
751 |
break; |
|
752 |
} |
|
753 |
} |
|
754 |
||
755 |
// determine the group kind, and also count the numbers, since |
|
756 |
// characterizing annotations are mutually exclusive. |
|
757 |
PropertyGroup group = null; |
|
758 |
int groupCount = 0; |
|
759 |
||
760 |
if(t!=null) { |
|
761 |
group = PropertyGroup.TRANSIENT; |
|
762 |
groupCount++; |
|
763 |
} |
|
764 |
if(aa!=null) { |
|
765 |
group = PropertyGroup.ANY_ATTRIBUTE; |
|
766 |
groupCount++; |
|
767 |
} |
|
768 |
if(a!=null) { |
|
769 |
group = PropertyGroup.ATTRIBUTE; |
|
770 |
groupCount++; |
|
771 |
} |
|
772 |
if(v!=null) { |
|
773 |
group = PropertyGroup.VALUE; |
|
774 |
groupCount++; |
|
775 |
} |
|
776 |
if(e1!=null || e2!=null) { |
|
777 |
group = PropertyGroup.ELEMENT; |
|
778 |
groupCount++; |
|
779 |
} |
|
780 |
if(r1!=null || r2!=null || xae!=null || mx!=null || ov != null) { |
|
781 |
group = PropertyGroup.ELEMENT_REF; |
|
782 |
groupCount++; |
|
783 |
} |
|
784 |
||
785 |
if(groupCount>1) { |
|
786 |
// collision between groups |
|
787 |
List<Annotation> err = makeSet(t,aa,a,v,pickOne(e1,e2),pickOne(r1,r2,xae)); |
|
788 |
throw new ConflictException(err); |
|
789 |
} |
|
790 |
||
791 |
if(group==null) { |
|
792 |
// if no characterizing annotation was found, it's either element or map |
|
793 |
// sniff the signature and then decide. |
|
794 |
assert groupCount==0; |
|
795 |
||
796 |
// UGLY: the presence of XmlJavaTypeAdapter makes it an element property. ARGH. |
|
797 |
if(nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class) ) |
|
798 |
&& !seed.hasAnnotation(XmlJavaTypeAdapter.class)) |
|
799 |
group = PropertyGroup.MAP; |
|
800 |
else |
|
801 |
group = PropertyGroup.ELEMENT; |
|
802 |
} else if (group.equals(PropertyGroup.ELEMENT)) { // see issue 791 - make sure @XmlElement annotated map property is mapped to map |
|
803 |
if (nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class)) && !seed.hasAnnotation(XmlJavaTypeAdapter.class)) { |
|
804 |
group = PropertyGroup.MAP; |
|
805 |
} |
|
806 |
} |
|
807 |
||
808 |
// group determined by now |
|
809 |
// make sure that there are no prohibited secondary annotations |
|
810 |
if( (secondaryAnnotations&group.allowedsecondaryAnnotations)!=0 ) { |
|
811 |
// uh oh. find the offending annotation |
|
812 |
for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) { |
|
813 |
if(group.allows(sa)) |
|
814 |
continue; |
|
815 |
for( Class<? extends Annotation> m : sa.members ) { |
|
816 |
Annotation offender = seed.readAnnotation(m); |
|
817 |
if(offender!=null) { |
|
818 |
// found it |
|
819 |
builder.reportError(new IllegalAnnotationException( |
|
820 |
Messages.ANNOTATION_NOT_ALLOWED.format(m.getSimpleName()),offender)); |
|
821 |
return; |
|
822 |
} |
|
823 |
} |
|
824 |
} |
|
825 |
// there must have been an offender |
|
826 |
assert false; |
|
827 |
} |
|
828 |
||
829 |
// actually create annotations |
|
830 |
switch(group) { |
|
831 |
case TRANSIENT: |
|
832 |
return; |
|
833 |
case ANY_ATTRIBUTE: |
|
834 |
// an attribute wildcard property |
|
835 |
if(attributeWildcard!=null) { |
|
836 |
builder.reportError(new IllegalAnnotationException( |
|
837 |
Messages.TWO_ATTRIBUTE_WILDCARDS.format( |
|
838 |
nav().getClassName(getClazz())),aa,attributeWildcard)); |
|
839 |
return; // recover by ignore |
|
840 |
} |
|
841 |
attributeWildcard = seed; |
|
842 |
||
843 |
if(inheritsAttributeWildcard()) { |
|
844 |
builder.reportError(new IllegalAnnotationException( |
|
845 |
Messages.SUPER_CLASS_HAS_WILDCARD.format(), |
|
846 |
aa,getInheritedAttributeWildcard())); |
|
847 |
return; |
|
848 |
} |
|
849 |
||
850 |
// check the signature and make sure it's assignable to Map |
|
851 |
if(!nav().isSubClassOf(seed.getRawType(),nav().ref(Map.class))) { |
|
852 |
builder.reportError(new IllegalAnnotationException( |
|
853 |
Messages.INVALID_ATTRIBUTE_WILDCARD_TYPE.format(nav().getTypeName(seed.getRawType())), |
|
854 |
aa,getInheritedAttributeWildcard())); |
|
855 |
return; |
|
856 |
} |
|
857 |
||
858 |
||
859 |
return; |
|
860 |
case ATTRIBUTE: |
|
861 |
properties.add(createAttributeProperty(seed)); |
|
862 |
return; |
|
863 |
case VALUE: |
|
864 |
properties.add(createValueProperty(seed)); |
|
865 |
return; |
|
866 |
case ELEMENT: |
|
867 |
properties.add(createElementProperty(seed)); |
|
868 |
return; |
|
869 |
case ELEMENT_REF: |
|
870 |
properties.add(createReferenceProperty(seed)); |
|
871 |
return; |
|
872 |
case MAP: |
|
873 |
properties.add(createMapProperty(seed)); |
|
874 |
return; |
|
875 |
default: |
|
876 |
assert false; |
|
877 |
} |
|
878 |
} catch( ConflictException x ) { |
|
879 |
// report a conflicting annotation |
|
880 |
List<Annotation> err = x.annotations; |
|
881 |
||
882 |
builder.reportError(new IllegalAnnotationException( |
|
883 |
Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format( |
|
884 |
nav().getClassName(getClazz())+'#'+seed.getName(), |
|
885 |
err.get(0).annotationType().getName(), err.get(1).annotationType().getName()), |
|
886 |
err.get(0), err.get(1) )); |
|
887 |
||
888 |
// recover by ignoring this property |
|
889 |
} catch( DuplicateException e ) { |
|
890 |
// both are present |
|
891 |
builder.reportError(new IllegalAnnotationException( |
|
892 |
Messages.DUPLICATE_ANNOTATIONS.format(e.a1.annotationType().getName()), |
|
893 |
e.a1, e.a2 )); |
|
894 |
// recover by ignoring this property |
|
895 |
||
896 |
} |
|
897 |
} |
|
898 |
||
899 |
protected ReferencePropertyInfoImpl<T,C,F,M> createReferenceProperty(PropertySeed<T,C,F,M> seed) { |
|
900 |
return new ReferencePropertyInfoImpl<T,C,F,M>(this,seed); |
|
901 |
} |
|
902 |
||
903 |
protected AttributePropertyInfoImpl<T,C,F,M> createAttributeProperty(PropertySeed<T,C,F,M> seed) { |
|
904 |
return new AttributePropertyInfoImpl<T,C,F,M>(this,seed); |
|
905 |
} |
|
906 |
||
907 |
protected ValuePropertyInfoImpl<T,C,F,M> createValueProperty(PropertySeed<T,C,F,M> seed) { |
|
908 |
return new ValuePropertyInfoImpl<T,C,F,M>(this,seed); |
|
909 |
} |
|
910 |
||
911 |
protected ElementPropertyInfoImpl<T,C,F,M> createElementProperty(PropertySeed<T,C,F,M> seed) { |
|
912 |
return new ElementPropertyInfoImpl<T,C,F,M>(this,seed); |
|
913 |
} |
|
914 |
||
915 |
protected MapPropertyInfoImpl<T,C,F,M> createMapProperty(PropertySeed<T,C,F,M> seed) { |
|
916 |
return new MapPropertyInfoImpl<T,C,F,M>(this,seed); |
|
917 |
} |
|
918 |
||
919 |
||
920 |
/** |
|
921 |
* Adds properties that consists of accessors. |
|
922 |
*/ |
|
923 |
private void findGetterSetterProperties(XmlAccessType at) { |
|
924 |
// in the first step we accumulate getters and setters |
|
925 |
// into this map keyed by the property name. |
|
926 |
Map<String,M> getters = new LinkedHashMap<String,M>(); |
|
927 |
Map<String,M> setters = new LinkedHashMap<String,M>(); |
|
928 |
||
929 |
C c = clazz; |
|
930 |
do { |
|
931 |
collectGetterSetters(clazz, getters, setters); |
|
932 |
||
933 |
// take super classes into account if they have @XmlTransient |
|
934 |
c = nav().getSuperClass(c); |
|
935 |
} while(shouldRecurseSuperClass(c)); |
|
936 |
||
937 |
||
938 |
// compute the intersection |
|
939 |
Set<String> complete = new TreeSet<String>(getters.keySet()); |
|
940 |
complete.retainAll(setters.keySet()); |
|
941 |
||
942 |
resurrect(getters, complete); |
|
943 |
resurrect(setters, complete); |
|
944 |
||
945 |
// then look for read/write properties. |
|
946 |
for (String name : complete) { |
|
947 |
M getter = getters.get(name); |
|
948 |
M setter = setters.get(name); |
|
949 |
||
950 |
Annotation[] ga = getter!=null ? reader().getAllMethodAnnotations(getter,new MethodLocatable<M>(this,getter,nav())) : EMPTY_ANNOTATIONS; |
|
951 |
Annotation[] sa = setter!=null ? reader().getAllMethodAnnotations(setter,new MethodLocatable<M>(this,setter,nav())) : EMPTY_ANNOTATIONS; |
|
952 |
||
953 |
boolean hasAnnotation = hasJAXBAnnotation(ga) || hasJAXBAnnotation(sa); |
|
954 |
boolean isOverriding = false; |
|
955 |
if(!hasAnnotation) { |
|
956 |
// checking if the method is overriding others isn't free, |
|
957 |
// so we don't compute it if it's not necessary. |
|
958 |
isOverriding = (getter!=null && nav().isOverriding(getter,c)) |
|
959 |
&& (setter!=null && nav().isOverriding(setter,c)); |
|
960 |
} |
|
961 |
||
962 |
if((at==XmlAccessType.PROPERTY && !isOverriding) |
|
963 |
|| (at==XmlAccessType.PUBLIC_MEMBER && isConsideredPublic(getter) && isConsideredPublic(setter) && !isOverriding) |
|
964 |
|| hasAnnotation) { |
|
965 |
// make sure that the type is consistent |
|
966 |
if(getter!=null && setter!=null |
|
967 |
&& !nav().isSameType(nav().getReturnType(getter), nav().getMethodParameters(setter)[0])) { |
|
968 |
// inconsistent |
|
969 |
builder.reportError(new IllegalAnnotationException( |
|
970 |
Messages.GETTER_SETTER_INCOMPATIBLE_TYPE.format( |
|
971 |
nav().getTypeName(nav().getReturnType(getter)), |
|
972 |
nav().getTypeName(nav().getMethodParameters(setter)[0]) |
|
973 |
), |
|
974 |
new MethodLocatable<M>( this, getter, nav()), |
|
975 |
new MethodLocatable<M>( this, setter, nav()))); |
|
976 |
continue; |
|
977 |
} |
|
978 |
||
979 |
// merge annotations from two list |
|
980 |
Annotation[] r; |
|
981 |
if(ga.length==0) { |
|
982 |
r = sa; |
|
983 |
} else |
|
984 |
if(sa.length==0) { |
|
985 |
r = ga; |
|
986 |
} else { |
|
987 |
r = new Annotation[ga.length+sa.length]; |
|
988 |
System.arraycopy(ga,0,r,0,ga.length); |
|
989 |
System.arraycopy(sa,0,r,ga.length,sa.length); |
|
990 |
} |
|
991 |
||
992 |
addProperty(createAccessorSeed(getter, setter), r, false); |
|
993 |
} |
|
994 |
} |
|
995 |
// done with complete pairs |
|
996 |
getters.keySet().removeAll(complete); |
|
997 |
setters.keySet().removeAll(complete); |
|
998 |
||
999 |
// TODO: think about |
|
1000 |
// class Foo { |
|
1001 |
// int getFoo(); |
|
1002 |
// } |
|
1003 |
// class Bar extends Foo { |
|
1004 |
// void setFoo(int x); |
|
1005 |
// } |
|
1006 |
// and how it will be XML-ized. |
|
1007 |
} |
|
1008 |
||
1009 |
private void collectGetterSetters(C c, Map<String,M> getters, Map<String,M> setters) { |
|
1010 |
// take super classes into account if they have @XmlTransient. |
|
1011 |
// always visit them first so that |
|
1012 |
// 1) order is right |
|
1013 |
// 2) overriden properties are handled accordingly |
|
1014 |
C sc = nav().getSuperClass(c); |
|
1015 |
if(shouldRecurseSuperClass(sc)) |
|
1016 |
collectGetterSetters(sc,getters,setters); |
|
1017 |
||
1018 |
Collection<? extends M> methods = nav().getDeclaredMethods(c); |
|
1019 |
Map<String,List<M>> allSetters = new LinkedHashMap<String,List<M>>(); |
|
1020 |
for( M method : methods ) { |
|
1021 |
boolean used = false; // if this method is added to getters or setters |
|
1022 |
||
1023 |
if(nav().isBridgeMethod(method)) |
|
1024 |
continue; // ignore |
|
1025 |
||
1026 |
String name = nav().getMethodName(method); |
|
1027 |
int arity = nav().getMethodParameters(method).length; |
|
1028 |
||
1029 |
if(nav().isStaticMethod(method)) { |
|
1030 |
ensureNoAnnotation(method); |
|
1031 |
continue; |
|
1032 |
} |
|
1033 |
||
1034 |
// is this a get method? |
|
1035 |
String propName = getPropertyNameFromGetMethod(name); |
|
1036 |
if(propName!=null && arity==0) { |
|
1037 |
getters.put(propName,method); |
|
1038 |
used = true; |
|
1039 |
} |
|
1040 |
||
1041 |
// is this a set method? |
|
1042 |
propName = getPropertyNameFromSetMethod(name); |
|
1043 |
if(propName!=null && arity==1) { |
|
1044 |
List<M> propSetters = allSetters.get(propName); |
|
1045 |
if(null == propSetters){ |
|
1046 |
propSetters = new ArrayList<M>(); |
|
1047 |
allSetters.put(propName, propSetters); |
|
1048 |
} |
|
1049 |
propSetters.add(method); |
|
1050 |
used = true; // used check performed later |
|
1051 |
} |
|
1052 |
||
1053 |
if(!used) |
|
1054 |
ensureNoAnnotation(method); |
|
1055 |
} |
|
1056 |
||
1057 |
// Match getter with setters by comparing getter return type to setter param |
|
1058 |
for (Map.Entry<String,M> entry : getters.entrySet()) { |
|
1059 |
String propName = entry.getKey(); |
|
1060 |
M getter = entry.getValue(); |
|
1061 |
List<M> propSetters = allSetters.remove(propName); |
|
1062 |
if (null == propSetters) { |
|
1063 |
//no matching setter |
|
1064 |
continue; |
|
1065 |
} |
|
1066 |
T getterType = nav().getReturnType(getter); |
|
1067 |
for (M setter : propSetters) { |
|
1068 |
T setterType = nav().getMethodParameters(setter)[0]; |
|
1069 |
if (nav().isSameType(setterType, getterType)) { |
|
1070 |
setters.put(propName, setter); |
|
1071 |
break; |
|
1072 |
} |
|
1073 |
} |
|
1074 |
} |
|
1075 |
||
1076 |
// also allow set-only properties |
|
1077 |
for (Map.Entry<String,List<M>> e : allSetters.entrySet()) { |
|
1078 |
setters.put(e.getKey(),e.getValue().get(0)); |
|
1079 |
} |
|
1080 |
} |
|
1081 |
||
1082 |
/** |
|
1083 |
* Checks if the properties in this given super class should be aggregated into this class. |
|
1084 |
*/ |
|
1085 |
private boolean shouldRecurseSuperClass(C sc) { |
|
1086 |
return sc!=null |
|
1087 |
&& (builder.isReplaced(sc) || reader().hasClassAnnotation(sc, XmlTransient.class)); |
|
1088 |
} |
|
1089 |
||
1090 |
/** |
|
1091 |
* Returns true if the method is considered 'public'. |
|
1092 |
*/ |
|
1093 |
private boolean isConsideredPublic(M m) { |
|
1094 |
return m ==null || nav().isPublicMethod(m); |
|
1095 |
} |
|
1096 |
||
1097 |
/** |
|
1098 |
* If the method has an explicit annotation, allow it to participate |
|
1099 |
* to the processing even if it lacks the setter or the getter. |
|
1100 |
*/ |
|
1101 |
private void resurrect(Map<String, M> methods, Set<String> complete) { |
|
1102 |
for (Map.Entry<String, M> e : methods.entrySet()) { |
|
1103 |
if(complete.contains(e.getKey())) |
|
1104 |
continue; |
|
1105 |
if(hasJAXBAnnotation(reader().getAllMethodAnnotations(e.getValue(),this))) |
|
1106 |
complete.add(e.getKey()); |
|
1107 |
} |
|
1108 |
} |
|
1109 |
||
1110 |
/** |
|
1111 |
* Makes sure that the method doesn't have any annotation, if it does, |
|
1112 |
* report it as an error |
|
1113 |
*/ |
|
1114 |
private void ensureNoAnnotation(M method) { |
|
1115 |
Annotation[] annotations = reader().getAllMethodAnnotations(method,this); |
|
1116 |
for( Annotation a : annotations ) { |
|
1117 |
if(isJAXBAnnotation(a)) { |
|
1118 |
builder.reportError(new IllegalAnnotationException( |
|
1119 |
Messages.ANNOTATION_ON_WRONG_METHOD.format(), |
|
1120 |
a)); |
|
1121 |
return; |
|
1122 |
} |
|
1123 |
} |
|
1124 |
} |
|
1125 |
||
1126 |
/** |
|
1127 |
* Returns true if a given annotation is a JAXB annotation. |
|
1128 |
*/ |
|
1129 |
private static boolean isJAXBAnnotation(Annotation a) { |
|
1130 |
return ANNOTATION_NUMBER_MAP.containsKey(a.annotationType()); |
|
1131 |
} |
|
1132 |
||
1133 |
/** |
|
1134 |
* Returns true if the array contains a JAXB annotation. |
|
1135 |
*/ |
|
1136 |
private static boolean hasJAXBAnnotation(Annotation[] annotations) { |
|
1137 |
return getSomeJAXBAnnotation(annotations)!=null; |
|
1138 |
} |
|
1139 |
||
1140 |
private static Annotation getSomeJAXBAnnotation(Annotation[] annotations) { |
|
1141 |
for( Annotation a : annotations ) |
|
1142 |
if(isJAXBAnnotation(a)) |
|
1143 |
return a; |
|
1144 |
return null; |
|
1145 |
} |
|
1146 |
||
1147 |
||
1148 |
/** |
|
1149 |
* Returns "Foo" from "getFoo" or "isFoo". |
|
1150 |
* |
|
1151 |
* @return null |
|
1152 |
* if the method name doesn't look like a getter. |
|
1153 |
*/ |
|
1154 |
private static String getPropertyNameFromGetMethod(String name) { |
|
1155 |
if(name.startsWith("get") && name.length()>3) |
|
1156 |
return name.substring(3); |
|
1157 |
if(name.startsWith("is") && name.length()>2) |
|
1158 |
return name.substring(2); |
|
1159 |
return null; |
|
1160 |
} |
|
1161 |
||
1162 |
/** |
|
1163 |
* Returns "Foo" from "setFoo". |
|
1164 |
* |
|
1165 |
* @return null |
|
1166 |
* if the method name doesn't look like a setter. |
|
1167 |
*/ |
|
1168 |
private static String getPropertyNameFromSetMethod(String name) { |
|
1169 |
if(name.startsWith("set") && name.length()>3) |
|
1170 |
return name.substring(3); |
|
1171 |
return null; |
|
1172 |
} |
|
1173 |
||
1174 |
/** |
|
1175 |
* Creates a new {@link FieldPropertySeed} object. |
|
1176 |
* |
|
1177 |
* <p> |
|
1178 |
* Derived class can override this method to create a sub-class. |
|
1179 |
*/ |
|
1180 |
protected PropertySeed<T,C,F,M> createFieldSeed(F f) { |
|
1181 |
return new FieldPropertySeed<T,C,F,M>(this, f); |
|
1182 |
} |
|
1183 |
||
1184 |
/** |
|
1185 |
* Creates a new {@link GetterSetterPropertySeed} object. |
|
1186 |
*/ |
|
1187 |
protected PropertySeed<T,C,F,M> createAccessorSeed(M getter, M setter) { |
|
1188 |
return new GetterSetterPropertySeed<T,C,F,M>(this, getter,setter); |
|
1189 |
} |
|
1190 |
||
1191 |
public final boolean isElement() { |
|
1192 |
return elementName!=null; |
|
1193 |
} |
|
1194 |
||
1195 |
public boolean isAbstract() { |
|
1196 |
return nav().isAbstract(clazz); |
|
1197 |
} |
|
1198 |
||
1199 |
public boolean isOrdered() { |
|
1200 |
return propOrder!=null; |
|
1201 |
} |
|
1202 |
||
1203 |
public final boolean isFinal() { |
|
1204 |
return nav().isFinal(clazz); |
|
1205 |
} |
|
1206 |
||
1207 |
public final boolean hasSubClasses() { |
|
1208 |
return hasSubClasses; |
|
1209 |
} |
|
1210 |
||
1211 |
public final boolean hasAttributeWildcard() { |
|
1212 |
return declaresAttributeWildcard() || inheritsAttributeWildcard(); |
|
1213 |
} |
|
1214 |
||
1215 |
public final boolean inheritsAttributeWildcard() { |
|
1216 |
return getInheritedAttributeWildcard()!=null; |
|
1217 |
} |
|
1218 |
||
1219 |
public final boolean declaresAttributeWildcard() { |
|
1220 |
return attributeWildcard!=null; |
|
1221 |
} |
|
1222 |
||
1223 |
/** |
|
1224 |
* Gets the {@link PropertySeed} object for the inherited attribute wildcard. |
|
1225 |
*/ |
|
1226 |
private PropertySeed<T,C,F,M> getInheritedAttributeWildcard() { |
|
1227 |
for( ClassInfoImpl<T,C,F,M> c=getBaseClass(); c!=null; c=c.getBaseClass() ) |
|
1228 |
if(c.attributeWildcard!=null) |
|
1229 |
return c.attributeWildcard; |
|
1230 |
return null; |
|
1231 |
} |
|
1232 |
||
1233 |
public final QName getElementName() { |
|
1234 |
return elementName; |
|
1235 |
} |
|
1236 |
||
1237 |
public final QName getTypeName() { |
|
1238 |
return typeName; |
|
1239 |
} |
|
1240 |
||
1241 |
public final boolean isSimpleType() { |
|
1242 |
List<? extends PropertyInfo> props = getProperties(); |
|
1243 |
if(props.size()!=1) return false; |
|
1244 |
return props.get(0).kind()==PropertyKind.VALUE; |
|
1245 |
} |
|
1246 |
||
1247 |
/** |
|
1248 |
* Called after all the {@link TypeInfo}s are collected into the {@link #owner}. |
|
1249 |
*/ |
|
1250 |
@Override |
|
1251 |
/*package*/ void link() { |
|
1252 |
getProperties(); // make sure properties!=null |
|
1253 |
||
1254 |
// property name collision cehck |
|
1255 |
Map<String,PropertyInfoImpl> names = new HashMap<String,PropertyInfoImpl>(); |
|
1256 |
for( PropertyInfoImpl<T,C,F,M> p : properties ) { |
|
1257 |
p.link(); |
|
1258 |
PropertyInfoImpl old = names.put(p.getName(),p); |
|
1259 |
if(old!=null) { |
|
1260 |
builder.reportError(new IllegalAnnotationException( |
|
1261 |
Messages.PROPERTY_COLLISION.format(p.getName()), |
|
1262 |
p, old )); |
|
1263 |
} |
|
1264 |
} |
|
1265 |
super.link(); |
|
1266 |
} |
|
1267 |
||
1268 |
public Location getLocation() { |
|
1269 |
return nav().getClassLocation(clazz); |
|
1270 |
} |
|
1271 |
||
1272 |
/** |
|
1273 |
* XmlType allows specification of factoryClass and |
|
1274 |
* factoryMethod. There are to be used if no default |
|
1275 |
* constructor is found. |
|
1276 |
* |
|
1277 |
* @return |
|
1278 |
* true if the factory method was found. False if not. |
|
1279 |
*/ |
|
1280 |
private boolean hasFactoryConstructor(XmlType t){ |
|
1281 |
if (t == null) return false; |
|
1282 |
||
1283 |
String method = t.factoryMethod(); |
|
1284 |
T fClass = reader().getClassValue(t, "factoryClass"); |
|
1285 |
if (method.length() > 0){ |
|
1286 |
if(nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){ |
|
1287 |
fClass = nav().use(clazz); |
|
1288 |
} |
|
1289 |
for(M m: nav().getDeclaredMethods(nav().asDecl(fClass))){ |
|
1290 |
//- Find the zero-arg public static method with the required return type |
|
1291 |
if (nav().getMethodName(m).equals(method) && |
|
1292 |
nav().isSameType(nav().getReturnType(m), nav().use(clazz)) && |
|
1293 |
nav().getMethodParameters(m).length == 0 && |
|
1294 |
nav().isStaticMethod(m)){ |
|
1295 |
factoryMethod = m; |
|
1296 |
break; |
|
1297 |
} |
|
1298 |
} |
|
1299 |
if (factoryMethod == null){ |
|
1300 |
builder.reportError(new IllegalAnnotationException( |
|
1301 |
Messages.NO_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass)), method), this )); |
|
1302 |
} |
|
1303 |
} else if(!nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){ |
|
1304 |
builder.reportError(new IllegalAnnotationException( |
|
1305 |
Messages.FACTORY_CLASS_NEEDS_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass))), this )); |
|
1306 |
} |
|
1307 |
return factoryMethod != null; |
|
1308 |
} |
|
1309 |
||
1310 |
public Method getFactoryMethod(){ |
|
1311 |
return (Method) factoryMethod; |
|
1312 |
} |
|
1313 |
||
1314 |
@Override |
|
1315 |
public String toString() { |
|
1316 |
return "ClassInfo("+clazz+')'; |
|
1317 |
} |
|
1318 |
||
1319 |
private static final String[] DEFAULT_ORDER = new String[0]; |
|
1320 |
} |