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 &quot;non-displayable.&quot;  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