OCLIntrospector.java

package org.andromda.translation.ocl.validation;

import java.lang.reflect.Method;

import org.andromda.core.common.Introspector;
import org.andromda.translation.ocl.syntax.OCLPatterns;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;


/**
 * Dynamically invokes operation and property calls on specified <strong>elements</code>.
 *
 * @author Wouter Zoons
 * @author Chad Brandon
 */
public final class OCLIntrospector
{
    private static final Logger logger = Logger.getLogger(OCLIntrospector.class);

    /**
     * Invokes the given <code>feature</code> on the <code>element</code>. Its expected that the feature is either an
     * operation or a property.
     *
     * @param element
     * @param feature
     * @return invoke(element,feature,null)
     */
    public static final Object invoke(
            final Object element,
            String feature)
    {
        Object result = null;
        try
        {
            feature = StringUtils.trimToEmpty(feature);
            if (OCLPatterns.isOperation(feature))
            {
                result = invoke(element, feature, null);
            } else
            {
                result = Introspector.instance().getProperty(element, feature);
            }
        }
        catch (final NullPointerException ignore)
        {
            // ignore (the result will just be null)
        }
        catch (final OCLIntrospectorException throwable)
        {
            // Don't catch our own exceptions.
            // Otherwise get Exception/Cause chain which
            // can hide the original exception.
            throw throwable;
        }
        catch (Throwable throwable)
        {
            throwable = getRootCause(throwable);

            // If cause is an OCLIntrospectorException re-throw
            // the exception rather than creating a new one.
            if (throwable instanceof OCLIntrospectorException)
            {
                throw (OCLIntrospectorException) throwable;
            }
            throw new OCLIntrospectorException(throwable);
        }
        return result;
    }

    /**
     * Invokes the given <code>feature</code> on the specified <code>element</code> taking the given
     * <code>arguments</code>. If <code>arguments</code> is null its expected that the feature is an empty operation.
     *
     * @param element
     * @param feature
     * @param arguments
     * @return invokeMethod(element,feature,arguments)
     */
    public static Object invoke(
            final Object element,
            String feature,
            final Object[] arguments)
    {
        Object result = null;
        try
        {
            // check for parenthesis
            int parenIndex = feature.indexOf('(');
            if (parenIndex != -1)
            {
                feature = feature.substring(0, parenIndex).trim();
            }
            result = invokeMethod(element, feature, arguments);
        }
        catch (final NullPointerException exception)
        {
            // ignore (the result will just be null)
        }
        catch (Throwable throwable)
        {
            // At least output the location where the error happened, not the entire stack trace.
            StackTraceElement[] trace = throwable.getStackTrace();
            String location = " AT " + trace[0].getClassName() + '.' + trace[0].getMethodName() + ':' + trace[0].getLineNumber();
            if (throwable.getMessage() != null)
            {
                location += ' ' + throwable.getMessage();
            }
            /*final String message =
                "Error invoking feature '" + feature + "' on element '" + element + "' with arguments '" +
                StringUtils.join(arguments, ',') + "'";*/
            throwable = getRootCause(throwable);
            logger.error("OCLIntrospector " + throwable + " invoking " + element + " METHOD " + feature + " WITH " + StringUtils.join(arguments, ',') + location);
            throw new OCLIntrospectorException(throwable);
        }
        return result;
    }

    private static final Object invokeMethod(
            final Object element,
            final String methodName,
            final Object[] arguments)
            throws Exception
    {
        Object property = null;

        if (element != null && StringUtils.isNotBlank(methodName))
        {
            Class[] argumentTypes = getObjectTypes(arguments);

            final Method method = element.getClass().getMethod(methodName, argumentTypes);
            property = method.invoke(element, arguments);
        }

        return property;
    }

    private static final Class[] getObjectTypes(final Object[] objects)
    {
        Class[] objectTypes = null;
        if (objects != null)
        {
            objectTypes = new Class[objects.length];
            for (int ctr = 0; ctr < objects.length; ctr++)
            {
                final Object object = objects[ctr];
                if (object != null)
                {
                    objectTypes[ctr] = object.getClass();
                }
            }
        }
        return objectTypes;
    }

    /**
     * Attempts to retrieve the root cause of the exception, if it can not be
     * found, the <code>throwable</code> itself is returned.
     *
     * @param throwable the exception from which to retrieve the root cause.
     * @return the root cause of the exception
     */
    private static final Throwable getRootCause(Throwable throwable)
    {
        Throwable root = ExceptionUtils.getRootCause(throwable);
        if (root != null)
        {
            throwable = root;
        }
        return throwable;
    }
}