/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.appium.android.bootstrap.utils; import com.android.uiautomator.core.UiSelector; import io.appium.android.bootstrap.Logger; import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; /** * For parsing strings which create new UiSelector objects into UiSelector object. */ public class UiSelectorParser { private String text; private UiSelector selector; private final static Method[] methods = UiSelector.class.getDeclaredMethods(); public UiSelector parse(String textToParse) throws UiSelectorSyntaxException { selector = new UiSelector(); text = cleanseText(textToParse); while (text.length() > 0) { consumePeriod(); consumeFunctionCall(); } return selector; } // prepares text for the main parsing loop private String cleanseText(String dirtyText) { String cleanText = dirtyText.trim(); if (cleanText.startsWith("new UiSelector()")) { cleanText = cleanText.substring(16); } else if (cleanText.startsWith("UiSelector()")) { cleanText = cleanText.substring(12); } else if (!cleanText.startsWith(".")){ cleanText = "." + cleanText; } return cleanText; } private void consumePeriod() throws UiSelectorSyntaxException { if (text.startsWith(".")) { text = text.substring(1); } else { throw new UiSelectorSyntaxException("Expected \".\" but saw \"" + text.charAt(0) + "\""); } } /* * consume [a-z]* then an open paren, this is our methodName * consume .* and count open/close parens until the original open paren is close, this is our argument * */ private void consumeFunctionCall() throws UiSelectorSyntaxException { String methodName; StringBuilder argument = new StringBuilder(); int parenIndex = text.indexOf('('); methodName = text.substring(0, parenIndex); int index = parenIndex+1; int parenCount = 1; while (parenCount > 0) { try { switch (text.charAt(index)) { case ')': parenCount--; if (parenCount > 0) { argument.append(text.charAt(index)); } break; case '(': parenCount++; argument.append(text.charAt(index)); break; default: argument.append(text.charAt(index)); } } catch (StringIndexOutOfBoundsException e) { throw new UiSelectorSyntaxException("unclosed paren in expression"); } index++; } if (argument.length() < 1) { throw new UiSelectorSyntaxException(methodName + " method expects an argument"); } // add two for parentheses surrounding arg text = text.substring(methodName.length() + argument.length() + 2); ArrayList<Method> overloadedMethods = getSelectorMethods(methodName); if (overloadedMethods.size() < 1) { throw new UiSelectorSyntaxException("UiSelector has no " + methodName + " method"); } selector = applyArgToMethods(overloadedMethods, argument.toString()); } private ArrayList<Method> getSelectorMethods(String methodName) { ArrayList<Method> ret = new ArrayList<Method>(); for (Method method : methods) { if (method.getName().equals(methodName)) { ret.add(method); } } return ret; } private UiSelector applyArgToMethods(ArrayList<Method> methods, String argument) throws UiSelectorSyntaxException { Object arg = null; Method ourMethod = null; UiSelectorSyntaxException exThrown = null; for (Method method : methods) { try { Type parameterType = method.getGenericParameterTypes()[0]; arg = coerceArgToType(parameterType, argument); ourMethod = method; } catch (UiSelectorSyntaxException e) { exThrown = e; } } if (ourMethod == null || arg == null) { if (exThrown != null) { throw exThrown; } else { throw new UiSelectorSyntaxException("Could not apply argument " + argument + " to UiSelector method"); } } try { return (UiSelector)ourMethod.invoke(selector, arg); } catch (IllegalAccessException e) { e.printStackTrace(); throw new UiSelectorSyntaxException("problem using reflection to call this method"); } catch (InvocationTargetException e) { e.printStackTrace(); throw new UiSelectorSyntaxException("problem using reflection to call this method"); } } private Object coerceArgToType(Type type, String argument) throws UiSelectorSyntaxException { Logger.debug("UiSelector coerce type: " + type + " arg: " + argument); if (type == boolean.class) { if (argument.equals("true")) { return true; } if (argument.equals("false")) { return false; } throw new UiSelectorSyntaxException(argument + " is not a boolean"); } if (type == String.class) { if (argument.charAt(0) != '"' || argument.charAt(argument.length()-1) != '"') { throw new UiSelectorSyntaxException(argument + " is not a string"); } return argument.substring(1, argument.length()-1); } if (type == int.class) { return Integer.parseInt(argument); } if (type.toString().equals("java.lang.Class<T>")) { try { return Class.forName(argument); } catch (ClassNotFoundException e) { throw new UiSelectorSyntaxException(argument + " class could not be found"); } } if (type == UiSelector.class) { UiSelectorParser parser = new UiSelectorParser(); return parser.parse(argument); } throw new UiSelectorSyntaxException("Could not coerce " + argument + " to any sort of Type"); } }