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

Last change on this file since 15217 was 15217, checked in by Don-vip, 5 years ago

see #17848 - update to metadata-extractor 2.12.0

File size: 39.8 KB
Line 
1/*
2 * Copyright 2002-2019 Drew Noakes and contributors
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 Number)
760 return ((Number)o).longValue();
761 if (o instanceof String || o instanceof StringValue) {
762 try {
763 return Long.parseLong(o.toString());
764 } catch (NumberFormatException nfe) {
765 return null;
766 }
767 } else if (o instanceof Rational[]) {
768 Rational[] rationals = (Rational[])o;
769 if (rationals.length == 1)
770 return rationals[0].longValue();
771 } else if (o instanceof byte[]) {
772 byte[] bytes = (byte[])o;
773 if (bytes.length == 1)
774 return (long)bytes[0];
775 } else if (o instanceof int[]) {
776 int[] ints = (int[])o;
777 if (ints.length == 1)
778 return (long)ints[0];
779 } else if (o instanceof short[]) {
780 short[] shorts = (short[])o;
781 if (shorts.length == 1)
782 return (long)shorts[0];
783 }
784 return null;
785 }
786
787 /** Returns the specified tag's value as a boolean, if possible. */
788 public boolean getBoolean(int tagType) throws MetadataException
789 {
790 Boolean value = getBooleanObject(tagType);
791 if (value != null)
792 return value;
793 Object o = getObject(tagType);
794 if (o == null)
795 throw new MetadataException("Tag '" + getTagName(tagType) + "' has not been set -- check using containsTag() first");
796 throw new MetadataException("Tag '" + tagType + "' cannot be converted to a boolean. It is of type '" + o.getClass() + "'.");
797 }
798
799 /** Returns the specified tag's value as a boolean. If the tag is not set or cannot be converted, <code>null</code> is returned. */
800 @Nullable
801 @SuppressWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "keep API interface consistent")
802 public Boolean getBooleanObject(int tagType)
803 {
804 Object o = getObject(tagType);
805 if (o == null)
806 return null;
807 if (o instanceof Boolean)
808 return (Boolean)o;
809 if (o instanceof String || o instanceof StringValue) {
810 try {
811 return Boolean.getBoolean(o.toString());
812 } catch (NumberFormatException nfe) {
813 return null;
814 }
815 }
816 if (o instanceof Number)
817 return (((Number)o).doubleValue() != 0);
818 return null;
819 }
820
821 /**
822 * 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.
823 * <p>
824 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
825 * the GMT {@link TimeZone}. If the {@link TimeZone} is known, call the overload that accepts one as an argument.
826 */
827 @Nullable
828 public java.util.Date getDate(int tagType)
829 {
830 return getDate(tagType, null, null);
831 }
832
833 /**
834 * 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.
835 * <p>
836 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
837 * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null). Note that this parameter
838 * is only considered if the underlying value is a string and it has no time zone information, otherwise it has no effect.
839 */
840 @Nullable
841 public java.util.Date getDate(int tagType, @Nullable TimeZone timeZone)
842 {
843 return getDate(tagType, null, timeZone);
844 }
845
846 /**
847 * 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.
848 * <p>
849 * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
850 * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null). Note that this parameter
851 * is only considered if the underlying value is a string and it has no time zone information, otherwise it has no effect.
852 * In addition, the {@code subsecond} parameter, which specifies the number of digits after the decimal point in the seconds,
853 * is set to the returned Date. This parameter is only considered if the underlying value is a string and is has
854 * no subsecond information, otherwise it has no effect.
855 *
856 * @param tagType the tag identifier
857 * @param subsecond the subsecond value for the Date
858 * @param timeZone the time zone to use
859 * @return a Date representing the time value
860 */
861 @Nullable
862 public java.util.Date getDate(int tagType, @Nullable String subsecond, @Nullable TimeZone timeZone)
863 {
864 Object o = getObject(tagType);
865
866 if (o instanceof java.util.Date)
867 return (java.util.Date)o;
868
869 java.util.Date date = null;
870
871 if ((o instanceof String) || (o instanceof StringValue)) {
872 // This seems to cover all known Exif and Xmp date strings
873 // 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
874 String datePatterns[] = {
875 "yyyy:MM:dd HH:mm:ss",
876 "yyyy:MM:dd HH:mm",
877 "yyyy-MM-dd HH:mm:ss",
878 "yyyy-MM-dd HH:mm",
879 "yyyy.MM.dd HH:mm:ss",
880 "yyyy.MM.dd HH:mm",
881 "yyyy-MM-dd'T'HH:mm:ss",
882 "yyyy-MM-dd'T'HH:mm",
883 "yyyy-MM-dd",
884 "yyyy-MM",
885 "yyyyMMdd", // as used in IPTC data
886 "yyyy" };
887
888 String dateString = o.toString();
889
890 // if the date string has subsecond information, it supersedes the subsecond parameter
891 Pattern subsecondPattern = Pattern.compile("(\\d\\d:\\d\\d:\\d\\d)(\\.\\d+)");
892 Matcher subsecondMatcher = subsecondPattern.matcher(dateString);
893 if (subsecondMatcher.find()) {
894 subsecond = subsecondMatcher.group(2).substring(1);
895 dateString = subsecondMatcher.replaceAll("$1");
896 }
897
898 // if the date string has time zone information, it supersedes the timeZone parameter
899 Pattern timeZonePattern = Pattern.compile("(Z|[+-]\\d\\d:\\d\\d)$");
900 Matcher timeZoneMatcher = timeZonePattern.matcher(dateString);
901 if (timeZoneMatcher.find()) {
902 timeZone = TimeZone.getTimeZone("GMT" + timeZoneMatcher.group().replaceAll("Z", ""));
903 dateString = timeZoneMatcher.replaceAll("");
904 }
905
906 for (String datePattern : datePatterns) {
907 try {
908 DateFormat parser = new SimpleDateFormat(datePattern);
909 if (timeZone != null)
910 parser.setTimeZone(timeZone);
911 else
912 parser.setTimeZone(TimeZone.getTimeZone("GMT")); // don't interpret zone time
913
914 date = parser.parse(dateString);
915 break;
916 } catch (ParseException ex) {
917 // simply try the next pattern
918 }
919 }
920 }
921
922 if (date == null)
923 return null;
924
925 if (subsecond == null)
926 return date;
927
928 try {
929 int millisecond = (int) (Double.parseDouble("." + subsecond) * 1000);
930 if (millisecond >= 0 && millisecond < 1000) {
931 Calendar calendar = Calendar.getInstance();
932 calendar.setTime(date);
933 calendar.set(Calendar.MILLISECOND, millisecond);
934 return calendar.getTime();
935 }
936 return date;
937 } catch (NumberFormatException e) {
938 return date;
939 }
940 }
941
942 /** Returns the specified tag's value as a Rational. If the value is unset or cannot be converted, <code>null</code> is returned. */
943 @Nullable
944 public Rational getRational(int tagType)
945 {
946 Object o = getObject(tagType);
947
948 if (o == null)
949 return null;
950
951 if (o instanceof Rational)
952 return (Rational)o;
953 if (o instanceof Integer)
954 return new Rational((Integer)o, 1);
955 if (o instanceof Long)
956 return new Rational((Long)o, 1);
957
958 // NOTE not doing conversions for real number types
959
960 return null;
961 }
962
963 /** 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. */
964 @Nullable
965 public Rational[] getRationalArray(int tagType)
966 {
967 Object o = getObject(tagType);
968 if (o == null)
969 return null;
970
971 if (o instanceof Rational[])
972 return (Rational[])o;
973
974 return null;
975 }
976
977 /**
978 * Returns the specified tag's value as a String. This value is the 'raw' value. A more presentable decoding
979 * of this value may be obtained from the corresponding Descriptor.
980 *
981 * @return the String representation of the tag's value, or
982 * <code>null</code> if the tag hasn't been defined.
983 */
984 @Nullable
985 public String getString(int tagType)
986 {
987 Object o = getObject(tagType);
988 if (o == null)
989 return null;
990
991 if (o instanceof Rational)
992 return ((Rational)o).toSimpleString(true);
993
994 if (o.getClass().isArray()) {
995 // handle arrays of objects and primitives
996 int arrayLength = Array.getLength(o);
997 final Class<?> componentType = o.getClass().getComponentType();
998
999 StringBuilder string = new StringBuilder();
1000
1001 if (Object.class.isAssignableFrom(componentType)) {
1002 // object array
1003 for (int i = 0; i < arrayLength; i++) {
1004 if (i != 0)
1005 string.append(' ');
1006 string.append(Array.get(o, i).toString());
1007 }
1008 } else if (componentType.getName().equals("int")) {
1009 for (int i = 0; i < arrayLength; i++) {
1010 if (i != 0)
1011 string.append(' ');
1012 string.append(Array.getInt(o, i));
1013 }
1014 } else if (componentType.getName().equals("short")) {
1015 for (int i = 0; i < arrayLength; i++) {
1016 if (i != 0)
1017 string.append(' ');
1018 string.append(Array.getShort(o, i));
1019 }
1020 } else if (componentType.getName().equals("long")) {
1021 for (int i = 0; i < arrayLength; i++) {
1022 if (i != 0)
1023 string.append(' ');
1024 string.append(Array.getLong(o, i));
1025 }
1026 } else if (componentType.getName().equals("float")) {
1027 DecimalFormat format = new DecimalFormat(_floatFormatPattern);
1028 for (int i = 0; i < arrayLength; i++) {
1029 if (i != 0)
1030 string.append(' ');
1031 String s = format.format(Array.getFloat(o, i));
1032 string.append(s.equals("-0") ? "0" : s);
1033 }
1034 } else if (componentType.getName().equals("double")) {
1035 DecimalFormat format = new DecimalFormat(_floatFormatPattern);
1036 for (int i = 0; i < arrayLength; i++) {
1037 if (i != 0)
1038 string.append(' ');
1039 String s = format.format(Array.getDouble(o, i));
1040 string.append(s.equals("-0") ? "0" : s);
1041 }
1042 } else if (componentType.getName().equals("byte")) {
1043 for (int i = 0; i < arrayLength; i++) {
1044 if (i != 0)
1045 string.append(' ');
1046 string.append(Array.getByte(o, i) & 0xff);
1047 }
1048 } else {
1049 addError("Unexpected array component type: " + componentType.getName());
1050 }
1051
1052 return string.toString();
1053 }
1054
1055 if (o instanceof Double)
1056 return new DecimalFormat(_floatFormatPattern).format(((Double)o).doubleValue());
1057
1058 if (o instanceof Float)
1059 return new DecimalFormat(_floatFormatPattern).format(((Float)o).floatValue());
1060
1061 // Note that several cameras leave trailing spaces (Olympus, Nikon) but this library is intended to show
1062 // the actual data within the file. It is not inconceivable that whitespace may be significant here, so we
1063 // do not trim. Also, if support is added for writing data back to files, this may cause issues.
1064 // We leave trimming to the presentation layer.
1065 return o.toString();
1066 }
1067
1068 @Nullable
1069 public String getString(int tagType, String charset)
1070 {
1071 byte[] bytes = getByteArray(tagType);
1072 if (bytes==null)
1073 return null;
1074 try {
1075 return new String(bytes, charset);
1076 } catch (UnsupportedEncodingException e) {
1077 return null;
1078 }
1079 }
1080
1081 @Nullable
1082 public StringValue getStringValue(int tagType)
1083 {
1084 Object o = getObject(tagType);
1085 if (o instanceof StringValue)
1086 return (StringValue)o;
1087 return null;
1088 }
1089
1090 /**
1091 * Returns the object hashed for the particular tag type specified, if available.
1092 *
1093 * @param tagType the tag type identifier
1094 * @return the tag's value as an Object if available, else <code>null</code>
1095 */
1096 @java.lang.SuppressWarnings({ "UnnecessaryBoxing" })
1097 @Nullable
1098 public Object getObject(int tagType)
1099 {
1100 return _tagMap.get(Integer.valueOf(tagType));
1101 }
1102
1103// OTHER METHODS
1104
1105 /**
1106 * Returns the name of a specified tag as a String.
1107 *
1108 * @param tagType the tag type identifier
1109 * @return the tag's name as a String
1110 */
1111 @NotNull
1112 public String getTagName(int tagType)
1113 {
1114 HashMap<Integer, String> nameMap = getTagNameMap();
1115 if (!nameMap.containsKey(tagType)) {
1116 String hex = Integer.toHexString(tagType);
1117 while (hex.length() < 4) {
1118 hex = "0" + hex;
1119 }
1120 return "Unknown tag (0x" + hex + ")";
1121 }
1122 return nameMap.get(tagType);
1123 }
1124
1125 /**
1126 * Gets whether the specified tag is known by the directory and has a name.
1127 *
1128 * @param tagType the tag type identifier
1129 * @return whether this directory has a name for the specified tag
1130 */
1131 public boolean hasTagName(int tagType)
1132 {
1133 return getTagNameMap().containsKey(tagType);
1134 }
1135
1136 /**
1137 * Provides a description of a tag's value using the descriptor set by
1138 * <code>setDescriptor(Descriptor)</code>.
1139 *
1140 * @param tagType the tag type identifier
1141 * @return the tag value's description as a String
1142 */
1143 @Nullable
1144 public String getDescription(int tagType)
1145 {
1146 assert(_descriptor != null);
1147 return _descriptor.getDescription(tagType);
1148 }
1149
1150 @Override
1151 public String toString()
1152 {
1153 return String.format("%s Directory (%d %s)",
1154 getName(),
1155 _tagMap.size(),
1156 _tagMap.size() == 1
1157 ? "tag"
1158 : "tags");
1159 }
1160}
Note: See TracBrowser for help on using the repository browser.