source: josm/trunk/src/com/drew/metadata/Directory.java@ 10865

Last change on this file since 10865 was 10862, checked in by Don-vip, 8 years ago

update to metadata-extractor 2.9.1

File size: 36.0 KB
Line 
1/*
2 * Copyright 2002-2016 Drew Noakes
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * More information about this project is available at:
17 *
18 * https://drewnoakes.com/code/exif/
19 * https://github.com/drewnoakes/metadata-extractor
20 */
21package com.drew.metadata;
22
23import com.drew.lang.Rational;
24import com.drew.lang.annotations.NotNull;
25import com.drew.lang.annotations.Nullable;
26
27import java.io.UnsupportedEncodingException;
28import java.lang.reflect.Array;
29import java.text.DateFormat;
30import java.text.DecimalFormat;
31import java.text.ParseException;
32import java.text.SimpleDateFormat;
33import java.util.*;
34import java.util.regex.Matcher;
35import java.util.regex.Pattern;
36
37/**
38 * Abstract base class for all directory implementations, having methods for getting and setting tag values of various
39 * data types.
40 *
41 * @author Drew Noakes https://drewnoakes.com
42 */
43public abstract class Directory
44{
45 private static final DecimalFormat _floatFormat = new DecimalFormat("0.###");
46
47 /** Map of values hashed by type identifiers. */
48 @NotNull
49 protected final Map<Integer, Object> _tagMap = new HashMap<Integer, Object>();
50
51 /**
52 * A convenient list holding tag values in the order in which they were stored.
53 * This is used for creation of an iterator, and for counting the number of
54 * defined tags.
55 */
56 @NotNull
57 protected final Collection<Tag> _definedTagList = new ArrayList<Tag>();
58
59 @NotNull
60 private final Collection<String> _errorList = new ArrayList<String>(4);
61
62 /** The descriptor used to interpret tag values. */
63 protected TagDescriptor _descriptor;
64
65 @Nullable
66 private Directory _parent;
67
68// ABSTRACT METHODS
69
70 /**
71 * Provides the name of the directory, for display purposes. E.g. <code>Exif</code>
72 *
73 * @return the name of the directory
74 */
75 @NotNull
76 public abstract String getName();
77
78 /**
79 * Provides the map of tag names, hashed by tag type identifier.
80 *
81 * @return the map of tag names
82 */
83 @NotNull
84 protected abstract HashMap<Integer, String> getTagNameMap();
85
86 protected Directory()
87 {}
88
89// VARIOUS METHODS
90
91 /**
92 * Gets a value indicating whether the directory is empty, meaning it contains no errors and no tag values.
93 */
94 public boolean isEmpty()
95 {
96 return _errorList.isEmpty() && _definedTagList.isEmpty();
97 }
98
99 /**
100 * Indicates whether the specified tag type has been set.
101 *
102 * @param tagType the tag type to check for
103 * @return true if a value exists for the specified tag type, false if not
104 */
105 @java.lang.SuppressWarnings({ "UnnecessaryBoxing" })
106 public boolean containsTag(int tagType)
107 {
108 return _tagMap.containsKey(Integer.valueOf(tagType));
109 }
110
111 /**
112 * Returns an Iterator of Tag instances that have been set in this Directory.
113 *
114 * @return an Iterator of Tag instances
115 */
116 @NotNull
117 public Collection<Tag> getTags()
118 {
119 return Collections.unmodifiableCollection(_definedTagList);
120 }
121
122 /**
123 * Returns the number of tags set in this Directory.
124 *
125 * @return the number of tags set in this Directory
126 */
127 public int getTagCount()
128 {
129 return _definedTagList.size();
130 }
131
132 /**
133 * Sets the descriptor used to interpret tag values.
134 *
135 * @param descriptor the descriptor used to interpret tag values
136 */
137 @java.lang.SuppressWarnings({ "ConstantConditions" })
138 public void setDescriptor(@NotNull TagDescriptor descriptor)
139 {
140 if (descriptor == null)
141 throw new NullPointerException("cannot set a null descriptor");
142 _descriptor = descriptor;
143 }
144
145 /**
146 * Registers an error message with this directory.
147 *
148 * @param message an error message.
149 */
150 public void addError(@NotNull String message)
151 {
152 _errorList.add(message);
153 }
154
155 /**
156 * Gets a value indicating whether this directory has any error messages.
157 *
158 * @return true if the directory contains errors, otherwise false
159 */
160 public boolean hasErrors()
161 {
162 return _errorList.size() > 0;
163 }
164
165 /**
166 * Used to iterate over any error messages contained in this directory.
167 *
168 * @return an iterable collection of error message strings.
169 */
170 @NotNull
171 public Iterable<String> getErrors()
172 {
173 return Collections.unmodifiableCollection(_errorList);
174 }
175
176 /** Returns the count of error messages in this directory. */
177 public int getErrorCount()
178 {
179 return _errorList.size();
180 }
181
182 @Nullable
183 public Directory getParent()
184 {
185 return _parent;
186 }
187
188 public void setParent(@NotNull Directory parent)
189 {
190 _parent = parent;
191 }
192
193// TAG SETTERS
194
195 /**
196 * Sets an <code>int</code> value for the specified tag.
197 *
198 * @param tagType the tag's value as an int
199 * @param value the value for the specified tag as an int
200 */
201 public void setInt(int tagType, int value)
202 {
203 setObject(tagType, value);
204 }
205
206 /**
207 * Sets an <code>int[]</code> (array) for the specified tag.
208 *
209 * @param tagType the tag identifier
210 * @param ints the int array to store
211 */
212 public void setIntArray(int tagType, @NotNull int[] ints)
213 {
214 setObjectArray(tagType, ints);
215 }
216
217 /**
218 * Sets a <code>float</code> value for the specified tag.
219 *
220 * @param tagType the tag's value as an int
221 * @param value the value for the specified tag as a float
222 */
223 public void setFloat(int tagType, float value)
224 {
225 setObject(tagType, value);
226 }
227
228 /**
229 * Sets a <code>float[]</code> (array) for the specified tag.
230 *
231 * @param tagType the tag identifier
232 * @param floats the float array to store
233 */
234 public void setFloatArray(int tagType, @NotNull float[] floats)
235 {
236 setObjectArray(tagType, floats);
237 }
238
239 /**
240 * Sets a <code>double</code> value for the specified tag.
241 *
242 * @param tagType the tag's value as an int
243 * @param value the value for the specified tag as a double
244 */
245 public void setDouble(int tagType, double value)
246 {
247 setObject(tagType, value);
248 }
249
250 /**
251 * Sets a <code>double[]</code> (array) for the specified tag.
252 *
253 * @param tagType the tag identifier
254 * @param doubles the double array to store
255 */
256 public void setDoubleArray(int tagType, @NotNull double[] doubles)
257 {
258 setObjectArray(tagType, doubles);
259 }
260
261 /**
262 * Sets a <code>String</code> value for the specified tag.
263 *
264 * @param tagType the tag's value as an int
265 * @param value the value for the specified tag as a String
266 */
267 @java.lang.SuppressWarnings({ "ConstantConditions" })
268 public void setString(int tagType, @NotNull String value)
269 {
270 if (value == null)
271 throw new NullPointerException("cannot set a null String");
272 setObject(tagType, value);
273 }
274
275 /**
276 * Sets a <code>String[]</code> (array) for the specified tag.
277 *
278 * @param tagType the tag identifier
279 * @param strings the String array to store
280 */
281 public void setStringArray(int tagType, @NotNull String[] strings)
282 {
283 setObjectArray(tagType, strings);
284 }
285
286 /**
287 * Sets a <code>boolean</code> value for the specified tag.
288 *
289 * @param tagType the tag's value as an int
290 * @param value the value for the specified tag as a boolean
291 */
292 public void setBoolean(int tagType, boolean value)
293 {
294 setObject(tagType, value);
295 }
296
297 /**
298 * Sets a <code>long</code> value for the specified tag.
299 *
300 * @param tagType the tag's value as an int
301 * @param value the value for the specified tag as a long
302 */
303 public void setLong(int tagType, long value)
304 {
305 setObject(tagType, value);
306 }
307
308 /**
309 * Sets a <code>java.util.Date</code> value for the specified tag.
310 *
311 * @param tagType the tag's value as an int
312 * @param value the value for the specified tag as a java.util.Date
313 */
314 public void setDate(int tagType, @NotNull java.util.Date value)
315 {
316 setObject(tagType, value);
317 }
318
319 /**
320 * Sets a <code>Rational</code> value for the specified tag.
321 *
322 * @param tagType the tag's value as an int
323 * @param rational rational number
324 */
325 public void setRational(int tagType, @NotNull Rational rational)
326 {
327 setObject(tagType, rational);
328 }
329
330 /**
331 * Sets a <code>Rational[]</code> (array) for the specified tag.
332 *
333 * @param tagType the tag identifier
334 * @param rationals the Rational array to store
335 */
336 public void setRationalArray(int tagType, @NotNull Rational[] rationals)
337 {
338 setObjectArray(tagType, rationals);
339 }
340
341 /**
342 * Sets a <code>byte[]</code> (array) for the specified tag.
343 *
344 * @param tagType the tag identifier
345 * @param bytes the byte array to store
346 */
347 public void setByteArray(int tagType, @NotNull byte[] bytes)
348 {
349 setObjectArray(tagType, bytes);
350 }
351
352 /**
353 * Sets a <code>Object</code> for the specified tag.
354 *
355 * @param tagType the tag's value as an int
356 * @param value the value for the specified tag
357 * @throws NullPointerException if value is <code>null</code>
358 */
359 @java.lang.SuppressWarnings( { "ConstantConditions", "UnnecessaryBoxing" })
360 public void setObject(int tagType, @NotNull Object value)
361 {
362 if (value == null)
363 throw new NullPointerException("cannot set a null object");
364
365 if (!_tagMap.containsKey(Integer.valueOf(tagType))) {
366 _definedTagList.add(new Tag(tagType, this));
367 }
368// else {
369// final Object oldValue = _tagMap.get(tagType);
370// if (!oldValue.equals(value))
371// addError(String.format("Overwritten tag 0x%s (%s). Old=%s, New=%s", Integer.toHexString(tagType), getTagName(tagType), oldValue, value));
372// }
373 _tagMap.put(tagType, value);
374 }
375
376 /**
377 * Sets an array <code>Object</code> for the specified tag.
378 *
379 * @param tagType the tag's value as an int
380 * @param array the array of values for the specified tag
381 */
382 public void setObjectArray(int tagType, @NotNull Object array)
383 {
384 // for now, we don't do anything special -- this method might be a candidate for removal once the dust settles
385 setObject(tagType, array);
386 }
387
388// TAG GETTERS
389
390 /**
391 * Returns the specified tag's value as an int, if possible. Every attempt to represent the tag's value as an int
392 * is taken. Here is a list of the action taken depending upon the tag's original type:
393 * <ul>
394 * <li> int - Return unchanged.
395 * <li> Number - Return an int value (real numbers are truncated).
396 * <li> Rational - Truncate any fractional part and returns remaining int.
397 * <li> String - Attempt to parse string as an int. If this fails, convert the char[] to an int (using shifts and OR).
398 * <li> Rational[] - Return int value of first item in array.
399 * <li> byte[] - Return int value of first item in array.
400 * <li> int[] - Return int value of first item in array.
401 * </ul>
402 *
403 * @throws MetadataException if no value exists for tagType or if it cannot be converted to an int.
404 */
405 public int getInt(int tagType) throws MetadataException
406 {
407 Integer integer = getInteger(tagType);
408 if (integer!=null)
409 return integer;
410
411 Object o = getObject(tagType);
412 if (o == null)
413 throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
414 throw new MetadataException("Tag '" + tagType + "' cannot be converted to int. It is of type '" + o.getClass() + "'.");
415 }
416
417 /**
418 * Returns the specified tag's value as an Integer, if possible. Every attempt to represent the tag's value as an
419 * Integer is taken. Here is a list of the action taken depending upon the tag's original type:
420 * <ul>
421 * <li> int - Return unchanged
422 * <li> Number - Return an int value (real numbers are truncated)
423 * <li> Rational - Truncate any fractional part and returns remaining int
424 * <li> String - Attempt to parse string as an int. If this fails, convert the char[] to an int (using shifts and OR)
425 * <li> Rational[] - Return int value of first item in array if length &gt; 0
426 * <li> byte[] - Return int value of first item in array if length &gt; 0
427 * <li> int[] - Return int value of first item in array if length &gt; 0
428 * </ul>
429 *
430 * If the value is not found or cannot be converted to int, <code>null</code> is returned.
431 */
432 @Nullable
433 public Integer getInteger(int tagType)
434 {
435 Object o = getObject(tagType);
436
437 if (o == null)
438 return null;
439
440 if (o instanceof Number) {
441 return ((Number)o).intValue();
442 } else if (o instanceof String) {
443 try {
444 return Integer.parseInt((String)o);
445 } catch (NumberFormatException nfe) {
446 // convert the char array to an int
447 String s = (String)o;
448 byte[] bytes = s.getBytes();
449 long val = 0;
450 for (byte aByte : bytes) {
451 val = val << 8;
452 val += (aByte & 0xff);
453 }
454 return (int)val;
455 }
456 } else if (o instanceof Rational[]) {
457 Rational[] rationals = (Rational[])o;
458 if (rationals.length == 1)
459 return rationals[0].intValue();
460 } else if (o instanceof byte[]) {
461 byte[] bytes = (byte[])o;
462 if (bytes.length == 1)
463 return (int)bytes[0];
464 } else if (o instanceof int[]) {
465 int[] ints = (int[])o;
466 if (ints.length == 1)
467 return ints[0];
468 }
469 return null;
470 }
471
472 /**
473 * Gets the specified tag's value as a String array, if possible. Only supported
474 * where the tag is set as String[], String, int[], byte[] or Rational[].
475 *
476 * @param tagType the tag identifier
477 * @return the tag's value as an array of Strings. If the value is unset or cannot be converted, <code>null</code> is returned.
478 */
479 @Nullable
480 public String[] getStringArray(int tagType)
481 {
482 Object o = getObject(tagType);
483 if (o == null)
484 return null;
485 if (o instanceof String[])
486 return (String[])o;
487 if (o instanceof String)
488 return new String[] { (String)o };
489 if (o instanceof int[]) {
490 int[] ints = (int[])o;
491 String[] strings = new String[ints.length];
492 for (int i = 0; i < strings.length; i++)
493 strings[i] = Integer.toString(ints[i]);
494 return strings;
495 } else if (o instanceof byte[]) {
496 byte[] bytes = (byte[])o;
497 String[] strings = new String[bytes.length];
498 for (int i = 0; i < strings.length; i++)
499 strings[i] = Byte.toString(bytes[i]);
500 return strings;
501 } else if (o instanceof Rational[]) {
502 Rational[] rationals = (Rational[])o;
503 String[] strings = new String[rationals.length];
504 for (int i = 0; i < strings.length; i++)
505 strings[i] = rationals[i].toSimpleString(false);
506 return strings;
507 }
508 return null;
509 }
510
511 /**
512 * Gets the specified tag's value as an int array, if possible. Only supported
513 * where the tag is set as String, Integer, int[], byte[] or Rational[].
514 *
515 * @param tagType the tag identifier
516 * @return the tag's value as an int array
517 */
518 @Nullable
519 public int[] getIntArray(int tagType)
520 {
521 Object o = getObject(tagType);
522 if (o == null)
523 return null;
524 if (o instanceof int[])
525 return (int[])o;
526 if (o instanceof Rational[]) {
527 Rational[] rationals = (Rational[])o;
528 int[] ints = new int[rationals.length];
529 for (int i = 0; i < ints.length; i++) {
530 ints[i] = rationals[i].intValue();
531 }
532 return ints;
533 }
534 if (o instanceof short[]) {
535 short[] shorts = (short[])o;
536 int[] ints = new int[shorts.length];
537 for (int i = 0; i < shorts.length; i++) {
538 ints[i] = shorts[i];
539 }
540 return ints;
541 }
542 if (o instanceof byte[]) {
543 byte[] bytes = (byte[])o;
544 int[] ints = new int[bytes.length];
545 for (int i = 0; i < bytes.length; i++) {
546 ints[i] = bytes[i];
547 }
548 return ints;
549 }
550 if (o instanceof CharSequence) {
551 CharSequence str = (CharSequence)o;
552 int[] ints = new int[str.length()];
553 for (int i = 0; i < str.length(); i++) {
554 ints[i] = str.charAt(i);
555 }
556 return ints;
557 }
558 if (o instanceof Integer)
559 return new int[] { (Integer)o };
560
561 return null;
562 }
563
564 /**
565 * Gets the specified tag's value as an byte array, if possible. Only supported
566 * where the tag is set as String, Integer, int[], byte[] or Rational[].
567 *
568 * @param tagType the tag identifier
569 * @return the tag's value as a byte array
570 */
571 @Nullable
572 public byte[] getByteArray(int tagType)
573 {
574 Object o = getObject(tagType);
575 if (o == null) {
576 return null;
577 } else if (o instanceof Rational[]) {
578 Rational[] rationals = (Rational[])o;
579 byte[] bytes = new byte[rationals.length];
580 for (int i = 0; i < bytes.length; i++) {
581 bytes[i] = rationals[i].byteValue();
582 }
583 return bytes;
584 } else if (o instanceof byte[]) {
585 return (byte[])o;
586 } else if (o instanceof int[]) {
587 int[] ints = (int[])o;
588 byte[] bytes = new byte[ints.length];
589 for (int i = 0; i < ints.length; i++) {
590 bytes[i] = (byte)ints[i];
591 }
592 return bytes;
593 } else if (o instanceof short[]) {
594 short[] shorts = (short[])o;
595 byte[] bytes = new byte[shorts.length];
596 for (int i = 0; i < shorts.length; i++) {
597 bytes[i] = (byte)shorts[i];
598 }
599 return bytes;
600 } else if (o instanceof CharSequence) {
601 CharSequence str = (CharSequence)o;
602 byte[] bytes = new byte[str.length()];
603 for (int i = 0; i < str.length(); i++) {
604 bytes[i] = (byte)str.charAt(i);
605 }
606 return bytes;
607 }
608 if (o instanceof Integer)
609 return new byte[] { ((Integer)o).byteValue() };
610
611 return null;
612 }
613
614 /** Returns the specified tag's value as a double, if possible. */
615 public double getDouble(int tagType) throws MetadataException
616 {
617 Double value = getDoubleObject(tagType);
618 if (value!=null)
619 return value;
620 Object o = getObject(tagType);
621 if (o == null)
622 throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
623 throw new MetadataException("Tag '" + tagType + "' cannot be converted to a double. It is of type '" + o.getClass() + "'.");
624 }
625 /** Returns the specified tag's value as a Double. If the tag is not set or cannot be converted, <code>null</code> is returned. */
626 @Nullable
627 public Double getDoubleObject(int tagType)
628 {
629 Object o = getObject(tagType);
630 if (o == null)
631 return null;
632 if (o instanceof String) {
633 try {
634 return Double.parseDouble((String)o);
635 } catch (NumberFormatException nfe) {
636 return null;
637 }
638 }
639 if (o instanceof Number)
640 return ((Number)o).doubleValue();
641
642 return null;
643 }
644
645 /** Returns the specified tag's value as a float, if possible. */
646 public float getFloat(int tagType) throws MetadataException
647 {
648 Float value = getFloatObject(tagType);
649 if (value!=null)
650 return value;
651 Object o = getObject(tagType);
652 if (o == null)
653 throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
654 throw new MetadataException("Tag '" + tagType + "' cannot be converted to a float. It is of type '" + o.getClass() + "'.");
655 }
656
657 /** Returns the specified tag's value as a float. If the tag is not set or cannot be converted, <code>null</code> is returned. */
658 @Nullable
659 public Float getFloatObject(int tagType)
660 {
661 Object o = getObject(tagType);
662 if (o == null)
663 return null;
664 if (o instanceof String) {
665 try {
666 return Float.parseFloat((String)o);
667 } catch (NumberFormatException nfe) {
668 return null;
669 }
670 }
671 if (o instanceof Number)
672 return ((Number)o).floatValue();
673 return null;
674 }
675
676 /** Returns the specified tag's value as a long, if possible. */
677 public long getLong(int tagType) throws MetadataException
678 {
679 Long value = getLongObject(tagType);
680 if (value!=null)
681 return value;
682 Object o = getObject(tagType);
683 if (o == null)
684 throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
685 throw new MetadataException("Tag '" + tagType + "' cannot be converted to a long. It is of type '" + o.getClass() + "'.");
686 }
687
688 /** Returns the specified tag's value as a long. If the tag is not set or cannot be converted, <code>null</code> is returned. */
689 @Nullable
690 public Long getLongObject(int tagType)
691 {
692 Object o = getObject(tagType);
693 if (o == null)
694 return null;
695 if (o instanceof String) {
696 try {
697 return Long.parseLong((String)o);
698 } catch (NumberFormatException nfe) {
699 return null;
700 }
701 }
702 if (o instanceof Number)
703 return ((Number)o).longValue();
704 return null;
705 }
706
707 /** Returns the specified tag's value as a boolean, if possible. */
708 public boolean getBoolean(int tagType) throws MetadataException
709 {
710 Boolean value = getBooleanObject(tagType);
711 if (value!=null)
712 return value;
713 Object o = getObject(tagType);
714 if (o == null)
715 throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
716 throw new MetadataException("Tag '" + tagType + "' cannot be converted to a boolean. It is of type '" + o.getClass() + "'.");
717 }
718
719 /** Returns the specified tag's value as a boolean. If the tag is not set or cannot be converted, <code>null</code> is returned. */
720 @Nullable
721 public Boolean getBooleanObject(int tagType)
722 {
723 Object o = getObject(tagType);
724 if (o == null)
725 return null;
726 if (o instanceof Boolean)
727 return (Boolean)o;
728 if (o instanceof String) {
729 try {
730 return Boolean.getBoolean((String)o);
731 } catch (NumberFormatException nfe) {
732 return null;
733 }
734 }
735 if (o instanceof Number)
736 return (((Number)o).doubleValue() != 0);
737 return null;
738 }
739
740 /**
741 * Returns the specified tag's value as a java.util.Date. If the value is unset or cannot be converted, <code>null</code> is returned.
742 * <p>
743 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
744 * the GMT {@link TimeZone}. If the {@link TimeZone} is known, call the overload that accepts one as an argument.
745 */
746 @Nullable
747 public java.util.Date getDate(int tagType)
748 {
749 return getDate(tagType, null, null);
750 }
751
752 /**
753 * Returns the specified tag's value as a java.util.Date. If the value is unset or cannot be converted, <code>null</code> is returned.
754 * <p>
755 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
756 * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null). Note that this parameter
757 * is only considered if the underlying value is a string and it has no time zone information, otherwise it has no effect.
758 */
759 @Nullable
760 public java.util.Date getDate(int tagType, @Nullable TimeZone timeZone)
761 {
762 return getDate(tagType, null, timeZone);
763 }
764
765 /**
766 * Returns the specified tag's value as a java.util.Date. If the value is unset or cannot be converted, <code>null</code> is returned.
767 * <p>
768 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
769 * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null). Note that this parameter
770 * is only considered if the underlying value is a string and it has no time zone information, otherwise it has no effect.
771 * In addition, the {@code subsecond} parameter, which specifies the number of digits after the decimal point in the seconds,
772 * is set to the returned Date. This parameter is only considered if the underlying value is a string and is has
773 * no subsecond information, otherwise it has no effect.
774 *
775 * @param tagType the tag identifier
776 * @param subsecond the subsecond value for the Date
777 * @param timeZone the time zone to use
778 * @return a Date representing the time value
779 */
780 @Nullable
781 public java.util.Date getDate(int tagType, @Nullable String subsecond, @Nullable TimeZone timeZone)
782 {
783 Object o = getObject(tagType);
784
785 if (o instanceof java.util.Date)
786 return (java.util.Date)o;
787
788 java.util.Date date = null;
789
790 if (o instanceof String) {
791 // This seems to cover all known Exif and Xmp date strings
792 // Note that " : : : : " is a valid date string according to the Exif spec (which means 'unknown date'): http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html
793 String datePatterns[] = {
794 "yyyy:MM:dd HH:mm:ss",
795 "yyyy:MM:dd HH:mm",
796 "yyyy-MM-dd HH:mm:ss",
797 "yyyy-MM-dd HH:mm",
798 "yyyy.MM.dd HH:mm:ss",
799 "yyyy.MM.dd HH:mm",
800 "yyyy-MM-dd'T'HH:mm:ss",
801 "yyyy-MM-dd'T'HH:mm",
802 "yyyy-MM-dd",
803 "yyyy-MM",
804 "yyyy" };
805 String dateString = (String)o;
806
807 // if the date string has subsecond information, it supersedes the subsecond parameter
808 Pattern subsecondPattern = Pattern.compile("(\\d\\d:\\d\\d:\\d\\d)(\\.\\d+)");
809 Matcher subsecondMatcher = subsecondPattern.matcher(dateString);
810 if (subsecondMatcher.find()) {
811 subsecond = subsecondMatcher.group(2).substring(1);
812 dateString = subsecondMatcher.replaceAll("$1");
813 }
814
815 // if the date string has time zone information, it supersedes the timeZone parameter
816 Pattern timeZonePattern = Pattern.compile("(Z|[+-]\\d\\d:\\d\\d)$");
817 Matcher timeZoneMatcher = timeZonePattern.matcher(dateString);
818 if (timeZoneMatcher.find()) {
819 timeZone = TimeZone.getTimeZone("GMT" + timeZoneMatcher.group().replaceAll("Z", ""));
820 dateString = timeZoneMatcher.replaceAll("");
821 }
822
823 for (String datePattern : datePatterns) {
824 try {
825 DateFormat parser = new SimpleDateFormat(datePattern);
826 if (timeZone != null)
827 parser.setTimeZone(timeZone);
828 else
829 parser.setTimeZone(TimeZone.getTimeZone("GMT")); // don't interpret zone time
830
831 date = parser.parse(dateString);
832 break;
833 } catch (ParseException ex) {
834 // simply try the next pattern
835 }
836 }
837 }
838
839 if (date == null)
840 return null;
841
842 if (subsecond == null)
843 return date;
844
845 try {
846 int millisecond = (int) (Double.parseDouble("." + subsecond) * 1000);
847 if (millisecond >= 0 && millisecond < 1000) {
848 Calendar calendar = Calendar.getInstance();
849 calendar.setTime(date);
850 calendar.set(Calendar.MILLISECOND, millisecond);
851 return calendar.getTime();
852 }
853 return date;
854 } catch (NumberFormatException e) {
855 return date;
856 }
857 }
858
859 /** Returns the specified tag's value as a Rational. If the value is unset or cannot be converted, <code>null</code> is returned. */
860 @Nullable
861 public Rational getRational(int tagType)
862 {
863 Object o = getObject(tagType);
864
865 if (o == null)
866 return null;
867
868 if (o instanceof Rational)
869 return (Rational)o;
870 if (o instanceof Integer)
871 return new Rational((Integer)o, 1);
872 if (o instanceof Long)
873 return new Rational((Long)o, 1);
874
875 // NOTE not doing conversions for real number types
876
877 return null;
878 }
879
880 /** Returns the specified tag's value as an array of Rational. If the value is unset or cannot be converted, <code>null</code> is returned. */
881 @Nullable
882 public Rational[] getRationalArray(int tagType)
883 {
884 Object o = getObject(tagType);
885 if (o == null)
886 return null;
887
888 if (o instanceof Rational[])
889 return (Rational[])o;
890
891 return null;
892 }
893
894 /**
895 * Returns the specified tag's value as a String. This value is the 'raw' value. A more presentable decoding
896 * of this value may be obtained from the corresponding Descriptor.
897 *
898 * @return the String representation of the tag's value, or
899 * <code>null</code> if the tag hasn't been defined.
900 */
901 @Nullable
902 public String getString(int tagType)
903 {
904 Object o = getObject(tagType);
905 if (o == null)
906 return null;
907
908 if (o instanceof Rational)
909 return ((Rational)o).toSimpleString(true);
910
911 if (o.getClass().isArray()) {
912 // handle arrays of objects and primitives
913 int arrayLength = Array.getLength(o);
914 final Class<?> componentType = o.getClass().getComponentType();
915
916 StringBuilder string = new StringBuilder();
917
918 if (Object.class.isAssignableFrom(componentType)) {
919 // object array
920 for (int i = 0; i < arrayLength; i++) {
921 if (i != 0)
922 string.append(' ');
923 string.append(Array.get(o, i).toString());
924 }
925 } else if (componentType.getName().equals("int")) {
926 for (int i = 0; i < arrayLength; i++) {
927 if (i != 0)
928 string.append(' ');
929 string.append(Array.getInt(o, i));
930 }
931 } else if (componentType.getName().equals("short")) {
932 for (int i = 0; i < arrayLength; i++) {
933 if (i != 0)
934 string.append(' ');
935 string.append(Array.getShort(o, i));
936 }
937 } else if (componentType.getName().equals("long")) {
938 for (int i = 0; i < arrayLength; i++) {
939 if (i != 0)
940 string.append(' ');
941 string.append(Array.getLong(o, i));
942 }
943 } else if (componentType.getName().equals("float")) {
944 for (int i = 0; i < arrayLength; i++) {
945 if (i != 0)
946 string.append(' ');
947 string.append(_floatFormat.format(Array.getFloat(o, i)));
948 }
949 } else if (componentType.getName().equals("double")) {
950 for (int i = 0; i < arrayLength; i++) {
951 if (i != 0)
952 string.append(' ');
953 string.append(_floatFormat.format(Array.getDouble(o, i)));
954 }
955 } else if (componentType.getName().equals("byte")) {
956 for (int i = 0; i < arrayLength; i++) {
957 if (i != 0)
958 string.append(' ');
959 string.append(Array.getByte(o, i) & 0xff);
960 }
961 } else {
962 addError("Unexpected array component type: " + componentType.getName());
963 }
964
965 return string.toString();
966 }
967
968 if (o instanceof Double)
969 return _floatFormat.format(((Double)o).doubleValue());
970
971 if (o instanceof Float)
972 return _floatFormat.format(((Float)o).floatValue());
973
974 // Note that several cameras leave trailing spaces (Olympus, Nikon) but this library is intended to show
975 // the actual data within the file. It is not inconceivable that whitespace may be significant here, so we
976 // do not trim. Also, if support is added for writing data back to files, this may cause issues.
977 // We leave trimming to the presentation layer.
978 return o.toString();
979 }
980
981 @Nullable
982 public String getString(int tagType, String charset)
983 {
984 byte[] bytes = getByteArray(tagType);
985 if (bytes==null)
986 return null;
987 try {
988 return new String(bytes, charset);
989 } catch (UnsupportedEncodingException e) {
990 return null;
991 }
992 }
993
994 /**
995 * Returns the object hashed for the particular tag type specified, if available.
996 *
997 * @param tagType the tag type identifier
998 * @return the tag's value as an Object if available, else <code>null</code>
999 */
1000 @java.lang.SuppressWarnings({ "UnnecessaryBoxing" })
1001 @Nullable
1002 public Object getObject(int tagType)
1003 {
1004 return _tagMap.get(Integer.valueOf(tagType));
1005 }
1006
1007// OTHER METHODS
1008
1009 /**
1010 * Returns the name of a specified tag as a String.
1011 *
1012 * @param tagType the tag type identifier
1013 * @return the tag's name as a String
1014 */
1015 @NotNull
1016 public String getTagName(int tagType)
1017 {
1018 HashMap<Integer, String> nameMap = getTagNameMap();
1019 if (!nameMap.containsKey(tagType)) {
1020 String hex = Integer.toHexString(tagType);
1021 while (hex.length() < 4) {
1022 hex = "0" + hex;
1023 }
1024 return "Unknown tag (0x" + hex + ")";
1025 }
1026 return nameMap.get(tagType);
1027 }
1028
1029 /**
1030 * Gets whether the specified tag is known by the directory and has a name.
1031 *
1032 * @param tagType the tag type identifier
1033 * @return whether this directory has a name for the specified tag
1034 */
1035 public boolean hasTagName(int tagType)
1036 {
1037 return getTagNameMap().containsKey(tagType);
1038 }
1039
1040 /**
1041 * Provides a description of a tag's value using the descriptor set by
1042 * <code>setDescriptor(Descriptor)</code>.
1043 *
1044 * @param tagType the tag type identifier
1045 * @return the tag value's description as a String
1046 */
1047 @Nullable
1048 public String getDescription(int tagType)
1049 {
1050 assert(_descriptor != null);
1051 return _descriptor.getDescription(tagType);
1052 }
1053
1054 @Override
1055 public String toString()
1056 {
1057 return String.format("%s Directory (%d %s)",
1058 getName(),
1059 _tagMap.size(),
1060 _tagMap.size() == 1
1061 ? "tag"
1062 : "tags");
1063 }
1064}
Note: See TracBrowser for help on using the repository browser.