package railo.runtime.type.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;

import org.apache.axis.AxisFault;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import railo.commons.digest.MD5;
import railo.commons.io.IOUtil;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.ClassUtil;
import railo.commons.lang.PhysicalClassLoader;
import railo.commons.lang.StringUtil;
import railo.commons.lang.types.RefBoolean;
import railo.runtime.Component;
import railo.runtime.ComponentPro;
import railo.runtime.ComponentWrap;
import railo.runtime.Mapping;
import railo.runtime.Page;
import railo.runtime.PageContext;
import railo.runtime.PageContextImpl;
import railo.runtime.PageSource;
import railo.runtime.PageSourceImpl;
import railo.runtime.component.Property;
import railo.runtime.config.Config;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.net.rpc.AxisCaster;
import railo.runtime.net.rpc.Pojo;
import railo.runtime.net.rpc.server.ComponentController;
import railo.runtime.net.rpc.server.RPCServer;
import railo.runtime.op.Caster;
import railo.runtime.type.Array;
import railo.runtime.type.ArrayImpl;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.FunctionArgument;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.UDF;
import railo.runtime.type.UDFPropertiesImpl;
import railo.runtime.type.cfc.ComponentAccess;
import railo.transformer.bytecode.BytecodeContext;
import railo.transformer.bytecode.literal.LitString;
import railo.transformer.bytecode.util.ASMProperty;
import railo.transformer.bytecode.util.ASMUtil;
import railo.transformer.bytecode.util.Types;
import railo.transformer.bytecode.visitor.ArrayVisitor;
// TODO doc
public final class ComponentUtil {
	

	private final static Method CONSTRUCTOR_OBJECT = Method.getMethod("void <init> ()");
	private static final Type COMPONENT_CONTROLLER = Type.getType(ComponentController.class); 
	private static final Method INVOKE = new Method("invoke",Types.OBJECT,new Type[]{Types.STRING,Types.OBJECT_ARRAY});
	//private static final Method INVOKE_PROPERTY = new Method("invoke",Types.OBJECT,new Type[]{Types.STRING,Types.OBJECT_ARRAY});
	
    /**
     * generate a ComponentJavaAccess (CJA) class from a component
     * a CJA is a dynamic genarted java class that has all method defined inside a component as java methods.
	 * 
	 * This is used to generated server side Webservices.
	 * @param component
     * @param isNew 
     * @return
     * @throws PageException
     */
	public static Class getComponentJavaAccess(PageContext pc,ComponentAccess component, RefBoolean isNew,boolean create,boolean writeLog, boolean supressWSbeforeArg) throws PageException {
    	isNew.setValue(false);
    	String classNameOriginal=component.getPageSource().getFullClassName();
    	String className=getClassname(component).concat("_wrap");
    	String real=className.replace('.','/');
    	String realOriginal=classNameOriginal.replace('.','/');
    	Mapping mapping = component.getPageSource().getMapping();
		PhysicalClassLoader cl=null;
		try {
			cl = (PhysicalClassLoader) ((PageContextImpl)pc).getRPCClassLoader(false);
		} catch (IOException e) {
			throw Caster.toPageException(e);
		}
		Resource classFile = cl.getDirectory().getRealResource(real.concat(".class"));
		Resource classFileOriginal = mapping.getClassRootDirectory().getRealResource(realOriginal.concat(".class"));
		
	// LOAD CLASS
    	//print.out(className);
		// check last Mod
		if(classFile.lastModified()>=classFileOriginal.lastModified()) {
			try {
				Class clazz=cl.loadClass(className);
				if(clazz!=null && !hasChangesOfChildren(classFile.lastModified(),clazz))return registerTypeMapping(clazz);
			}
			catch(Throwable t){}
		}
		if(!create) return null;
		isNew.setValue(true);
    	//print.out("new");
    // CREATE CLASS	
		ClassWriter cw = ASMUtil.getClassWriter();
        //ClassWriter cw = new ClassWriter(true);
    	cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC, real, null, "java/lang/Object", null);

    	//GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC,Page.STATIC_CONSTRUCTOR,null,null,cw);
		BytecodeContext statConstr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.STATIC_CONSTRUCTOR);

    	///ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC,Page.CONSTRUCTOR,null,null,cw);
		BytecodeContext constr = null;//new BytecodeContext(null,null,null,cw,real,ga,Page.CONSTRUCTOR);
		
    	
   	// field component
    	//FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE, "c", "Lrailo/runtime/ComponentImpl;", null, null);
    	//fv.visitEnd();
    	
    	java.util.List<LitString> _keys=new ArrayList<LitString>();
    
        // remote methods
        Collection.Key[] keys = component.keys(Component.ACCESS_REMOTE);
        int max;
        for(int i=0;i<keys.length;i++){
        	max=-1;
        	while((max=createMethod(statConstr,constr,_keys,cw,real,component.get(keys[i]),max, writeLog,supressWSbeforeArg))!=-1){
        		break;// for overload remove this
        	}
        }
        
        // Constructor
        GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC,CONSTRUCTOR_OBJECT,null,null,cw);
		adapter.loadThis();
        adapter.invokeConstructor(Types.OBJECT, CONSTRUCTOR_OBJECT);
        railo.transformer.bytecode.Page.registerFields(new BytecodeContext(null,statConstr,constr,getPage(statConstr,constr),_keys,cw,real,adapter,CONSTRUCTOR_OBJECT,writeLog,supressWSbeforeArg), _keys);
        adapter.returnValue();
        adapter.endMethod();
        
        
        cw.visitEnd();
        byte[] barr = cw.toByteArray();
    	
        try {
        	ResourceUtil.touch(classFile);
	        IOUtil.copy(new ByteArrayInputStream(barr), classFile,true);
	        
	        cl = (PhysicalClassLoader) ((PageContextImpl)pc).getRPCClassLoader(true);
	        
	        return registerTypeMapping(cl.loadClass(className, barr));
        }
        catch(Throwable t) {
        	throw Caster.toPageException(t);
        }
    }

    private static railo.transformer.bytecode.Page getPage(BytecodeContext bc1, BytecodeContext bc2) {
    	railo.transformer.bytecode.Page page=null;
    	if(bc1!=null)page=bc1.getPage();
    	if(page==null && bc2!=null)page=bc2.getPage();
    	return page;
	}

	/**
	 * check if one of the children is changed
	 * @param component
	 * @param clazz
	 * @return return true if children has changed
	 */
	private static boolean hasChangesOfChildren(long last, Class clazz) {

		boolean b= hasChangesOfChildren(last,ThreadLocalPageContext.get(),clazz);
		return b;
	}

	/**
	 * check if one of the children is changed
	 * @param component
	 * @param pc
	 * @param clazz
	 * @return return true if children has changed
	 */
	private static boolean hasChangesOfChildren(long last,PageContext pc, Class clazz) {

    	java.lang.reflect.Method[] methods = clazz.getMethods();
    	java.lang.reflect.Method method;
    	Class[] params;
    	for(int i=0;i<methods.length;i++){
    		method=methods[i];
    		if(method.getDeclaringClass()==clazz){
    			if(_hasChangesOfChildren(pc,   last,method.getReturnType())) return true;
    			params = method.getParameterTypes();
    			for(int y=0;y<params.length;y++){
    				if(_hasChangesOfChildren(pc,  last, params[y])) return true;
    			}
    		}
    	}
		return false;
	}

	private static boolean _hasChangesOfChildren(PageContext pc, long last, Class clazz) {
		clazz=ClassUtil.toComponentType(clazz);
		java.lang.reflect.Method m = getComplexTypeMethod(clazz);
		if(m==null) return false;
		try {
			String path=Caster.toString(m.invoke(null, new Object[0]));
			Resource res = ResourceUtil.toResourceExisting(pc, path);
			if(last<res.lastModified()) {
				return true;
			}
		} 
		catch (Exception e) {
			return true;
		}
		// possible that a child of the Cmplex Object is also a complex object
		return hasChangesOfChildren(last, pc, clazz);
	}

	private static boolean isComplexType(Class clazz) {
		return getComplexTypeMethod(clazz)!=null;
		
	}
	private static java.lang.reflect.Method getComplexTypeMethod(Class clazz) {
		try {
			return clazz.getMethod("_srcName", new Class[0]);
		} 
		catch (Exception e) {
			return null;
		}
	}

	/**
     * search in methods of a class for complex types
     * @param clazz
     * @return
     */
    private static Class registerTypeMapping(Class clazz) throws AxisFault {
    	PageContext pc = ThreadLocalPageContext.get();
    	RPCServer server=RPCServer.getInstance(pc.getId(),pc.getServletContext());
		return registerTypeMapping(server, clazz);
    }
    /**
     * search in methods of a class for complex types
     * @param server
     * @param clazz
     * @return
     */
    private static Class registerTypeMapping(RPCServer server, Class clazz) {
    	java.lang.reflect.Method[] methods = clazz.getMethods();
    	java.lang.reflect.Method method;
    	Class[] params;
    	for(int i=0;i<methods.length;i++){
    		method=methods[i];
    		if(method.getDeclaringClass()==clazz){
    			_registerTypeMapping(server, method.getReturnType());
    			params = method.getParameterTypes();
    			for(int y=0;y<params.length;y++){
    				_registerTypeMapping(server, params[y]);
    			}
    		}
    	}
    	return clazz;
	}

	/**
	 * register ComplexType
	 * @param server
	 * @param clazz
	 */
	private static void _registerTypeMapping(RPCServer server, Class clazz) {
		if(clazz==null) return;
		
		if(!isComplexType(clazz)) {
			if(clazz.isArray()) {
				_registerTypeMapping(server, clazz.getComponentType());
			}
			return;
		}
		server.registerTypeMapping(clazz);
		registerTypeMapping(server,clazz);
	}

	public static String getClassname(Component component) {
    	PageSource ps = component.getPageSource();
    	String path=ps.getDisplayPath();// Must remove webroot
    	Config config = ps.getMapping().getConfig();
    	String root = config.getRootDirectory().getAbsolutePath();
    	if(path.startsWith(root))
    		path=path.substring(root.length());

    	path=path.replace('\\', '/').toLowerCase();
    	path=ListUtil.trim(path, "/");
    	String[] arr = ListUtil.listToStringArray(path, '/');
    	
    	StringBuffer rtn=new StringBuffer();
    	for(int i=0;i<arr.length;i++) {
    		if(i+1==arr.length) {
    			rtn.append(StringUtil.toVariableName(StringUtil.replaceLast(arr[i],".cfc","")));
    		}
    		else {
    			rtn.append(StringUtil.toVariableName(arr[i]));
    			rtn.append('.');
    		}
    	}
    	return rtn.toString();
	}

	/*
	 * includes the application context javasettings 
	 * @param pc
	 * @param className
	 * @param properties
	 * @return
	 * @throws PageException
	 */
	public static Object getClientComponentPropertiesObject(PageContext pc, String className, ASMProperty[] properties) throws PageException {
		try {
			return _getClientComponentPropertiesObject(pc,pc.getConfig(), className, properties);
		} catch (Exception e) {
			throw Caster.toPageException(e);
		}
	}
	/*
	 * does not include the application context javasettings 
	 * @param pc
	 * @param className
	 * @param properties
	 * @return
	 * @throws PageException
	 */
	public static Object getClientComponentPropertiesObject(Config config, String className, ASMProperty[] properties) throws PageException {
		try {
			return _getClientComponentPropertiesObject(null,config, className, properties);
		} catch (Exception e) {
			throw Caster.toPageException(e);
		}
	}
	

    
    private static Object _getClientComponentPropertiesObject(PageContext pc, Config secondChanceConfig, String className, ASMProperty[] properties) throws PageException, IOException, ClassNotFoundException {
    	String real=className.replace('.','/');
    	
		PhysicalClassLoader cl;
    	if(pc==null)cl = (PhysicalClassLoader)secondChanceConfig.getRPCClassLoader(false);
    	else cl = (PhysicalClassLoader)((PageContextImpl)pc).getRPCClassLoader(false);
		
		Resource rootDir = cl.getDirectory();
		Resource classFile = rootDir.getRealResource(real.concat(".class"));
		
		if(classFile.exists()) {
			try {
				Class clazz = cl.loadClass(className);
				Field field = clazz.getField("_md5_");
				if(ASMUtil.createMD5(properties).equals(field.get(null))){
				//if(equalInterface(properties,clazz)) {
					return ClassUtil.loadInstance(clazz);
				}
			}
			catch(Exception e) {
				
			}
		}
		// create file
		byte[] barr = ASMUtil.createPojo(real, properties,Object.class,new Class[]{Pojo.class},null);
    	boolean exist=classFile.exists();
		ResourceUtil.touch(classFile);
    	IOUtil.copy(new ByteArrayInputStream(barr), classFile,true);
    	
    	if(pc==null)cl = (PhysicalClassLoader)secondChanceConfig.getRPCClassLoader(exist);
    	else cl = (PhysicalClassLoader)((PageContextImpl)pc).getRPCClassLoader(exist);
    	
		return ClassUtil.loadInstance(cl.loadClass(className));

	}

	
    
	public static Class getServerComponentPropertiesClass(PageContext pc,Component component) throws PageException {
		try {
	    	return _getServerComponentPropertiesClass(pc,component);
		}
    	catch (Exception e) {
			throw Caster.toPageException(e);
		}
    }
    
    private static Class _getServerComponentPropertiesClass(PageContext pc,Component component) throws PageException, IOException, ClassNotFoundException {
    	String className=getClassname(component);//StringUtil.replaceLast(classNameOriginal,"$cfc","");
    	String real=className.replace('.','/');

    	Mapping mapping = component.getPageSource().getMapping();
		PhysicalClassLoader cl = (PhysicalClassLoader)((PageContextImpl)pc).getRPCClassLoader(false);
		
		Resource classFile = cl.getDirectory().getRealResource(real.concat(".class"));
		
    	String classNameOriginal=component.getPageSource().getFullClassName();
    	String realOriginal=classNameOriginal.replace('.','/');
		Resource classFileOriginal = mapping.getClassRootDirectory().getRealResource(realOriginal.concat(".class"));

		// load existing class
		if(classFile.lastModified()>=classFileOriginal.lastModified()) {
			try {
				Class clazz=cl.loadClass(className);
				if(clazz!=null && !hasChangesOfChildren(classFile.lastModified(), clazz))return clazz;//ClassUtil.loadInstance(clazz);
			}
			catch(Throwable t){}
		}

		// create file
		byte[] barr = ASMUtil.createPojo(real, ASMUtil.toASMProperties(
				ComponentUtil.getProperties(component, false, true, false, false)),Object.class,new Class[]{Pojo.class},component.getPageSource().getDisplayPath());
    	ResourceUtil.touch(classFile);
    	IOUtil.copy(new ByteArrayInputStream(barr), classFile,true);
    	cl = (PhysicalClassLoader)((PageContextImpl)pc).getRPCClassLoader(true);
		return cl.loadClass(className); //ClassUtil.loadInstance(cl.loadClass(className));
    }

	private static int createMethod(BytecodeContext statConstr,BytecodeContext constr, java.util.List<LitString> keys,ClassWriter cw,String className, Object member,int max,boolean writeLog, boolean supressWSbeforeArg) throws PageException {
		
		boolean hasOptionalArgs=false;
		
    	if(member instanceof UDF) {
    		UDF udf = (UDF) member;
    		FunctionArgument[] args = udf.getFunctionArguments();
    		Type[] types=new Type[max<0?args.length:max];
    		for(int y=0;y<types.length;y++){
    			types[y]=toType(args[y].getTypeAsString(),true);//Type.getType(Caster.cfTypeToClass(args[y].getTypeAsString()));
    			if(!args[y].isRequired())hasOptionalArgs=true;
    		}
    		Type rtnType=toType(udf.getReturnTypeAsString(),true);
            Method method = new Method(
            		udf.getFunctionName(),
            		rtnType,
        			types
            		);
            GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL , method, null, null, cw);
            BytecodeContext bc = new BytecodeContext(null,statConstr,constr,getPage(statConstr,constr),keys,cw,className,adapter,method,writeLog,supressWSbeforeArg);
            Label start=adapter.newLabel();
            adapter.visitLabel(start);
            
       //ComponentController.invoke(name, args);
            // name
            adapter.push(udf.getFunctionName());
            
            // args
            ArrayVisitor av=new ArrayVisitor();
            av.visitBegin(adapter,Types.OBJECT,types.length);
            for(int y=0;y<types.length;y++){
    			av.visitBeginItem(adapter, y);
    				adapter.loadArg(y);
    			av.visitEndItem(bc.getAdapter());
            }
            av.visitEnd();
            adapter.invokeStatic(COMPONENT_CONTROLLER, INVOKE);
            adapter.checkCast(rtnType);
            
            //ASMConstants.NULL(adapter);
            adapter.returnValue();
            Label end=adapter.newLabel();
            adapter.visitLabel(end);
            
            for(int y=0;y<types.length;y++){
    			adapter.visitLocalVariable(args[y].getName().getString(), types[y].getDescriptor(), null, start, end, y+1);
            }
            adapter.endMethod();
            
            if(hasOptionalArgs) {
            	if(max==-1)max=args.length-1;
            	else max--;
            	return max;
            }
    	}
    	return -1;
	}



	private static Type toType(String cfType, boolean axistype) throws PageException {
		Class clazz=Caster.cfTypeToClass(cfType);
		if(axistype)clazz=AxisCaster.toAxisTypeClass(clazz);
		return Type.getType(clazz);
		
	}



	public static String md5(Component c) throws IOException, ExpressionException {
		return md5(ComponentWrap.toComponentWrap(Component.ACCESS_PRIVATE,c));
	}
	public static String md5(ComponentWrap cw) throws IOException {
		Key[] keys = cw.keys();
		Arrays.sort(keys);
		
		StringBuffer _interface=new StringBuffer();
		
		Object member;
        UDF udf;
        FunctionArgument[] args;
        FunctionArgument arg;
        for(int y=0;y<keys.length;y++) {
        	member = cw.get(keys[y],null);
        	if(member instanceof UDF) {
        		udf=(UDF) member;
        		//print.out(udf.);
        		_interface.append(udf.getAccess());
        		_interface.append(udf.getOutput());
        		_interface.append(udf.getFunctionName());
        		_interface.append(udf.getReturnTypeAsString());
        		args = udf.getFunctionArguments();
        		for(int i=0;i<args.length;i++){
        			arg=args[i];
            		_interface.append(arg.isRequired());
            		_interface.append(arg.getName());
            		_interface.append(arg.getTypeAsString());
        		}
        	}
        }
		return  MD5.getDigestAsString(_interface.toString().toLowerCase());
	}
	

    /**
     * cast a strong access definition to the int type
     * @param access access type
     * @return int access type
     * @throws ExpressionException
     */
	public static int toIntAccess(String access) throws ExpressionException {
        access=StringUtil.toLowerCase(access.trim());
        if(access.equals("package"))return Component.ACCESS_PACKAGE;
        else if(access.equals("private"))return Component.ACCESS_PRIVATE;
        else if(access.equals("public"))return Component.ACCESS_PUBLIC;
        else if(access.equals("remote"))return Component.ACCESS_REMOTE;
        throw new ExpressionException("invalid access type ["+access+"], access types are remote, public, package, private");
        
    }
	
	public static int toIntAccess(String access, int defaultValue) {
        access=StringUtil.toLowerCase(access.trim());
        if(access.equals("package"))return Component.ACCESS_PACKAGE;
        else if(access.equals("private"))return Component.ACCESS_PRIVATE;
        else if(access.equals("public"))return Component.ACCESS_PUBLIC;
        else if(access.equals("remote"))return Component.ACCESS_REMOTE;
        return defaultValue;
    }
    
    /**
     * cast int type to string type
     * @param access
     * @return String access type
     * @throws ExpressionException
     */
    public static String toStringAccess(int access) throws ExpressionException {
        String res = toStringAccess(access,null);
        if(res!=null) return res; 
        throw new ExpressionException("invalid access type ["+access+"], access types are Component.ACCESS_PACKAGE, Component.ACCESS_PRIVATE, Component.ACCESS_PUBLIC, Component.ACCESS_REMOTE");
    }
    
    public static String toStringAccess(int access,String defaultValue)  {
        switch(access) {
            case Component.ACCESS_PACKAGE:      return "package";
            case Component.ACCESS_PRIVATE:      return "private";
            case Component.ACCESS_PUBLIC:       return "public";
            case Component.ACCESS_REMOTE:       return "remote";
        }
        return defaultValue;
    }

	public static ExpressionException notFunction(Component c,Collection.Key key, Object member,int access) {
		if(member==null) {
			String strAccess = toStringAccess(access,"");
			
			Collection.Key[] other;
			if(c instanceof ComponentAccess)
				other=((ComponentAccess)c).keys(access);
			else 
				other=CollectionUtil.keys(c);
			
			if(other.length==0)
				return new ExpressionException(
						"component ["+c.getCallName()+"] has no "+strAccess+" function with name ["+key+"]");
			
			return new ExpressionException(
					"component ["+c.getCallName()+"] has no "+strAccess+" function with name ["+key+"]",
					"accessible functions are ["+ListUtil.arrayToList(other,",")+"]");
		}
		return new ExpressionException("member ["+key+"] of component ["+c.getCallName()+"] is not a function", "Member is of type ["+Caster.toTypeName(member)+"]");
	}

	public static Property[] getProperties(Component c,boolean onlyPeristent, boolean includeBaseProperties, boolean preferBaseProperties, boolean inheritedMappedSuperClassOnly) {
		if(c instanceof ComponentPro)
			return ((ComponentPro)c).getProperties(onlyPeristent, includeBaseProperties,preferBaseProperties,preferBaseProperties);
		return c.getProperties(onlyPeristent);
	}

	public static ComponentAccess toComponentAccess(Component comp) throws ExpressionException {
		ComponentAccess ca = toComponentAccess(comp, null);
		if(ca!=null) return ca;
		throw new ExpressionException("can't cast class ["+Caster.toClassName(comp)+"] to a class of type ComponentAccess");
	}

	public static ComponentAccess toComponentAccess(Component comp, ComponentAccess defaultValue) {
		if(comp instanceof ComponentAccess) return (ComponentAccess) comp;
		if(comp instanceof ComponentWrap) return ((ComponentWrap) comp).getComponentAccess();
		return defaultValue;
	}
	
	
	
	public static Component toComponent(Object obj) throws ExpressionException {
		if(obj instanceof Component) return (Component) obj;
		throw new ExpressionException("can't cast class ["+Caster.toClassName(obj)+"] to a class of type Component");
	}
	


	public static PageSource getPageSource(Component cfc) {
		// TODO Auto-generated method stub
		try {
			return toComponent(cfc).getPageSource();
		} catch (ExpressionException e) {
			return null;
		}
	}

	public static ComponentAccess getActiveComponent(PageContext pc, ComponentAccess current) {
		if(pc.getActiveComponent()==null) return current; 
		if(pc.getActiveUDF()!=null && (pc.getActiveComponent()).getPageSource()==(pc.getActiveUDF().getOwnerComponent()).getPageSource()){
			
			return (ComponentAccess) pc.getActiveUDF().getOwnerComponent();
		}
		return (ComponentAccess) pc.getActiveComponent();//+++
		
		
	}

	public static long getCompileTime(PageContext pc, PageSource ps,long defaultValue) {
		try {
			return getCompileTime(pc, ps);
		} catch (Throwable t) {
			return defaultValue;
		}
	}

	public static long getCompileTime(PageContext pc, PageSource ps) throws PageException {
		return getPage(pc,ps).getCompileTime();
	}

	public static Page getPage(PageContext pc, PageSource ps) throws PageException {
		PageSourceImpl psi = (PageSourceImpl)ps;
		
		Page p = psi.getPage();
		if(p!=null){
			//print.o("getPage(existing):"+ps.getDisplayPath()+":"+psi.hashCode()+":"+p.hashCode());
			return p;
		}
		pc=ThreadLocalPageContext.get(pc);
		return psi.loadPage(pc);
	}

	public static Struct getPropertiesAsStruct(ComponentAccess ca, boolean onlyPersistent) {
		Property[] props = ca.getProperties(onlyPersistent);
		Struct sct=new StructImpl();
		if(props!=null)for(int i=0;i<props.length;i++){
			sct.setEL(KeyImpl.getInstance(props[i].getName()), props[i]);
		}
		return sct;
	}
	public static Struct getMetaData(PageContext pc,UDFPropertiesImpl udf) throws PageException {
		StructImpl func=new StructImpl();
        pc=ThreadLocalPageContext.get(pc);
		// TODO func.set("roles", value);
        // TODO func.set("userMetadata", value); neo unterstﾟtzt irgendwelche a
        // meta data
        Struct meta = udf.meta;
        if(meta!=null) StructUtil.copy(meta, func, true);
        
        func.setEL(KeyConstants._closure, Boolean.FALSE);
		
		func.set(KeyConstants._access,ComponentUtil.toStringAccess(udf.getAccess()));
        String hint=udf.hint;
        if(!StringUtil.isEmpty(hint))func.set(KeyConstants._hint,hint);
        String displayname=udf.displayName;
        if(!StringUtil.isEmpty(displayname))func.set(KeyConstants._displayname,displayname);
        func.set(KeyConstants._name,udf.functionName);
        func.set(KeyConstants._output,Caster.toBoolean(udf.output));
        func.set(KeyConstants._returntype, udf.strReturnType);
        func.set(KeyConstants._description, udf.description);
        
        func.set(KeyConstants._owner, udf.pageSource.getDisplayPath());
        
	    	   
	    int format = udf.returnFormat;
        if(format<0 || format==UDF.RETURN_FORMAT_WDDX)			func.set(KeyConstants._returnFormat, "wddx");
        else if(format==UDF.RETURN_FORMAT_PLAIN)	func.set(KeyConstants._returnFormat, "plain");
        else if(format==UDF.RETURN_FORMAT_JSON)	func.set(KeyConstants._returnFormat, "json");
        else if(format==UDF.RETURN_FORMAT_SERIALIZE)func.set(KeyConstants._returnFormat, "cfml");
        
        
        FunctionArgument[] args =  udf.arguments;
        Array params=new ArrayImpl();
        //Object defaultValue;
        Struct m;
        //Object defaultValue;
        for(int y=0;y<args.length;y++) {
            StructImpl param=new StructImpl();
            param.set(KeyConstants._name,args[y].getName().getString());
            param.set(KeyConstants._required,Caster.toBoolean(args[y].isRequired()));
            param.set(KeyConstants._type,args[y].getTypeAsString());
            displayname=args[y].getDisplayName();
            if(!StringUtil.isEmpty(displayname)) param.set(KeyConstants._displayname,displayname);
            
            int defType = args[y].getDefaultType();
            if(defType==FunctionArgument.DEFAULT_TYPE_RUNTIME_EXPRESSION){
            	param.set(KeyConstants._default, "[runtime expression]");
            }
            else if(defType==FunctionArgument.DEFAULT_TYPE_LITERAL){
            	param.set(KeyConstants._default, 
            			UDFUtil.getDefaultValue(pc, udf.pageSource, udf.index, y, null));
            }
            
            hint=args[y].getHint();
            if(!StringUtil.isEmpty(hint))param.set(KeyConstants._hint,hint);
            // TODO func.set("userMetadata", value); neo unterstﾟtzt irgendwelche attr, die dann hier ausgebenen werden blﾚdsinn
            
            // meta data
            m=args[y].getMetaData();
            if(m!=null) StructUtil.copy(m, param, true);
                
            params.append(param);
        }
        func.set(KeyConstants._parameters,params);
		return func;
	}


}
