1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package entagged.audioformats.asf.data;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.UnsupportedEncodingException;
23 import java.util.Arrays;
24 import java.util.HashSet;
25
26 import entagged.audioformats.asf.util.Utils;
27
28 /***
29 * This class is a wrapper for properties within a
30 * {@link entagged.audioformats.asf.data.ExtendedContentDescription}.<br>
31 *
32 * @author Christian Laireiter
33 */
34 public final class ContentDescriptor implements Comparable {
35 /***
36 * This field stores all values of the "ID_"-constants.
37 */
38 public final static HashSet COMMON_FIELD_IDS;
39
40 /***
41 * This constant gives the common id (name) for the "album" field in an asf
42 * extended content description.
43 */
44 public final static String ID_ALBUM = "WM/AlbumTitle";
45
46 /***
47 * This constant gives the common id (name) for the "artist" field in an asf
48 * extended content description.
49 */
50 public final static String ID_ARTIST ="WM/AlbumArtist";
51
52 /***
53 * This constant gives the common id (name) for the "genre" field in an asf
54 * extended content description.
55 */
56 public final static String ID_GENRE = "WM/Genre";
57
58 /***
59 * This constant gives the common id (name) for the "genre Id" field in an
60 * asf extended content description.
61 */
62 public final static String ID_GENREID = "WM/GenreID";
63
64 /***
65 * This constant gives the common id (name) for the "track number" field in
66 * an asf extended content description.
67 */
68 public final static String ID_TRACKNUMBER = "WM/TrackNumber";
69
70 /***
71 * This constant gives the common id (name) for the "year" field in an asf
72 * extended content description.
73 */
74 public final static String ID_YEAR = "WM/Year";
75
76 /***
77 * Constant for the content descriptor-type for binary data.
78 */
79 public final static int TYPE_BINARY = 1;
80
81 /***
82 * Constant for the content descriptor-type for booleans.
83 */
84 public final static int TYPE_BOOLEAN = 2;
85
86 /***
87 * Constant for the content descriptor-type for integers (32-bit). <br>
88 */
89 public final static int TYPE_DWORD = 3;
90
91 /***
92 * Constant for the content descriptor-type for integers (64-bit). <br>
93 */
94 public final static int TYPE_QWORD = 4;
95
96 /***
97 * Constant for the content descriptor-type for Strings.
98 */
99 public final static int TYPE_STRING = 0;
100
101 /***
102 * Constant for the content descriptor-type for integers (16-bit). <br>
103 */
104 public final static int TYPE_WORD = 5;
105
106 static {
107 COMMON_FIELD_IDS = new HashSet();
108 COMMON_FIELD_IDS.add(ID_ALBUM);
109 COMMON_FIELD_IDS.add(ID_ARTIST);
110 COMMON_FIELD_IDS.add(ID_GENRE);
111 COMMON_FIELD_IDS.add(ID_GENREID);
112 COMMON_FIELD_IDS.add(ID_TRACKNUMBER);
113 COMMON_FIELD_IDS.add(ID_YEAR);
114 }
115
116 /***
117 * The binary representation of the value.
118 */
119 protected byte[] content = new byte[0];
120
121 /***
122 * This field shows the type of the content descriptor. <br>
123 *
124 * @see #TYPE_BINARY
125 * @see #TYPE_BOOLEAN
126 * @see #TYPE_DWORD
127 * @see #TYPE_QWORD
128 * @see #TYPE_STRING
129 * @see #TYPE_WORD
130 */
131 private int descriptorType;
132
133 /***
134 * The name of the content descriptor.
135 */
136 private final String name;
137
138 /***
139 * Creates an Instance.
140 *
141 * @param propName
142 * Name of the ContentDescriptor.
143 * @param propType
144 * Type of the content descriptor. See {@link #descriptorType}
145 *
146 */
147 public ContentDescriptor(String propName, int propType) {
148 if (propName == null) {
149 throw new IllegalArgumentException("Arguments must not be null.");
150 }
151 Utils.checkStringLengthNullSafe(propName);
152 this.name = propName;
153 this.descriptorType = propType;
154 }
155
156 /***
157 * (overridden)
158 *
159 * @see java.lang.Object#clone()
160 */
161 public Object clone() throws CloneNotSupportedException {
162 return createCopy();
163 }
164
165 /***
166 * (overridden)
167 *
168 * @see java.lang.Comparable#compareTo(java.lang.Object)
169 */
170 public int compareTo(Object o) {
171 int result = 0;
172 if (o instanceof ContentDescriptor) {
173 ContentDescriptor other = (ContentDescriptor) o;
174 result = getName().compareTo(other.getName());
175 }
176 return result;
177 }
178
179 /***
180 * This mehtod creates a copy of the current object. <br>
181 * All data will be copied, too. <br>
182 *
183 * @return A new Contentdescriptor containing the same values as the current
184 * one.
185 */
186 public ContentDescriptor createCopy() {
187 ContentDescriptor result = new ContentDescriptor(getName(), getType());
188 result.content = getRawData();
189 return result;
190 }
191
192 /***
193 * (overridden)
194 *
195 * @see java.lang.Object#equals(java.lang.Object)
196 */
197 public boolean equals(Object obj) {
198 boolean result = false;
199 if (obj instanceof ContentDescriptor) {
200 if (obj == this) {
201 result = true;
202 } else {
203 ContentDescriptor other = (ContentDescriptor) obj;
204 result = other.getName().equals(getName())
205 && other.descriptorType == this.descriptorType
206 && Arrays.equals(this.content, other.content);
207 }
208 }
209 return result;
210 }
211
212 /***
213 * Returns the value of the ContentDescriptor as a Boolean. <br>
214 * If no Conversion is Possible false is returned. <br>
215 * <code>true</code> if first byte of {@link #content}is not zero.
216 *
217 * @return boolean representation of the current value.
218 */
219 public boolean getBoolean() {
220 return content.length > 0 && content[0] != 0;
221 }
222
223 /***
224 * This method will return a byte array, which can directly be written into
225 * an "Extended Content Description"-chunk. <br>
226 *
227 * @return byte[] with the data, that occurs in asf files.
228 */
229 public byte[] getBytes() {
230 ByteArrayOutputStream result = new ByteArrayOutputStream();
231 try {
232 byte[] nameBytes = getName().getBytes("UTF-16LE");
233
234
235 result.write(Utils.getBytes(nameBytes.length + 2, 2));
236
237 result.write(nameBytes);
238
239 result.write(Utils.getBytes(0, 2));
240
241 result.write(Utils.getBytes(getType(), 2));
242
243
244
245 if (this.getType() == TYPE_STRING) {
246
247 result.write(Utils.getBytes(content.length + 2, 2));
248
249 result.write(content);
250
251 result.write(Utils.getBytes(0, 2));
252 } else {
253 result.write(Utils.getBytes(content.length, 2));
254 result.write(content);
255 }
256 } catch (Exception e) {
257 e.printStackTrace();
258 }
259 return result.toByteArray();
260 }
261
262 /***
263 * This method returns the name of the content descriptor.
264 *
265 * @return Name.
266 */
267 public String getName() {
268 return this.name;
269 }
270
271 /***
272 * This method returns the value of the content descriptor as an integer.
273 * <br>
274 * Converts the needed amount of byte out of {@link #content}to a number.
275 * <br>
276 * Only possible if {@link #getType()}equals on of the following: <br>
277 * <li>
278 *
279 * @see #TYPE_BOOLEAN </li>
280 * <li>
281 * @see #TYPE_DWORD </li>
282 * <li>
283 * @see #TYPE_QWORD </li>
284 * <li>
285 * @see #TYPE_WORD </li>
286 *
287 * @return integer value.
288 */
289 public long getNumber() {
290 long result = 0;
291 int bytesNeeded = -1;
292 switch (getType()) {
293 case TYPE_BOOLEAN:
294 bytesNeeded = 1;
295 break;
296 case TYPE_DWORD:
297 bytesNeeded = 4;
298 break;
299 case TYPE_QWORD:
300 bytesNeeded = 8;
301 break;
302 case TYPE_WORD:
303 bytesNeeded = 2;
304 break;
305 default:
306 throw new UnsupportedOperationException(
307 "The current type doesn't allow an interpretation as a number.");
308 }
309 if (bytesNeeded > content.length) {
310 throw new IllegalStateException(
311 "The stored data cannot represent the type of current object.");
312 }
313 for (int i = 0; i < bytesNeeded; i++) {
314 result |= (content[i] << (i * 8));
315 }
316 return result;
317 }
318
319 /***
320 * This method returns a copy of the content of the descriptor. <br>
321 *
322 * @return The content in binary representation, as it would be written to
323 * asf file. <br>
324 */
325 public byte[] getRawData() {
326 byte[] copy = new byte[this.content.length];
327 System.arraycopy(copy, 0, this.content, 0, this.content.length);
328 return copy;
329 }
330
331 /***
332 * Returns the value of the ContentDescriptor as a String. <br>
333 *
334 * @return String - Representation Value
335 */
336 public String getString() {
337 String result = "";
338 switch (getType()) {
339 case TYPE_BINARY:
340 result = "binary data";
341 break;
342 case TYPE_BOOLEAN:
343 result = String.valueOf(getBoolean());
344 break;
345 case TYPE_QWORD:
346 case TYPE_DWORD:
347 case TYPE_WORD:
348 result = String.valueOf(getNumber());
349 break;
350 case TYPE_STRING:
351 try {
352 result = new String(content, "UTF-16LE");
353 } catch (Exception e) {
354 e.printStackTrace();
355 }
356 break;
357 default:
358 throw new IllegalStateException("Current type is not known.");
359 }
360 return result;
361 }
362
363 /***
364 * Returns the type of the content descriptor. <br>
365 *
366 * @see #TYPE_BINARY
367 * @see #TYPE_BOOLEAN
368 * @see #TYPE_DWORD
369 * @see #TYPE_QWORD
370 * @see #TYPE_STRING
371 * @see #TYPE_WORD
372 *
373 * @return the value of {@link #descriptorType}
374 */
375 public int getType() {
376 return this.descriptorType;
377 }
378
379 /***
380 * This method checks whether the name of the current field is one of the
381 * commonly specified fields. <br>
382 *
383 * @see #ID_ALBUM
384 * @see #ID_GENRE
385 * @see #ID_GENREID
386 * @see #ID_TRACKNUMBER
387 * @see #ID_YEAR
388 * @return <code>true</code> if a common field.
389 */
390 public boolean isCommon() {
391 return COMMON_FIELD_IDS.contains(this.getName());
392 }
393
394 /***
395 * This method checks if the binary data is empty. <br>
396 * Disregarding the type of the descriptor its content is stored as a byte
397 * array.
398 *
399 * @return <code>true</code> if no value is set.
400 */
401 public boolean isEmpty() {
402 return this.content.length == 0;
403 }
404
405 /***
406 * Sets the Value of the current content descriptor. <br>
407 * Using this method will change {@link #descriptorType}to
408 * {@link #TYPE_BINARY}.<br>
409 *
410 * @param data
411 * Value to set.
412 * @throws IllegalArgumentException
413 * If the byte array is greater that 65535 bytes.
414 */
415 public void setBinaryValue(byte[] data) throws IllegalArgumentException {
416 if (data.length > 65535) {
417 throw new IllegalArgumentException(
418 "Too many bytes. 65535 is maximum.");
419 }
420 this.content = data;
421 this.descriptorType = TYPE_BINARY;
422 }
423
424 /***
425 * Sets the Value of the current content descriptor. <br>
426 * Using this method will change {@link #descriptorType}to
427 * {@link #TYPE_BOOLEAN}.<br>
428 *
429 * @param value
430 * Value to set.
431 */
432 public void setBooleanValue(boolean value) {
433 this.content = new byte[] { value ? (byte) 1 : 0, 0, 0, 0 };
434 this.descriptorType = TYPE_BOOLEAN;
435 }
436
437 /***
438 * Sets the Value of the current content descriptor. <br>
439 * Using this method will change {@link #descriptorType}to
440 * {@link #TYPE_DWORD}.
441 *
442 * @param value
443 * Value to set.
444 */
445 public void setDWordValue(long value) {
446 this.content = Utils.getBytes(value, 4);
447 this.descriptorType = TYPE_DWORD;
448 }
449
450 /***
451 * Sets the Value of the current content descriptor. <br>
452 * Using this method will change {@link #descriptorType}to
453 * {@link #TYPE_QWORD}
454 *
455 * @param value
456 * Value to set.
457 */
458 public void setQWordValue(long value) {
459 this.content = Utils.getBytes(value, 8);
460 this.descriptorType = TYPE_QWORD;
461 }
462
463 /***
464 * Sets the Value of the current content descriptor. <br>
465 * Using this method will change {@link #descriptorType}to
466 * {@link #TYPE_STRING}.
467 *
468 * @param value
469 * Value to set.
470 * @throws IllegalArgumentException
471 * If byte representation would take more than 65535 Bytes.
472 */
473 public void setStringValue(String value) throws IllegalArgumentException {
474 try {
475 byte[] tmp = value.getBytes("UTF-16LE");
476 if (tmp.length > 65535) {
477 throw new IllegalArgumentException(
478 "Byte representation of String in "
479 + "\"UTF-16LE\" is to great. (Maximum is 65535 Bytes)");
480 }
481 this.content = tmp;
482 } catch (UnsupportedEncodingException e) {
483 e.printStackTrace();
484 this.content = new byte[0];
485 }
486 this.descriptorType = TYPE_STRING;
487 }
488
489 /***
490 * Sets the Value of the current content descriptor. <br>
491 * Using this method will change {@link #descriptorType}to
492 * {@link #TYPE_WORD}
493 *
494 * @param value
495 * Value to set.
496 */
497 public void setWordValue(int value) {
498 this.content = Utils.getBytes(value, 2);
499 this.descriptorType = TYPE_WORD;
500 }
501
502 /***
503 * (overridden)
504 *
505 * @see java.lang.Object#toString()
506 */
507 public String toString() {
508 return getName()
509 + " : "
510 + new String[] { "String: ", "Binary: ", "Boolean: ",
511 "DWORD: ", "QWORD:", "WORD:" }[this.descriptorType]
512 + getString();
513 }
514 }