001/*
002 * $RCSfile: Box.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.6 $
042 * $Date: 2007/09/05 20:03:20 $
043 * $State: Exp $
044 */
045package com.sun.media.imageioimpl.plugins.jpeg2000;
046
047import java.lang.reflect.Constructor;
048import java.lang.reflect.Method;
049import java.lang.reflect.InvocationTargetException;
050import javax.imageio.metadata.IIOInvalidTreeException;
051import javax.imageio.metadata.IIOMetadataNode;
052
053import java.io.EOFException;
054import java.io.IOException;
055import java.util.Enumeration;
056import java.util.Hashtable;
057import java.util.StringTokenizer;
058
059import org.w3c.dom.NamedNodeMap;
060import org.w3c.dom.Node;
061import org.w3c.dom.NodeList;
062
063import javax.imageio.IIOException;
064import javax.imageio.stream.ImageOutputStream;
065import javax.imageio.stream.ImageInputStream;
066
067import com.sun.media.imageioimpl.common.ImageUtil;
068
069/**
070 * This class is defined to create the box of JP2 file format.  A box has
071 *  a length, a type, an optional extra length and its content.  The subclasses
072 *  should explain the content information.
073 */
074public class Box {
075    /** The table to link tag names for all the JP2 boxes. */
076    private static Hashtable names = new Hashtable();
077
078    // Initializes the hash table "names".
079    static {
080        //children for the root
081        names.put(new Integer(0x6A502020), "JPEG2000SignatureBox");
082        names.put(new Integer(0x66747970), "JPEG2000FileTypeBox");
083
084        // children for the boxes other than
085        //JPEG2000SignatureBox/JPEG2000FileTypeBox
086        names.put(new Integer(0x6A703269),
087                              "JPEG2000IntellectualPropertyRightsBox");
088        names.put(new Integer(0x786D6C20), "JPEG2000XMLBox");
089        names.put(new Integer(0x75756964), "JPEG2000UUIDBox");
090        names.put(new Integer(0x75696E66), "JPEG2000UUIDInfoBox");
091
092        // Children of HeadCStream
093        names.put(new Integer(0x6a703268), "JPEG2000HeaderSuperBox");
094        names.put(new Integer(0x6a703263), "JPEG2000CodeStreamBox");
095
096        // Children of JPEG2000HeaderSuperBox
097        names.put(new Integer(0x69686472), "JPEG2000HeaderBox");
098
099        // Optional boxes in JPEG2000HeaderSuperBox
100        names.put(new Integer(0x62706363), "JPEG2000BitsPerComponentBox");
101        names.put(new Integer(0x636f6c72), "JPEG2000ColorSpecificationBox");
102        names.put(new Integer(0x70636c72), "JPEG2000PaletteBox");
103        names.put(new Integer(0x636d6170), "JPEG2000ComponentMappingBox");
104        names.put(new Integer(0x63646566), "JPEG2000ChannelDefinitionBox");
105        names.put(new Integer(0x72657320), "JPEG2000ResolutionBox");
106
107        // Children of JPEG2000ResolutionBox
108        names.put(new Integer(0x72657363), "JPEG2000CaptureResolutionBox");
109        names.put(new Integer(0x72657364),
110                              "JPEG2000DefaultDisplayResolutionBox");
111
112        // Children of JPEG2000UUIDInfoBox
113        names.put(new Integer(0x756c7374), "JPEG2000UUIDListBox");
114        names.put(new Integer(0x75726c20), "JPEG2000DataEntryURLBox");
115    }
116
117    /** A Hashtable contains the class names for each type of the boxes.
118     *  This table will be used to construct a Box object from a Node object
119     *  by using reflection.
120     */
121    private static Hashtable boxClasses = new Hashtable();
122
123    // Initializes the hash table "boxClasses".
124    static {
125        //children for the root
126        boxClasses.put(new Integer(0x6A502020), SignatureBox.class);
127        boxClasses.put(new Integer(0x66747970), FileTypeBox.class);
128
129        // children for the boxes other than
130        //JPEG2000SignatureBox/JPEG2000FileTypeBox
131        boxClasses.put(new Integer(0x6A703269), Box.class);
132        boxClasses.put(new Integer(0x786D6C20), XMLBox.class);
133        boxClasses.put(new Integer(0x75756964), UUIDBox.class);
134
135        // Children of JPEG2000HeaderSuperBox
136        boxClasses.put(new Integer(0x69686472), HeaderBox.class);
137
138        // Optional boxes in JPEG2000HeaderSuperBox
139        boxClasses.put(new Integer(0x62706363), BitsPerComponentBox.class);
140        boxClasses.put(new Integer(0x636f6c72), ColorSpecificationBox.class);
141        boxClasses.put(new Integer(0x70636c72), PaletteBox.class);
142        boxClasses.put(new Integer(0x636d6170), ComponentMappingBox.class);
143        boxClasses.put(new Integer(0x63646566), ChannelDefinitionBox.class);
144        boxClasses.put(new Integer(0x72657320), ResolutionBox.class);
145
146        // Children of JPEG2000ResolutionBox
147        boxClasses.put(new Integer(0x72657363), ResolutionBox.class);
148        boxClasses.put(new Integer(0x72657364), ResolutionBox.class);
149
150        // Children of JPEG2000UUIDInfoBox
151        boxClasses.put(new Integer(0x756c7374), UUIDListBox.class);
152        boxClasses.put(new Integer(0x75726c20), DataEntryURLBox.class);
153    }
154
155    /** Returns the XML tag name defined in JP2 XML xsd/dtd for the box
156     *  with the provided <code>type</code>. If the <code>type</code> is
157     * not known, the string <code>"unknown"</code> is returned.
158     */
159    public static String getName(int type) {
160        String name = (String)names.get(new Integer(type));
161        return name == null ? "unknown" : name;
162    }
163
164    /** Returns the Box class for the box with the provided <code>type</code>.
165     */
166    public static Class getBoxClass(int type) {
167        if (type == 0x6a703268 || type == 0x72657320)
168            return null;
169        return (Class)boxClasses.get(new Integer(type));
170    }
171
172    /** Returns the type String based on the provided name. */
173    public static String getTypeByName(String name) {
174        Enumeration keys = names.keys();
175        while (keys.hasMoreElements()) {
176            Integer i = (Integer)keys.nextElement();
177            if (name.equals(names.get(i)))
178                return getTypeString(i.intValue());
179        }
180        return null;
181    }
182
183    /** Creates a <code>Box</code> object with the provided <code>type</code>
184     *  based on the provided Node object based on reflection.
185     */
186    public static Box createBox(int type,
187                                Node node) throws IIOInvalidTreeException {
188        Class boxClass = (Class)boxClasses.get(new Integer(type));
189
190        try {
191            // gets the constructor with <code>Node</code parameter
192            Constructor cons =
193                boxClass.getConstructor(new Class[] {Node.class});
194            if (cons != null) {
195                return (Box)cons.newInstance(new Object[]{node});
196            }
197        } catch(NoSuchMethodException e) {
198            // If exception throws, create a <code>Box</code> instance.
199            e.printStackTrace();
200            return new Box(node);
201        } catch(InvocationTargetException e) {
202            e.printStackTrace();
203            return new Box(node);
204        } catch (IllegalAccessException e) {
205            e.printStackTrace();
206            return new Box(node);
207        } catch (InstantiationException e) {
208            e.printStackTrace();
209            return new Box(node);
210        }
211
212        return null;
213    }
214
215    /** Extracts the value of the attribute from name. */
216    public static Object getAttribute(Node node, String name) {
217        NamedNodeMap map = node.getAttributes();
218        node = map.getNamedItem(name);
219        return (node != null) ? node.getNodeValue() : null;
220    }
221
222    /** Parses the byte array expressed by a string. */
223    public static byte[] parseByteArray(String value) {
224        if (value == null)
225            return null;
226
227        StringTokenizer token = new StringTokenizer(value);
228        int count = token.countTokens();
229
230        byte[] buf = new byte[count];
231        int i = 0;
232        while(token.hasMoreElements()) {
233            buf[i++] = new Byte(token.nextToken()).byteValue();
234        }
235        return buf;
236    }
237
238    /** Parses the integer array expressed a string. */
239    protected static int[] parseIntArray(String value) {
240        if (value == null)
241            return null;
242
243        StringTokenizer token = new StringTokenizer(value);
244        int count = token.countTokens();
245
246        int[] buf = new int[count];
247        int i = 0;
248        while(token.hasMoreElements()) {
249            buf[i++] = new Integer(token.nextToken()).intValue();
250        }
251        return buf;
252    }
253
254    /** Gets its <code>String</code> value from an <code>IIOMetadataNode</code>.
255     */
256    protected static String getStringElementValue(Node node) {
257
258        if (node instanceof IIOMetadataNode) { 
259            Object obj = ((IIOMetadataNode)node).getUserObject(); 
260            if (obj instanceof String) 
261                return (String)obj; 
262        }
263
264        return node.getNodeValue();
265    }
266
267    /** Gets its byte value from an <code>IIOMetadataNode</code>. */
268    protected static byte getByteElementValue(Node node) {      
269        if (node instanceof IIOMetadataNode) {
270            Object obj = ((IIOMetadataNode)node).getUserObject();
271            if (obj instanceof Byte)
272                return ((Byte)obj).byteValue();
273        }
274
275        String value = node.getNodeValue();
276        if (value != null)
277            return new Byte(value).byteValue();
278        return (byte)0;
279    }
280
281    /** Gets its integer value from an <code>IIOMetadataNode</code>. */
282    protected static int getIntElementValue(Node node) {
283        if (node instanceof IIOMetadataNode) {
284            Object obj = ((IIOMetadataNode)node).getUserObject();
285            if (obj instanceof Integer)
286                return ((Integer)obj).intValue();
287        }
288
289        String value = node.getNodeValue();
290        if (value != null)
291            return new Integer(value).intValue();
292        return 0;
293    }
294
295    /** Gets its short value from an <code>IIOMetadataNode</code>. */
296    protected static short getShortElementValue(Node node) {
297        if (node instanceof IIOMetadataNode) {
298            Object obj = ((IIOMetadataNode)node).getUserObject();
299            if (obj instanceof Short)
300                return ((Short)obj).shortValue();
301        }
302        String value = node.getNodeValue();
303        if (value != null)
304            return new Short(value).shortValue();
305        return (short)0;
306    }
307
308    /** Gets the byte array from an <code>IIOMetadataNode</code>. */
309    protected static byte[] getByteArrayElementValue(Node node) {
310        if (node instanceof IIOMetadataNode) {
311            Object obj = ((IIOMetadataNode)node).getUserObject();
312            if (obj instanceof byte[])
313                return (byte[])obj;
314        }
315
316        return parseByteArray(node.getNodeValue());
317    }
318
319    /** Gets the integer array from an <code>IIOMetadataNode</code>. */
320    protected static int[] getIntArrayElementValue(Node node) {
321        if (node instanceof IIOMetadataNode) {
322            Object obj = ((IIOMetadataNode)node).getUserObject();
323            if (obj instanceof int[])
324                return (int[])obj;
325        }
326
327        return parseIntArray(node.getNodeValue());
328    }
329
330    /** Copies that four bytes of an integer into the byte array.  Necessary
331     *  for the subclasses to compose the content array from the data elements
332     */
333    public static void copyInt(byte[] data, int pos, int value) {
334        data[pos++] = (byte)(value >> 24);
335        data[pos++] = (byte)(value >> 16);
336        data[pos++] = (byte)(value >> 8);
337        data[pos++] = (byte)(value & 0xFF);
338    }
339
340    /** Converts the box type from integer to string. This is necessary because
341     *  type is defined as String in xsd/dtd and integer in the box classes.
342     */
343    public static String getTypeString(int type) {
344        byte[] buf = new byte[4];
345        for (int i = 3; i >= 0; i--) {
346            buf[i] = (byte)(type & 0xFF);
347            type >>>= 8;
348        }
349
350        return new String(buf);
351    }
352
353    /**
354     * Converts the box type from integer to string.  This is necessary because
355     *  type is defined as String in xsd/dtd and integer in the box classes.
356     */
357    public static int getTypeInt(String s) {
358        byte[] buf = s.getBytes();
359        int t = buf[0];
360        for (int i = 1; i < 4; i++) {
361            t = (t <<8) | buf[i];
362        }
363
364        return t;
365    }
366
367    /** Box length, extra length, type and content data array */
368    protected int length;
369    protected long extraLength;
370    protected int type;
371    protected byte[] data;
372
373    /** Constructs a <code>Box</code> instance using the provided
374     *  the box type and the box content in byte array format.
375     *
376     * @param length The provided box length.
377     * @param type The provided box type.
378     * @param data The provided box content in a byte array.
379     *
380     * @throws IllegalArgumentException If the length of the content byte array
381     *         is not length - 8.
382     */
383    public Box(int length, int type, byte[] data) {
384        this.type = type;
385        setLength(length);
386        setContent(data);
387    }
388
389    /** Constructs a <code>Box</code> instance using the provided
390     *  the box type, the box extra length, and the box content in byte
391     *  array format.  In this case, the length of the box is set to 1,
392     *  which indicates the extra length is meaningful.
393     *
394     * @param length The provided box length.
395     * @param type The provided box type.
396     * @param extraLength The provided box extra length.
397     * @param data The provided box content in a byte array.
398     *
399     * @throws IllegalArgumentException If the length of the content byte array
400     *         is not extra length - 16.
401     */
402    public Box(int length, int type, long extraLength, byte[] data) {
403        this.type = type;
404        setLength(length);
405        if (length == 1)
406            setExtraLength(extraLength);
407        setContent(data);
408    }
409
410    /** Constructs a <code>Box</code> instance from the provided <code>
411     *  ImageInputStream</code> at the specified position.
412     *
413     * @param iis The <code>ImageInputStream</code> contains the box.
414     * @param pos The position from where to read the box.
415     * @throws IOException If any IOException is thrown in the called read
416     *         methods.
417     */
418    public Box(ImageInputStream iis, int pos) throws IOException {
419        read(iis, pos);
420    }
421
422    /**
423     * Constructs a Box from an "unknown" Node.  This node has at
424     * least the attribute "Type", and may have the attribute "Length",
425     * "ExtraLength" and a child "Content".  The child node content is a
426     * IIOMetaDataNode with a byte[] user object.
427     */
428    public Box(Node node) throws IIOInvalidTreeException {
429        NodeList children = node.getChildNodes();
430
431        String value = (String)Box.getAttribute(node, "Type");
432        type = getTypeInt(value);
433        if (value == null || names.get(new Integer(type)) == null)
434            throw new IIOInvalidTreeException("Type is not defined", node);
435
436        value = (String)Box.getAttribute(node, "Length");
437        if (value != null)
438            length = new Integer(value).intValue();
439
440        value = (String)Box.getAttribute(node, "ExtraLength");
441        if (value != null)
442            extraLength = new Long(value).longValue();
443
444        for (int i = 0; i < children.getLength(); i++) {
445            Node child = children.item(i);
446            if ("Content".equals(child.getNodeName())) {
447                if (child instanceof IIOMetadataNode) {
448                    IIOMetadataNode cnode = (IIOMetadataNode)child;
449                    try {
450                        data = (byte[])cnode.getUserObject();
451                    } catch (Exception e) {
452                    }
453                }else  {
454                    data = getByteArrayElementValue(child);
455                }
456
457                if (data == null) {
458                    value = node.getNodeValue();
459                    if (value != null)
460                        data = value.getBytes();
461                }
462            }
463        }
464    }
465
466    /** Creates an <code>IIOMetadataNode</code> from this
467     *  box.  The format of this node is defined in the XML dtd and xsd
468     *  for the JP2 image file.
469     */
470    public IIOMetadataNode getNativeNode() {
471        String name = Box.getName(getType());
472        if (name == null)
473            name = "unknown";
474
475        IIOMetadataNode node = new IIOMetadataNode(name);
476        setDefaultAttributes(node);
477        IIOMetadataNode child = new IIOMetadataNode("Content");
478        child.setUserObject(data);
479        child.setNodeValue(ImageUtil.convertObjectToString(data));
480        node.appendChild(child);
481
482        return node;
483    }
484
485    /** Creates an <code>IIOMetadataNode</code> from this
486     *  box.  The format of this node is defined in the XML dtd and xsd
487     *  for the JP2 image file.
488     *
489     *  This method is designed for the types of boxes whose XML tree
490     *  only has 2 levels.
491     */
492    protected IIOMetadataNode getNativeNodeForSimpleBox() {
493        try {
494            Method m = this.getClass().getMethod("getElementNames",
495                                                 (Class[])null);
496            String[] elementNames = (String[])m.invoke(null, (Object[])null);
497
498            IIOMetadataNode node = new IIOMetadataNode(Box.getName(getType()));
499            setDefaultAttributes(node);
500            for (int i = 0; i < elementNames.length; i++) {
501                IIOMetadataNode child = new IIOMetadataNode(elementNames[i]);
502                m = this.getClass().getMethod("get" + elementNames[i],
503                                              (Class[])null);
504                Object obj = m.invoke(this, (Object[])null);
505                child.setUserObject(obj);
506                child.setNodeValue(ImageUtil.convertObjectToString(obj));
507                node.appendChild(child);
508            }
509            return node;
510        } catch (Exception e) {
511            throw new IllegalArgumentException(I18N.getString("Box0"));
512        }
513    }
514
515    /** Sets the default attributes, "Length", "Type", and "ExtraLength", to
516     *  the provided <code>IIOMetadataNode</code>.
517     */
518    protected void setDefaultAttributes(IIOMetadataNode node) {
519        node.setAttribute("Length", Integer.toString(length));
520        node.setAttribute("Type", getTypeString(type));
521
522        if (length == 1) {
523            node.setAttribute("ExtraLength", Long.toString(extraLength));
524        }
525    }
526
527    /** Returns the box length. */
528    public int getLength() {
529        return length;
530    }
531
532    /** Returns the box type. */
533    public int getType() {
534        return type;
535    }
536
537    /** Returns the box extra length. */
538    public long getExtraLength() {
539        return extraLength;
540    }
541
542    /** Returns the box content in byte array. */
543    public byte[] getContent() {
544        if (data == null)
545            compose();
546        return data;
547    }
548
549    /** Sets the box length to the provided value. */
550    public void setLength(int length) {
551        this.length = length;
552    }
553
554    /** Sets the box extra length length to the provided value. */
555    public void setExtraLength(long extraLength) {
556        if (length != 1)
557            throw new IllegalArgumentException(I18N.getString("Box1"));
558        this.extraLength = extraLength;
559    }
560
561    /** Sets the box content.  If the content length is not length -8 or
562     *  extra length - 16, IllegalArgumentException will be thrown.
563     */
564    public void setContent(byte[] data) {
565        if (data != null &&
566            ((length ==1 && (extraLength - 16 != data.length)) ||
567            (length != 1 && length - 8 != data.length)))
568            throw new IllegalArgumentException(I18N.getString("Box2"));
569        this.data = data;
570        if (data != null)
571            parse(data);
572    }
573
574    /** Writes this box instance into a <code>ImageOutputStream</code>. */
575    public void write(ImageOutputStream ios) throws IOException {
576        ios.writeInt(length);
577        ios.writeInt(type);
578        if (length == 1) {
579            ios.writeLong(extraLength);
580            ios.write(data, 0, (int)extraLength);
581        } else if (data != null)
582            ios.write(data, 0, length);
583    }
584
585    /** Reads a box from the <code>ImageInputStream</code. at the provided
586     *  position.
587     */
588    public void read(ImageInputStream iis, int pos) throws IOException {
589        iis.mark();
590        iis.seek(pos);
591        length = iis.readInt();
592        type = iis.readInt();
593        int dataLength = 0;
594        if(length == 0) {
595            // Length unknown at time of stream creation.
596            long streamLength = iis.length();
597            if(streamLength != -1)
598                // Calculate box length from known stream length.
599                dataLength = (int)(streamLength - iis.getStreamPosition());
600            else {
601                // Calculate box length by reading to EOF.
602                long dataPos = iis.getStreamPosition();
603                int bufLen = 1024;
604                byte[] buf = new byte[bufLen];
605                long savePos = dataPos;
606                try {
607                    iis.readFully(buf);
608                    dataLength += bufLen;
609                    savePos = iis.getStreamPosition();
610                } catch(EOFException eofe) {
611                    iis.seek(savePos);
612                    while(iis.read() != -1) dataLength++;
613                }
614                iis.seek(dataPos);
615            }
616        } else if(length == 1) {
617            // Length given by XL parameter.
618            extraLength = iis.readLong();
619            dataLength = (int)(extraLength - 16);
620        } else if(length >= 8 && length < (1 << 32)) {
621            // Length given by L parameter.
622            dataLength = length - 8;
623        } else {
624            // Illegal value for L parameter.
625            throw new IIOException("Illegal value "+length+
626                                   " for box length parameter.");
627        }
628        data = new byte[dataLength];
629        iis.readFully(data);
630        iis.reset();
631    }
632
633    /** Parses the data elements from the byte array.  The subclasses should
634     *  override this method.
635     */
636    protected void parse(byte[] data) {
637    }
638
639    /** Composes the content byte array from the data elements.
640     */
641    protected void compose() {
642    }
643}