001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.tiff;
018
019import java.io.IOException;
020import java.io.PrintWriter;
021import java.io.StringWriter;
022import java.nio.ByteOrder;
023import java.text.DateFormat;
024import java.text.SimpleDateFormat;
025import java.util.Date;
026import java.util.Locale;
027import java.util.logging.Level;
028import java.util.logging.Logger;
029
030import org.apache.commons.imaging.ImageReadException;
031import org.apache.commons.imaging.common.BinaryFunctions;
032import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
033import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
034import org.apache.commons.imaging.formats.tiff.fieldtypes.FieldType;
035import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
036
037/**
038 * A TIFF field in a TIFF directory. Immutable.
039 */
040public class TiffField {
041
042    private static final Logger LOGGER = Logger.getLogger(TiffField.class.getName());
043
044    private final TagInfo tagInfo;
045    private final int tag;
046    private final int directoryType;
047    private final FieldType fieldType;
048    private final long count;
049    private final long offset;
050    private final byte[] value;
051    private final ByteOrder byteOrder;
052    private final int sortHint;
053
054    public TiffField(final int tag, final int directoryType, final FieldType fieldType,
055            final long count, final long offset, final byte[] value,
056            final ByteOrder byteOrder, final int sortHint) {
057
058        this.tag = tag;
059        this.directoryType = directoryType;
060        this.fieldType = fieldType;
061        this.count = count;
062        this.offset = offset;
063        this.value = value;
064        this.byteOrder = byteOrder;
065        this.sortHint = sortHint;
066
067        tagInfo = TiffTags.getTag(directoryType, tag);
068    }
069
070    public int getDirectoryType() {
071        return directoryType;
072    }
073
074    public TagInfo getTagInfo() {
075        return tagInfo;
076    }
077
078    /**
079     * Returns the field's tag, derived from bytes 0-1.
080     * @return the tag, as an {@code int} in which only the lowest 2 bytes are set
081     */
082    public int getTag() {
083        return tag;
084    }
085
086    /**
087     * Returns the field's type, derived from bytes 2-3.
088     * @return the field's type, as a {@code FieldType} object.
089     */
090    public FieldType getFieldType() {
091        return fieldType;
092    }
093
094    /**
095     * Returns the field's count, derived from bytes 4-7.
096     * @return the count
097     */
098    public long getCount() {
099        return count;
100    }
101
102    /**
103     * Returns the TIFF field's offset/value field, derived from bytes 8-11.
104     * @return the field's offset in a {@code long} of 4 packed bytes,
105     * or its inlined value <= 4 bytes long encoded in the field's byte order.
106     */
107    public int getOffset() {
108        return (int) offset;
109    }
110
111    /**
112     * Returns the field's byte order.
113     * @return the byte order
114     */
115    public ByteOrder getByteOrder() {
116        return byteOrder;
117    }
118
119    public int getSortHint() {
120        return sortHint;
121    }
122
123    /**
124     * Indicates whether the field's value is inlined into the offset field.
125     * @return true if the value is inlined
126     */
127    public boolean isLocalValue() {
128        return (count * fieldType.getSize()) <= TiffConstants.TIFF_ENTRY_MAX_VALUE_LENGTH;
129    }
130
131    /**
132     * The length of the field's value.
133     * @return the length, in bytes.
134     */
135    public int getBytesLength() {
136        return (int) count * fieldType.getSize();
137    }
138
139    /**
140     * Returns a copy of the raw value of the field.
141     * @return the value of the field, in the byte order of the field.
142     */
143    public byte[] getByteArrayValue() {
144        return BinaryFunctions.head(value, getBytesLength());
145    }
146
147    public final class OversizeValueElement extends TiffElement {
148        public OversizeValueElement(final int offset, final int length) {
149            super(offset, length);
150        }
151
152        @Override
153        public String getElementDescription() {
154            return "OversizeValueElement, tag: " + getTagInfo().name
155                    + ", fieldType: " + getFieldType().getName();
156        }
157    }
158
159    public TiffElement getOversizeValueElement() {
160        if (isLocalValue()) {
161            return null;
162        }
163
164        return new OversizeValueElement(getOffset(), value.length);
165    }
166
167    public String getValueDescription() {
168        try {
169            return getValueDescription(getValue());
170        } catch (final ImageReadException e) {
171            return "Invalid value: " + e.getMessage();
172        }
173    }
174
175    private String getValueDescription(final Object o) {
176        if (o == null) {
177            return null;
178        }
179
180        if (o instanceof Number) {
181            return o.toString();
182        } else if (o instanceof String) {
183            return "'" + o.toString().trim() + "'";
184        } else if (o instanceof Date) {
185            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ENGLISH);
186            return df.format((Date) o);
187        } else if (o instanceof Object[]) {
188            final Object[] objects = (Object[]) o;
189            final StringBuilder result = new StringBuilder();
190
191            for (int i = 0; i < objects.length; i++) {
192                final Object object = objects[i];
193
194                if (i > 50) {
195                    result.append("... (").append(objects.length).append(")");
196                    break;
197                }
198                if (i > 0) {
199                    result.append(", ");
200                }
201                result.append(object.toString());
202            }
203            return result.toString();
204        // } else if (o instanceof Number[])
205        // {
206        // Number numbers[] = (Number[]) o;
207        // StringBuilder result = new StringBuilder();
208        //
209        // for (int i = 0; i < numbers.length; i++)
210        // {
211        // Number number = numbers[i];
212        //
213        // if (i > 0)
214        // result.append(", ");
215        // result.append("" + number);
216        // }
217        // return result.toString();
218        // }
219        } else if (o instanceof short[]) {
220            final short[] values = (short[]) o;
221            final StringBuilder result = new StringBuilder();
222
223            for (int i = 0; i < values.length; i++) {
224                final short sval = values[i];
225
226                if (i > 50) {
227                    result.append("... (").append(values.length).append(")");
228                    break;
229                }
230                if (i > 0) {
231                    result.append(", ");
232                }
233                result.append(sval);
234            }
235            return result.toString();
236        } else if (o instanceof int[]) {
237            final int[] values = (int[]) o;
238            final StringBuilder result = new StringBuilder();
239
240            for (int i = 0; i < values.length; i++) {
241                final int iVal = values[i];
242
243                if (i > 50) {
244                    result.append("... (").append(values.length).append(")");
245                    break;
246                }
247                if (i > 0) {
248                    result.append(", ");
249                }
250                result.append(iVal);
251            }
252            return result.toString();
253        } else if (o instanceof long[]) {
254            final long[] values = (long[]) o;
255            final StringBuilder result = new StringBuilder();
256
257            for (int i = 0; i < values.length; i++) {
258                final long lVal = values[i];
259
260                if (i > 50) {
261                    result.append("... (").append(values.length).append(")");
262                    break;
263                }
264                if (i > 0) {
265                    result.append(", ");
266                }
267                result.append(lVal);
268            }
269            return result.toString();
270        } else if (o instanceof double[]) {
271            final double[] values = (double[]) o;
272            final StringBuilder result = new StringBuilder();
273
274            for (int i = 0; i < values.length; i++) {
275                final double dVal = values[i];
276
277                if (i > 50) {
278                    result.append("... (").append(values.length).append(")");
279                    break;
280                }
281                if (i > 0) {
282                    result.append(", ");
283                }
284                result.append(dVal);
285            }
286            return result.toString();
287        } else if (o instanceof byte[]) {
288            final byte[] values = (byte[]) o;
289            final StringBuilder result = new StringBuilder();
290
291            for (int i = 0; i < values.length; i++) {
292                final byte bVal = values[i];
293
294                if (i > 50) {
295                    result.append("... (").append(values.length).append(")");
296                    break;
297                }
298                if (i > 0) {
299                    result.append(", ");
300                }
301                result.append(bVal);
302            }
303            return result.toString();
304        } else if (o instanceof char[]) {
305            final char[] values = (char[]) o;
306            final StringBuilder result = new StringBuilder();
307
308            for (int i = 0; i < values.length; i++) {
309                final char cVal = values[i];
310
311                if (i > 50) {
312                    result.append("... (").append(values.length).append(")");
313                    break;
314                }
315                if (i > 0) {
316                    result.append(", ");
317                }
318                result.append(cVal);
319            }
320            return result.toString();
321        } else if (o instanceof float[]) {
322            final float[] values = (float[]) o;
323            final StringBuilder result = new StringBuilder();
324
325            for (int i = 0; i < values.length; i++) {
326                final float fVal = values[i];
327
328                if (i > 50) {
329                    result.append("... (").append(values.length).append(")");
330                    break;
331                }
332                if (i > 0) {
333                    result.append(", ");
334                }
335                result.append(fVal);
336            }
337            return result.toString();
338        }
339        // else if (o instanceof short[])
340        // {
341        // short numbers[] = (short[]) o;
342        // StringBuilder result = new StringBuilder();
343        //
344        // for (int i = 0; i < numbers.length; i++)
345        // {
346        // short number = numbers[i];
347        //
348        // if (i > 0)
349        // result.append(", ");
350        // result.append("" + number);
351        // }
352        // return result.toString();
353        // }
354
355        return "Unknown: " + o.getClass().getName();
356    }
357
358    public void dump() {
359        try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
360            dump(pw);
361            pw.flush();
362            sw.flush();
363            LOGGER.fine(sw.toString());
364        } catch (final IOException e) {
365            LOGGER.log(Level.SEVERE, e.getMessage(), e);
366        }
367    }
368
369    public void dump(final PrintWriter pw) {
370        dump(pw, null);
371    }
372
373    public void dump(final PrintWriter pw, final String prefix) {
374        if (prefix != null) {
375            pw.print(prefix + ": ");
376        }
377
378        pw.println(toString());
379        pw.flush();
380    }
381
382    public String getDescriptionWithoutValue() {
383        return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name
384                + "): ";
385    }
386
387    @Override
388    public String toString() {
389        return getTag() +
390                " (0x" +
391                Integer.toHexString(getTag()) +
392                ": " +
393                getTagInfo().name +
394                "): " +
395                getValueDescription() +
396                " (" +
397                getCount() +
398                " " +
399                getFieldType().getName() +
400                ")";
401    }
402
403    public String getTagName() {
404        if (getTagInfo() == TiffTagConstants.TIFF_TAG_UNKNOWN) {
405            return getTagInfo().name + " (0x" + Integer.toHexString(getTag()) + ")";
406        }
407        return getTagInfo().name;
408    }
409
410    public String getFieldTypeName() {
411        return getFieldType().getName();
412    }
413
414    public Object getValue() throws ImageReadException {
415        // System.out.print("getValue");
416        return getTagInfo().getValue(this);
417    }
418
419    public String getStringValue() throws ImageReadException {
420        final Object o = getValue();
421        if (o == null) {
422            return null;
423        }
424        if (!(o instanceof String)) {
425            throw new ImageReadException("Expected String value("
426                    + getTagInfo().getDescription() + "): " + o);
427        }
428        return (String) o;
429    }
430
431    public int[] getIntArrayValue() throws ImageReadException {
432        final Object o = getValue();
433        // if (o == null)
434        // return null;
435
436        if (o instanceof Number) {
437            return new int[] { ((Number) o).intValue() };
438        } else if (o instanceof Number[]) {
439            final Number[] numbers = (Number[]) o;
440            final int[] result = new int[numbers.length];
441            for (int i = 0; i < numbers.length; i++) {
442                result[i] = numbers[i].intValue();
443            }
444            return result;
445        } else if (o instanceof short[]) {
446            final short[] numbers = (short[]) o;
447            final int[] result = new int[numbers.length];
448            for (int i = 0; i < numbers.length; i++) {
449                result[i] = 0xffff & numbers[i];
450            }
451            return result;
452        } else if (o instanceof int[]) {
453            final int[] numbers = (int[]) o;
454            final int[] result = new int[numbers.length];
455            System.arraycopy(numbers, 0, result, 0, numbers.length);
456            return result;
457        }
458
459        throw new ImageReadException("Unknown value: " + o + " for: "
460                + getTagInfo().getDescription());
461        // return null;
462    }
463
464    public double[] getDoubleArrayValue() throws ImageReadException {
465        final Object o = getValue();
466        // if (o == null)
467        // return null;
468
469        if (o instanceof Number) {
470            return new double[] { ((Number) o).doubleValue() };
471        } else if (o instanceof Number[]) {
472            final Number[] numbers = (Number[]) o;
473            final double[] result = new double[numbers.length];
474            for (int i = 0; i < numbers.length; i++) {
475                result[i] = numbers[i].doubleValue();
476            }
477            return result;
478        } else if (o instanceof short[]) {
479            final short[] numbers = (short[]) o;
480            final double[] result = new double[numbers.length];
481            for (int i = 0; i < numbers.length; i++) {
482                result[i] = numbers[i];
483            }
484            return result;
485        } else if (o instanceof int[]) {
486            final int[] numbers = (int[]) o;
487            final double[] result = new double[numbers.length];
488            for (int i = 0; i < numbers.length; i++) {
489                result[i] = numbers[i];
490            }
491            return result;
492        } else if (o instanceof float[]) {
493            final float[] numbers = (float[]) o;
494            final double[] result = new double[numbers.length];
495            for (int i = 0; i < numbers.length; i++) {
496                result[i] = numbers[i];
497            }
498            return result;
499        } else if (o instanceof double[]) {
500            final double[] numbers = (double[]) o;
501            final double[] result = new double[numbers.length];
502            System.arraycopy(numbers, 0, result, 0, numbers.length);
503            return result;
504        }
505
506        throw new ImageReadException("Unknown value: " + o + " for: "
507                + getTagInfo().getDescription());
508        // return null;
509    }
510
511    public int getIntValueOrArraySum() throws ImageReadException {
512        final Object o = getValue();
513        // if (o == null)
514        // return -1;
515
516        if (o instanceof Number) {
517            return ((Number) o).intValue();
518        } else if (o instanceof Number[]) {
519            final Number[] numbers = (Number[]) o;
520            int sum = 0;
521            for (final Number number : numbers) {
522                sum += number.intValue();
523            }
524            return sum;
525        } else if (o instanceof short[]) {
526            final short[] numbers = (short[]) o;
527            int sum = 0;
528            for (final short number : numbers) {
529                sum += number;
530            }
531            return sum;
532        } else if (o instanceof int[]) {
533            final int[] numbers = (int[]) o;
534            int sum = 0;
535            for (final int number : numbers) {
536                sum += number;
537            }
538            return sum;
539        }
540
541        throw new ImageReadException("Unknown value: " + o + " for: "
542                + getTagInfo().getDescription());
543        // return -1;
544    }
545
546    public int getIntValue() throws ImageReadException {
547        final Object o = getValue();
548        if (o == null) {
549            throw new ImageReadException("Missing value: "
550                    + getTagInfo().getDescription());
551        }
552
553        return ((Number) o).intValue();
554    }
555
556    public double getDoubleValue() throws ImageReadException {
557        final Object o = getValue();
558        if (o == null) {
559            throw new ImageReadException("Missing value: "
560                    + getTagInfo().getDescription());
561        }
562
563        return ((Number) o).doubleValue();
564    }
565}