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.Arrays;
026import java.util.Date;
027import java.util.Locale;
028import java.util.logging.Level;
029import java.util.logging.Logger;
030
031import org.apache.commons.imaging.ImagingException;
032import org.apache.commons.imaging.common.Allocator;
033import org.apache.commons.imaging.common.BinaryFunctions;
034import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
035import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
036import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
038
039/**
040 * A TIFF field in a TIFF directory. Immutable.
041 */
042public class TiffField {
043
044    public final class OversizeValueElement extends AbstractTiffElement {
045        public OversizeValueElement(final int offset, final int length) {
046            super(offset, length);
047        }
048
049        @Override
050        public String getElementDescription() {
051            return "OversizeValueElement, tag: " + getTagInfo().name + ", fieldType: " + getFieldType().getName();
052        }
053    }
054
055    private static final Logger LOGGER = Logger.getLogger(TiffField.class.getName());
056    private final TagInfo tagInfo;
057    private final int tag;
058    private final int directoryType;
059    private final AbstractFieldType abstractFieldType;
060    private final long count;
061    private final long offset;
062    private final byte[] value;
063    private final ByteOrder byteOrder;
064
065    private final int sortHint;
066
067    public TiffField(final int tag, final int directoryType, final AbstractFieldType abstractFieldType, final long count, final long offset, final byte[] value,
068            final ByteOrder byteOrder, final int sortHint) {
069
070        this.tag = tag;
071        this.directoryType = directoryType;
072        this.abstractFieldType = abstractFieldType;
073        this.count = count;
074        this.offset = offset;
075        this.value = value;
076        this.byteOrder = byteOrder;
077        this.sortHint = sortHint;
078
079        tagInfo = TiffTags.getTag(directoryType, tag);
080    }
081
082    public void dump() {
083        try (StringWriter sw = new StringWriter();
084                PrintWriter pw = new PrintWriter(sw)) {
085            dump(pw);
086            pw.flush();
087            sw.flush();
088            LOGGER.fine(sw.toString());
089        } catch (final IOException e) {
090            LOGGER.log(Level.SEVERE, e.getMessage(), e);
091        }
092    }
093
094    public void dump(final PrintWriter pw) {
095        dump(pw, null);
096    }
097
098    public void dump(final PrintWriter pw, final String prefix) {
099        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.ENGLISH);
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}