Coverage Report - org.codehaus.plexus.metadata.merge.support.AbstractMergeableElementList
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractMergeableElementList
93 %
45/48
73 %
19/26
2,875
 
 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  9
         super( element );
 52  9
     }
 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  8
         Map mergeables = new LinkedHashMap();
 68  8
         List list = this.getChildren( tagName );
 69  8
         for (Object aList : list) {
 70  9
             Element ce = (Element) aList;
 71  
 
 72  
             // use the composite key specified by the passed in list
 73  9
             String compositeKey = "";
 74  9
             for (Object aCompositeKeyList : compositeKeyList) {
 75  14
                 String key = (String) aCompositeKeyList;
 76  14
                 if (null != ce.getChildText(key)) {
 77  11
                     compositeKey = compositeKey + ce.getChildText(key);
 78  
                 }
 79  14
             }
 80  
 
 81  
             // create a Mergeable instance and store it in the map.
 82  9
             DescriptorTag tag = lookupTagInstanceByName(tagName, parentElement.getAllowedTags());
 83  9
             Mergeable mergeable = tag.createMergeable(ce);
 84  
             // register the Mergeable instance based on composite key
 85  9
             mergeables.put(compositeKey, mergeable);
 86  9
         }
 87  8
         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  9
         DescriptorTag value = null;
 101  
 
 102  18
         for ( int i = 0; i < values.length && value == null; i++ )
 103  
         {
 104  9
             if ( values[i].getTagName().equals( name ) )
 105  
             {
 106  9
                 value = values[i];
 107  
             }
 108  
         }
 109  
         // not found!
 110  9
         return value;
 111  
     }
 112  
 
 113  
     public void merge( Mergeable me )
 114  
         throws MergeException
 115  
     {
 116  
         try
 117  
         {
 118  4
             Map dRequirementsMap = parseRecurringMergeables( getTagNameForRecurringMergeable(),
 119  
                                                              getElementNamesForConflictResolution( new ArrayList() ), me );
 120  4
             Map rRequirementsMap = ( (AbstractMergeableElementList) me )
 121  
                 .parseRecurringMergeables( getTagNameForRecurringMergeable(),
 122  
                                            getElementNamesForConflictResolution( new ArrayList() ), me );
 123  4
             merge( getElement(), dRequirementsMap, rRequirementsMap );
 124  
         }
 125  0
         catch ( Exception e )
 126  
         {
 127  
             // TODO: log to error
 128  
             // TODO: better error message
 129  0
             throw new MergeException( "Unable to merge Mergeable lists for element '" + getName() + "'.", e );
 130  4
         }
 131  
 
 132  4
     }
 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  4
         Set dKeySet = dMap.keySet();
 150  4
         Set rKeySet = rMap.keySet();
 151  
         // check if there are any entities to merge
 152  4
         if ( !isMergeRequired( dKeySet, rKeySet ) )
 153  
         {
 154  0
             return;
 155  
         }
 156  
 
 157  
         // iterate over components and process them
 158  4
         for (Object aDKeySet : dKeySet) {
 159  4
             String dKey = (String) aDKeySet;
 160  4
             if (rMap.containsKey(dKey)) {
 161  
                 // conflict ! merge this component                
 162  4
                 Mergeable dMeregeable = (Mergeable) dMap.get(dKey);
 163  4
                 Mergeable rMergeable = (Mergeable) rMap.get(dKey);
 164  
 
 165  4
                 dMeregeable.merge(rMergeable);
 166  
 
 167  
                 // and remove from the recessive list to mark it as merged.
 168  4
                 rMap.remove(dKey);
 169  
             }
 170  4
         }
 171  
 
 172  
         // check if any unmerged components are left in the recessive map.
 173  4
         if ( rMap.keySet().size() > 0 )
 174  
         {
 175  
             // add them to results
 176  1
             for (Object aRKeySet : rKeySet) {
 177  1
                 String rKey = (String) aRKeySet;
 178  
                 // add to parent
 179  1
                 parent.addContent((Element) ((Mergeable) rMap.get(rKey)).getElement().clone());
 180  1
             }
 181  
         }
 182  4
     }
 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  4
         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  
 }