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}