Coverage Report - org.jaudiotagger.audio.asf.data.MetadataDescriptor
 
Classes in this File Line Coverage Branch Coverage Complexity
MetadataDescriptor
89%
198/221
79%
99/125
3.351
 
 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 org.jaudiotagger.audio.asf.data;
 20  
 
 21  
 import org.jaudiotagger.audio.asf.util.Utils;
 22  
 import org.jaudiotagger.logging.ErrorMessage;
 23  
 import org.jaudiotagger.tag.TagOptionSingleton;
 24  
 
 25  
 import java.io.ByteArrayOutputStream;
 26  
 import java.io.IOException;
 27  
 import java.io.OutputStream;
 28  
 import java.io.UnsupportedEncodingException;
 29  
 import java.math.BigInteger;
 30  
 import java.util.Arrays;
 31  
 import java.util.logging.Logger;
 32  
 
 33  
 /**
 34  
  * This structure represents metadata objects in ASF {@link MetadataContainer}.<br>
 35  
  * The values are
 36  
  * {@linkplain ContainerType#assertConstraints(String, byte[], int, int, int)
 37  
  * checked} against the capability introduced by the given
 38  
  * {@link ContainerType} at construction.<br>
 39  
  * <br>
 40  
  * <b>Limitation</b>: Even though some container types do not restrict the data
 41  
  * size to {@link Integer#MAX_VALUE}, this implementation does it (due to java
 42  
  * nature).<br>
 43  
  * 2 GiB of data should suffice, and even be to large for normal java heap.
 44  
  * 
 45  
  * @author Christian Laireiter
 46  
  */
 47  4
 public class MetadataDescriptor implements Comparable<MetadataDescriptor>,
 48  
         Cloneable {
 49  
 
 50  
     /**
 51  
      * Maximum value for WORD.
 52  
      */
 53  4
     public static final long DWORD_MAXVALUE = new BigInteger("FFFFFFFF", 16)
 54  
             .longValue();
 55  
 
 56  
     /**
 57  
      * Logger instance.
 58  
      */
 59  4
     private static final Logger LOGGER = Logger
 60  
             .getLogger("org.jaudiotagger.audio.asf.data");
 61  
 
 62  
     /**
 63  
      * The maximum language index allowed. (exclusive)
 64  
      */
 65  
     public static final int MAX_LANG_INDEX = 127;
 66  
 
 67  
     /**
 68  
      * Maximum stream number. (inclusive)
 69  
      */
 70  
     public static final int MAX_STREAM_NUMBER = 127;
 71  
 
 72  
     /**
 73  
      * Maximum value for a QWORD value (64 bit unsigned).<br>
 74  
      */
 75  4
     public static final BigInteger QWORD_MAXVALUE = new BigInteger(
 76  
             "FFFFFFFFFFFFFFFF", 16);
 77  
 
 78  
     /**
 79  
      * Constant for the metadata descriptor-type for binary data.
 80  
      */
 81  
     public final static int TYPE_BINARY = 1;
 82  
 
 83  
     /**
 84  
      * Constant for the metadata descriptor-type for booleans.
 85  
      */
 86  
     public final static int TYPE_BOOLEAN = 2;
 87  
 
 88  
     /**
 89  
      * Constant for the metadata descriptor-type for DWORD (32-bit unsigned). <br>
 90  
      */
 91  
     public final static int TYPE_DWORD = 3;
 92  
 
 93  
     /**
 94  
      * Constant for the metadata descriptor-type for GUIDs (128-bit).<br>
 95  
      */
 96  
     public final static int TYPE_GUID = 6;
 97  
 
 98  
     /**
 99  
      * Constant for the metadata descriptor-type for QWORD (64-bit unsinged). <br>
 100  
      */
 101  
     public final static int TYPE_QWORD = 4;
 102  
 
 103  
     /**
 104  
      * Constant for the metadata descriptor-type for Strings.
 105  
      */
 106  
     public final static int TYPE_STRING = 0;
 107  
 
 108  
     /**
 109  
      * Constant for the metadata descriptor-type for WORD (16-bit unsigned). <br>
 110  
      */
 111  
     public final static int TYPE_WORD = 5;
 112  
 
 113  
     /**
 114  
      * Maximum value for WORD.
 115  
      */
 116  
     public static final int WORD_MAXVALUE = 65535;
 117  
 
 118  
     /**
 119  
      * Stores the containerType of the descriptor.
 120  
      */
 121  
     private final ContainerType containerType;
 122  
 
 123  
     /**
 124  
      * The binary representation of the value.
 125  
      */
 126  
     /*
 127  
      * Note: The maximum data length could be up to a 64-Bit number (unsigned),
 128  
      * but java for now handles just int sized byte[]. Since this class stores
 129  
      * all data in primitive byte[] this size restriction is cascaded to all
 130  
      * dependent implementations.
 131  
      */
 132  18803
     private byte[] content = new byte[0];
 133  
 
 134  
     /**
 135  
      * This field shows the type of the metadata descriptor. <br>
 136  
      * 
 137  
      * @see #TYPE_BINARY
 138  
      * @see #TYPE_BOOLEAN
 139  
      * @see #TYPE_DWORD
 140  
      * @see #TYPE_GUID
 141  
      * @see #TYPE_QWORD
 142  
      * @see #TYPE_STRING
 143  
      * @see #TYPE_WORD
 144  
      */
 145  
     private int descriptorType;
 146  
 
 147  
     /**
 148  
      * the index of the language in the {@linkplain LanguageList language list}
 149  
      * this descriptor applies to.<br>
 150  
      * 
 151  
      */
 152  18803
     private int languageIndex = 0;
 153  
 
 154  
     /**
 155  
      * The name of the metadata descriptor.
 156  
      */
 157  
     private final String name;
 158  
 
 159  
     /**
 160  
      * The number of the stream, this descriptor applies to.<br>
 161  
      */
 162  18803
     private int streamNumber = 0;
 163  
 
 164  
     /**
 165  
      * Creates an Instance.<br>
 166  
      * 
 167  
      * @param type
 168  
      *            the container type, this descriptor is resctricted to.
 169  
      * @param propName
 170  
      *            Name of the MetadataDescriptor.
 171  
      * @param propType
 172  
      *            Type of the metadata descriptor. See {@link #descriptorType}
 173  
      */
 174  
     public MetadataDescriptor(final ContainerType type, final String propName,
 175  
             final int propType) {
 176  2312
         this(type, propName, propType, 0, 0);
 177  2296
     }
 178  
 
 179  
     /**
 180  
      * Creates an Instance.
 181  
      * 
 182  
      * @param type
 183  
      *            The container type the values (the whole descriptor) is
 184  
      *            restricted to.
 185  
      * @param propName
 186  
      *            Name of the MetadataDescriptor.
 187  
      * @param propType
 188  
      *            Type of the metadata descriptor. See {@link #descriptorType}
 189  
      * @param stream
 190  
      *            the number of the stream the descriptor refers to.
 191  
      * @param language
 192  
      *            the index of the language entry in a {@link LanguageList} this
 193  
      *            descriptor refers to.<br>
 194  
      *            <b>Consider</b>: No checks performed if language entry exists.
 195  
      * 
 196  
      */
 197  
     public MetadataDescriptor(final ContainerType type, final String propName,
 198  18803
             final int propType, final int stream, final int language) {
 199  18803
         assert type != null;
 200  18803
         type.assertConstraints(propName, new byte[0], propType, stream,
 201  
                 language);
 202  18779
         this.containerType = type;
 203  18779
         this.name = propName;
 204  18779
         this.descriptorType = propType;
 205  18779
         this.streamNumber = stream;
 206  18779
         this.languageIndex = language;
 207  18779
     }
 208  
 
 209  
     /**
 210  
      * Creates an instance.<br>
 211  
      * Capabilities are set to {@link ContainerType#METADATA_LIBRARY_OBJECT}.<br>
 212  
      * 
 213  
      * @param propName
 214  
      *            name of the metadata descriptor.
 215  
      */
 216  
     public MetadataDescriptor(final String propName) {
 217  1877
         this(propName, TYPE_STRING);
 218  1877
     }
 219  
 
 220  
     /**
 221  
      * Creates an Instance.<br>
 222  
      * Capabilities are set to {@link ContainerType#METADATA_LIBRARY_OBJECT}.<br>
 223  
      * 
 224  
      * @param propName
 225  
      *            Name of the MetadataDescriptor.
 226  
      * @param propType
 227  
      *            Type of the metadata descriptor. See {@link #descriptorType}
 228  
      */
 229  
     public MetadataDescriptor(final String propName, final int propType) {
 230  1917
         this(ContainerType.METADATA_LIBRARY_OBJECT, propName, propType, 0, 0);
 231  1917
     }
 232  
 
 233  
     /**
 234  
      * Converts the descriptors value into a number if possible.<br>
 235  
      * A boolean will be converted to &quot;1&quot; if <code>true</code>,
 236  
      * otherwise &quot;0&quot;.<br>
 237  
      * String will be interpreted as number with radix &quot;10&quot;.<br>
 238  
      * Binary data will be interpreted as the default WORD,DWORD or QWORD binary
 239  
      * representation, but only if the data does not exceed 8 bytes. This
 240  
      * precaution is done to prevent creating a number of a multi kilobyte
 241  
      * image.<br>
 242  
      * A GUID cannot be converted in any case.
 243  
      * 
 244  
      * @return number representation.
 245  
      * @throws NumberFormatException
 246  
      *             If no conversion is supported.
 247  
      */
 248  
     public BigInteger asNumber() {
 249  32
         BigInteger result = null;
 250  32
         switch (this.descriptorType) {
 251  
         case TYPE_BOOLEAN:
 252  
         case TYPE_WORD:
 253  
         case TYPE_DWORD:
 254  
         case TYPE_QWORD:
 255  
         case TYPE_BINARY:
 256  32
             if (this.content.length > 8) {
 257  0
                 throw new NumberFormatException(
 258  
                         "Binary data would exceed QWORD");
 259  
             }
 260  
             break;
 261  
         case TYPE_GUID:
 262  0
             throw new NumberFormatException(
 263  
                     "GUID cannot be converted to a number.");
 264  
         case TYPE_STRING:
 265  0
             result = new BigInteger(getString(), 10);
 266  0
             break;
 267  
         default:
 268  0
             throw new IllegalStateException();
 269  
         }
 270  32
         if (result == null) {
 271  32
             final byte[] copy = new byte[this.content.length];
 272  192
             for (int i = 0; i < copy.length; i++) {
 273  160
                 copy[i] = this.content[this.content.length - (i + 1)];
 274  
             }
 275  32
             result = new BigInteger(1, copy);
 276  
         }
 277  32
         return result;
 278  
     }
 279  
 
 280  
     /**
 281  
      * (overridden)
 282  
      * 
 283  
      * @see java.lang.Object#clone()
 284  
      */
 285  
     @Override
 286  
     public Object clone() throws CloneNotSupportedException {
 287  0
         return super.clone();
 288  
     }
 289  
 
 290  
     /**
 291  
      * {@inheritDoc}
 292  
      */
 293  
     public int compareTo(final MetadataDescriptor other) {
 294  0
         return getName().compareTo(other.getName());
 295  
     }
 296  
 
 297  
     /**
 298  
      * This method creates a copy of the current object. <br>
 299  
      * All data will be copied, too. <br>
 300  
      * 
 301  
      * @return A new metadata descriptor containing the same values as the
 302  
      *         current one.
 303  
      */
 304  
     public MetadataDescriptor createCopy() {
 305  5549
         final MetadataDescriptor result = new MetadataDescriptor(
 306  
                 this.containerType, this.name, this.descriptorType,
 307  
                 this.streamNumber, this.languageIndex);
 308  5549
         result.content = getRawData();
 309  5549
         return result;
 310  
     }
 311  
 
 312  
     /**
 313  
      * (overridden)
 314  
      * 
 315  
      * @see java.lang.Object#equals(java.lang.Object)
 316  
      */
 317  
     @Override
 318  
     public boolean equals(final Object obj) {
 319  85696
         boolean result = false;
 320  85696
         if (obj instanceof MetadataDescriptor) {
 321  85656
             if (obj == this) {
 322  2568
                 result = true;
 323  
             } else {
 324  83088
                 final MetadataDescriptor other = (MetadataDescriptor) obj;
 325  83088
                 result = other.getName().equals(getName())
 326  
                         && other.descriptorType == this.descriptorType
 327  
                         && other.languageIndex == this.languageIndex
 328  
                         && other.streamNumber == this.streamNumber
 329  
                         && Arrays.equals(this.content, other.content);
 330  
             }
 331  
         }
 332  85696
         return result;
 333  
     }
 334  
 
 335  
     /**
 336  
      * Returns the value of the MetadataDescriptor as a Boolean. <br>
 337  
      * If no Conversion is Possible false is returned. <br>
 338  
      * <code>true</code> if first byte of {@link #content}is not zero.
 339  
      * 
 340  
      * @return boolean representation of the current value.
 341  
      */
 342  
     public boolean getBoolean() {
 343  652
         return this.content.length > 0 && this.content[0] != 0;
 344  
     }
 345  
 
 346  
     /**
 347  
      * This method will return a byte array, which can directly be written into
 348  
      * an "Extended Content Description"-chunk. <br>
 349  
      * 
 350  
      * @return byte[] with the data, that occurs in ASF files.
 351  
      * @deprecated {@link #writeInto(OutputStream,ContainerType)} is used
 352  
      */
 353  
     @Deprecated
 354  
     public byte[] getBytes() {
 355  0
         final ByteArrayOutputStream result = new ByteArrayOutputStream();
 356  
         try {
 357  0
             writeInto(result, this.containerType);
 358  0
         } catch (final IOException e) {
 359  0
             LOGGER.warning(e.getMessage());
 360  0
         }
 361  0
         return result.toByteArray();
 362  
     }
 363  
 
 364  
     /**
 365  
      * Returns the container type this descriptor ist restricted to.
 366  
      * 
 367  
      * @return the container type
 368  
      */
 369  
     public ContainerType getContainerType() {
 370  14046
         return this.containerType;
 371  
     }
 372  
 
 373  
     /**
 374  
      * Returns the size (in bytes) this descriptor will take when written to an
 375  
      * ASF file.<br>
 376  
      * 
 377  
      * @param type
 378  
      *            the container type for which the size is calculated.
 379  
      * 
 380  
      * @return size of the descriptor in an ASF file.
 381  
      */
 382  
     public int getCurrentAsfSize(final ContainerType type) {
 383  
         /*
 384  
          * 2 bytes name length, 2 bytes name zero term, 2 bytes type, 2 bytes
 385  
          * content length
 386  
          */
 387  6783
         int result = 8;
 388  
 
 389  6783
         if (type != ContainerType.EXTENDED_CONTENT) {
 390  
             // Stream number and language index (respectively reserved field).
 391  
             // And +2 bytes, because data type is 32 bit, not 16
 392  558
             result += 6;
 393  
         }
 394  6783
         result += getName().length() * 2;
 395  
 
 396  6783
         if (this.getType() == TYPE_BOOLEAN) {
 397  270
             result += 2;
 398  270
             if (type == ContainerType.EXTENDED_CONTENT) {
 399  
                 // Extended content description boolean values are stored with
 400  
                 // 32-bit
 401  135
                 result += 2;
 402  
             }
 403  
         } else {
 404  
 
 405  6513
             result += this.content.length;
 406  6513
             if (TYPE_STRING == this.getType()) {
 407  5964
                 result += 2; // zero term of content string.
 408  
             }
 409  
         }
 410  6783
         return result;
 411  
     }
 412  
 
 413  
     /**
 414  
      * Returns the GUID value, if content could represent one.
 415  
      * 
 416  
      * @return GUID value
 417  
      */
 418  
     public GUID getGuid() {
 419  248
         GUID result = null;
 420  248
         if (getType() == TYPE_GUID && this.content.length == GUID.GUID_LENGTH) {
 421  248
             result = new GUID(this.content);
 422  
         }
 423  248
         return result;
 424  
     }
 425  
 
 426  
     /**
 427  
      * Returns the index of the language that is referred (see
 428  
      * {@link LanguageList}):
 429  
      * 
 430  
      * @return the language index
 431  
      */
 432  
     public int getLanguageIndex() {
 433  73674
         return this.languageIndex;
 434  
     }
 435  
 
 436  
     /**
 437  
      * This method returns the name of the metadata descriptor.
 438  
      * 
 439  
      * @return Name.
 440  
      */
 441  
     public String getName() {
 442  369710
         return this.name;
 443  
     }
 444  
 
 445  
     /**
 446  
      * This method returns the value of the metadata descriptor as a long. <br>
 447  
      * Converts the needed amount of byte out of {@link #content}to a number. <br>
 448  
      * Only possible if {@link #getType()}equals on of the following: <br>
 449  
      * <li>
 450  
      * 
 451  
      * @return integer value.
 452  
      * @see #TYPE_BOOLEAN </li> <li>
 453  
      * @see #TYPE_DWORD </li> <li>
 454  
      * @see #TYPE_QWORD </li> <li>
 455  
      * @see #TYPE_WORD </li>
 456  
      */
 457  
     public long getNumber() {
 458  
         int bytesNeeded;
 459  1024
         switch (getType()) {
 460  
         case TYPE_BOOLEAN:
 461  4
             bytesNeeded = 1;
 462  4
             break;
 463  
         case TYPE_DWORD:
 464  640
             bytesNeeded = 4;
 465  640
             break;
 466  
         case TYPE_QWORD:
 467  192
             bytesNeeded = 8;
 468  192
             break;
 469  
         case TYPE_WORD:
 470  180
             bytesNeeded = 2;
 471  180
             break;
 472  
         default:
 473  8
             throw new UnsupportedOperationException(
 474  
                     "The current type doesn't allow an interpretation as a number. ("
 475  
                             + getType() + ")");
 476  
         }
 477  1016
         if (bytesNeeded > this.content.length) {
 478  0
             throw new IllegalStateException(
 479  
                     "The stored data cannot represent the type of current object.");
 480  
         }
 481  1016
         long result = 0;
 482  5476
         for (int i = 0; i < bytesNeeded; i++) {
 483  4460
             result |= (((long) this.content[i] & 0xFF) << (i * 8));
 484  
         }
 485  1016
         return result;
 486  
     }
 487  
 
 488  
     /**
 489  
      * This method returns a copy of the content of the descriptor. <br>
 490  
      * 
 491  
      * @return The content in binary representation, as it would be written to
 492  
      *         asf file. <br>
 493  
      */
 494  
     public byte[] getRawData() {
 495  40640
         final byte[] copy = new byte[this.content.length];
 496  40640
         System.arraycopy(this.content, 0, copy, 0, this.content.length);
 497  40640
         return copy;
 498  
     }
 499  
 
 500  
     /**
 501  
      * Returns the size (in bytes) the binary representation of the content
 502  
      * uses. (length of {@link #getRawData()})<br>
 503  
      * 
 504  
      * @return size of binary representation of the content.
 505  
      */
 506  
     public int getRawDataSize() {
 507  104
         return this.content.length;
 508  
     }
 509  
 
 510  
     /**
 511  
      * Returns the stream number this descriptor applies to.<br>
 512  
      * 
 513  
      * @return the stream number.
 514  
      */
 515  
     public int getStreamNumber() {
 516  73678
         return this.streamNumber;
 517  
     }
 518  
 
 519  
     /**
 520  
      * Returns the value of the MetadataDescriptor as a String. <br>
 521  
      * 
 522  
      * @return String - Representation Value
 523  
      */
 524  
     public String getString() {
 525  10840
         String result = null;
 526  10840
         switch (getType()) {
 527  
         case TYPE_BINARY:
 528  24
             result = "binary data";
 529  24
             break;
 530  
         case TYPE_BOOLEAN:
 531  542
             result = String.valueOf(getBoolean());
 532  542
             break;
 533  
         case TYPE_GUID:
 534  124
             result = getGuid() == null ? "Invalid GUID" : getGuid().toString();
 535  124
             break;
 536  
         case TYPE_QWORD:
 537  
         case TYPE_DWORD:
 538  
         case TYPE_WORD:
 539  976
             result = String.valueOf(getNumber());
 540  976
             break;
 541  
         case TYPE_STRING:
 542  
             try {
 543  9174
                 result = new String(this.content, "UTF-16LE");
 544  0
             } catch (final UnsupportedEncodingException e) {
 545  0
                 LOGGER.warning(e.getMessage());
 546  9174
             }
 547  0
             break;
 548  
         default:
 549  0
             throw new IllegalStateException("Current type is not known.");
 550  
         }
 551  10840
         return result;
 552  
     }
 553  
 
 554  
     /**
 555  
      * Returns the type of the metadata descriptor. <br>
 556  
      * 
 557  
      * @return the value of {@link #descriptorType}
 558  
      * @see #TYPE_BINARY
 559  
      * @see #TYPE_BOOLEAN
 560  
      * @see #TYPE_DWORD
 561  
      * @see #TYPE_GUID
 562  
      * @see #TYPE_QWORD
 563  
      * @see #TYPE_STRING
 564  
      * @see #TYPE_WORD
 565  
      */
 566  
     public int getType() {
 567  71337
         return this.descriptorType;
 568  
     }
 569  
 
 570  
     /**
 571  
      * {@inheritDoc}
 572  
      */
 573  
     @Override
 574  
     public int hashCode() {
 575  0
         return this.name.hashCode();
 576  
     }
 577  
 
 578  
     /**
 579  
      * This method checks if the binary data is empty. <br>
 580  
      * Disregarding the type of the descriptor its content is stored as a byte
 581  
      * array.
 582  
      * 
 583  
      * @return <code>true</code> if no value is set.
 584  
      */
 585  
     public boolean isEmpty() {
 586  556
         return this.content.length == 0;
 587  
     }
 588  
 
 589  
     /**
 590  
      * Sets the Value of the current metadata descriptor. <br>
 591  
      * Using this method will change {@link #descriptorType}to
 592  
      * {@link #TYPE_BINARY}.<br>
 593  
      * 
 594  
      * @param data
 595  
      *            Value to set.
 596  
      * @throws IllegalArgumentException
 597  
      *             if data is invalid for {@linkplain #getContainerType()
 598  
      *             container}.
 599  
      */
 600  
     public void setBinaryValue(final byte[] data)
 601  
             throws IllegalArgumentException {
 602  226
         this.containerType.assertConstraints(this.name, data,
 603  
                 this.descriptorType, this.streamNumber, this.languageIndex);
 604  222
         this.content = data.clone();
 605  222
         this.descriptorType = TYPE_BINARY;
 606  222
     }
 607  
 
 608  
     /**
 609  
      * Sets the Value of the current metadata descriptor. <br>
 610  
      * Using this method will change {@link #descriptorType}to
 611  
      * {@link #TYPE_BOOLEAN}.<br>
 612  
      * 
 613  
      * @param value
 614  
      *            Value to set.
 615  
      */
 616  
     public void setBooleanValue(final boolean value) {
 617  594
         this.content = new byte[] { value ? (byte) 1 : 0 };
 618  594
         this.descriptorType = TYPE_BOOLEAN;
 619  594
     }
 620  
 
 621  
     /**
 622  
      * Sets the Value of the current metadata descriptor. <br>
 623  
      * Using this method will change {@link #descriptorType}to
 624  
      * {@link #TYPE_DWORD}.
 625  
      * 
 626  
      * @param value
 627  
      *            Value to set.
 628  
      */
 629  
     public void setDWordValue(final long value) {
 630  845
         if (value < 0 || value > DWORD_MAXVALUE) {
 631  20
             throw new IllegalArgumentException("value out of range (0-"
 632  
                     + DWORD_MAXVALUE + ")");
 633  
         }
 634  825
         this.content = Utils.getBytes(value, 4);
 635  825
         this.descriptorType = TYPE_DWORD;
 636  825
     }
 637  
 
 638  
     /**
 639  
      * Sets the value of the metadata descriptor.<br>
 640  
      * Using this method will change {@link #descriptorType} to
 641  
      * {@link #TYPE_GUID}
 642  
      * 
 643  
      * @param value
 644  
      *            value to set.
 645  
      */
 646  
     public void setGUIDValue(final GUID value) {
 647  143
         this.containerType.assertConstraints(this.name, value.getBytes(),
 648  
                 TYPE_GUID, this.streamNumber, this.languageIndex);
 649  127
         this.content = value.getBytes();
 650  127
         this.descriptorType = TYPE_GUID;
 651  127
     }
 652  
 
 653  
     /**
 654  
      * Sets the index of the referred language (see {@link LanguageList}).<br>
 655  
      * <b>Consider</b>: The {@linkplain #containerType requirements} must be
 656  
      * held.
 657  
      * 
 658  
      * @param language
 659  
      *            the language index to set
 660  
      */
 661  
     public void setLanguageIndex(final int language) {
 662  12
         this.containerType.assertConstraints(this.name, this.content,
 663  
                 this.descriptorType, this.streamNumber, language);
 664  4
         this.languageIndex = language;
 665  4
     }
 666  
 
 667  
     /**
 668  
      * Sets the Value of the current metadata descriptor. <br>
 669  
      * Using this method will change {@link #descriptorType}to
 670  
      * {@link #TYPE_QWORD}
 671  
      * 
 672  
      * @param value
 673  
      *            Value to set.
 674  
      * @throws NumberFormatException
 675  
      *             on <code>null</code> values.
 676  
      * @throws IllegalArgumentException
 677  
      *             on illegal values or values exceeding range.
 678  
      */
 679  
     public void setQWordValue(final BigInteger value)
 680  
             throws IllegalArgumentException {
 681  200
         if (value == null) {
 682  4
             throw new NumberFormatException("null");
 683  
         }
 684  196
         if (BigInteger.ZERO.compareTo(value) > 0) {
 685  8
             throw new IllegalArgumentException(
 686  
                     "Only unsigned values allowed (no negative)");
 687  
         }
 688  188
         if (MetadataDescriptor.QWORD_MAXVALUE.compareTo(value) < 0) {
 689  8
             throw new IllegalArgumentException(
 690  
                     "Value exceeds QWORD (64 bit unsigned)");
 691  
         }
 692  180
         this.content = new byte[8];
 693  180
         final byte[] valuesBytes = value.toByteArray();
 694  180
         if (valuesBytes.length <= 8) {
 695  352
             for (int i = valuesBytes.length - 1; i >= 0; i--) {
 696  180
                 this.content[valuesBytes.length - (i + 1)] = valuesBytes[i];
 697  
             }
 698  
         } else {
 699  
             /*
 700  
              * In case of 64-Bit set
 701  
              */
 702  8
             Arrays.fill(this.content, (byte) 0xFF);
 703  
         }
 704  180
         this.descriptorType = TYPE_QWORD;
 705  180
     }
 706  
 
 707  
     /**
 708  
      * Sets the Value of the current metadata descriptor. <br>
 709  
      * Using this method will change {@link #descriptorType}to
 710  
      * {@link #TYPE_QWORD}
 711  
      * 
 712  
      * @param value
 713  
      *            Value to set.
 714  
      */
 715  
     public void setQWordValue(final long value) {
 716  35
         if (value < 0) {
 717  0
             throw new IllegalArgumentException("value out of range (0-"
 718  
                     + MetadataDescriptor.QWORD_MAXVALUE.toString() + ")");
 719  
         }
 720  35
         this.content = Utils.getBytes(value, 8);
 721  35
         this.descriptorType = TYPE_QWORD;
 722  35
     }
 723  
 
 724  
     /**
 725  
      * Sets the stream number the descriptor applies to.<br>
 726  
      * <b>Consider</b>: The {@linkplain #containerType requirements} must be
 727  
      * held.
 728  
      * 
 729  
      * @param stream
 730  
      *            the stream number to set
 731  
      */
 732  
     public void setStreamNumber(final int stream) {
 733  16
         this.containerType.assertConstraints(this.name, this.content,
 734  
                 this.descriptorType, stream, this.languageIndex);
 735  8
         this.streamNumber = stream;
 736  8
     }
 737  
 
 738  
     /**
 739  
      * This method converts the given string value into the current
 740  
      * {@linkplain #getType() data type}.
 741  
      * 
 742  
      * @param value
 743  
      *            value to set.
 744  
      * @throws IllegalArgumentException
 745  
      *             If conversion was impossible.
 746  
      */
 747  
     public void setString(final String value)
 748  
             throws IllegalArgumentException {
 749  
         try {
 750  1693
             switch (getType()) {
 751  
             case TYPE_BINARY:
 752  0
                 throw new IllegalArgumentException(
 753  
                         "Cannot interpret binary as string.");
 754  
             case TYPE_BOOLEAN:
 755  168
                 setBooleanValue(Boolean.parseBoolean(value));
 756  168
                 break;
 757  
             case TYPE_DWORD:
 758  176
                 setDWordValue(Long.parseLong(value));
 759  164
                 break;
 760  
             case TYPE_QWORD:
 761  180
                 setQWordValue(new BigInteger(value, 10));
 762  168
                 break;
 763  
             case TYPE_WORD:
 764  176
                 setWordValue(Integer.parseInt(value));
 765  164
                 break;
 766  
             case TYPE_GUID:
 767  12
                 setGUIDValue(GUID.parseGUID(value));
 768  4
                 break;
 769  
             case TYPE_STRING:
 770  981
                 setStringValue(value);
 771  973
                 break;
 772  
             default:
 773  
                 // new Type added but not handled.
 774  0
                 throw new IllegalStateException();
 775  
             }
 776  16
         } catch (final NumberFormatException nfe) {
 777  16
             throw new IllegalArgumentException(
 778  
                     "Value cannot be parsed as Number or is out of range (\""
 779  
                             + value + "\")", nfe);
 780  1641
         }
 781  1641
     }
 782  
 
 783  
     /**
 784  
      * Sets the Value of the current metadata descriptor. <br>
 785  
      * Using this method will change {@link #descriptorType}to
 786  
      * {@link #TYPE_STRING}.
 787  
      * 
 788  
      * @param value
 789  
      *            Value to set.
 790  
      * @throws IllegalArgumentException
 791  
      *             If byte representation would take more than 65535 Bytes.
 792  
      */
 793  
     // TODO Test
 794  
     public void setStringValue(final String value)
 795  
             throws IllegalArgumentException {
 796  9374
         if (value == null) {
 797  0
             this.content = new byte[0];
 798  
         } else {
 799  9374
             final byte[] tmp = Utils.getBytes(value, AsfHeader.ASF_CHARSET);
 800  9374
             if (getContainerType().isWithinValueRange(tmp.length)) {
 801  
                 // Everything is fine here, data can be stored.
 802  9326
                 this.content = tmp;
 803  
             } else {
 804  
                 // Normally a size violation, check if JAudiotagger my truncate
 805  
                 // the string
 806  48
                 if (TagOptionSingleton.getInstance()
 807  
                         .isTruncateTextWithoutErrors()) {
 808  
                     // truncate the string
 809  12
                     final int copyBytes = (int) getContainerType()
 810  
                             .getMaximumDataLength().longValue();
 811  12
                     this.content = new byte[copyBytes % 2 == 0 ? copyBytes
 812  
                             : copyBytes - 1];
 813  12
                     System.arraycopy(tmp, 0, this.content, 0,
 814  
                             this.content.length);
 815  12
                 } else {
 816  
                     // We may not truncate, so its an error
 817  36
                     throw new IllegalArgumentException(
 818  
                             ErrorMessage.WMA_LENGTH_OF_DATA_IS_TOO_LARGE
 819  
                                     .getMsg(tmp.length, getContainerType()
 820  
                                             .getMaximumDataLength(),
 821  
                                             getContainerType()
 822  
                                                     .getContainerGUID()
 823  
                                                     .getDescription()));
 824  
                 }
 825  
             }
 826  
         }
 827  9338
         this.descriptorType = TYPE_STRING;
 828  9338
     }
 829  
 
 830  
     /**
 831  
      * Sets the Value of the current metadata descriptor. <br>
 832  
      * Using this method will change {@link #descriptorType}to
 833  
      * {@link #TYPE_WORD}
 834  
      * 
 835  
      * @param value
 836  
      *            Value to set.
 837  
      * @throws IllegalArgumentException
 838  
      *             on negative values. ASF just supports unsigned values.
 839  
      */
 840  
     public void setWordValue(final int value)
 841  
             throws IllegalArgumentException {
 842  204
         if (value < 0 || value > WORD_MAXVALUE) {
 843  12
             throw new IllegalArgumentException("value out of range (0-"
 844  
                     + WORD_MAXVALUE + ")");
 845  
         }
 846  192
         this.content = Utils.getBytes(value, 2);
 847  192
         this.descriptorType = TYPE_WORD;
 848  192
     }
 849  
 
 850  
     /**
 851  
      * (overridden)
 852  
      * 
 853  
      * @see java.lang.Object#toString()
 854  
      */
 855  
     @Override
 856  
     public String toString() {
 857  1036
         return getName()
 858  
                 + " : "
 859  
                 + new String[] { "String: ", "Binary: ", "Boolean: ",
 860  
                         "DWORD: ", "QWORD:", "WORD:", "GUID:" }[this.descriptorType]
 861  
                 + getString() + " (language: " + this.languageIndex
 862  
                 + " / stream: " + this.streamNumber + ")";
 863  
     }
 864  
 
 865  
     /**
 866  
      * Writes this descriptor into the specified output stream.<br>
 867  
      * 
 868  
      * @param out
 869  
      *            stream to write into.
 870  
      * @param contType
 871  
      *            the container type this descriptor is written to.
 872  
      * @return amount of bytes written.
 873  
      * @throws IOException
 874  
      *             on I/O Errors
 875  
      */
 876  
     public int writeInto(final OutputStream out,
 877  
             final ContainerType contType) throws IOException {
 878  2261
         final int size = getCurrentAsfSize(contType);
 879  
         /*
 880  
          * Booleans are stored as one byte, if a boolean is written, the data
 881  
          * must be converted according to the container type.
 882  
          */
 883  
         byte[] binaryData;
 884  2261
         if (this.descriptorType == TYPE_BOOLEAN) {
 885  90
             binaryData = new byte[contType == ContainerType.EXTENDED_CONTENT ? 4
 886  
                     : 2];
 887  90
             binaryData[0] = (byte) (getBoolean() ? 1 : 0);
 888  
         } else {
 889  2171
             binaryData = this.content;
 890  
         }
 891  
         // for Metadata objects the stream number and language index
 892  2261
         if (contType != ContainerType.EXTENDED_CONTENT) {
 893  186
             Utils.writeUINT16(getLanguageIndex(), out);
 894  186
             Utils.writeUINT16(getStreamNumber(), out);
 895  
         }
 896  2261
         Utils.writeUINT16(getName().length() * 2 + 2, out);
 897  
 
 898  
         // The name for the metadata objects come later
 899  2261
         if (contType == ContainerType.EXTENDED_CONTENT) {
 900  2075
             out.write(Utils.getBytes(getName(), AsfHeader.ASF_CHARSET));
 901  2075
             out.write(AsfHeader.ZERO_TERM);
 902  
         }
 903  
 
 904  
         // type and content len follow up are identical
 905  2261
         final int type = getType();
 906  2261
         Utils.writeUINT16(type, out);
 907  2261
         int contentLen = binaryData.length;
 908  2261
         if (TYPE_STRING == type) {
 909  1988
             contentLen += 2; // Zero Term
 910  
         }
 911  
 
 912  2261
         if (contType == ContainerType.EXTENDED_CONTENT) {
 913  2075
             Utils.writeUINT16(contentLen, out);
 914  
         } else {
 915  186
             Utils.writeUINT32(contentLen, out);
 916  
         }
 917  
 
 918  
         // Metadata objects now write their descriptor name
 919  2261
         if (contType != ContainerType.EXTENDED_CONTENT) {
 920  186
             out.write(Utils.getBytes(getName(), AsfHeader.ASF_CHARSET));
 921  186
             out.write(AsfHeader.ZERO_TERM);
 922  
         }
 923  
 
 924  
         // The content.
 925  2261
         out.write(binaryData);
 926  2261
         if (TYPE_STRING == type) {
 927  1988
             out.write(AsfHeader.ZERO_TERM);
 928  
         }
 929  2261
         return size;
 930  
     }
 931  
 }