Source code: org/apache/axis/description/TypeDesc.java
1 /*
2 * Copyright 2002-2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.axis.description;
18
19 import org.apache.axis.utils.BeanPropertyDescriptor;
20 import org.apache.axis.utils.BeanUtils;
21 import org.apache.axis.utils.Messages;
22 import org.apache.axis.utils.cache.MethodCache;
23
24 import javax.xml.namespace.QName;
25 import java.io.Serializable;
26 import java.lang.reflect.Method;
27 import java.util.HashMap;
28 import java.util.Hashtable;
29 import java.util.Map;
30
31 /**
32 * A TypeDesc represents a Java<->XML data binding. It is essentially
33 * a collection of FieldDescs describing how to map each field in a Java
34 * class to XML.
35 *
36 * @author Glen Daniels (gdaniels@apache.org)
37 */
38 public class TypeDesc implements Serializable {
39 public static final Class [] noClasses = new Class [] {};
40 public static final Object[] noObjects = new Object[] {};
41
42 /** A map of class -> TypeDesc */
43 private static Map classMap = new Hashtable();
44
45 /** Have we already introspected for the special "any" property desc? */
46 private boolean lookedForAny = false;
47
48 /** Can this instance search for metadata in parents of the type it describes? */
49 private boolean canSearchParents = true;
50 private boolean hasSearchedParents = false;
51
52 /** My superclass TypeDesc */
53 private TypeDesc parentDesc = null;
54
55 /**
56 * Creates a new <code>TypeDesc</code> instance. The type desc can search
57 * the metadata of its type'sparent classes.
58 *
59 * @param javaClass a <code>Class</code> value
60 */
61 public TypeDesc(Class javaClass) {
62 this(javaClass, true);
63 }
64
65 /**
66 * Creates a new <code>TypeDesc</code> instance.
67 *
68 * @param javaClass a <code>Class</code> value
69 * @param canSearchParents whether the type desc can search the metadata of
70 * its type's parent classes.
71 */
72 public TypeDesc(Class javaClass, boolean canSearchParents) {
73 this.javaClass = javaClass;
74 this.canSearchParents = canSearchParents;
75 Class cls = javaClass.getSuperclass();
76 if (cls != null && !cls.getName().startsWith("java.")) {
77 parentDesc = getTypeDescForClass(cls);
78 }
79 }
80
81 /**
82 * Static function to explicitly register a type description for
83 * a given class.
84 *
85 * @param cls the Class we're registering metadata about
86 * @param td the TypeDesc containing the metadata
87 */
88 public static void registerTypeDescForClass(Class cls, TypeDesc td)
89 {
90 classMap.put(cls, td);
91 }
92
93 /**
94 * Static function for centralizing access to type metadata for a
95 * given class.
96 *
97 * This checks for a static getTypeDesc() method on the
98 * class or _Helper class.
99 * Eventually we may extend this to provide for external
100 * metadata config (via files sitting in the classpath, etc).
101 *
102 */
103 public static TypeDesc getTypeDescForClass(Class cls)
104 {
105 // First see if we have one explicitly registered
106 // or cached from previous lookup
107 TypeDesc result = (TypeDesc)classMap.get(cls);
108
109 if (result == null) {
110 try {
111 Method getTypeDesc =
112 MethodCache.getInstance().getMethod(cls,
113 "getTypeDesc",
114 noClasses);
115 if (getTypeDesc != null) {
116 result = (TypeDesc)getTypeDesc.invoke(null, noObjects);
117 if (result != null) {
118 classMap.put(cls, result);
119 }
120 }
121 } catch (Exception e) {
122 }
123 }
124
125 return result;
126 }
127
128 /** The Java class for this type */
129 private Class javaClass = null;
130
131 /** The XML type QName for this type */
132 private QName xmlType = null;
133
134 /** The various fields in here */
135 private FieldDesc [] fields;
136
137 /** A cache of FieldDescs by name */
138 private HashMap fieldNameMap = new HashMap();
139
140 /** A cache of FieldDescs by Element QName */
141 private HashMap fieldElementMap = null;
142
143 /** Are there any fields which are serialized as attributes? */
144 private boolean _hasAttributes = false;
145
146 /** Introspected property descriptors */
147 private BeanPropertyDescriptor[] propertyDescriptors = null;
148 /** Map with key = property descriptor name, value = descriptor */
149 private Map propertyMap = null;
150
151 /**
152 * Indication if this type has support for xsd:any.
153 */
154 private BeanPropertyDescriptor anyDesc = null;
155
156 public BeanPropertyDescriptor getAnyDesc() {
157 return anyDesc;
158 }
159
160 /**
161 * Obtain the current array of FieldDescs
162 */
163 public FieldDesc[] getFields() {
164 return fields;
165 }
166
167 public FieldDesc[] getFields(boolean searchParents) {
168 // note that if canSearchParents is false, this is identical
169 // to getFields(), because the parent type's metadata is off
170 // limits for restricted types which are required to provide a
171 // complete description of their content model in their own
172 // metadata, per the XML schema rules for
173 // derivation-by-restriction
174 if (canSearchParents && searchParents && !hasSearchedParents) {
175 // check superclasses if they exist
176 if (parentDesc != null) {
177 FieldDesc [] parentFields = parentDesc.getFields(true);
178 // START FIX http://nagoya.apache.org/bugzilla/show_bug.cgi?id=17188
179 if (parentFields != null) {
180 if (fields != null) {
181 FieldDesc [] ret = new FieldDesc[parentFields.length + fields.length];
182 System.arraycopy(parentFields, 0, ret, 0, parentFields.length);
183 System.arraycopy(fields, 0, ret, parentFields.length, fields.length);
184 fields = ret;
185 } else {
186 FieldDesc [] ret = new FieldDesc[parentFields.length];
187 System.arraycopy(parentFields, 0, ret, 0, parentFields.length);
188 fields = ret;
189 }
190 // END FIX http://nagoya.apache.org/bugzilla/show_bug.cgi?id=17188
191 }
192 }
193
194 hasSearchedParents = true;
195 }
196
197 return fields;
198 }
199
200 /**
201 * Replace the array of FieldDescs, making sure we keep our convenience
202 * caches in sync.
203 */
204 public void setFields(FieldDesc [] newFields)
205 {
206 fieldNameMap = new HashMap();
207 fields = newFields;
208 _hasAttributes = false;
209 fieldElementMap = null;
210
211 for (int i = 0; i < newFields.length; i++) {
212 FieldDesc field = newFields[i];
213 if (!field.isElement()) {
214 _hasAttributes = true;
215 }
216 fieldNameMap.put(field.getFieldName(), field);
217 }
218 }
219
220 /**
221 * Add a new FieldDesc, keeping the convenience fields in sync.
222 */
223 public void addFieldDesc(FieldDesc field)
224 {
225 if (field == null) {
226 throw new IllegalArgumentException(
227 Messages.getMessage("nullFieldDesc"));
228 }
229
230 int numFields = 0;
231 if (fields != null) {
232 numFields = fields.length;
233 }
234 FieldDesc [] newFields = new FieldDesc[numFields + 1];
235 if (fields != null) {
236 System.arraycopy(fields, 0, newFields, 0, numFields);
237 }
238 newFields[numFields] = field;
239 fields = newFields;
240
241 // Keep track of the field by name for fast lookup
242 fieldNameMap.put(field.getFieldName(), field);
243
244 if (!_hasAttributes && !field.isElement())
245 _hasAttributes = true;
246 }
247
248 /**
249 * Get the QName associated with this field, but only if it's
250 * marked as an element.
251 */
252 public QName getElementNameForField(String fieldName)
253 {
254 FieldDesc desc = (FieldDesc)fieldNameMap.get(fieldName);
255 if (desc == null) {
256 // check superclasses if they exist
257 // and we are allowed to look
258 if (canSearchParents) {
259 if (parentDesc != null) {
260 return parentDesc.getElementNameForField(fieldName);
261 }
262 }
263 } else if (desc.isElement()) {
264 return desc.getXmlName();
265 }
266 return null;
267 }
268
269 /**
270 * Get the QName associated with this field, but only if it's
271 * marked as an attribute.
272 */
273 public QName getAttributeNameForField(String fieldName)
274 {
275 FieldDesc desc = (FieldDesc)fieldNameMap.get(fieldName);
276 if (desc == null) {
277 // check superclasses if they exist
278 // and we are allowed to look
279 if (canSearchParents) {
280 if (parentDesc != null) {
281 return parentDesc.getAttributeNameForField(fieldName);
282 }
283 }
284 } else if (!desc.isElement()) {
285 QName ret = desc.getXmlName();
286 if (ret == null) {
287 ret = new QName("", fieldName);
288 }
289 return ret;
290 }
291 return null;
292 }
293
294 /**
295 * Get the field name associated with this QName, but only if it's
296 * marked as an element.
297 *
298 * If the "ignoreNS" argument is true, just compare localNames.
299 */
300 public String getFieldNameForElement(QName qname, boolean ignoreNS)
301 {
302 // have we already computed the answer to this question?
303 if (fieldElementMap != null) {
304 String cached = (String) fieldElementMap.get(qname);
305 if (cached != null) return cached;
306 }
307
308 String result = null;
309
310 String localPart = qname.getLocalPart();
311
312 // check fields in this class
313 for (int i = 0; fields != null && i < fields.length; i++) {
314 FieldDesc field = fields[i];
315 if (field.isElement()) {
316 QName xmlName = field.getXmlName();
317 if (localPart.equals(xmlName.getLocalPart())) {
318 if (ignoreNS || qname.getNamespaceURI().
319 equals(xmlName.getNamespaceURI())) {
320 result = field.getFieldName();
321 break;
322 }
323 }
324 }
325 }
326
327 // check superclasses if they exist
328 // and we are allowed to look
329 if (result == null && canSearchParents) {
330 if (parentDesc != null) {
331 result = parentDesc.getFieldNameForElement(qname, ignoreNS);
332 }
333 }
334
335 // cache the answer away for quicker retrieval next time.
336 if (result != null) {
337 if (fieldElementMap == null) fieldElementMap = new HashMap();
338 fieldElementMap.put(qname, result);
339 }
340
341 return result;
342 }
343
344 /**
345 * Get the field name associated with this QName, but only if it's
346 * marked as an attribute.
347 */
348 public String getFieldNameForAttribute(QName qname)
349 {
350 String possibleMatch = null;
351
352 for (int i = 0; fields != null && i < fields.length; i++) {
353 FieldDesc field = fields[i];
354 if (!field.isElement()) {
355 // It's an attribute, so if we have a solid match, return
356 // its name.
357 if (qname.equals(field.getXmlName())) {
358 return field.getFieldName();
359 }
360 // Not a solid match, but it's still possible we might match
361 // the default (i.e. QName("", fieldName))
362 if (qname.getNamespaceURI().equals("") &&
363 qname.getLocalPart().equals(field.getFieldName())) {
364 possibleMatch = field.getFieldName();
365 }
366 }
367 }
368
369 if (possibleMatch == null && canSearchParents) {
370 // check superclasses if they exist
371 // and we are allowed to look
372 if (parentDesc != null) {
373 possibleMatch = parentDesc.getFieldNameForAttribute(qname);
374 }
375 }
376
377 return possibleMatch;
378 }
379
380 /**
381 * Get a FieldDesc by field name.
382 */
383 public FieldDesc getFieldByName(String name)
384 {
385 FieldDesc ret = (FieldDesc)fieldNameMap.get(name);
386 if (ret == null && canSearchParents) {
387 if (parentDesc != null) {
388 ret = parentDesc.getFieldByName(name);
389 }
390 }
391 return ret;
392 }
393
394 /**
395 * Do we have any FieldDescs marked as attributes?
396 */
397 public boolean hasAttributes() {
398 if (_hasAttributes)
399 return true;
400
401 if (canSearchParents) {
402 if (parentDesc != null) {
403 return parentDesc.hasAttributes();
404 }
405 }
406
407 return false;
408 }
409
410 public QName getXmlType() {
411 return xmlType;
412 }
413
414 public void setXmlType(QName xmlType) {
415 this.xmlType = xmlType;
416 }
417
418 /**
419 * Get/Cache the property descriptors
420 * @return PropertyDescriptor
421 */
422 public BeanPropertyDescriptor[] getPropertyDescriptors() {
423 // Return the propertyDescriptors if already set.
424 // If not set, use BeanUtils.getPd to get the property descriptions.
425 //
426 // Since javaClass is a generated class, there
427 // may be a faster way to set the property descriptions than
428 // using BeanUtils.getPd. But for now calling getPd is sufficient.
429 if (propertyDescriptors == null) {
430 makePropertyDescriptors();
431 }
432 return propertyDescriptors;
433 }
434
435 private synchronized void makePropertyDescriptors() {
436 if (propertyDescriptors != null)
437 return;
438
439 propertyDescriptors = BeanUtils.getPd(javaClass, this);
440 if (!lookedForAny) {
441 anyDesc = BeanUtils.getAnyContentPD(javaClass);
442 lookedForAny = true;
443 }
444 }
445
446 public BeanPropertyDescriptor getAnyContentDescriptor() {
447 if (!lookedForAny) {
448 anyDesc = BeanUtils.getAnyContentPD(javaClass);
449 lookedForAny = true;
450 }
451 return anyDesc;
452 }
453
454 /**
455 * Get/Cache the property descriptor map
456 * @return Map with key=propertyName, value=descriptor
457 */
458 public Map getPropertyDescriptorMap() {
459 synchronized (this) {
460 // Return map if already set.
461 if (propertyMap != null) {
462 return propertyMap;
463 }
464
465 // Make sure properties exist
466 if (propertyDescriptors == null) {
467 getPropertyDescriptors();
468 }
469
470 // Build the map
471 propertyMap = new HashMap();
472 for (int i = 0; i < propertyDescriptors.length; i++) {
473 BeanPropertyDescriptor descriptor = propertyDescriptors[i];
474 propertyMap.put(descriptor.getName(), descriptor);
475 }
476 }
477 return propertyMap;
478 }
479 }