// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved. // Released under the terms of the CPL Common Public License version 1.0. package fitnesse.slim; import fitnesse.slim.converters.*; import java.beans.PropertyEditorManager; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.*; /** * This is the API for executing a SLIM statement. This class should not know * about the syntax of a SLIM statement. */ public class StatementExecutor implements StatementExecutorInterface { private static final String SLIM_HELPER_LIBRARY_INSTANCE_NAME = "SlimHelperLibrary"; private Map<String, Object> instances = new HashMap<String, Object>(); private List<Library> libraries = new ArrayList<Library>(); private List<MethodExecutor> executorChain = new ArrayList<MethodExecutor>(); private VariableStore variables = new VariableStore(); private List<String> paths = new ArrayList<String>(); private boolean stopRequested = false; private String lastActor; public StatementExecutor() { PropertyEditorManager.registerEditor(Map.class, MapEditor.class); executorChain.add(new FixtureMethodExecutor(instances)); executorChain.add(new SystemUnderTestMethodExecutor(instances)); executorChain.add(new LibraryMethodExecutor(libraries)); addSlimHelperLibraryToLibraries(); } private void addSlimHelperLibraryToLibraries() { SlimHelperLibrary slimHelperLibrary = new SlimHelperLibrary(); slimHelperLibrary.setStatementExecutor(this); libraries.add(new Library(SLIM_HELPER_LIBRARY_INSTANCE_NAME, slimHelperLibrary)); } public void setVariable(String name, Object value) { variables.setSymbol(name, new MethodExecutionResult(value, Object.class)); } private void setVariable(String name, MethodExecutionResult value) { variables.setSymbol(name, value); } public Object addPath(String path) { paths.add(path); return "OK"; } public Object getInstance(String instanceName) { Object instance = instances.get(instanceName); if (instance != null) { return instance; } for (Library library : libraries) { if (library.instanceName.equals(instanceName)) { return library.instance; } } throw new SlimError(String.format("message:<<NO_INSTANCE %s.>>", instanceName)); } public Converter getConverter(Class<?> k) { return ConverterSupport.getConverter(k); } public Object create(String instanceName, String className, Object[] args) { try { if (hasStoredActor(className)) { addToInstancesOrLibrary(instanceName, getStoredActor(className)); } else { String replacedClassName = variables.replaceSymbolsInString(className); Object instance = createInstanceOfConstructor(replacedClassName, replaceSymbols(args)); addToInstancesOrLibrary(instanceName, instance); } return "OK"; } catch (SlimError e) { return couldNotInvokeConstructorException(className, args); } catch (IllegalArgumentException e) { return couldNotInvokeConstructorException(className, args); } catch (Throwable e) { return exceptionToString(e); } } private void addToInstancesOrLibrary(String instanceName, Object instance) { if (isLibrary(instanceName)) { libraries.add(new Library(instanceName, instance)); } else { setInstance(instanceName, instance); } } public void setInstance(String instanceName, Object instance) { instances.put(instanceName, instance); } private boolean hasStoredActor(String nameWithDollar) { if (!variables.containsValueFor(nameWithDollar)) { return false; } Object potentialActor = getStoredActor(nameWithDollar); return potentialActor != null && !(potentialActor instanceof String); } private Object getStoredActor(String nameWithDollar) { return variables.getStored(nameWithDollar); } private boolean isLibrary(String instanceName) { return instanceName.startsWith("library"); } private String couldNotInvokeConstructorException(String className, Object[] args) { return exceptionToString(new SlimError(String.format( "message:<<COULD_NOT_INVOKE_CONSTRUCTOR %s[%d]>>", className, args.length))); } private Object createInstanceOfConstructor(String className, Object[] args) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Class<?> k = searchPathsForClass(className); Constructor<?> constructor = getConstructor(k.getConstructors(), args); if (constructor == null) throw new SlimError(String.format("message:<<NO_CONSTRUCTOR %s>>", className)); Object newInstance = constructor.newInstance(ConverterSupport.convertArgs(args, constructor .getParameterTypes())); if (newInstance instanceof StatementExecutorConsumer) { ((StatementExecutorConsumer) newInstance).setStatementExecutor(this); } return newInstance; } private Class<?> searchPathsForClass(String className) { Class<?> k = getClass(className); if (k != null) return k; List<String> reversedPaths = new ArrayList<String>(paths); Collections.reverse(reversedPaths); for (String path : reversedPaths) { k = getClass(path + "." + className); if (k != null) return k; } throw new SlimError(String.format("message:<<NO_CLASS %s>>", className)); } private Class<?> getClass(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { return null; } } private Constructor<?> getConstructor(Constructor<?>[] constructors, Object[] args) { for (Constructor<?> constructor : constructors) { Class<?> arguments[] = constructor.getParameterTypes(); if (arguments.length == args.length) return constructor; } return null; } public Object call(String instanceName, String methodName, Object... args) { try { return getMethodExecutionResult(instanceName, methodName, args).returnValue(); } catch (Throwable e) { return exceptionToString(e); } } private MethodExecutionResult getMethodExecutionResult(String instanceName, String methodName, Object... args) throws Throwable { MethodExecutionResults results = new MethodExecutionResults(); for (int i = 0; i < executorChain.size(); i++) { MethodExecutionResult result = executorChain.get(i).execute(instanceName, methodName, replaceSymbols(args)); if (result.hasResult()) { return result; } results.add(result); } return results.getFirstResult(); } public Object callAndAssign(String variable, String instanceName, String methodName, Object[] args) { try { MethodExecutionResult result = getMethodExecutionResult(instanceName, methodName, args); setVariable(variable, result); return result.returnValue(); } catch (Throwable e) { return exceptionToString(e); } } private Object[] replaceSymbols(Object[] args) { return variables.replaceSymbols(args); } private String exceptionToString(Throwable exception) { StringWriter stringWriter = new StringWriter(); PrintWriter pw = new PrintWriter(stringWriter); exception.printStackTrace(pw); if (exception.getClass().toString().contains("StopTest")) { stopRequested = true; return SlimServer.EXCEPTION_STOP_TEST_TAG + stringWriter.toString(); } else { return SlimServer.EXCEPTION_TAG + stringWriter.toString(); } } public boolean stopHasBeenRequested() { return stopRequested; } public void reset() { stopRequested = false; } }