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