lucee.runtime.type.util.ComponentUtil.java Source code

Java tutorial

Introduction

Here is the source code for lucee.runtime.type.util.ComponentUtil.java

Source

/**
 *
 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
package lucee.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 java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import lucee.commons.digest.MD5;
import lucee.commons.io.IOUtil;
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.ClassUtil;
import lucee.commons.lang.PhysicalClassLoader;
import lucee.commons.lang.StringUtil;
import lucee.commons.lang.types.RefBoolean;
import lucee.runtime.Component;
import lucee.runtime.ComponentSpecificAccess;
import lucee.runtime.Mapping;
import lucee.runtime.Page;
import lucee.runtime.PageContext;
import lucee.runtime.PageContextImpl;
import lucee.runtime.PageSource;
import lucee.runtime.PageSourceImpl;
import lucee.runtime.component.Property;
import lucee.runtime.config.Config;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.ExpressionException;
import lucee.runtime.exp.PageException;
import lucee.runtime.listener.AppListenerUtil;
import lucee.runtime.net.rpc.AxisCaster;
import lucee.runtime.net.rpc.Pojo;
import lucee.runtime.net.rpc.server.ComponentController;
import lucee.runtime.net.rpc.server.RPCServer;
import lucee.runtime.op.Caster;
import lucee.runtime.type.Array;
import lucee.runtime.type.ArrayImpl;
import lucee.runtime.type.Collection;
import lucee.runtime.type.Collection.Key;
import lucee.runtime.type.FunctionArgument;
import lucee.runtime.type.KeyImpl;
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.UDF;
import lucee.runtime.type.UDFPropertiesImpl;
import lucee.transformer.bytecode.BytecodeContext;
import lucee.transformer.bytecode.literal.LitString;
import lucee.transformer.bytecode.util.ASMProperty;
import lucee.transformer.bytecode.util.ASMPropertyImpl;
import lucee.transformer.bytecode.util.ASMUtil;
import lucee.transformer.bytecode.util.Types;
import lucee.transformer.bytecode.visitor.ArrayVisitor;

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;

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, Component component, RefBoolean isNew,
            boolean create, boolean writeLog, boolean suppressWSbeforeArg, boolean output) throws PageException {
        isNew.setValue(false);
        String classNameOriginal = component.getPageSource().getFullClassName();
        String className = getClassname(component, null).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();
        cw.visit(Opcodes.V1_6, 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", "Llucee/runtime/ComponentImpl;", null, null);
        //fv.visitEnd();

        java.util.List<LitString> _keys = new ArrayList<LitString>();

        // remote methods
        Collection.Key[] keys = ComponentProUtil.keys(component, 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,
                    suppressWSbeforeArg, output)) != -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);
        lucee.transformer.bytecode.Page
                .registerFields(new BytecodeContext(null, statConstr, constr, getPage(statConstr, constr), _keys,
                        cw, real, adapter, CONSTRUCTOR_OBJECT, writeLog, suppressWSbeforeArg, output), _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 lucee.transformer.bytecode.Page getPage(BytecodeContext bc1, BytecodeContext bc2) {
        lucee.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) {
        return hasChangesOfChildren(last, ThreadLocalPageContext.get(), clazz);
    }

    /**
     * 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, ASMProperty[] props) {

        String prefix = "";
        /*if(props!=null) {
            StringBuilder sb=new StringBuilder();
            
           for(int i=0;i<props.length;i++){
        sb.append(props[i].toString()).append(';');
           }
               
               
           prefix = Long.toString(HashUtil.create64BitHash(sb),Character.MAX_RADIX);
           char c=prefix.charAt(0);
           if(c>='0' && c<='9') prefix="a"+prefix;
           prefix=prefix+".";
        }*/

        PageSource ps = component.getPageSource();
        return prefix + ps.getComponentName();
    }

    /*
     * includes the application context javasettings 
     * @param pc
     * @param className
     * @param properties
     * @return
     * @throws PageException
     */
    public static Class getClientComponentPropertiesClass(PageContext pc, String className,
            ASMProperty[] properties, Class extendsClass) throws PageException {
        try {
            return _getComponentPropertiesClass(pc, pc.getConfig(), className, properties, extendsClass);
        } 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 Class getComponentPropertiesClass(Config config, String className, ASMProperty[] properties,
            Class extendsClass) throws PageException {
        try {
            return _getComponentPropertiesClass(null, config, className, properties, extendsClass);
        } catch (Exception e) {
            throw Caster.toPageException(e);
        }
    }

    private static Class _getComponentPropertiesClass(PageContext pc, Config secondChanceConfig, String className,
            ASMProperty[] properties, Class extendsClass)
            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 clazz;
                }
            } catch (Exception e) {

            }
        }
        // create file
        if (extendsClass == null)
            extendsClass = Object.class;
        byte[] barr = ASMUtil.createPojo(real, properties, extendsClass, 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 cl.loadClass(className);

    }

    public static Class getComponentPropertiesClass(PageContext pc, Component component) throws PageException {
        try {
            return _getComponentPropertiesClass(pc, component);
        } catch (Exception e) {
            throw Caster.toPageException(e);
        }
    }

    private static Class _getComponentPropertiesClass(PageContext pc, Component component)
            throws PageException, IOException, ClassNotFoundException {

        ASMProperty[] props = ASMUtil
                .toASMProperties(ComponentProUtil.getProperties(component, false, true, false, false));

        String className = getClassname(component, props);//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"));

        // get component class information
        String classNameOriginal = component.getPageSource().getFullClassName();
        String realOriginal = classNameOriginal.replace('.', '/');
        Resource classFileOriginal = mapping.getClassRootDirectory().getRealResource(realOriginal.concat(".class"));

        // load existing class when pojo is still newer than component class file
        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) {
            }
        }

        // extends
        String strExt = component.getExtends();
        Class<?> ext = Object.class;
        if (!StringUtil.isEmpty(strExt, true)) {
            ext = Caster.cfTypeToClass(strExt);
        }
        //
        // create file
        byte[] barr = ASMUtil.createPojo(real, props, ext, 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));
    }

    public static Class getStructPropertiesClass(PageContext pc, Struct sct, PhysicalClassLoader cl)
            throws PageException {
        try {
            return _getStructPropertiesClass(pc, sct, cl);
        } catch (Exception e) {
            throw Caster.toPageException(e);
        }
    }

    private static Class _getStructPropertiesClass(PageContext pc, Struct sct, PhysicalClassLoader cl)
            throws PageException, IOException, ClassNotFoundException {
        // create hash based on the keys of the struct
        String hash = StructUtil.keyHash(sct);
        char c = hash.charAt(0);
        if (c >= '0' && c <= '9')
            hash = "a" + hash;

        // create class name (struct class name + hash)
        String className = sct.getClass().getName() + "." + hash;

        // create physcal location for the file
        String real = className.replace('.', '/');
        Resource classFile = cl.getDirectory().getRealResource(real.concat(".class"));

        // load existing class
        if (classFile.exists()) {
            try {
                Class clazz = cl.loadClass(className);
                if (clazz != null)
                    return clazz;
            } catch (Throwable t) {
            }
        }

        // Properties
        List<ASMProperty> props = new ArrayList<ASMProperty>();
        Iterator<Entry<Key, Object>> it = sct.entryIterator();
        Entry<Key, Object> e;
        while (it.hasNext()) {
            e = it.next();
            props.add(new ASMPropertyImpl(ASMUtil
                    .toType(e.getValue() == null ? Object.class : Object.class/*e.getValue().getClass()*/, true),
                    e.getKey().getString()));
        }

        // create file
        byte[] barr = ASMUtil.createPojo(real, props.toArray(new ASMProperty[props.size()]), Object.class,
                new Class[] { Pojo.class }, null);

        // create class file from bytecode 
        ResourceUtil.touch(classFile);
        IOUtil.copy(new ByteArrayInputStream(barr), classFile, true);
        cl = (PhysicalClassLoader) ((PageContextImpl) pc).getRPCClassLoader(true);
        return 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 suppressWSbeforeArg, boolean output) 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, suppressWSbeforeArg, output);
            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 {
        return md5(ComponentSpecificAccess.toComponentSpecificAccess(Component.ACCESS_PRIVATE, c));
    }

    public static String md5(ComponentSpecificAccess 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 = ComponentProUtil.keys(c, access);

            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 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 Component toComponentAccess(Component comp, Component defaultValue) {
       if(comp instanceof ComponentAccess) return (ComponentAccess) comp;
       if(comp instanceof ComponentSpecificAccess) return ((ComponentSpecificAccess) 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 Component getActiveComponent(PageContext pc, Component current) {
        if (pc.getActiveComponent() == null)
            return current;
        if (pc.getActiveUDF() != null && (pc.getActiveComponent())
                .getPageSource() == (pc.getActiveUDF().getOwnerComponent()).getPageSource()) {

            return pc.getActiveUDF().getOwnerComponent();
        }
        return 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(Component c, boolean onlyPersistent) {
        Property[] props = c.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 unterstuetzt 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);
        if (udf.localMode != null)
            func.set("localMode", AppListenerUtil.toLocalMode(udf.localMode.intValue(), ""));

        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 unterstuetzt irgendwelche attr, die dann hier ausgebenen werden bloedsinn

            // meta data
            m = args[y].getMetaData();
            if (m != null)
                StructUtil.copy(m, param, true);

            params.append(param);
        }
        func.set(KeyConstants._parameters, params);
        return func;
    }

}