/*******************************************************************************
 * Copyright (c) 2008 IGA Tosiki, NTT DATA BUSINESS BRAINS Corp.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IGA Tosiki (NTT DATA BUSINESS BRAINS Corp.) - initial API and implementation
 *******************************************************************************/
/*
 * blanco Framework
 * Copyright (C) 2008 NTT DATA BUSINESS BRAINS CORPORATION
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.properties.manifest;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import blanco.properties.BlancoPropertiesUtil;
import blanco.properties.manifest.valueobject.BlancoPropertiesManifest;
import blanco.properties.message.BlancoPropertiesMessage;

/**
 * マニフェスト情報 ([MANIFEST.MF]ファイル) を読み込むクラス。
 * 
 * @author IGA Tosiki
 */
public class BlancoPropertiesManifestReader {
    /**
     * メッセージオブジェクト。
     */
    private final BlancoPropertiesMessage fMsg = new BlancoPropertiesMessage();

    /**
     * 与えられた Manifestクラスから マニフェスト情報を読み込みます。
     * 
     * @param manifest
     *            Manifestクラスのインスタンス。
     * @return Manifestバリューオブジェクト。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public BlancoPropertiesManifest read(final Manifest manifest)
            throws IOException {
        final BlancoPropertiesManifest result = new BlancoPropertiesManifest();

        final Attributes attrs = manifest.getMainAttributes();
        if (attrs == null) {
            throw new IllegalArgumentException(fMsg.getMbprmr01(manifest
                    .toString()));
        }

        for (Iterator<Entry<Object, Object>> ite = attrs.entrySet().iterator(); ite
                .hasNext();) {
            final Entry<Object, Object> entry = ite.next();

            final Attributes.Name name = (Attributes.Name) entry.getKey();
            if ("Bundle-Version".equals(name.toString())) {
                result.setBundleVersion(entry.getValue().toString());
            }
            if ("Bundle-SymbolicName".equals(name.toString())) {
                // シンボル名には ;が含まれる場合があり、これを除去する。
                result.setBundleSymbolicName(getValueWithoutOption(entry
                        .getValue().toString()));
            }
            if ("Bundle-Name".equals(name.toString())) {
                result.setBundleName(entry.getValue().toString());
            }
            if ("Bundle-Vendor".equals(name.toString())) {
                result.setBundleVendor(entry.getValue().toString());
            }

            // TODO 必要に応じて、他のキーおよび値も読み込むべきです。
        }

        if (result.getBundleName() == null) {
            throw new IllegalArgumentException(fMsg.getMbprmr02());
        }
        if (result.getBundleSymbolicName() == null) {
            throw new IllegalArgumentException(fMsg.getMbprmr03());
        }
        if (result.getBundleVersion() == null) {
            throw new IllegalArgumentException(fMsg.getMbprmr04());
        }

        return result;
    }

    /**
     * 与えられた MANIFEST.MFのための入力ストリームから マニフェスト情報を読み込みます。
     * 
     * @param inStream
     *            MANIFEST.MFファイルへの入力ストリーム。
     * @return Manifestバリューオブジェクト。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    private BlancoPropertiesManifest read(final InputStream inStream)
            throws IOException {
        final Manifest manifest = new Manifest(inStream);
        return read(manifest);
    }

    /**
     * 指定されたプラグインディレクトリから MANIFEST.MF情報を読み取ります。
     * 
     * @param inputDir
     *            プラグインディレクトリ (内部に MANIFEST.MFファイルが含まれることが前提)
     * @return Manifestバリューオブジェクト。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public BlancoPropertiesManifest readPluginDirectory(final File inputDir)
            throws IOException {
        BlancoPropertiesManifest result = null;

        if (inputDir.exists() == false) {
            throw new IllegalArgumentException(fMsg.getMbprmr11(inputDir
                    .getAbsolutePath()));
        }
        if (inputDir.isDirectory() == false) {
            throw new IllegalArgumentException(fMsg.getMbprmr12(inputDir
                    .getAbsolutePath()));
        }

        final File[] files = inputDir.listFiles();
        if (files != null) {
            for (int index = 0; index < files.length; index++) {
                if (files[index].isDirectory()) {
                    if (files[index].getName().equals("META-INF")) {
                        // これだ！
                        final File[] filesMetaInf = files[index].listFiles();
                        if (filesMetaInf != null) {
                            for (int indexMeta = 0; indexMeta < filesMetaInf.length; indexMeta++) {
                                if (filesMetaInf[indexMeta].getName().equals(
                                        "MANIFEST.MF")) {
                                    // ヒット！
                                    final InputStream inStream = new BufferedInputStream(
                                            new FileInputStream(
                                                    filesMetaInf[indexMeta]));

                                    try {
                                        result = read(inStream);
                                    } catch (IllegalArgumentException ex) {
                                        result = readManifestFromTargetName(inputDir
                                                .getName());
                                    }
                                    inStream.close();
                                }
                            }
                        }
                    }
                } else {
                    // ディレクトリ以外は処理せず。
                }
            }
        }

        if (result == null) {
            // プラグインディレクトリからの読み取りで
            // MANIFEST.MFが見つからなかった場合には、ディレクトリ名をきっかけとした名称決定を行います。
            result = readManifestFromTargetName(inputDir.getName());
        }

        return result;
    }

    /**
     * 指定されたプラグインjarファイルから MANIFEST.MF情報を読み取ります。
     * 
     * @param inputFile
     *            プラグインjarファイル (内部に MANIFEST.MFファイルが含まれることが前提)
     * @return Manifestバリューオブジェクト。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public BlancoPropertiesManifest readPluginJar(final File inputFile)
            throws IOException {
        BlancoPropertiesManifest result = null;

        if (inputFile.exists() == false) {
            throw new IllegalArgumentException(fMsg.getMbprmr21(inputFile
                    .getAbsolutePath()));
        }
        if (inputFile.isFile() == false) {
            throw new IllegalArgumentException(fMsg.getMbprmr22(inputFile
                    .getAbsolutePath()));
        }

        final ZipInputStream jarInStream = new ZipInputStream(
                new BufferedInputStream(new FileInputStream(inputFile)));
        try {
            for (;;) {
                final ZipEntry entry = jarInStream.getNextEntry();
                if (entry == null) {
                    break;
                }

                if (entry.isDirectory()) {
                } else {
                    if (entry.getName().equals("META-INF/MANIFEST.MF")) {
                        try {
                            result = read(jarInStream);
                        } catch (IllegalArgumentException ex) {
                            result = readManifestFromTargetName(inputFile
                                    .getName());
                        }
                    }
                }
                jarInStream.closeEntry();
            }
        } finally {
            jarInStream.close();
        }

        if (result == null) {
            // プラグインjarからの読み取りで
            // MANIFEST.MFが見つからなかった場合には、ファイル名をきっかけとした名称決定を行います。
            result = readManifestFromTargetName(inputFile.getName());
        }

        return result;
    }

    /**
     * セミコロンによるオプションが もしあればこれを除去する。
     * 
     * @param value
     * @return
     */
    private String getValueWithoutOption(final String value) {
        final StringTokenizer token = new StringTokenizer(value, ";");
        // セミコロン区切りの場合には前後の空白を除去。
        return token.nextToken().trim();
    }

    /**
     * 与えられたディレクトリ名・ファイル名からターゲット名を導出します。
     * 
     * MANIFEST.MFファイルから情報をとることが出来なかった場合に、しかたなくこのメソッドを利用します。
     * 
     * @param targetName
     *            プラグインディレクトリ名またはプラグインjarファイル名。
     * @return マニフェスト情報。
     */
    private BlancoPropertiesManifest readManifestFromTargetName(
            String targetName) {
        if (targetName.endsWith(".jar")) {
            // MANIFEST.MFから名称を取得できなかった代替処理の際に、ターゲット名に .jar が含まれる場合にはこれを除去します。
            targetName = targetName.substring(0, targetName.length()
                    - ".jar".length());
        }

        final BlancoPropertiesManifest result = new BlancoPropertiesManifest();
        result.setBundleSymbolicName(BlancoPropertiesUtil
                .getBundleSymbolicName(targetName));
        result.setBundleVersion(BlancoPropertiesUtil
                .getBundleVersion(targetName));
        result.setBundleName(targetName);
        return result;
    }
}
