View Javadoc

1   /*
2    * Copyright 2010 Capgemini
3    * Licensed under the Apache License, Version 2.0 (the "License"); 
4    * you may not use this file except in compliance with the License. 
5    * You may obtain a copy of the License at 
6    * 
7    * http://www.apache.org/licenses/LICENSE-2.0 
8    * 
9    * Unless required by applicable law or agreed to in writing, software 
10   * distributed under the License is distributed on an "AS IS" BASIS, 
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
12   * See the License for the specific language governing permissions and 
13   * limitations under the License. 
14   * 
15   */
16  package org.xmlfield.core.internal;
17  
18  import static com.google.common.base.Preconditions.checkNotNull;
19  import static org.apache.commons.lang.StringUtils.isBlank;
20  import static org.apache.commons.lang.StringUtils.substringAfterLast;
21  import static org.apache.commons.lang.StringUtils.substringBeforeLast;
22  import static org.xmlfield.core.internal.XmlFieldUtils.getExplicitCollections;
23  import static org.xmlfield.core.internal.XmlFieldUtils.getFieldFormat;
24  import static org.xmlfield.core.internal.XmlFieldUtils.getFieldXPath;
25  import static org.xmlfield.core.internal.XmlFieldUtils.getFieldXPathType;
26  import static org.xmlfield.core.internal.XmlFieldUtils.getResourceNamespaces;
27  
28  import java.lang.reflect.Field;
29  import java.lang.reflect.InvocationHandler;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  import java.lang.reflect.Modifier;
33  import java.util.ArrayList;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.TreeSet;
39  
40  import org.apache.commons.lang.ArrayUtils;
41  import org.apache.commons.lang.NotImplementedException;
42  import org.apache.commons.lang.StringUtils;
43  import org.joda.time.DateTime;
44  import org.joda.time.chrono.ISOChronology;
45  import org.joda.time.format.DateTimeFormat;
46  import org.joda.time.format.DateTimeFormatter;
47  import org.joda.time.format.ISODateTimeFormat;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  import org.xmlfield.annotations.FieldXPath;
51  import org.xmlfield.core.XmlField;
52  import org.xmlfield.core.api.XmlFieldNode;
53  import org.xmlfield.core.api.XmlFieldNodeList;
54  import org.xmlfield.core.api.XmlFieldObject;
55  import org.xmlfield.core.exception.XmlFieldTechnicalException;
56  import org.xmlfield.core.exception.XmlFieldXPathException;
57  
58  import com.google.common.collect.MapMaker;
59  
60  /**
61   * l'objet {@link InvocationHandler} à utiliser sur les proxies chargés à la
62   * lecture des nœuds XML.
63   * 
64   * @author David Andrianavalontsalama
65   * @author Nicolas Richeton <nicolas.richeton@capgemini.com>
66   * @author Guillaume Mary <guillaume.mary@capgemini.com>
67   */
68  public class XmlFieldInvocationHandler implements InvocationHandler {
69  
70  	private static final Logger logger = LoggerFactory
71  			.getLogger(XmlFieldInvocationHandler.class);
72  
73  	private static Map<String, Set<String>> methodnamesCache = new MapMaker()
74  			.softValues().makeMap();
75  
76  	private static Map<String, NamespaceMap> namespaceCache = new MapMaker()
77  			.softValues().makeMap();
78  
79  	/**
80  	 * vérifie qu'un type réel est compatible avec un type déclaré.
81  	 */
82  	private static boolean isCompatible(final Class<?> realType,
83  			final Class<?> declaredType) {
84  
85  		if (declaredType.isAssignableFrom(realType)) {
86  
87  			return true;
88  		}
89  
90  		if (declaredType.isPrimitive()) {
91  
92  			// TODO : voir si on peut faire mieux
93  
94  			final Field primitiveTypeField;
95  
96  			try {
97  
98  				primitiveTypeField = realType.getField("TYPE");
99  
100 			} catch (final NoSuchFieldException e) {
101 
102 				return false;
103 			}
104 
105 			final int modifiers = primitiveTypeField.getModifiers();
106 
107 			if (!Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)
108 					|| !Modifier.isPublic(modifiers)) {
109 
110 				return false;
111 			}
112 
113 			final Object primitiveType;
114 
115 			try {
116 
117 				primitiveType = primitiveTypeField.get(null);
118 
119 			} catch (final IllegalAccessException e) {
120 
121 				return false;
122 			}
123 
124 			return declaredType.equals(primitiveType);
125 		}
126 
127 		return false;
128 	}
129 
130 	/**
131 	 * vérifie qu'un nom de méthode est un nom de getter, en <tt>"getXxx()"</tt>
132 	 * , <tt>"hasXxx()"</tt> ou <tt>"isXxx()"</tt>, mais pas
133 	 * <tt>"isNullXxx()"</tt>.
134 	 */
135 	private static boolean isMethodNameGetter(final String methodName) {
136 		return methodName.startsWith("get") || methodName.startsWith("has")
137 				|| methodName.startsWith("is")
138 				&& !methodName.startsWith("isNull");
139 	}
140 
141 	private final Map<String, Object> cache = new HashMap<String, Object>();
142 
143 	private Set<String> methodNames = null;
144 
145 	private NamespaceMap namespaces;
146 
147 	private final XmlFieldNode node;
148 
149 	private final Class<?> type;
150 	private final XmlField xmlField;
151 
152 	/**
153 	 * T .
154 	 * 
155 	 * @param xmlField
156 	 *            l'objet reader, qui permet notamment de récupérer des
157 	 *            sous-champs.
158 	 * @param node
159 	 *            le nœud de l'objet Java.
160 	 * @param type
161 	 *            le type de l'objet Java.
162 	 * @param selector
163 	 *            selector object to use for that proxy.
164 	 * @param modifier
165 	 *            TODO
166 	 */
167 	public XmlFieldInvocationHandler(final XmlField xmlField,
168 			final XmlFieldNode node, final Class<?> type) {
169 
170 		this.xmlField = checkNotNull(xmlField, "xmlField");
171 		this.node = checkNotNull(node, "node");
172 		this.type = checkNotNull(type, "type");
173 
174 		String typeName = type.getName();
175 		// Load namespaces
176 
177 		namespaces = namespaceCache.get(typeName);
178 		if (namespaces == null) {
179 			this.namespaces = getResourceNamespaces(type);
180 			if (namespaces != null) {
181 				namespaceCache.put(typeName, namespaces);
182 			} else {
183 				namespaceCache.put(typeName, new NamespaceMap());
184 			}
185 		} else if (namespaces.isEmpty()) {
186 			namespaces = null;
187 		}
188 
189 		// Load method names
190 		methodNames = methodnamesCache.get(typeName);
191 		if (methodNames == null) {
192 			methodNames = new TreeSet<String>();
193 			for (final Method method : type.getMethods()) {
194 
195 				final String methodName = method.getName();
196 
197 				if (method.isAnnotationPresent(FieldXPath.class)
198 						&& isMethodNameGetter(methodName)) {
199 
200 					final Class<?>[] paramTypes = method.getParameterTypes();
201 
202 					if (paramTypes == null || paramTypes.length == 0) {
203 
204 						methodNames.add(methodName);
205 					}
206 				}
207 			}
208 			methodnamesCache.put(typeName, methodNames);
209 		}
210 	}
211 
212 	/**
213 	 * Add a bonded element add the end of a list located by the xpath.
214 	 * 
215 	 * @param root
216 	 *            root element
217 	 * @param xpath
218 	 *            xpath location to the element list
219 	 * @param type
220 	 *            element type to add
221 	 * @return the new element
222 	 * @throws XmlFieldXPathException
223 	 *             xpath exception
224 	 */
225 	public <T> T add(final Object root, final String xpath, final Class<T> type)
226 			throws XmlFieldXPathException {
227 		return add(XmlFieldUtils.getXmlFieldNode(root), xpath, type);
228 	}
229 
230 	/**
231 	 * Add a binded instance at the end of the nodes located by the xpath.
232 	 * 
233 	 * @param root
234 	 *            root element
235 	 * @param xpath
236 	 *            xpath location to the element list
237 	 * @param type
238 	 *            element type to add
239 	 * @return the new element
240 	 * @throws XmlFieldXPathException
241 	 *             xpath exception
242 	 */
243 	public <T> T add(final XmlFieldNode root, final String xpath,
244 			final Class<T> type) throws XmlFieldXPathException {
245 
246 		final XmlFieldNode node = addNode(root, xpath, type);
247 
248 		final XmlField binder = new XmlField();
249 
250 		return binder.nodeToObject(null, node, type);
251 	}
252 
253 	/**
254 	 * Add the specified binded node at the end of the xpath location.
255 	 * 
256 	 * @param root
257 	 *            root element
258 	 * @param fieldXPath
259 	 *            xpath location relative to the root element where we want to
260 	 *            add the new element
261 	 * @param type
262 	 *            element type to add
263 	 * @return the new node
264 	 * @throws XmlFieldXPathException
265 	 *             xpath exception
266 	 */
267 	public XmlFieldNode addNode(final XmlFieldNode root,
268 			final String fieldXPath, final Class<?> type)
269 			throws XmlFieldXPathException {
270 
271 		final NamespaceMap namespaces = getResourceNamespaces(type);
272 		final XmlFieldNode parentNode = addParentNodes(root, fieldXPath, type);
273 
274 		// Create requested node.
275 
276 		final String elementName = XPathUtils
277 				.getElementNameWithSelector(fieldXPath);
278 		final XmlFieldNode node = XmlFieldUtils.createComplexElement(
279 				namespaces, parentNode, elementName, null, xmlField);
280 
281 		return node;
282 	}
283 
284 	/**
285 	 * Add parent nodes to a specified node.
286 	 * 
287 	 * @param root
288 	 *            root element
289 	 * @param fieldXPath
290 	 * @param type
291 	 * @return
292 	 * @throws XmlFieldXPathException
293 	 */
294 	public XmlFieldNode addParentNodes(final XmlFieldNode root,
295 			final String fieldXPath, final Class<?> type)
296 			throws XmlFieldXPathException {
297 
298 		final NamespaceMap namespaces = getResourceNamespaces(type);
299 
300 		final XmlFieldNode parentNode;
301 
302 		// Check if this type of element already exists in the document.
303 		final XmlFieldNodeList nodeList = xmlField._getSelector()
304 				.selectXPathToNodeList(namespaces, fieldXPath, root);
305 
306 		if (nodeList != null && nodeList.getLength() > 0) {
307 			// Siblings exist. We will add item to their parent.
308 			if (nodeList.item(0).getNodeType() == XmlFieldNode.ATTRIBUTE_NODE) {
309 				String xPathElement = XPathUtils.getElementXPath(fieldXPath);
310 				if (xPathElement != null) {
311 					parentNode = xmlField._getSelector().selectXPathToNode(
312 							namespaces, xPathElement, root);
313 				} else {
314 					parentNode = root;
315 				}
316 			} else {
317 				if (".".equals(fieldXPath)) {
318 					parentNode = nodeList.item(0);
319 				} else {
320 					parentNode = nodeList.item(0).getParentNode();
321 				}
322 			}
323 
324 		} else {
325 			// Sibling do not exist. We need to create the appropriate node
326 			// hierarchy :
327 
328 			// Do we even need a hierarchy ?
329 			if (!fieldXPath.contains("/")) {
330 				// We can create this node directly in the parent.
331 				parentNode = root;
332 			} else {
333 				// Get hierarchy
334 				final List<String> elementsToCreate = new ArrayList<String>();
335 				XmlFieldNode node;
336 
337 				// Loop over the XPath hierarchy
338 				for (String xPathBuffer = fieldXPath;;) {
339 					// Remove field name. Keep only parents
340 					xPathBuffer = substringBeforeLast(xPathBuffer, "/");
341 
342 					// Ensure xpath was valid.
343 					if (isBlank(xPathBuffer)) {
344 						throw new IllegalStateException(
345 								"Unable to create child in list with XPath: "
346 										+ fieldXPath);
347 					}
348 
349 					// If parent node already exists, we can create the element
350 					// directly.
351 					final XmlFieldNodeList nList = xmlField._getSelector()
352 							.selectXPathToNodeList(namespaces, xPathBuffer,
353 									root);
354 					if (nList != null && nList.getLength() != 0) {
355 						node = nList.item(0);
356 						break; // Escape from the loop and go to node creation.
357 					}
358 
359 					// We have not parent, we need to create a node.
360 					final String elementName;
361 					if (xPathBuffer.contains("/")) {
362 						// Keep only parent name
363 						elementName = substringAfterLast(xPathBuffer, "/");
364 					} else {
365 						// This was the last element.
366 						elementName = xPathBuffer;
367 						xPathBuffer = null;
368 					}
369 
370 					// Remenber we have to create elementName
371 					elementsToCreate.add(0, elementName);
372 
373 					// If that was the last parent, exit the loop.
374 					if (xPathBuffer == null) {
375 						node = root;
376 						break;
377 					}
378 				}
379 
380 				// Create all required elements
381 				for (final String elementName : elementsToCreate) {
382 					final XmlFieldNode n = XmlFieldUtils.createComplexElement(
383 							namespaces, node, elementName, null, xmlField);
384 					node = n;
385 				}
386 
387 				// The request node will be attached to the last node created
388 				// (the parent).
389 				parentNode = node;
390 			}
391 		}
392 
393 		return parentNode;
394 	}
395 
396 	/**
397 	 * Is there a value in cache associated to the specified invoked method?
398 	 * 
399 	 * @param methodName
400 	 *            invoked method name.
401 	 * @return <code>true</code> if a value exists, <code>false</code> otherwise
402 	 */
403 	private boolean cacheExists(final String methodName) {
404 		return cache.containsKey(getCacheKey(methodName));
405 	}
406 
407 	/**
408 	 * invoque une méthode "<tt>addToXxx()</tt>".
409 	 */
410 	private Object doAddTo(final Object proxy, final Method method,
411 			final Class<?> type) throws Exception {
412 		removeFromCache(method);
413 
414 		final String fieldXPath = getFieldXPath(method);
415 
416 		return add(proxy, fieldXPath, type);
417 	}
418 
419 	/**
420 	 * invoque une méthode "<tt>addToXxx(Xxx.class)</tt>".
421 	 * 
422 	 * <p>
423 	 * La méthode récupère les champs FieldXPath et ExplicitCollection(avec les
424 	 * associations) de la méthode "get" associée à la méthode "addTo". Elle
425 	 * cherche ensuite un match entre une Association et le type d'objet à
426 	 * ajouter.
427 	 * </p>
428 	 * 
429 	 */
430 	private Object doAddTo(final Object proxy, final Method method,
431 			final Object objectType) throws Exception {
432 		removeFromCache(method);
433 
434 		final String fieldXPath = getFieldXPath(method);
435 
436 		final Class<?> objectClass = (Class<?>) objectType;
437 
438 		Map<String, Class<?>> explicitCollectionAssociations = getExplicitCollections(method);
439 
440 		Set<String> keysAssociations = explicitCollectionAssociations.keySet();
441 
442 		String specificFieldXPath = "";
443 
444 		for (String key : keysAssociations) {
445 			if (objectClass.isAssignableFrom(explicitCollectionAssociations
446 					.get(key))) {
447 				specificFieldXPath = fieldXPath.replace("*", key);
448 			}
449 		}
450 
451 		if (StringUtils.isEmpty(specificFieldXPath)) {
452 			throw new XmlFieldXPathException("Aucune @Association du type "
453 					+ objectClass.getName() + " n'a été définie.");
454 		}
455 
456 		return add(proxy, specificFieldXPath, objectClass);
457 	}
458 
459 	/**
460 	 * invoque la méthode "<tt>equals(Object)</tt>".
461 	 * 
462 	 * @throws XmlFieldXPathException
463 	 */
464 	private Object doEquals(final Object proxy, final Object ob)
465 			throws IllegalAccessException, InvocationTargetException,
466 			NoSuchMethodException, XmlFieldXPathException {
467 
468 		if (ob == null) {
469 			return false;
470 		}
471 
472 		final Class<?> proxyClass = proxy.getClass();
473 
474 		final Class<?> obClass = ob.getClass();
475 
476 		if (!proxyClass.equals(obClass)) {
477 			return false;
478 		}
479 
480 		for (final String methodName : methodNames) {
481 
482 			if (!isMethodNameGetter(methodName)) {
483 
484 				continue;
485 			}
486 
487 			final Object value = getMethodValue(methodName);
488 
489 			final Object obValue = obClass.getMethod(methodName).invoke(ob);
490 
491 			if (value == null && obValue == null) {
492 				continue;
493 			}
494 
495 			if (value == null || obValue == null) {
496 				return false;
497 			}
498 
499 			if (!value.equals(obValue)) {
500 				return false;
501 			}
502 		}
503 
504 		return true;
505 	}
506 
507 	/**
508 	 * invoque une méthode "<tt>getXxx()</tt>".
509 	 * 
510 	 * @throws XmlFieldXPathException
511 	 */
512 	private Object doGet(final String methodName) throws NoSuchMethodException,
513 			XmlFieldXPathException {
514 		if (xmlField.isGetterCache() && cacheExists(methodName)) {
515 			return getFromCache(methodName);
516 		}
517 
518 		final Object value = getMethodValue(methodName);
519 
520 		if (value == null) {
521 			setIntoCache(methodName, value);
522 			return null;
523 		}
524 
525 		final Class<?> returnType = type.getMethod(methodName).getReturnType();
526 
527 		final Class<? extends Object> valueClass = value.getClass();
528 
529 		if (!isCompatible(valueClass, returnType)) {
530 
531 			throw new RuntimeException("Expected: " + returnType.getName()
532 					+ " on method " + methodName
533 					+ "(), but stored value has type: " + valueClass.getName()
534 					+ " for class: " + type.getName());
535 		}
536 		setIntoCache(methodName, value);
537 		return value;
538 	}
539 
540 	/**
541 	 * invoque la méthode "<tt>hashCode()</tt>".
542 	 * 
543 	 * @throws XmlFieldXPathException
544 	 */
545 	private Object doHashCode() throws XmlFieldXPathException {
546 		int hash = 0;
547 
548 		for (final String methodName : methodNames) {
549 
550 			if (!isMethodNameGetter(methodName)) {
551 
552 				continue;
553 			}
554 
555 			final Object value = getMethodValue(methodName);
556 
557 			hash *= 5;
558 
559 			hash += methodName.hashCode();
560 
561 			hash *= 3;
562 
563 			if (value != null) {
564 
565 				hash += value.hashCode();
566 			}
567 		}
568 
569 		return hash;
570 	}
571 
572 	/**
573 	 * invoque une méthode "<tt>isNullXxx()</tt>".
574 	 * 
575 	 * @throws XmlFieldXPathException
576 	 */
577 	private Object doIsNull(final String methodName)
578 			throws XmlFieldXPathException {
579 		if (xmlField.isGetterCache() && cacheExists(methodName)) {
580 			return getFromCache(methodName);
581 		}
582 
583 		final Object rawValue = getMethodDomValue("get"
584 				+ methodName.substring(6));
585 
586 		final Boolean isNull;
587 		if (rawValue instanceof XmlFieldNode) {
588 			isNull = ((XmlFieldNode) rawValue).getNode() == null;
589 			setIntoCache(methodName, isNull);
590 			return isNull;
591 		}
592 		isNull = rawValue == null;
593 		setIntoCache(methodName, isNull);
594 		return isNull;
595 	}
596 
597 	/**
598 	 * invoque une méthode "<tt>addToXxx()</tt>".
599 	 */
600 	private Object doNew(final Object proxy, final Method method,
601 			final Class<?> type) throws Exception {
602 		removeFromCache(method);
603 
604 		final String fieldXPath = getFieldXPath(method);
605 
606 		return add(proxy, fieldXPath, type);
607 	}
608 
609 	/**
610 	 * Remove object from xml.
611 	 */
612 	private Object doRemoveFrom(Method method, Object obj) throws Exception {
613 		removeFromCache(method);
614 
615 		XmlFieldUtils.remove(obj, xmlField);
616 
617 		return null;
618 	}
619 
620 	/**
621 	 * Invoke method "<tt>setXxx(Object obj)</tt>".
622 	 * <p>
623 	 * Behavior :
624 	 * <ul>
625 	 * <li>if obj == null -> remove node</li>
626 	 * <li>if obj == null -> remove node</li>
627 	 * 
628 	 * </ul>
629 	 * 
630 	 * @throws XmlFieldXPathException
631 	 */
632 	private Object doSet(final Method method, final Object value)
633 			throws XmlFieldXPathException {
634 		removeFromCache(method);
635 
636 		final String fieldXPath = getFieldXPath(method);
637 
638 		final XmlFieldNode contextNode;
639 
640 		XmlFieldNode n;
641 
642 		if (value == null || value instanceof Object[]
643 				&& ((Object[]) value).length == 0) {
644 			// Value is null. We have to delete the current value.
645 			n = xmlField._getSelector().selectXPathToNode(namespaces,
646 					fieldXPath, node);
647 			if (n == null) {
648 				// No node was matching the Xpath. Value is already null
649 				if (logger.isDebugEnabled()) {
650 					logger.debug("value null, node null");
651 				}
652 			} else if (n.getNodeType() == XmlFieldNode.ATTRIBUTE_NODE) {
653 				final String attributeName = n.getNodeName();
654 				n = node;
655 				if (!n.hasAttributes()) {
656 					// the resource is not the node who contains the attributes
657 					String elementXPath = XPathUtils
658 							.getElementXPath(fieldXPath);
659 					n = xmlField._getSelector().selectXPathToNode(namespaces,
660 							elementXPath, node);
661 				}
662 				xmlField._getModifier().removeAttribute(n, attributeName);
663 			} else {
664 				// Remove all matching nodes.
665 				XmlFieldNodeList nodesToRemove = xmlField._getSelector()
666 						.selectXPathToNodeList(namespaces, fieldXPath, node);
667 				xmlField._getModifier().removeChildren(nodesToRemove);
668 			}
669 
670 		} else {
671 			// We have to set a value.
672 			// First : create all parent nodes.
673 			contextNode = addParentNodes(node, fieldXPath, type);
674 
675 			// Ensure we have an array to loop on. If single item, convert to
676 			// array.
677 			Object[] items = null;
678 			if (value instanceof Object[]) {
679 				items = (Object[]) value;
680 
681 				if (!(items[0] instanceof XmlFieldObject)) {
682 					if (logger.isWarnEnabled()) {
683 						logger.warn("You are using "
684 								+ type.getName()
685 								+ "#"
686 								+ method.getName()
687 								+ "()"
688 								+ " with an array of a Java primitive type."
689 								+ " This usage is not able to ensure that additionnal data (such as org.xmlfield.tests.attribute)"
690 								+ " are not erased during call. Please use the corresponding xml-field type implementation instead."
691 								+ " String -> XmlString for instance.");
692 					}
693 				}
694 			} else {
695 				items = new Object[] { value };
696 			}
697 
698 			// Get all matching nodes
699 			XmlFieldNodeList nodeXmlFieldList = xmlField._getSelector()
700 					.selectXPathToNodeList(namespaces,
701 							XPathUtils.getElementNameWithSelector(fieldXPath),
702 							contextNode);
703 
704 			// Loop on new values
705 			XmlFieldNode currentNode = null;
706 			Object currentValue = null;
707 			String stringValue = null;
708 
709 			// Loop 1 : reorder
710 			XmlFieldNode valueNode = null;
711 			boolean listUpdated = false;
712 			for (int i = 0; i < items.length; i++) {
713 				// Get current existing node and value.
714 				// Note: currentNode may be null, node will be created.
715 				currentNode = nodeXmlFieldList.item(i);
716 				currentValue = items[i];
717 
718 				if (currentValue instanceof XmlFieldObject) {
719 					valueNode = ((XmlFieldObject) currentValue).toNode();
720 					xmlField._getModifier().insertBefore(
721 							valueNode.getParentNode(), valueNode, currentNode);
722 					listUpdated = true;
723 				}
724 			}
725 
726 			// Update list if necessary
727 			if (listUpdated) {
728 				nodeXmlFieldList = xmlField
729 						._getSelector()
730 						.selectXPathToNodeList(
731 								namespaces,
732 								XPathUtils
733 										.getElementNameWithSelector(fieldXPath),
734 								contextNode);
735 			}
736 
737 			// Java bug : we have to call item() once with a valid node to make
738 			// this method work again.
739 			// see : http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6333993
740 			// currentNode = nodeList.item(0);
741 
742 			// Loop 2 :Assign values
743 			for (int i = 0; i < items.length; i++) {
744 				// Get current existing node and value.
745 				// Note: currentNode may be null, node will be created.
746 				currentNode = nodeXmlFieldList.item(i);
747 				currentValue = items[i];
748 
749 				if (currentValue instanceof XmlFieldObject) {
750 					// Values are already set by setter methods
751 					continue;
752 				}
753 
754 				if (currentValue instanceof DateTime) {
755 
756 					final String pattern = getFieldFormat(method);
757 
758 					DateTimeFormatter formatter = ISODateTimeFormat.dateTime();
759 					if (pattern != null) {
760 						formatter = DateTimeFormat.forPattern(pattern)
761 								.withChronology(ISOChronology.getInstanceUTC());
762 					}
763 					final DateTime d = (DateTime) currentValue;
764 					stringValue = d.toString(formatter);
765 				} else {
766 					stringValue = currentValue.toString();
767 				}
768 
769 				if (currentNode == null) {
770 					// Node didn't exist : create new node
771 					XmlFieldUtils.createComplexElement(namespaces, contextNode,
772 							XPathUtils.getElementNameWithSelector(fieldXPath),
773 							stringValue, xmlField);
774 				} else {
775 					// Node exists : set value.
776 					currentNode.setTextContent(stringValue);
777 				}
778 			}
779 
780 			// Remove additional items
781 			for (int i = items.length; i < nodeXmlFieldList.getLength(); i++) {
782 				currentNode = nodeXmlFieldList.item(i);
783 				xmlField._getModifier().removeChild(
784 						currentNode.getParentNode(), currentNode);
785 			}
786 		}
787 
788 		return null;
789 	}
790 
791 	/**
792 	 * invoque une méthode "<tt>sizeOfXxx()</tt>".
793 	 * 
794 	 * @throws XmlFieldXPathException
795 	 */
796 	private Object doSizeOf(final String methodName)
797 			throws XmlFieldXPathException {
798 
799 		final Object value = getMethodValue("get" + methodName.substring(6));
800 
801 		if (value == null) {
802 
803 			return 0;
804 		}
805 
806 		if (value.getClass().isArray()) {
807 
808 			return ((Object[]) value).length;
809 		}
810 
811 		return 1;
812 	}
813 
814 	/**
815 	 * invoque la méthode "<tt>toString()</tt>".
816 	 * 
817 	 * @throws XmlFieldXPathException
818 	 */
819 	private Object doToString() throws XmlFieldXPathException {
820 
821 		final StringBuilder sb = new StringBuilder("{");
822 
823 		boolean start = true;
824 
825 		for (final String methodName : methodNames) {
826 
827 			if (!isMethodNameGetter(methodName)) {
828 
829 				continue;
830 			}
831 
832 			final Object value = getMethodValue(methodName);
833 
834 			if (value == null) {
835 
836 				continue;
837 			}
838 
839 			if (start) {
840 
841 				start = false;
842 
843 			} else {
844 
845 				sb.append(", ");
846 			}
847 
848 			sb.append(Character.toLowerCase(methodName.charAt(3)));
849 			sb.append(methodName.substring(4));
850 			sb.append(": ");
851 
852 			if (value.getClass().isArray()) {
853 				sb.append(ArrayUtils.toString(value));
854 			} else {
855 				sb.append(value.toString());
856 			}
857 		}
858 
859 		sb.append("}");
860 
861 		return sb.toString();
862 	}
863 
864 	/**
865 	 * Builds cache key from method name.<br/>
866 	 * Method name can be either getter, setters or others. Normalize all these
867 	 * names.
868 	 * 
869 	 * @param methodName
870 	 *            method name
871 	 * @return cache key
872 	 */
873 	private String getCacheKey(final String methodName) {
874 		final String[] prefixes = new String[] { "get", "set", "addTo",
875 				"removeFrom", "new", "is" };
876 		for (String p : prefixes) {
877 			if (methodName.startsWith(p)) {
878 				return methodName.substring(p.length());
879 			}
880 		}
881 		throw new XmlFieldTechnicalException("Methode non cacheable: "
882 				+ methodName);
883 	}
884 
885 	/**
886 	 * Retrieves a value from cache.
887 	 * 
888 	 * @param methodName
889 	 *            invoked method name.
890 	 * @return value contained in cache.
891 	 */
892 	private Object getFromCache(final String methodName) {
893 		return cache.get(getCacheKey(methodName));
894 	}
895 
896 	private Method getMethodByName(final String methodName) {
897 
898 		for (final Method method : type.getMethods()) {
899 
900 			if (methodName.equals(method.getName())) {
901 
902 				final Class<?>[] paramTypes = method.getParameterTypes();
903 
904 				if (paramTypes == null || paramTypes.length == 0) {
905 
906 					return method;
907 				}
908 			}
909 		}
910 
911 		return null;
912 	}
913 
914 	private Object getMethodDomValue(final String methodName)
915 			throws XmlFieldXPathException {
916 
917 		Method method = getMethodByName(methodName);
918 
919 		if (method == null) {
920 			return null;
921 		}
922 
923 		final String fieldXPath = getFieldXPath(method);
924 
925 		if (fieldXPath == null) {
926 			return null;
927 		}
928 
929 		final Object value;
930 
931 		final Class<?> xpathType = getFieldXPathType(method);
932 
933 		if (Number.class.equals(xpathType)) {
934 
935 			final Double d = xmlField._getSelector().selectXPathToNumber(
936 					namespaces, fieldXPath, node);
937 
938 			final double v = d == null ? 0 : d.doubleValue();
939 
940 			value = v;
941 
942 		} else if (String.class.equals(xpathType)) {
943 
944 			final String s = xmlField._getSelector().selectXPathToString(
945 					namespaces, fieldXPath, node);
946 
947 			value = s;
948 
949 		} else if (Boolean.class.equals(xpathType)) {
950 
951 			final Boolean b = xmlField._getSelector().selectXPathToBoolean(
952 					namespaces, fieldXPath, node);
953 
954 			final boolean v = b == null ? false : b.booleanValue();
955 
956 			value = v;
957 
958 		} else {
959 
960 			final XmlFieldNode n = xmlField._getSelector().selectXPathToNode(
961 					namespaces, fieldXPath, node);
962 
963 			value = n;
964 		}
965 
966 		return value;
967 	}
968 
969 	/**
970 	 * récupère de façon dynamique la valeur d'un champ repéré par une
971 	 * expression XPath, donnée en annotation d'une méthode.
972 	 * 
973 	 * @param methodName
974 	 *            le nom de la méthode.
975 	 * @return la valeur du champ repéré par le XPath donné en annotation de la
976 	 *         méthode, ou <tt>null</tt> si le champ n'existe pas.
977 	 * @throws XmlFieldXPathException
978 	 */
979 	private Object getMethodValue(final String methodName)
980 			throws XmlFieldXPathException {
981 
982 		final Object domValue = getMethodDomValue(methodName);
983 
984 		final Method method = getMethodByName(methodName);
985 
986 		final Class<?> fieldType = method.getReturnType();
987 
988 		final String fieldXPath = getFieldXPath(method);
989 
990 		// research Explicit collections
991 		final Map<String, Class<?>> explicitAssociations = getExplicitCollections(method);
992 
993 		final Object value;
994 
995 		if (String.class.equals(fieldType)) {
996 
997 			value = parseString(domValue);
998 
999 		} else if (int.class.equals(fieldType)) {
1000 
1001 			value = parseInt(methodName, domValue, fieldXPath);
1002 
1003 		} else if (long.class.equals(fieldType)) {
1004 
1005 			value = parseLong(methodName, domValue, fieldXPath);
1006 
1007 		} else if (short.class.equals(fieldType)) {
1008 
1009 			value = parseShort(methodName, domValue, fieldXPath);
1010 
1011 		} else if (float.class.equals(fieldType)) {
1012 
1013 			value = parseFloat(methodName, domValue, fieldXPath);
1014 
1015 		} else if (double.class.equals(fieldType)) {
1016 
1017 			value = parseDouble(methodName, domValue, fieldXPath);
1018 
1019 		} else if (boolean.class.equals(fieldType)) {
1020 
1021 			value = parseBoolean(methodName, domValue, fieldXPath);
1022 
1023 		} else if (Number.class.isAssignableFrom(fieldType)) {
1024 
1025 			value = parseNumber(methodName, fieldType, domValue, fieldXPath);
1026 
1027 		} else if (DateTime.class.equals(fieldType)) {
1028 
1029 			value = parseDateTime(methodName, domValue, method, fieldXPath);
1030 
1031 		} else if (fieldType.isArray() && explicitAssociations.size() != 0) {
1032 			// case of an explicit collection
1033 			value = xmlField.nodeToExplicitArray(fieldXPath, node,
1034 					explicitAssociations);
1035 
1036 		} else if (fieldType.isArray()) {
1037 			// cas nominal
1038 			value = xmlField.nodeToArray(fieldXPath, node,
1039 					fieldType.getComponentType());
1040 
1041 		} else if (fieldType.isEnum()) {
1042 			value = parseEnum(domValue, (Class<? extends Enum>) fieldType);
1043 		} else if (isXmlFieldInterface(fieldType)) {
1044 			value = xmlField.nodeToObject(fieldXPath, node, fieldType);
1045 
1046 		} else {
1047 
1048 			throw new NotImplementedException("fieldType: " + type
1049 					+ ", method: " + method);
1050 		}
1051 
1052 		return value;
1053 	}
1054 
1055 	public XmlFieldNode getNode() {
1056 		return node;
1057 	}
1058 
1059 	@Override
1060 	public Object invoke(final Object proxy, final Method method,
1061 			final Object[] args) throws Throwable {
1062 
1063 		final String methodName = method.getName();
1064 
1065 		final boolean noArg = args == null || args.length == 0;
1066 
1067 		if ("toString".equals(methodName) && noArg) {
1068 
1069 			return doToString();
1070 
1071 		} else if ("hashCode".equals(methodName) && noArg) {
1072 
1073 			return doHashCode();
1074 
1075 		} else if ("equals".equals(methodName) && !noArg && args.length == 1) {
1076 
1077 			return doEquals(proxy, args[0]);
1078 
1079 		} else if ("toNode".equals(methodName) && noArg) {
1080 
1081 			return node;
1082 
1083 		} else if (isMethodNameGetter(methodName) && noArg) {
1084 
1085 			return doGet(methodName);
1086 
1087 		} else if (methodName.startsWith("set") && !noArg && args.length == 1) {
1088 
1089 			return doSet(method, args[0]);
1090 
1091 		} else if (methodName.startsWith("addTo") && noArg) {
1092 
1093 			return doAddTo(proxy, method, method.getReturnType());
1094 
1095 		} else if (methodName.startsWith("addTo") && !noArg) {
1096 
1097 			return doAddTo(proxy, method, args[0]);
1098 
1099 		} else if (methodName.startsWith("new") && noArg) {
1100 			Object presentObject = doGet(methodName);
1101 			if (presentObject != null) {
1102 				return presentObject;
1103 			}
1104 			return doNew(proxy, method, method.getReturnType());
1105 
1106 		} else if (methodName.startsWith("isNull") && noArg) {
1107 
1108 			return doIsNull(methodName);
1109 
1110 		} else if (methodName.startsWith("sizeOf") && noArg) {
1111 
1112 			return doSizeOf(methodName);
1113 		} else if (methodName.startsWith("removeFrom") && args.length == 1) {
1114 
1115 			return doRemoveFrom(method, args[0]);
1116 		}
1117 
1118 		return null;
1119 	}
1120 
1121 	private boolean isXmlFieldInterface(final Class<?> fieldType) {
1122 		return XmlFieldUtils.getResourceXPath(fieldType) != null;
1123 	}
1124 
1125 	private boolean parseBoolean(final String methodName,
1126 			final Object domValue, final String fieldXPath) {
1127 		if (Boolean.class.isInstance(domValue)) {
1128 			return (Boolean) domValue;
1129 		}
1130 		if (domValue instanceof XmlFieldNode) {
1131 			String textContent = ((XmlFieldNode) domValue).getTextContent();
1132 			try {
1133 				return Boolean.parseBoolean(textContent);
1134 			} catch (final RuntimeException e) {
1135 				logger.error("Cannot parse boolean: " + textContent
1136 						+ //
1137 						", xpath=" + fieldXPath + ", method=" + methodName
1138 						+ "()");
1139 				logger.warn("Cannot parse boolean (details) : ", e);
1140 			}
1141 		}
1142 
1143 		return false;
1144 	}
1145 
1146 	private DateTime parseDateTime(final String methodName,
1147 			final Object domValue, final Method method, final String fieldXPath) {
1148 
1149 		final XmlFieldNode n = (XmlFieldNode) domValue;
1150 
1151 		if (n != null) {
1152 
1153 			final String textContent = n.getTextContent();
1154 
1155 			final String pattern = getFieldFormat(method);
1156 
1157 			DateTimeFormatter formatter = ISODateTimeFormat.dateTime();
1158 			if (pattern != null) {
1159 				formatter = DateTimeFormat.forPattern(pattern).withChronology(
1160 						ISOChronology.getInstanceUTC());
1161 			}
1162 
1163 			try {
1164 
1165 				return formatter.parseDateTime(textContent);
1166 
1167 			} catch (final RuntimeException e) {
1168 
1169 				e.printStackTrace();
1170 
1171 				logger.error("Cannot parse DateTime: " + textContent
1172 						+ ", xpath=" + fieldXPath + ", method=" + methodName
1173 						+ "()");
1174 				logger.warn("Cannot parse DateTime (details) : ", e);
1175 			}
1176 		}
1177 
1178 		return null;
1179 	}
1180 
1181 	private double parseDouble(final String methodName, final Object domValue,
1182 			final String fieldXPath) {
1183 
1184 		if (Double.class.isInstance(domValue)) {
1185 
1186 			return (Double) domValue;
1187 
1188 		} else if (Integer.class.isInstance(domValue)) {
1189 
1190 			return ((Integer) domValue).intValue();
1191 
1192 		} else if (Long.class.isInstance(domValue)) {
1193 
1194 			return ((Long) domValue).longValue();
1195 
1196 		} else if (Short.class.isInstance(domValue)) {
1197 
1198 			return ((Short) domValue).shortValue();
1199 
1200 		} else if (Float.class.isInstance(domValue)) {
1201 
1202 			return ((Float) domValue).floatValue();
1203 
1204 		} else {
1205 
1206 			final XmlFieldNode n = (XmlFieldNode) domValue;
1207 
1208 			if (n != null) {
1209 
1210 				final String textContent = n.getTextContent();
1211 
1212 				try {
1213 
1214 					return Double.parseDouble(textContent);
1215 
1216 				} catch (final RuntimeException e) {
1217 
1218 					logger.error("Cannot parse double: " + textContent
1219 							+ ", xpath=" + fieldXPath + ", method="
1220 							+ methodName + "()");
1221 					logger.warn("Cannot parse double (details) : ", e);
1222 				}
1223 			}
1224 		}
1225 
1226 		return 0;
1227 	}
1228 
1229 	private Object parseEnum(final Object domValue,
1230 			final Class<? extends Enum> fieldType) {
1231 		String s = parseString(domValue);
1232 		if (StringUtils.isEmpty(s)) {
1233 			return null;
1234 		}
1235 		return Enum.valueOf(fieldType, s);
1236 	}
1237 
1238 	private float parseFloat(final String methodName, final Object domValue,
1239 			final String fieldXPath) {
1240 		if (domValue instanceof Number) {
1241 			return ((Number) domValue).floatValue();
1242 		}
1243 		if (domValue instanceof XmlFieldNode) {
1244 			String textContent = ((XmlFieldNode) domValue).getTextContent();
1245 			try {
1246 				return Float.parseFloat(textContent);
1247 			} catch (final RuntimeException e) {
1248 				logger.error("Cannot parse float: " + textContent
1249 						+ //
1250 						", xpath=" + fieldXPath + ", method=" + methodName
1251 						+ "()");
1252 				logger.warn("Cannot parse float (details) : ", e);
1253 			}
1254 		}
1255 		return 0;
1256 	}
1257 
1258 	private int parseInt(final String methodName, final Object domValue,
1259 			final String fieldXPath) {
1260 		if (domValue instanceof Number) {
1261 			return ((Number) domValue).intValue();
1262 		}
1263 		if (domValue instanceof XmlFieldNode) {
1264 			String textContent = ((XmlFieldNode) domValue).getTextContent();
1265 			try {
1266 				return Integer.parseInt(textContent);
1267 			} catch (final RuntimeException e) {
1268 				logger.error("Cannot parse int: " + textContent
1269 						+ //
1270 						", xpath=" + fieldXPath + ", method=" + methodName
1271 						+ "()");
1272 				logger.warn("Cannot parse int (details) : ", e);
1273 			}
1274 		}
1275 
1276 		return 0;
1277 	}
1278 
1279 	private long parseLong(final String methodName, final Object domValue,
1280 			final String fieldXPath) {
1281 
1282 		if (domValue instanceof Number) {
1283 			return ((Number) domValue).longValue();
1284 		}
1285 
1286 		if (domValue instanceof XmlFieldNode) {
1287 			String textContent = ((XmlFieldNode) domValue).getTextContent();
1288 			try {
1289 				return Long.parseLong(textContent);
1290 			} catch (final RuntimeException e) {
1291 				logger.error("Cannot parse long: " + textContent + ", xpath="
1292 						+ fieldXPath + //
1293 						", method=" + methodName + "()");
1294 				logger.warn("Cannot parse long (details) : ", e);
1295 			}
1296 		}
1297 
1298 		return 0;
1299 	}
1300 
1301 	private Number parseNumber(String methodName, Class<?> fieldType,
1302 			Object domValue, String fieldXPath) {
1303 
1304 		if (Number.class.isInstance(domValue)) {
1305 			if (fieldType == Byte.class) {
1306 				return ((Number) domValue).byteValue();
1307 			}
1308 			if (fieldType == Double.class) {
1309 				return ((Number) domValue).doubleValue();
1310 			}
1311 			if (fieldType == Float.class) {
1312 				return ((Number) domValue).floatValue();
1313 			}
1314 			if (fieldType == Short.class) {
1315 				return ((Number) domValue).shortValue();
1316 			}
1317 
1318 			if (fieldType == Integer.class) {
1319 				return ((Number) domValue).intValue();
1320 			}
1321 			if (fieldType == Long.class) {
1322 				return ((Number) domValue).longValue();
1323 			}
1324 		}
1325 
1326 		if (domValue instanceof XmlFieldNode) {
1327 			String textContent = ((XmlFieldNode) domValue).getTextContent();
1328 			try {
1329 				if (fieldType == Byte.class) {
1330 					return Byte.parseByte(textContent);
1331 				}
1332 				if (fieldType == Double.class) {
1333 					return Double.parseDouble(textContent);
1334 				}
1335 				if (fieldType == Float.class) {
1336 					return Float.parseFloat(textContent);
1337 				}
1338 				if (fieldType == Short.class) {
1339 					return Short.parseShort(textContent);
1340 				}
1341 				if (fieldType == Integer.class) {
1342 					return Integer.parseInt(textContent);
1343 				}
1344 				if (fieldType == Long.class) {
1345 					return Long.parseLong(textContent);
1346 				}
1347 			} catch (final NumberFormatException e) {
1348 				logger.error(
1349 						"Cannot parse Number: {} , xpath= {} , method= {}()",
1350 						new Object[] { textContent, fieldXPath, methodName, e });
1351 			}
1352 		}
1353 		return null;
1354 	}
1355 
1356 	private short parseShort(final String methodName, final Object domValue,
1357 			final String fieldXPath) {
1358 
1359 		if (domValue instanceof Number) {
1360 			return ((Number) domValue).shortValue();
1361 		}
1362 		if (domValue instanceof XmlFieldNode) {
1363 
1364 			XmlFieldNode n = (XmlFieldNode) domValue;
1365 			String textContent = n.getTextContent();
1366 
1367 			try {
1368 				return Short.parseShort(textContent);
1369 			} catch (final RuntimeException e) {
1370 				logger.error("Cannot parse short: " + textContent + ", xpath="
1371 						+ fieldXPath + //
1372 						", method=" + methodName + "()");
1373 				logger.warn("Cannot parse short (details) : ", e);
1374 			}
1375 		}
1376 
1377 		return 0;
1378 	}
1379 
1380 	private String parseString(final Object domValue) {
1381 		if (domValue instanceof String) {
1382 			return (String) domValue;
1383 		}
1384 		if (domValue instanceof XmlFieldNode) {
1385 			return ((XmlFieldNode) domValue).getTextContent();
1386 		}
1387 		return null;
1388 	}
1389 
1390 	/**
1391 	 * Removes any value contained in cache for the specified invoked method.
1392 	 * 
1393 	 * @param method
1394 	 *            invoked method.
1395 	 */
1396 	private void removeFromCache(final Method method) {
1397 		removeFromCache(method.getName());
1398 	}
1399 
1400 	/**
1401 	 * Removes any value contained in cache for the specified invoked method
1402 	 * name.
1403 	 * 
1404 	 * @param methodName
1405 	 *            invoked method name.
1406 	 */
1407 	private void removeFromCache(final String methodName) {
1408 		cache.remove(getCacheKey(methodName));
1409 	}
1410 
1411 	/**
1412 	 * Put a value into cache.
1413 	 * 
1414 	 * @param methodName
1415 	 *            method name
1416 	 * @param value
1417 	 *            value to put
1418 	 */
1419 	private void setIntoCache(final String methodName, final Object value) {
1420 		cache.put(getCacheKey(methodName), value);
1421 	}
1422 }