001: /*
002: * XML 2 Java Binding (X2JB) - the excellent Java tool.
003: * Copyright 2007, by Richard Opalka.
004: *
005: * This is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU Lesser General Public License as
007: * published by the Free Software Foundation; either version 2.1 of
008: * the License, or (at your option) any later version.
009: *
010: * This software is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this software; if not see the FSF site:
017: * http://www.fsf.org/ and search for the LGPL License document there.
018: */
019: package org.x2jb.bind;
020:
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Array;
023: import java.text.MessageFormat;
024: import java.util.Map;
025: import java.util.HashMap;
026: import org.x2jb.bind.Messages.BundleKey;
027: import org.x2jb.bind.provider.BindingDefinition;
028: import org.w3c.dom.Attr;
029: import org.w3c.dom.Element;
030: import java.util.List;
031:
032: /**
033: * Implements binding of DOM elements and attributes to Java objects and primitives
034: *
035: * @author <a href="mailto:richard_opalka@yahoo.com">Richard Opalka</a>
036: * @version 1.0
037: */
038: final class Assembler {
039:
040: /**
041: * Constructor
042: */
043: private Assembler() {
044: // no instances
045: }
046:
047: static Object bind(Class iface, Element element,
048: String parentNamespace) throws BindingException {
049: return bindMethods(iface, element, Helper.getNamespace(element));
050: }
051:
052: private static Object bindMethods(Class iface, Element element,
053: String parentNamespace) throws BindingException {
054: Map returnValues = new HashMap();
055: Method[] methods = iface.getMethods();
056:
057: for (int i = 0; i < methods.length; i++) {
058: Method method = methods[i];
059: String methodSignature = iface.getName()
060: + Helper.POINT_STRING + method.getName();
061:
062: if (method.getParameterTypes().length != 0) {
063: // method cannot have parameters
064: throw new BindingException(
065: MessageFormat
066: .format(
067: Messages
068: .get(BundleKey.METHOD_CONTAINS_PARAMETERS),
069: new Object[] { methodSignature }));
070: }
071:
072: Class returnType = method.getReturnType();
073:
074: BindingDefinition binding = Helper.getBinding(method);
075:
076: if (binding == null) {
077: // method must declare binding annotation
078: throw new BindingException(
079: MessageFormat
080: .format(
081: Messages
082: .get(BundleKey.METHOD_WITHOUT_METADATA),
083: new Object[] { methodSignature }));
084: }
085:
086: // return type must be supported simple type or iface with
087: // annotation or array of such types
088: boolean returnTypeIsArray = false;
089: if (returnType.isArray()) {
090: if (!binding.isNodeUnique()) {
091: returnType = returnType.getComponentType();
092: returnTypeIsArray = true;
093: }
094: }
095:
096: if ((returnType.equals(Void.TYPE))
097: || (returnType.equals(Void.class))) {
098: // either void nor Void are not allowed to be as return type
099: throw new BindingException(MessageFormat.format(
100: Messages.get(BundleKey.VOID_METHOD_RETURN),
101: new Object[] { methodSignature }));
102: }
103:
104: if (!HandlersRepository.handlerExists(returnType, binding,
105: false)) {
106: if (returnType.isInterface()) {
107: bindInterface(returnType, iface, method,
108: parentNamespace, element,
109: returnTypeIsArray, returnValues);
110: } else {
111: // throw detailed exception
112: try {
113: HandlersRepository.handlerExists(returnType,
114: binding, true);
115: } catch (BindingException be) {
116: throw new BindingException(
117: MessageFormat
118: .format(
119: Messages
120: .get(BundleKey.METHOD_FULL_NAME),
121: new Object[] { methodSignature })
122: + be.getMessage(), be);
123: }
124: }
125: } else {
126: bindPrimitive(method, iface, parentNamespace,
127: returnTypeIsArray, element, returnValues,
128: returnType);
129: }
130: }
131:
132: // returning wrapping proxy
133: return ProxyImpl.newInstance(iface, returnValues);
134: }
135:
136: private static void bindPrimitive(Method method, Class iface,
137: String parentNamespace, boolean array, Element e,
138: Map returnValues, Class returnType) throws BindingException {
139: String methodSignature = iface.getName() + Helper.POINT_STRING
140: + method.getName();
141: BindingDefinition binding = Helper.getBinding(method);
142:
143: String methodBindingNamespace = Helper.getNamespace(binding,
144: parentNamespace);
145: String methodBindingName = Helper.getName(binding);
146: String methodBindingNode = Helper.getAbsNode(
147: methodBindingNamespace, methodBindingName);
148: String ifaceBindingNode = Helper.getAbsNode(Helper
149: .getNamespace(e, parentNamespace), e.getNodeName());
150:
151: if (methodBindingName.trim().equals(Helper.EMPTY_STRING)) {
152: // node names with whitespaces are not allowed
153: throw new BindingException(MessageFormat.format(Messages
154: .get(BundleKey.METHOD_WRONG_NODE_VALUE),
155: new Object[] { methodSignature }));
156: }
157:
158: if (!binding.isElementNode()) {
159: // attribute binding
160: bindAttribute(method, array, e, returnValues, returnType,
161: methodSignature, binding, methodBindingNamespace,
162: methodBindingName, methodBindingNode);
163: } else {
164: // element binding
165: bindElement(method, array, e, returnValues, returnType,
166: methodSignature, binding, methodBindingNamespace,
167: methodBindingName, methodBindingNode,
168: ifaceBindingNode);
169: }
170: }
171:
172: private static void bindElement(Method method,
173: boolean returnTypeIsArray, Element element,
174: Map returnValues, Class returnType, String methodSignature,
175: BindingDefinition methodBinding,
176: String methodBindingNamespace, String methodBindingName,
177: String methodBindingNode, String ifaceBindingNode)
178: throws BindingException {
179: List matchingSubelements = Helper.getElements(element,
180: methodBindingNamespace, methodBindingName);
181:
182: int subelementsCount = matchingSubelements.size();
183:
184: if ((matchingSubelements == null) || (subelementsCount == 0)) {
185: if (methodBinding.isNodeMandatory()) {
186: // required subelement is missing
187: throw new BindingException(
188: MessageFormat
189: .format(
190: Messages
191: .get(BundleKey.MISSING_REQUIRED_SUBELEMENT),
192: new Object[] {
193: ifaceBindingNode,
194: methodBindingNode,
195: methodSignature }));
196: } else {
197: // optional subelement is missing
198: if (HandlersRepository.handlerExists(returnType,
199: methodBinding, true)) {
200: if (!returnTypeIsArray) {
201: returnValues.put(method.getName(),
202: Helper.defaultValue(returnType,
203: methodBinding));
204: } else {
205: // return type is array thus returning zero length array
206: returnValues.put(method.getName(), Helper
207: .createEmptyArray(returnType));
208: }
209: }
210: }
211: } else {
212: if ((!returnTypeIsArray) && (subelementsCount > 1)) {
213: // multiple elements present but return type is not array
214: throw new BindingException(
215: MessageFormat
216: .format(
217: Messages
218: .get(BundleKey.ARRAY_RETURN_TYPE_REQUIRED),
219: new Object[] { methodSignature,
220: methodBindingNode }));
221: }
222:
223: if (HandlersRepository.handlerExists(returnType,
224: methodBinding, true)) {
225: if (!returnTypeIsArray) {
226: if (subelementsCount == 1) {
227: returnValues
228: .put(
229: method.getName(),
230: Helper
231: .bindElement(
232: returnType,
233: (Element) matchingSubelements
234: .get(0),
235: methodBinding));
236: }
237: } else {
238: Element[] elements = new Element[subelementsCount];
239: for (int i = 0; i < subelementsCount; i++) {
240: // recursive calls over xml data
241: elements[i] = (Element) matchingSubelements
242: .get(i);
243: }
244: returnValues.put(method.getName(), Helper
245: .bindElements(returnType, elements,
246: methodBinding));
247: }
248: }
249: }
250: }
251:
252: private static void bindAttribute(Method method,
253: boolean returnTypeIsArray, Element element,
254: Map returnValues, Class returnType, String methodSignature,
255: BindingDefinition methodBinding,
256: String methodBindingNamespace, String methodBindingName,
257: String methodBindingNode) throws BindingException {
258: if (HandlersRepository.handlerExists(returnType, methodBinding,
259: true)) {
260: String attributeValue = null;
261: Attr attribute = null;
262:
263: if (methodBindingNamespace.equals(Helper.EMPTY_STRING)) {
264: // no namespace declared
265: attributeValue = element
266: .getAttribute(methodBindingName);
267: attribute = element.getAttributeNode(methodBindingName);
268: } else {
269: // namespace declared
270: attributeValue = element.getAttributeNS(
271: methodBindingNamespace, methodBindingName);
272: attribute = element.getAttributeNodeNS(
273: methodBindingNamespace, methodBindingName);
274: }
275:
276: if (Helper.mandatoryAttributeIsMissing(attributeValue,
277: methodBinding)) {
278: // attribute is mandatory but not present
279: throw new BindingException(
280: MessageFormat
281: .format(
282: Messages
283: .get(BundleKey.METHOD_MANDATORY_ATTRIBUTE),
284: new Object[] { methodSignature,
285: methodBindingNode }));
286: }
287:
288: if (Helper.attributeValueIsDefined(attributeValue)) {
289: // attribute exists, so bind it
290: returnValues.put(method.getName(), Helper
291: .bindAttribute(returnType, attribute,
292: methodBinding));
293: } else {
294: // attribute doesn't exist, so use default value
295: returnValues.put(method.getName(), Helper.defaultValue(
296: returnType, methodBinding));
297: }
298: }
299: }
300:
301: private static void bindInterface(Class returnType, Class iface,
302: Method method, String parentNamespace, Element element,
303: boolean returnTypeIsArray, Map returnValues)
304: throws BindingException {
305: String methodSignature = iface.getName() + Helper.POINT_STRING
306: + method.getName();
307: BindingDefinition binding = Helper.getBinding(method);
308: Helper.ensureIfaceIsMappedToElement(returnType, binding);
309:
310: String methodBindingNamespace = Helper.getNamespace(binding,
311: parentNamespace);
312: String methodBindingName = Helper.getName(binding);
313: String methodBindingNode = Helper.getAbsNode(
314: methodBindingNamespace, methodBindingName);
315: String ifaceBindingNode = Helper.getAbsNode(Helper
316: .getNamespace(element, parentNamespace), element
317: .getNodeName());
318:
319: if (!binding.isElementNode()) {
320: // interface mapped to attribute
321: throw new BindingException(MessageFormat.format(Messages
322: .get(BundleKey.IFACE_MAPPED_TO_ATTRIBUTE),
323: new Object[] { methodSignature }));
324: }
325:
326: if (methodBindingName.trim().equals(Helper.EMPTY_STRING)) {
327: // element names with whitespaces are not allowed
328: throw new BindingException(MessageFormat.format(Messages
329: .get(BundleKey.METHOD_WRONG_NODE_VALUE),
330: new Object[] { methodSignature }));
331: }
332:
333: List matchingElements = Helper.getElements(element,
334: methodBindingNamespace, methodBindingName);
335:
336: int subelementsCount = matchingElements.size();
337:
338: if (subelementsCount == 0) {
339: if (binding.isNodeMandatory()) {
340: // required subelement is missing
341: throw new BindingException(
342: MessageFormat
343: .format(
344: Messages
345: .get(BundleKey.MISSING_REQUIRED_SUBELEMENT),
346: new Object[] {
347: ifaceBindingNode,
348: methodBindingNode,
349: methodSignature }));
350: } else {
351: // optional sublements are missing
352: if (returnTypeIsArray) {
353: // return type is array thus returning zero length array
354: returnValues.put(method.getName(), Helper
355: .createEmptyArray(returnType));
356: }
357: }
358: } else {
359: if ((!returnTypeIsArray) && (subelementsCount > 1)) {
360: // multiple elements present but return type is not array
361: throw new BindingException(
362: MessageFormat
363: .format(
364: Messages
365: .get(BundleKey.ARRAY_RETURN_TYPE_REQUIRED),
366: new Object[] { methodSignature,
367: methodBindingNode }));
368: }
369:
370: if (!returnTypeIsArray) {
371: if (subelementsCount == 1) {
372: returnValues.put(method.getName(), bind(returnType,
373: (Element) matchingElements.get(0),
374: methodBindingNamespace));
375: }
376: } else {
377: Object retVals = Array.newInstance(returnType,
378: subelementsCount);
379: for (int i = 0; i < subelementsCount; i++) {
380: // recursive calls over xml data
381: Array.set(retVals, i, bind(returnType,
382: (Element) matchingElements.get(i),
383: methodBindingNamespace));
384: }
385: returnValues.put(method.getName(), retVals);
386: }
387: }
388: }
389:
390: }
|