package org.jaudiotagger.audio.asf.data;
import org.jaudiotagger.audio.asf.util.Utils;
import org.jaudiotagger.logging.ErrorMessage;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
/**
* Enumerates capabilities, respectively uses, of metadata descriptors.<br>
* <br>
* The {@link #METADATA_LIBRARY_OBJECT} allows the most variations of data, as
* well as no size limitation (if it can be stored within a DWORD amount of
* bytes).<br>
*
* @author Christian Laireiter
*/
public enum ContainerType
{
/**
* The descriptor is used in the content branding object (chunk)
*/
CONTENT_BRANDING(GUID.GUID_CONTENT_BRANDING, 32, false, false, false, false),
/**
* The descriptor is used in the content description object (chunk), so
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data length}
* applies, no language index and stream number are allowed, as well as no
* multiple values.
*/
CONTENT_DESCRIPTION(GUID.GUID_CONTENTDESCRIPTION, 16, false, false, false,
false),
/**
* The descriptor is used in an extended content description object, so the
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data size} applies,
* and no language index and stream number other than "0" is
* allowed. Additionally no multiple values are permitted.
*/
EXTENDED_CONTENT(GUID.GUID_EXTENDED_CONTENT_DESCRIPTION, 16, false, false,
false, false),
/**
* The descriptor is used in a metadata library object. No real size limit
* (except DWORD range) applies. Stream numbers and language indexes can be
* specified.
*/
METADATA_LIBRARY_OBJECT(GUID.GUID_METADATA_LIBRARY, 32, true, true, true,
true),
/**
* The descriptor is used in a metadata object. The
* {@linkplain MetadataDescriptor#DWORD_MAXVALUE maximum data size} applies.
* Stream numbers can be specified. But no language index (always
* "0").
*/
METADATA_OBJECT(GUID.GUID_METADATA, 16, false, true, false, true);
/**
* Determines if low has <= index as high, in respect to
* {@link #getOrdered()}
*
* @param low
* @param high
* @return <code>true</code> if in correct order.
*/
public static boolean areInCorrectOrder(final ContainerType low,
final ContainerType high) {
final List<ContainerType> asList = Arrays.asList(getOrdered());
return asList.indexOf(low) <= asList.indexOf(high);
}
/**
* Returns the elements in an order, that indicates more capabilities
* (ascending).<br>
*
* @return capability ordered types
*/
public static ContainerType[] getOrdered() {
return new ContainerType[] {CONTENT_DESCRIPTION, CONTENT_BRANDING, EXTENDED_CONTENT, METADATA_OBJECT, METADATA_LIBRARY_OBJECT};
}
/**
* Stores the guid that identifies ASF chunks which store metadata of the
* current type.
*/
private final GUID containerGUID;
/**
* <code>true</code> if the descriptor field can store {@link GUID} values.
*/
private final boolean guidEnabled;
/**
* <code>true</code> if descriptor field can refer to a language.
*/
private final boolean languageEnabled;
/**
* The maximum amount of bytes the descriptor data may consume.<br>
*/
private final BigInteger maximumDataLength;
/**
* <code>true</code> if the container may store multiple values of the same
* metadata descriptor specification (equality on name, language, and
* stream).<br>
* WindowsMedia players advanced tag editor for example stores the
* WM/Picture attribute once in the extended content description, and all
* others in the metadata library object.
*/
private final boolean multiValued;
/**
* if <code>-1</code> a size value has to be compared against
* {@link #maximumDataLength} because {@link Long#MAX_VALUE} is exceeded.<br>
* Otherwise this is the {@link BigInteger#longValue()} representation.
*/
private final long perfMaxDataLen;
/**
* <code>true</code> if descriptor field can refer to specific streams.
*/
private final boolean streamEnabled;
/**
* Creates an instance
*
* @param guid
* see {@link #containerGUID}
* @param maxDataLenBits
* The amount of bits that is used to represent an unsigned value
* for the containers size descriptors. Will create a maximum
* value for {@link #maximumDataLength}. (2 ^ maxDataLenBits -1)
* @param guidAllowed
* see {@link #guidEnabled}
* @param stream
* see {@link #streamEnabled}
* @param language
* see {@link #languageEnabled}
* @param multiValue
* see {@link #multiValued}
*/
private ContainerType(final GUID guid, final int maxDataLenBits,
final boolean guidAllowed, final boolean stream,
final boolean language, final boolean multiValue) {
this.containerGUID = guid;
this.maximumDataLength = BigInteger.valueOf(2).pow(maxDataLenBits)
.subtract(BigInteger.ONE);
if (this.maximumDataLength
.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
this.perfMaxDataLen = this.maximumDataLength.longValue();
} else {
this.perfMaxDataLen = -1;
}
this.guidEnabled = guidAllowed;
this.streamEnabled = stream;
this.languageEnabled = language;
this.multiValued = multiValue;
}
/**
* Calls {@link #checkConstraints(String, byte[], int, int, int)} and
* actually throws the exception if there is one.
*
* @param name
* name of the descriptor
* @param data
* content
* @param type
* data type
* @param stream
* stream number
* @param language
* language index
*/
public void assertConstraints(final String name, final byte[] data,
final int type, final int stream, final int language) {
final RuntimeException result = checkConstraints(name, data, type,
stream, language);
if (result != null) {
throw result;
}
}
/**
* Checks if the values for a {@linkplain MetadataDescriptor content
* descriptor} match the contraints of the container type, and returns a
* {@link RuntimeException} if the requirements aren't met.
*
* @param name
* name of the descriptor
* @param data
* content
* @param type
* data type
* @param stream
* stream number
* @param language
* language index
* @return <code>null</code> if everything is fine.
*/
public RuntimeException checkConstraints(final String name,
final byte[] data, final int type, final int stream,
final int language) {
RuntimeException result = null;
// TODO generate tests
if (name == null || data == null) {
result = new IllegalArgumentException("Arguments must not be null.");
} else {
if (!Utils.isStringLengthValidNullSafe(name)) {
result = new IllegalArgumentException(
ErrorMessage.WMA_LENGTH_OF_STRING_IS_TOO_LARGE
.getMsg(name.length()));
}
}
if (result == null && !isWithinValueRange(data.length)) {
result = new IllegalArgumentException(
ErrorMessage.WMA_LENGTH_OF_DATA_IS_TOO_LARGE.getMsg(
data.length, getMaximumDataLength(),
getContainerGUID().getDescription()));
}
if (result == null
&& (stream < 0 || stream > MetadataDescriptor.MAX_STREAM_NUMBER || (!isStreamNumberEnabled() && stream != 0))) {
final String streamAllowed = isStreamNumberEnabled() ? "0 to 127"
: "0";
result = new IllegalArgumentException(
ErrorMessage.WMA_INVALID_STREAM_REFERNCE.getMsg(stream,
streamAllowed, getContainerGUID().getDescription()));
}
if (result == null && type == MetadataDescriptor.TYPE_GUID
&& !isGuidEnabled()) {
result = new IllegalArgumentException(
ErrorMessage.WMA_INVALID_GUID_USE.getMsg(getContainerGUID()
.getDescription()));
}
if (result == null
&& ((language != 0 && !isLanguageEnabled()) || (language < 0 || language >= MetadataDescriptor.MAX_LANG_INDEX))) {
final String langAllowed = isStreamNumberEnabled() ? "0 to 126"
: "0";
result = new IllegalArgumentException(
ErrorMessage.WMA_INVALID_LANGUAGE_USE.getMsg(language,
getContainerGUID().getDescription(), langAllowed));
}
if (result == null && this == CONTENT_DESCRIPTION
&& type != MetadataDescriptor.TYPE_STRING) {
result = new IllegalArgumentException(
ErrorMessage.WMA_ONLY_STRING_IN_CD.getMsg());
}
return result;
}
/**
* @return the containerGUID
*/
public GUID getContainerGUID() {
return this.containerGUID;
}
/**
* @return the maximumDataLength
*/
public BigInteger getMaximumDataLength() {
return this.maximumDataLength;
}
/**
* @return the guidEnabled
*/
public boolean isGuidEnabled() {
return this.guidEnabled;
}
/**
* @return the languageEnabled
*/
public boolean isLanguageEnabled() {
return this.languageEnabled;
}
/**
* Tests if the given value is less than or equal to
* {@link #getMaximumDataLength()}, and greater or equal to zero.<br>
*
* @param value
* The value to test
* @return <code>true</code> if size restrictions for binary data are met
* with this container type.
*/
public boolean isWithinValueRange(final long value) {
return (this.perfMaxDataLen == -1 || this.perfMaxDataLen >= value)
&& value >= 0;
}
/**
* @return the multiValued
*/
public boolean isMultiValued() {
return this.multiValued;
}
/**
* @return the streamEnabled
*/
public boolean isStreamNumberEnabled() {
return this.streamEnabled;
}
} |