JAL-2446 tidied up / generalised binary search in FeatureStore
[jalview.git] / src / jalview / datamodel / features / NCNode.java
1 package jalview.datamodel.features;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 /**
7  * Each node of the NCList tree consists of a range, and (optionally) the NCList
8  * of ranges it encloses
9  *
10  * @param <V>
11  */
12 class NCNode<V extends ContiguousI> implements ContiguousI
13 {
14   /*
15    * deep size (number of ranges included)
16    */
17   private int size;
18
19   private V region;
20
21   /*
22    * null, or an object holding contained subregions of this nodes region
23    */
24   private NCList<V> subregions;
25
26   /**
27    * Constructor given a list of ranges
28    * 
29    * @param ranges
30    */
31   NCNode(List<V> ranges)
32   {
33     build(ranges);
34   }
35
36   /**
37    * Constructor given a single range
38    * 
39    * @param range
40    */
41   NCNode(V range)
42   {
43     List<V> ranges = new ArrayList<V>();
44     ranges.add(range);
45     build(ranges);
46   }
47
48   NCNode(V entry, NCList<V> newNCList)
49   {
50     region = entry;
51     subregions = newNCList;
52     size = 1 + newNCList.size();
53   }
54
55   /**
56    * @param ranges
57    */
58   protected void build(List<V> ranges)
59   {
60     size = ranges.size();
61
62     if (!ranges.isEmpty())
63     {
64       region = ranges.get(0);
65     }
66     if (ranges.size() > 1)
67     {
68       subregions = new NCList<V>(ranges.subList(1, ranges.size()));
69     }
70   }
71
72   @Override
73   public int getBegin()
74   {
75     return region.getBegin();
76   }
77
78   @Override
79   public int getEnd()
80   {
81     return region.getEnd();
82   }
83
84   /**
85    * Formats the node as a bracketed list e.g.
86    * 
87    * <pre>
88    * [1-100 [10-30 [10-20]], 15-30 [20-20]]
89    * </pre>
90    */
91   @Override
92   public String toString() {
93     StringBuilder sb = new StringBuilder(10 * size);
94     sb.append(region.getBegin()).append("-").append(region.getEnd());
95     if (subregions != null)
96     {
97       sb.append(" ").append(subregions.toString());
98     }
99     return sb.toString();
100   }
101
102   void prettyPrint(StringBuilder sb, int offset, int indent) {
103     for (int i = 0 ; i < offset ; i++) {
104       sb.append(" ");
105     }
106     sb.append(region.getBegin()).append("-").append(region.getEnd());
107     if (subregions != null)
108     {
109       sb.append(System.lineSeparator());
110       subregions.prettyPrint(sb, offset + 2, indent);
111     }
112   }
113   /**
114    * Add any ranges that overlap the from-to range to the result list
115    * 
116    * @param from
117    * @param to
118    * @param result
119    */
120   void findOverlaps(long from, long to, List<V> result)
121   {
122     if (region.getBegin() <= to && region.getEnd() >= from)
123     {
124       result.add(region);
125     }
126     if (subregions != null)
127     {
128       subregions.findOverlaps(from, to, result);
129     }
130   }
131
132   /**
133    * Add one range to this subrange
134    * 
135    * @param entry
136    */
137   synchronized void add(V entry)
138   {
139     if (entry.getBegin() < region.getBegin() || entry.getEnd() > region.getEnd()) {
140       throw new IllegalArgumentException(String.format(
141               "adding improper subrange %d-%d to range %d-%d",
142               entry.getBegin(), entry.getEnd(), region.getBegin(),
143               region.getEnd()));
144     }
145     if (subregions == null)
146     {
147       subregions = new NCList<V>(entry);
148     }
149     else
150     {
151       subregions.add(entry);
152     }
153     size++;
154   }
155
156   /**
157    * Answers true if the data held satisfy the rules of construction of an
158    * NCList, else false.
159    * 
160    * @return
161    */
162   boolean isValid()
163   {
164     /*
165      * we don't handle reverse ranges
166      */
167     if (region != null && region.getBegin() > region.getEnd())
168     {
169       return false;
170     }
171     if (subregions == null)
172     {
173       return true;
174     }
175     return subregions.isValid(getBegin(), getEnd());
176   }
177
178   /**
179    * Adds all contained entries to the given list
180    * 
181    * @param entries
182    */
183   void getEntries(List<V> entries)
184   {
185     entries.add(region);
186     if (subregions != null)
187     {
188       subregions.getEntries(entries);
189     }
190   }
191
192   /**
193    * Answers true if this object contains the given entry (by object equals
194    * test), else false
195    * 
196    * @param entry
197    * @return
198    */
199   boolean contains(V entry)
200   {
201     if (entry == null)
202     {
203       return false;
204     }
205     if (entry.equals(region))
206     {
207       return true;
208     }
209     return subregions == null ? false : subregions.contains(entry);
210   }
211
212   /**
213    * Answers the 'root' region modelled by this object
214    * 
215    * @return
216    */
217   V getRegion()
218   {
219     return region;
220   }
221
222   /**
223    * Answers the (possibly null) contained regions within this object
224    * 
225    * @return
226    */
227   NCList<V> getSubRegions()
228   {
229     return subregions;
230   }
231
232   /**
233    * Nulls the subregion reference if it is empty (after a delete entry
234    * operation)
235    */
236   void deleteSubRegionsIfEmpty()
237   {
238     if (subregions != null && subregions.size() == 0)
239     {
240       subregions = null;
241     }
242   }
243
244   /**
245    * Answers the (deep) size of this node i.e. the number of ranges it models
246    * 
247    * @return
248    */
249   int size()
250   {
251     return size;
252   }
253 }