/*
 * @(#)ImportEnv.java 1.3 05/05/25
 * 
 * Copyright (c) 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */
package pnuts.lang;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.*;
import gnu.trove.*;
import java.util.concurrent.*;
import java.lang.reflect.Modifier;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.pnuts.util.MemoryCache;
import org.pnuts.lang.CallableMethod;

import org.pnuts.util.* ;
import org.maachang.atomic.*;

class ImportEnv implements Serializable, Cloneable {

    private final static boolean DEBUG = false;

    static final long serialVersionUID = -755390125835215465L;

    /**
     * @serial
     */
    private Map/*<String,Import>*/ imports = new ConcurrentHashMap();

    /**
     * @serial
     */
    private Map/*<String,List<String>>*/ importedClasses = new ConcurrentHashMap();

    /**
     * @serial
     */
    private Map statics = new ConcurrentHashMap();

    /**
     * @serial
     */
    private List/*<Import>*/ pkgOrder = new AtomicSkipListSet();

    /**
     * @serial
     */
    private List/*<String>*/ classOrder = new AtomicSkipListSet();

    /**
     * @serial
     */
    private List/*<String>*/ staticsOrder = new AtomicSkipListSet();

    private transient AtomicObject<MemoryCache> cache = new AtomicObject<MemoryCache>( createCache() ) ;

    private transient AtomicObject<MemoryCache> failCache = new AtomicObject<MemoryCache>( createCache() ) ;

    private static MemoryCache createCache(){
        return new MemoryCache();
    }
    
    private void createCacheAll() {
        cache.get().clear() ;
        failCache.get().clear() ;
    }

    private Import getImport(String name) {
System.out.println( "### getImport:" + name ) ;
        Import imp = (Import) imports.get(name);
        if (imp == null) {
            imp = new Import(name);
            imports.put( name,imp ) ;
        }
        return imp;
    }
    
    /** pnuts内で、import宣言が行われた場合は、このメソッドが呼ばれる **/
    void addClass(String className) {
System.out.println( "### addClass:" + className ) ;
        int idx = className.lastIndexOf('.');
        if( idx == -1 ) {
            return ;
        }
        String name = className.substring(idx + 1);
        // クラス名直接設定の場合.
        /*
        char c = name.charAt( 0 ) ;
        if( c >= 'A' && c <= 'Z' ) {
            if( !defClass.containsKey( name ) ) {
                putDefault( name,className ) ;
            }
        }
        // クラス名直接でない場合.
        else {
            List vec = (List) importedClasses.get(name);
            if (vec == null) {
                importedClasses.put(name, (vec = new AtomicSkipListSet()));
            }
            ((AtomicSkipListSet)classOrder).toTop( className ) ;
            ((AtomicSkipListSet)vec).toTop( className ) ;
            createCacheAll() ;
        }
        */
        List vec = (List) importedClasses.get(name);
        if (vec == null) {
            importedClasses.put(name, (vec = new AtomicSkipListSet()));
        }
        ((AtomicSkipListSet)classOrder).toTop( className ) ;
        ((AtomicSkipListSet)vec).toTop( className ) ;
        createCacheAll() ;
    }

    void addPackage(String pkgname) {
System.out.println( "### addPackage:" + pkgname ) ;
        Import imp = (Import) imports.get(pkgname);
        if (imp == null) {
            imp = getImport(pkgname);
        }
        ((AtomicSkipListSet)pkgOrder).toTop( imp ) ;
        createCacheAll() ;
    }

    void addStaticMembers(String name, boolean wildcard, Context context) {
System.out.println( "### addStaticMembers:" + name + "," + wildcard ) ;
        String arg = wildcard ? name + ".*" : name;
        ((AtomicSkipListSet)staticsOrder).toTop( arg ) ;

        try {
            if (wildcard) {
                Class cls = Pnuts.loadClass(name, context);
                Field[] fields = cls.getFields();
                for (int i = 0; i < fields.length; i++) {
                    Field f = fields[i];
                    if (Modifier.isStatic(f.getModifiers())) {
                        statics.put(f.getName(), f.get(null));
                    }
                }
                Set names = new THashSet();
                Method[] methods = cls.getMethods();
                for (int i = 0; i < methods.length; i++) {
                    Method m = methods[i];
                    if (Modifier.isStatic(m.getModifiers())) {
                        names.add(m.getName());
                    }
                }
                for (Iterator it = names.iterator(); it.hasNext();) {
                    String memberName = (String) it.next();
                    statics.put(memberName, new CallableMethod(cls, memberName));
                }
            } else {
                int idx = name.lastIndexOf('.');
                if (idx > 0) {
                    String className = name.substring(0, idx);
                    final String memberName = name.substring(idx + 1);
                    final Class cls = Pnuts.loadClass(className, context);
                    try {
                        Field f = cls.getField(memberName);
                        if (Modifier.isStatic(f.getModifiers())) {
                            statics.put(memberName, f.get(null));
                            return;
                        }
                    } catch (NoSuchFieldException e) {
                    }
                    statics.put(memberName, new CallableMethod(cls, memberName));
                }
            }
            createCacheAll() ;

        } catch (ClassNotFoundException e1) {
            throw new PnutsException(e1, context);
        } catch (IllegalAccessException e2) {
            throw new PnutsException(e2, context);
        }
    }
    
    /**
     * よく利用する略称クラス名群.
     */
    private static final Map<String,Class> defClass = new HashMap<String,Class>() ;
    static {
        defClass.put( "StrBuf",org.maachang.seabass.mod.object.StrBuf.class ) ;
        defClass.put( "JSAdapter",org.maachang.seabass.mod.object.JSAdapter.class ) ;
        defClass.put( "JDate",org.maachang.JDate.class ) ;
        defClass.put( "EasyDate",org.maachang.EasyDate.class ) ;
        defClass.put( "Boolean",java.lang.Boolean.class ) ;
        defClass.put( "Byte",java.lang.Byte.class ) ;
        defClass.put( "Character",java.lang.Character.class ) ;
        defClass.put( "Short",java.lang.Short.class ) ;
        defClass.put( "Integer",java.lang.Integer.class ) ;
        defClass.put( "Long",java.lang.Long.class ) ;
        defClass.put( "Float",java.lang.Float.class ) ;
        defClass.put( "Double",java.lang.Double.class ) ;
        defClass.put( "BigInteger",java.math.BigInteger.class ) ;
        defClass.put( "BigDecimal",java.math.BigDecimal.class ) ;
        defClass.put( "String",java.lang.String.class ) ;
        defClass.put( "Date",java.util.Date.class ) ;
        defClass.put( "StringBuffer",java.lang.StringBuilder.class ) ;
        defClass.put( "StringBuilder",java.lang.StringBuilder.class ) ;
        defClass.put( "Map",java.util.Map.class ) ;
        defClass.put( "List",java.util.List.class ) ;
        defClass.put( "Exception",java.lang.Exception.class ) ;
        defClass.put( "IllegalArgumentException",java.lang.IllegalArgumentException.class ) ;
        defClass.put( "IOException",java.io.IOException.class ) ;
        defClass.put( "Error",java.lang.Error.class ) ;
        defClass.put( "Throwable",java.lang.Throwable.class ) ;
    }
    
    /** デフォルトクラスに追加 **/
    void putDefault( String name,Class c ) {
        if( name != null && c != null ) {
            defClass.put( name,c ) ;
        }
    }
    
    /** デフォルトクラスに追加 **/
    void putDefault( String name,String clazz ) {
        if( name != null && clazz != null ) {
            try {
                defClass.put( name,SearchJavaInfo.getClass( clazz ) ) ;
            } catch( Exception e ) {
            }
        }
    }
    
    Object get(String sym, Context context) {
        Object ret = defClass.get( sym ) ;
        if( ret == null ) {
            ret = _get( sym,context ) ;
        }
        return ret ;
    }
    
    /** パッケージ指定された内容から検索. **/
    synchronized Object _get(String sym, Context context) {
        Object v = cache.get().get(sym);
        if (v != null) {
            return v;
        }
        List vec = (List) importedClasses.get(sym);
        if (vec != null) {
            int size = vec.size();
            for (int i = size - 1; i >= 0; i--) {
                try {
                    return Pnuts.loadClass((String) vec.get(i), context);
                } catch (ClassNotFoundException cnf) {
                }
            }
        }
        int symlen = sym.length();
        StringBuilder buf = new StringBuilder(sym);
        boolean lowercase = !Character.isUpperCase(sym.charAt(0));
        
        int size = pkgOrder.size();
        for (int i = size - 1; i >= 0; i--) {
            Import imp = (Import) pkgOrder.get(i);
            buf.setLength(symlen);
            String name = imp.getName();
            if (lowercase && name.startsWith("java.")) {
                continue;
            }
            buf.append(name);
            String key = buf.toString();
            if (failCache.get().get(key) != null) {
                continue;
            }
            Class value = imp.get(sym, context);
            if (value != null) {
                cache.get().put(sym, value);
                return value;
            } else {
                failCache.get().put(key, key);
            }
        }
        Object value = statics.get(sym);
        if (value != null){
            cache.get().put(sym, value);
            return value;
        }
        return null;
    }

    String[] list() {
        String ret[] = new String[pkgOrder.size() + classOrder.size() + staticsOrder.size()];
        int i = ret.length - 1;
        for (Iterator it = staticsOrder.iterator(); it.hasNext();) {
            ret[i--] = "static " + (String) it.next();
        }
        for (Iterator it = pkgOrder.iterator(); it.hasNext();) {
            String name = ((Import) it.next()).getName();
            if ("".equals(name)) {
                ret[i--] = "*";
            } else {
                ret[i--] = name + ".*";
            }
        }
        for (Iterator it = classOrder.iterator(); it.hasNext();) {
            ret[i--] = (String) it.next();
        }
        return ret;
    }

    void reset() {
        createCacheAll() ;
        int len = pkgOrder.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            Import imp = (Import)pkgOrder.get( i ) ;
            if( imp != null ) {
                imp.reset() ;
            }
        }
    }

    public Object clone() {
        try {
            ImportEnv c = (ImportEnv) super.clone();
            c.imports = new ConcurrentHashMap( imports ) ;
            c.importedClasses = new ConcurrentHashMap( importedClasses ) ;
            c.pkgOrder = (List)((AtomicSkipListSet)pkgOrder).clone();
            c.classOrder = (List)((AtomicSkipListSet)classOrder).clone();
            c.statics = new ConcurrentHashMap( statics ) ;
            c.staticsOrder = (List)((AtomicSkipListSet)staticsOrder).clone();
            c.reset();
            return c;
        } catch (Throwable t) {
            throw new InternalError();
        }
    }

    private void readObject(ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        cache = new AtomicObject<MemoryCache>() ;
        failCache = new AtomicObject<MemoryCache>() ;
        createCacheAll() ;
    }
}
