/* * #%L * Fork of JAI Image I/O Tools. * %% * Copyright (C) 2008 - 2014 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are * those of the authors and should not be interpreted as representing official * policies, either expressed or implied, of any organization. * #L% */ /* * $RCSfile: Box.java,v $ * * * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for * use in the design, construction, operation or maintenance of any * nuclear facility. * * $Revision: 1.6 $ * $Date: 2007/09/05 20:03:20 $ * $State: Exp $ */ package com.sun.media.imageioimpl.plugins.jpeg2000; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import javax.imageio.metadata.IIOInvalidTreeException; import javax.imageio.metadata.IIOMetadataNode; import java.io.EOFException; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import java.util.StringTokenizer; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.imageio.IIOException; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageInputStream; import com.sun.media.imageioimpl.common.ImageUtil; /** * This class is defined to create the box of JP2 file format. A box has * a length, a type, an optional extra length and its content. The subclasses * should explain the content information. */ public class Box { /** The table to link tag names for all the JP2 boxes. */ private static Hashtable names = new Hashtable(); // Initializes the hash table "names". static { //children for the root names.put(new Integer(0x6A502020), "JPEG2000SignatureBox"); names.put(new Integer(0x66747970), "JPEG2000FileTypeBox"); // children for the boxes other than //JPEG2000SignatureBox/JPEG2000FileTypeBox names.put(new Integer(0x6A703269), "JPEG2000IntellectualPropertyRightsBox"); names.put(new Integer(0x786D6C20), "JPEG2000XMLBox"); names.put(new Integer(0x75756964), "JPEG2000UUIDBox"); names.put(new Integer(0x75696E66), "JPEG2000UUIDInfoBox"); // Children of HeadCStream names.put(new Integer(0x6a703268), "JPEG2000HeaderSuperBox"); names.put(new Integer(0x6a703263), "JPEG2000CodeStreamBox"); // Children of JPEG2000HeaderSuperBox names.put(new Integer(0x69686472), "JPEG2000HeaderBox"); // Optional boxes in JPEG2000HeaderSuperBox names.put(new Integer(0x62706363), "JPEG2000BitsPerComponentBox"); names.put(new Integer(0x636f6c72), "JPEG2000ColorSpecificationBox"); names.put(new Integer(0x70636c72), "JPEG2000PaletteBox"); names.put(new Integer(0x636d6170), "JPEG2000ComponentMappingBox"); names.put(new Integer(0x63646566), "JPEG2000ChannelDefinitionBox"); names.put(new Integer(0x72657320), "JPEG2000ResolutionBox"); // Children of JPEG2000ResolutionBox names.put(new Integer(0x72657363), "JPEG2000CaptureResolutionBox"); names.put(new Integer(0x72657364), "JPEG2000DefaultDisplayResolutionBox"); // Children of JPEG2000UUIDInfoBox names.put(new Integer(0x756c7374), "JPEG2000UUIDListBox"); names.put(new Integer(0x75726c20), "JPEG2000DataEntryURLBox"); } /** A Hashtable contains the class names for each type of the boxes. * This table will be used to construct a Box object from a Node object * by using reflection. */ private static Hashtable boxClasses = new Hashtable(); // Initializes the hash table "boxClasses". static { //children for the root boxClasses.put(new Integer(0x6A502020), SignatureBox.class); boxClasses.put(new Integer(0x66747970), FileTypeBox.class); // children for the boxes other than //JPEG2000SignatureBox/JPEG2000FileTypeBox boxClasses.put(new Integer(0x6A703269), Box.class); boxClasses.put(new Integer(0x786D6C20), XMLBox.class); boxClasses.put(new Integer(0x75756964), UUIDBox.class); // Children of JPEG2000HeaderSuperBox boxClasses.put(new Integer(0x69686472), HeaderBox.class); // Optional boxes in JPEG2000HeaderSuperBox boxClasses.put(new Integer(0x62706363), BitsPerComponentBox.class); boxClasses.put(new Integer(0x636f6c72), ColorSpecificationBox.class); boxClasses.put(new Integer(0x70636c72), PaletteBox.class); boxClasses.put(new Integer(0x636d6170), ComponentMappingBox.class); boxClasses.put(new Integer(0x63646566), ChannelDefinitionBox.class); boxClasses.put(new Integer(0x72657320), ResolutionBox.class); // Children of JPEG2000ResolutionBox boxClasses.put(new Integer(0x72657363), ResolutionBox.class); boxClasses.put(new Integer(0x72657364), ResolutionBox.class); // Children of JPEG2000UUIDInfoBox boxClasses.put(new Integer(0x756c7374), UUIDListBox.class); boxClasses.put(new Integer(0x75726c20), DataEntryURLBox.class); } /** Returns the XML tag name defined in JP2 XML xsd/dtd for the box * with the provided <code>type</code>. If the <code>type</code> is * not known, the string <code>"unknown"</code> is returned. */ public static String getName(int type) { String name = (String)names.get(new Integer(type)); return name == null ? "unknown" : name; } /** Returns the Box class for the box with the provided <code>type</code>. */ public static Class getBoxClass(int type) { if (type == 0x6a703268 || type == 0x72657320) return null; return (Class)boxClasses.get(new Integer(type)); } /** Returns the type String based on the provided name. */ public static String getTypeByName(String name) { Enumeration keys = names.keys(); while (keys.hasMoreElements()) { Integer i = (Integer)keys.nextElement(); if (name.equals(names.get(i))) return getTypeString(i.intValue()); } return null; } /** Creates a <code>Box</code> object with the provided <code>type</code> * based on the provided Node object based on reflection. */ public static Box createBox(int type, Node node) throws IIOInvalidTreeException { Class boxClass = (Class)boxClasses.get(new Integer(type)); try { // gets the constructor with <code>Node</code parameter Constructor cons = boxClass.getConstructor(new Class[] {Node.class}); if (cons != null) { return (Box)cons.newInstance(new Object[]{node}); } } catch(NoSuchMethodException e) { // If exception throws, create a <code>Box</code> instance. e.printStackTrace(); return new Box(node); } catch(InvocationTargetException e) { e.printStackTrace(); return new Box(node); } catch (IllegalAccessException e) { e.printStackTrace(); return new Box(node); } catch (InstantiationException e) { e.printStackTrace(); return new Box(node); } return null; } /** Extracts the value of the attribute from name. */ public static Object getAttribute(Node node, String name) { NamedNodeMap map = node.getAttributes(); node = map.getNamedItem(name); return (node != null) ? node.getNodeValue() : null; } /** Parses the byte array expressed by a string. */ public static byte[] parseByteArray(String value) { if (value == null) return null; StringTokenizer token = new StringTokenizer(value); int count = token.countTokens(); byte[] buf = new byte[count]; int i = 0; while(token.hasMoreElements()) { buf[i++] = new Byte(token.nextToken()).byteValue(); } return buf; } /** Parses the integer array expressed a string. */ protected static int[] parseIntArray(String value) { if (value == null) return null; StringTokenizer token = new StringTokenizer(value); int count = token.countTokens(); int[] buf = new int[count]; int i = 0; while(token.hasMoreElements()) { buf[i++] = new Integer(token.nextToken()).intValue(); } return buf; } /** Gets its <code>String</code> value from an <code>IIOMetadataNode</code>. */ protected static String getStringElementValue(Node node) { if (node instanceof IIOMetadataNode) { Object obj = ((IIOMetadataNode)node).getUserObject(); if (obj instanceof String) return (String)obj; } return node.getNodeValue(); } /** Gets its byte value from an <code>IIOMetadataNode</code>. */ protected static byte getByteElementValue(Node node) { if (node instanceof IIOMetadataNode) { Object obj = ((IIOMetadataNode)node).getUserObject(); if (obj instanceof Byte) return ((Byte)obj).byteValue(); } String value = node.getNodeValue(); if (value != null) return new Byte(value).byteValue(); return (byte)0; } /** Gets its integer value from an <code>IIOMetadataNode</code>. */ protected static int getIntElementValue(Node node) { if (node instanceof IIOMetadataNode) { Object obj = ((IIOMetadataNode)node).getUserObject(); if (obj instanceof Integer) return ((Integer)obj).intValue(); } String value = node.getNodeValue(); if (value != null) return new Integer(value).intValue(); return 0; } /** Gets its short value from an <code>IIOMetadataNode</code>. */ protected static short getShortElementValue(Node node) { if (node instanceof IIOMetadataNode) { Object obj = ((IIOMetadataNode)node).getUserObject(); if (obj instanceof Short) return ((Short)obj).shortValue(); } String value = node.getNodeValue(); if (value != null) return new Short(value).shortValue(); return (short)0; } /** Gets the byte array from an <code>IIOMetadataNode</code>. */ protected static byte[] getByteArrayElementValue(Node node) { if (node instanceof IIOMetadataNode) { Object obj = ((IIOMetadataNode)node).getUserObject(); if (obj instanceof byte[]) return (byte[])obj; } return parseByteArray(node.getNodeValue()); } /** Gets the integer array from an <code>IIOMetadataNode</code>. */ protected static int[] getIntArrayElementValue(Node node) { if (node instanceof IIOMetadataNode) { Object obj = ((IIOMetadataNode)node).getUserObject(); if (obj instanceof int[]) return (int[])obj; } return parseIntArray(node.getNodeValue()); } /** Copies that four bytes of an integer into the byte array. Necessary * for the subclasses to compose the content array from the data elements */ public static void copyInt(byte[] data, int pos, int value) { data[pos++] = (byte)(value >> 24); data[pos++] = (byte)(value >> 16); data[pos++] = (byte)(value >> 8); data[pos++] = (byte)(value & 0xFF); } /** Converts the box type from integer to string. This is necessary because * type is defined as String in xsd/dtd and integer in the box classes. */ public static String getTypeString(int type) { byte[] buf = new byte[4]; for (int i = 3; i >= 0; i--) { buf[i] = (byte)(type & 0xFF); type >>>= 8; } return new String(buf); } /** * Converts the box type from integer to string. This is necessary because * type is defined as String in xsd/dtd and integer in the box classes. */ public static int getTypeInt(String s) { byte[] buf = s.getBytes(); int t = buf[0]; for (int i = 1; i < 4; i++) { t = (t <<8) | buf[i]; } return t; } /** Box length, extra length, type and content data array */ protected int length; protected long extraLength; protected int type; protected byte[] data; /** Constructs a <code>Box</code> instance using the provided * the box type and the box content in byte array format. * * @param length The provided box length. * @param type The provided box type. * @param data The provided box content in a byte array. * * @throws IllegalArgumentException If the length of the content byte array * is not length - 8. */ public Box(int length, int type, byte[] data) { this.type = type; setLength(length); setContent(data); } /** Constructs a <code>Box</code> instance using the provided * the box type, the box extra length, and the box content in byte * array format. In this case, the length of the box is set to 1, * which indicates the extra length is meaningful. * * @param length The provided box length. * @param type The provided box type. * @param extraLength The provided box extra length. * @param data The provided box content in a byte array. * * @throws IllegalArgumentException If the length of the content byte array * is not extra length - 16. */ public Box(int length, int type, long extraLength, byte[] data) { this.type = type; setLength(length); if (length == 1) setExtraLength(extraLength); setContent(data); } /** Constructs a <code>Box</code> instance from the provided <code> * ImageInputStream</code> at the specified position. * * @param iis The <code>ImageInputStream</code> contains the box. * @param pos The position from where to read the box. * @throws IOException If any IOException is thrown in the called read * methods. */ public Box(ImageInputStream iis, int pos) throws IOException { read(iis, pos); } /** * Constructs a Box from an "unknown" Node. This node has at * least the attribute "Type", and may have the attribute "Length", * "ExtraLength" and a child "Content". The child node content is a * IIOMetaDataNode with a byte[] user object. */ public Box(Node node) throws IIOInvalidTreeException { NodeList children = node.getChildNodes(); String value = (String)Box.getAttribute(node, "Type"); type = getTypeInt(value); if (value == null || names.get(new Integer(type)) == null) throw new IIOInvalidTreeException("Type is not defined", node); value = (String)Box.getAttribute(node, "Length"); if (value != null) length = new Integer(value).intValue(); value = (String)Box.getAttribute(node, "ExtraLength"); if (value != null) extraLength = new Long(value).longValue(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if ("Content".equals(child.getNodeName())) { if (child instanceof IIOMetadataNode) { IIOMetadataNode cnode = (IIOMetadataNode)child; try { data = (byte[])cnode.getUserObject(); } catch (Exception e) { } }else { data = getByteArrayElementValue(child); } if (data == null) { value = node.getNodeValue(); if (value != null) data = value.getBytes(); } } } } /** Creates an <code>IIOMetadataNode</code> from this * box. The format of this node is defined in the XML dtd and xsd * for the JP2 image file. */ public IIOMetadataNode getNativeNode() { String name = Box.getName(getType()); if (name == null) name = "unknown"; IIOMetadataNode node = new IIOMetadataNode(name); setDefaultAttributes(node); IIOMetadataNode child = new IIOMetadataNode("Content"); child.setUserObject(data); child.setNodeValue(ImageUtil.convertObjectToString(data)); node.appendChild(child); return node; } /** Creates an <code>IIOMetadataNode</code> from this * box. The format of this node is defined in the XML dtd and xsd * for the JP2 image file. * * This method is designed for the types of boxes whose XML tree * only has 2 levels. */ protected IIOMetadataNode getNativeNodeForSimpleBox() { try { Method m = this.getClass().getMethod("getElementNames", (Class[])null); String[] elementNames = (String[])m.invoke(null, (Object[])null); IIOMetadataNode node = new IIOMetadataNode(Box.getName(getType())); setDefaultAttributes(node); for (int i = 0; i < elementNames.length; i++) { IIOMetadataNode child = new IIOMetadataNode(elementNames[i]); m = this.getClass().getMethod("get" + elementNames[i], (Class[])null); Object obj = m.invoke(this, (Object[])null); child.setUserObject(obj); child.setNodeValue(ImageUtil.convertObjectToString(obj)); node.appendChild(child); } return node; } catch (Exception e) { throw new IllegalArgumentException(I18N.getString("Box0")); } } /** Sets the default attributes, "Length", "Type", and "ExtraLength", to * the provided <code>IIOMetadataNode</code>. */ protected void setDefaultAttributes(IIOMetadataNode node) { node.setAttribute("Length", Integer.toString(length)); node.setAttribute("Type", getTypeString(type)); if (length == 1) { node.setAttribute("ExtraLength", Long.toString(extraLength)); } } /** Returns the box length. */ public int getLength() { return length; } /** Returns the box type. */ public int getType() { return type; } /** Returns the box extra length. */ public long getExtraLength() { return extraLength; } /** Returns the box content in byte array. */ public byte[] getContent() { if (data == null) compose(); return data; } /** Sets the box length to the provided value. */ public void setLength(int length) { this.length = length; } /** Sets the box extra length length to the provided value. */ public void setExtraLength(long extraLength) { if (length != 1) throw new IllegalArgumentException(I18N.getString("Box1")); this.extraLength = extraLength; } /** Sets the box content. If the content length is not length -8 or * extra length - 16, IllegalArgumentException will be thrown. */ public void setContent(byte[] data) { if (data != null && ((length ==1 && (extraLength - 16 != data.length)) || (length != 1 && length - 8 != data.length))) throw new IllegalArgumentException(I18N.getString("Box2")); this.data = data; if (data != null) parse(data); } /** Writes this box instance into a <code>ImageOutputStream</code>. */ public void write(ImageOutputStream ios) throws IOException { ios.writeInt(length); ios.writeInt(type); if (length == 1) { ios.writeLong(extraLength); ios.write(data, 0, (int)extraLength); } else if (data != null) ios.write(data, 0, length); } /** Reads a box from the <code>ImageInputStream</code. at the provided * position. */ public void read(ImageInputStream iis, int pos) throws IOException { iis.mark(); iis.seek(pos); length = iis.readInt(); type = iis.readInt(); int dataLength = 0; if(length == 0) { // Length unknown at time of stream creation. long streamLength = iis.length(); if(streamLength != -1) // Calculate box length from known stream length. dataLength = (int)(streamLength - iis.getStreamPosition()); else { // Calculate box length by reading to EOF. long dataPos = iis.getStreamPosition(); int bufLen = 1024; byte[] buf = new byte[bufLen]; long savePos = dataPos; try { iis.readFully(buf); dataLength += bufLen; savePos = iis.getStreamPosition(); } catch(EOFException eofe) { iis.seek(savePos); while(iis.read() != -1) dataLength++; } iis.seek(dataPos); } } else if(length == 1) { // Length given by XL parameter. extraLength = iis.readLong(); dataLength = (int)(extraLength - 16); } else if(length >= 8 && length < (1 << 32)) { // Length given by L parameter. dataLength = length - 8; } else { // Illegal value for L parameter. throw new IIOException("Illegal value "+length+ " for box length parameter."); } data = new byte[dataLength]; iis.readFully(data); iis.reset(); } /** Parses the data elements from the byte array. The subclasses should * override this method. */ protected void parse(byte[] data) { } /** Composes the content byte array from the data elements. */ protected void compose() { } }