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