1 /***
2 * Java DAB EPG API - Serialize/Deserialize To/From POJOs to XML/Binary as per
3 * ETSI specifications TS 102 818 (XML Specification for DAB EPG) and TS 102
4 * 371 (Transportation and Binary Encoding Specification for EPG).
5 *
6 * Copyright (C) 2007 GCap Media PLC
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22 package com.gcapmedia.dab.epg.binary;
23
24 import java.util.*;
25
26 /***
27 * Element binary encoding
28 */
29 public class Element implements Encodable {
30
31 /***
32 * Element tag uniquely identifies the element.
33 */
34 private final ElementTag tag;
35
36 /***
37 * Element attributes
38 */
39 private final Collection<Attribute> attributes;
40
41 /***
42 * Child elements
43 */
44 private final Collection<Element> children;
45
46 /***
47 * Element CDATA content
48 */
49 private CData cdata;
50
51 /***
52 * Token table
53 */
54 private TokenTable tokens;
55
56 /***
57 *
58 * @param tag
59 */
60 public Element(ElementTag tag) {
61 this.tag = tag;
62 this.attributes = new ArrayList<Attribute>();
63 this.children = new ArrayList<Element>();
64 }
65
66 /***
67 * @return Returns the element tag
68 */
69 public ElementTag getTag() {
70 return tag;
71 }
72
73 public void setTokenTable(TokenTable tokens) {
74 this.tokens = tokens;
75 }
76
77 /***
78 * Add an attribute to this element
79 * @param attribute Attribute to add
80 */
81 public void addAttribute(Attribute attribute) {
82 this.attributes.add(attribute);
83 }
84
85 /***
86 * @return Returns any element attributes
87 */
88 public Collection<Attribute> getAttributes() {
89 return Collections.unmodifiableCollection(attributes);
90 }
91
92 /***
93 * Returns a specific attribute
94 * @param tag Tag of attribute to retrieve
95 * @return Returns the attribute if found, null otherwise
96 */
97 public Attribute getAttribute(AttributeTag tag) {
98 Attribute result = null;
99 for(Attribute attribute : attributes) {
100 if(attribute.getTag() == tag) {
101 result = attribute;
102 break;
103 }
104 }
105 return result;
106 }
107
108 /***
109 * Returns whether a specific attribute is defined for this element
110 * @param tag Tag of attribute to test for
111 * @return Returns true if the attribute is defined for this element
112 */
113 public boolean hasAttribute(AttributeTag tag) {
114 return getAttribute(tag) != null;
115 }
116
117 /***
118 * Add a child to this element
119 * @param child Child to add
120 */
121 public void addChild(Element child) {
122 this.children.add(child);
123 }
124
125 /***
126 * @return Returns all child elements
127 */
128 public Collection<Element> getChildren() {
129 return Collections.unmodifiableCollection(children);
130 }
131
132 /***
133 * Returns child elements of the specified type
134 * @param tag Element tag to return
135 * @return
136 */
137 public Collection<Element> getChildren(ElementTag tag) {
138 List<Element> result = new ArrayList<Element>();
139 for(Element child : children) {
140 if(child.getTag() == tag) {
141 result.add(child);
142 }
143 }
144 return Collections.unmodifiableCollection(result);
145 }
146
147 /***
148 * Returns whether any elements of the specified type are children to this element
149 * @param tag Element tag to test for
150 * @return True if any elements of the specified type are children to this element
151 */
152 public boolean hasChildren(ElementTag tag) {
153 return getChildren(tag).size() > 0;
154 }
155
156 /***
157 * Set this elements CDATA
158 * @param cdata CDATA to set
159 */
160 public void setCData(CData cdata) {
161 this.cdata = cdata;
162 }
163
164 /***
165 * @return Returns any associated CData
166 */
167 public CData getCData() {
168 return cdata;
169 }
170
171 /***
172 * @see com.gcapmedia.dab.epg.binary.Encodable#getBytes()
173 */
174 public byte[] getBytes() {
175
176
177 byte[] databytes = new byte[0];
178 for(Attribute attribute : attributes) {
179 byte[] tmp = attribute.getBytes();
180 byte[] bytes = new byte[databytes.length + tmp.length];
181 System.arraycopy(databytes, 0, bytes, 0, databytes.length);
182 System.arraycopy(tmp, 0, bytes, databytes.length, tmp.length);
183 databytes = bytes;
184 }
185 for(Element child : children) {
186 byte[] tmp = child.getBytes();
187 byte[] bytes = new byte[databytes.length + tmp.length];
188 System.arraycopy(databytes, 0, bytes, 0, databytes.length);
189 System.arraycopy(tmp, 0, bytes, databytes.length, tmp.length);
190 databytes = bytes;
191 }
192 if(cdata != null) {
193 byte[] tmp = cdata.getBytes();
194 byte[] bytes = new byte[databytes.length + tmp.length];
195 System.arraycopy(databytes, 0, bytes, 0, databytes.length);
196 System.arraycopy(tmp, 0, bytes, databytes.length, tmp.length);
197 databytes = bytes;
198 }
199 BitBuilder bits = new BitBuilder(8);
200
201
202 bits.put(0, 8, tag.getBytes());
203
204
205
206
207 int datalength = databytes.length;
208 if(datalength <= 253) {
209 bits.setSize(16 + datalength * 8);
210 bits.put(8, 8, datalength);
211 } else if(datalength >= 1<<8 && datalength <= 1<<16) {
212 bits.setSize(32 + datalength * 8);
213 bits.put(8, 8, 0xFE);
214 bits.put(16, 16, datalength);
215 } else if(datalength > 1<<16 && datalength <= 1<<24) {
216 bits.setSize(40 + datalength * 8);
217 bits.put(8, 8, 0xFF);
218 bits.put(16, 24, datalength);
219 } else {
220 throw new IndexOutOfBoundsException("Element data length exceeds the maximum allowed by the extended element length (24bits): " + datalength + " > " + (1<<24));
221 }
222
223
224 bits.put(bits.position(), databytes.length * 8, databytes);
225
226 byte[] result = bits.toByteArray();
227
228 return result;
229 }
230
231 /***
232 * @see com.gcapmedia.dab.epg.binary.Encodable#getLength()
233 */
234 public int getLength() {
235 return getBytes().length;
236 }
237
238 /***
239 * Parse an object from its byte array representation
240 * @param bytes Byte array representation
241 */
242 public static Element fromBytes(byte[] bytes) {
243 return fromBytes(bytes, null);
244 }
245
246 /***
247 * Parse an object from its byte array representation
248 * @param bytes Byte array representation
249 */
250 public static Element fromBytes(byte[] bytes, TokenTable tokens) {
251
252 BitParser parser = new BitParser(bytes);
253
254 int position = 0;
255
256
257 ElementTag tag = ElementTag.fromBytes(parser.getByteArray(position, 8));
258 Element element = new Element(tag);
259 position += 8;
260
261
262 int length = parser.getInt(position, 8);
263 position += 8;
264 if(length == 0xFE) {
265
266 length = parser.getInt(position, 16);
267 position += 16;
268 } else if(length == 0xFE) {
269
270 length = parser.getInt(position, 24);
271 position += 24;
272 }
273 int dataend = position + length * 8;
274
275
276 while(position < dataend) {
277
278 int t = parser.getInt(position, 8);
279
280
281 int datalength = parser.getInt(position + 8, 8);
282 int datastart = position + 8;
283 if(datalength == 0xFE) {
284
285 datalength = parser.getInt(position + 16, 16);
286 datastart += 16;
287 } else if(datalength == 0xFE) {
288
289 datalength = parser.getInt(position + 16, 24);
290 datastart += 24;
291 }
292
293
294 byte[] data = parser.getByteArray(position, 8 + (datastart - position) + datalength * 8);
295
296 if(t == CData.TAG) {
297 CData cdata = CData.fromBytes(data, tokens);
298 element.setCData(cdata);
299
300 } else if(t == TokenTable.TAG && (tag == ElementTag.epg || tag == ElementTag.serviceInformation)) {
301 tokens = TokenTable.fromBytes(data);
302
303 } else if(t == 0x05 && tag == ElementTag.epg) {
304
305 } else if(t >= 0x10 && t <=0x7E) {
306 Element child = Element.fromBytes(data, tokens);
307 element.addChild(child);
308
309 } else if((t & 0xFF) >= 0x80 && (t & 0xFF) <=0xFF) {
310 Attribute attribute = Attribute.fromBytes(tag, data, tokens);
311 element.addAttribute(attribute);
312
313 } else {
314 throw new IllegalArgumentException("Unknown child tag found at " + position + " (byte " + (position / 8) + ") : " + t);
315 }
316 position = 8 + datastart + (datalength * 8);
317 }
318
319 return element;
320 }
321
322 /***
323 * @see java.lang.Object#equals(java.lang.Object)
324 */
325 @Override
326 public boolean equals(Object obj) {
327 if(!(obj instanceof Element)) {
328 return false;
329 }
330 Element that = (Element)obj;
331
332
333 if(this.tag != that.tag) {
334 return false;
335 }
336
337
338 if(!this.attributes.equals(that.attributes)) {
339 return false;
340 }
341
342
343 if(!this.children.equals(that.children)) {
344 return false;
345 }
346
347
348 if((this.cdata != null && that.cdata == null) ||
349 (this.cdata == null && that.cdata != null)) {
350 return false;
351 }
352 if((this.cdata != null && that.cdata != null) &&
353 !(this.cdata.equals(that.cdata))) {
354 return false;
355 }
356
357 return true;
358 }
359
360 /***
361 * @see java.lang.Object#toString()
362 */
363 public String toString() {
364 List<String> subs = new ArrayList<String>();
365 if(children.size() > 0) {
366 StringBuilder tmp = new StringBuilder();
367 tmp.append("elements=[");
368 for(Element element : children) {
369 tmp.append(element.getTag() + ",");
370 }
371 tmp.append("]");
372 subs.add(tmp.toString());
373 }
374 if(attributes.size() > 0) {
375 subs.add("attributes=" + attributes.toString());
376 }
377 if(cdata != null) {
378 subs.add("data=" + cdata + "}");
379 }
380 return tag.toString() + "{" + subs.toString() + "}";
381 }
382
383 }