View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.tiff;
18  
19  import java.io.IOException;
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  import java.nio.ByteOrder;
23  import java.text.DateFormat;
24  import java.text.SimpleDateFormat;
25  import java.util.Arrays;
26  import java.util.Date;
27  import java.util.Locale;
28  import java.util.logging.Level;
29  import java.util.logging.Logger;
30  
31  import org.apache.commons.imaging.ImagingException;
32  import org.apache.commons.imaging.common.Allocator;
33  import org.apache.commons.imaging.common.BinaryFunctions;
34  import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
35  import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
36  import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
37  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
38  
39  /**
40   * A TIFF field in a TIFF directory. Immutable.
41   */
42  public class TiffField {
43  
44      public final class OversizeValueElement extends AbstractTiffElement {
45          public OversizeValueElement(final int offset, final int length) {
46              super(offset, length);
47          }
48  
49          @Override
50          public String getElementDescription() {
51              return "OversizeValueElement, tag: " + getTagInfo().name + ", fieldType: " + getFieldType().getName();
52          }
53      }
54  
55      private static final Logger LOGGER = Logger.getLogger(TiffField.class.getName());
56      private final TagInfo tagInfo;
57      private final int tag;
58      private final int directoryType;
59      private final AbstractFieldType abstractFieldType;
60      private final long count;
61      private final long offset;
62      private final byte[] value;
63      private final ByteOrder byteOrder;
64  
65      private final int sortHint;
66  
67      public TiffField(final int tag, final int directoryType, final AbstractFieldType abstractFieldType, final long count, final long offset, final byte[] value,
68              final ByteOrder byteOrder, final int sortHint) {
69  
70          this.tag = tag;
71          this.directoryType = directoryType;
72          this.abstractFieldType = abstractFieldType;
73          this.count = count;
74          this.offset = offset;
75          this.value = value;
76          this.byteOrder = byteOrder;
77          this.sortHint = sortHint;
78  
79          tagInfo = TiffTags.getTag(directoryType, tag);
80      }
81  
82      public void dump() {
83          try (StringWriter sw = new StringWriter();
84                  PrintWriter pw = new PrintWriter(sw)) {
85              dump(pw);
86              pw.flush();
87              sw.flush();
88              LOGGER.fine(sw.toString());
89          } catch (final IOException e) {
90              LOGGER.log(Level.SEVERE, e.getMessage(), e);
91          }
92      }
93  
94      public void dump(final PrintWriter pw) {
95          dump(pw, null);
96      }
97  
98      public void dump(final PrintWriter pw, final String prefix) {
99          if (prefix != null) {
100             pw.print(prefix + ": ");
101         }
102 
103         pw.println(toString());
104         pw.flush();
105     }
106 
107     /**
108      * Returns a copy of the raw value of the field.
109      *
110      * @return the value of the field, in the byte order of the field.
111      */
112     public byte[] getByteArrayValue() {
113         return BinaryFunctions.head(value, getBytesLength());
114     }
115 
116     /**
117      * Returns the field's byte order.
118      *
119      * @return the byte order
120      */
121     public ByteOrder getByteOrder() {
122         return byteOrder;
123     }
124 
125     /**
126      * The length of the field's value.
127      *
128      * @return the length, in bytes.
129      */
130     public int getBytesLength() {
131         return (int) count * abstractFieldType.getSize();
132     }
133 
134     /**
135      * Returns the field's count, derived from bytes 4-7.
136      *
137      * @return the count
138      */
139     public long getCount() {
140         return count;
141     }
142 
143     public String getDescriptionWithoutValue() {
144         return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): ";
145     }
146 
147     public int getDirectoryType() {
148         return directoryType;
149     }
150 
151     public double[] getDoubleArrayValue() throws ImagingException {
152         final Object o = getValue();
153         // if (o == null)
154         // return null;
155 
156         if (o instanceof Number) {
157             return new double[] { ((Number) o).doubleValue() };
158         }
159         if (o instanceof Number[]) {
160             final Number[] numbers = (Number[]) o;
161             final double[] result = Allocator.doubleArray(numbers.length);
162             Arrays.setAll(result, i -> numbers[i].doubleValue());
163             return result;
164         }
165         if (o instanceof short[]) {
166             final short[] numbers = (short[]) o;
167             final double[] result = Allocator.doubleArray(numbers.length);
168             Arrays.setAll(result, i -> numbers[i]);
169             return result;
170         }
171         if (o instanceof int[]) {
172             final int[] numbers = (int[]) o;
173             final double[] result = Allocator.doubleArray(numbers.length);
174             Arrays.setAll(result, i -> numbers[i]);
175             return result;
176         }
177         if (o instanceof float[]) {
178             final float[] numbers = (float[]) o;
179             final double[] result = Allocator.doubleArray(numbers.length);
180             Arrays.setAll(result, i -> numbers[i]);
181             return result;
182         }
183         if (o instanceof double[]) {
184             final double[] numbers = (double[]) o;
185             return Arrays.copyOf(numbers, numbers.length);
186         }
187 
188         throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
189         // return null;
190     }
191 
192     public double getDoubleValue() throws ImagingException {
193         final Object o = getValue();
194         if (o == null) {
195             throw new ImagingException("Missing value: " + getTagInfo().getDescription());
196         }
197 
198         return ((Number) o).doubleValue();
199     }
200 
201     /**
202      * Returns the field's type, derived from bytes 2-3.
203      *
204      * @return the field's type, as a {@code FieldType} object.
205      */
206     public AbstractFieldType getFieldType() {
207         return abstractFieldType;
208     }
209 
210     public String getFieldTypeName() {
211         return getFieldType().getName();
212     }
213 
214     public int[] getIntArrayValue() throws ImagingException {
215         final Object o = getValue();
216         // if (o == null)
217         // return null;
218 
219         if (o instanceof Number) {
220             return new int[] { ((Number) o).intValue() };
221         }
222         if (o instanceof Number[]) {
223             final Number[] numbers = (Number[]) o;
224             final int[] result = Allocator.intArray(numbers.length);
225             Arrays.setAll(result, i -> numbers[i].intValue());
226             return result;
227         }
228         if (o instanceof short[]) {
229             final short[] numbers = (short[]) o;
230             final int[] result = Allocator.intArray(numbers.length);
231             Arrays.setAll(result, i -> 0xffff & numbers[i]);
232             return result;
233         }
234         if (o instanceof int[]) {
235             final int[] numbers = (int[]) o;
236             return Arrays.copyOf(numbers, numbers.length);
237         }
238         if (o instanceof long[]) {
239             final long[] numbers = (long[]) o;
240             final int[] iNumbers = new int[numbers.length];
241             for (int i = 0; i < iNumbers.length; i++) {
242                 iNumbers[i] = (int) numbers[i];
243             }
244             return iNumbers;
245         }
246 
247         throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
248         // return null;
249     }
250 
251     public int getIntValue() throws ImagingException {
252         final Object o = getValue();
253         if (o == null) {
254             throw new ImagingException("Missing value: " + getTagInfo().getDescription());
255         }
256 
257         return ((Number) o).intValue();
258     }
259 
260     public int getIntValueOrArraySum() throws ImagingException {
261         final Object o = getValue();
262         // if (o == null)
263         // return -1;
264 
265         if (o instanceof Number) {
266             return ((Number) o).intValue();
267         }
268         if (o instanceof Number[]) {
269             final Number[] numbers = (Number[]) o;
270             int sum = 0;
271             for (final Number number : numbers) {
272                 sum += number.intValue();
273             }
274             return sum;
275         }
276         if (o instanceof short[]) {
277             final short[] numbers = (short[]) o;
278             int sum = 0;
279             for (final short number : numbers) {
280                 sum += number;
281             }
282             return sum;
283         }
284         if (o instanceof int[]) {
285             final int[] numbers = (int[]) o;
286             int sum = 0;
287             for (final int number : numbers) {
288                 sum += number;
289             }
290             return sum;
291         }
292 
293         throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
294         // return -1;
295     }
296 
297     /**
298      * Gets the value of the field in the form of an array of eight-byte (long) integers.
299      *
300      * @return an valid array of size zero or larger giving signed long integer values.
301      * @throws ImagingException if the field instance is of an incompatible type or does not contain a valid data element.
302      */
303     public long[] getLongArrayValue() throws ImagingException {
304         final Object o = getValue();
305         if (o instanceof Number) {
306             return new long[] { ((Number) o).longValue() };
307         }
308         if (o instanceof Number[]) {
309             final Number[] numbers = (Number[]) o;
310             final long[] result = Allocator.longArray(numbers.length);
311             Arrays.setAll(result, i -> numbers[i].longValue());
312             return result;
313         }
314         if (o instanceof short[]) {
315             final short[] numbers = (short[]) o;
316             final long[] result = Allocator.longArray(numbers.length);
317             Arrays.setAll(result, i -> 0xffff & numbers[i]);
318             return result;
319         }
320         if (o instanceof int[]) {
321             final int[] numbers = (int[]) o;
322             final long[] result = Allocator.longArray(numbers.length);
323             Arrays.setAll(result, i -> 0xFFFFffffL & numbers[i]);
324             return result;
325         }
326         if (o instanceof long[]) {
327             final long[] numbers = (long[]) o;
328             return Arrays.copyOf(numbers, numbers.length);
329         }
330 
331         throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
332     }
333 
334     /**
335      * Gets the value of the field in the form of an eight-byte (long) integer.
336      *
337      * @return a signed long integer value.
338      * @throws ImagingException if the field instance is of an incompatible type or does not contain a valid data element.
339      */
340     public long getLongValue() throws ImagingException {
341         final Object o = getValue();
342         if (o == null) {
343             throw new ImagingException("Missing value: " + getTagInfo().getDescription());
344         }
345         return ((Number) o).longValue();
346     }
347 
348     /**
349      * Returns the TIFF field's offset/value field, derived from bytes 8-11.
350      *
351      * @return the field's offset in a {@code long} of 4 packed bytes, or its inlined value &lt;= 4 bytes long encoded in the field's byte order.
352      */
353     public int getOffset() {
354         return (int) offset;
355     }
356 
357     public AbstractTiffElement getOversizeValueElement() {
358         if (isLocalValue()) {
359             return null;
360         }
361 
362         return new OversizeValueElement(getOffset(), value.length);
363     }
364 
365     public int getSortHint() {
366         return sortHint;
367     }
368 
369     public String getStringValue() throws ImagingException {
370         final Object o = getValue();
371         if (o == null) {
372             return null;
373         }
374         if (!(o instanceof String)) {
375             throw new ImagingException("Expected String value(" + getTagInfo().getDescription() + "): " + o);
376         }
377         return (String) o;
378     }
379 
380     /**
381      * Returns the field's tag, derived from bytes 0-1.
382      *
383      * @return the tag, as an {@code int} in which only the lowest 2 bytes are set
384      */
385     public int getTag() {
386         return tag;
387     }
388 
389     public TagInfo getTagInfo() {
390         return tagInfo;
391     }
392 
393     public String getTagName() {
394         if (getTagInfo() == TiffTagConstants.TIFF_TAG_UNKNOWN) {
395             return getTagInfo().name + " (0x" + Integer.toHexString(getTag()) + ")";
396         }
397         return getTagInfo().name;
398     }
399 
400     public Object getValue() throws ImagingException {
401         // System.out.print("getValue");
402         return getTagInfo().getValue(this);
403     }
404 
405     public String getValueDescription() {
406         try {
407             return getValueDescription(getValue());
408         } catch (final ImagingException e) {
409             return "Invalid value: " + e.getMessage();
410         }
411     }
412 
413     private String getValueDescription(final Object o) {
414         if (o == null) {
415             return null;
416         }
417 
418         if (o instanceof Number) {
419             return o.toString();
420         }
421         if (o instanceof String) {
422             return "'" + o.toString().trim() + "'";
423         }
424         if (o instanceof Date) {
425             final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ROOT);
426             return df.format((Date) o);
427         }
428         if (o instanceof Object[]) {
429             final Object[] objects = (Object[]) o;
430             final StringBuilder result = new StringBuilder();
431 
432             for (int i = 0; i < objects.length; i++) {
433                 final Object object = objects[i];
434 
435                 if (i > 50) {
436                     result.append("... (").append(objects.length).append(")");
437                     break;
438                 }
439                 if (i > 0) {
440                     result.append(", ");
441                 }
442                 result.append(object.toString());
443             }
444             return result.toString();
445             // } else if (o instanceof Number[])
446             // {
447             // Number[] numbers = (Number[]) o;
448             // StringBuilder result = new StringBuilder();
449             //
450             // for (int i = 0; i < numbers.length; i++)
451             // {
452             // Number number = numbers[i];
453             //
454             // if (i > 0)
455             // result.append(", ");
456             // result.append("" + number);
457             // }
458             // return result.toString();
459             // }
460         }
461         if (o instanceof short[]) {
462             final short[] values = (short[]) o;
463             final StringBuilder result = new StringBuilder();
464 
465             for (int i = 0; i < values.length; i++) {
466                 final short sVal = values[i];
467 
468                 if (i > 50) {
469                     result.append("... (").append(values.length).append(")");
470                     break;
471                 }
472                 if (i > 0) {
473                     result.append(", ");
474                 }
475                 result.append(sVal);
476             }
477             return result.toString();
478         }
479         if (o instanceof int[]) {
480             final int[] values = (int[]) o;
481             final StringBuilder result = new StringBuilder();
482 
483             for (int i = 0; i < values.length; i++) {
484                 final int iVal = values[i];
485 
486                 if (i > 50) {
487                     result.append("... (").append(values.length).append(")");
488                     break;
489                 }
490                 if (i > 0) {
491                     result.append(", ");
492                 }
493                 result.append(iVal);
494             }
495             return result.toString();
496         }
497         if (o instanceof long[]) {
498             final long[] values = (long[]) o;
499             final StringBuilder result = new StringBuilder();
500 
501             for (int i = 0; i < values.length; i++) {
502                 final long lVal = values[i];
503 
504                 if (i > 50) {
505                     result.append("... (").append(values.length).append(")");
506                     break;
507                 }
508                 if (i > 0) {
509                     result.append(", ");
510                 }
511                 result.append(lVal);
512             }
513             return result.toString();
514         }
515         if (o instanceof double[]) {
516             final double[] values = (double[]) o;
517             final StringBuilder result = new StringBuilder();
518 
519             for (int i = 0; i < values.length; i++) {
520                 final double dVal = values[i];
521 
522                 if (i > 50) {
523                     result.append("... (").append(values.length).append(")");
524                     break;
525                 }
526                 if (i > 0) {
527                     result.append(", ");
528                 }
529                 result.append(dVal);
530             }
531             return result.toString();
532         }
533         if (o instanceof byte[]) {
534             final byte[] values = (byte[]) o;
535             final StringBuilder result = new StringBuilder();
536 
537             for (int i = 0; i < values.length; i++) {
538                 final byte bVal = values[i];
539 
540                 if (i > 50) {
541                     result.append("... (").append(values.length).append(")");
542                     break;
543                 }
544                 if (i > 0) {
545                     result.append(", ");
546                 }
547                 result.append(bVal);
548             }
549             return result.toString();
550         }
551         if (o instanceof char[]) {
552             final char[] values = (char[]) o;
553             final StringBuilder result = new StringBuilder();
554 
555             for (int i = 0; i < values.length; i++) {
556                 final char cVal = values[i];
557 
558                 if (i > 50) {
559                     result.append("... (").append(values.length).append(")");
560                     break;
561                 }
562                 if (i > 0) {
563                     result.append(", ");
564                 }
565                 result.append(cVal);
566             }
567             return result.toString();
568         }
569         if (o instanceof float[]) {
570             final float[] values = (float[]) o;
571             final StringBuilder result = new StringBuilder();
572 
573             for (int i = 0; i < values.length; i++) {
574                 final float fVal = values[i];
575 
576                 if (i > 50) {
577                     result.append("... (").append(values.length).append(")");
578                     break;
579                 }
580                 if (i > 0) {
581                     result.append(", ");
582                 }
583                 result.append(fVal);
584             }
585             return result.toString();
586         }
587         // else if (o instanceof short[])
588         // {
589         // short[] numbers = (short[]) o;
590         // StringBuilder result = new StringBuilder();
591         //
592         // for (int i = 0; i < numbers.length; i++)
593         // {
594         // short number = numbers[i];
595         //
596         // if (i > 0)
597         // result.append(", ");
598         // result.append("" + number);
599         // }
600         // return result.toString();
601         // }
602 
603         return "Unknown: " + o.getClass().getName();
604     }
605 
606     /**
607      * Indicates whether the field's value is inlined into the offset field.
608      *
609      * @return true if the value is inlined
610      */
611     public boolean isLocalValue() {
612         return count * abstractFieldType.getSize() <= TiffConstants.TIFF_ENTRY_MAX_VALUE_LENGTH;
613     }
614 
615     @Override
616     public String toString() {
617         return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): " + getValueDescription() + " (" + getCount() + " "
618                 + getFieldType().getName() + ")";
619     }
620 }