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}