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        }
183        if (o instanceof String) {
184            return "'" + o.toString().trim() + "'";
185        }
186        if (o instanceof Date) {
187            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ENGLISH);
188            return df.format((Date) o);
189        }
190        if (o instanceof Object[]) {
191            final Object[] objects = (Object[]) o;
192            final StringBuilder result = new StringBuilder();
193
194            for (int i = 0; i < objects.length; i++) {
195                final Object object = objects[i];
196
197                if (i > 50) {
198                    result.append("... (").append(objects.length).append(")");
199                    break;
200                }
201                if (i > 0) {
202                    result.append(", ");
203                }
204                result.append(object.toString());
205            }
206            return result.toString();
207        // } else if (o instanceof Number[])
208        // {
209        // Number numbers[] = (Number[]) o;
210        // StringBuilder result = new StringBuilder();
211        //
212        // for (int i = 0; i < numbers.length; i++)
213        // {
214        // Number number = numbers[i];
215        //
216        // if (i > 0)
217        // result.append(", ");
218        // result.append("" + number);
219        // }
220        // return result.toString();
221        // }
222        }
223        if (o instanceof short[]) {
224            final short[] values = (short[]) o;
225            final StringBuilder result = new StringBuilder();
226
227            for (int i = 0; i < values.length; i++) {
228                final short sVal = values[i];
229
230                if (i > 50) {
231                    result.append("... (").append(values.length).append(")");
232                    break;
233                }
234                if (i > 0) {
235                    result.append(", ");
236                }
237                result.append(sVal);
238            }
239            return result.toString();
240        }
241        if (o instanceof int[]) {
242            final int[] values = (int[]) o;
243            final StringBuilder result = new StringBuilder();
244
245            for (int i = 0; i < values.length; i++) {
246                final int iVal = values[i];
247
248                if (i > 50) {
249                    result.append("... (").append(values.length).append(")");
250                    break;
251                }
252                if (i > 0) {
253                    result.append(", ");
254                }
255                result.append(iVal);
256            }
257            return result.toString();
258        }
259        if (o instanceof long[]) {
260            final long[] values = (long[]) o;
261            final StringBuilder result = new StringBuilder();
262
263            for (int i = 0; i < values.length; i++) {
264                final long lVal = values[i];
265
266                if (i > 50) {
267                    result.append("... (").append(values.length).append(")");
268                    break;
269                }
270                if (i > 0) {
271                    result.append(", ");
272                }
273                result.append(lVal);
274            }
275            return result.toString();
276        }
277        if (o instanceof double[]) {
278            final double[] values = (double[]) o;
279            final StringBuilder result = new StringBuilder();
280
281            for (int i = 0; i < values.length; i++) {
282                final double dVal = values[i];
283
284                if (i > 50) {
285                    result.append("... (").append(values.length).append(")");
286                    break;
287                }
288                if (i > 0) {
289                    result.append(", ");
290                }
291                result.append(dVal);
292            }
293            return result.toString();
294        }
295        if (o instanceof byte[]) {
296            final byte[] values = (byte[]) o;
297            final StringBuilder result = new StringBuilder();
298
299            for (int i = 0; i < values.length; i++) {
300                final byte bVal = values[i];
301
302                if (i > 50) {
303                    result.append("... (").append(values.length).append(")");
304                    break;
305                }
306                if (i > 0) {
307                    result.append(", ");
308                }
309                result.append(bVal);
310            }
311            return result.toString();
312        }
313        if (o instanceof char[]) {
314            final char[] values = (char[]) o;
315            final StringBuilder result = new StringBuilder();
316
317            for (int i = 0; i < values.length; i++) {
318                final char cVal = values[i];
319
320                if (i > 50) {
321                    result.append("... (").append(values.length).append(")");
322                    break;
323                }
324                if (i > 0) {
325                    result.append(", ");
326                }
327                result.append(cVal);
328            }
329            return result.toString();
330        }
331        if (o instanceof float[]) {
332            final float[] values = (float[]) o;
333            final StringBuilder result = new StringBuilder();
334
335            for (int i = 0; i < values.length; i++) {
336                final float fVal = values[i];
337
338                if (i > 50) {
339                    result.append("... (").append(values.length).append(")");
340                    break;
341                }
342                if (i > 0) {
343                    result.append(", ");
344                }
345                result.append(fVal);
346            }
347            return result.toString();
348        }
349        // else if (o instanceof short[])
350        // {
351        // short numbers[] = (short[]) o;
352        // StringBuilder result = new StringBuilder();
353        //
354        // for (int i = 0; i < numbers.length; i++)
355        // {
356        // short number = numbers[i];
357        //
358        // if (i > 0)
359        // result.append(", ");
360        // result.append("" + number);
361        // }
362        // return result.toString();
363        // }
364
365        return "Unknown: " + o.getClass().getName();
366    }
367
368    public void dump() {
369        try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
370            dump(pw);
371            pw.flush();
372            sw.flush();
373            LOGGER.fine(sw.toString());
374        } catch (final IOException e) {
375            LOGGER.log(Level.SEVERE, e.getMessage(), e);
376        }
377    }
378
379    public void dump(final PrintWriter pw) {
380        dump(pw, null);
381    }
382
383    public void dump(final PrintWriter pw, final String prefix) {
384        if (prefix != null) {
385            pw.print(prefix + ": ");
386        }
387
388        pw.println(toString());
389        pw.flush();
390    }
391
392    public String getDescriptionWithoutValue() {
393        return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name
394                + "): ";
395    }
396
397    @Override
398    public String toString() {
399        return getTag() +
400                " (0x" +
401                Integer.toHexString(getTag()) +
402                ": " +
403                getTagInfo().name +
404                "): " +
405                getValueDescription() +
406                " (" +
407                getCount() +
408                " " +
409                getFieldType().getName() +
410                ")";
411    }
412
413    public String getTagName() {
414        if (getTagInfo() == TiffTagConstants.TIFF_TAG_UNKNOWN) {
415            return getTagInfo().name + " (0x" + Integer.toHexString(getTag()) + ")";
416        }
417        return getTagInfo().name;
418    }
419
420    public String getFieldTypeName() {
421        return getFieldType().getName();
422    }
423
424    public Object getValue() throws ImageReadException {
425        // System.out.print("getValue");
426        return getTagInfo().getValue(this);
427    }
428
429    public String getStringValue() throws ImageReadException {
430        final Object o = getValue();
431        if (o == null) {
432            return null;
433        }
434        if (!(o instanceof String)) {
435            throw new ImageReadException("Expected String value("
436                    + getTagInfo().getDescription() + "): " + o);
437        }
438        return (String) o;
439    }
440
441    public int[] getIntArrayValue() throws ImageReadException {
442        final Object o = getValue();
443        // if (o == null)
444        // return null;
445
446        if (o instanceof Number) {
447            return new int[] { ((Number) o).intValue() };
448        }
449        if (o instanceof Number[]) {
450            final Number[] numbers = (Number[]) o;
451            final int[] result = new int[numbers.length];
452            for (int i = 0; i < numbers.length; i++) {
453                result[i] = numbers[i].intValue();
454            }
455            return result;
456        }
457        if (o instanceof short[]) {
458            final short[] numbers = (short[]) o;
459            final int[] result = new int[numbers.length];
460            for (int i = 0; i < numbers.length; i++) {
461                result[i] = 0xffff & numbers[i];
462            }
463            return result;
464        }
465        if (o instanceof int[]) {
466            final int[] numbers = (int[]) o;
467            final int[] result = new int[numbers.length];
468            System.arraycopy(numbers, 0, result, 0, numbers.length);
469            return result;
470        }
471
472        throw new ImageReadException("Unknown value: " + o + " for: "
473                + getTagInfo().getDescription());
474        // return null;
475    }
476
477    public double[] getDoubleArrayValue() throws ImageReadException {
478        final Object o = getValue();
479        // if (o == null)
480        // return null;
481
482        if (o instanceof Number) {
483            return new double[] { ((Number) o).doubleValue() };
484        }
485        if (o instanceof Number[]) {
486            final Number[] numbers = (Number[]) o;
487            final double[] result = new double[numbers.length];
488            for (int i = 0; i < numbers.length; i++) {
489                result[i] = numbers[i].doubleValue();
490            }
491            return result;
492        }
493        if (o instanceof short[]) {
494            final short[] numbers = (short[]) o;
495            final double[] result = new double[numbers.length];
496            for (int i = 0; i < numbers.length; i++) {
497                result[i] = numbers[i];
498            }
499            return result;
500        }
501        if (o instanceof int[]) {
502            final int[] numbers = (int[]) o;
503            final double[] result = new double[numbers.length];
504            for (int i = 0; i < numbers.length; i++) {
505                result[i] = numbers[i];
506            }
507            return result;
508        }
509        if (o instanceof float[]) {
510            final float[] numbers = (float[]) o;
511            final double[] result = new double[numbers.length];
512            for (int i = 0; i < numbers.length; i++) {
513                result[i] = numbers[i];
514            }
515            return result;
516        }
517        if (o instanceof double[]) {
518            final double[] numbers = (double[]) o;
519            final double[] result = new double[numbers.length];
520            System.arraycopy(numbers, 0, result, 0, numbers.length);
521            return result;
522        }
523
524        throw new ImageReadException("Unknown value: " + o + " for: "
525                + getTagInfo().getDescription());
526        // return null;
527    }
528
529    public int getIntValueOrArraySum() throws ImageReadException {
530        final Object o = getValue();
531        // if (o == null)
532        // return -1;
533
534        if (o instanceof Number) {
535            return ((Number) o).intValue();
536        }
537        if (o instanceof Number[]) {
538            final Number[] numbers = (Number[]) o;
539            int sum = 0;
540            for (final Number number : numbers) {
541                sum += number.intValue();
542            }
543            return sum;
544        }
545        if (o instanceof short[]) {
546            final short[] numbers = (short[]) o;
547            int sum = 0;
548            for (final short number : numbers) {
549                sum += number;
550            }
551            return sum;
552        }
553        if (o instanceof int[]) {
554            final int[] numbers = (int[]) o;
555            int sum = 0;
556            for (final int number : numbers) {
557                sum += number;
558            }
559            return sum;
560        }
561
562        throw new ImageReadException("Unknown value: " + o + " for: "
563                + getTagInfo().getDescription());
564        // return -1;
565    }
566
567    public int getIntValue() throws ImageReadException {
568        final Object o = getValue();
569        if (o == null) {
570            throw new ImageReadException("Missing value: "
571                    + getTagInfo().getDescription());
572        }
573
574        return ((Number) o).intValue();
575    }
576
577    public double getDoubleValue() throws ImageReadException {
578        final Object o = getValue();
579        if (o == null) {
580            throw new ImageReadException("Missing value: "
581                    + getTagInfo().getDescription());
582        }
583
584        return ((Number) o).doubleValue();
585    }
586}