package cuke4duke.internal.jvmclass; import cuke4duke.Scenario; import cuke4duke.StepMother; import cuke4duke.internal.language.AbstractProgrammingLanguage; import cuke4duke.internal.language.Transformable; import cuke4duke.spi.ExceptionFactory; import cuke4duke.spi.jruby.JRuby; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; public class ClassLanguage extends AbstractProgrammingLanguage { private final ObjectFactory objectFactory; private final List<ClassAnalyzer> analyzers; private final Map<Class<?>, Transformable> transformers = new HashMap<Class<?>, Transformable>(); public ClassLanguage(ClassLanguageMixin languageMixin, ExceptionFactory exceptionFactory, StepMother stepMother, List<ClassAnalyzer> analyzers) throws Throwable { this(languageMixin, exceptionFactory, stepMother, analyzers, createObjectFactory()); } public ClassLanguage(ClassLanguageMixin languageMixin, ExceptionFactory exceptionFactory, StepMother stepMother, List<ClassAnalyzer> analyzers, ObjectFactory objectFactory) throws Throwable { super(languageMixin, exceptionFactory); this.analyzers = analyzers; this.objectFactory = objectFactory; objectFactory.addStepMother(stepMother); for (ClassAnalyzer analyzer : analyzers) { for (Class<?> clazz : analyzer.alwaysLoad()) { if (objectFactory.canHandle(clazz)) { objectFactory.addClass(clazz); } } } } public void addTransform(Class<?> returnType, Transformable javaTransform) { transformers.put(returnType, javaTransform); } @Override protected Object customTransform(Object arg, Class<?> parameterType, Locale locale) throws Throwable { Transformable transformer = transformers.get(parameterType); return transformer == null ? null : transformer.transform(arg, locale); } @Override public void load_code_file(String classFile) throws Throwable { Class<?> clazz = loadClass(classFile); addClass(clazz); } public void addClass(Class<?> clazz) { if (!Modifier.isAbstract(clazz.getModifiers()) && objectFactory.canHandle(clazz)) { objectFactory.addClass(clazz); } } @Override public void begin_scenario(Scenario scenario) throws Throwable { clearHooksAndStepDefinitions(); objectFactory.createObjects(); for (ClassAnalyzer analyzer : analyzers) { analyzer.populateStepDefinitionsAndHooks(objectFactory, this); } } @Override public void end_scenario() throws Throwable { objectFactory.disposeObjects(); } private Class<?> loadClass(String classFile) throws ClassNotFoundException { String withoutExt = classFile.substring(0, classFile.length() - ".class".length()); String[] pathElements = withoutExt.split("\\/"); String className = null; for (int i = pathElements.length - 1; i >= 0; i--) { if (className == null) { className = pathElements[i]; } else { className = pathElements[i] + "." + className; } try { return JRuby.getRuntime().getJRubyClassLoader().loadClass(className); } catch (ClassNotFoundException ignore) { } } throw new ClassNotFoundException("Couldn't determine class from file: " + classFile); } private static ObjectFactory createObjectFactory() throws Throwable { String objectFactoryClassName = System.getProperty("cuke4duke.objectFactory", "cuke4duke.internal.jvmclass.PicoFactory"); Class<?> ofc = JRuby.getRuntime().getJRubyClassLoader().loadClass(objectFactoryClassName); Constructor<?> ctor = ofc.getConstructor(); try { return (ObjectFactory) ctor.newInstance(); } catch (InvocationTargetException e) { throw e.getTargetException(); } } public Object invokeHook(Method method, Scenario scenario) throws Throwable { Object[] args = new Object[0]; if (method.getParameterTypes().length == 1) { args = new Object[]{scenario}; } else if (method.getParameterTypes().length > 1) { throw cucumberArityMismatchError("Hooks must take 0 or 1 arguments. " + method); } return invoke(method, args, Locale.getDefault()); } public Object invoke(Method method, Object[] args, Locale locale) throws Throwable { Object target = objectFactory.getComponent(method.getDeclaringClass()); Object[] transformedArgs = transform(args, method.getParameterTypes(), locale); return methodInvoker.invoke(method, target, transformedArgs); } public List<Class<?>> getClasses() { return objectFactory.getClasses(); } }