View Javadoc
1   package emissary.parser;
2   
3   import org.apache.commons.collections4.CollectionUtils;
4   import org.slf4j.Logger;
5   import org.slf4j.LoggerFactory;
6   
7   import java.util.ArrayList;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  import javax.annotation.Nullable;
12  
13  /**
14   * Detailed session information as it is parsed This class just records offsets and length of various things using lists
15   * of PositionRecord. If you want to actually produce the bytes of a session see SessionParser.decomposeSession and
16   * emissary.parser.DecomposedSession
17   * <p>
18   * No assertions are made about the order of the header, footer, and data sections within the original byte array, it
19   * can be constructed from position records in any order, possibly overlapping and repeated, We only assert that the
20   * sections asked for are not out of bounds with respect to the overall array boundaries.
21   * <p>
22   * The validation scheme allows the overall bounds to be set at either the beginning or at the end of session parsing
23   * without performance penalty either way.
24   */
25  public class InputSession {
26  
27      // Logger
28      private static final Logger logger = LoggerFactory.getLogger(InputSession.class);
29  
30      // Overall start/length of the session
31      @Nullable
32      protected PositionRecord overall = null;
33  
34      // ordered list of PositionRecord for the header within the session
35      protected List<PositionRecord> header = new ArrayList<>();
36  
37      // ordered list of PositionRecord for the footer within the session
38      protected List<PositionRecord> footer = new ArrayList<>();
39  
40      // ordered list of PositionRecord for the data within the session
41      protected List<PositionRecord> data = new ArrayList<>();
42  
43      // unordered collected metadata during parsing
44      protected Map<String, Object> metaData = new HashMap<>();
45  
46      // Indicator of validness
47      protected boolean valid = true;
48  
49      /**
50       * Record details of an input session
51       */
52      public InputSession() {}
53  
54      /**
55       * Record details of an input session
56       * 
57       * @param o the overall position record
58       */
59      public InputSession(PositionRecord o) {
60          this.overall = o;
61      }
62  
63      /**
64       * Record details of an input session
65       * 
66       * @param o the overall position record
67       * @param d position records for the data
68       */
69      public InputSession(PositionRecord o, List<PositionRecord> d) throws ParserException {
70          this(o);
71          addDataRecs(d);
72      }
73  
74      /**
75       * Record details of an input session
76       * 
77       * @param o the overall position record
78       * @param d position record for the data
79       */
80      public InputSession(PositionRecord o, PositionRecord d) throws ParserException {
81          this(o);
82          addDataRec(d);
83      }
84  
85      /**
86       * Record details of an input session
87       * 
88       * @param o the overall position record
89       * @param h position records for the header
90       * @param f position records for the footer
91       * @param d position records for the data
92       */
93      public InputSession(PositionRecord o, List<PositionRecord> h, List<PositionRecord> f, List<PositionRecord> d) throws ParserException {
94          this(o, d);
95          addHeaderRecs(h);
96          addFooterRecs(f);
97      }
98  
99      /**
100      * Record details of an input session
101      * 
102      * @param o the overall position record
103      * @param h position record for the header
104      * @param f position record for the footer
105      * @param d position record for the data
106      * @param m map of collected metadata
107      */
108     public InputSession(PositionRecord o, List<PositionRecord> h, List<PositionRecord> f, List<PositionRecord> d, Map<String, Object> m)
109             throws ParserException {
110         this(o, h, f, d);
111         addMetaData(m);
112     }
113 
114     /**
115      * Set the overall position record
116      * 
117      * @param rec the PositionRecord for the overall session range
118      */
119     public void setOverall(PositionRecord rec) throws ParserException {
120         this.overall = rec;
121         validateAll();
122     }
123 
124     /**
125      * Set the overall position record from the data
126      * 
127      * @param start for the position record
128      * @param length for the position record
129      */
130     public void setOverall(int start, int length) throws ParserException {
131         setOverall(new PositionRecord(start, length));
132     }
133 
134     /**
135      * Add a map of metadata
136      * 
137      * @param m map of String key and String or PositionRecord values
138      */
139     public void addMetaData(Map<String, Object> m) throws ParserException {
140         for (Map.Entry<String, Object> entry : m.entrySet()) {
141             String key = entry.getKey();
142             Object val = entry.getValue();
143             if (val instanceof PositionRecord) {
144                 validateRecord((PositionRecord) val);
145                 metaData.put(key, val);
146             } else if (val instanceof String) {
147                 metaData.put(key, val);
148             } else {
149                 logger.warn("Ignoring metadata record named {} with type of {} - it is not a PositionRecord or a String", key,
150                         val.getClass().getName());
151             }
152         }
153     }
154 
155     /**
156      * Set the session validity
157      * 
158      * @param b true if session is valid
159      */
160     public void setValid(boolean b) {
161         this.valid = b;
162     }
163 
164     /**
165      * Get session validity
166      * 
167      * @return true if session is valid
168      */
169     public boolean isValid() {
170         return valid;
171     }
172 
173     /**
174      * Get overall start position
175      * 
176      * @return overall start
177      */
178     public long getStart() {
179         if (overall != null) {
180             return overall.getPosition();
181         }
182         return 0;
183     }
184 
185     /**
186      * Get overall length
187      * 
188      * @return overall length
189      */
190     public long getLength() {
191         if (overall != null) {
192             return overall.getLength();
193         }
194         return 0;
195     }
196 
197     /**
198      * Get overall position record for the session
199      * 
200      * @return overall position record
201      */
202     public PositionRecord getOverall() {
203         return overall;
204     }
205 
206     /**
207      * Add and validate a list of header records
208      * 
209      * @param h list of PositionRecord
210      * @throws ParserException when a record is out of bounds
211      */
212     public void addHeaderRecs(@Nullable List<PositionRecord> h) throws ParserException {
213         if (CollectionUtils.isNotEmpty(h)) {
214             validateList(h);
215             header.addAll(h);
216         }
217     }
218 
219     /**
220      * Add and validate a list of data records
221      * 
222      * @param d list of PositionRecord
223      * @throws ParserException when a record is out of bounds
224      */
225     public void addDataRecs(@Nullable List<PositionRecord> d) throws ParserException {
226         if (CollectionUtils.isNotEmpty(d)) {
227             validateList(d);
228             data.addAll(d);
229         }
230     }
231 
232     /**
233      * Add and validate a list of footer records
234      * 
235      * @param f list of PositionRecord
236      * @throws ParserException when a record is out of bounds
237      */
238     public void addFooterRecs(@Nullable List<PositionRecord> f) throws ParserException {
239         if (CollectionUtils.isNotEmpty(f)) {
240             validateList(f);
241             footer.addAll(f);
242         }
243     }
244 
245     /**
246      * Add and validate a header record
247      * 
248      * @param r the record to add
249      * @throws ParserException when a record is out of bounds
250      */
251     public void addHeaderRec(PositionRecord r) throws ParserException {
252         validateRecord(r);
253         header.add(r);
254     }
255 
256     /**
257      * Create a position record and add it to the header
258      * 
259      * @param pos starting position
260      * @param len length of data
261      * @throws ParserException when out of bounds
262      */
263     public void addHeaderRec(int pos, int len) throws ParserException {
264         addHeaderRec(new PositionRecord(pos, len));
265     }
266 
267     /**
268      * Add and validate a footer record
269      * 
270      * @param r the record to add
271      * @throws ParserException when a record is out of bounds
272      */
273     public void addFooterRec(PositionRecord r) throws ParserException {
274         validateRecord(r);
275         footer.add(r);
276     }
277 
278     /**
279      * Create a position record and add it to the footer
280      * 
281      * @param pos starting position
282      * @param len length of data
283      * @throws ParserException when out of bounds
284      */
285     public void addFooterRec(int pos, int len) throws ParserException {
286         addFooterRec(new PositionRecord(pos, len));
287     }
288 
289     /**
290      * Add a validate a data record
291      * 
292      * @param r the record to add
293      * @throws ParserException when a record is out of bounds
294      */
295     public void addDataRec(PositionRecord r) throws ParserException {
296         validateRecord(r);
297         data.add(r);
298     }
299 
300     /**
301      * Create a position record and add it to the data
302      * 
303      * @param pos starting position
304      * @param len length of data
305      * @throws ParserException when out of bounds
306      */
307     public void addDataRec(int pos, int len) throws ParserException {
308         addDataRec(new PositionRecord(pos, len));
309     }
310 
311     /**
312      * Add a metadata position record
313      * 
314      * @param name name of metadata item
315      * @param r position record of data
316      * @throws ParserException when out of bounds
317      */
318     public void addMetaDataRec(String name, PositionRecord r) throws ParserException {
319         validateRecord(r);
320         metaData.put(name, r);
321     }
322 
323     /**
324      * Add a metadata position record
325      * 
326      * @param name name of metadata item
327      * @param rec string value of metadata item
328      */
329     public void addMetaDataRec(String name, String rec) {
330         metaData.put(name, rec);
331     }
332 
333     /**
334      * Count of data position records
335      * 
336      * @return count
337      */
338     public int getDataCount() {
339         return data.size();
340     }
341 
342     /**
343      * Count of footer position records
344      * 
345      * @return count
346      */
347     public int getFooterCount() {
348         return footer.size();
349     }
350 
351     /**
352      * Count of header position records
353      * 
354      * @return count
355      */
356     public int getHeaderCount() {
357         return header.size();
358     }
359 
360     /**
361      * Count of metadata records
362      * 
363      * @return count
364      */
365     public int getMetaDataCount() {
366         return metaData.size();
367     }
368 
369     /**
370      * Get footer position records
371      * 
372      * @return list of PositionRecord
373      */
374     public List<PositionRecord> getFooter() {
375         return footer;
376     }
377 
378     /**
379      * List header records
380      * 
381      * @return list of PositionRecord
382      */
383     public List<PositionRecord> getHeader() {
384         return header;
385     }
386 
387     /**
388      * Get data position records
389      * 
390      * @return list of PositionRecord
391      */
392     public List<PositionRecord> getData() {
393         return data;
394     }
395 
396     /**
397      * Return a map of metadata information. Some values will be Strings, others will be PositionRecords
398      * 
399      * @return metadata
400      */
401     public Map<String, Object> getMetaData() {
402         return metaData;
403     }
404 
405     /**
406      * Info pump for debugging output
407      * 
408      * @return string representation
409      */
410     @Override
411     public String toString() {
412         StringBuilder sb = new StringBuilder();
413         sb.append("Session is ").append((this.isValid() ? "" : "not")).append("valid").append("\n");
414         sb.append("Session overall ").append(this.getOverall()).append("\n");
415         sb.append("Header record ").append(this.getHeader()).append("\n");
416         sb.append("Data record ").append(this.getData()).append("\n");
417         sb.append("Footer record ").append(this.getFooter()).append("\n");
418         Map<String, Object> m = this.getMetaData();
419         sb.append("Metadata count ").append((m == null ? 0 : m.size())).append("\n");
420         return sb.toString();
421     }
422 
423     /**
424      * Validation of one PositionRecord. Does nothing if overall bounds not yet set
425      * 
426      * @param r the record to check
427      * @throws ParserException when out of bounds
428      */
429     protected void validateRecord(PositionRecord r) throws ParserException {
430         if (overall != null && r != null && ((r.getPosition() < overall.getPosition()) || (r.getEnd() > overall.getEnd()))) {
431             throw new ParserException("Position record " + r + " is out of bounds for the data " + overall);
432         }
433     }
434 
435     /**
436      * Validate a list of PositionRecord. Does nothing if overall bounds not yet set
437      * 
438      * @param list the list of PositionRecord
439      * @throws ParserException when out of bounds
440      */
441     protected void validateList(@Nullable List<PositionRecord> list) throws ParserException {
442 
443         if (overall == null || list == null) {
444             return;
445         }
446 
447         for (PositionRecord p : list) {
448             validateRecord(p);
449         }
450     }
451 
452     /**
453      * Validate everything. Does nothing if overall bounds not yet set
454      * 
455      * @throws ParserException when out of bounds
456      */
457     protected void validateAll() throws ParserException {
458         if (overall == null) {
459             return;
460         }
461         validateList(header);
462         validateList(footer);
463         validateList(data);
464         validateMetaData();
465     }
466 
467     /**
468      * Validate the PositionRecords in the metadata map Does nothing if overall bounds not yet set
469      * 
470      * @throws ParserException when out of bounds
471      */
472     protected void validateMetaData() throws ParserException {
473         if (overall == null || metaData.isEmpty()) {
474             return;
475         }
476 
477         for (Object obj : metaData.values()) {
478             if (obj instanceof PositionRecord) {
479                 validateRecord((PositionRecord) obj);
480             }
481         }
482     }
483 
484 }