/*
 * Copyright 2004-2012 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.mayaa.impl.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.net.URLConnection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Koji Suga (Gluegent, Inc.)
 */
public class IOUtil {

    private static final Log LOG = LogFactory.getLog(IOUtil.class);
    private static boolean _useURLCache = false;

    static {
        // 一応環境変数で設定できるように。基本的に有効にしない。
        String env = System.getProperty("org.seasar.mayaa.useURLCache");
        if (env != null && env.equalsIgnoreCase("true")) {
            _useURLCache = true;
        }
    }

    private IOUtil() {
        // no instantiation.
    }

    /**
     * InputStreamをcloseする。
     * 例外が発生した場合はログにINFOレベルで出力する。
     *
     * @param stream closeするInputStream
     */
    public static void close(InputStream stream) {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException ignore) {
                LOG.info(ignore.getMessage(), ignore);
            }
        }
    }

    /**
     * Readerをcloseする。
     * 例外が発生した場合はログにINFOレベルで出力する。
     *
     * @param reader closeするReader
     */
    public static void close(Reader reader) {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException ignore) {
                LOG.info(ignore.getMessage(), ignore);
            }
        }
    }

    /**
     * OutputStreamをcloseする。
     * 例外が発生した場合はログにINFOレベルで出力する。
     *
     * @param stream closeするOutputStream
     */
    public static void close(OutputStream stream) {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException ignore) {
                LOG.info(ignore.getMessage(), ignore);
            }
        }
    }

    /**
     * InputStreamから指定文字エンコーディングでStringを読み出して返す。
     * 読み出したあとはInputStreamをcloseする。
     *
     * @param is 読み出すInputStream
     * @param encoding 文字エンコーディング
     * @return InputStreamの内容
     * @throws RuntimeException 内部で発生した例外をラップしたもの
     */
    public static String readStream(InputStream is, String encoding) {
        try {
            Reader reader = new InputStreamReader(is, encoding);
            try {
                StringBuffer sb = new StringBuffer();
                char[] buffer = new char[1024];
                int readSize = -1;
                while ((readSize = reader.read(buffer)) > 0) {
                    sb.append(buffer, 0, readSize);
                }
                return sb.toString();
            } finally {
                is.close();
            }
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }

    /**
     * OutputStreamへ指定文字エンコーディングでStringを書き出す。
     * 書き出したあとはOutputStreamをcloseする。
     *
     * @param os 書き出すInputStream
     * @param value 出力する内容
     * @param encoding 文字エンコーディング
     * @throws RuntimeException 内部で発生した例外をラップしたもの
     */
    public static void writeStream(OutputStream os, String value, String encoding) {
        try {
            Writer writer = new OutputStreamWriter(os, encoding);
            try {
                writer.write(value);
                writer.flush();
            } finally {
                os.close();
            }
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }

    /**
     * URLからInputStreamを取得する。
     * 標準だとURLのキャッシュを使わない。
     * キャッシュを使わない場合、jarファイルをホールドしない代わりに
     * パフォーマンスに問題が出る場合がある。
     * urlがnullの場合はnullを返す。
     *
     * URLのキャッシュを使うようにするには、システムプロパティに
     * "org.seasar.mayaa.useURLCache=true" を設定すること。
     *
     * @param url 読み込むURL
     * @return InputStream
     */
    public static InputStream openStream(URL url) {
        if (url == null) {
            return null;
        }
        try {
            URLConnection connection = url.openConnection();
            // キャッシュを使うとjarファイルを掴んでしまう
            // TODO useURLCacheをエンジン設定できるようにする
            connection.setUseCaches(_useURLCache);
            return connection.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * URLがファイルプロトコルかどうか判定する。
     * urlがnullまたは不正な場合はfalseを返す。
     *
     * @param url 対象のURL
     * @return ファイルプロトコルならtrue
     */
    public static File getFile(URL url) {
        if (url != null) {
            if ("file".equalsIgnoreCase(url.getProtocol())) {
                return new File(url.toString().substring(5));
            }
        }
        return null;
    }

    /**
     * URLから最終更新時刻を取得する。
     * fileにのみ対応。それ以外はシステム時刻を返す。
     *
     * @param url 読み込むURL
     * @return InputStream
     */
    public static long getLastModified(URL url) {
        File file = getFile(url);
        if (file != null) {
            return file.lastModified();
        }
        return System.currentTimeMillis();
    }

    /**
     * カレントスレッドのContextClassLoaderを使ってリソースを読み込むInputStreamを返す。
     * リソースが見つからない場合、あるいは権限がない場合はnullを返す。
     * リソースのパスはクラスパスをルートとする絶対パス。
     *
     * @param name リソースの名前
     * @return InputStream
     */
    public static URL getResource(String name) {
        return getResource(name, Thread.currentThread().getContextClassLoader());
    }

    /**
     * カレントスレッドのContextClassLoaderを使ってリソースを読み込むInputStreamを返す。
     * リソースが見つからない場合、あるいは権限がない場合はnullを返す。
     * リソースのパスはクラスパスをルートとする絶対パス。
     *
     * @param name リソースの名前
     * @return InputStream
     */
    public static InputStream getResourceAsStream(String name) {
        return getResourceAsStream(name, Thread.currentThread().getContextClassLoader());
    }

    /**
     * クラスのあるパッケージを起点としてパスを解決する。
     * java.lang.Class#resolveName(String)
     *
     * @param neighbor 基準とするクラス
     * @param name リソースの名前
     * @return 解決したパス
     */
    protected static String resolveName(Class neighbor, String name) {
        if (name == null) {
            return name;
        }
        if (name.startsWith("/") == false) {
            Class c = neighbor;
            while (c.isArray()) {
                c = c.getComponentType();
            }
            String baseName = c.getName();
            int index = baseName.lastIndexOf('.');
            if (index != -1) {
                name = baseName.substring(0, index).replace('.', '/') + '/' + name;
            }
        } else {
            name = name.substring(1);
        }
        return name;
    }

    /**
     * neighborのClassLoaderを使ってリソースのURLを返す。
     * リソースが見つからない場合、あるいは権限がない場合はnullを返す。
     * リソースのパスはクラスパスをルートとする絶対パス。
     * neighborがnullの場合はカレントスレッドのContextClassLoaderを使う。
     *
     * @param name リソースの名前
     * @param neighbor リソースを探すための起点とするクラス
     * @return InputStream
     */
    public static URL getResource(String name, Class neighbor) {
        if (neighbor == null) {
            return getResource(name);
        }
        return getResource(resolveName(neighbor, name), neighbor.getClassLoader());
    }

    /**
     * neighborのClassLoaderを使ってリソースを読み込むInputStreamを返す。
     * リソースが見つからない場合、あるいは権限がない場合はnullを返す。
     * リソースのパスはneighborのパッケージを起点とする相対パス。
     * neighborがnullの場合はカレントスレッドのContextClassLoaderを使う。
     *
     * @param name リソースの名前
     * @param neighbor リソースを探すための起点とするクラス
     * @return InputStream
     */
    public static InputStream getResourceAsStream(String name, Class neighbor) {
        return openStream(getResource(name, neighbor));
    }

    /**
     * 指定したClassLoaderを使ってリソースのURLを返す。
     * リソースが見つからない場合、あるいは権限がない場合はnullを返す。
     * リソースのパスはクラスパスをルートとする絶対パス。
     * loaderがnullの場合はnullを返す。
     *
     * @param name リソースの名前
     * @param loader リソースを読み込むためのクラスローダー
     * @return InputStream
     */
    public static URL getResource(String name, ClassLoader loader) {
        if (loader == null) {
            return null;
        }
        return loader.getResource(name);
    }

    /**
     * 指定したClassLoaderを使ってリソースを読み込むInputStreamを返す。
     * リソースが見つからない場合、あるいは権限がない場合はnullを返す。
     * リソースのパスはクラスパスをルートとする絶対パス。
     * loaderがnullの場合はnullを返す。
     *
     * @param name リソースの名前
     * @param loader リソースを読み込むためのクラスローダー
     * @return InputStream
     */
    public static InputStream getResourceAsStream(String name, ClassLoader loader) {
        return openStream(getResource(name, loader));
    }

}
