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   *
42   * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
43   * @version $Id$
44   */
45  public abstract class AbstractMergeableElementList
46      extends AbstractMergeableElement
47  {
48      public AbstractMergeableElementList( Element element )
49      {
50          super( element );
51      }
52  
53      /**
54       * Parses &lt;component&gt; elements and builds a map keyed basd on the list of composite keys specified.
55       *
56       * @param tagName          Name of the tag that appears multiple times
57       * @param compositeKeyList List of element/tag names to be used as composite keys to register recurring
58       *                         {@link Mergeable} instances.
59       * @return Map of {@link Mergeable} instances keyed on the composite key obtained from
60       *         {@link #getElementNamesForConflictResolution(java.util.List)}
61       * @throws Exception if there was an error parsing and registering {@link Mergeable} instances
62       */
63      protected Map parseRecurringMergeables( String tagName, List compositeKeyList, Mergeable parentElement )
64          throws Exception
65      {
66          Map mergeables = new LinkedHashMap();
67          List list = this.getChildren( tagName );
68          for (Object aList : list) {
69              Element ce = (Element) aList;
70  
71              // use the composite key specified by the passed in list
72              String compositeKey = "";
73              for (Object aCompositeKeyList : compositeKeyList) {
74                  String key = (String) aCompositeKeyList;
75                  if (null != ce.getChildText(key)) {
76                      compositeKey = compositeKey + ce.getChildText(key);
77                  }
78              }
79  
80              // create a Mergeable instance and store it in the map.
81              DescriptorTag tag = lookupTagInstanceByName(tagName, parentElement.getAllowedTags());
82              Mergeable mergeable = tag.createMergeable(ce);
83              // register the Mergeable instance based on composite key
84              mergeables.put(compositeKey, mergeable);
85          }
86          return mergeables;
87      }
88  
89      /**
90       * Looks up and returns an {@link DescriptorTag} instance for the
91       * specified tag name.
92       *
93       * @param name key to look up the {@link DescriptorTag} instance on.
94       * @return {@link DescriptorTag} instance whose name matches the name specified.
95       *         Returns <code>null</code> if no match is found.
96       */
97      private DescriptorTag lookupTagInstanceByName( String name, DescriptorTag[] values )
98      {
99          DescriptorTag value = null;
100 
101         for ( int i = 0; i < values.length && value == null; i++ )
102         {
103             if ( values[i].getTagName().equals( name ) )
104             {
105                 value = values[i];
106             }
107         }
108         // not found!
109         return value;
110     }
111 
112     public void merge( Mergeable me )
113         throws MergeException
114     {
115         try
116         {
117             Map dRequirementsMap = parseRecurringMergeables( getTagNameForRecurringMergeable(),
118                                                              getElementNamesForConflictResolution( new ArrayList() ), me );
119             Map rRequirementsMap = ( (AbstractMergeableElementList) me )
120                 .parseRecurringMergeables( getTagNameForRecurringMergeable(),
121                                            getElementNamesForConflictResolution( new ArrayList() ), me );
122             merge( getElement(), dRequirementsMap, rRequirementsMap );
123         }
124         catch ( Exception e )
125         {
126             // TODO: log to error
127             // TODO: better error message
128             throw new MergeException( "Unable to merge Mergeable lists for element '" + getName() + "'.", e );
129         }
130 
131     }
132 
133     /**
134      * Identifies the conflicting elements in the dominant and recessive
135      * {@link Map} instance and merges as required.
136      *
137      * @param parent {@link Element} that is parent for the children in the dominant Map instance. Merged content is
138      *               added to this element.
139      * @param dMap   Dominant Map keyed by the composite key obtained from
140      *               {@link #getElementNamesForConflictResolution(List)}
141      * @param rMap   Recessive Map keyed by the composite key obtained from
142      *               {@link #getElementNamesForConflictResolution(List)}
143      * @throws Exception if there was an error merging both the maps.
144      */
145     protected void merge( Element parent, Map dMap, Map rMap )
146         throws Exception
147     {
148         Set dKeySet = dMap.keySet();
149         Set rKeySet = rMap.keySet();
150         // check if there are any entities to merge
151         if ( !isMergeRequired( dKeySet, rKeySet ) )
152         {
153             return;
154         }
155 
156         // iterate over components and process them
157         for (Object aDKeySet : dKeySet) {
158             String dKey = (String) aDKeySet;
159             if (rMap.containsKey(dKey)) {
160                 // conflict ! merge this component                
161                 Mergeable dMeregeable = (Mergeable) dMap.get(dKey);
162                 Mergeable rMergeable = (Mergeable) rMap.get(dKey);
163 
164                 dMeregeable.merge(rMergeable);
165 
166                 // and remove from the recessive list to mark it as merged.
167                 rMap.remove(dKey);
168             }
169         }
170 
171         // check if any unmerged components are left in the recessive map.
172         if ( rMap.keySet().size() > 0 )
173         {
174             // add them to results
175             for (Object aRKeySet : rKeySet) {
176                 String rKey = (String) aRKeySet;
177                 // add to parent
178                 parent.addContent((Element) ((Mergeable) rMap.get(rKey)).getElement().clone());
179             }
180         }
181     }
182 
183     /**
184      * Determines if a merge operation is required for the two sets (dominant and recessive) specified.
185      *
186      * @param dKeySet the dominant set of elements.
187      * @param rKeySet the recessive set of elements.
188      * @return <code>true</code> if a merge operation was required.
189      */
190     private boolean isMergeRequired( Set dKeySet, Set rKeySet )
191     {
192         return ( dKeySet.size() > 0 || rKeySet.size() > 0 );
193     }
194 
195     /**
196      * Allows the sub classes to provided a tag name that they expect to recurr
197      * within them.
198      * <p/>
199      * For instance: <br>
200      * <ul>
201      * <li>&lt;components&gt; expects &lt;component&gt; to recurr within
202      * itself.</li>
203      * <li>&lt;requirements&gt; expects &lt;requirement&gt; to recurr within
204      * itself.</li>
205      * </ul>
206      *
207      * @return tag name of the {@link Mergeable} element that occurs multiple times.
208      */
209     protected abstract String getTagNameForRecurringMergeable();
210 
211     protected abstract List getElementNamesForConflictResolution( List defaultList );
212 }