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 }