View Javadoc
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 &lt;component&gt; 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>&lt;components&gt; expects &lt;component&gt; to recurr within
187      * itself.</li>
188      * <li>&lt;requirements&gt; expects &lt;requirement&gt; 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 }