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}