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