package org.jaudiotagger.audio.asf.data;
import org.jaudiotagger.audio.asf.io.WriteableChunk;
import org.jaudiotagger.audio.asf.util.Utils;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.*;
/**
* This structure represents the "Metadata Object","Metadata
* Library Object" and "Extended Content Description".<br>
*
* @author Christian Laireiter
*/
public class MetadataContainer extends Chunk implements WriteableChunk {
/**
* This class is used to uniquely identify an enclosed descriptor by its
* name, language index and stream number.<br>
* The type of the descriptor is ignored, since it just specifies the data
* content.
*
* @author Christian Laireiter
*/
private final static class DescriptorPointer {
/**
* The represented descriptor.
*/
private MetadataDescriptor desc;
/**
* Creates an instance.
*
* @param descriptor
* the metadata descriptor to identify.
*/
public DescriptorPointer(final MetadataDescriptor descriptor) {
setDescriptor(descriptor);
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(final Object obj) {
boolean result = obj == this;
if (obj instanceof DescriptorPointer && !result) {
final MetadataDescriptor other = ((DescriptorPointer) obj).desc;
result = this.desc.getName().equals(other.getName());
result &= this.desc.getLanguageIndex() == other
.getLanguageIndex();
result &= this.desc.getStreamNumber() == other
.getStreamNumber();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int hashCode;
hashCode = this.desc.getName().hashCode();
hashCode = hashCode * 31 + this.desc.getLanguageIndex();
hashCode = hashCode * 31 + this.desc.getStreamNumber();
return hashCode;
}
/**
* Sets the descriptor to identify.
*
* @param descriptor
* the descriptor to identify.
* @return this instance.
*/
protected DescriptorPointer setDescriptor(
final MetadataDescriptor descriptor) {
assert descriptor != null;
this.desc = descriptor;
return this;
}
}
/**
* Looks up all {@linkplain ContainerType#getContainerGUID() guids} and
* returns the matching type.
*
* @param guid
* GUID to look up
* @return matching container type.
* @throws IllegalArgumentException
* if no container type matches
*/
private static ContainerType determineType(final GUID guid)
throws IllegalArgumentException {
assert guid != null;
ContainerType result = null;
for (final ContainerType curr : ContainerType.values()) {
if (curr.getContainerGUID().equals(guid)) {
result = curr;
break;
}
}
if (result == null) {
throw new IllegalArgumentException(
"Unknown metadata container specified by GUID ("
+ guid.toString() + ")");
}
return result;
}
/**
* stores the represented container type.<br>
*/
private final ContainerType containerType;
/**
* Stores the descriptors.
*/
private final Map<DescriptorPointer, List<MetadataDescriptor>> descriptors = new Hashtable<DescriptorPointer, List<MetadataDescriptor>>();
/**
* for performance reasons this instance is used to look up existing
* descriptors in {@link #descriptors}.<br>
*/
private final DescriptorPointer perfPoint = new DescriptorPointer(
new MetadataDescriptor(""));
/**
* Creates an instance.
*
* @param type
* determines the type of the container
*/
public MetadataContainer(final ContainerType type) {
this(type, 0, BigInteger.ZERO);
}
/**
* Creates an instance.
*
* @param type
* determines the type of the container
* @param pos
* location in the ASF file
* @param size
* size of the chunk.
*/
public MetadataContainer(final ContainerType type, final long pos,
final BigInteger size) {
super(type.getContainerGUID(), pos, size);
this.containerType = type;
}
/**
* Creates an instance.
*
* @param containerGUID
* the containers GUID
* @param pos
* location in the ASF file
* @param size
* size of the chunk.
*/
public MetadataContainer(final GUID containerGUID, final long pos,
final BigInteger size) {
this(determineType(containerGUID), pos, size);
}
/**
* Adds a metadata descriptor.
*
* @param toAdd
* the descriptor to add.
* @throws IllegalArgumentException
* if descriptor does not meet container requirements, or
* already exist.
*/
public final void addDescriptor(final MetadataDescriptor toAdd)
throws IllegalArgumentException {
// check with throwing exceptions
this.containerType.assertConstraints(toAdd.getName(), toAdd
.getRawData(), toAdd.getType(), toAdd.getStreamNumber(), toAdd
.getLanguageIndex());
// validate containers capabilities
if (!isAddSupported(toAdd)) {
throw new IllegalArgumentException(
"Descriptor cannot be added, see isAddSupported(...)");
}
/*
* Check for containers types capabilities.
*/
// Search for descriptor list by name, language and stream.
List<MetadataDescriptor> list;
synchronized (this.perfPoint) {
list = this.descriptors.get(this.perfPoint.setDescriptor(toAdd));
}
if (list == null) {
list = new ArrayList<MetadataDescriptor>();
this.descriptors.put(new DescriptorPointer(toAdd), list);
} else {
if (!list.isEmpty() && !this.containerType.isMultiValued()) {
throw new IllegalArgumentException(
"Container does not allow multiple values of descriptors with same name, language index and stream number");
}
}
list.add(toAdd);
}
/**
* This method asserts that this container has a descriptor with the
* specified key, means returns an existing or creates a new descriptor.
*
* @param key
* the descriptor name to look up (or create)
* @return the/a descriptor with the specified name (and initial type of
* {@link MetadataDescriptor#TYPE_STRING}.
*/
protected final MetadataDescriptor assertDescriptor(final String key) {
return assertDescriptor(key, MetadataDescriptor.TYPE_STRING);
}
/**
* This method asserts that this container has a descriptor with the
* specified key, means returns an existing or creates a new descriptor.
*
* @param key
* the descriptor name to look up (or create)
* @param type
* if the descriptor is created, this data type is applied.
* @return the/a descriptor with the specified name.
*/
protected final MetadataDescriptor assertDescriptor(final String key,
final int type) {
MetadataDescriptor desc;
final List<MetadataDescriptor> descriptorsByName = getDescriptorsByName(key);
if (descriptorsByName == null || descriptorsByName.isEmpty()) {
desc = new MetadataDescriptor(getContainerType(), key, type);
addDescriptor(desc);
} else {
desc = descriptorsByName.get(0);
}
return desc;
}
/**
* Checks whether a descriptor already exists.<br>
* Name, stream number and language index are compared. Data and data type
* are ignored.
*
* @param lookup
* descriptor to look up.
* @return <code>true</code> if such a descriptor already exists.
*/
public final boolean containsDescriptor(final MetadataDescriptor lookup) {
assert lookup != null;
return this.descriptors.containsKey(this.perfPoint
.setDescriptor(lookup));
}
/**
* Returns the type of container this instance represents.<br>
*
* @return represented container type.
*/
public final ContainerType getContainerType() {
return this.containerType;
}
/**
* {@inheritDoc}
*/
public long getCurrentAsfChunkSize() {
/*
* 16 bytes GUID, 8 bytes chunk size, 2 bytes descriptor count
*/
long result = 26;
for (final MetadataDescriptor curr : getDescriptors()) {
result += curr.getCurrentAsfSize(this.containerType);
}
return result;
}
/**
* Returns the number of contained descriptors.
*
* @return number of descriptors.
*/
public final int getDescriptorCount() {
return this.getDescriptors().size();
}
/**
* Returns all stored descriptors.
*
* @return stored descriptors.
*/
public final List<MetadataDescriptor> getDescriptors() {
final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
for (final List<MetadataDescriptor> curr : this.descriptors.values()) {
result.addAll(curr);
}
return result;
}
/**
* Returns a list of descriptors with the given
* {@linkplain MetadataDescriptor#getName() name}.<br>
*
* @param name
* name of the descriptors to return
* @return list of descriptors with given name.
*/
public final List<MetadataDescriptor> getDescriptorsByName(final String name) {
assert name != null;
final List<MetadataDescriptor> result = new ArrayList<MetadataDescriptor>();
final Collection<List<MetadataDescriptor>> values = this.descriptors
.values();
for (final List<MetadataDescriptor> currList : values) {
if (!currList.isEmpty() && currList.get(0).getName().equals(name)) {
result.addAll(currList);
}
}
return result;
}
/**
* This method looks up a descriptor with given name and returns its value
* as string.<br>
*
* @param name
* the name of the descriptor to look up.
* @return the string representation of a found descriptors value. Even an
* empty string if no descriptor has been found.
*/
protected final String getValueFor(final String name) {
String result = "";
final List<MetadataDescriptor> descs = getDescriptorsByName(name);
if (descs != null) {
assert descs.size() <= 1;
if (!descs.isEmpty()) {
result = descs.get(0).getString();
}
}
return result;
}
/**
* Determines if this container contains a descriptor with given
* {@linkplain MetadataDescriptor#getName() name}.<br>
*
* @param name
* Name of the descriptor to look for.
* @return <code>true</code> if descriptor has been found.
*/
public final boolean hasDescriptor(final String name) {
return !getDescriptorsByName(name).isEmpty();
}
/**
* Determines/checks if the given descriptor may be added to the container.<br>
* This implies a check for the capabilities of the container specified by
* its {@linkplain #getContainerType() container type}.<br>
*
* @param descriptor
* the descriptor to test.
* @return <code>true</code> if {@link #addDescriptor(MetadataDescriptor)}
* can be called with given descriptor.
*/
public boolean isAddSupported(final MetadataDescriptor descriptor) {
boolean result = getContainerType().checkConstraints(
descriptor.getName(), descriptor.getRawData(),
descriptor.getType(), descriptor.getStreamNumber(),
descriptor.getLanguageIndex()) == null;
// Now check if there is already a value contained.
if (result && !getContainerType().isMultiValued()) {
synchronized (this.perfPoint) {
final List<MetadataDescriptor> list = this.descriptors
.get(this.perfPoint.setDescriptor(descriptor));
if (list != null) {
result = list.isEmpty();
}
}
}
return result;
}
/**
* {@inheritDoc}
*/
public final boolean isEmpty() {
boolean result = true;
if (getDescriptorCount() != 0) {
final Iterator<MetadataDescriptor> iterator = getDescriptors()
.iterator();
while (result && iterator.hasNext()) {
result &= iterator.next().isEmpty();
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public String prettyPrint(final String prefix) {
final StringBuilder result = new StringBuilder(super.prettyPrint(prefix));
for (final MetadataDescriptor curr : getDescriptors()) {
result.append(prefix).append(" |-> ");
result.append(curr);
result.append(Utils.LINE_SEPARATOR);
}
return result.toString();
}
/**
* Removes all stored descriptors with the given
* {@linkplain MetadataDescriptor#getName() name}.<br>
*
* @param name
* the name to remove.
*/
public final void removeDescriptorsByName(final String name) {
assert name != null;
final Iterator<List<MetadataDescriptor>> iterator = this.descriptors
.values().iterator();
while (iterator.hasNext()) {
final List<MetadataDescriptor> curr = iterator.next();
if (!curr.isEmpty() && curr.get(0).getName().equals(name)) {
iterator.remove();
}
}
}
/**
* {@linkplain #assertDescriptor(String) asserts} the existence of a
* descriptor with given <code>name</code> and
* {@linkplain MetadataDescriptor#setStringValue(String) assings} the string
* value.
*
* @param name
* the name of the descriptor to set the value for.
* @param value
* the string value.
*/
protected final void setStringValue(final String name, final String value) {
assertDescriptor(name).setStringValue(value);
}
/**
* {@inheritDoc}
*/
public long writeInto(final OutputStream out) throws IOException {
final long chunkSize = getCurrentAsfChunkSize();
final List<MetadataDescriptor> descriptorList = getDescriptors();
out.write(getGuid().getBytes());
Utils.writeUINT64(chunkSize, out);
Utils.writeUINT16(descriptorList.size(), out);
for (final MetadataDescriptor curr : descriptorList) {
curr.writeInto(out, this.containerType);
}
return chunkSize;
}
}
|