/** * JHOVE2 - Next-generation architecture for format-aware characterization * <p> * Copyright (c) 2010 by The Regents of the University of California. All rights reserved. * </p> * <p> * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * </p> * <ul> * <li>Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer.</li> * <li>Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution.</li> * <li>Neither the name of the University of California/California Digital * Library, Ithaka Harbors/Portico, or Stanford University, nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission.</li> * </ul> * <p> * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * </p> */ package org.jhove2.module.format.tiff; import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import org.jhove2.annotation.ReportableProperty; import org.jhove2.config.ConfigInfo; import org.jhove2.core.JHOVE2; import org.jhove2.core.JHOVE2Exception; import org.jhove2.core.Message; import org.jhove2.core.Message.Context; import org.jhove2.core.Message.Severity; import org.jhove2.core.io.Input; import org.jhove2.core.reportable.AbstractReportable; import org.jhove2.core.source.Source; import org.jhove2.module.format.Validator.Validity; import com.sleepycat.persist.model.Persistent; /** * @author mstrong * */ @Persistent public abstract class IFD extends AbstractReportable { protected static Properties tiffTagProps; protected static Properties tiffTypeProps; /** * @return Properties the Tiff Tag Properties stored in the Java Properties file */ public static Properties getTiffTags(ConfigInfo config) throws JHOVE2Exception { if (tiffTagProps==null){ tiffTagProps = config.getProperties("TiffTags"); } return tiffTagProps; } /** * @return Properties the Tiff Type Properties stored in the Java Properties file */ public static Properties getTiffType(ConfigInfo config) throws JHOVE2Exception { if (tiffTypeProps==null){ tiffTypeProps = config.getProperties("TiffTypes"); } return tiffTypeProps; } /** IFD Entries in the IFD */ protected HashMap<Integer, IFDEntry> entries = new HashMap<Integer, IFDEntry>(); /** True if this is the first IFD. */ private boolean first; /** validity of IFD */ protected Validity isValid; /** offset to the next IFD */ protected long nextIFD; /** number of IFD Entries in the IFD */ protected int numEntries; /** offset of the IFD */ protected long offset; /** True if this is the "thumbnail" IFD. */ private boolean thumbnail; /** TIFF version determined by data in IFDEntry */ protected int version; /** Message for Zero IFD Entries */ private Message zeroIFDEntriesMessage; /** * Instantiate an IFD Class with the Input source * @param input */ public IFD() { super(); } /** * Sort the entries in the HashMap and then return only the IFDEntries * @return List<IFDEntry> */ @ReportableProperty(order = 3, value="IFD entries.") public List<IFDEntry> getIFDEntries() { Map<Integer, IFDEntry> sortedEntries = new TreeMap<Integer, IFDEntry>(entries); List<IFDEntry> sortedList = new ArrayList<IFDEntry>(sortedEntries.values()); return sortedList; } /** * returns the entries * @return Map<Integer, IFDEntry> */ public Map<Integer, IFDEntry> getEntries() { return this.entries; } /** * get the offsetof the next IFD * @return long */ @ReportableProperty(order = 4, value = "Offset of next IFD.") public long getNextIFD() { return nextIFD; } /** * get the number of IFD entries * @return int */ @ReportableProperty (order = 2, value = "Number of IFD entries.") public int getNumEntries() { return numEntries; } /** * get the byte offset of the IFD * @return long */ @ReportableProperty(order = 1, value = "Byte offset of IFD.") public long getOffset() { return offset; } /** * @return the version for this IFD */ public int getVersion() { return this.version; } /** * Get the Zero IFD entries message */ @ReportableProperty(order=5, value = "Zero IFD Entries message.") public Message getZeroIFDEntriesMessage(){ return zeroIFDEntriesMessage; } /** * @return the flag indicating this if the first IFD */ public boolean isFirst() { return first; } /** * @return the flag indicating this IFD is a thumbnail */ public boolean isThumbnail() { return thumbnail; } /** * @return the isValid */ public Validity isValid() { return isValid; } /** * Parse an IFD. Read and parse each IFDEntry encountered. * @param input * JHOVE2 framework * @param input * Input * @param tiff2FormatMapper Tiff2FormatMapFactory to map tiff id to Format * @throws EOFException * If End-of-File is reached reading the source unit * @throws IOException * If an I/O exception is raised reading the source unit * @throws JHOVE2Exception */ public void parse(JHOVE2 jhove2, Source source, Input input, Tiff2FormatMapFactory tiff2FormatMapper) throws EOFException, IOException, JHOVE2Exception { this.isValid = Validity.True; long offsetInIFD = this.offset; this.nextIFD = 0L; try { /* Read the first byte. */ input.setPosition(offsetInIFD); numEntries = input.readUnsignedShort(); offsetInIFD += 2; if (this.numEntries < 1){ this.isValid = Validity.False; Object[]messageArgs = new Object[]{0, input.getPosition(), numEntries}; this.zeroIFDEntriesMessage = new Message(Severity.ERROR, Context.OBJECT, "org.jhove2.module.format.tiff.IFD.zeroIFDEntriesMessage", messageArgs, jhove2.getConfigInfo()); } long length = numEntries * 12; /* go to the field that contains the offset to the next IFD - 0 if none */ offsetInIFD += length; this.nextIFD = 0L; } catch (EOFException e) { throw new EOFException("Premature EOF" + offsetInIFD); } try { /* parse the IFD traversing through the list of Directory Entries (IFDEntry) */ IFDEntry.resetPrevTag(0); for (int i=0; i<this.numEntries; i++) { IFDEntry ifdEntry = new IFDEntry(); ifdEntry.parse(jhove2, source, input, tiff2FormatMapper); Validity validity = ifdEntry.isValid(); switch (validity){ case Undetermined: if (this.isValid==Validity.True){ this.isValid = validity; } break; case False: //False is stronger than Undetermined this.isValid = validity; break; case True: // if IFD already flagged as False or Undetermined, will not undo just because one tag is good break; } int version = ifdEntry.getVersion(); if (version > this.version) { this.version = version; } this.entries.put(ifdEntry.getTag(), ifdEntry); /* reset the input position to point to the next IFD Entry (after parsing current IFD Entry, * input position is modified when reading data at location that IFD Entry value offset points to) * Calculation is: * offset (offset start of IFD) + * 14 (2 bytes for numofEntries field + 12 bytes for IFD Entry 0) + * 12 * i (12 bytes for each IFD read in so far) */ input.setPosition(this.offset + 14 + 12*i); } } catch (IOException e) { throw new IOException ("IOException while reading IFD " + (offset + 2), e); } } /** * @parm boolean - * the flag indicating this if the first IFD */ public void setFirst(boolean first) { this.first = first; } public void setOffset(long offset) { this.offset = offset; } public void setThumbnail(boolean thumbnail) { this.thumbnail = thumbnail; } /** * validate the IFD * * @return Validity * @throws IOException * @throws FileNotFoundException * @throws JHOVE2Exception */ abstract Validity validate(JHOVE2 jhove2, Source source) throws JHOVE2Exception, FileNotFoundException, IOException; }