1 /*
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Intalio, Inc. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Intalio, Inc. Exolab is a registered
23 * trademark of Intalio, Inc.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2002 (C) Intalio Inc. All Rights Reserved.
42 *
43 * $Id$
44 */
45 package org.exolab.castor.builder.binding;
46
47 //--Castor imports
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Hashtable;
51 import java.util.Map;
52 import java.util.Set;
53
54 import org.exolab.castor.builder.binding.xml.AutomaticNamingType;
55 import org.exolab.castor.builder.binding.xml.Binding;
56 import org.exolab.castor.builder.binding.xml.ComponentBindingType;
57 import org.exolab.castor.builder.binding.xml.Exclude;
58 import org.exolab.castor.builder.binding.xml.Excludes;
59 import org.exolab.castor.builder.binding.xml.Forces;
60 import org.exolab.castor.xml.schema.Annotated;
61 import org.exolab.castor.xml.schema.AttributeDecl;
62 import org.exolab.castor.xml.schema.ElementDecl;
63 import org.exolab.castor.xml.schema.Structure;
64
65 /**
66 * This class adds the necessary logic to a Binding Object to bring the gap
67 * between the XML Schema Object Model and the Binding File. It queries the
68 * Binding Object to retrieve the the associated ComponentBinding.
69 * <p>
70 * An "XPath like" representation of an XML Schema structure is built to lookup
71 * the component bindings in their storage structure. The algorithm used to
72 * build the "XPath like" representation is summarized in the following example:
73 * Given the XML schema declaration:
74 *
75 * <pre>
76 * <xsd:element name="foo">
77 * <xsd:complextype>
78 * <xsd:attribute name="bar" type="xsd:string"/>
79 * </xsd:complextype>
80 * </xsd:element>
81 * </pre>
82 *
83 * The path to identify the attribute 'bar' will be:
84 *
85 * <pre>
86 * /foo/@bar
87 * </pre>
88 *
89 * The keywords <tt>complexType</tt> and <tt>group</tt> are used to identify
90 * respectively an XML Schema ComplexType and a Model Group <b>definition</b>.
91 *
92 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
93 * @version $Revision$ $Date: 2005-03-05 06:42:06 -0700 (Sat, 05 Mar 2005) $
94 */
95 public final class ExtendedBinding extends Binding {
96
97 /**
98 * Constants needed to create the XPath.
99 */
100 protected static final String PATH_SEPARATOR = "/";
101 /**
102 * Prefix used to identify an attribute.
103 */
104 protected static final String ATTRIBUTE_PREFIX = "@";
105 /**
106 * Prefix used to identify a complexType.
107 */
108 public static final String COMPLEXTYPE_ID = "complexType:";
109 /**
110 * Prefix used to identity a simplyType.
111 */
112 public static final String SIMPLETYPE_ID = "simpleType:";
113 /**
114 * Prefix used to identify an enumeration.
115 */
116 public static final String ENUMTYPE_ID = "enumType:";
117 /**
118 * Prefix used to identify a model group.
119 */
120 public static final String GROUP_ID = "group:";
121
122 private static final short ATTRIBUTE = 10;
123 private static final short ELEMENT = 11;
124 private static final short COMPLEXTYPE = 12;
125 private static final short GROUP = 13;
126 private static final short ENUM_TYPE = 14;
127 private static final short SIMPLETYPE = 15;
128
129 /**
130 * The hashtables that contain the different componentBindings.
131 */
132 private Hashtable<String, ComponentBindingType> _componentBindings;
133
134 /**
135 * A flag that indicates if the component bindings of that Binding have been
136 * processed.
137 */
138 private boolean _bindingProcessed = false;
139
140 /**
141 * Maintains a list of element names where automatic name conflict resolution should be
142 * used all times, incl. the first one.
143 */
144 private Set<String> _automaticNameResolutionForced = new HashSet<String>();
145
146 /**
147 * Maintains a map of exclusions from the automatic name conflict.
148 */
149 private Map<String, Exclude> _automaticNameResolutionExcludes = new HashMap<String, Exclude>();
150
151 /**
152 * Default constructor.
153 * @see java.lang.Object#Object()
154 */
155 public ExtendedBinding() {
156 super();
157 _componentBindings = new Hashtable<String, ComponentBindingType>();
158 }
159
160 /**
161 * Returns the ComponentBinding that corresponds to the given Annotated XML
162 * Schema structure An Schema location will be built for the given Annotated
163 * XML schema structure.
164 *
165 * @param annotated the XML Schema annotated structure for which to query
166 * the Binding object for a ComponentBinding.
167 *
168 * @return the ComponentBinding that corresponds to the given Annotated XML
169 * Schema structure.
170 */
171 public ComponentBindingType getComponentBindingType(final Annotated annotated) {
172 if (annotated == null) {
173 return null;
174 }
175
176 //--no binding can be defined for a GROUP
177 if (annotated.getStructureType() == Structure.GROUP) {
178 return null;
179 }
180
181 if (!_bindingProcessed) {
182 processBindingComponents();
183 }
184
185 String xPath = XPathHelper.getSchemaLocation(annotated);
186 ComponentBindingType result = lookupComponentBindingType(xPath);
187 if (result == null) {
188 //--handle reference
189 switch (annotated.getStructureType()) {
190
191 case Structure.ELEMENT :
192 //--handle reference: if the element referred a
193 //--global element then we use the global binding
194 if (result == null) {
195 ElementDecl element = (ElementDecl) annotated;
196 if (element.isReference()) {
197 xPath = XPathHelper.getSchemaLocation(element.getReference());
198 result = lookupComponentBindingType(xPath);
199 }
200 //--discard the element
201 element = null;
202 }
203 break;
204
205 case Structure.ATTRIBUTE :
206 if (result == null) {
207 //--handle reference: if the element referred a
208 //--global element then we use the global binding
209 AttributeDecl attribute = (AttributeDecl) annotated;
210 if (attribute.isReference()) {
211 xPath = XPathHelper.getSchemaLocation(attribute.getReference());
212 result = lookupComponentBindingType(xPath);
213 }
214 attribute = null;
215 }
216 break;
217
218 default :
219 break;
220 }
221 } //--result == null
222
223 return result;
224 }
225
226 /**
227 * Returns the ComponentBindingType that corresponds to the given Schema
228 * location XPath. This is a direct lookup in the hashtable, null is
229 * returned if no ComponentBindingType corresponds to the given Schema
230 * Location XPath.
231 *
232 * @param xPath the schema location xpath
233 * @return The ComponentBindingType that correspond to the given Schema
234 * Location XPath, Null is returned when no ComponentBindingType is
235 * found.
236 * @see org.exolab.castor.builder.binding.XPathHelper#getSchemaLocation(Structure)
237 */
238 private ComponentBindingType lookupComponentBindingType(final String xPath) {
239 if (xPath == null) {
240 return null;
241 }
242 ComponentBindingType componentBinding =
243 _componentBindings.get(xPath);
244
245 // if no component binding has been found, retry with xpath without namespaces
246 // this is to ensure backwards compatibility
247 if (componentBinding == null) {
248 int occurence = xPath.indexOf('{');
249 String xPathNoNamespaces = xPath;
250 if (occurence > 0) {
251 while (occurence > 0) {
252 String xPathOld = xPathNoNamespaces;
253 xPathNoNamespaces = xPathOld.substring(0, occurence);
254 int closingOccurence = xPathOld.indexOf('}');
255 xPathNoNamespaces += xPathOld.substring(closingOccurence + 1);
256 occurence = xPathNoNamespaces.indexOf('{');
257 }
258 componentBinding = lookupComponentBindingType(xPathNoNamespaces);
259 }
260 }
261 return componentBinding;
262 }
263
264 /**
265 * Process the top-level Binding Component definitions and their children.
266 * Processing a binding component is a 2-step process:
267 * <ul>
268 * <li>Create a key for the component for direct lookup.</li>
269 * <li>Process its children</li>
270 * </ul>
271 */
272 private void processBindingComponents() {
273 ComponentBindingType temp;
274 ComponentBindingType[] tempBindings = getAttributeBinding();
275
276 //1--attributes
277 for (int i = 0; i < tempBindings.length; i++) {
278 temp = tempBindings[i];
279 //--top-level attribute --> no location computation
280 handleComponent(temp, null, ATTRIBUTE);
281 }
282
283 //2--complexTypes
284 tempBindings = getComplexTypeBinding();
285 for (int i = 0; i < tempBindings.length; i++) {
286 temp = tempBindings[i];
287 handleComponent(temp, null, COMPLEXTYPE);
288 }
289
290 //3--elements
291 tempBindings = getElementBinding();
292 for (int i = 0; i < tempBindings.length; i++) {
293 temp = tempBindings[i];
294 handleComponent(temp, null, ELEMENT);
295 }
296
297 //4--groups
298 tempBindings = getGroupBinding();
299 for (int i = 0; i < tempBindings.length; i++) {
300 temp = tempBindings[i];
301 handleComponent(temp, null, GROUP);
302 }
303
304 //5--enums
305 tempBindings = getEnumBinding();
306 for (int i = 0; i < tempBindings.length; i++) {
307 temp = tempBindings[i];
308 handleComponent(temp, null, ENUM_TYPE);
309 }
310
311 //6--simpleTypes
312 tempBindings = getSimpleTypeBinding();
313 for (int i = 0; i < tempBindings.length; i++) {
314 temp = tempBindings[i];
315 handleComponent(temp, null, SIMPLETYPE);
316 }
317
318 temp = null;
319 tempBindings = null;
320
321 _bindingProcessed = true;
322 }
323
324 /**
325 * Process automatic name conflict resolution section, and memorize definitions.
326 * @param type {@link AutomaticNamingType} instance
327 */
328 void handleAutomaticNaming(final AutomaticNamingType type) {
329 Forces forcesOuter = type.getForces();
330 if (forcesOuter != null) {
331 String[] forces = forcesOuter.getForce();
332 for (int i = 0; i < forces.length; i++) {
333 String elementName = forces[i];
334 _automaticNameResolutionForced.add(elementName);
335
336 }
337 }
338
339 Excludes excludesOuter = type.getExcludes();
340 if (excludesOuter != null) {
341 Exclude[] excludes = excludesOuter.getExclude();
342 for (int i = 0; i < excludes.length; i++) {
343 Exclude exclude = excludes[i];
344 _automaticNameResolutionExcludes.put(exclude.getName(), exclude);
345 }
346 }
347 }
348
349 /**
350 * Processes the given ComponentBindingType given its type.
351 *
352 * @param binding the ComponentBindingType for which we want to process the
353 * children.
354 * @param xPath the current XPath location that points to the parent of the
355 * given ComponentBindingType.
356 * @param type an integer that indicates the type of the given
357 * ComponentBindingType
358 */
359 private void handleComponent(
360 final ComponentBindingType binding, final String xPath, final int type) {
361 if (binding == null) {
362 return;
363 }
364
365 String currentPath = xPath;
366 if (currentPath == null) {
367 currentPath = new String();
368 }
369
370 String name = binding.getName();
371 boolean xpathUsed = (name.indexOf("/") != -1);
372
373 switch (type) {
374 case ATTRIBUTE :
375 //--handle attributes
376 if (!xpathUsed) {
377 currentPath = currentPath + PATH_SEPARATOR + ATTRIBUTE_PREFIX;
378 }
379 currentPath += name;
380 _componentBindings.put(currentPath, binding);
381 break;
382
383 case SIMPLETYPE :
384 //--handle simpleType
385 if (!xpathUsed) {
386 currentPath += SIMPLETYPE_ID;
387 }
388 currentPath += name;
389 _componentBindings.put(currentPath, binding);
390 break;
391
392 case ELEMENT :
393 //--handle element
394 if (!xpathUsed) {
395 currentPath += PATH_SEPARATOR;
396 }
397 currentPath += name;
398 _componentBindings.put(currentPath, binding);
399 break;
400
401 case COMPLEXTYPE :
402 //--handle complexType
403 if (!xpathUsed) {
404 currentPath += COMPLEXTYPE_ID;
405 } else {
406 if (!name.substring(1, 12).equals("complexType")) {
407 currentPath += PATH_SEPARATOR + COMPLEXTYPE_ID;
408 currentPath += name.substring(1);
409 } else {
410 currentPath += name;
411 }
412 }
413 _componentBindings.put(currentPath, binding);
414 break;
415
416 case ENUM_TYPE :
417 //--handle enum
418 if (!xpathUsed) {
419 currentPath += ENUMTYPE_ID;
420 }
421 currentPath += name;
422 _componentBindings.put(currentPath, binding);
423 break;
424
425 case GROUP :
426 //--handle group
427 if (!xpathUsed) {
428 currentPath += GROUP_ID;
429 }
430 currentPath += name;
431 _componentBindings.put(currentPath, binding);
432 break;
433
434 default :
435 //--there's a problem somewhere
436 throw new IllegalStateException("Invalid ComponentBindingType: the"
437 + " type (attribute, element, complextype or group) is unknown");
438 }
439
440 //--process children
441 ComponentBindingType temp;
442 ComponentBindingType[] tempBindings = binding.getAttributeBinding();
443
444 //1--attributes
445 for (int i = 0; i < tempBindings.length; i++) {
446 temp = tempBindings[i];
447 //--top-level attribute --> no location computation
448 handleComponent(temp, currentPath, ATTRIBUTE);
449 }
450
451 //2--complexTypes
452 tempBindings = binding.getComplexTypeBinding();
453 for (int i = 0; i < tempBindings.length; i++) {
454 temp = tempBindings[i];
455 handleComponent(temp, currentPath, COMPLEXTYPE);
456 }
457
458 //X--simpleTypes
459 tempBindings = binding.getSimpleTypeBinding();
460 for (int i = 0; i < tempBindings.length; i++) {
461 temp = tempBindings[i];
462 handleComponent(temp, currentPath, SIMPLETYPE);
463 }
464
465 //3--elements
466 tempBindings = binding.getElementBinding();
467 for (int i = 0; i < tempBindings.length; i++) {
468 temp = tempBindings[i];
469 handleComponent(temp, currentPath, ELEMENT);
470 }
471
472 //4--groups
473 tempBindings = binding.getGroupBinding();
474 for (int i = 0; i < tempBindings.length; i++) {
475 temp = tempBindings[i];
476 handleComponent(temp, currentPath, GROUP);
477 }
478
479 //5--enums
480 tempBindings = binding.getEnumBinding();
481 for (int i = 0; i < tempBindings.length; i++) {
482 temp = tempBindings[i];
483 handleComponent(temp, currentPath, ENUM_TYPE);
484 }
485
486 //
487 temp = null;
488 tempBindings = null;
489 }
490
491 /**
492 * Indicates whether an <exclude> element has been specified in a binding
493 * file for the given 'local name' of an element definition.
494 * @param localName 'local name' of an element definition
495 * @return True if an <exclude> element has been specified
496 */
497 public boolean existsExclusion(final String localName) {
498 return _automaticNameResolutionExcludes.containsKey(localName);
499 }
500
501 /**
502 * Returns the {@link Exclude} instance for the element identified by the given local name.
503 * @param localName Local name for an element (definition).
504 * @return The {@link Exclude} instance.
505 */
506 public Exclude getExclusion(final String localName) {
507 return _automaticNameResolutionExcludes.get(localName);
508 }
509
510 /**
511 * Indicates whether an <force> element has been specified in a binding
512 * file for the given 'local name' of an element definition.
513 * @param localName 'local name' of an element definition
514 * @return True if an <force> element has been specified
515 */
516 public boolean existsForce(final String localName) {
517 return _automaticNameResolutionForced.contains(localName);
518 }
519
520 /**
521 * Returns all <force> elements defined in the binding file.
522 * @return all <force> elements defined in the binding file
523 */
524 public Set<String> getForces() {
525 return _automaticNameResolutionForced;
526 }
527
528 }