/*
Copyright(c) 2005 arton

Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
*/
package org.itscool.commons.util;

import java.util.*;
import java.util.regex.*;
import java.io.*;
import java.lang.reflect.*;

/**
 * 指定されたインターフェイスを実装したスタブのソースを生成する。
 *
 * @author arton
 */
public class StubGenerator {

    PrintWriter writer;
    int nest;
    boolean closed;
    Class klass;
    String targetName;
    String packageName;

    static final Pattern PPATTERN = Pattern.compile("^(\\[+)(\\w)$");
    static final Pattern TPATTERN = Pattern.compile("^(\\[+)L(.+?);$");
    static final Map TYPE_CONV;
    static {
        Map map = new HashMap();
        map.put("I", "int");
        map.put("Z", "boolean");
        map.put("B", "byte");
        map.put("C", "char");
        map.put("D", "double");
        map.put("F", "float");
        map.put("J", "long");
        map.put("S", "short");
        TYPE_CONV = Collections.unmodifiableMap(map);
    }

    /**
     * Stubジェネレータを生成する。
     * @param intf 作成対象のインターフェイス
     * @param target 出力対象のクラス（パッケージ名付き）
     */
    public StubGenerator(String intf, String target) throws Exception {
        this(intf);
        int last = target.lastIndexOf(".") + 1;
        targetName = target.substring(last);
        if (last > 1) {
            packageName = target.substring(0, last - 1);
        }
        writer = new PrintWriter(new BufferedWriter(new FileWriter(
                                           targetName + ".java")));
    }

    /**
     * Stubジェネレータを生成する。
     * 2引数のものと異なり標準出力へ出力する。クラス名は元のインターフェイス名
     * の先頭にStubを付加したものを利用する。
     * @param intf 作成対象のインターフェイス
     */
    public StubGenerator(String intf) throws Exception {
        klass = Class.forName(intf);
        writer = new PrintWriter(System.out);
        closed = true;
        int off = (intf.lastIndexOf("$") >= 0) 
            ? intf.lastIndexOf("$") : intf.lastIndexOf(".");
        targetName = "Stub" + intf.substring(off + 1);
    }

    /**
     * 利用したリソースを解放する。
     */
    public void close() throws IOException {
        writer.flush();
        if (!closed) {
            writer.close();
            closed = true;
        }
    }

    /**
     * ソースファイルを生成する。
     */
    public void generate() throws IOException {
        prepare();
        Method[] m = klass.getMethods();
        for (int i = 0; i < m.length; i++) {
            indent(); 
            print("public ");
            printType(m[i].getReturnType());
            print(" ");
            print(m[i].getName());
            print("(");
            printArgs(m[i].getParameterTypes());
            print(")");
            printExceptions(m[i].getExceptionTypes());
            printReturn(m[i].getReturnType());
            indent(); println("}");
        }
        last();
    }

    void printArgs(Class[] p) {
        for (int i = 0; i < p.length; i++) {
            if (i != 0) {
                print(", ");
            }
            printType(p[i]);
            print(" a" + i);
        }
    }

    void printExceptions(Class[] p) {
        for (int i = 0; i < p.length; i++) {
            if (i == 0) {
                println("");
                ++nest;
                indent();
                --nest;
                print(" throws ");
            } else {
                print(", ");
            }
            print(p[i].getName());
        }
        println(" {");
    }

    void printReturn(Class c) {
        if (c.getName().equals("void")) {
            return;
        }
        ++nest;
        indent(); print("return ");
        if (c.isPrimitive()) {
            if (c.getName().equals("boolean")) {
                println("false;");
            } else {
                print("("); printType(c); println(")0;");
            }
        } else {
            println("null;");
        }
        --nest;
    }

    void prepare() {
        if (packageName != null) {
            print("package ");
            print(packageName);
            println(";");
            println("");
        }
        println("/**");
        print(" * "); print(klass.getName());
        println("のスタブクラス。");
        println(" * @author StubGenerator by arton.");
        println(" */");
        print("public class "); print(targetName); 
        print(" implements "); print(klass.getName()); println(" {");
        ++nest;
        indent(); print("public "); print(targetName); println("() {");
        indent(); println("}");
    }

    void last() {
        println("}");
    }

    void printType(Class c) {
        if (!c.isArray()) {
            print(c.getName());
            return;
        }
        String nm = c.getName();
        
        Matcher m = PPATTERN.matcher(nm);
        if (m.find()) {
            print((String)TYPE_CONV.get(m.group(2)));
        } else {
            m = TPATTERN.matcher(nm);
            m.find();
            print(m.group(2));
        }
        for (int i = 0, n = m.group(1).length(); i < n; i++) {
            print("[]");
        }
    }

    void indent() {
        for (int i = 0; i < nest; i++) {
            writer.print("    ");
        }
    }

    void print(String string) {
        writer.print(string);
    }

    void println(String string) {
        writer.println(string);
    }

    /**
     * スタンドアローンで実行する。<br>
     * <code>呼び出し：java com.example.tool.StubGenerator itf [class]</code>
     * <ul>
     * <li>itf: 生成対象のインターフェイス</li>
     * <li>class: 出力クラス名
     *    （無指定時は標準出力へStub+インターフェイス名として出力）</li>
     * </ul>
     */
    public static void main(String[] args) throws Exception {
        if (args.length < 1 || args.length > 2) {
            System.err.println("usage: java com.example.tool.StubGenerator interface [class]");
            System.exit(1);
        }
        StubGenerator gen = (args.length == 1) ? new StubGenerator(args[0])
            : new StubGenerator(args[0], args[1]);
        gen.generate();
        gen.close();
    }
}
