001package org.biopax.paxtools.controller; 002 003import org.apache.commons.logging.Log; 004import org.apache.commons.logging.LogFactory; 005import org.biopax.paxtools.model.BioPAXElement; 006import org.biopax.paxtools.model.Model; 007 008import java.util.*; 009 010/** 011 * Utility class to merge multiple biopax models into one. Note that this merger does not preserve 012 * the integrity of the passed models. Target will be a merged model and source will become 013 * unusable. 014 */ 015 016public class Merger implements Visitor 017{ 018// ------------------------------ FIELDS ------------------------------ 019 020 private static final Log log = LogFactory.getLog(Merger.class); 021 022 private final Traverser traverser; 023 024 private final HashMap<Integer, List<BioPAXElement>> equivalenceMap = 025 new HashMap<Integer, List<BioPAXElement>>(); 026 private final EditorMap map; 027 028 // Keep track of merged elements 029 private final HashSet<BioPAXElement> mergedElements = 030 new HashSet<BioPAXElement>(); 031 032 // Keep track of new elements 033 private final HashSet<BioPAXElement> addedElements = new HashSet<BioPAXElement>(); 034 035 // --------------------------- CONSTRUCTORS --------------------------- 036 037 /** 038 * @param map a class to editor map containing the editors for the elements of models to be 039 * modified. 040 */ 041 public Merger(EditorMap map) 042 { 043 this.map = map; 044 traverser = new Traverser(map, this); 045 } 046 047// ------------------------ INTERFACE METHODS ------------------------ 048 049 // --------------------- Interface Visitor --------------------- 050 051 /** 052 * Checks whether <em>model</em> contains <em>bpe</em> element, and if it does, then it updates the 053 * value of the equivalent element for <em>bpe</em> by using the specific <em>editor</em>. 054 * 055 * @param domain owner 056 * @param range property value 057 * @param model model containing the equivalent element's equivalent 058 * @param editor biopax property editor specific for the value type to be updated 059 */ 060 public void visit(BioPAXElement domain, Object range, Model model, PropertyEditor editor) 061 { 062 if (range != null && range instanceof BioPAXElement) 063 { 064 BioPAXElement bpe = (BioPAXElement) range; 065 066 // do nothing if you already inserted this 067 068 if (!model.contains(bpe)) 069 { 070 //if there is an identical 071 if (model.getByID(bpe.getRDFId()) != null) 072 { 073 if (editor.isMultipleCardinality()) 074 { 075 editor.removeValueFromBean(bpe, domain); 076 } 077 editor.setValueToBean(getIdentical(bpe), domain); 078 } 079 } 080 } 081 } 082 083// -------------------------- OTHER METHODS -------------------------- 084 085 /** 086 * After a merge is accomplished, this set will contain the merged elements. This is not an 087 * essential method for paxtools functionality, but it may be useful for 3rd party applications. 088 * 089 * @return a hashet of merged elements in the target 090 * @see #merge 091 */ 092 public HashSet<BioPAXElement> getMergedElements() 093 { 094 return this.mergedElements; 095 } 096 097 /** 098 * After a merge is accomplished, this set will contain the newly added elements. This is not an 099 * essential method for paxtools functionallity, but it may be useful for 3rd party applications. 100 * 101 * @return a hashet of newly added elements in the target 102 * @see #merge 103 */ 104 public HashSet<BioPAXElement> getAddedElements() 105 { 106 return this.addedElements; 107 } 108 109 /** 110 * Merges the <em>source</em> models into <em>target</em> model. 111 * 112 * @param target model into which merging process will be done 113 * @param sources model(s) that are going to be merged with <em>target</em> 114 */ 115 public void merge 116 (Model target, Model... sources) 117 { 118 // Empty merged and added elements sets 119 mergedElements.clear(); 120 addedElements.clear(); 121 122 // Fill equivalence map with objects from target model 123 Set<BioPAXElement> targetElements = target.getObjects(); 124 for (BioPAXElement t_bpe : targetElements) 125 { 126 this.addIntoEquivalanceMap(t_bpe); 127 } 128 129 // Try to insert every biopax element in every source one by one 130 for (Model source : sources) 131 { 132 Set<BioPAXElement> sourceElements = source.getObjects(); 133 for (BioPAXElement bpe : sourceElements) 134 { 135 insert(target, bpe); 136 } 137 } 138 } 139 140 /** 141 * Inserts a BioPAX element into the <em>target</em> model if it does not contain an equivalent; 142 * but if does, than it updates the equivalent using this element's values. 143 * 144 * @param target model into which bpe will be inserted 145 * @param bpe BioPAX element to be inserted into target 146 */ 147 private void insert(Model target, BioPAXElement bpe) 148 { 149 // do nothing if you already inserted this 150 if (!target.contains(bpe)) 151 { 152 //if there is an identical (in fact, "equal") object 153 BioPAXElement ibpe = target.getByID(bpe.getRDFId()); 154 if (ibpe != null && ibpe.equals(bpe)) 155 /* - ibpe.equals(bpe) can be 'false' here, because, 156 * even though the above !target.contains(bpe) is true, 157 * it probably compared objects using '==' operator 158 * (see the ModelImpl for details) 159 */ 160 { 161 updateObjectFields(bpe, ibpe, target); 162 // We have a merged element, add it into the tracker 163 mergedElements.add(ibpe); 164 } 165 else 166 { 167 target.add(bpe); 168 this.addIntoEquivalanceMap(bpe); 169 traverser.traverse(bpe, target); 170 // We have a new element, add it into the tracker 171 addedElements.add(bpe); 172 } 173 } 174 } 175 176 /** 177 * Searches the target model for an identical of given BioPAX element, and returns this element if 178 * it finds it. 179 * 180 * @param bpe BioPAX element for which equivalent will be searched in target. 181 * @return the BioPAX element that is found in target model, if there is none it returns null 182 */ 183 private BioPAXElement getIdentical(BioPAXElement bpe) 184 { 185 int key = bpe.hashCode(); 186 List<BioPAXElement> list = equivalenceMap.get(key); 187 if (list != null) 188 { 189 for (BioPAXElement other : list) 190 { 191 if (other.equals(bpe)) 192 { 193 return other; 194 } 195 } 196 } 197 return null; 198 } 199 200 /** 201 * Updates each value of <em>existing</em> element, using the value(s) of <em>update</em>. 202 * 203 * @param update BioPAX element of which values are used for update 204 * @param existing BioPAX element to be updated 205 * @param target 206 */ 207 private void updateObjectFields(BioPAXElement update, 208 BioPAXElement existing, Model target) 209 { 210 Set<PropertyEditor> editors = 211 map.getEditorsOf(update); 212 for (PropertyEditor editor : editors) 213 { 214 updateObjectFieldsForEditor(editor, update, existing, target); 215 } 216 217// if (!update.getRDFId().equals(existing.getRDFId())) 218// { 219//TODO addNew a unification xref 220// if(existing instanceof XReferrable) 221// { 222// ((XReferrable) existing).addXref(fa); 223// } 224// } 225 } 226 227 /** 228 * Updates the value of <em>existing</em> element, using the value of <em>update</em>. Editor is 229 * the used for the modification. 230 * 231 * @param editor editor for the specific value to be updated 232 * @param update BioPAX element of which value is used for the update 233 * @param existing BioPAX element to be updated 234 * @param target 235 */ 236 private void updateObjectFieldsForEditor(PropertyEditor editor, 237 BioPAXElement update, 238 BioPAXElement existing, 239 Model target) 240 { 241 if (editor.isMultipleCardinality()) 242 { 243 for (Object updateValue : editor.getValueFromBean(update)) 244 { 245 updateField(editor, updateValue, existing, target); 246 } 247 } 248 else 249 { 250 Object existingValue = editor.getValueFromBean(existing); 251 Object updateValue = editor.getValueFromBean(update); 252 253 if (editor.isUnknown(existingValue)) 254 { 255 if (!editor.isUnknown(updateValue)) 256 { 257 updateField(editor, updateValue, existing, target); 258 } 259 } 260 else 261 { 262 if (!existingValue.equals(updateValue)) 263 { 264 log.warn("Mismatch in single cardinality field:" + 265 existingValue + ":" + updateValue); 266 log.warn("Using existing value"); 267 } 268 } 269 } 270 } 271 272 /** 273 * Updates <em>existing</em>, using the <em>updateValue</em> by the editor. 274 * 275 * @param editor editor for the specific value to be updated 276 * @param updateValue the value for the update 277 * @param existing BioPAX element to be updated 278 * @param target 279 */ 280 private void updateField(PropertyEditor editor, Object updateValue, 281 BioPAXElement existing, Model target) 282 { 283 if (updateValue instanceof BioPAXElement) 284 { 285 BioPAXElement bpe = (BioPAXElement) updateValue; 286 //Now there are two possibilities 287 288 289 BioPAXElement ibpe = target.getByID(bpe.getRDFId()); 290 //1. has an identical in the target 291 if (ibpe != null) 292 { 293 updateValue = ibpe; 294 } 295 //2. it has no identical in the target 296 //I do not have to do anything as it will eventually 297 //be moved. 298 299 } 300 editor.setValueToBean(updateValue, existing); 301 } 302 303 /** 304 * Adds a BioPAX element into the equivalence map. 305 * 306 * @param bpe BioPAX element to be added into the map. 307 */ 308 private void addIntoEquivalanceMap(BioPAXElement bpe) 309 { 310 int key = bpe.hashCode(); 311 List<BioPAXElement> list = equivalenceMap.get(key); 312 if (list == null) 313 { 314 list = new ArrayList<BioPAXElement>(); 315 equivalenceMap.put(key, list); 316 } 317 list.add(bpe); 318 } 319}