1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.image.metadata.values; 4 5 import java.io.Externalizable; 6 import java.io.IOException; 7 import java.io.ObjectInput; 8 import java.io.ObjectOutput; 9 10 import org.w3c.dom.Element; 11 import org.w3c.dom.Document; 12 13 import com.lightcrafts.image.metadata.ImageMetadataDirectory; 14 import com.lightcrafts.image.metadata.ImageMetaType; 15 import com.lightcrafts.utils.xml.XMLUtil; 16 17 import static com.lightcrafts.image.metadata.XMPConstants.*; 18 19 /** 20 * An <code>ImageMetaValue</code> contains a metadata value extracted from an 21 * image. 22 * 23 * @author Paul J. Lucas [paul@lightcrafts.com] 24 */ 25 public abstract class ImageMetaValue implements 26 Cloneable, Comparable, Externalizable { 27 28 ////////// public ///////////////////////////////////////////////////////// 29 30 /** 31 * Parse and append a new value. 32 * 33 * @param newValue The new value. 34 * @throws IllegalArgumentException if the {@link String} is an illegal 35 * value for the given underlying type. 36 */ appendValue( String newValue )37 public final synchronized void appendValue( String newValue ) { 38 if ( !m_isEditable ) 39 throw new IllegalStateException(); 40 appendValueImpl( newValue ); 41 m_isEdited = true; 42 clearCache(); 43 } 44 45 /** 46 * Clears the changed flag. 47 * 48 * @see #isEdited() 49 */ clearEdited()50 public void clearEdited() { 51 m_isEdited = false; 52 } 53 54 /** 55 * {@inheritDoc} 56 */ clone()57 public ImageMetaValue clone() { 58 try { 59 return (ImageMetaValue)super.clone(); 60 } 61 catch ( CloneNotSupportedException e ) { 62 // 63 // CloneNotSupportedException as a checked exception is dumb. 64 // 65 throw new IllegalStateException( e ); 66 } 67 } 68 69 /** 70 * Compares this <code>ImageMetaValue</code> to another object. By 71 * default, a string comparison is done. 72 * 73 * @param o The object, presumed to be another <code>ImageMetaValue</code>, 74 * to compare to. 75 * @return Returns a negative integer, zero, or a positive integer as this 76 * <code>ImageMetaValue</code> is less than, equal to, or greater than the 77 * other <code>ImageMetaValue</code>. 78 * @throws IllegalArgumentException if the other object is not an 79 * <code>ImageMetaValue</code>. 80 * @see #compareTo(String) 81 */ compareTo( Object o )82 public int compareTo( Object o ) { 83 if ( o instanceof ImageMetaValue ) { 84 final ImageMetaValue rightValue = (ImageMetaValue)o; 85 final String leftString = getStringValue(); 86 final String rightString = rightValue.getStringValue(); 87 if ( leftString == null ) 88 return rightString == null ? 0 : -1; 89 if ( rightString == null ) 90 return 1; 91 return leftString.compareTo( rightString ); 92 } 93 throw new IllegalArgumentException( 94 "Can not compare an ImageMetaValue to a " + o.getClass().getName() 95 ); 96 } 97 98 /** 99 * Compares this <code>ImageMetaValue</code> to a {@link String}. By 100 * default, a string comparison is done. 101 * 102 * @param s The {@link String} to compare to. 103 * @return Returns a negative integer, zero, or a positive integer as this 104 * <code>ImageMetaValue</code> is less than, equal to, or greater than the 105 * string. 106 * @see #compareTo(Object) 107 */ compareTo( String s )108 public int compareTo( String s ) { 109 final String leftString = getStringValue(); 110 if ( leftString == null ) 111 return s == null ? 0 : -1; 112 return leftString.compareTo( s ); 113 } 114 115 /** 116 * Creates a new, empty instance of a class derived from 117 * <code>ImageMetaValue</code> based on the given type. 118 * 119 * @param type The {@link ImageMetaType} of the instance to create. 120 * @return Returns a new instance of the requested type. 121 */ create( ImageMetaType type )122 public static ImageMetaValue create( ImageMetaType type ) { 123 switch ( type ) { 124 case META_DATE: 125 return new DateMetaValue(); 126 case META_DOUBLE: 127 return new DoubleMetaValue(); 128 case META_FLOAT: 129 return new FloatMetaValue(); 130 case META_SBYTE: 131 return new ByteMetaValue(); 132 case META_SLONG: 133 return new LongMetaValue(); 134 case META_SRATIONAL: 135 return new RationalMetaValue(); 136 case META_SSHORT: 137 return new ShortMetaValue(); 138 case META_STRING: 139 return new StringMetaValue(); 140 case META_UNDEFINED: 141 return new UndefinedMetaValue(); 142 case META_UBYTE: 143 return new UnsignedByteMetaValue(); 144 case META_ULONG: 145 return new UnsignedLongMetaValue(); 146 case META_URATIONAL: 147 return new UnsignedRationalMetaValue(); 148 case META_USHORT: 149 return new UnsignedShortMetaValue(); 150 default: 151 throw new IllegalArgumentException(); 152 } 153 } 154 155 /** 156 * Gets this metadata value as a <code>byte</code>. 157 * 158 * @return Returns said value. 159 */ getByteValue()160 public final int getByteValue() { 161 return (byte)getLongValue(); 162 } 163 164 /** 165 * Gets this metadata value as a <code>double</code>. 166 * 167 * @return Returns said value. 168 */ getDoubleValue()169 public double getDoubleValue() { 170 return getLongValue(); 171 } 172 173 /** 174 * Gets this metadata value as a <code>float</code>. 175 * 176 * @return Returns said value. 177 */ getFloatValue()178 public float getFloatValue() { 179 return (float)getDoubleValue(); 180 } 181 182 /** 183 * Gets this metadata value as an <code>int</code>. 184 * 185 * @return Returns said value. 186 */ getIntValue()187 public final int getIntValue() { 188 return getIntValueAt(0); 189 } 190 191 /** 192 * Gets the metadata value at the given index as a {@code long}. 193 * 194 * @param index The index of the value to get. 195 * @return Returns said value. 196 */ getIntValueAt(int index)197 public int getIntValueAt(int index) { 198 return (int) getLongValueAt(index); 199 } 200 201 /** 202 * Gets this metadata value as a <code>long</code>. 203 * 204 * @return Returns said value. 205 */ getLongValue()206 public final long getLongValue() { 207 return getLongValueAt(0); 208 } 209 210 /** 211 * Gets the metadata value at the given index as a {@code long}. 212 * 213 * @param index The index of the value to get. 214 * @return Returns said value. 215 */ getLongValueAt(int index)216 public abstract long getLongValueAt(int index); 217 218 /** 219 * Returns the {@link ImageMetadataDirectory} to which this 220 * <code>ImageMetaValue</code> belongs. 221 * 222 * @return Returns said {@link ImageMetadataDirectory}. 223 */ getOwningDirectory()224 public final ImageMetadataDirectory getOwningDirectory() { 225 return m_owningDirectory; 226 } 227 228 /** 229 * Returns the tag ID that this <code>ImageMetaValue</code> is a value for. 230 * 231 * @return Returns said tag ID. 232 */ getOwningTagID()233 public final int getOwningTagID() { 234 return m_owningTagID; 235 } 236 237 /** 238 * Gets this metadata value as a <code>short</code>. 239 * 240 * @return Returns said value. 241 */ getShortValue()242 public final short getShortValue() { 243 return (short)getLongValue(); 244 } 245 246 /** 247 * Gets this metadata value as a <code>String</code>. 248 * 249 * @return Returns said value. 250 */ getStringValue()251 public final String getStringValue() { 252 return getStringValueAt(0); 253 } 254 255 /** 256 * Gets this metadata value at the given index as a <code>String</code>. 257 * 258 * @return Returns said value. 259 */ getStringValueAt(int index)260 public final String getStringValueAt(int index) { 261 final String[] values = getValues(); 262 return values != null ? values[index] : null; 263 } 264 265 /** 266 * Gets this metadata value's tag name. 267 * 268 * @return Returns said tag name or <code>null</code> if it either has 269 * no owning {@link ImageMetadataDirectory} or said directory has no such 270 * tag. 271 */ getTagName()272 public final String getTagName() { 273 final ImageMetadataDirectory dir = getOwningDirectory(); 274 return dir != null ? 275 dir.getTagNameFor( getOwningTagID(), false ) : null; 276 } 277 278 /** 279 * Gets the type of this metadata value. 280 * 281 * @return Returns said type. 282 * @see #isNumeric() 283 */ getType()284 public abstract ImageMetaType getType(); 285 286 /** 287 * Gets this metadata value as an unsigned <code>byte</code>. 288 * 289 * @return Returns said value. 290 */ getUnsignedByteValue()291 public final short getUnsignedByteValue() { 292 return (short)(getLongValue() & 0x000000FF); 293 } 294 295 /** 296 * Gets this metadata value as an unsigned <code>byte</code>. 297 * 298 * @return Returns said value. 299 */ getUnsignedShortValue()300 public final int getUnsignedShortValue() { 301 return (int)(getLongValue() & 0x0000FFFF); 302 } 303 304 /** 305 * Gets the number of values. 306 * 307 * @return Returns said number. 308 */ getValueCount()309 public abstract int getValueCount(); 310 311 /** 312 * Gets the values as an array of {@link String}. 313 * 314 * @return Returns said array. 315 */ getValues()316 public final synchronized String[] getValues() { 317 if ( m_valuesCache == null ) 318 m_valuesCache = getValuesImpl(); 319 return m_valuesCache; 320 } 321 322 /** 323 * Returns whether this value should be displayed to the user. 324 * 325 * @return Returns <code>true</code> only if this value is displayable. 326 */ isDisplayable()327 public final boolean isDisplayable() { 328 return m_isDisplayable; 329 } 330 331 /** 332 * Gets whether this metadata value is editable. 333 * 334 * @return Returns <code>true</code> only if it's editable. 335 */ isEditable()336 public final boolean isEditable() { 337 return m_isEditable; 338 } 339 340 /** 341 * Returns whether this value has ever been edited. 342 * 343 * @return Returns <code>true</code> only if it has. 344 * @see #clearEdited() 345 */ isEdited()346 public final boolean isEdited() { 347 return m_isEdited; 348 } 349 350 /** 351 * Checks whether the given value is a legal value as a parameter to 352 * {@link #setValues(String...)}. 353 * 354 * @param value The value to check. 355 * @return Returns <code>true</code> only if the value is legal. 356 */ isLegalValue( String value )357 public boolean isLegalValue( String value ) { 358 if ( m_owningDirectory != null ) 359 return m_owningDirectory.isLegalValue( m_owningTagID, value ); 360 return true; 361 } 362 363 /** 364 * Returns whether this image metadata value is numeric. 365 * 366 * @return Returns <code>false</code> by default. 367 * @see #getType() 368 */ isNumeric()369 public boolean isNumeric() { 370 return false; 371 } 372 373 /** 374 * Sets this metadata value from a <code>byte</code>. 375 * 376 * @param newValue The new value. 377 */ setByteValue( byte newValue )378 public final void setByteValue( byte newValue ) { 379 setLongValue( newValue ); 380 } 381 382 /** 383 * Sets this metadata value from a <code>double</code>. 384 * 385 * @param newValue The new value. 386 */ setDoubleValue( double newValue )387 public void setDoubleValue( double newValue ) { 388 setLongValue( (long)newValue ); 389 } 390 391 /** 392 * Sets this metadata value from a <code>float</code>. 393 * 394 * @param newValue The new value. 395 */ setFloatValue( float newValue )396 public final void setFloatValue( float newValue ) { 397 setDoubleValue( newValue ); 398 } 399 400 /** 401 * Sets this metadata value from an <code>int</code>. 402 * 403 * @param newValue The new value. 404 */ setIntValue( int newValue )405 public final void setIntValue( int newValue ) { 406 setLongValue( newValue ); 407 } 408 409 /** 410 * Sets whether this metadata is changeable. 411 * 412 * @param isChangeable The new changeable value. 413 * @return Returns the old changeable value. 414 */ setIsChangeable( boolean isChangeable )415 public final boolean setIsChangeable( boolean isChangeable ) { 416 final boolean old = m_isEditable; 417 m_isEditable = isChangeable; 418 return old; 419 } 420 421 /** 422 * Sets this metadata value from a <code>long</code>. 423 * 424 * @param newValue The new value. 425 */ setLongValue( long newValue )426 public abstract void setLongValue( long newValue ); 427 428 /** 429 * Sets this metadata value as "non-displayable." This is done 430 * for {@link UndefinedMetaValue}s, values that are IFD pointers, or 431 * contain subvalues that need to be expanded. Once set, it can't be unset 432 * (nor should there ever be a reason to). 433 */ setNonDisplayable()434 public final void setNonDisplayable() { 435 m_isDisplayable = false; 436 } 437 438 /** 439 * Set the {@link ImageMetadataDirectory} to which this 440 * <code>ImageMetaValue</code>'s belongs. 441 * 442 * @param dir The owning {@link ImageMetadataDirectory}. 443 */ setOwningDirectory( ImageMetadataDirectory dir )444 public final void setOwningDirectory( ImageMetadataDirectory dir ) { 445 m_owningDirectory = dir; 446 } 447 448 /** 449 * Set the tag ID that this <code>ImageMetaValue</code> is a value for. 450 * 451 * @param tagID The owning {@link ImageMetadataDirectory}. 452 */ setOwningTagID( int tagID )453 public final void setOwningTagID( int tagID ) { 454 m_owningTagID = tagID; 455 } 456 457 /** 458 * Sets this metadata value from a <code>short</code>. 459 * 460 * @param newValue The new value. 461 */ setShortValue( short newValue )462 public void setShortValue( short newValue ) { 463 setLongValue( newValue ); 464 } 465 466 /** 467 * Parse and set the values. 468 * 469 * @param newValues The array of new values. 470 * @throws IllegalArgumentException if any one of the {@link String}s are 471 * an illegal value for the given underlying type. 472 * @see #isLegalValue(String) 473 */ setValues( String... newValues )474 public final synchronized void setValues( String... newValues ) { 475 checkIsEditable(); 476 for ( String value : newValues ) 477 if ( !isLegalValue( value ) ) 478 throw new IllegalArgumentException( value ); 479 setValuesImpl( newValues ); 480 dirty(); 481 } 482 483 /** 484 * Convert this value to its {@link String} representation. Multiple 485 * values are separated by commas. 486 * 487 * @return Returns said {@link String}. 488 */ toString()489 public final synchronized String toString() { 490 if ( m_toStringCache == null ) { 491 final ImageMetadataDirectory dir = getOwningDirectory(); 492 if ( dir != null ) { 493 // 494 // First, consult the the owning directory to see if this 495 // metadata value needs special-handling. 496 // 497 m_toStringCache = dir.valueToString( this ); 498 if ( m_toStringCache == null ) { 499 // 500 // The owning directory didn't create a string for it 501 // because it didn't need special-handling, so revert to 502 // the ordinary way to create its string. 503 // 504 m_toStringCache = toStringImpl(); 505 } 506 } 507 } 508 return m_toStringCache; 509 } 510 511 /** 512 * Convert this value to is {@link String} representation but without 513 * doing any {@link ImageMetadataDirectory} value consultation. 514 * 515 * @return Returns said string. 516 */ toStringWithoutDirectoryConsult()517 public final String toStringWithoutDirectoryConsult() { 518 return toStringImpl(); 519 } 520 521 /** 522 * Convert this value to its XMP XML element representation. 523 * 524 * @param xmpDoc The {@link Document} to create the elements as part of. 525 * @param nsURI The XML namespace URI to use. 526 * @param prefix The XML namespace prefix to use. 527 * @return Returns said XMP XML element or <code>null</code> if this value 528 * can not be converted to an XMP XML element. 529 */ toXMP( Document xmpDoc, String nsURI, String prefix )530 public Element toXMP( Document xmpDoc, String nsURI, String prefix ) { 531 final String tagName = getTagName(); 532 if ( tagName == null ) 533 return null; 534 Element tagElement = null; 535 final String[] values = getValues(); 536 if ( values.length == 1 ) { 537 // 538 // The "if" below is commented out (for now) otherwise you can 539 // never delete IPTC metadata from a photo if there's only one 540 // value. 541 // 542 //if ( values[0].length() > 0 ) { 543 tagElement = 544 xmpDoc.createElementNS( nsURI, prefix + ':' + tagName ); 545 XMLUtil.setTextContentOf( tagElement, values[0] ); 546 //} 547 } else if ( values.length > 1 ) { 548 tagElement = 549 xmpDoc.createElementNS( nsURI, prefix + ':' + tagName ); 550 final Element seqElement = XMLUtil.addElementChildTo( 551 tagElement, XMP_RDF_NS, XMP_RDF_PREFIX + ":Seq" 552 ); 553 for ( String value : values ) { 554 final Element listItem = XMLUtil.addElementChildTo( 555 seqElement, XMP_RDF_NS, XMP_RDF_PREFIX + ":li" 556 ); 557 XMLUtil.setTextContentOf( listItem, value ); 558 } 559 } 560 return tagElement; 561 } 562 563 /** 564 * Reconstitutes this <code>ImageMetaValue</code> from the externalized 565 * form. 566 * 567 * @param in The {@link ObjectInput} to read from. 568 */ readExternal( ObjectInput in )569 public abstract void readExternal( ObjectInput in ) throws IOException; 570 571 /** 572 * Writes this <code>ImageMetaValue</code> to an externalized form. 573 * 574 * @param out The {@link ObjectOutput} to write to. 575 */ writeExternal( ObjectOutput out )576 public abstract void writeExternal( ObjectOutput out ) throws IOException; 577 578 ////////// protected ////////////////////////////////////////////////////// 579 580 /** 581 * Construct an <code>ImageMetaValue</code>. 582 */ ImageMetaValue()583 protected ImageMetaValue() { 584 m_isEditable = false; 585 m_isDisplayable = true; 586 } 587 588 /** 589 * Parse and append a new value. 590 * 591 * @param newValue The new value. 592 * @throws IllegalArgumentException if the {@link String} is an illegal 593 * value for the given underlying type. 594 */ appendValueImpl( String newValue )595 protected abstract void appendValueImpl( String newValue ); 596 597 /** 598 * Checks whether this value is editable. 599 * 600 * @throws IllegalStateException if the value is not editable. 601 */ checkIsEditable()602 protected final void checkIsEditable() { 603 if ( !m_isEditable ) 604 throw new IllegalStateException(); 605 } 606 607 /** 608 * Clear the caches used for this object. 609 */ clearCache()610 protected final synchronized void clearCache() { 611 m_toStringCache = null; 612 m_valuesCache = null; 613 } 614 615 /** 616 * Marks this value as "dirty", i.e., having been changed. 617 */ dirty()618 protected void dirty() { 619 m_isEdited = true; 620 clearCache(); 621 } 622 623 /** 624 * Gets the values as an array of {@link String}. 625 * 626 * @return Returns said array. 627 */ getValuesImpl()628 protected abstract String[] getValuesImpl(); 629 630 /** 631 * Parse and set the values. 632 * 633 * @param newValue The array of new values. 634 * @throws IllegalArgumentException if any one of the {@link String}s are 635 * an illegal value for the given underlying type. 636 */ setValuesImpl( String[] newValue )637 protected abstract void setValuesImpl( String[] newValue ); 638 639 /** 640 * Convert this value to its {@link String} representation. Multiple 641 * values are separated by commas. 642 * 643 * @return Returns said string. 644 */ toStringImpl()645 protected abstract String toStringImpl(); 646 647 /** 648 * Reads the header information from the externalized form. 649 * 650 * @param in The {@link ObjectInput} to read from. 651 * @return Returns the number of values. 652 */ readHeader( ObjectInput in )653 protected final int readHeader( ObjectInput in ) throws IOException { 654 m_isEditable = in.readBoolean(); 655 m_isDisplayable = in.readBoolean(); 656 return in.readInt(); 657 } 658 659 /** 660 * Writes the header information to the externalized form. 661 * 662 * @param out The {@link ObjectOutput} to write to. 663 */ writeHeader( ObjectOutput out )664 protected final void writeHeader( ObjectOutput out ) throws IOException { 665 out.writeBoolean( m_isEditable ); 666 out.writeBoolean( m_isDisplayable ); 667 out.writeInt( getValueCount() ); 668 } 669 670 ////////// private //////////////////////////////////////////////////////// 671 672 /** 673 * This is <code>true</code> if this value should be displayed to the user. 674 * Values that should not be displayed include {@link UndefinedMetaValue}s, 675 * values that are IFD pointers, or contain subvalues that need to be 676 * expanded. 677 */ 678 private boolean m_isDisplayable; 679 680 /** 681 * This is <code>true</code> only if this metadata value is allowed to be 682 * edited. 683 */ 684 private boolean m_isEditable; 685 686 /** 687 * This is <code>true</code> only if the current value has been edited 688 * from the original value. 689 */ 690 private boolean m_isEdited; 691 692 /** 693 * The {@link ImageMetadataDirectory} to which this 694 * <code>ImageMetaValue</code>'s owning belongs. 695 */ 696 private ImageMetadataDirectory m_owningDirectory; 697 698 /** 699 * The tag ID that this <code>ImageMetaValue</code> is a value for. 700 */ 701 private int m_owningTagID; 702 703 /** 704 * A cache of the {@link #toString()} representation of the values. 705 */ 706 private String m_toStringCache; 707 708 /** 709 * A cache of the string representations of the values. 710 */ 711 private String[] m_valuesCache; 712 } 713 /* vim:set et sw=4 ts=4: */ 714