001package org.biopax.paxtools.util; 002 003import org.apache.commons.logging.Log; 004import org.apache.commons.logging.LogFactory; 005import org.biopax.paxtools.model.BioPAXElement; 006 007import java.util.HashSet; 008import java.util.LinkedList; 009import java.util.List; 010import java.util.Set; 011 012/** 013 * Utility class for equivalence based comparison of a set of BioPAXElements. 014 * 015 * BioPAXElement by default uses equals and hash code methods based on the type and URI. 016 * On the other hand for many elements it is possible to determine semantic equivalence 017 * among elements. For example two entityFeatures with exactly the same type and location 018 * are equivalent. This logic is implemented in isEquivalent() and equivalenceCode() 019 * methods. 020 * 021 * For most Java collections that uses hashCode and equals there is no easy way to plug-in a 022 * comparator to switch to different comparison behavior. This is a simple Set implementation that uses 023 * equivalence codes when possible. It uses HashMap as the underlying implementation and will use a special bucket in 024 * the case of clashes. 025 * 026 */ 027public class EquivalenceGrouper<T extends BioPAXElement> 028{ 029 030 031 HashSet<EquivalanceBucket<T>> buckets; 032 033 Log log = LogFactory.getLog(EquivalenceGrouper.class); 034 035 public EquivalenceGrouper(Set<? extends T> bpes) 036 { 037 this(); 038 addAll(bpes); 039 } 040 041 public HashSet<? extends List<T>> getBuckets() 042 { 043 return buckets; 044 } 045 046 void addAll(Set<? extends T> bpes) 047 { 048 for (T bpe : bpes) 049 { 050 add(bpe); 051 } 052 } 053 054 public EquivalanceBucket<T> access(final T element) 055 { 056 EquivalanceBucket<T> value =null; 057 if (element != null) 058 { 059 value = access(element, EquivalanceBucket.EQUALITY); 060 if (value==null) //now try with equivalence 061 { 062 value=access(element,EquivalanceBucket.EQUIVALENCE); 063 } 064 } 065 return value; 066 067 } 068 069 private EquivalanceBucket<T> access(final T element, final boolean parity) 070 { 071 final Object[] trap = new Object[]{null}; 072 if (this.buckets.contains(new Object() 073 { 074 public int hashCode() 075 { 076 return parity?element.equivalenceCode():element.hashCode(); 077 } 078 079 public boolean equals(Object other) 080 { 081 if(other != null && other.equals(element)) 082 { 083 trap[0] = other; 084 return true; 085 } 086 else return false; 087 } 088 })) 089 { 090 return (EquivalanceBucket<T>) trap[0]; 091 } 092 return null; 093 } 094 095 096 public EquivalenceGrouper() 097 { 098 this.buckets = new HashSet<EquivalanceBucket<T>>(); 099 } 100 101 public void add(T bpe) 102 { 103 104 // If this is the case then we will simply return false when 105 // we have something that matches the evcode 106 // AND if that something is a bucket contains something that is equivalent to bpe 107 // AND if that something is not a bucket and it is equivalent to bpe 108 EquivalanceBucket<T> bucket = access(bpe); 109 if (bucket == null) 110 { 111 bucket = new EquivalanceBucket(bpe); 112 this.buckets.add(bucket); 113 } else 114 { 115 for (T t : bucket) 116 { 117 if(t==bpe) return; 118 } 119 bucket.add(bpe); 120 } 121 } 122 123 124 private class EquivalanceBucket<T extends BioPAXElement> extends LinkedList<T> 125 { 126 127 static final boolean EQUALITY = false; 128 129 static final boolean EQUIVALENCE = true; 130 131 private final int code; 132 133 private boolean parity; 134 135 private EquivalanceBucket(T first) 136 { 137 this.add(first); 138 if (first.equivalenceCode() == 0 || first.equivalenceCode() == first.hashCode()) 139 { 140 parity = EQUALITY; 141 this.code = first.hashCode(); 142 } else 143 { 144 parity = EQUIVALENCE; 145 this.code = first.equivalenceCode(); 146 } 147 } 148 149 @Override public int hashCode() 150 { 151 return code; 152 } 153 154 @Override public boolean equals(Object o) 155 { 156 if (o instanceof EquivalanceBucket) 157 { 158 return super.equals(o); 159 } else 160 { 161 T t = this.get(0); 162 if (parity) //EQUIVALENCE 163 { 164 return t.isEquivalent((BioPAXElement) o); 165 } else //EQUALITY 166 { 167 return t.equals(o); 168 } 169 } 170 } 171 172 } 173} 174