/*
 * SyetemUtil.java
 * 
 * Author: http://www.hrtdotnet.jp/
 * 
 */
package jp.hrtdotnet.java.util;

import java.util.Timer;
import java.util.TimerTask;


/**
 * 主にメモリチェックに関する処理を実装したシステム管理クラスです。
 * メモリ使用量のチェックを現在、もしくは定期的に行うことが可能で、チェック結果に応じた処理をさせることができます。
 * またメモリチェックの他、仕様メモリや利用可能メモリを取得することもできます。
 * <br><br>
 * 基本的に拡張することを考えて作られていませんが、定期的なメモリチェックを
 * させるためにはチェック後の処理部分をオーバーライドして独自の実装をさせる必要があります。
 * 
 * @version 2.0
 * @author hrtdotnet.jp
 * <br>http://www.hrtdotnet.jp/
 * @since 2.0
 */
public class SystemUtil {

    /** 最大メモリ使用量のチェックを行うステータスコードです。 */
    public final static int MAXIMIZE_USEDMEMORY_SIZE = 1;
    /** 最大メモリ使用率のチェックを行うステータスコードです。 */
    public final static int MAXIMIZE_USEDMEMORY_PERCENTAGE = 2;    

    /** 最大メモリ使用量です。 */
    protected long maximizeMemorySize = 0L;
    /** 最大メモリ使用率です。 */
    protected int maximizeMemoryPercentage = 0;

    // ここのメンバ変数は拡張したクラスに直接使わせない
    private int status = 0;
    private boolean maximizeUsedMemorySizeFlg = false;
    private boolean maximizeUsedMemoryPercentageFlg = false;    
    private Timer timer;
    
    /**
     * JavaVMが利用可能なメモリの上限値を取得します。
     * ただし常にJavaVMが上限値分のメモリ領域を確保しているわけではなく、
     * 実際の値は物理メモリや仮想メモリの空き具合にもよります。
     * 
     * @return メモリを確保し続けることで利用可能なメモリの上限値
     * @since 2.0
     */
    public static long getMaxMemory() {
        return Runtime.getRuntime().maxMemory();
    }

    /**
     * JavaVMが現在確保しているメモリ領域のうち利用可能なメモリサイズを取得します。
     * 
     * @return 現在利用できるメモリサイズ
     * @since 2.0
     */
    public static long getFreeMemory() {
        return Runtime.getRuntime().freeMemory();
    }

    /**
     * JavaVMが現在確保しているメモリ領域のサイズを取得します。
     * 
     * @return 現在確保しているメモリサイズ
     * @since 2.0
     */
    public static long getTotalMemory() {
        return Runtime.getRuntime().totalMemory();        
    }
 
    /**
     * JavaVMが現在利用しているメモリサイズを取得します。
     * 
     * @return 利用しているメモリサイズ
     * @since 2.0
     */
    public static long getUsedMemory() {
        return getTotalMemory() - getFreeMemory();
    }

    
    /**
     * メモリチェック用のオブジェクトを生成します。
     * チェックする項目のステータスコードやメモリの使用量や使用率の上限値を設定し、
     * チェックをかけることができます。なお設定値はコンストラクタ引数でのみ
     * 設定することができます。
     * <br><br>
     * ステータスコードを各項目のステータスコードの論理和を指定します。
     * ステータスコードは以下の通りです。
     * <ul>
     * <li>MAXIMIZE_USEDMEMORY_SIZE：メモリ使用量の上限値のチェック</li>
     * <li>MAXIMIZE_USEDMEMORY_PERCENTAGE：メモリ使用率の上限値のチェック</li>
     * </ul>
     * 例えばメモリの使用量と使用率の両方をチェックしたい場合は、
     * statusにMAXIMIZE_USEDMEMORY_SIZEとMAXIMIZE_USEDMEMORY_PERCENTAGEの和を指定します。
     * 
     * @param status チェックをする項目のステータスコード
     * @param maximizeMemorySize メモリ使用量の上限値
     * @param maximizeMemoryPercentage メモリ使用率の上限値
     * @since 2.0
     */
    public SystemUtil(int status, long maximizeMemorySize, int maximizeMemoryPercentage) {
        this.status = status;
        setStatus();
        this.maximizeMemorySize = maximizeMemorySize;
        if (maximizeUsedMemorySizeFlg) {
            if (maximizeMemorySize <= 0) {
                throw new IllegalArgumentException();
            }
        }
        this.maximizeMemoryPercentage = maximizeMemoryPercentage;
        if (maximizeUsedMemoryPercentageFlg) {
            if (maximizeMemoryPercentage <= 0) {
                throw new IllegalArgumentException();
            }
        }
    }
    
    /**
     * 設定情報を元にメモリのチェックをし、その結果を取得します。
     * 
     * @return メモリチェックで範囲内であればtrue、範囲外であればfalse
     * @since 2.0
     */
    public boolean check() {
        if(this.maximizeUsedMemorySizeFlg) {
            if (!usedMemorySizeCheck()) {
                return false;
            }
        }        
        if (this.maximizeUsedMemoryPercentageFlg) {
            if (!usedMemoryPercentageCheck()) {
                return false;
            }
        }
        return true;
    }

    /**
     * メモリの使用量が指定した上限値を超えていないかをチェックします。
     * 
     * @return メモリチェックで範囲内であればtrue、範囲外であればfalse
     * @since 2.0
     */
    public boolean usedMemorySizeCheck() {
        try {
            return usedMemorySizeCheck(maximizeMemorySize);
        } catch (IllegalArgumentException et) {
            throw new IllegalStateException("The maximizeMemorySize is illegal status.");
        }
    }
    

    /**
     * メモリの使用量が指定した上限値を超えていないかをチェックします。
     * 使用量と上限値が等しい場合は範囲内となります。
     * 
     * @param maximizeMemorySize メモリ使用量の上限値
     * @return メモリチェックで範囲内であればtrue、範囲外であればfalse
     * @since 2.0
     */
    public static boolean usedMemorySizeCheck(long maximizeMemorySize) {
        if(maximizeMemorySize <= 0) {
            throw new IllegalArgumentException("Memory-size <= 0.");
        }
        if(maximizeMemorySize < getUsedMemory()) {
            return false;
        }
        return true;
    }

    /**
     * メモリの使用率が指定した指定した上限値を超えていないかをチェックします。
     * 
     * @return メモリチェックで範囲内であればtrue、範囲外であればfalse
     * @since 2.0
     */
    public boolean usedMemoryPercentageCheck() {
        try {
            return usedMemoryPercentageCheck(maximizeMemoryPercentage);
        } catch (IllegalArgumentException et) {
            throw new IllegalStateException("The maximizeMemorySize is illegal status.");
        }        
    }

    /**
     * メモリの使用率が指定した指定した上限値を超えていないかをチェックします。
     * 
     * @param maximizeMemoryPercentage チェックするメモリ使用率の上限値
     * @return メモリチェックで範囲内であればtrue、範囲外であればfalse
     * @since 2.0
     */
    public static boolean usedMemoryPercentageCheck(int maximizeMemoryPercentage) {
        if(maximizeMemoryPercentage <= 0 || maximizeMemoryPercentage > 100) {
            throw new IllegalArgumentException("Memory-size <= 0 or Memory-size > 100.");
        }
        if(maximizeMemoryPercentage < (double)getUsedMemory() / getTotalMemory() * 100) {
            return false;
        }
        return true;
    }

    /**
     * メモリチェックのタイマーを開始します。
     * ただし同一オブジェクトのタイマーがすでに開始している場合はIllegalStateExceptionを返します。
     * またメモリチェックする間隔が0ミリ秒以下の場合はIllegalArgumentExceptionを返します。
     * 
     * @param period チェックするタイマーの間隔（ミリ秒数）
     * @throws IllegalArgumentException タイマーの間隔が 0 以下 (<= 0) の場合
     * @throws IllegalStateException
     *          タイマーがすでに開始している場合
     * @since 2.0
     */
    public synchronized void timerStart(long period) {
        if (period <= 0) {
            throw new IllegalArgumentException("Timer is equal to or less than 0 milliseconds.");
        }
        if (timer != null) {
            throw new IllegalStateException("Timer is starting now.");
        }
        TimerTask tt = new MemoryCheckTimerTask(this);
        timer = new Timer();
        timer.schedule(tt, 0, period);
    }
    

    /**
     * メモリチェックのタイマーを終了します。
     * ただし同一オブジェクトのタイマーが開始していない場合はIllegalStateExceptionを返します。
     * 
     * @throws IllegalStateException
     *          タイマーが開始していない場合
     * @since 2.0
     */
    public synchronized void timerCancel() {
        if (timer == null) {
            throw new IllegalStateException("Timer is not starting now");
        }
        timer.cancel();
        timer = null;
    }
    
    /**
     * タイマーが動作中（開始している）かどうかを判定します。
     * 
     * @return 動作中であればtrue、開始していなければfalse
     * @since 2.0
     */
    public synchronized boolean isRunTimer() {
        return timer == null ? false : true;
    }
    
    
    /**
     * チェック項目を設定するためのステータスを指定します。
     * 
     * @since 2.0
     */
    protected void setStatus() {
        int stat = status;
        if (stat % 2 == 1) {
            this.maximizeUsedMemorySizeFlg = true;
        }
        stat = stat / 2;
        if (stat % 2 == 1) {
            this.maximizeUsedMemoryPercentageFlg = true;
        }
    }
    
    /**
     * タイマーによる定期的なメモリチェックをさせた場合に
     * メモリの上限値が範囲内であればこのメソッドが処理されます。
     * <br><br>
     * 利用者で特定の処理をさせる場合はこのメソッドをオーバーライドする必要があります。
     * 
     * @since 2.0
     */
    protected void runCheckFalse() {
    }

    /**
     * タイマーによる定期的なメモリチェックをさせた場合に
     * メモリの上限値が範囲外であればこのメソッドが処理されます。
     * <br><br>
     * 利用者で特定の処理をさせる場合はこのメソッドをオーバーライドする必要があります。
     * 
     * @since 2.0
     */
    protected void runCheckTrue() {
    }

}

class MemoryCheckTimerTask extends TimerTask {

    SystemUtil m = null;
    
    MemoryCheckTimerTask(SystemUtil m) {        
        if (m == null) {
            throw new IllegalStateException();
        }
        this.m = m;
    }
    
    public void run() {
        if (m.check()) {
            m.runCheckTrue();
        } else {
            m.runCheckFalse();
        }
    }
    
}