View Javadoc

1   /*
2    * Entagged Audio Tag library
3    * Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
4    * 
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *  
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   * 
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
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             // Write the number of bytes the name needs. +2 because of the
234             // Zero term character.
235             result.write(Utils.getBytes(nameBytes.length + 2, 2));
236             // Write the name itself
237             result.write(nameBytes);
238             // Write zero term character
239             result.write(Utils.getBytes(0, 2));
240             // Write the type of the current descriptor
241             result.write(Utils.getBytes(getType(), 2));
242             /*
243              * Now the content.
244              */
245             if (this.getType() == TYPE_STRING) {
246                 // String length +2 for zero termination
247                 result.write(Utils.getBytes(content.length + 2, 2));
248                 // Value
249                 result.write(content);
250                 // Zero term
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 }