Coverage Report - org.jaudiotagger.audio.asf.data.MetadataContainer
 
Classes in this File Line Coverage Branch Coverage Complexity
MetadataContainer
97%
101/104
75%
59/78
2.28
MetadataContainer$DescriptorPointer
100%
18/18
50%
7/14
2.28
 
 1  
 package org.jaudiotagger.audio.asf.data;
 2  
 
 3  
 import org.jaudiotagger.audio.asf.io.WriteableChunk;
 4  
 import org.jaudiotagger.audio.asf.util.Utils;
 5  
 
 6  
 import java.io.IOException;
 7  
 import java.io.OutputStream;
 8  
 import java.math.BigInteger;
 9  
 import java.util.*;
 10  
 
 11  
 /**
 12  
  * This structure represents the "Metadata Object","Metadata
 13  
  * Library Object&quot; and &quot;Extended Content Description&quot;.<br>
 14  
  * 
 15  
  * @author Christian Laireiter
 16  
  */
 17  4
 public class MetadataContainer extends Chunk implements WriteableChunk {
 18  
 
 19  
     /**
 20  
      * This class is used to uniquely identify an enclosed descriptor by its
 21  
      * name, language index and stream number.<br>
 22  
      * The type of the descriptor is ignored, since it just specifies the data
 23  
      * content.
 24  
      * 
 25  
      * @author Christian Laireiter
 26  
      */
 27  4
     private final static class DescriptorPointer {
 28  
 
 29  
         /**
 30  
          * The represented descriptor.
 31  
          */
 32  
         private MetadataDescriptor desc;
 33  
 
 34  
         /**
 35  
          * Creates an instance.
 36  
          * 
 37  
          * @param descriptor
 38  
          *            the metadata descriptor to identify.
 39  
          */
 40  14377
         public DescriptorPointer(final MetadataDescriptor descriptor) {
 41  14377
             setDescriptor(descriptor);
 42  14377
         }
 43  
 
 44  
         /**
 45  
          * {@inheritDoc}
 46  
          */
 47  
         @Override
 48  
         public boolean equals(final Object obj) {
 49  1137
             boolean result = obj == this;
 50  1137
             if (obj instanceof DescriptorPointer && !result) {
 51  1137
                 final MetadataDescriptor other = ((DescriptorPointer) obj).desc;
 52  1137
                 result = this.desc.getName().equals(other.getName());
 53  1137
                 result &= this.desc.getLanguageIndex() == other
 54  
                         .getLanguageIndex();
 55  1137
                 result &= this.desc.getStreamNumber() == other
 56  
                         .getStreamNumber();
 57  
             }
 58  1137
             return result;
 59  
         }
 60  
 
 61  
         /**
 62  
          * {@inheritDoc}
 63  
          */
 64  
         @Override
 65  
         public int hashCode() {
 66  
             int hashCode;
 67  40879
             hashCode = this.desc.getName().hashCode();
 68  40879
             hashCode = hashCode * 31 + this.desc.getLanguageIndex();
 69  40879
             hashCode = hashCode * 31 + this.desc.getStreamNumber();
 70  40879
             return hashCode;
 71  
         }
 72  
 
 73  
         /**
 74  
          * Sets the descriptor to identify.
 75  
          * 
 76  
          * @param descriptor
 77  
          *            the descriptor to identify.
 78  
          * @return this instance.
 79  
          */
 80  
         protected DescriptorPointer setDescriptor(
 81  
                 final MetadataDescriptor descriptor) {
 82  42624
             assert descriptor != null;
 83  42624
             this.desc = descriptor;
 84  42624
             return this;
 85  
         }
 86  
     }
 87  
 
 88  
     /**
 89  
      * Looks up all {@linkplain ContainerType#getContainerGUID() guids} and
 90  
      * returns the matching type.
 91  
      * 
 92  
      * @param guid
 93  
      *            GUID to look up
 94  
      * @return matching container type.
 95  
      * @throws IllegalArgumentException
 96  
      *             if no container type matches
 97  
      */
 98  
     private static ContainerType determineType(final GUID guid)
 99  
             throws IllegalArgumentException {
 100  521
         assert guid != null;
 101  521
         ContainerType result = null;
 102  1976
         for (final ContainerType curr : ContainerType.values()) {
 103  1976
             if (curr.getContainerGUID().equals(guid)) {
 104  521
                 result = curr;
 105  521
                 break;
 106  
             }
 107  
         }
 108  521
         if (result == null) {
 109  0
             throw new IllegalArgumentException(
 110  
                     "Unknown metadata container specified by GUID ("
 111  
                             + guid.toString() + ")");
 112  
         }
 113  521
         return result;
 114  
     }
 115  
 
 116  
     /**
 117  
      * stores the represented container type.<br>
 118  
      */
 119  
     private final ContainerType containerType;
 120  
 
 121  
     /**
 122  
      * Stores the descriptors.
 123  
      */
 124  1745
     private final Map<DescriptorPointer, List<MetadataDescriptor>> descriptors = new Hashtable<DescriptorPointer, List<MetadataDescriptor>>();
 125  
 
 126  
     /**
 127  
      * for performance reasons this instance is used to look up existing
 128  
      * descriptors in {@link #descriptors}.<br>
 129  
      */
 130  1745
     private final DescriptorPointer perfPoint = new DescriptorPointer(
 131  
             new MetadataDescriptor(""));
 132  
 
 133  
     /**
 134  
      * Creates an instance.
 135  
      * 
 136  
      * @param type
 137  
      *            determines the type of the container
 138  
      */
 139  
     public MetadataContainer(final ContainerType type) {
 140  108
         this(type, 0, BigInteger.ZERO);
 141  108
     }
 142  
 
 143  
     /**
 144  
      * Creates an instance.
 145  
      * 
 146  
      * @param type
 147  
      *            determines the type of the container
 148  
      * @param pos
 149  
      *            location in the ASF file
 150  
      * @param size
 151  
      *            size of the chunk.
 152  
      */
 153  
     public MetadataContainer(final ContainerType type, final long pos,
 154  
             final BigInteger size) {
 155  1793
         super(type.getContainerGUID(), pos, size);
 156  1745
         this.containerType = type;
 157  1745
     }
 158  
 
 159  
     /**
 160  
      * Creates an instance.
 161  
      * 
 162  
      * @param containerGUID
 163  
      *            the containers GUID
 164  
      * @param pos
 165  
      *            location in the ASF file
 166  
      * @param size
 167  
      *            size of the chunk.
 168  
      */
 169  
     public MetadataContainer(final GUID containerGUID, final long pos,
 170  
             final BigInteger size) {
 171  521
         this(determineType(containerGUID), pos, size);
 172  505
     }
 173  
 
 174  
     /**
 175  
      * Adds a metadata descriptor.
 176  
      * 
 177  
      * @param toAdd
 178  
      *            the descriptor to add.
 179  
      * @throws IllegalArgumentException
 180  
      *             if descriptor does not meet container requirements, or
 181  
      *             already exist.
 182  
      */
 183  
     public final void addDescriptor(final MetadataDescriptor toAdd)
 184  
             throws IllegalArgumentException {
 185  
         // check with throwing exceptions
 186  12852
         this.containerType.assertConstraints(toAdd.getName(), toAdd
 187  
                 .getRawData(), toAdd.getType(), toAdd.getStreamNumber(), toAdd
 188  
                 .getLanguageIndex());
 189  
         // validate containers capabilities
 190  12852
         if (!isAddSupported(toAdd)) {
 191  0
             throw new IllegalArgumentException(
 192  
                     "Descriptor cannot be added, see isAddSupported(...)");
 193  
         }
 194  
         /*
 195  
          * Check for containers types capabilities.
 196  
          */
 197  
         // Search for descriptor list by name, language and stream.
 198  
         List<MetadataDescriptor> list;
 199  12852
         synchronized (this.perfPoint) {
 200  12852
             list = this.descriptors.get(this.perfPoint.setDescriptor(toAdd));
 201  12852
         }
 202  12852
         if (list == null) {
 203  12632
             list = new ArrayList<MetadataDescriptor>();
 204  12632
             this.descriptors.put(new DescriptorPointer(toAdd), list);
 205  
         } else {
 206  220
             if (!list.isEmpty() && !this.containerType.isMultiValued()) {
 207  0
                 throw new IllegalArgumentException(
 208  
                         "Container does not allow multiple values of descriptors with same name, language index and stream number");
 209  
             }
 210  
         }
 211  12852
         list.add(toAdd);
 212  12852
     }
 213  
 
 214  
     /**
 215  
      * This method asserts that this container has a descriptor with the
 216  
      * specified key, means returns an existing or creates a new descriptor.
 217  
      * 
 218  
      * @param key
 219  
      *            the descriptor name to look up (or create)
 220  
      * @return the/a descriptor with the specified name (and initial type of
 221  
      *         {@link MetadataDescriptor#TYPE_STRING}.
 222  
      */
 223  
     protected final MetadataDescriptor assertDescriptor(final String key) {
 224  1391
         return assertDescriptor(key, MetadataDescriptor.TYPE_STRING);
 225  
     }
 226  
 
 227  
     /**
 228  
      * This method asserts that this container has a descriptor with the
 229  
      * specified key, means returns an existing or creates a new descriptor.
 230  
      * 
 231  
      * @param key
 232  
      *            the descriptor name to look up (or create)
 233  
      * @param type
 234  
      *            if the descriptor is created, this data type is applied.
 235  
      * @return the/a descriptor with the specified name.
 236  
      */
 237  
     protected final MetadataDescriptor assertDescriptor(final String key,
 238  
             final int type) {
 239  
         MetadataDescriptor desc;
 240  1463
         final List<MetadataDescriptor> descriptorsByName = getDescriptorsByName(key);
 241  1463
         if (descriptorsByName == null || descriptorsByName.isEmpty()) {
 242  1423
             desc = new MetadataDescriptor(getContainerType(), key, type);
 243  1423
             addDescriptor(desc);
 244  
         } else {
 245  40
             desc = descriptorsByName.get(0);
 246  
         }
 247  1463
         return desc;
 248  
     }
 249  
 
 250  
     /**
 251  
      * Checks whether a descriptor already exists.<br>
 252  
      * Name, stream number and language index are compared. Data and data type
 253  
      * are ignored.
 254  
      * 
 255  
      * @param lookup
 256  
      *            descriptor to look up.
 257  
      * @return <code>true</code> if such a descriptor already exists.
 258  
      */
 259  
     public final boolean containsDescriptor(final MetadataDescriptor lookup) {
 260  1832
         assert lookup != null;
 261  1832
         return this.descriptors.containsKey(this.perfPoint
 262  
                 .setDescriptor(lookup));
 263  
     }
 264  
 
 265  
     /**
 266  
      * Returns the type of container this instance represents.<br>
 267  
      * 
 268  
      * @return represented container type.
 269  
      */
 270  
     public final ContainerType getContainerType() {
 271  63943
         return this.containerType;
 272  
     }
 273  
 
 274  
     /**
 275  
      * {@inheritDoc}
 276  
      */
 277  
     public long getCurrentAsfChunkSize() {
 278  
         /*
 279  
          * 16 bytes GUID, 8 bytes chunk size, 2 bytes descriptor count
 280  
          */
 281  254
         long result = 26;
 282  254
         for (final MetadataDescriptor curr : getDescriptors()) {
 283  4522
             result += curr.getCurrentAsfSize(this.containerType);
 284  
         }
 285  254
         return result;
 286  
     }
 287  
 
 288  
     /**
 289  
      * Returns the number of contained descriptors.
 290  
      * 
 291  
      * @return number of descriptors.
 292  
      */
 293  
     public final int getDescriptorCount() {
 294  1081
         return this.getDescriptors().size();
 295  
     }
 296  
 
 297  
     /**
 298  
      * Returns all stored descriptors.
 299  
      * 
 300  
      * @return stored descriptors.
 301  
      */
 302  
     public final List<MetadataDescriptor> getDescriptors() {
 303  2422
         final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
 304  2422
         for (final List<MetadataDescriptor> curr : this.descriptors.values()) {
 305  25636
             result.addAll(curr);
 306  
         }
 307  2422
         return result;
 308  
     }
 309  
 
 310  
     /**
 311  
      * Returns a list of descriptors with the given
 312  
      * {@linkplain MetadataDescriptor#getName() name}.<br>
 313  
      * 
 314  
      * @param name
 315  
      *            name of the descriptors to return
 316  
      * @return list of descriptors with given name.
 317  
      */
 318  
     public final List<MetadataDescriptor> getDescriptorsByName(final String name) {
 319  5198
         assert name != null;
 320  5198
         final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
 321  5198
         final Collection<List<MetadataDescriptor>> values = this.descriptors
 322  
                 .values();
 323  5198
         for (final List<MetadataDescriptor> currList : values) {
 324  58452
             if (!currList.isEmpty() && currList.get(0).getName().equals(name)) {
 325  2921
                 result.addAll(currList);
 326  
             }
 327  
         }
 328  5198
         return result;
 329  
     }
 330  
 
 331  
     /**
 332  
      * This method looks up a descriptor with given name and returns its value
 333  
      * as string.<br>
 334  
      * 
 335  
      * @param name
 336  
      *            the name of the descriptor to look up.
 337  
      * @return the string representation of a found descriptors value. Even an
 338  
      *         empty string if no descriptor has been found.
 339  
      */
 340  
     protected final String getValueFor(final String name) {
 341  2405
         String result = "";
 342  2405
         final List<MetadataDescriptor> descs = getDescriptorsByName(name);
 343  2405
         if (descs != null) {
 344  2405
             assert descs.size() <= 1;
 345  2405
             if (!descs.isEmpty()) {
 346  1255
                 result = descs.get(0).getString();
 347  
             }
 348  
         }
 349  2405
         return result;
 350  
     }
 351  
 
 352  
     /**
 353  
      * Determines if this container contains a descriptor with given
 354  
      * {@linkplain MetadataDescriptor#getName() name}.<br>
 355  
      * 
 356  
      * @param name
 357  
      *            Name of the descriptor to look for.
 358  
      * @return <code>true</code> if descriptor has been found.
 359  
      */
 360  
     public final boolean hasDescriptor(final String name) {
 361  748
         return !getDescriptorsByName(name).isEmpty();
 362  
     }
 363  
 
 364  
     /**
 365  
      * Determines/checks if the given descriptor may be added to the container.<br>
 366  
      * This implies a check for the capabilities of the container specified by
 367  
      * its {@linkplain #getContainerType() container type}.<br>
 368  
      * 
 369  
      * @param descriptor
 370  
      *            the descriptor to test.
 371  
      * @return <code>true</code> if {@link #addDescriptor(MetadataDescriptor)}
 372  
      *         can be called with given descriptor.
 373  
      */
 374  
     public boolean isAddSupported(final MetadataDescriptor descriptor) {
 375  17431
         boolean result = getContainerType().checkConstraints(
 376  
                 descriptor.getName(), descriptor.getRawData(),
 377  
                 descriptor.getType(), descriptor.getStreamNumber(),
 378  
                 descriptor.getLanguageIndex()) == null;
 379  
         // Now check if there is already a value contained.
 380  17431
         if (result && !getContainerType().isMultiValued()) {
 381  13563
             synchronized (this.perfPoint) {
 382  13563
                 final List<MetadataDescriptor> list = this.descriptors
 383  
                         .get(this.perfPoint.setDescriptor(descriptor));
 384  13563
                 if (list != null) {
 385  1
                     result = list.isEmpty();
 386  
                 }
 387  13563
             }
 388  
         }
 389  17431
         return result;
 390  
     }
 391  
 
 392  
     /**
 393  
      * {@inheritDoc}
 394  
      */
 395  
     public final boolean isEmpty() {
 396  1037
         boolean result = true;
 397  1037
         if (getDescriptorCount() != 0) {
 398  424
             final Iterator<MetadataDescriptor> iterator = getDescriptors()
 399  
                     .iterator();
 400  848
             while (result && iterator.hasNext()) {
 401  424
                 result &= iterator.next().isEmpty();
 402  
             }
 403  
         }
 404  1037
         return result;
 405  
     }
 406  
 
 407  
     /**
 408  
      * {@inheritDoc}
 409  
      */
 410  
     @Override
 411  
     public String prettyPrint(final String prefix) {
 412  4
         final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
 413  4
         for (final MetadataDescriptor curr : getDescriptors()) {
 414  120
             result.append(prefix).append("  |-> ");
 415  120
             result.append(curr);
 416  120
             result.append(Utils.LINE_SEPARATOR);
 417  
         }
 418  4
         return result.toString();
 419  
     }
 420  
 
 421  
     /**
 422  
      * Removes all stored descriptors with the given
 423  
      * {@linkplain MetadataDescriptor#getName() name}.<br>
 424  
      * 
 425  
      * @param name
 426  
      *            the name to remove.
 427  
      */
 428  
     public final void removeDescriptorsByName(final String name) {
 429  356
         assert name != null;
 430  356
         final Iterator<List<MetadataDescriptor>> iterator = this.descriptors
 431  
                 .values().iterator();
 432  9936
         while (iterator.hasNext()) {
 433  9580
             final List<MetadataDescriptor> curr = iterator.next();
 434  9580
             if (!curr.isEmpty() && curr.get(0).getName().equals(name)) {
 435  696
                 iterator.remove();
 436  
             }
 437  9580
         }
 438  356
     }
 439  
 
 440  
     /**
 441  
      * {@linkplain #assertDescriptor(String) asserts} the existence of a
 442  
      * descriptor with given <code>name</code> and
 443  
      * {@linkplain MetadataDescriptor#setStringValue(String) assings} the string
 444  
      * value.
 445  
      * 
 446  
      * @param name
 447  
      *            the name of the descriptor to set the value for.
 448  
      * @param value
 449  
      *            the string value.
 450  
      */
 451  
     protected final void setStringValue(final String name, final String value) {
 452  1371
         assertDescriptor(name).setStringValue(value);
 453  1351
     }
 454  
 
 455  
     /**
 456  
      * {@inheritDoc}
 457  
      */
 458  
     public long writeInto(final OutputStream out) throws IOException {
 459  127
         final long chunkSize = getCurrentAsfChunkSize();
 460  127
         final List<MetadataDescriptor> descriptorList = getDescriptors();
 461  127
         out.write(getGuid().getBytes());
 462  127
         Utils.writeUINT64(chunkSize, out);
 463  127
         Utils.writeUINT16(descriptorList.size(), out);
 464  127
         for (final MetadataDescriptor curr : descriptorList) {
 465  2261
             curr.writeInto(out, this.containerType);
 466  
         }
 467  127
         return chunkSize;
 468  
     }
 469  
 }