View Javadoc

1   package org.lcsim.event.base;
2   
3   import java.util.Collection;
4   import java.util.Collections;
5   import java.util.Comparator;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.LinkedHashMap;
9   import java.util.Map;
10  import java.util.Set;
11  import java.util.TreeSet;
12  
13  import org.lcsim.event.LCRelation;
14  import org.lcsim.event.RelationalTable;
15  
16  /**
17   * An implementation of RealtionalTable
18   * @author tonyj
19   */
20  public class BaseRelationalTable<F,T> implements RelationalTable<F, T>
21  {
22     private enum OneMany { ONE, MANY };
23     private final static double WEIGHT_ONE = 1.0;
24     private final static double WEIGHT_ZERO = 0.0;
25     
26     private final Mode mode;
27     private final Weighting weighting;
28     
29     private Map<Relation<F,T>,Double> weightedRelations;
30     private Set<Relation<F,T>> unweightedRelations;
31     
32     private Map<F, Set<T>> fromMultiMap;
33     private Map<T, Set<F>> toMultiMap;
34     
35     private Map<F,T> fromMap;
36     private Map<T,F> toMap;
37     
38     private OneMany fromMode()
39     {
40        return mode == Mode.ONE_TO_MANY || mode == Mode.ONE_TO_ONE ? OneMany.ONE : OneMany.MANY;
41     }
42     private OneMany toMode()
43     {
44        return mode == Mode.MANY_TO_ONE || mode == Mode.ONE_TO_ONE ? OneMany.ONE : OneMany.MANY;
45     }
46     
47     /** Creates an empty ManyToMany relational table, in ManyToMany mode and
48      * with weights */
49     public BaseRelationalTable()
50     {
51        this(Mode.MANY_TO_MANY,Weighting.WEIGHTED);
52     }
53     
54     public BaseRelationalTable(Mode mode, Weighting weighting)
55     {
56        this.mode = mode;
57        this.weighting = weighting;
58        
59        if (weighting == Weighting.WEIGHTED) weightedRelations = new HashMap<Relation<F,T>,Double>();
60        else unweightedRelations = new HashSet<Relation<F,T>>();
61        
62        if (toMode() == OneMany.ONE) fromMap = new HashMap<F,T>();
63        else fromMultiMap = new HashMap<F,Set<T>>();
64        
65        if (fromMode() == OneMany.ONE) toMap = new HashMap<T,F>();
66        else toMultiMap = new HashMap<T,Set<F>>();
67     }
68     
69     public boolean add(F from, T to)
70     {
71        return add(from,to,WEIGHT_ONE);
72     }
73     
74     public boolean add(F from, T to, double weight)
75     {
76        if (from == null || to == null) throw new IllegalArgumentException("Argument to RelationalTable.add cannot be null");
77        if (weighting == Weighting.UNWEIGHTED && weight != WEIGHT_ONE) throw new IllegalArgumentException("Weight must be 1 for unweighted relational table");
78        
79        boolean result = false;
80        
81        if (toMode() == OneMany.ONE)
82        {
83           T oldTo = fromMap.put(from,to);
84           if (oldTo != null)
85           {
86              removeRelation(from,oldTo);
87              result |= true;
88           }
89        }
90        if (fromMode() == OneMany.ONE)
91        {
92           F oldFrom = toMap.put(to,from);
93           if (oldFrom != null)
94           {
95              removeRelation(oldFrom,to);
96              result |= true;
97           }
98        }
99        
100       // This must be done before the multimaps below to ensure ordering
101       result |= addRelation(from,to,weight);
102       
103       if (toMode() == OneMany.MANY)
104       {
105          Set<T> toList = fromMultiMap.get(from);
106          if (toList == null)
107          {
108             toList = weighting == Weighting.UNWEIGHTED ? new HashSet<T>() : new TreeSet<T>(new FromWeightComparator(from));
109             fromMultiMap.put(from,toList);
110          }
111          toList.add(to);
112       }
113       
114       
115       if (fromMode() == OneMany.MANY)
116       {
117          Set<F> fromList = toMultiMap.get(to);
118          if (fromList == null)
119          {
120             fromList = weighting == Weighting.UNWEIGHTED ? new HashSet<F>() : new TreeSet<F>(new ToWeightComparator(to));
121             toMultiMap.put(to,fromList);
122          }
123          fromList.add(from);
124       }
125       
126       return result;
127    }
128    private Set<Relation<F,T>> relations()
129    {
130       return weighting == Weighting.UNWEIGHTED ? unweightedRelations : weightedRelations.keySet();
131    }
132    private boolean removeRelation(F from, T to)
133    {
134       Relation relation = new Relation(from,to);
135       return relations().remove(relation);
136    }
137    private boolean addRelation(F from, T to, double weight)
138    {
139       Relation relation = new Relation<F,T>(from,to);
140       return weighting == Weighting.UNWEIGHTED ? !unweightedRelations.add(relation) : weightedRelations.put(relation,weight) != null;
141    }
142    
143    public boolean remove(F from, T to)
144    {
145       boolean result = removeRelation(from,to);
146       if (result)
147       {
148          if (toMode() == OneMany.ONE)
149          {
150             fromMap.remove(from);
151          }
152          else
153          {
154             Set<T> toList = fromMultiMap.get(from);
155             toList.remove(from);
156          }
157          
158          if (fromMode() == OneMany.ONE)
159          {
160             toMap.remove(to);
161          }
162          else
163          {
164             Set<T> fromList = fromMultiMap.get(to);
165             fromList.remove(to);
166          }
167       }
168       return result;
169    }
170    
171    public T to(F from)
172    {
173       if (toMode() == OneMany.ONE) return fromMap.get(from);
174       else
175       {
176          Set<T> all = allFrom(from);
177          if (all.size() > 2) throw new IllegalArgumentException("Ambiguous relationship for "+from);
178          return all.size() == 1 ? all.iterator().next() : null;
179       }
180    }
181    
182    public F from(T to)
183    {
184       if (fromMode() == OneMany.ONE) return toMap.get(to);
185       else
186       {
187          Set<F> all = allTo(to);
188          if (all.size() > 2) throw new IllegalArgumentException("Ambiguous relationship for "+to);
189          return all.size() == 1 ? all.iterator().next() : null;
190       }
191    }
192    
193    public Set<T> allFrom(F from)
194    {
195       if (toMode() == OneMany.ONE)
196       {
197          T to = fromMap.get(from);
198          return to == null ? Collections.<T>emptySet() : Collections.singleton(to);
199       }
200       else
201       {
202          Set<T> result = fromMultiMap.get(from);
203          return result == null ? Collections.<T>emptySet() : result;
204       }
205    }
206    
207    public Map<T,Double> allFromWithWeights(F from)
208    {
209       if (toMode() == OneMany.ONE)
210       {
211          T to = fromMap.get(from);
212          return to == null ? Collections.<T,Double>emptyMap() : Collections.singletonMap(to,weightFromTo(from,to));
213       }
214       else
215       {
216          Set<T> toList = fromMultiMap.get(from);
217          if (toList == null || toList.isEmpty()) return Collections.<T,Double>emptyMap();
218          Map<T,Double> result = new LinkedHashMap<T,Double>();
219          for (T to : toList) result.put(to,weightFromTo(from,to));
220          return result;
221       }
222    }
223    
224    public Set<F> allTo(T to)
225    {
226       if (fromMode() == OneMany.ONE)
227       {
228          F from = toMap.get(to);
229          return from == null ? Collections.<F>emptySet() : Collections.singleton(from);
230       }
231       else
232       {
233          Set<F> result = toMultiMap.get(to);
234          return result == null ? Collections.<F>emptySet() : result;
235       }
236    }
237    
238    public Map<F,Double> allToWithWeights(T to)
239    {
240       if (fromMode() == OneMany.ONE)
241       {
242          F from = toMap.get(to);
243          return from == null ? Collections.<F,Double>emptyMap() : Collections.singletonMap(from,weightFromTo(from,to));
244       }
245       else
246       {
247          Set<F> fromList = toMultiMap.get(to);
248          if (fromList == null || fromList.isEmpty()) return Collections.<F,Double>emptyMap();
249          Map<F,Double> result = new LinkedHashMap<F,Double>();
250          for (F from : fromList) result.put(from,weightFromTo(from,to));
251          return result;
252       }
253    }
254    
255    public double weightFromTo(F from, T to)
256    {
257       Relation relation = new Relation(from,to);
258       if (weighting == Weighting.UNWEIGHTED) return unweightedRelations.contains(relation) ? WEIGHT_ONE : WEIGHT_ZERO;
259       else
260       {
261          Double d = weightedRelations.get(relation);
262          return d == null ? WEIGHT_ZERO : d.doubleValue();
263       }
264    }
265    
266    public Mode getMode()
267    {
268       return mode;
269    }
270    public Weighting getWeighting()
271    {
272       return weighting;
273    }
274    public int size()
275    {
276       return relations().size();
277    }
278    
279    private static class Relation<F,T>
280    {
281       private F from;
282       private T to;
283       
284       Relation(F from, T to)
285       {
286          this.from = from;
287          this.to = to;
288       }
289       F getFrom()
290       {
291          return from;
292       }
293       T getTo()
294       {
295          return to;
296       }
297       
298       public int hashCode()
299       {
300          return to.hashCode() + 37*from.hashCode();
301       }
302       
303       public boolean equals(Object obj)
304       {
305          if (obj instanceof Relation)
306          {
307             Relation that = (Relation) obj;
308             return this.from.equals(that.from) && this.to.equals(that.to);
309          }
310          else return false;
311       }
312    }
313    private class FromWeightComparator implements Comparator<T>
314    {
315       private F from;
316       FromWeightComparator(F from)
317       {
318          this.from = from;
319       }
320       public int compare(T o1, T o2)
321       {
322          double w1 = weightFromTo(from,o1);
323          double w2 = weightFromTo(from,o2);
324          if (w1 == w2)
325          {
326             // FIXME: TreeSet bases equality on the return code from the comparator, with equal
327             // elements being eliminated. Thus we only want to return 0 for object which are really equal.
328             // Using hashcode is not strictly correct, since unequal object can have the same hashcode.
329             return o1.hashCode() - o2.hashCode();
330          }
331          else return (int) Math.signum(w2 - w1);
332       }
333    }
334    private class ToWeightComparator implements Comparator<F>
335    {
336       private T to;
337       ToWeightComparator(T to)
338       {
339          this.to = to;
340       }
341       public int compare(F o1, F o2)
342       {
343          double w1 = weightFromTo(o1,to);
344          double w2 = weightFromTo(o2,to);
345          if (w1 == w2)
346          {
347             // FIXME: TreeSet bases equality on the return code from the comparator, with equal
348             // elements being eliminated. Thus we only want to return 0 for object which are really equal.
349             // Using hashcode is not strictly correct, since unequal object can have the same hashcode.
350             return o1.hashCode() - o2.hashCode();
351          }
352          else return (int) Math.signum(w2 - w1);
353       }
354    }
355    
356    public void addRelations(Collection<LCRelation> relations)
357    {
358        for (LCRelation r : relations) {
359            this.add((F)r.getFrom(), (T)r.getTo(), r.getWeight());
360        }
361    }
362 }