View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2006 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  package ch.elca.el4j.services.xmlmerge.action;
18  
19  import java.util.ArrayList;
20  import java.util.LinkedHashMap;
21  import java.util.List;
22  
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  import org.jdom.Attribute;
26  import org.jdom.Comment;
27  import org.jdom.Content;
28  import org.jdom.Element;
29  import org.jdom.Text;
30  
31  import ch.elca.el4j.services.xmlmerge.AbstractXmlMergeException;
32  import ch.elca.el4j.services.xmlmerge.Action;
33  import ch.elca.el4j.services.xmlmerge.DocumentException;
34  import ch.elca.el4j.services.xmlmerge.Mapper;
35  import ch.elca.el4j.services.xmlmerge.Matcher;
36  import ch.elca.el4j.services.xmlmerge.MergeAction;
37  
38  /**
39   * Merge implementation traversing parallelly both element contents. Works when
40   * contents are in the same order in both elements.
41   *
42   * @svnLink $Revision: 3882 $;$Date: 2009-08-04 15:24:14 +0200 (Di, 04. Aug 2009) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/xml_merge/common/src/main/java/ch/elca/el4j/services/xmlmerge/action/OrderedMergeAction.java $
43   *
44   * @author Laurent Bovet (LBO)
45   * @author Alex Mathey (AMA)
46   */
47  public class OrderedMergeAction extends AbstractMergeAction {
48  
49  	/**
50  	 * Private logger.
51  	 */
52  	private static Logger s_logger
53  		= LoggerFactory.getLogger(OrderedMergeAction.class);
54  	
55  	/**
56  	 * {@inheritDoc}
57  	 */
58  	public void perform(Element originalElement, Element patchElement,
59  		Element outputParentElement) throws AbstractXmlMergeException {
60  
61  		s_logger.debug("Merging: " + originalElement + "(List 1) and "
62  			+ patchElement + "(List 2)");
63  
64  		Mapper mapper = (Mapper) m_mapperFactory.getOperation(originalElement,
65  			patchElement);
66  
67  		if (originalElement == null) {
68  			outputParentElement.addContent(mapper.map(patchElement));
69  		} else if (patchElement == null) {
70  			outputParentElement.addContent((Content) originalElement.clone());
71  		} else {
72  
73  			Element workingElement = new Element(originalElement.getName(),
74  				originalElement.getNamespacePrefix(), originalElement
75  					.getNamespaceURI());
76  			addAttributes(workingElement, originalElement);
77  
78  			s_logger.debug("Adding " + workingElement);
79  			outputParentElement.addContent(workingElement);
80  
81  			doIt(workingElement, originalElement, patchElement);
82  		}
83  
84  	}
85  
86  	/**
87  	 * Performs the actual merge between two source elements.
88  	 *
89  	 * @param parentOut
90  	 *            The merged element
91  	 * @param parentIn1
92  	 *            The first source element
93  	 * @param parentIn2
94  	 *            The second source element
95  	 * @throws AbstractXmlMergeException
96  	 *             If an error occurred during the merge
97  	 */
98  	private void doIt(Element parentOut, Element parentIn1, Element parentIn2)
99  		throws AbstractXmlMergeException {
100 
101 		addAttributes(parentOut, parentIn2);
102 
103 		Content[] list1 = (Content[]) parentIn1.getContent().toArray(
104 			new Content[] {});
105 		Content[] list2 = (Content[]) parentIn2.getContent().toArray(
106 			new Content[] {});
107 
108 		int offsetTreated1 = 0;
109 		int offsetTreated2 = 0;
110 
111 		for (int i = 0; i < list1.length; i++) {
112 
113 			s_logger.debug("List 1: " + list1[i]);
114 
115 			if (list1[i] instanceof Comment || list1[i] instanceof Text) {
116 				parentOut.addContent((Content) list1[i].clone());
117 				offsetTreated1++;
118 			} else if (!(list1[i] instanceof Element)) {
119 				throw new DocumentException(list1[i].getDocument(),
120 					"Contents of type " + list1[i].getClass().getName()
121 						+ " not supported");
122 			} else {
123 				Element e1 = (Element) list1[i];
124 
125 				// does e1 exist on list2 and has not yet been treated
126 				int posInList2 = -1;
127 				for (int j = offsetTreated2; j < list2.length; j++) {
128 
129 					s_logger.debug("List 2: " + list2[j]);
130 
131 					if (list2[j] instanceof Element) {
132 
133 						if (((Matcher) m_matcherFactory.getOperation(e1,
134 								(Element) list2[j]))
135 							.matches(e1, (Element) list2[j])) {
136 							s_logger.debug("Match found: " + e1 + " and "
137 								+ list2[j]);
138 							posInList2 = j;
139 							break;
140 						}
141 					} else if (list2[j] instanceof Comment
142 						|| list2[j] instanceof Text) {
143 						// skip
144 					} else {
145 						throw new DocumentException(list2[j].getDocument(),
146 							"Contents of type " + list2[j].getClass().getName()
147 								+ " not supported");
148 					}
149 				}
150 
151 				// element found in second list, but there is some elements to
152 				// be
153 				// treated before in second list
154 				while (posInList2 != -1 && offsetTreated2 < posInList2) {
155 					Content contentToAdd;
156 					if (list2[offsetTreated2] instanceof Element) {
157 						applyAction(parentOut, null,
158 							(Element) list2[offsetTreated2]);
159 					} else {
160 						contentToAdd = (Content) list2[offsetTreated2].clone();
161 						parentOut.addContent(contentToAdd);
162 					}
163 
164 					offsetTreated2++;
165 				}
166 
167 				// element found in all lists
168 				if (posInList2 != -1) {
169 
170 					applyAction(parentOut, (Element) list1[offsetTreated1],
171 						(Element) list2[offsetTreated2]);
172 
173 					offsetTreated1++;
174 					offsetTreated2++;
175 				} else {
176 				// element not found in second list
177 					applyAction(parentOut, (Element) list1[offsetTreated1],
178 						null);
179 					offsetTreated1++;
180 				}
181 			}
182 		}
183 
184 		// at end of list1, is there some elements on list2 which must be still
185 		// treated?
186 		while (offsetTreated2 < list2.length) {
187 			Content contentToAdd;
188 			if (list2[offsetTreated2] instanceof Element) {
189 				applyAction(parentOut, null, (Element) list2[offsetTreated2]);
190 			} else {
191 				contentToAdd = (Content) list2[offsetTreated2].clone();
192 				parentOut.addContent(contentToAdd);
193 			}
194 
195 			offsetTreated2++;
196 		}
197 
198 	}
199 
200 	/**
201 	 * Applies the action which performs the merge between two source elements.
202 	 *
203 	 * @param workingParent
204 	 *            Output parent element
205 	 * @param originalElement
206 	 *            Original element
207 	 * @param patchElement
208 	 *            Patch element
209 	 * @throws AbstractXmlMergeException
210 	 *             if an error occurred during the merge
211 	 */
212 	private void applyAction(Element workingParent, Element originalElement,
213 		Element patchElement) throws AbstractXmlMergeException {
214 		Action action = (Action) m_actionFactory.getOperation(originalElement,
215 			patchElement);
216 		Mapper mapper = (Mapper) m_mapperFactory.getOperation(originalElement,
217 			patchElement);
218 
219 		// Propagate the factories to deeper merge actions
220 		// TODO: find a way to make it cleaner
221 		if (action instanceof MergeAction) {
222 			MergeAction mergeAction = (MergeAction) action;
223 			mergeAction.setActionFactory(m_actionFactory);
224 			mergeAction.setMapperFactory(m_mapperFactory);
225 			mergeAction.setMatcherFactory(m_matcherFactory);
226 		}
227 
228 		action
229 			.perform(originalElement, mapper.map(patchElement), workingParent);
230 	}
231 
232 	/**
233 	 * Adds attributes from in element to out element.
234 	 * @param out out element
235 	 * @param in in element
236 	 */
237 	private void addAttributes(Element out, Element in) {
238 
239 		LinkedHashMap allAttributes = new LinkedHashMap();
240 
241 		List outAttributes = new ArrayList(out.getAttributes());
242 		List inAttributes = new ArrayList(in.getAttributes());
243 
244 		for (int i = 0; i < outAttributes.size(); i++) {
245 			Attribute attr = (Attribute) outAttributes.get(i);
246 			attr.detach();
247 			allAttributes.put(attr.getQualifiedName(), attr);
248 			s_logger.debug("adding attr from out:" + attr);
249 		}
250 
251 		for (int i = 0; i < inAttributes.size(); i++) {
252 			Attribute attr = (Attribute) inAttributes.get(i);
253 			attr.detach();
254 			allAttributes.put(attr.getQualifiedName(), attr);
255 			s_logger.debug("adding attr from in:" + attr);
256 		}
257 
258 		out.setAttributes(new ArrayList(allAttributes.values()));
259 	}
260 
261 }