FileDocCategorySizeDatePackage
GUID.javaAPI DocJaudiotagger 2.0.419833Wed Mar 30 16:11:50 BST 2011org.jaudiotagger.audio.asf.data

GUID.java

/*
 * Entagged Audio Tag library
 * Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jaudiotagger.audio.asf.data;

import org.jaudiotagger.audio.asf.util.Utils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * This class is used for representation of GUIDs and as a reference list of all
 * Known GUIDs. <br>
 * 
 * @author Christian Laireiter
 */
public final class GUID {

    /**
     * This constant defines the GUID for stream chunks describing audio
     * streams, indicating the the audio stream has no error concealment. <br>
     */
    public final static GUID GUID_AUDIO_ERROR_CONCEALEMENT_ABSENT = new GUID(
            new int[] { 0x40, 0xA4, 0xF1, 0x49, 0xCE, 0x4E, 0xD0, 0x11, 0xA3,
                    0xAC, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 },
            "Audio error concealment absent.");

    /**
     * This constant defines the GUID for stream chunks describing audio
     * streams, indicating the the audio stream has interleaved error
     * concealment. <br>
     */
    public final static GUID GUID_AUDIO_ERROR_CONCEALEMENT_INTERLEAVED = new GUID(
            new int[] { 0x40, 0xA4, 0xF1, 0x49, 0xCE, 0x4E, 0xD0, 0x11, 0xA3,
                    0xAC, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 },
            "Interleaved audio error concealment.");

    /**
     * This constant stores the GUID indicating that stream type is audio.
     */
    public final static GUID GUID_AUDIOSTREAM = new GUID(new int[] { 0x40,
            0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80,
            0x5F, 0x5C, 0x44, 0x2B }, " Audio stream");

    /**
     * This constant stores the GUID indicating a content branding object.
     */
    public final static GUID GUID_CONTENT_BRANDING = new GUID(new int[] { 0xFA,
            0xB3, 0x11, 0x22, 0x23, 0xBD, 0xD2, 0x11, 0xB4, 0xB7, 0x00, 0xA0,
            0xC9, 0x55, 0xFC, 0x6E }, "Content Branding");

    /**
     * This is for the Content Encryption Object
     * 2211B3FB-BD23-11D2-B4B7-00A0C955FC6E, needs to be little-endian.
     */
    public final static GUID GUID_CONTENT_ENCRYPTION = new GUID(new int[] {
            0xfb, 0xb3, 0x11, 0x22, 0x23, 0xbd, 0xd2, 0x11, 0xb4, 0xb7, 0x00,
            0xa0, 0xc9, 0x55, 0xfc, 0x6e }, "Content Encryption Object");

    /**
     * This constant represents the guidData for a chunk which contains Title,
     * author, copyright, description and rating.
     */
    public final static GUID GUID_CONTENTDESCRIPTION = new GUID(new int[] {
            0x33, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00,
            0xAA, 0x00, 0x62, 0xCE, 0x6C }, "Content Description");

    /**
     * This constant stores the GUID for Encoding-Info chunks.
     */
    public final static GUID GUID_ENCODING = new GUID(new int[] { 0x40, 0x52,
            0xD1, 0x86, 0x1D, 0x31, 0xD0, 0x11, 0xA3, 0xA4, 0x00, 0xA0, 0xC9,
            0x03, 0x48, 0xF6 }, "Encoding description");

    /**
     * This constant defines the GUID for a WMA "Extended Content Description"
     * chunk. <br>
     */
    public final static GUID GUID_EXTENDED_CONTENT_DESCRIPTION = new GUID(
            new int[] { 0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, 0x97,
                    0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 },
            "Extended Content Description");

    /**
     * GUID of ASF file header.
     */
    public final static GUID GUID_FILE = new GUID(new int[] { 0xA1, 0xDC, 0xAB,
            0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20,
            0x53, 0x65 }, "File header");

    /**
     * This constant defines the GUID of a asf header chunk.
     */
    public final static GUID GUID_HEADER = new GUID(new int[] { 0x30, 0x26,
            0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00,
            0x62, 0xce, 0x6c }, "Asf header");

    /**
     * This constant stores a GUID whose functionality is unknown.
     */
    public final static GUID GUID_HEADER_EXTENSION = new GUID(new int[] { 0xB5,
            0x03, 0xBF, 0x5F, 0x2E, 0xA9, 0xCF, 0x11, 0x8E, 0xE3, 0x00, 0xC0,
            0x0C, 0x20, 0x53, 0x65 }, "Header Extension");

    /**
     * This constant stores the GUID indicating the asf language list object.<br>
     */
    public final static GUID GUID_LANGUAGE_LIST = new GUID(new int[] { 0xa9,
            0x46, 0x43, 0x7c, 0xe0, 0xef, 0xfc, 0x4b, 0xb2, 0x29, 0x39, 0x3e,
            0xde, 0x41, 0x5c, 0x85 }, "Language List");

    /**
     * This constant stores the length of GUIDs used with ASF streams. <br>
     */
    public final static int GUID_LENGTH = 16;

    /**
     * This constant stores the GUID indicating the asf metadata object.<br>
     */
    public final static GUID GUID_METADATA = new GUID(new int[] { 0xea, 0xcb,
            0xf8, 0xc5, 0xaf, 0x5b, 0x77, 0x48, 0x84, 0x67, 0xaa, 0x8c, 0x44,
            0xfa, 0x4c, 0xca }, "Metadata");

    /**
     * This constant stores the GUID indicating the asf metadata library object.<br>
     */
    public final static GUID GUID_METADATA_LIBRARY = new GUID(new int[] { 0x94,
            0x1c, 0x23, 0x44, 0x98, 0x94, 0xd1, 0x49, 0xa1, 0x41, 0x1d, 0x13,
            0x4e, 0x45, 0x70, 0x54 }, "Metadata Library");

    /**
     * The GUID String values format.<br>
     */
    private final static Pattern GUID_PATTERN = Pattern
            .compile(
                    "[a-f0-9]{8}\\-[a-f0-9]{4}\\-[a-f0-9]{4}\\-[a-f0-9]{4}\\-[a-f0-9]{12}",
                    Pattern.CASE_INSENSITIVE);

    /**
     * This constant stores the GUID indicating a stream object.
     */
    public final static GUID GUID_STREAM = new GUID(new int[] { 0x91, 0x07,
            0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C,
            0x20, 0x53, 0x65 }, "Stream");

    /**
     * This constant stores a GUID indicating a "stream bitrate properties"
     * chunk.
     */
    public final static GUID GUID_STREAM_BITRATE_PROPERTIES = new GUID(
            new int[] { 0xCE, 0x75, 0xF8, 0x7B, 0x8D, 0x46, 0xD1, 0x11, 0x8D,
                    0x82, 0x00, 0x60, 0x97, 0xC9, 0xA2, 0xB2 },
            "Stream bitrate properties");

    /**
     * This map is used, to get the description of a GUID instance, which has
     * been created by reading.<br>
     * The map comparison is done against the {@link GUID#guidData} field. But
     * only the {@link #KNOWN_GUIDS} have a description set.
     */
    private final static Map<GUID, GUID> GUID_TO_CONFIGURED;

    /**
     * This constant represents a GUID implementation which can be used for
     * generic implementations, which have to provide a GUID, but do not really
     * require a specific GUID to work.
     */
    public final static GUID GUID_UNSPECIFIED = new GUID(new int[] { 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00 }, "Unspecified");

    /**
     * This constant stores the GUID indicating that stream type is video.
     */
    public final static GUID GUID_VIDEOSTREAM = new GUID(new int[] { 0xC0,
            0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80,
            0x5F, 0x5C, 0x44, 0x2B }, "Video stream");

    /**
     * This field stores all known GUIDs.
     */
    public final static GUID[] KNOWN_GUIDS;

    /**
     * This constant stores the GUID for a "script command object".<br>
     */
    public final static GUID SCRIPT_COMMAND_OBJECT = new GUID(new int[] { 0x30,
            0x1a, 0xfb, 0x1e, 0x62, 0x0b, 0xd0, 0x11, 0xa3, 0x9b, 0x00, 0xa0,
            0xc9, 0x03, 0x48, 0xf6 }, "Script Command Object");

    static {
        KNOWN_GUIDS = new GUID[] { GUID_AUDIO_ERROR_CONCEALEMENT_ABSENT,
                GUID_CONTENTDESCRIPTION, GUID_AUDIOSTREAM, GUID_ENCODING,
                GUID_FILE, GUID_HEADER, GUID_STREAM,
                GUID_EXTENDED_CONTENT_DESCRIPTION, GUID_VIDEOSTREAM,
                GUID_HEADER_EXTENSION, GUID_STREAM_BITRATE_PROPERTIES,
                SCRIPT_COMMAND_OBJECT, GUID_CONTENT_ENCRYPTION,
                GUID_CONTENT_BRANDING, GUID_UNSPECIFIED, GUID_METADATA_LIBRARY,
                GUID_METADATA, GUID_LANGUAGE_LIST };
        GUID_TO_CONFIGURED = new HashMap<GUID, GUID>(KNOWN_GUIDS.length);
        for (final GUID curr : KNOWN_GUIDS) {
            assert !GUID_TO_CONFIGURED.containsKey(curr) : "Double definition: \""
                    + GUID_TO_CONFIGURED.get(curr).getDescription()
                    + "\" <-> \"" + curr.getDescription() + "\"";
            GUID_TO_CONFIGURED.put(curr, curr);
        }
    }

    /**
     * This method checks if the given <code>value</code> is matching the GUID
     * specification of ASF streams. <br>
     * 
     * @param value
     *            possible GUID.
     * @return <code>true</code> if <code>value</code> matches the specification
     *         of a GUID.
     */
    public static boolean assertGUID(final int[] value) {
        return value != null && value.length == GUID.GUID_LENGTH;
    }

    /**
     * This method looks up a GUID instance from {@link #KNOWN_GUIDS} which
     * matches the value of the given GUID.
     * 
     * @param orig
     *            GUID to look up.
     * @return a GUID instance from {@link #KNOWN_GUIDS} if available.
     *         <code>null</code> else.
     */
    public static GUID getConfigured(final GUID orig) {
        // safe against null
        return GUID_TO_CONFIGURED.get(orig);
    }

    /**
     * This method searches a GUID in {@link #KNOWN_GUIDS}which is equal to the
     * given <code>guidData</code> and returns its description. <br>
     * This method is useful if a GUID was read out of a file and no
     * identification has been done yet.
     * 
     * @param guid
     *            GUID, which description is needed.
     * @return description of the GUID if found. Else <code>null</code>
     */
    public static String getGuidDescription(final GUID guid) {
        String result = null;
        if (guid == null) {
            throw new IllegalArgumentException("Argument must not be null.");
        }
        if (getConfigured(guid) != null) {
            result = getConfigured(guid).getDescription();
        }
        return result;
    }

    /**
     * This method parses a String as GUID.<br>
     * The format is like the one in the ASF specification.<br>
     * An Example: <code>C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA</code><br>
     * 
     * @param guid
     *            the string to parse.
     * @return the GUID.
     * @throws GUIDFormatException
     *             If the GUID has an invalid format.
     */
    public static GUID parseGUID(final String guid) throws GUIDFormatException {
        if (guid == null) {
            throw new GUIDFormatException("null");
        }
        if (!GUID_PATTERN.matcher(guid).matches()) {
            throw new GUIDFormatException("Invalid guidData format.");
        }
        final int[] bytes = new int[GUID_LENGTH];
        /*
         * Don't laugh, but did not really come up with a nicer solution today
         */
        final int[] arrayIndices = { 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12,
                13, 14, 15 };
        int arrayPointer = 0;
        for (int i = 0; i < guid.length(); i++) {
            if (guid.charAt(i) == '-') {
                continue;
            }
            bytes[arrayIndices[arrayPointer++]] = Integer.parseInt(guid
                    .substring(i, i + 2), 16);
            i++;
        }
        return new GUID(bytes);
    }

    /**
     * Stores an optionally description of the GUID.
     */
    private String description = "";

    /**
     * An instance of this class stores the value of the wrapped GUID in this
     * field. <br>
     */
    private int[] guidData = null;

    /**
     * Stores the hash code of the object.<br>
     * <code>"-1"</code> if not determined yet.
     */
    private int hash;

    /**
     * Creates an instance and assigns given <code>value</code>.<br>
     * 
     * @param value
     *            GUID, which should be assigned. (will be converted to int[])
     */
    public GUID(final byte[] value) {
        assert value != null;
        final int[] tmp = new int[value.length];
        for (int i = 0; i < value.length; i++) {
            tmp[i] = (0xFF & value[i]);
        }
        setGUID(tmp);
    }

    /**
     * Creates an instance and assigns given <code>value</code>.<br>
     * 
     * @param value
     *            GUID, which should be assigned.
     */
    public GUID(final int[] value) {
        setGUID(value);
    }

    /**
     * Creates an instance like {@link #GUID(int[])}and sets the optional
     * description. <br>
     * 
     * @param value
     *            GUID, which should be assigned.
     * @param desc
     *            Description for the GUID.
     */
    public GUID(final int[] value, final String desc) {
        this(value);
        if (desc == null) {
            throw new IllegalArgumentException("Argument must not be null.");
        }
        this.description = desc;
    }

    /**
     * Creates an instance like {@link #GUID(int[])} and sets the optional
     * description. (the int[] is obtained by {@link GUID#parseGUID(String)}) <br>
     * 
     * @param guidString
     *            GUID, which should be assigned.
     * @param desc
     *            Description for the GUID.
     */
    public GUID(final String guidString, final String desc) {
        this(parseGUID(guidString).getGUID());
        if (desc == null) {
            throw new IllegalArgumentException("Argument must not be null.");
        }
        this.description = desc;
    }

    /**
     * This method compares two objects. If the given Object is a {@link GUID},
     * the stored GUID values are compared. <br>
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        boolean result = false;
        if (obj instanceof GUID) {
            final GUID other = (GUID) obj;
            result = Arrays.equals(this.getGUID(), other.getGUID());
        }
        return result;
    }

    /**
     * This method returns the GUID as an array of bytes. <br>
     * 
     * @return The GUID as a byte array.
     * @see #getGUID()
     */
    public byte[] getBytes() {
        final byte[] result = new byte[this.guidData.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = (byte) (this.guidData[i] & 0xFF);
        }
        return result;
    }

    /**
     * @return Returns the description.
     */
    public String getDescription() {
        return this.description;
    }

    /**
     * This method returns the GUID of this object. <br>
     * 
     * @return stored GUID.
     */
    public int[] getGUID() {
        final int[] copy = new int[this.guidData.length];
        System.arraycopy(this.guidData, 0, copy, 0, this.guidData.length);
        return copy;
    }

    /**
     * Convenience method to get 2digit hex values of each byte.
     * 
     * @param bytes
     *            bytes to convert.
     * @return each byte as 2 digit hex.
     */
    private String[] getHex(final byte[] bytes) {
        final String[] result = new String[bytes.length];
        final StringBuilder tmp = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            tmp.delete(0, tmp.length());
            tmp.append(Integer.toHexString(0xFF & bytes[i]));
            if (tmp.length() == 1) {
                tmp.insert(0, "0");
            }
            result[i] = tmp.toString();
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        if (this.hash == -1) {
            int tmp = 0;
            for (final int curr : getGUID()) {
                tmp = tmp * 31 + curr;
            }
            this.hash = tmp;
        }
        return this.hash;
    }

    /**
     * This method checks if the currently stored GUID ({@link #guidData}) is
     * correctly filled. <br>
     * 
     * @return <code>true</code> if it is.
     */
    public boolean isValid() {
        return assertGUID(getGUID());
    }

    /**
     * This method gives a hex formatted representation of {@link #getGUID()}
     * 
     * @return hex formatted representation.
     */
    public String prettyPrint() {
        final StringBuilder result = new StringBuilder();
        String descr = getDescription();
        if (Utils.isBlank(descr)) {
            descr = getGuidDescription(this);
        }
        if (!Utils.isBlank(descr)) {
            result.append("Description: ").append(descr).append(
                    Utils.LINE_SEPARATOR).append("   ");
        }
        result.append(this.toString());
        return result.toString();
    }

    /**
     * This method saves a copy of the given <code>value</code> as the
     * represented value of this object. <br>
     * The given value is checked with {@link #assertGUID(int[])}.<br>
     * 
     * @param value
     *            GUID to assign.
     */
    private void setGUID(final int[] value) {
        if (assertGUID(value)) {
            this.guidData = new int[GUID_LENGTH];
            System.arraycopy(value, 0, this.guidData, 0, GUID_LENGTH);
        } else {
            throw new IllegalArgumentException(
                    "The given guidData doesn't match the GUID specification.");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        // C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA
        // 0xea, 0xcb,0xf8, 0xc5, 0xaf, 0x5b, 0x77, 0x48, 0x84, 0x67, 0xaa,
        // 0x8c, 0x44,0xfa, 0x4c, 0xca
        final StringBuilder result = new StringBuilder();
        final String[] bytes = getHex(getBytes());
        result.append(bytes[3]);
        result.append(bytes[2]);
        result.append(bytes[1]);
        result.append(bytes[0]);
        result.append('-');
        result.append(bytes[5]);
        result.append(bytes[4]);
        result.append('-');
        result.append(bytes[7]);
        result.append(bytes[6]);
        result.append('-');
        result.append(bytes[8]);
        result.append(bytes[9]);
        result.append('-');
        result.append(bytes[10]);
        result.append(bytes[11]);
        result.append(bytes[12]);
        result.append(bytes[13]);
        result.append(bytes[14]);
        result.append(bytes[15]);
        return result.toString();
    }

}