• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.compatibility.common.util;
18 
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24 
25 import org.junit.AssumptionViolatedException;
26 
27 /**
28  * Resolves methods provided by the BusinessLogicService and invokes them
29  */
30 public abstract class BusinessLogicExecutor {
31 
32     protected static final String LOG_TAG = "BusinessLogicExecutor";
33 
34     /** String representations of the String class and String[] class */
35     protected static final String STRING_CLASS = "java.lang.String";
36     protected static final String STRING_ARRAY_CLASS = "[Ljava.lang.String;";
37 
38     /**
39      * Execute a business logic condition.
40      * @param method the name of the method to invoke. Must include fully qualified name of the
41      * enclosing class, followed by '.', followed by the name of the method
42      * @param args the string arguments to supply to the method
43      * @return the return value of the method invoked
44      * @throws RuntimeException when failing to resolve or invoke the condition method
45      */
executeCondition(String method, String... args)46     public boolean executeCondition(String method, String... args) {
47         logDebug("Executing condition: %s", formatExecutionString(method, args));
48         try {
49             return (Boolean) invokeMethod(method, args);
50         } catch (ClassNotFoundException | IllegalAccessException | InstantiationException |
51                 InvocationTargetException | NoSuchMethodException e) {
52             throw new RuntimeException(String.format(
53                     "BusinessLogic: Failed to invoke condition method %s with args: %s", method,
54                     Arrays.toString(args)), e);
55         }
56     }
57 
58     /**
59      * Execute a business logic action.
60      * @param method the name of the method to invoke. Must include fully qualified name of the
61      * enclosing class, followed by '.', followed by the name of the method
62      * @param args the string arguments to supply to the method
63      * @throws RuntimeException when failing to resolve or invoke the action method
64      */
executeAction(String method, String... args)65     public void executeAction(String method, String... args) {
66         logDebug("Executing action: %s", formatExecutionString(method, args));
67         try {
68             invokeMethod(method, args);
69         } catch (ClassNotFoundException | IllegalAccessException | InstantiationException |
70                 NoSuchMethodException e) {
71             throw new RuntimeException(String.format(
72                     "BusinessLogic: Failed to invoke action method %s with args: %s", method,
73                     Arrays.toString(args)), e);
74         } catch (InvocationTargetException e) {
75             // This action throws an exception, so throw the original exception (e.g.
76             // AssertionFailedError) for a more readable stacktrace.
77             Throwable t = e.getCause();
78             if (AssumptionViolatedException.class.isInstance(t)) {
79                 // This is an assumption failure (registered as a "pass") so don't wrap this
80                 // throwable in a RuntimeException
81                 throw (AssumptionViolatedException) t;
82             } else {
83                 RuntimeException re = new RuntimeException(t.getMessage(), t.getCause());
84                 re.setStackTrace(t.getStackTrace());
85                 throw re;
86             }
87         }
88     }
89 
90     /**
91      * Format invokation information as "method(args[0], args[1], ...)".
92      */
formatExecutionString(String method, String... args)93     protected abstract String formatExecutionString(String method, String... args);
94 
95     /**
96      * Execute a business logic method.
97      * @param method the name of the method to invoke. Must include fully qualified name of the
98      * enclosing class, followed by '.', followed by the name of the method
99      * @param args the string arguments to supply to the method
100      * @return the return value of the method invoked (type Boolean if method is a condition)
101      * @throws RuntimeException when failing to resolve or invoke the method
102      */
invokeMethod(String method, String... args)103     protected Object invokeMethod(String method, String... args) throws ClassNotFoundException,
104             IllegalAccessException, InstantiationException, InvocationTargetException,
105             NoSuchMethodException {
106         // Method names served by the BusinessLogic service should assume format
107         // classname.methodName, but also handle format classname#methodName since test names use
108         // this format
109         int index = (method.indexOf('#') == -1) ? method.lastIndexOf('.') : method.indexOf('#');
110         if (index == -1) {
111             throw new RuntimeException(String.format("BusinessLogic: invalid method name "
112                     + "\"%s\". Method string must include fully qualified class name. "
113                     + "For example, \"com.android.packagename.ClassName.methodName\".", method));
114         }
115         String className = method.substring(0, index);
116         Class cls = Class.forName(className);
117         Object obj = cls.getDeclaredConstructor().newInstance();
118         if (getTestObject() != null && cls.isAssignableFrom(getTestObject().getClass())) {
119             // The given method is a member of the test class, use the known test class instance
120             obj = getTestObject();
121         }
122         ResolvedMethod rm = getResolvedMethod(cls, method.substring(index + 1), args);
123         return rm.invoke(obj);
124     }
125 
126     /**
127      * Log information with whichever logging mechanism is available to the instance. This varies
128      * from host-side to device-side, so implementations are left to subclasses.
129      * See {@link String.format(String, Object...)} for parameter information.
130      */
logInfo(String format, Object... args)131     public abstract void logInfo(String format, Object... args);
132 
133     /**
134      * Log debugging information to the host or device logs (depending on implementation).
135      * See {@link String.format(String, Object...)} for parameter information.
136      */
logDebug(String format, Object... args)137     public abstract void logDebug(String format, Object... args);
138 
139     /**
140      * Get the test object. This method is left abstract, since non-abstract subclasses will set
141      * the test object in the constructor.
142      * @return the test case instance
143      */
getTestObject()144     protected abstract Object getTestObject();
145 
146     /**
147      * Get the method and list of arguments corresponding to the class, method name, and proposed
148      * argument values, in the form of a {@link ResolvedMethod} object. This object stores all
149      * information required to successfully invoke the method. getResolvedMethod is left abstract,
150      * since argument types differ between device-side (e.g. Context) and host-side
151      * (e.g. ITestDevice) implementations of this class.
152      * @param cls the Class to which the method belongs
153      * @param methodName the name of the method to invoke
154      * @param args the string arguments to use when invoking the method
155      * @return a {@link ResolvedMethod}
156      * @throws ClassNotFoundException
157      */
getResolvedMethod(Class cls, String methodName, String... args)158     protected abstract ResolvedMethod getResolvedMethod(Class cls, String methodName,
159             String... args) throws ClassNotFoundException;
160 
161     /**
162      * Retrieve all methods within a class that match a given name
163      * @param cls the class
164      * @param name the method name
165      * @return a list of method objects
166      */
getMethodsWithName(Class cls, String name)167     protected List<Method> getMethodsWithName(Class cls, String name) {
168         List<Method> methodList = new ArrayList<>();
169         for (Method m : cls.getMethods()) {
170             if (name.equals(m.getName())) {
171                 methodList.add(m);
172             }
173         }
174         return methodList;
175     }
176 
177     /**
178      * Helper class for storing a method object, and a list of arguments to use when invoking the
179      * method. The class is also equipped with an "invoke" method for convenience.
180      */
181     protected static class ResolvedMethod {
182         private Method mMethod;
183         List<Object> mArgs;
184 
ResolvedMethod(Method method)185         public ResolvedMethod(Method method) {
186             mMethod = method;
187             mArgs = new ArrayList<>();
188         }
189 
190         /** Add an argument to the argument list for this instance */
addArg(Object arg)191         public void addArg(Object arg) {
192             mArgs.add(arg);
193         }
194 
195         /** Invoke the stored method with the stored args on a given object */
invoke(Object instance)196         public Object invoke(Object instance) throws IllegalAccessException,
197                 InvocationTargetException {
198             return mMethod.invoke(instance, mArgs.toArray());
199         }
200     }
201 }
202