1 package org.codehaus.plexus.metadata.merge.support;
2
3 /*
4 * The MIT License
5 *
6 * Copyright (c) 2006, The Codehaus
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy of
9 * this software and associated documentation files (the "Software"), to deal in
10 * the Software without restriction, including without limitation the rights to
11 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 * of the Software, and to permit persons to whom the Software is furnished to do
13 * so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27 import java.util.ArrayList;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32
33 import org.codehaus.plexus.metadata.merge.MergeException;
34 import org.jdom2.Element;
35
36 /**
37 * Base class that allows for handling merging two element lists.
38 * <p>
39 * <em>TODO Refactor and make this extend {@link AbstractMergeableElement} which is what
40 * this actually is, but with added bits for merging child element lists.</em>
41 * </p>
42 *
43 * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
44 */
45 public abstract class AbstractMergeableElementList extends AbstractMergeableElement {
46 public AbstractMergeableElementList(Element element) {
47 super(element);
48 }
49
50 /**
51 * Parses <component> elements and builds a map keyed basd on the list of composite keys specified.
52 *
53 * @param tagName Name of the tag that appears multiple times
54 * @param compositeKeyList List of element/tag names to be used as composite keys to register recurring
55 * {@link Mergeable} instances.
56 * @param parentElement {@link Mergeable}.
57 * @return Map of {@link Mergeable} instances keyed on the composite key obtained from
58 * {@link #getElementNamesForConflictResolution(java.util.List)}
59 * @throws Exception if there was an error parsing and registering {@link Mergeable} instances
60 */
61 protected Map parseRecurringMergeables(String tagName, List compositeKeyList, Mergeable parentElement)
62 throws Exception {
63 Map mergeables = new LinkedHashMap();
64 List list = this.getChildren(tagName);
65 for (Object aList : list) {
66 Element ce = (Element) aList;
67
68 // use the composite key specified by the passed in list
69 String compositeKey = "";
70 for (Object aCompositeKeyList : compositeKeyList) {
71 String key = (String) aCompositeKeyList;
72 if (null != ce.getChildText(key)) {
73 compositeKey = compositeKey + ce.getChildText(key);
74 }
75 }
76
77 // create a Mergeable instance and store it in the map.
78 DescriptorTag tag = lookupTagInstanceByName(tagName, parentElement.getAllowedTags());
79 Mergeable mergeable = tag.createMergeable(ce);
80 // register the Mergeable instance based on composite key
81 mergeables.put(compositeKey, mergeable);
82 }
83 return mergeables;
84 }
85
86 /**
87 * Looks up and returns an {@link DescriptorTag} instance for the
88 * specified tag name.
89 *
90 * @param name key to look up the {@link DescriptorTag} instance on.
91 * @return {@link DescriptorTag} instance whose name matches the name specified.
92 * Returns <code>null</code> if no match is found.
93 */
94 private DescriptorTag lookupTagInstanceByName(String name, DescriptorTag[] values) {
95 DescriptorTag value = null;
96
97 for (int i = 0; i < values.length && value == null; i++) {
98 if (values[i].getTagName().equals(name)) {
99 value = values[i];
100 }
101 }
102 // not found!
103 return value;
104 }
105
106 public void merge(Mergeable me) throws MergeException {
107 try {
108 Map dRequirementsMap = parseRecurringMergeables(
109 getTagNameForRecurringMergeable(), getElementNamesForConflictResolution(new ArrayList()), me);
110 Map rRequirementsMap = ((AbstractMergeableElementList) me)
111 .parseRecurringMergeables(
112 getTagNameForRecurringMergeable(),
113 getElementNamesForConflictResolution(new ArrayList()),
114 me);
115 merge(getElement(), dRequirementsMap, rRequirementsMap);
116 } catch (Exception e) {
117 // TODO: log to error
118 // TODO: better error message
119 throw new MergeException("Unable to merge Mergeable lists for element '" + getName() + "'.", e);
120 }
121 }
122
123 /**
124 * Identifies the conflicting elements in the dominant and recessive
125 * {@link Map} instance and merges as required.
126 *
127 * @param parent {@link Element} that is parent for the children in the dominant Map instance. Merged content is
128 * added to this element.
129 * @param dMap Dominant Map keyed by the composite key obtained from
130 * {@link #getElementNamesForConflictResolution(List)}
131 * @param rMap Recessive Map keyed by the composite key obtained from
132 * {@link #getElementNamesForConflictResolution(List)}
133 * @throws Exception if there was an error merging both the maps.
134 */
135 protected void merge(Element parent, Map dMap, Map rMap) throws Exception {
136 Set dKeySet = dMap.keySet();
137 Set rKeySet = rMap.keySet();
138 // check if there are any entities to merge
139 if (!isMergeRequired(dKeySet, rKeySet)) {
140 return;
141 }
142
143 // iterate over components and process them
144 for (Object aDKeySet : dKeySet) {
145 String dKey = (String) aDKeySet;
146 if (rMap.containsKey(dKey)) {
147 // conflict ! merge this component
148 Mergeable dMeregeable = (Mergeable) dMap.get(dKey);
149 Mergeable rMergeable = (Mergeable) rMap.get(dKey);
150
151 dMeregeable.merge(rMergeable);
152
153 // and remove from the recessive list to mark it as merged.
154 rMap.remove(dKey);
155 }
156 }
157
158 // check if any unmerged components are left in the recessive map.
159 if (rMap.keySet().size() > 0) {
160 // add them to results
161 for (Object aRKeySet : rKeySet) {
162 String rKey = (String) aRKeySet;
163 // add to parent
164 parent.addContent(
165 (Element) ((Mergeable) rMap.get(rKey)).getElement().clone());
166 }
167 }
168 }
169
170 /**
171 * Determines if a merge operation is required for the two sets (dominant and recessive) specified.
172 *
173 * @param dKeySet the dominant set of elements.
174 * @param rKeySet the recessive set of elements.
175 * @return <code>true</code> if a merge operation was required.
176 */
177 private boolean isMergeRequired(Set dKeySet, Set rKeySet) {
178 return (dKeySet.size() > 0 || rKeySet.size() > 0);
179 }
180
181 /**
182 * Allows the sub classes to provided a tag name that they expect to recurr
183 * within them.
184 * For instance:
185 * <ul>
186 * <li><components> expects <component> to recurr within
187 * itself.</li>
188 * <li><requirements> expects <requirement> to recurr within
189 * itself.</li>
190 * </ul>
191 *
192 * @return tag name of the {@link Mergeable} element that occurs multiple times.
193 */
194 protected abstract String getTagNameForRecurringMergeable();
195
196 protected abstract List getElementNamesForConflictResolution(List defaultList);
197 }