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

Last change on this file since 13500 was 13061, checked in by Don-vip, 6 years ago

fix #15505 - update to metadata-extractor 2.10.1

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