View Javadoc

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 		// need to shove the 3 things together: attributes, child, CDATA
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 		// b0-b7: element tag
202 		bits.put(0, 8, tag.getBytes());
203 		
204 		// b8-15: element data length (0-253 bytes)
205 		// b16-31: extended element length (256-65536 bytes)
206 		// b16-39: extended element length (65537-16777216 bytes)
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 		// b16/32/40-(16/32/40 + bytes*8): attribute data bytes
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 		// b0-7: element tag
257 		ElementTag tag = ElementTag.fromBytes(parser.getByteArray(position, 8));
258 		Element element = new Element(tag);
259 		position += 8;
260 		
261 		// b8-15: element value length
262 		int length = parser.getInt(position, 8);
263 		position += 8;
264 		if(length == 0xFE) {
265 			// b16-31: extended attribute length (16bits)
266 			length = parser.getInt(position, 16);
267 			position += 16;
268 		} else if(length == 0xFE) {
269 			// b16-39: extended attribute length (24bits)
270 			length = parser.getInt(position, 24);
271 			position += 24;
272 		}
273 		int dataend = position + length * 8;
274 		
275 		// b16/32/40-end: element value data (attributes, children, CDATA)
276 		while(position < dataend) {
277 
278 			int t = parser.getInt(position, 8);
279 			
280 			// get the correct data length and end position
281 			int datalength = parser.getInt(position + 8, 8);
282 			int datastart = position + 8;
283 			if(datalength == 0xFE) {
284 				// b16-31: extended attribute length (16bits)
285 				datalength = parser.getInt(position + 16, 16);
286 				datastart += 16;
287 			} else if(datalength == 0xFE) {
288 				// b16-39: extended attribute length (24bits)
289 				datalength = parser.getInt(position + 16, 24);
290 				datastart += 24;
291 			}	
292 			
293 			// pass the complete data to the correct decoder based on the tag
294 			byte[] data = parser.getByteArray(position, 8 + (datastart - position) + datalength * 8);
295 			
296 			if(t == CData.TAG) { // CDATA
297 				CData cdata = CData.fromBytes(data, tokens);
298 				element.setCData(cdata);
299 				//System.out.println("CDATA: " + cdata);
300 			} else if(t == TokenTable.TAG && (tag == ElementTag.epg || tag == ElementTag.serviceInformation)) { // Token Table
301 				tokens = TokenTable.fromBytes(data);
302 				//System.out.println("token table: " + tokens);
303 			} else if(t == 0x05 && tag == ElementTag.epg) {
304 				// TODO parse the default content ID
305 			} else if(t >= 0x10 && t <=0x7E) { // Standard Elements
306 				Element child = Element.fromBytes(data, tokens);
307 				element.addChild(child);
308 				//System.out.println("element: " + element);
309 			} else if((t & 0xFF) >= 0x80 && (t & 0xFF) <=0xFF) { // Attributes
310 				Attribute attribute = Attribute.fromBytes(tag, data, tokens);
311 				element.addAttribute(attribute);
312 				//System.out.println("attribute: " + attribute);
313 			} else { // unknown
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 		// tag
333 		if(this.tag != that.tag) {
334 			return false;
335 		}
336 		
337 		// attributes
338 		if(!this.attributes.equals(that.attributes)) {
339 			return false;
340 		}
341 		
342 		// children
343 		if(!this.children.equals(that.children)) {
344 			return false;
345 		}
346 		
347 		// CDATA
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 }