/* * @(#)Format.java 1.40 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package javax.media; import java.lang.Class; /** * A <code>Format</code> abstracts an exact media format. * It carries no encoding-specific parameters * or timing information global to the presentation. * <p> * <h3> Comparing different formats </h3> * Not all of the attributes in a <code>Format</code> object have to be specified. * This enables selected attributes to be specified, making it possible to * locate a supported <CODE>Format</CODE> that meets certain requirements without needing to * find an exact match. * <p> * Two methods are provided for comparing <code>Formats</code>. * The <code>equals</code> method returns <CODE>true</CODE> if two <CODE>Format</CODE> * objects are exactly the same--they're the same type and all of their attributes are the * same. The <code>matches</code> method relaxes the comparison, comparing * only the attributes that are explicitly specified in the <CODE>Format</CODE> you are comparing. * @since JMF 2.0 */ public class Format implements java.lang.Cloneable, java.io.Serializable { public static final int NOT_SPECIFIED = -1; public static final int TRUE = 1; public static final int FALSE = 0; protected String encoding; /** * The data object required by the <CODE>Format</CODE> is an integer array. */ public static final Class intArray = (new int[0]).getClass(); /** * The data object required by the <CODE>Format</CODE> is a short array. */ public static final Class shortArray = (new short[0]).getClass(); /** * The data object required by the <CODE>Format</CODE> is a byte array. */ public static final Class byteArray = (new byte[0]).getClass(); /** * The data object required by the <CODE>Format</CODE> is an array of <CODE>Format</CODE> objects. */ public static final Class formatArray = (new Format[0]).getClass(); protected Class dataType = byteArray; protected Class clz = getClass(); // Cache the to optimize on // equals, matches & intersect. private long encodingCode = 0; /** * Constructs a <CODE>Format</CODE> that has the specified encoding type. * @param encoding A <CODE>String</CODE> that contains the encoding * type of the <CODE>Format</CODE> to be constructed. */ public Format(String encoding) { this.encoding = encoding; } /** * Constructs a <CODE>Format</CODE> that has the specified encoding and data types. * @param encoding A <CODE>String</CODE> that contains the encoding * type of the <CODE>Format</CODE> to be constructed. * @param dataType The type of data object required by the <CODE>Format</CODE> to be constructed, * such as: * <CODE>byteArray</CODE>, <CODE>intArray</CODE>, or <CODE>shortArray</CODE>. * For example, for a byte array the data type would be "<CODE>Format.byteArray</CODE>". */ public Format(String encoding, Class dataType) { this(encoding); this.dataType = dataType; } /** * Gets the uniquely-qualified encoding name for this <CODE>Format</CODE>. * <p> * In the reference implementation of JMF, these strings follow the QuickTime * codec strings. * * @return The encoding of the <CODE>Format</CODE>. */ public String getEncoding() { return encoding; } /** * Gets the type of the data that this <CODE>Format</CODE> requires. * For example, for byte array it returns "<CODE>byte[].class</CODE>". * @return The data type of this <CODE>Format</CODE>. */ public Class getDataType() { return dataType; } /** * Checks whether or not the specified <CODE>Format</CODE> is the same as this <CODE>Format</CODE>. * To be equal, the two <CODE>Formats</CODE> must be of the same type and all of their attributes must be the same. * @param format The <CODE>Format</CODE> to compare with this one. * @return <CODE>true</CODE> if the specified <CODE>Format</CODE> is the same as this one, <CODE>false</CODE> if it is not. */ public boolean equals(Object format) { if (format == null || clz != ((Format)format).clz) return false; String otherEncoding = ((Format)format).encoding; Class otherType = ((Format)format).dataType; return (dataType == otherType) && (encoding == otherEncoding || ((encoding != null && otherEncoding != null) && isSameEncoding((Format)format))); } /** * Checks whether or not the specified <CODE>Format</CODE> <EM>matches</EM> this <CODE>Format</CODE>. * Matches only compares the attributes that are defined in the specified <CODE>Format</CODE>, * unspecified attributes are ignored. * <p> * The two <CODE>Format</CODE> objects do not have to be of the same class to * match. For example, if "A" are "B" are being compared, a * match is possible if "A" is derived from "B" * or "B" is derived from "A". (The compared attributes must still match, or <CODE>matches</CODE> fails.) * @param format The <CODE>Format</CODE> to compare with this one. * @return <CODE>true</CODE> if the specified <CODE>Format</CODE> matches this one, <CODE>false</CODE> if it does not. */ public boolean matches(Format format) { if( format == null) return false; return (format.encoding == null || encoding == null || isSameEncoding(format)) && (format.dataType == null || dataType == null || format.dataType == dataType) && (clz.isAssignableFrom(format.clz) || format.clz.isAssignableFrom(clz)); } /** * Intersects the attributes of this format and the specified format to create * a new <CODE>Format</code> object. The two objects being intersected should either be of the * same type or one should be a subclass of the other. The resulting object will be * the same type as the subclass. <P> * Common attributes are intersected as follows: If both objects have NOT_SPECIFIED * values for an attribute, the result will also have a NOT_SPECIFIED value. If one * of them has a NOT_SPECIFIED value then the result will have the value that is * specified in the other object. If both objects have specified values then the value * in this object will be used. <P> * Attributes that are specific to the subclass will be carried forward to the result. * @param other The <CODE>Format</CODE> object to intersect with this * <CODE>Format</CODE>. * @return A <CODE>Format</CODE> object * with its attributes set to those attributes common to both <CODE>Format</CODE> objects. * @see #matches */ public Format intersects(Format other) { Format res; if (clz.isAssignableFrom(other.clz)) res = (Format)other.clone(); else if (other.clz.isAssignableFrom(clz)) res = (Format)clone(); else return null; if (res.encoding == null) res.encoding = (encoding != null ? encoding : other.encoding); if (res.dataType == null) res.dataType = (dataType != null ? dataType : other.dataType); return res; } /** * Checks if the encodings of both format objects are the same. Its * faster than calling String.equalsIgnoreCase to compare the two * encodings. * @return true if the encodings are the same, false otherwise. */ public boolean isSameEncoding(Format other) { if (encoding == null || other == null || other.encoding == null) return false; // Quick checks if (encoding == other.encoding) return true; if (encodingCode > 0 && other.encodingCode > 0) return encodingCode == other.encodingCode; // Works faster only for shorter strings of 10 chars or less. if (encoding.length() > 10) return encoding.equalsIgnoreCase(other.encoding); if (encodingCode == 0) { encodingCode = getEncodingCode(encoding); } // If the encoding code cannot be computed (out of bounds chars) // or in the off chance that its all spaces. if (encodingCode <= 0) return encoding.equalsIgnoreCase(other.encoding); if (other.encodingCode == 0) return other.isSameEncoding(this); else return encodingCode == other.encodingCode; } /** * Checks if the encoding of this format is same as the parameter. Its * faster than calling String.equalsIgnoreCase to compare the two * encodings. * @return true if the encodings are the same, false otherwise. */ public boolean isSameEncoding(String encoding) { if (this.encoding == null || encoding == null) return false; // Quick check if (this.encoding == encoding) return true; // Works faster only for shorter strings of 10 chars or less. if (this.encoding.length() > 10) return this.encoding.equalsIgnoreCase(encoding); // Compute encoding code only once if (encodingCode == 0) { encodingCode = getEncodingCode(this.encoding); } // If the encoding code cannot be computed (out of bounds chars) if (encodingCode < 0) return this.encoding.equalsIgnoreCase(encoding); long otherEncodingCode = getEncodingCode(encoding); return encodingCode == otherEncodingCode; } private long getEncodingCode(String enc) { byte chars[] = enc.getBytes(); byte b; long code = 0; for (int i = 0; i < enc.length(); i++) { b = chars[i]; if (b > 96 && b < 123) b -= 32; // lower to upper b -= 32; if (b > 63) return -1; code = (code << 6) | (long) b; } return code; } /** * Generate a format that's less restrictive than this format but * contains the basic attributes that will make this resulting format * useful for format matching. * @return A <CODE>Format</CODE> that's less restrictive than the * this format. */ public Format relax() { return (Format)clone(); } /** * Creates a clone of this <CODE>Format</CODE>. * @return A clone of this format. */ public Object clone() { Format f = new Format(encoding); f.copy(this); return f; } /** * Copies the attributes from the specified <CODE>Format</CODE> into this <CODE>Format</CODE>. * @param f The <CODE>Format</CODE> to copy the attributes from. */ protected void copy(Format f) { dataType = f.dataType; } /** * Gets a <CODE>String</CODE> representation of the <CODE>Format</CODE> attributes. * For example: "PCM, 44.1 KHz, Stereo, Signed". * @return A <CODE>String</CODE> that describes the <CODE>Format</CODE> attributes. */ public String toString() { return getEncoding(); } }