    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 &lt;= 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}