Source code: com/drew/metadata/Directory.java
1 /*
2 * Created by dnoakes on 25-Nov-2002 20:30:39 using IntelliJ IDEA.
3 */
4 package com.drew.metadata;
5
6 import com.drew.lang.Rational;
7
8 import java.lang.reflect.Array;
9 import java.text.DateFormat;
10 import java.util.*;
11 import java.io.Serializable;
12
13 /**
14 * Base class for all Metadata directory types with supporting methods for setting and
15 * getting tag values.
16 */
17 public abstract class Directory implements Serializable
18 {
19 /**
20 * Map of values hashed by type identifiers.
21 */
22 protected final HashMap _tagMap;
23
24 /**
25 * The descriptor used to interperet tag values.
26 */
27 protected TagDescriptor _descriptor;
28
29 /**
30 * A convenient list holding tag values in the order in which they were stored.
31 * This is used for creation of an iterator, and for counting the number of
32 * defined tags.
33 */
34 protected final List _definedTagList;
35
36 private List _errorList;
37
38 // ABSTRACT METHODS
39
40 /**
41 * Provides the name of the directory, for display purposes. E.g. <code>Exif</code>
42 * @return the name of the directory
43 */
44 public abstract String getName();
45
46 /**
47 * Provides the map of tag names, hashed by tag type identifier.
48 * @return the map of tag names
49 */
50 protected abstract HashMap getTagNameMap();
51
52 // CONSTRUCTORS
53
54 /**
55 * Creates a new Directory.
56 */
57 public Directory()
58 {
59 _tagMap = new HashMap();
60 _definedTagList = new ArrayList();
61 }
62
63 // VARIOUS METHODS
64
65 /**
66 * Indicates whether the specified tag type has been set.
67 * @param tagType the tag type to check for
68 * @return true if a value exists for the specified tag type, false if not
69 */
70 public boolean containsTag(int tagType)
71 {
72 return _tagMap.containsKey(new Integer(tagType));
73 }
74
75 /**
76 * Returns an Iterator of Tag instances that have been set in this Directory.
77 * @return an Iterator of Tag instances
78 */
79 public Iterator getTagIterator()
80 {
81 return _definedTagList.iterator();
82 }
83
84 /**
85 * Returns the number of tags set in this Directory.
86 * @return the number of tags set in this Directory
87 */
88 public int getTagCount()
89 {
90 return _definedTagList.size();
91 }
92
93 /**
94 * Sets the descriptor used to interperet tag values.
95 * @param descriptor the descriptor used to interperet tag values
96 */
97 public void setDescriptor(TagDescriptor descriptor)
98 {
99 if (descriptor == null) {
100 throw new NullPointerException("cannot set a null descriptor");
101 }
102 _descriptor = descriptor;
103 }
104
105 public void addError(String message)
106 {
107 if (_errorList==null) {
108 _errorList = new ArrayList();
109 }
110 _errorList.add(message);
111 }
112
113 public boolean hasErrors()
114 {
115 return (_errorList!=null && _errorList.size() > 0);
116 }
117
118 public Iterator getErrors()
119 {
120 return _errorList.iterator();
121 }
122
123 public int getErrorCount()
124 {
125 return _errorList.size();
126 }
127
128 // TAG SETTERS
129
130 /**
131 * Sets an int value for the specified tag.
132 * @param tagType the tag's value as an int
133 * @param value the value for the specified tag as an int
134 */
135 public void setInt(int tagType, int value)
136 {
137 addObject(tagType, new Integer(value));
138 }
139
140 /**
141 * Sets a double value for the specified tag.
142 * @param tagType the tag's value as an int
143 * @param value the value for the specified tag as a double
144 */
145 public void setDouble(int tagType, double value)
146 {
147 addObject(tagType, new Double(value));
148 }
149
150 /**
151 * Sets a float value for the specified tag.
152 * @param tagType the tag's value as an int
153 * @param value the value for the specified tag as a float
154 */
155 public void setFloat(int tagType, float value)
156 {
157 addObject(tagType, new Float(value));
158 }
159
160 /**
161 * Sets an int value for the specified tag.
162 * @param tagType the tag's value as an int
163 * @param value the value for the specified tag as a String
164 */
165 public void setString(int tagType, String value)
166 {
167 addObject(tagType, value);
168 }
169
170 /**
171 * Sets an int value for the specified tag.
172 * @param tagType the tag's value as an int
173 * @param value the value for the specified tag as a boolean
174 */
175 public void setBoolean(int tagType, boolean value)
176 {
177 addObject(tagType, new Boolean(value));
178 }
179
180 /**
181 * Sets a long value for the specified tag.
182 * @param tagType the tag's value as an int
183 * @param value the value for the specified tag as a long
184 */
185 public void setLong(int tagType, long value)
186 {
187 addObject(tagType, new Long(value));
188 }
189
190 /**
191 * Sets a java.util.Date value for the specified tag.
192 * @param tagType the tag's value as an int
193 * @param value the value for the specified tag as a java.util.Date
194 */
195 public void setDate(int tagType, java.util.Date value)
196 {
197 addObject(tagType, value);
198 }
199
200 /**
201 * Sets a Rational value for the specified tag.
202 * @param tagType the tag's value as an int
203 * @param rational rational number
204 */
205 public void setRational(int tagType, Rational rational)
206 {
207 addObject(tagType, rational);
208 }
209
210 /**
211 * Sets a Rational array for the specified tag.
212 * @param tagType the tag identifier
213 * @param rationals the Rational array to store
214 */
215 public void setRationalArray(int tagType, Rational[] rationals)
216 {
217 addObjectArray(tagType, rationals);
218 }
219
220 /**
221 * Sets an int array for the specified tag.
222 * @param tagType the tag identifier
223 * @param ints the int array to store
224 */
225 public void setIntArray(int tagType, int[] ints)
226 {
227 addObjectArray(tagType, ints);
228 }
229
230 /**
231 * Sets a byte array for the specified tag.
232 * @param tagType the tag identifier
233 * @param bytes the byte array to store
234 */
235 public void setByteArray(int tagType, byte[] bytes)
236 {
237 addObjectArray(tagType, bytes);
238 }
239
240 /**
241 * Sets a String array for the specified tag.
242 * @param tagType the tag identifier
243 * @param strings the String array to store
244 */
245 public void setStringArray(int tagType, String[] strings)
246 {
247 addObjectArray(tagType, strings);
248 }
249
250 /**
251 * Private helper method, containing common functionality for all 'add'
252 * methods.
253 * @param tagType the tag's value as an int
254 * @param value the value for the specified tag
255 * @throws NullPointerException if value is <code>null</code>
256 */
257 private void addObject(int tagType, Object value)
258 {
259 if (value == null) {
260 throw new NullPointerException("cannot set a null object");
261 }
262 Integer key = new Integer(tagType);
263 if (!_tagMap.containsKey(key)) {
264 _definedTagList.add(new Tag(tagType, this));
265 }
266 _tagMap.put(key, value);
267 }
268
269 /**
270 * Private helper method, containing common functionality for all 'add...Array'
271 * methods.
272 * @param tagType the tag's value as an int
273 * @param array the array of values for the specified tag
274 */
275 private void addObjectArray(int tagType, Object array)
276 {
277 // for now, we don't do anything special -- this method might be a candidate for removal once the dust settles
278 addObject(tagType, array);
279 }
280
281 // TAG GETTERS
282
283 /**
284 * Returns the specified tag's value as an int, if possible.
285 */
286 public int getInt(int tagType) throws MetadataException
287 {
288 Object o = getObject(tagType);
289 if (o == null) {
290 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
291 } else if (o instanceof String) {
292 try {
293 return Integer.parseInt((String)o);
294 } catch (NumberFormatException nfe) {
295 // convert the char array to an int
296 String s = (String)o;
297 int val = 0;
298 for (int i = s.length() - 1; i >= 0; i--) {
299 val += s.charAt(i) << (i * 8);
300 }
301 return val;
302 }
303 } else if (o instanceof Number) {
304 return ((Number)o).intValue();
305 }
306 throw new MetadataException("Requested tag cannot be cast to int");
307 }
308
309 /**
310 * Gets the specified tag's value as a String array, if possible. Only supported
311 * where the tag is set as String[], String, int[], byte[] or Rational[].
312 * @param tagType the tag identifier
313 * @return the tag's value as an array of Strings
314 * @throws MetadataException if the tag has not been set or cannot be represented
315 * as a String[]
316 */
317 public String[] getStringArray(int tagType) throws MetadataException
318 {
319 Object o = getObject(tagType);
320 if (o == null) {
321 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
322 } else if (o instanceof String[]) {
323 return (String[])o;
324 } else if (o instanceof String) {
325 String[] strings = {(String)o};
326 return strings;
327 } else if (o instanceof int[]) {
328 int[] ints = (int[])o;
329 String[] strings = new String[ints.length];
330 for (int i = 0; i < strings.length; i++) {
331 strings[i] = Integer.toString(ints[i]);
332 }
333 return strings;
334 } else if (o instanceof byte[]) {
335 byte[] bytes = (byte[])o;
336 String[] strings = new String[bytes.length];
337 for (int i = 0; i < strings.length; i++) {
338 strings[i] = Byte.toString(bytes[i]);
339 }
340 return strings;
341 } else if (o instanceof Rational[]) {
342 Rational[] rationals = (Rational[])o;
343 String[] strings = new String[rationals.length];
344 for (int i = 0; i < strings.length; i++) {
345 strings[i] = rationals[i].toSimpleString(false);
346 }
347 return strings;
348 }
349 throw new MetadataException("Requested tag cannot be cast to String array (" + o.getClass().toString() + ")");
350 }
351
352 /**
353 * Gets the specified tag's value as an int array, if possible. Only supported
354 * where the tag is set as String, int[], byte[] or Rational[].
355 * @param tagType the tag identifier
356 * @return the tag's value as an int array
357 * @throws MetadataException if the tag has not been set, or cannot be converted to
358 * an int array
359 */
360 public int[] getIntArray(int tagType) throws MetadataException
361 {
362 Object o = getObject(tagType);
363 if (o == null) {
364 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
365 } else if (o instanceof Rational[]) {
366 Rational[] rationals = (Rational[])o;
367 int[] ints = new int[rationals.length];
368 for (int i = 0; i < ints.length; i++) {
369 ints[i] = rationals[i].intValue();
370 }
371 return ints;
372 } else if (o instanceof int[]) {
373 return (int[])o;
374 } else if (o instanceof byte[]) {
375 byte[] bytes = (byte[])o;
376 int[] ints = new int[bytes.length];
377 for (int i = 0; i < bytes.length; i++) {
378 byte b = bytes[i];
379 ints[i] = b;
380 }
381 return ints;
382 } else if (o instanceof String) {
383 String str = (String)o;
384 int[] ints = new int[str.length()];
385 for (int i = 0; i < str.length(); i++) {
386 ints[i] = str.charAt(i);
387 }
388 return ints;
389 }
390 throw new MetadataException("Requested tag cannot be cast to int array (" + o.getClass().toString() + ")");
391 }
392
393 /**
394 * Gets the specified tag's value as an byte array, if possible. Only supported
395 * where the tag is set as String, int[], byte[] or Rational[].
396 * @param tagType the tag identifier
397 * @return the tag's value as a byte array
398 * @throws MetadataException if the tag has not been set, or cannot be converted to
399 * a byte array
400 */
401 public byte[] getByteArray(int tagType) throws MetadataException
402 {
403 Object o = getObject(tagType);
404 if (o == null) {
405 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
406 } else if (o instanceof Rational[]) {
407 Rational[] rationals = (Rational[])o;
408 byte[] bytes = new byte[rationals.length];
409 for (int i = 0; i < bytes.length; i++) {
410 bytes[i] = rationals[i].byteValue();
411 }
412 return bytes;
413 } else if (o instanceof byte[]) {
414 return (byte[])o;
415 } else if (o instanceof int[]) {
416 int[] ints = (int[])o;
417 byte[] bytes = new byte[ints.length];
418 for (int i = 0; i < ints.length; i++) {
419 bytes[i] = (byte)ints[i];
420 }
421 return bytes;
422 } else if (o instanceof String) {
423 String str = (String)o;
424 byte[] bytes = new byte[str.length()];
425 for (int i = 0; i < str.length(); i++) {
426 bytes[i] = (byte)str.charAt(i);
427 }
428 return bytes;
429 }
430 throw new MetadataException("Requested tag cannot be cast to byte array (" + o.getClass().toString() + ")");
431 }
432
433 /**
434 * Returns the specified tag's value as a double, if possible.
435 */
436 public double getDouble(int tagType) throws MetadataException
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 } else if (o instanceof String) {
442 try {
443 return Double.parseDouble((String)o);
444 } catch (NumberFormatException nfe) {
445 throw new MetadataException("unable to parse string " + o + " as a double", nfe);
446 }
447 } else if (o instanceof Number) {
448 return ((Number)o).doubleValue();
449 }
450 throw new MetadataException("Requested tag cannot be cast to double");
451 }
452
453 /**
454 * Returns the specified tag's value as a float, if possible.
455 */
456 public float getFloat(int tagType) throws MetadataException
457 {
458 Object o = getObject(tagType);
459 if (o == null) {
460 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
461 } else if (o instanceof String) {
462 try {
463 return Float.parseFloat((String)o);
464 } catch (NumberFormatException nfe) {
465 throw new MetadataException("unable to parse string " + o + " as a float", nfe);
466 }
467 } else if (o instanceof Number) {
468 return ((Number)o).floatValue();
469 }
470 throw new MetadataException("Requested tag cannot be cast to float");
471 }
472
473 /**
474 * Returns the specified tag's value as a long, if possible.
475 */
476 public long getLong(int tagType) throws MetadataException
477 {
478 Object o = getObject(tagType);
479 if (o == null) {
480 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
481 } else if (o instanceof String) {
482 try {
483 return Long.parseLong((String)o);
484 } catch (NumberFormatException nfe) {
485 throw new MetadataException("unable to parse string " + o + " as a long", nfe);
486 }
487 } else if (o instanceof Number) {
488 return ((Number)o).longValue();
489 }
490 throw new MetadataException("Requested tag cannot be cast to long");
491 }
492
493 /**
494 * Returns the specified tag's value as a boolean, if possible.
495 */
496 public boolean getBoolean(int tagType) throws MetadataException
497 {
498 Object o = getObject(tagType);
499 if (o == null) {
500 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
501 } else if (o instanceof Boolean) {
502 return ((Boolean)o).booleanValue();
503 } else if (o instanceof String) {
504 try {
505 return Boolean.getBoolean((String)o);
506 } catch (NumberFormatException nfe) {
507 throw new MetadataException("unable to parse string " + o + " as a boolean", nfe);
508 }
509 } else if (o instanceof Number) {
510 return (((Number)o).doubleValue() != 0);
511 }
512 throw new MetadataException("Requested tag cannot be cast to boolean");
513 }
514
515 /**
516 * Returns the specified tag's value as a java.util.Date, if possible.
517 */
518 public java.util.Date getDate(int tagType) throws MetadataException
519 {
520 Object o = getObject(tagType);
521 if (o == null) {
522 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
523 } else if (o instanceof java.util.Date) {
524 return (java.util.Date)o;
525 } else if (o instanceof String) {
526 String datePatterns[] = {
527 "yyyy:MM:dd hh:mm:ss",
528 "yyyy:MM:dd hh:mm",
529 "yyyy-MM-dd hh:mm:ss",
530 "yyyy-MM-dd hh:mm"}; // Try these patterns. Add new to make this method even smarter
531 String dateString = (String)o;
532 for (int i = 0; i < datePatterns.length; i++) {
533 try {
534 DateFormat parser = new java.text.SimpleDateFormat(datePatterns[i]);
535 return parser.parse(dateString);
536 } catch (java.text.ParseException ex) {
537 // simply try the next pattern
538 }
539 }
540 }
541 throw new MetadataException("Requested tag cannot be cast to java.util.Date");
542 }
543
544 /**
545 * Returns the specified tag's value as a Rational, if possible.
546 */
547 public Rational getRational(int tagType) throws MetadataException
548 {
549 Object o = getObject(tagType);
550 if (o == null) {
551 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
552 } else if (o instanceof Rational) {
553 return (Rational)o;
554 }
555 throw new MetadataException("Requested tag cannot be cast to Rational");
556 }
557
558 public Rational[] getRationalArray(int tagType) throws MetadataException
559 {
560 Object o = getObject(tagType);
561 if (o == null) {
562 throw new MetadataException("Tag " + getTagName(tagType) + " has not been set -- check using containsTag() first");
563 } else if (o instanceof Rational[]) {
564 return (Rational[])o;
565 }
566 throw new MetadataException("Requested tag cannot be cast to Rational array (" + o.getClass().toString() + ")");
567 }
568
569 /**
570 * Returns the specified tag's value as a String. In many cases, more
571 * presentable values will be obtained from getDescription(int).
572 * @return the String reprensentation of the tag's value, or
573 * <code>null</code> if the tag hasn't been defined.
574 */
575 public String getString(int tagType)
576 {
577 // TODO handle types other than Rational[], Object[] and int[] here...
578 Object o = getObject(tagType);
579 if (o == null) {
580 return null;
581 } else if (o instanceof Rational) {
582 return ((Rational)o).toSimpleString(true);
583 } else if (o.getClass().isArray()) {
584 int arrayLength = Array.getLength(o);
585 // determine if this is an array of objects i.e. [Lcom.drew.blah
586 boolean isObjectArray = o.getClass().toString().startsWith("class [L");
587 StringBuffer sbuffer = new StringBuffer();
588 for (int i = 0; i < arrayLength; i++) {
589 if (i != 0) {
590 sbuffer.append(' ');
591 }
592 if (isObjectArray) {
593 sbuffer.append(Array.get(o, i).toString());
594 } else {
595 sbuffer.append(Array.getInt(o, i));
596 }
597 }
598 return sbuffer.toString();
599 } else {
600 return o.toString();
601 }
602 }
603
604 /**
605 * Returns the object hashed for the particular tag type specified, if available.
606 * @param tagType the tag type identifier
607 * @return the tag's value as an Object if available, else null
608 */
609 public Object getObject(int tagType)
610 {
611 return _tagMap.get(new Integer(tagType));
612 }
613
614 // OTHER METHODS
615
616 /**
617 * Returns the name of a specified tag as a String.
618 * @param tagType the tag type identifier
619 * @return the tag's name as a String
620 */
621 public String getTagName(int tagType)
622 {
623 Integer key = new Integer(tagType);
624 HashMap nameMap = getTagNameMap();
625 if (!nameMap.containsKey(key)) {
626 String hex = Integer.toHexString(tagType);
627 while (hex.length() < 4) {
628 hex = "0" + hex;
629 }
630 return "Unknown tag (0x" + hex + ")";
631 }
632 return (String)nameMap.get(key);
633 }
634
635 /**
636 * Provides a description of a tag's value using the descriptor set by
637 * <code>setDescriptor(Descriptor)</code>.
638 * @param tagType the tag type identifier
639 * @return the tag value's description as a String
640 * @throws MetadataException if a descriptor hasn't been set, or if an error
641 * occurs during calculation of the description within the Descriptor
642 */
643 public String getDescription(int tagType) throws MetadataException
644 {
645 if (_descriptor == null) {
646 throw new MetadataException("a descriptor must be set using setDescriptor(...) before descriptions can be provided");
647 }
648 return _descriptor.getDescription(tagType);
649 }
650 }