Catalog.java

/*
 * Copyright (c) 2008, Harald Kuhr
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * 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.
 *
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * 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 HOLDER 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.
 */

package com.twelvemonkeys.imageio.plugins.thumbsdb;

import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.ole2.CompoundDocument;
import com.twelvemonkeys.lang.StringUtil;

import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Iterator;

/**
 * Represents a {@code Catalog} structure, typically found in a {@link com.twelvemonkeys.io.ole2.CompoundDocument}. 
 *
 * @see <a href="http://www.petedavis.net/MySite/DynPageView.aspx?pageid=31">PeteDavis.NET</a>
 *
 * @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
 * @author last modified by $Author: haku$
 * @version $Id: Catalog.java,v 1.0 01.feb.2007 17:19:59 haku Exp$
 */
// TODO: Consider moving this one to io.ole2
public final class Catalog implements Iterable<Catalog.CatalogItem> {

    private final CatalogHeader header;
    private final CatalogItem[] items;

    Catalog(final CatalogHeader pHeader, final CatalogItem[] pItems) {
        header = pHeader;
        items = pItems;
    }

    /**
     * Reads the {@code Catalog} entry from the given input stream.
     *
     * @param pInput the input stream
     * @return a new {@code Catalog}
     *
     * @throws java.io.IOException if an I/O exception occurs during read
     */
    public static Catalog read(final InputStream pInput) throws IOException {
        DataInput dataInput = new LittleEndianDataInputStream(pInput);
        return read(dataInput);
    }

    /**
     * Reads the {@code Catalog} entry from the given input stream.
     * <p>
     * The data is assumed to be in little endian byte order.
     * </p>
     *
     * @param pDataInput the input stream
     * @return a new {@code Catalog}
     *
     * @throws java.io.IOException if an I/O exception occurs during read
     */
    public static Catalog read(final DataInput pDataInput) throws IOException {
        CatalogHeader header = CatalogHeader.read(pDataInput);

        CatalogItem[] items = new CatalogItem[header.getThumbnailCount()];
        for (int i = 0; i < header.getThumbnailCount(); i++) {
            CatalogItem item = CatalogItem.read(pDataInput);
            //System.out.println("item: " + item);
            items[item.getItemId() - 1] = item;
        }

        return new Catalog(header, items);
    }

    public final int getThumbnailCount() {
        return header.mThumbCount;
    }

    public final int getMaxThumbnailWidth() {
        return header.mThumbWidth;
    }

    public final int getMaxThumbnailHeight() {
        return header.mThumbHeight;
    }

    final CatalogItem getItem(final int pIndex) {
        return items[pIndex];
    }

    final CatalogItem getItem(final String pName) {
        return items[getIndex(pName)];
    }

    final int getItemId(final int pIndex) {
        return items[pIndex].getItemId();
    }

    public final int getIndex(final String pName) {
        for (int i = 0; i < items.length; i++) {
            CatalogItem item = items[i];

            if (item.getName().equals(pName)) {
                return i;
            }
        }

        return -1;
    }

    public final String getStreamName(final int pIndex) {
        return StringUtil.reverse(String.valueOf(getItemId(pIndex)));
    }

    public final String getName(String pStreamName) {
        return getName(Integer.parseInt(StringUtil.reverse(pStreamName)));
    }

    final String getName(int pItemId) {
        return items[pItemId - 1].getName();
    }

    @Override
    public String toString() {
        return String.format("%s[%s]", getClass().getSimpleName(), header);
    }

    public Iterator<CatalogItem> iterator() {
        return new Iterator<CatalogItem>() {
            int mCurrentIdx;

            public boolean hasNext() {
                return mCurrentIdx < items.length;
            }

            public CatalogItem next() {
                return items[mCurrentIdx++];
            }

            public void remove() {
                throw new UnsupportedOperationException("Remove not supported");
            }
        };
    }

    private static class CatalogHeader {
        short mReserved1;
        short mReserved2;
        int mThumbCount;
        int mThumbWidth;
        int mThumbHeight;

        CatalogHeader() {
        }

        public static CatalogHeader read(final DataInput pDataInput) throws IOException {
            CatalogHeader header = new CatalogHeader();

            header.mReserved1 = pDataInput.readShort();
            header.mReserved2 = pDataInput.readShort();
            header.mThumbCount = pDataInput.readInt();
            header.mThumbWidth = pDataInput.readInt();
            header.mThumbHeight = pDataInput.readInt();

            return header;
        }

        public int getThumbnailCount() {
            return mThumbCount;
        }

        public int getThumbHeight() {
            return mThumbHeight;
        }

        public int getThumbWidth() {
            return mThumbWidth;
        }

        @Override
        public String toString() {
            return String.format(
                    "%s: %s %s thumbs: %d maxWidth: %d maxHeight: %d",
                    getClass().getSimpleName(),  mReserved1, mReserved2, mThumbCount, mThumbWidth, mThumbHeight
            );
        }
    }

    public static final class CatalogItem {
        int mReserved1;
        int mItemId; // Reversed stream name
        String mFilename;
        short mReserved2;
        private long mLastModified;

        private static CatalogItem read(final DataInput pDataInput) throws IOException {
            CatalogItem item = new CatalogItem();
            item.mReserved1 = pDataInput.readInt();
            item.mItemId = pDataInput.readInt();

            item.mLastModified = CompoundDocument.toJavaTimeInMillis(pDataInput.readLong());

            char[] chars = new char[256];
            char ch;
            int len = 0;
            while ((ch = pDataInput.readChar()) != 0) {
                chars[len++] = ch;
            }

            String name = new String(chars, 0, len);
            item.mFilename = StringUtil.getLastElement(name, "\\");

            item.mReserved2 = pDataInput.readShort();
            return item;
        }

        public String getName() {
            return mFilename;
        }

        public int getItemId() {
            return mItemId;
        }

        public long lastModified() {
            return mLastModified;
        }

        @Override
        public String toString() {
            return String.format(
                    "%s: %d itemId: %d lastModified: %s fileName: %s %s",
                    getClass().getSimpleName(), mReserved1, mItemId, new Date(mLastModified), mFilename, mReserved2
            );
        }
    }
}