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