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