001package org.andromda.translation.ocl.validation;
002
003import java.lang.reflect.Method;
004
005import org.andromda.core.common.Introspector;
006import org.andromda.translation.ocl.syntax.OCLPatterns;
007import org.apache.commons.lang.StringUtils;
008import org.apache.commons.lang.exception.ExceptionUtils;
009import org.apache.log4j.Logger;
010
011
012/**
013 * Dynamically invokes operation and property calls on specified <strong>elements</code>.
014 *
015 * @author Wouter Zoons
016 * @author Chad Brandon
017 */
018public final class OCLIntrospector
019{
020    private static final Logger logger = Logger.getLogger(OCLIntrospector.class);
021
022    /**
023     * Invokes the given <code>feature</code> on the <code>element</code>. Its expected that the feature is either an
024     * operation or a property.
025     *
026     * @param element
027     * @param feature
028     * @return invoke(element,feature,null)
029     */
030    public static final Object invoke(
031            final Object element,
032            String feature)
033    {
034        Object result = null;
035        try
036        {
037            feature = StringUtils.trimToEmpty(feature);
038            if (OCLPatterns.isOperation(feature))
039            {
040                result = invoke(element, feature, null);
041            } else
042            {
043                result = Introspector.instance().getProperty(element, feature);
044            }
045        }
046        catch (final NullPointerException ignore)
047        {
048            // ignore (the result will just be null)
049        }
050        catch (final OCLIntrospectorException throwable)
051        {
052            // Don't catch our own exceptions.
053            // Otherwise get Exception/Cause chain which
054            // can hide the original exception.
055            throw throwable;
056        }
057        catch (Throwable throwable)
058        {
059            throwable = getRootCause(throwable);
060
061            // If cause is an OCLIntrospectorException re-throw
062            // the exception rather than creating a new one.
063            if (throwable instanceof OCLIntrospectorException)
064            {
065                throw (OCLIntrospectorException) throwable;
066            }
067            throw new OCLIntrospectorException(throwable);
068        }
069        return result;
070    }
071
072    /**
073     * Invokes the given <code>feature</code> on the specified <code>element</code> taking the given
074     * <code>arguments</code>. If <code>arguments</code> is null its expected that the feature is an empty operation.
075     *
076     * @param element
077     * @param feature
078     * @param arguments
079     * @return invokeMethod(element,feature,arguments)
080     */
081    public static Object invoke(
082            final Object element,
083            String feature,
084            final Object[] arguments)
085    {
086        Object result = null;
087        try
088        {
089            // check for parenthesis
090            int parenIndex = feature.indexOf('(');
091            if (parenIndex != -1)
092            {
093                feature = feature.substring(0, parenIndex).trim();
094            }
095            result = invokeMethod(element, feature, arguments);
096        }
097        catch (final NullPointerException exception)
098        {
099            // ignore (the result will just be null)
100        }
101        catch (Throwable throwable)
102        {
103            // At least output the location where the error happened, not the entire stack trace.
104            StackTraceElement[] trace = throwable.getStackTrace();
105            String location = " AT " + trace[0].getClassName() + '.' + trace[0].getMethodName() + ':' + trace[0].getLineNumber();
106            if (throwable.getMessage() != null)
107            {
108                location += ' ' + throwable.getMessage();
109            }
110            /*final String message =
111                "Error invoking feature '" + feature + "' on element '" + element + "' with arguments '" +
112                StringUtils.join(arguments, ',') + "'";*/
113            throwable = getRootCause(throwable);
114            logger.error("OCLIntrospector " + throwable + " invoking " + element + " METHOD " + feature + " WITH " + StringUtils.join(arguments, ',') + location);
115            throw new OCLIntrospectorException(throwable);
116        }
117        return result;
118    }
119
120    private static final Object invokeMethod(
121            final Object element,
122            final String methodName,
123            final Object[] arguments)
124            throws Exception
125    {
126        Object property = null;
127
128        if (element != null && StringUtils.isNotBlank(methodName))
129        {
130            Class[] argumentTypes = getObjectTypes(arguments);
131
132            final Method method = element.getClass().getMethod(methodName, argumentTypes);
133            property = method.invoke(element, arguments);
134        }
135
136        return property;
137    }
138
139    private static final Class[] getObjectTypes(final Object[] objects)
140    {
141        Class[] objectTypes = null;
142        if (objects != null)
143        {
144            objectTypes = new Class[objects.length];
145            for (int ctr = 0; ctr < objects.length; ctr++)
146            {
147                final Object object = objects[ctr];
148                if (object != null)
149                {
150                    objectTypes[ctr] = object.getClass();
151                }
152            }
153        }
154        return objectTypes;
155    }
156
157    /**
158     * Attempts to retrieve the root cause of the exception, if it can not be
159     * found, the <code>throwable</code> itself is returned.
160     *
161     * @param throwable the exception from which to retrieve the root cause.
162     * @return the root cause of the exception
163     */
164    private static final Throwable getRootCause(Throwable throwable)
165    {
166        Throwable root = ExceptionUtils.getRootCause(throwable);
167        if (root != null)
168        {
169            throwable = root;
170        }
171        return throwable;
172    }
173}