diff --git a/build.gradle b/build.gradle index 9073b716ee..e2b41f7123 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,9 @@ +apply from: file('gradle/convention.gradle') +apply from: file('gradle/maven.gradle') +//apply from: file('gradle/check.gradle') +apply from: file('gradle/license.gradle') +apply from: file('gradle/release.gradle') + ext.githubProjectName = rootProject.name buildscript { @@ -9,20 +15,53 @@ allprojects { repositories { mavenCentral() } } -apply from: file('gradle/convention.gradle') -apply from: file('gradle/maven.gradle') -//apply from: file('gradle/check.gradle') -apply from: file('gradle/license.gradle') -apply from: file('gradle/release.gradle') - subprojects { + apply plugin: 'java' + apply plugin: 'eclipse' + apply plugin: 'idea' group = "com.netflix.${githubProjectName}" + ext.codeGenOutputDir = file("build/rewritten_classes") + + // make 'examples' use the same classpath + configurations { + core + examplesCompile.extendsFrom compile + examplesRuntime.extendsFrom runtime + } + sourceSets.test.java.srcDir 'src/main/java' tasks.withType(Javadoc).each { it.classpath = sourceSets.main.compileClasspath } + + sourceSets { + //include /src/examples folder + examples + } + + // include 'examples' in build task + tasks.build { + dependsOn(examplesClasses) + } + + eclipse { + classpath { + // include 'provided' dependencies on the classpath + plusConfigurations += configurations.provided + + downloadSources = true + downloadJavadoc = true + } + } + + idea { + module { + // include 'provided' dependencies on the classpath + scopes.PROVIDED.plus += configurations.provided + } + } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e230e2b1c4..2abe81ceda 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.3-bin.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip diff --git a/language-adaptors/codegen/build.gradle b/language-adaptors/codegen/build.gradle new file mode 100644 index 0000000000..af0e5f2eb8 --- /dev/null +++ b/language-adaptors/codegen/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'java' +apply plugin: 'groovy' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'osgi' + +dependencies { + compile project(':rxjava-core') + compile 'org.javassist:javassist:3.17.1-GA' + + provided 'junit:junit:4.10' +} + +jar { + manifest { + name = 'rxjava-codegen' + instruction 'Bundle-Vendor', 'Netflix' + instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + instruction 'Fragment-Host', 'com.netflix.rxjava.core' + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/examples.txt b/language-adaptors/codegen/examples.txt new file mode 100644 index 0000000000..3cece97b6f --- /dev/null +++ b/language-adaptors/codegen/examples.txt @@ -0,0 +1,47 @@ +Here's the example source of Observable (heavily elided): + +public class rx.Observable { + public static Observable create(Func1, Subscription> func) { + return new Observable(func); + } + + public static Observable create(Object func) { + ... + } + + public static Observable take(final Observable items, final int num) { + return create(OperationTake.take(items, num)); + } + + public static Observable takeWhile(final Observable items, Func1 predicate) { + return create(OperationTakeWhile.takeWhile(items, predicate)); + } + + public Observable filter(Func1 predicate) { + return filter(this, predicate); + } + + public static Observable filter(Observable that, Func1 predicate) { + return create(OperationFilter.filter(that, predicate)); + } +} + +Groovy-friendly version adds: + +public class rx.Observable { + public static rx.Observable create(groovy.lang.Closure func) { + return create(new GroovyFunctionAdaptor(func)); + } + + public static rx.Observable takeWhile(final Observable items, groovy.lang.Closure predicate) { + return takeWhile(items, new GroovyFunctionAdaptor(predicate)); + } + + public rx.Observable filter(groovy.lang.Closure predicate) { + return filter(new GroovyFunctionAdaptor(predicate)); + } + + public static rxObservable filter(Observable that, groovy.lang.Closure predicate) { + return filter(that, new GroovyFunctionAdaptor(predicate)); + } +} diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java new file mode 100644 index 0000000000..d02d1a8fe1 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java @@ -0,0 +1,95 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.ArrayList; +import java.util.List; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; + +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * An implementation of method rewriting that leaves the rx.Function variant alone and adds dynamic language + * support by adding a new method per native function class. The new methods do nothing more than convert the + * native function class into an rx.Function and forward to the core implementation. + * + * For example, if {@code Observable} has a method Integer foo(String s, Func1 f, Integer i), then the + * rewritten class will add more 'foo's with dynamic language support. + * + * For Groovy (as an example), the new class would contain: + * Integer foo(String s, Func1 f, Integer i); + * Integer foo(String s, Closure f, Integer i) { + * return foo(s, new GroovyFunctionWrapper(f), i); + * } + */ +public class AddSpecializedDynamicMethodRewriter extends MethodRewriter { + + public AddSpecializedDynamicMethodRewriter(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor) { + this.enclosingClass = enclosingClass; + this.initialMethod = method; + this.adaptor = adaptor; + } + + @Override + public boolean needsRewrite() { + return true; + } + + @Override + public boolean isReplacement() { + return false; + } + + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + return duplicatedMethodsWithWrappedFunctionTypes(); + } + + @Override + protected String getRewrittenMethodBody(MethodRewriteRequest req) { + StringBuffer newBody = new StringBuffer(); + List argumentList = new ArrayList(); + newBody.append("{ return "); + if (Modifier.isStatic(initialMethod.getModifiers())) { + newBody.append(enclosingClass.getName() + "."); + } else { + newBody.append("this."); + } + newBody.append(initialMethod.getName()); + newBody.append("("); + try { + for (int i = 0; i < initialMethod.getParameterTypes().length; i++) { + CtClass argType = initialMethod.getParameterTypes()[i]; + if (isRxActionType(argType) && req.getActionAdaptorClass() != null) { + argumentList.add(getAdaptedArg(req.getActionAdaptorClass(), i + 1)); + } else if (isRxFunctionType(argType) && req.getFunctionAdaptorClass() != null) { + argumentList.add(getAdaptedArg(req.getFunctionAdaptorClass(), i + 1)); + } else { + argumentList.add(getUntouchedArg(i + 1)); + } + } + } catch (Exception ex) { + System.out.println("Exception while creating body for dynamic version of : " + initialMethod.getName()); + } + newBody.append(makeArgList(argumentList)); + newBody.append(")"); + newBody.append(";}"); + return newBody.toString(); + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java new file mode 100644 index 0000000000..80eeaee38d --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java @@ -0,0 +1,94 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; + +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * Java Executable that performs bytecode rewriting at compile-time. + * It accepts 2 args: dynamic language and dir to store result output classfiles + */ +public class ClassPathBasedRunner { + public static void main(String[] args) { + if (args.length != 2) { + System.out.println("Usage : Expects 2 args: (Language, File to place classfiles)"); + System.out.println("Currently supported languages: Groovy/Clojure/JRuby"); + System.exit(1); + } + String lang = args[0]; + File dir = new File(args[1]); + System.out.println("Using dir : " + dir + " for outputting classfiles"); + System.out.println("Looking for Adaptor for : " + lang); + String className = "rx.lang." + lang.toLowerCase() + "." + lang + "Adaptor"; + try { + ClassPool pool = ClassPool.getDefault(); + + Class adaptorClass = Class.forName(className); + System.out.println("Found Adaptor : " + adaptorClass); + FunctionLanguageAdaptor adaptor = (FunctionLanguageAdaptor) adaptorClass.newInstance(); + + Func1Generator func1Generator = new Func1Generator(pool, adaptor); + CtClass dynamicFunc1Class = func1Generator.createDynamicFunc1Class(); + writeClassFile(dynamicFunc1Class, dir); + pool.appendPathList(dir.getCanonicalPath()); + + ObservableRewriter rewriter = new ObservableRewriter(pool, adaptor); + for (Class observableClass: getObservableClasses()) { + CtClass rewrittenClass = rewriter.addMethods(observableClass); + writeClassFile(rewrittenClass, dir); + } + } catch (ClassNotFoundException ex) { + System.out.println("Did not find adaptor class : " + className); + System.exit(1); + } catch (InstantiationException ex) { + System.out.println("Reflective constuctor on : " + className + " failed"); + System.exit(1); + } catch (IllegalAccessException ex) { + System.out.println("Access to constructor on : " + className + " failed"); + System.exit(1); + } catch (Exception ex) { + System.out.println("Exception : " + ex.getMessage()); + System.exit(1); + } + } + + protected static void writeClassFile(CtClass clazz, File dir) { + try { + System.out.println("Using " + dir.getCanonicalPath() + " for dynamic class file"); + clazz.writeFile(dir.getCanonicalPath()); + } catch (java.io.IOException ioe) { + System.out.println("Could not write classfile to : " + dir.toString()); + System.exit(1); + } catch (javassist.CannotCompileException cce) { + System.out.println("Could not create a valid classfile"); + System.exit(2); + } + } + + private static List> getObservableClasses() { + List> observableClasses = new ArrayList>(); + observableClasses.add(rx.Observable.class); + observableClasses.add(rx.observables.BlockingObservable.class); + return observableClasses; + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodGroup.java b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodGroup.java new file mode 100644 index 0000000000..6475bef7bc --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodGroup.java @@ -0,0 +1,83 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javassist.CtMethod; +import javassist.Modifier; + +/** + * A group of methods that, if rewritten using the normal {@code AddSpecializedDynamicMethodRewriter} rewriting strategy, + * would conflict at the JVM level. These methods contain parameters with types that are different in Java, but the same + * in a dynamic language. + * + * The first example (more are expected) came up on the following buffer method signatures: + * public static Observable> buffer(Observable source, Func0> bufferClosingSelector) + * public Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) + * These methods both would be rewritten to buffer(Observable, groovy.lang.Closure) unless we handled the conflict + * explicitly. + */ +public class ConflictingMethodGroup { + private final List conflictingMethods; + + /** + * Comparator that provides a stable sort on methods. This is important, since we add 1 method per group. + * Sort by instance methods first, and then sort both instance and static methods by their hashes. + */ + private static Comparator methodSorter = new Comparator() { + @Override + public int compare(CtMethod m1, CtMethod m2) { + boolean isM1Static = Modifier.isStatic(m1.getModifiers()); + boolean isM2Static = Modifier.isStatic(m2.getModifiers()); + if (isM1Static && !isM2Static) { + return 1; + } else if (!isM1Static && isM2Static) { + return -1; + } else { + Integer hash1 = m1.hashCode(); + Integer hash2 = m2.hashCode(); + return hash1.compareTo(hash2); + } + } + }; + + public ConflictingMethodGroup(List conflictingMethods) { + assert(conflictingMethods.size() >= 2); + this.conflictingMethods = new ArrayList(conflictingMethods); + Collections.sort(this.conflictingMethods, methodSorter); + } + + /** + * Get all methods in the group + * @return all methods + */ + public List getMethods() { + return this.conflictingMethods; + } + + /** + * Given a method, is that method the first element in this group? + * @param method method to check + * @return true iff the method is the first element of this group + */ + public boolean hasHead(CtMethod method) { + return conflictingMethods.get(0).equals(method); + } +} diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodRewriter.java new file mode 100644 index 0000000000..dd805ff4a2 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodRewriter.java @@ -0,0 +1,207 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +import rx.util.functions.FunctionLanguageAdaptor; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.Func5; +import rx.util.functions.Func6; +import rx.util.functions.Func7; +import rx.util.functions.Func8; +import rx.util.functions.Func9; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action2; +import rx.util.functions.Action3; + +/** + * A strategy for rewriting a single method out of a {@code ConflictingMethodGroup}. The goal is to have a single + * method inserted that branches on the arity of the arguments and dynamically forwards to an Rx core method. + * + * The first example of needing conflict resolution (more are expected) came up on the following buffer method signatures: + * public static Observable> buffer(Observable source, Func0> bufferClosingSelector) + * public Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) + * These methods both would be rewritten to buffer(Observable, groovy.lang.Closure) unless we handled the conflict + * explicitly. + */ +public class ConflictingMethodRewriter extends MethodRewriter { + + private ConflictingMethodGroup conflictingMethodGroup; + + public ConflictingMethodRewriter(CtClass enclosingClass, CtMethod initialMethod, FunctionLanguageAdaptor adaptor, ConflictingMethodGroup conflictingMethodGroup) { + this.enclosingClass = enclosingClass; + this.initialMethod = initialMethod; + this.adaptor = adaptor; + this.conflictingMethodGroup = conflictingMethodGroup; + } + + @Override + public boolean needsRewrite() { + return true; + } + + @Override + public boolean isReplacement() { + return false; + } + + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + return duplicatedMethodsWithWrappedFunctionTypes(); + } + + //Sample body: + //public T buffer(Observable $1, Closure $2) { + // GroovyFunctionAdaptor adaptor = new GroovyFunctionAdaptor($2); + // int arity = adaptor.getArity(); + // if (arity == 0) { + // return buffer($1, ((Func0) adaptor)); + // } else if (arity == 1) { + // return buffer($1, ((Func1) adaptor)); + // } else { + // throw new RuntimeException("Couldn't match num args : " + arity); + // } + //} + + /* + * Known shortcoming - this only works for method groups that have 1 Function in the params + * This simplified the generated code and at the moment, there is only 1 set of methods which + * needs this treatment: + * public static Observable> buffer(Observable source, Func0> bufferClosingSelector) + * public Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) + */ + @Override + protected String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest) { + final Map arityMap = new HashMap(); + try { + for (CtMethod conflictingMethod: conflictingMethodGroup.getMethods()) { + CtClass[] parameterTypes = conflictingMethod.getParameterTypes(); + for (CtClass paramType: parameterTypes) { + if (isRxFunctionType(paramType) || isRxActionType(paramType)) { + Integer arity = getArityFromName(paramType); + arityMap.put(arity, conflictingMethod); + } + } + } + } catch (Exception e) { + System.out.println("Error determining arity map : " + e); + } + + final StringBuffer newBody = new StringBuffer(); + newBody.append("{"); + + try { + for (int i = 0; i < initialMethod.getParameterTypes().length; i++) { + CtClass argType = initialMethod.getParameterTypes()[i]; + if (isRxActionType(argType)) { + newBody.append(methodRewriteRequest.getActionAdaptorClass().getName() + " adaptor = "); + newBody.append(getAdaptedArg(methodRewriteRequest.getActionAdaptorClass(), i + 1)); + newBody.append(";"); + } else if (isRxFunctionType(argType)) { + newBody.append(methodRewriteRequest.getFunctionAdaptorClass().getName() + " adaptor = "); + newBody.append(getAdaptedArg(methodRewriteRequest.getFunctionAdaptorClass(), i + 1)); + newBody.append(";"); + } + } + + newBody.append("int arity = adaptor.getArity();"); + for (Integer arity: arityMap.keySet()) { + CtMethod methodWithArity = arityMap.get(arity); + newBody.append("if (arity == " + arity.toString() + ") {"); + newBody.append("return "); + if (Modifier.isStatic(methodWithArity.getModifiers())) { + newBody.append(enclosingClass.getName() + "."); + } else { + newBody.append("this."); + } + newBody.append(methodWithArity.getName()); + newBody.append("("); + List argumentList = new ArrayList(); + for (int i = 0; i < initialMethod.getParameterTypes().length; i++) { + CtClass argType = initialMethod.getParameterTypes()[i]; + if (isRxActionType(argType)) { + String actionArg = "((rx.util.functions.Action" + arity.toString() + ") adaptor)"; + argumentList.add(actionArg); + } else if (isRxFunctionType(argType)) { + String functionArg = "((rx.util.functions.Func" + arity.toString() + ") adaptor)"; + argumentList.add(functionArg); + } else { + argumentList.add(getUntouchedArg(i + 1)); + } + } + newBody.append(makeArgList(argumentList)); + newBody.append(");"); + newBody.append(" } else "); + } + newBody.append(" {"); + newBody.append("throw new RuntimeException(\"Couldn't match arity : \" + arity);"); + newBody.append("}"); + newBody.append("}"); + } catch (Exception e) { + System.out.println("Exception while create conflicted method body : " + e); + } + + return newBody.toString(); + } + + private int getArityFromName(CtClass clazz) { + if (clazz.getName().equals(Func0.class.getName())) { + return 0; + } else if (clazz.getName().equals(Func1.class.getName())) { + return 1; + } else if (clazz.getName().equals(Func2.class.getName())) { + return 2; + } else if (clazz.getName().equals(Func3.class.getName())) { + return 3; + } else if (clazz.getName().equals(Func4.class.getName())) { + return 4; + } else if (clazz.getName().equals(Func5.class.getName())) { + return 5; + } else if (clazz.getName().equals(Func6.class.getName())) { + return 6; + } else if (clazz.getName().equals(Func7.class.getName())) { + return 7; + } else if (clazz.getName().equals(Func8.class.getName())) { + return 8; + } else if (clazz.getName().equals(Func9.class.getName())) { + return 9; + } else if (clazz.getName().equals(Action0.class.getName())) { + return 0; + } else if (clazz.getName().equals(Action1.class.getName())) { + return 1; + } else if (clazz.getName().equals(Action2.class.getName())) { + return 2; + } else if (clazz.getName().equals(Action3.class.getName())) { + return 3; + } else { + return -1; + } + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/Func1Generator.java b/language-adaptors/codegen/src/main/java/rx/codegen/Func1Generator.java new file mode 100644 index 0000000000..290bbd0f18 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/Func1Generator.java @@ -0,0 +1,87 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +import rx.util.functions.Func1; +import rx.util.functions.Function; +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * Generate a class named {@code DYNAMIC_FUNC1_NAME}. This class gets constructed in code generated by + * {@code OneArgSubscribeOnMapMethodRewriter}. It supports a chain of instanceof checks that determine which + * native function class gets passed in, and converts it to an rx core {@code Action}. Contrast this with + * the version in core {@code actionIdentity}, which needs no runtime checks - it's just an identity. + * + * Once the Func1 has been constructed, you can pass that into the core subscribeWithConversion(Map, Func1) to + * apply the correct runtime checks/conversions. + */ +public class Func1Generator { + + private final ClassPool pool; + private final FunctionLanguageAdaptor adaptor; + + public Func1Generator(ClassPool pool, FunctionLanguageAdaptor adaptor) { + this.pool = pool; + this.adaptor = adaptor; + } + + static final String DYNAMIC_FUNC1_NAME = "Func1DynamicConverter"; + + public CtClass createDynamicFunc1Class() throws CannotCompileException, NotFoundException { + Class func1Class = Func1.class; + CtClass func1CtClass = pool.get(func1Class.getName()); + Class funcClass = Function.class; + CtClass funcCtClass = pool.get(funcClass.getName()); + CtClass objectCtClass = pool.get(Object.class.getName()); + CtClass[] params = new CtClass[1]; + params[0] = objectCtClass; + + CtClass conversionClass = pool.makeClass(DYNAMIC_FUNC1_NAME); + StringBuffer methodBody = new StringBuffer(); + StringBuffer classCheckBranching = new StringBuffer(); + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + + classCheckBranching.append("} else if ($1 instanceof " + nativeFunctionClass.getName() + ") {\n"); + classCheckBranching.append("return new " + actionAdaptorClass.getName() + "((" + nativeFunctionClass.getName() + ") $1);\n"); + } + + methodBody.append("{\n"); + methodBody.append("if ($1 instanceof " + funcClass.getName() + ") {\n"); + methodBody.append("return (" + funcClass.getName() + ") $1;\n"); + methodBody.append(classCheckBranching.toString()); + methodBody.append("} else {\n"); + methodBody.append("throw new RuntimeException(\"Can't convert from class : \" + $1.getClass().getName());\n"); + methodBody.append("}\n"); + methodBody.append("}\n"); + + CtMethod callMethod = new CtMethod(objectCtClass, "call", params, conversionClass); + callMethod.setModifiers(Modifier.PUBLIC); + callMethod.setBody(methodBody.toString()); + conversionClass.addMethod(callMethod); + conversionClass.addInterface(func1CtClass); + int classModifiers = conversionClass.getModifiers(); + conversionClass.setModifiers(Modifier.PUBLIC); + return conversionClass; + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriteRequest.java b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriteRequest.java new file mode 100644 index 0000000000..174bc95b03 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriteRequest.java @@ -0,0 +1,54 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.List; + +import javassist.CtClass; + +/** + * Immutable class representing a request to rewrite a method, without specifying how + */ +public class MethodRewriteRequest { + private Class functionAdaptorClass; + private Class actionAdaptorClass; + private List newArgTypes; + + /** + * Public constructor + * @param functionAdaptorClass adaptor for rewriting a native function to an Rx core {@code Function}. + * @param actionAdaptorClass adaptor for rewriting a native action to an Rx core {@code Action}. + * @param newArgTypes args of the new method that needs to be written + */ + public MethodRewriteRequest(Class functionAdaptorClass, Class actionAdaptorClass, List newArgTypes) { + this.functionAdaptorClass = functionAdaptorClass; + this.actionAdaptorClass = actionAdaptorClass; + this.newArgTypes = newArgTypes; + } + + public Class getFunctionAdaptorClass() { + return this.functionAdaptorClass; + } + + public Class getActionAdaptorClass() { + return this.actionAdaptorClass; + } + + public List getNewArgTypes() { + return this.newArgTypes; + } + +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java new file mode 100644 index 0000000000..9ddcf0b049 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java @@ -0,0 +1,406 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import rx.util.functions.Action; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action2; +import rx.util.functions.Action3; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.Func5; +import rx.util.functions.Func6; +import rx.util.functions.Func7; +import rx.util.functions.Func8; +import rx.util.functions.Func9; +import rx.util.functions.Function; +import rx.util.functions.FunctionLanguageAdaptor; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; + +/** + * A strategy for how to rewrite methods. This uses the javassist library heavily. For every method in an + * {@code Observable} class, a {@code MethodRewriter} gets constructed and used to output a method or methods + * that add dynamic language support to the {@code Observable} class. + */ +abstract class MethodRewriter { + + protected static ClassPool pool = ClassPool.getDefault(); + protected CtClass enclosingClass; + protected CtMethod initialMethod; + protected FunctionLanguageAdaptor adaptor; + + /** + * Public static constructor + * @param enclosingClass class in which method exists + * @param method method to rewrite + * @param adaptor Language adaptor which describes how to add dynamic language support + * @return a strategy for rewriting the given {@code CtMethod} + * @throws Exception on any error + */ + public static MethodRewriter from(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor, List conflictingMethodGroups) throws Exception { + ConflictingMethodGroup conflictingMethodGroup = getConflictingMethodGroup(conflictingMethodGroups, method); + if (conflictingMethodGroup != null) { + if (conflictingMethodGroup.hasHead(method)) { + return new ConflictingMethodRewriter(enclosingClass, method, adaptor, conflictingMethodGroup); + } + } else { + if (enclosingClass.equals(method.getDeclaringClass())) { + if (isOneArgSubscribeOnMap(enclosingClass, method)) { + return new OneArgSubscribeOnMapMethodRewriter(enclosingClass, method, adaptor); + } else if (argTypeIncludesRxFunction(enclosingClass, method)) { + return new AddSpecializedDynamicMethodRewriter(enclosingClass, method, adaptor); + } + } + } + return new NoOpMethodRewriter(); + } + + /** + * Does the method require a rewrite at all? + */ + public abstract boolean needsRewrite(); + + /** + * Should the method replace the original or add additional methods? + */ + public abstract boolean isReplacement(); + + /** + * Given arguments on the initial method, what methods should get generated? + * @param initialArgTypes arguments of the initial method + * @return a list of method rewrites to attempt + */ + protected abstract List getNewMethodsToRewrite(CtClass[] initialArgTypes); + + /** + * Return the body of the rewritten body for the {@code MethodRewriteRequest} that gets made + * @param method initial method + * @param enclosingClass {@code Observable} class in which initial method exists + * @param methodRewriteRequest request for rewrite (includes args of new method and adaptor for how to add dynamic + * support for a given language) + * @return {@code String} body of new method + */ + protected abstract String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest); + + /** + * Return all the fully-formed {@code CtMethod}s that the rewriting process generates, given the initial method + * @return new {@code CtMethod}s + * @throws Exception on any error + */ + public List getNewMethods() throws Exception { + List methodList = new ArrayList(); + + CtClass[] initialArgTypes = initialMethod.getParameterTypes(); + + List newMethodRewriteRequests = getNewMethodsToRewrite(initialArgTypes); + for (MethodRewriteRequest newMethodRewriteRequest: newMethodRewriteRequests) { + List newArgTypes = newMethodRewriteRequest.getNewArgTypes(); + CtClass[] newArgTypeArray = newArgTypes.toArray(new CtClass[newArgTypes.size()]); + CtMethod newMethod = new CtMethod(initialMethod.getReturnType(), initialMethod.getName(), newArgTypeArray, enclosingClass); + newMethod.setModifiers(initialMethod.getModifiers()); + String newBody = getRewrittenMethodBody(newMethodRewriteRequest); + newMethod.setBody(newBody); + methodList.add(newMethod); + + //Uncomment this to see the rewritten method body + //debugMethodRewriting(initialMethod, newMethod, newBody); + } + return methodList; + } + + /** + * Verbose output on how methods are getting rewritten + * @param initialMethod initial method + * @param newMethod new method + * @param newBody new method body + * @throws NotFoundException + */ + private static void debugMethodRewriting(CtMethod initialMethod, CtMethod newMethod, String newBody) throws NotFoundException { + List initialArgStrings = mapClassNames(initialMethod.getParameterTypes()); + List finalArgStrings = mapClassNames(newMethod.getParameterTypes()); + + String initialArgString = makeArgList(initialArgStrings); + String finalArgString = makeArgList(finalArgStrings); + + System.out.println(initialMethod.getReturnType().getName() + " " + initialMethod.getName() + "(" + initialArgString +") --> " + newMethod.getReturnType().getName() + " " + newMethod.getName() + "(" + finalArgString + ")"); + System.out.println(" " + newBody.toString()); + } + + private static ConflictingMethodGroup getConflictingMethodGroup(List conflictingMethodGroups, CtMethod method) { + for (ConflictingMethodGroup group: conflictingMethodGroups) { + if (group.getMethods().contains(method)) { + return group; + } + } + return null; + } + + /** + * We need to special case the subscribe(Map m) method. Check if the given method is subscribe(Map m) + * @param enclosingClass {@code Observable} class where method lives + * @param method method to check + * @return true iff the passed-in method is subscribe(Map m) + * @throws NotFoundException + */ + private static boolean isOneArgSubscribeOnMap(CtClass enclosingClass, CtMethod method) throws Exception { + String methodName = method.getName(); + CtClass[] args = method.getParameterTypes(); + if (args.length == 1 && methodName.equals("subscribe")) { + if (isMap(args[0])) { + return true; + } + } + return false; + } + + /** + * Check if the passed-in method has any arguments which are an Rx core function type. + * If so, we will need to add a variant of this method that can be called with a native function class instead. + * @param method method to check + * @return true iff the method has an Rx core function type in its args + * @throws Exception + */ + private static boolean argTypeIncludesRxFunction(CtClass enclosingClass, CtMethod method) throws Exception { + CtClass[] args = method.getParameterTypes(); + for (CtClass methodArg: args) { + if (isRxFunctionType(methodArg) || isRxActionType(methodArg)) { + return true; + } + } + return false; + } + + /** + * Check if a class is an Rx core {@code Function} type + * @param type class to check + * @return true iff the passed-in class is a {@code Function} + * @throws Exception + */ + protected static boolean isRxFunctionType(CtClass type) throws Exception { + return implementsInterface(type, Function.class); + } + + /** + * Check if a class is an Rx core {@code Action} type + * @param type class to check + * @return true iff the passed-in class is a {@code Action} + * @throws Exception + */ + protected static boolean isRxActionType(CtClass type) throws Exception { + return implementsInterface(type, Action.class); + } + + /** + * Check if the class is any subclass of {@code Map}. + * @param type class to check + * @return true iff the passed-in class in a {@code Map} + * @throws Exception + */ + protected static boolean isMap(CtClass type) throws Exception { + return implementsInterface(type, Map.class); + } + + /** + * Helper method to check if a given class implements a given interface (recursively) + * @param type class to check + * @param interfaceClass interface to check implementation of + * @return true iff the passed-in type implements the passed-in interface + * @throws Exception + */ + protected static boolean implementsInterface(CtClass type, Class interfaceClass) throws Exception { + // Did I pass in the exact interface? + if (type.getName().equals(interfaceClass.getName())) { + return true; + } + CtClass[] implementedInterfaces = type.getInterfaces(); + if (implementedInterfaces.length == 0) { + //no more superclasses to check + return false; + } else { + for (CtClass implementedInterface: implementedInterfaces) { + if (implementedInterface.getName().equals(interfaceClass.getName())) { + return true; + } + if (implementsInterface(implementedInterface, interfaceClass)) { + return true; + } + } + } + return false; + } + + /** + * Given an initial method, return {@code MethodRewriteRequest}s that replace all Rx functions with + * the native function equivalent, so that dynamic languages can target this method. + * @return {@code MethodRewriteRequest}s that dynamic languages can natively target + */ + protected List duplicatedMethodsWithWrappedFunctionTypes() { + List reqs = new ArrayList(); + + try { + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + + CtClass[] argTypes = initialMethod.getParameterTypes(); + List parameters = new ArrayList(); + + for (CtClass argType : argTypes) { + if (isRxFunctionType(argType) || isRxActionType(argType)) { + // needs conversion + parameters.add(pool.get(nativeFunctionClass.getName())); + } else { + // no conversion, copy through + parameters.add(argType); + } + } + MethodRewriteRequest req = new MethodRewriteRequest(functionAdaptorClass, actionAdaptorClass, parameters); + reqs.add(req); + } + } catch (Exception ex) { + System.out.println("Exception while rewriting method : " + initialMethod.getName()); + } + return reqs; + } + + /** + * Convert {@code CtClass} array to {@code List} + * @param classes classes to get names of + * @return list of class names + */ + protected static List mapClassNames(CtClass[] classes) { + List strs = new ArrayList(); + for (CtClass clazz: classes) { + strs.add(clazz.getName()); + } + return strs; + } + + /** + * Convert {@code List} into comma-separated single {@code String}. + * @param argTypes list of argument strings + * @return comma-separated list of strings + */ + protected static String makeArgList(List argTypes) { + if (argTypes.size() > 0) { + StringBuffer buffer = new StringBuffer(argTypes.get(0)); + for (String argType: argTypes.subList(1, argTypes.size())) { + buffer.append("," + argType); + } + return buffer.toString(); + } + return ""; + } + + /** + * Within a rewritten method body, wrap an arg in an adaptor class in a javassist-friendly way + * Ex: If adaptorClass is FooAdaptor, then this method performs: "$2" -> "new FooAdaptor($2)" + * @param adaptorClass adaptor intended to wrap the original arg + * @param index position of arg (this is how javassist calls args) + * @return String of wrapped arg + */ + protected static String getAdaptedArg(Class adaptorClass, int index) { + StringBuffer buffer = new StringBuffer(); + buffer.append("new "); + buffer.append(adaptorClass.getName()); + buffer.append("("); + buffer.append(getUntouchedArg(index)); + buffer.append(")"); + return buffer.toString(); + } + + /** + * Rewrite an arg (based on position) in a javassist-friendly way. + * Ex: 2 -> "$2" + * @param index arg index + * @return String of arg (in javassist-format) + */ + protected static String getUntouchedArg(int index) { + StringBuffer buffer = new StringBuffer(); + buffer.append("$"); + buffer.append(index); + return buffer.toString(); + } + + public static class UnitTest { + private static ClassPool pool = ClassPool.getDefault(); + + @Test + public void testIsRxFunctionType() { + try { + assertTrue(isRxFunctionType(pool.get(Function.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func0.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func1.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func2.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func3.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func4.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func5.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func6.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func7.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func8.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func9.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action0.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action1.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action2.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action3.class.getName()))); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void testIsRxActionType() { + try { + assertFalse(isRxActionType(pool.get(Function.class.getName()))); + assertFalse(isRxActionType(pool.get(Func0.class.getName()))); + assertFalse(isRxActionType(pool.get(Func1.class.getName()))); + assertFalse(isRxActionType(pool.get(Func2.class.getName()))); + assertFalse(isRxActionType(pool.get(Func3.class.getName()))); + assertFalse(isRxActionType(pool.get(Func4.class.getName()))); + assertFalse(isRxActionType(pool.get(Func5.class.getName()))); + assertFalse(isRxActionType(pool.get(Func6.class.getName()))); + assertFalse(isRxActionType(pool.get(Func7.class.getName()))); + assertFalse(isRxActionType(pool.get(Func8.class.getName()))); + assertFalse(isRxActionType(pool.get(Func9.class.getName()))); + assertTrue(isRxActionType(pool.get(Action.class.getName()))); + assertTrue(isRxActionType(pool.get(Action0.class.getName()))); + assertTrue(isRxActionType(pool.get(Action1.class.getName()))); + assertTrue(isRxActionType(pool.get(Action2.class.getName()))); + assertTrue(isRxActionType(pool.get(Action3.class.getName()))); + } catch (Exception e) { + fail(e.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java new file mode 100644 index 0000000000..48e3dbec29 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java @@ -0,0 +1,48 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.ArrayList; +import java.util.List; + +import javassist.CtClass; +import javassist.CtMethod; + +/** + * Method that needs no rewriting to function with dynamic language. + */ +public class NoOpMethodRewriter extends MethodRewriter { + + @Override + public boolean needsRewrite() { + return false; + } + + @Override + public boolean isReplacement() { + return false; + } + + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + return new ArrayList(); + } + + @Override + protected String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest) { + return ""; + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java new file mode 100644 index 0000000000..b519aa5636 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java @@ -0,0 +1,155 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * Given an initial {@code Observable} class, rewrite it with all necessary method to support a particular + * dynamic language (as specified by the {@code FunctionLanguageAdaptor}). + */ +public class ObservableRewriter { + + private final ClassPool pool; + private final FunctionLanguageAdaptor adaptor; + + public ObservableRewriter(ClassPool pool, FunctionLanguageAdaptor adaptor) { + this.pool = pool; + this.adaptor = adaptor; + } + + /** + * Entry point - given the passed-in class, add dynamic language support to the class and write it out + * to the filesystem + * + * @param initialClass class to add dynamic support to + * @return class with all rewritten methods + */ + public CtClass addMethods(Class initialClass) { + if (!initialClass.getName().startsWith("rx.")) { + throw new IllegalStateException("Refusing to rewrite a class that is not a core Rx Observable!"); + } + + Set> nativeFunctionClasses = adaptor.getAllClassesToRewrite(); + System.out.println("Adding dynamic language support to : " + initialClass.getSimpleName()); + for (Class nativeFunctionClass: nativeFunctionClasses) { + System.out.println(" * Adding : " + nativeFunctionClass.getName()); + } + + try { + CtClass clazz = pool.get(initialClass.getName()); + CtClass rewrittenClass = rewriteMethodsWithRxArgs(clazz); + return rewrittenClass; + } catch (NotFoundException e) { + throw new RuntimeException("Failed to add language adaptor methods as could not find observable Class named " + initialClass.getName(), e); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to add language adaptor methods.", e); + } + } + + /** + * Loop over all methods and rewrite each (if necessary), returning the end result + * @param enclosingClass + * @return + * @throws Exception + */ + private CtClass rewriteMethodsWithRxArgs(CtClass enclosingClass) throws Exception { + List conflictingMethodGroups = getConflictingMethodGroups(enclosingClass); + for (CtMethod method : enclosingClass.getMethods()) { + MethodRewriter methodRewriter = MethodRewriter.from(enclosingClass, method, adaptor, conflictingMethodGroups); + if (methodRewriter.needsRewrite()) { + if (methodRewriter.isReplacement()) { + enclosingClass.removeMethod(method); + } + List newMethods = methodRewriter.getNewMethods(); + for (CtMethod cm: newMethods) { + enclosingClass.addMethod(cm); + } + } + } + return enclosingClass; + } + + /** + * Iterate through all the methods in the given class and find methods which would collide + * if they were rewritten with dynamic wrappers. + * @param enclosingClass class to find conflicting methods in + * @return list of {@code ConflictingMethodGroup}s found in given class + */ + private List getConflictingMethodGroups(CtClass enclosingClass) throws Exception { + List groups = new ArrayList(); + + Map> methodHashes = new HashMap>(); + for (CtMethod m: enclosingClass.getMethods()) { + if (enclosingClass.equals(m.getDeclaringClass())) { + Integer hash = getHash(m); + if (methodHashes.containsKey(hash)) { + List existingMethods = methodHashes.get(hash); + existingMethods.add(m); + methodHashes.put(hash, existingMethods); + } else { + List newMethodList = new ArrayList(); + newMethodList.add(m); + methodHashes.put(hash, newMethodList); + } + } + } + + for (Integer key: methodHashes.keySet()) { + List methodsWithSameHash = methodHashes.get(key); + if (methodsWithSameHash.size() > 1) { + ConflictingMethodGroup group = new ConflictingMethodGroup(methodsWithSameHash); + groups.add(group); + } + } + + return groups; + } + + //we care about collisions post-rewrite. So take a hash over method name and arguments + //where every Rx {@code Action} or Rx {@code Function} is hashed to the same value + // in this case {@code Function} + private int getHash(CtMethod m) throws Exception { + final String delimiter = ","; + + final StringBuffer buffer = new StringBuffer(); + final String name = m.getName(); + buffer.append(name); + buffer.append(delimiter); + for (CtClass argClass: m.getParameterTypes()) { + if (MethodRewriter.isRxActionType(argClass) || MethodRewriter.isRxFunctionType(argClass)) { + buffer.append("RxFunc"); + } else { + buffer.append(argClass.getName()); + } + buffer.append(delimiter); + } + return buffer.toString().hashCode(); + } +} + diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java new file mode 100644 index 0000000000..8d87b17749 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java @@ -0,0 +1,99 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.codegen; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import rx.util.functions.Func1; +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * An implementation of method rewriting that handles subscribe(Map) specially. The core method has the type signature + * subscribe(Map m). Since {@code Map} is generic, we can't add a new method + * subscribe(Map m) (or some other dynamic variant), since the JVM believes those 2 methods + * are the same because of type erasure. + * + * Instead, we rewrite the body of the method to handle any necessary run-time checks and casting, while leaving the + * signature along. + * + * For Groovy (as an example), the new class would contain: + * subscribe(Map callbacks) { + * Func1 dynamicLanguageSupport = new DynamicConversion(); + * return subscribeWithConversion(callbacks, dynamicLanguageSupport); + * } + * {@see Func1Generator} for details on how the Func1 is generated to have the appropriate runtime checks. + */ +public class OneArgSubscribeOnMapMethodRewriter extends MethodRewriter { + + private static final String DYNAMIC_FUNC1_NAME = "Func1DynamicConverter"; + + public OneArgSubscribeOnMapMethodRewriter(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor) { + this.enclosingClass = enclosingClass; + this.initialMethod = method; + this.adaptor = adaptor; + } + + @Override + public boolean needsRewrite() { + return true; + } + + @Override + public boolean isReplacement() { + return true; + } + + /** + * We want to return a subscribe(Map m) method. Note that this is true no matter how many + * dynamic languages we intend to support, since Map is generic. + */ + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + List reqs = new ArrayList(); + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + reqs.add(new MethodRewriteRequest(functionAdaptorClass, actionAdaptorClass, Arrays.asList(initialArgTypes))); + return reqs; + } + return reqs; + } + + @Override + protected String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest) { + Class func1Class = Func1.class; + StringBuffer methodBody = new StringBuffer(); + + try { + CtClass dynamicFunc1CtClass = pool.get(DYNAMIC_FUNC1_NAME); + + methodBody.append("{"); + methodBody.append(func1Class.getName() + " dynamicLanguageSupport = new " + dynamicFunc1CtClass.getName() + "();\n"); + methodBody.append("return subscribeWithConversion($1, dynamicLanguageSupport);"); + methodBody.append("}"); + } catch (NotFoundException ex) { + System.out.println("Couldn't find class for : " + DYNAMIC_FUNC1_NAME); + throw new RuntimeException("Couldn't find class for : " + DYNAMIC_FUNC1_NAME); + } + return methodBody.toString(); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-clojure/build.gradle b/language-adaptors/rxjava-clojure/build.gradle index 0ea7feb27c..5731e9acf5 100644 --- a/language-adaptors/rxjava-clojure/build.gradle +++ b/language-adaptors/rxjava-clojure/build.gradle @@ -1,17 +1,14 @@ -apply plugin: 'java' apply plugin: 'clojure' -apply plugin: 'eclipse' -apply plugin: 'idea' apply plugin: 'osgi' dependencies { - compile project(':rxjava-core') - provided 'org.clojure:clojure:1.4.+' + core project(':rxjava-core') + provided project(':rxjava-core') + provided project(':language-adaptors:codegen') + compile 'org.clojure:clojure:1.4.+' + compile 'clj-http:clj-http:0.6.4' // https://clojars.org/clj-http provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' - - // clojure - testCompile 'clj-http:clj-http:0.6.4' // https://clojars.org/clj-http } /* @@ -26,57 +23,51 @@ buildscript { } repositories { - mavenCentral() clojarsRepo() } /* - * Add Counterclockwise and include 'provided' dependencies + * Add Counterclockwise to Eclipse */ eclipse { project { natures "ccw.nature" } - classpath { - plusConfigurations += configurations.provided - downloadSources = true - downloadJavadoc = true - } } -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } +tasks.clojureTest { + classpath = classpath + configurations.provided } +tasks.compileExamplesClojure { + classpath = classpath + configurations.provided +} -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided +task createAdaptedObservable(type: JavaExec) { + main = 'rx.codegen.ClassPathBasedRunner' + classpath = sourceSets.main.runtimeClasspath + configurations.provided + args = ["Clojure", codeGenOutputDir] - downloadSources = true - downloadJavadoc = true - } + inputs.files(sourceSets.main.runtimeClasspath) + outputs.dir(codeGenOutputDir) } -// include /src/examples folder -sourceSets { - examples -} +tasks.test { + dependsOn(createAdaptedObservable) -// make 'examples' use the same classpath -configurations { - examplesCompile.extendsFrom compile - examplesRuntime.extendsFrom runtime + //Reorders the classpath so that the newly-create Observables win + classpath = createAdaptedObservable.outputs.files + configurations.provided + sourceSets.test.runtimeClasspath } -// include 'examples' in build task -build.dependsOn examplesClasses +tasks.jar { + dependsOn(createAdaptedObservable) + + from (zipTree(configurations.core.singleFile)) { + exclude "rx/Observable.class" + exclude "rx/observables/BlockingObservable.class" + } + from(codeGenOutputDir) -jar { manifest { name = 'rxjava-clojure' instruction 'Bundle-Vendor', 'Netflix' @@ -84,4 +75,4 @@ jar { instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' instruction 'Fragment-Host', 'com.netflix.rxjava.core' } -} \ No newline at end of file +} diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java new file mode 100644 index 0000000000..86a7d4bbf2 --- /dev/null +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java @@ -0,0 +1,43 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.clojure; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +import clojure.lang.IFn; +import clojure.lang.RT; +import clojure.lang.Var; + +/** + * Concrete wrapper that accepts an {@code IFn} and produces any needed Rx {@code Action}. + * @param + */ +public class ClojureActionWrapper extends ClojureArityChecker implements Action0, Action1 { + public ClojureActionWrapper(IFn ifn) { + this.ifn = ifn; + } + + @Override + public void call() { + ifn.invoke(); + } + + @Override + public void call(T1 t1) { + ifn.invoke(t1); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java index 4420f7c919..fec78b95b9 100644 --- a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java @@ -15,7 +15,10 @@ */ package rx.lang.clojure; -import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -29,60 +32,31 @@ import clojure.lang.RT; import clojure.lang.Var; +/** + * Defines the single Clojure class {@code IFn} that should map to Rx functions + * For now, a unit test to prove the Clojure integration works + */ public class ClojureAdaptor implements FunctionLanguageAdaptor { @Override - public Object call(Object function, Object[] args) { - if (args.length == 0) { - return ((IFn) function).invoke(); - } else if (args.length == 1) { - return ((IFn) function).invoke(args[0]); - } else if (args.length == 2) { - return ((IFn) function).invoke(args[0], args[1]); - } else if (args.length == 3) { - return ((IFn) function).invoke(args[0], args[1], args[2]); - } else if (args.length == 4) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3]); - } else if (args.length == 5) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4]); - } else if (args.length == 6) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5]); - } else if (args.length == 7) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - } else if (args.length == 8) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); - } else if (args.length == 9) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); - } else if (args.length == 10) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); - } else if (args.length == 11) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); - } else if (args.length == 12) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); - } else if (args.length == 13) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); - } else if (args.length == 14) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); - } else if (args.length == 15) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); - } else if (args.length == 16) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]); - } else if (args.length == 17) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]); - } else if (args.length == 18) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17]); - } else if (args.length == 19) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18]); - } else if (args.length == 20) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19]); - } else { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19], Arrays.copyOfRange(args, 20, args.length)); - } + public Map, Class> getFunctionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(IFn.class, ClojureFunctionWrapper.class); + return m; + } + + @Override + public Map, Class> getActionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(IFn.class, ClojureActionWrapper.class); + return m; } @Override - public Class[] getFunctionClass() { - return new Class[] { IFn.class }; + public Set> getAllClassesToRewrite() { + Set> classes = new HashSet>(); + classes.add(IFn.class); + return classes; } public static class UnitTest { diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureArityChecker.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureArityChecker.java new file mode 100644 index 0000000000..2506e1058a --- /dev/null +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureArityChecker.java @@ -0,0 +1,37 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.clojure; + +import clojure.lang.IFn; + +import java.lang.reflect.Method; + +/** + * Base class for Clojure adaptors that knows how to get the arity of an {@code IFn}. + */ +public abstract class ClojureArityChecker { + protected IFn ifn; + + //Hoping this is correct: + //http://stackoverflow.com/questions/1696693/clojure-how-to-find-out-the-arity-of-function-at-runtime + int getArity() { + Class ifnClass = ifn.getClass(); + for (Method m: ifnClass.getDeclaredMethods()) { + return m.getParameterTypes().length; + } + return 0; + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java new file mode 100644 index 0000000000..3ad96f8e8b --- /dev/null +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java @@ -0,0 +1,64 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.clojure; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.FunctionLanguageAdaptor; + +import clojure.lang.IFn; + +/** + * Concrete wrapper that accepts an {@code IFn} and produces any needed Rx {@code Function}. + * @param + * @param + * @param + * @param + * @param + */ +public class ClojureFunctionWrapper extends ClojureArityChecker implements Func0, Func1, Func2, Func3, Func4 { + public ClojureFunctionWrapper(IFn ifn) { + this.ifn = ifn; + } + + @Override + public R call() { + return (R) ifn.invoke(); + } + + @Override + public R call(T1 t1) { + return (R) ifn.invoke(t1); + } + + @Override + public R call(T1 t1, T2 t2) { + return (R) ifn.invoke(t1, t2); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + return (R) ifn.invoke(t1, t2, t3); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + return (R) ifn.invoke(t1, t2, t3, t4); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/build.gradle b/language-adaptors/rxjava-groovy/build.gradle index 91c060193d..fea197d263 100644 --- a/language-adaptors/rxjava-groovy/build.gradle +++ b/language-adaptors/rxjava-groovy/build.gradle @@ -1,48 +1,44 @@ -apply plugin: 'java' apply plugin: 'groovy' -apply plugin: 'eclipse' -apply plugin: 'idea' apply plugin: 'osgi' dependencies { - compile project(':rxjava-core') - groovy 'org.codehaus.groovy:groovy-all:2.+' + core project(':rxjava-core') + provided project(':rxjava-core') + provided project(':language-adaptors:codegen') + compile 'org.codehaus.groovy:groovy-all:2.+' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' } -// include /src/examples folder -sourceSets { - examples -} +task createAdaptedObservable(type: JavaExec) { + main = 'rx.codegen.ClassPathBasedRunner' + classpath = sourceSets.main.runtimeClasspath + configurations.provided + args = ["Groovy", codeGenOutputDir] -// make 'examples' use the same classpath -configurations { - examplesCompile.extendsFrom compile - examplesRuntime.extendsFrom runtime + inputs.files(sourceSets.main.runtimeClasspath) + outputs.dir(codeGenOutputDir) } -// include 'examples' in build task -build.dependsOn examplesClasses +tasks.test { + dependsOn(createAdaptedObservable) -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } + //Reorders the classpath so that the newly-create Observables win + classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath } -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } +tasks.compileExamplesGroovy { + classpath = classpath + configurations.provided } -jar { +tasks.jar { + dependsOn(createAdaptedObservable) + + from (zipTree(configurations.core.singleFile)) { + exclude "rx/Observable.class" + exclude "rx/observables/BlockingObservable.class" + } + from(codeGenOutputDir) + manifest { name = 'rxjava-groovy' instruction 'Bundle-Vendor', 'Netflix' @@ -50,4 +46,4 @@ jar { instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' instruction 'Fragment-Host', 'com.netflix.rxjava.core' } -} \ No newline at end of file +} diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java new file mode 100644 index 0000000000..36e88794eb --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java @@ -0,0 +1,41 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.groovy; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +import groovy.lang.Closure; + +/** + * Concrete wrapper that accepts a {@code Closure} and produces any needed Rx {@code Action}. + * @param + */ +public class GroovyActionWrapper extends GroovyArityChecker implements Action0, Action1 { + public GroovyActionWrapper(Closure closure) { + this.closure = closure; + } + + @Override + public void call() { + closure.call(); + } + + @Override + public void call(T1 t1) { + closure.call(t1); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java index 70cef9c18e..55e416ac1b 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java @@ -15,17 +15,40 @@ */ package rx.lang.groovy; -import groovy.lang.Closure; +import java.util.HashMap; +import java.util.Map; + import rx.util.functions.FunctionLanguageAdaptor; +import groovy.lang.Closure; + +import java.util.HashSet; +import java.util.Set; + +/** + * Defines the single Groovy class {@code Closure} that should map to Rx functions + */ public class GroovyAdaptor implements FunctionLanguageAdaptor { @Override - public Object call(Object function, Object[] args) { - return ((Closure) function).call(args); + public Map, Class> getFunctionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(Closure.class, GroovyFunctionWrapper.class); + return m; + } + + @Override + public Map, Class> getActionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(Closure.class, GroovyActionWrapper.class); + return m; } - public Class[] getFunctionClass() { - return new Class[] { Closure.class }; + @Override + public Set> getAllClassesToRewrite() { + Set> groovyClasses = new HashSet>(); + groovyClasses.add(Closure.class); + return groovyClasses; } } + diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyArityChecker.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyArityChecker.java new file mode 100644 index 0000000000..c23783b1cb --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyArityChecker.java @@ -0,0 +1,29 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.groovy; + +import groovy.lang.Closure; + +/** + * Base class for Groovy adaptors that knows how to get the arity of a {@code Closure}. + */ +public abstract class GroovyArityChecker { + protected Closure closure; + + public int getArity() { + return closure.getMaximumNumberOfParameters(); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java new file mode 100644 index 0000000000..0d0946c38d --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java @@ -0,0 +1,65 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.groovy; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.FuncN; +import rx.util.functions.FunctionLanguageAdaptor; + +import groovy.lang.Closure; + +/** + * Concrete wrapper that accepts a {@code Closure} and produces any needed Rx {@code Function}. + * @param + * @param + * @param + * @param + * @param + */ +public class GroovyFunctionWrapper extends GroovyArityChecker implements Func0, Func1, Func2, Func3, Func4 { + public GroovyFunctionWrapper(Closure closure) { + this.closure = closure; + } + + @Override + public R call() { + return (R) closure.call(); + } + + @Override + public R call(T1 t1) { + return (R) closure.call(t1); + } + + @Override + public R call(T1 t1, T2 t2) { + return (R) closure.call(t1, t2); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + return (R) closure.call(t1, t2, t3); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + return (R) closure.call(t1, t2, t3, t4); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy index dd36af1088..b539e6c65e 100644 --- a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy +++ b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy @@ -21,20 +21,27 @@ import static org.mockito.Mockito.*; import java.util.Arrays; import java.util.Collection; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; +import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import rx.Notification; import rx.Observable; import rx.Observer; import rx.Subscription; +import rx.concurrency.TestScheduler; import rx.observables.GroupedObservable; import rx.subscriptions.Subscriptions; +import rx.util.BufferClosings; +import rx.util.BufferOpenings; +import rx.util.functions.Action0; import rx.util.functions.Func1; def class ObservableTests { @@ -45,9 +52,48 @@ def class ObservableTests { @Mock Observer w; + @Mock Observer> observer; + + TestScheduler scheduler; + @Before public void before() { MockitoAnnotations.initMocks(this); + scheduler = new TestScheduler(); + } + + @Test + public void testSubscribeWithMap() { + def o = Observable.from(1, 2, 3) + def subscribeMap = [ + "onNext" : { i -> a.received(i) }, + "onError" : { e -> a.received(e) }, + "onCompleted" : { a.received("COMPLETE") } + ] + o.subscribe(subscribeMap) + verify(a, times(1)).received(1) + verify(a, times(1)).received(2) + verify(a, times(1)).received(3) + verify(a, times(1)).received("COMPLETE") + } + + @Test + public void testSubscribeWithMapAndScheduler() { + def o = Observable.from(1, 2, 3) + def subscribeMap = [ + "onNext" : { i -> a.received(i) }, + "onError" : { e -> a.received(e) }, + "onCompleted" : { a.received("COMPLETE") } + ] + o.subscribe(subscribeMap, scheduler) + verify(a, never()).received(any(Object.class)) + + scheduler.advanceTimeBy(5, TimeUnit.SECONDS) + + verify(a, times(1)).received(1) + verify(a, times(1)).received(2) + verify(a, times(1)).received(3) + verify(a, times(1)).received("COMPLETE") } @Test @@ -306,7 +352,107 @@ def class ObservableTests { assertEquals(6, count); } - + + /*@Test + public void testStaticBufferOfCollectedValues() { + def source = Observable.create( + { observer -> + push(observer, "one", 10) + push(observer, "two", 60) + push(observer, "three", 110) + push(observer, "four", 160) + push(observer, "five", 210) + complete(observer, 250) + return Subscriptions.empty() + } + ) + + def closer = { -> + return Observable.create( + { observer -> + push(observer, BufferClosings.create(), 100) + complete(observer, 101) + return Subscriptions.empty() + } + ) + } + + def buffered = Observable.create(buffer(source, closer)) + buffered.subscribe(observer) + + InOrder inOrder = Mockito.inOrder(observer) + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS) + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")) + inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")) + inOrder.verify(observer, Mockito.times(1)).onNext(list("five")) + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)) + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)) + inOrder.verify(observer, Mockito.times(1)).onCompleted() + }*/ + + @Test + public void testInstanceBufferWithOpeningAndClosing() { + def source = Observable.create( + { observer -> + push(observer, "one", 10) + push(observer, "two", 60) + push(observer, "three", 110) + push(observer, "four", 160) + push(observer, "five", 210) + complete(observer, 500) + return Subscriptions.empty() + } + ) + + def openings = Observable.create( + { observer -> + push(observer, BufferOpenings.create(), 50) + push(observer, BufferOpenings.create(), 200) + complete(observer, 250) + return Subscriptions.empty() + } + ) + + def closer = { + opening -> Observable.create( + { observer -> + push(observer, BufferClosings.create(), 100) + complete(observer, 101) + return Subscriptions.empty() + } + ) + } + + def buffered = source.buffer(openings, closer) + buffered.subscribe(observer) + + InOrder inOrder = Mockito.inOrder(observer) + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS) + inOrder.verify(observer, Mockito.times(1)).onNext(["two", "three"]) + inOrder.verify(observer, Mockito.times(1)).onNext(["five"]) + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)) + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)) + inOrder.verify(observer, Mockito.times(1)).onCompleted() + } + + def push(observer, value, delay) { + Action0 onNext = new Action0() { + def void call() { + observer.onNext(value) + } + } + scheduler.schedule(onNext, delay, TimeUnit.MILLISECONDS) + } + + def complete(observer, delay) { + Action0 onCompleted = new Action0() { + def void call() { + observer.onCompleted() + } + } + + scheduler.schedule(onCompleted, delay, TimeUnit.MILLISECONDS) + } def class AsyncObservable implements Func1, Subscription> { diff --git a/language-adaptors/rxjava-jruby/build.gradle b/language-adaptors/rxjava-jruby/build.gradle index cf7d9533e2..be02311889 100644 --- a/language-adaptors/rxjava-jruby/build.gradle +++ b/language-adaptors/rxjava-jruby/build.gradle @@ -1,33 +1,39 @@ -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' apply plugin: 'osgi' dependencies { - compile project(':rxjava-core') - provided 'org.jruby:jruby:1.6+' + core project(':rxjava-core') + provided project(':rxjava-core') + provided project(':language-adaptors:codegen') + compile 'org.jruby:jruby:1.6+' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided +task createAdaptedObservable(type: JavaExec) { + main = 'rx.codegen.ClassPathBasedRunner' + classpath = sourceSets.main.runtimeClasspath + configurations.provided + args = ["JRuby", codeGenOutputDir] - downloadSources = true - downloadJavadoc = true - } + inputs.files(sourceSets.main.runtimeClasspath) + outputs.dir(codeGenOutputDir) } -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } +tasks.test { + dependsOn(createAdaptedObservable) + + //Reorders the classpath so that the newly-create Observables win + classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath } -jar { +tasks.jar { + dependsOn(createAdaptedObservable) + + from (zipTree(configurations.core.singleFile)) { + exclude "rx/Observable.class" + exclude "rx/observables/BlockingObservable.class" + } + from(codeGenOutputDir) + manifest { name = 'rxjava-jruby' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java new file mode 100644 index 0000000000..7edf51c471 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java @@ -0,0 +1,50 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.jruby; + +import org.jruby.Ruby; +import org.jruby.RubyProc; +import org.jruby.javasupport.JavaEmbedUtils; +import org.jruby.runtime.builtin.IRubyObject; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * Concrete wrapper that accepts a {@code RubyProc} and produces any needed Rx {@code Action}. + * @param + */ +public class JRubyActionWrapper extends JRubyArityChecker implements Action0, Action1 { + public JRubyActionWrapper(RubyProc proc) { + this.proc = proc; + } + + @Override + public void call() { + Ruby ruby = proc.getRuntime(); + IRubyObject[] rubyArgs = new IRubyObject[0]; + proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + } + + @Override + public void call(T1 t1) { + Ruby ruby = proc.getRuntime(); + IRubyObject[] rubyArgs = new IRubyObject[1]; + rubyArgs[0] = JavaEmbedUtils.javaToRuby(ruby, t1); + proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java index d66dc16acf..c804e53023 100644 --- a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java @@ -19,12 +19,13 @@ import static org.mockito.Mockito.*; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; -import org.jruby.Ruby; import org.jruby.RubyProc; import org.jruby.embed.ScriptingContainer; -import org.jruby.javasupport.JavaEmbedUtils; -import org.jruby.runtime.builtin.IRubyObject; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -37,22 +38,30 @@ import rx.util.functions.Func1; import rx.util.functions.FunctionLanguageAdaptor; +/** + * Defines the single JRuby class {@code RubyProc} that should map to Rx functions + */ public class JRubyAdaptor implements FunctionLanguageAdaptor { @Override - public Object call(Object function, Object[] args) { - RubyProc rubyProc = ((RubyProc) function); - Ruby ruby = rubyProc.getRuntime(); - IRubyObject rubyArgs[] = new IRubyObject[args.length]; - for (int i = 0; i < args.length; i++) { - rubyArgs[i] = JavaEmbedUtils.javaToRuby(ruby, args[i]); - } - return rubyProc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + public Map, Class> getFunctionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(RubyProc.class, JRubyFunctionWrapper.class); + return m; + } + + @Override + public Map, Class> getActionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(RubyProc.class, JRubyActionWrapper.class); + return m; } @Override - public Class[] getFunctionClass() { - return new Class[] { RubyProc.class }; + public Set> getAllClassesToRewrite() { + Set> classes = new HashSet>(); + classes.add(RubyProc.class); + return classes; } public static class UnitTest { @@ -163,10 +172,10 @@ private void runGroovyScript(String script) { container.put("a", assertion); StringBuilder b = new StringBuilder(); - // force JRuby to always use subscribe(Object) + // force JRuby to always use subscribe(RubyProc) b.append("import \"rx.Observable\"").append("\n"); b.append("class Observable").append("\n"); - b.append(" java_alias :subscribe, :subscribe, [java.lang.Object]").append("\n"); + b.append(" java_alias :subscribe, :subscribe, [org.jruby.RubyProc]").append("\n"); b.append("end").append("\n"); b.append(script); diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyArityChecker.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyArityChecker.java new file mode 100644 index 0000000000..0bf0d3b937 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyArityChecker.java @@ -0,0 +1,29 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.jruby; + +import org.jruby.RubyProc; + +/** + * Base class for JRuby adaptors that knows how to get the arity of a {@code RubyProc}. + */ +public abstract class JRubyArityChecker { + protected RubyProc proc; + + public int getArity() { + return (int) proc.arity().getLongValue(); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java new file mode 100644 index 0000000000..d56a39d717 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java @@ -0,0 +1,82 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.jruby; + +import org.jruby.Ruby; +import org.jruby.RubyBoolean; +import org.jruby.RubyProc; +import org.jruby.javasupport.JavaEmbedUtils; +import org.jruby.runtime.builtin.IRubyObject; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * Concrete wrapper that accepts a {@code RubyProc} and produces any needed Rx {@code Function}. + * @param + * @param + * @param + * @param + * @param + */ +public class JRubyFunctionWrapper extends JRubyArityChecker implements Func0, Func1, Func2, Func3, Func4 { + public JRubyFunctionWrapper(RubyProc proc) { + this.proc = proc; + } + + @Override + public R call() { + return (R) callRubyProc(); + } + + @Override + public R call(T1 t1) { + return (R) callRubyProc(t1); + } + + @Override + public R call(T1 t1, T2 t2) { + return (R) callRubyProc(t1, t2); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + return (R) callRubyProc(t1, t2, t3); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + return (R) callRubyProc(t1, t2, t3, t4); + } + + private Object callRubyProc(Object... args) { + Ruby ruby = proc.getRuntime(); + IRubyObject[] rubyArgs = new IRubyObject[args.length]; + for (int i = 0; i < args.length; i++) { + rubyArgs[i] = JavaEmbedUtils.javaToRuby(ruby, args[i]); + } + R result = (R) proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + if (result instanceof RubyBoolean) { + return ((RubyBoolean) result).isTrue(); + } else { + return result; + } + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/build.gradle b/language-adaptors/rxjava-scala/build.gradle index 43704248d6..6a4e223439 100644 --- a/language-adaptors/rxjava-scala/build.gradle +++ b/language-adaptors/rxjava-scala/build.gradle @@ -1,11 +1,10 @@ apply plugin: 'scala' -apply plugin: 'eclipse' -apply plugin: 'idea' apply plugin: 'osgi' tasks.withType(ScalaCompile) { scalaCompileOptions.fork = true scalaCompileOptions.unchecked = true + scalaCompileOptions.setAdditionalParameters(['-feature']) configure(scalaCompileOptions.forkOptions) { memoryMaximumSize = '1g' @@ -13,47 +12,42 @@ tasks.withType(ScalaCompile) { } } +sourceSets { + test { + scala { + srcDir 'src/main/scala' + } + } +} + dependencies { - // Scala compiler and related tools - scalaTools 'org.scala-lang:scala-compiler:2.10+' - scalaTools 'org.scala-lang:scala-library:2.10+' - provided 'org.scalatest:scalatest_2.10:1.9.1' + compile 'org.scala-lang:scala-library:2.10+' + + core project(':rxjava-core') + provided project(':rxjava-core') - compile project(':rxjava-core') + provided 'org.scalatest:scalatest_2.10:1.9.1' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' - - testCompile 'org.scalatest:scalatest_2.10:1.9.1' } task test(overwrite: true, dependsOn: testClasses) << { ant.taskdef(name: 'scalatest', - classname: 'org.scalatest.tools.ScalaTestAntTask', - classpath: sourceSets.test.runtimeClasspath.asPath - ) - ant.scalatest(runpath: sourceSets.test.classesDir, - haltonfailure: 'true', - fork: 'false') {reporter(type: 'stdout')} + classname: 'org.scalatest.tools.ScalaTestAntTask', + classpath: configurations.testRuntime.asPath + ':' + compileScala.destinationDir + ":" + configurations.provided.asPath + ) + ant.scalatest(runpath: sourceSets.test.output.classesDir, + haltonfailure: 'true', + fork: 'false') {reporter(type: 'stdout')} } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } -} - -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } +tasks.compileScala { + classpath = classpath + configurations.provided } jar { + from (zipTree(configurations.core.singleFile)) + manifest { name = 'rxjava-scala' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala new file mode 100644 index 0000000000..8f68f0e149 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala @@ -0,0 +1,593 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.lang.scala + +object RxImplicits { + import java.{ lang => jlang } + import language.implicitConversions + + import rx.Observable + import rx.observables.BlockingObservable + import rx.util.functions._ + + /** + * Converts 0-arg function to Rx Action0 + */ + implicit def toRxAction0(f: (() => Unit)): Action0 = + new Action0 { + def call(): Unit = f() + } + + /** + * Converts 1-arg function to Rx Action1 + */ + implicit def toRxAction1[A](f: (A => Unit)): Action1[A] = + new Action1[A] { + def call(a: A): Unit = f(a) + } + + /** + * Converts 1-arg predicate to Rx Func1[A, java.lang.Boolean] + */ + implicit def toRxBoolFunc1[A](f: (A => Boolean)): Func1[A, jlang.Boolean] = + new Func1[A, jlang.Boolean] { + def call(a: A): jlang.Boolean = f(a).booleanValue + } + + /** + * Converts a specific function shape (used in takeWhile) to the equivalent Java types with an Rx Func2 + */ + implicit def toRxTakeWhileFunc[A](f: (A, Int) => Boolean): Func2[A, jlang.Integer, jlang.Boolean] = + new Func2[A, jlang.Integer, jlang.Boolean] { + def call(a: A, b: jlang.Integer): jlang.Boolean = f(a, b).booleanValue + } + + /** + * Converts a function shaped ilke compareTo into the equivalent Rx Func2 + */ + implicit def toRxCompareFunc[A](f: (A, A) => Int): Func2[A, A, jlang.Integer] = + new Func2[A, A, jlang.Integer] { + def call(a1: A, a2: A): jlang.Integer = f(a1, a2).intValue + } + + /* + * This implicit allows Scala code to use any exception type and still work + * with invariant Func1 interface + */ + implicit def toRxExceptionFunc[A <: Exception, B](f: (A => B)): Func1[Exception, B] = + new Func1[Exception, B] { + def call(ex: Exception): B = f(ex.asInstanceOf[A]) + } + + /** + * The following implicits convert functions of different arities into the Rx equivalents + */ + implicit def toRxFunc0[A](f: () => A): Func0[A] = + new Func0[A] { + def call(): A = f() + } + + implicit def toRxFunc1[A, B](f: (A => B)): Func1[A, B] = + new Func1[A, B] { + def call(a: A): B = f(a) + } + + implicit def toRxFunc2[A, B, C](f: (A, B) => C): Func2[A, B, C] = + new Func2[A, B, C] { + def call(a: A, b: B) = f(a, b) + } + + implicit def toRxFunc3[A, B, C, D](f: (A, B, C) => D): Func3[A, B, C, D] = + new Func3[A, B, C, D] { + def call(a: A, b: B, c: C) = f(a, b, c) + } + + implicit def toRxFunc4[A, B, C, D, E](f: (A, B, C, D) => E): Func4[A, B, C, D, E] = + new Func4[A, B, C, D, E] { + def call(a: A, b: B, c: C, d: D) = f(a, b, c, d) + } + + /** + * This implicit class implements all of the methods necessary for + * including Observables in a for-comprehension. + * Note that return type is always Observable, so that the ScalaObservable + * type never escapes the for-comprehension + */ + implicit class ScalaObservable[A](wrapped: Observable[A]) { + def map[B](f: A => B): Observable[B] = wrapped.map(f) + def flatMap[B](f: A => Observable[B]): Observable[B] = wrapped.mapMany(f) + def foreach(f: A => Unit): Unit = wrapped.toBlockingObservable.forEach(f) + def withFilter(p: A => Boolean): WithFilter = new WithFilter(p) + + class WithFilter(p: A => Boolean) { + def map[B](f: A => B): Observable[B] = wrapped.filter(p).map(f) + def flatMap[B](f: A => Observable[B]): Observable[B] = wrapped.filter(p).flatMap(f) + def foreach(f: A => Unit): Unit = wrapped.filter(p).toBlockingObservable.forEach(f) + def withFilter(p: A => Boolean): Observable[A] = wrapped.filter(p) + } + } +} + +import org.scalatest.junit.JUnitSuite + +class UnitTestSuite extends JUnitSuite { + import rx.lang.scala.RxImplicits._ + + import org.junit.{ Before, Test } + import org.junit.Assert._ + import org.mockito.Matchers.any + import org.mockito.Mockito._ + import org.mockito.{ MockitoAnnotations, Mock } + import rx.{ Notification, Observer, Observable, Subscription } + import rx.observables.GroupedObservable + import collection.mutable.ArrayBuffer + import collection.JavaConverters._ + + @Mock private[this] + val observer: Observer[Any] = null + + @Mock private[this] + val subscription: Subscription = null + + val isOdd = (i: Int) => i % 2 == 1 + val isEven = (i: Int) => i % 2 == 0 + + class ObservableWithException(s: Subscription, values: String*) extends Observable[String] { + var t: Thread = null + + override def subscribe(observer: Observer[String]): Subscription = { + println("ObservableWithException subscribed to ...") + t = new Thread(new Runnable() { + override def run() { + try { + println("running ObservableWithException thread") + values.toList.foreach(v => { + println("ObservableWithException onNext: " + v) + observer.onNext(v) + }) + throw new RuntimeException("Forced Failure") + } catch { + case ex: Exception => observer.onError(ex) + } + } + }) + println("starting ObservableWithException thread") + t.start() + println("done starting ObservableWithException thread") + s + } + } + + @Before def before { + MockitoAnnotations.initMocks(this) + } + + // tests of static methods + + @Test def testSingle { + assertEquals(1, Observable.from(1).toBlockingObservable.single) + } + + @Test def testSinglePredicate { + val found = Observable.from(1, 2, 3).toBlockingObservable.single(isEven) + assertEquals(2, found) + } + + @Test def testSingleOrDefault { + assertEquals(0, Observable.from[Int]().toBlockingObservable.singleOrDefault(0)) + assertEquals(1, Observable.from(1).toBlockingObservable.singleOrDefault(0)) + try { + Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0) + fail("Did not catch any exception, expected IllegalStateException") + } catch { + case ex: IllegalStateException => println("Caught expected IllegalStateException") + case ex: Throwable => fail("Caught unexpected exception " + ex.getCause + ", expected IllegalStateException") + } + } + + @Test def testSingleOrDefaultPredicate { + assertEquals(2, Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0, isEven)) + assertEquals(0, Observable.from(1, 3).toBlockingObservable.singleOrDefault(0, isEven)) + try { + Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0, isOdd) + fail("Did not catch any exception, expected IllegalStateException") + } catch { + case ex: IllegalStateException => println("Caught expected IllegalStateException") + case ex: Throwable => fail("Caught unexpected exception " + ex.getCause + ", expected IllegalStateException") + } + } + + @Test def testFromJavaInterop { + val observable = Observable.from(List(1, 2, 3).asJava) + assertSubscribeReceives(observable)(1, 2, 3) + } + + @Test def testSubscribe { + val observable = Observable.from("1", "2", "3") + assertSubscribeReceives(observable)("1", "2", "3") + } + + //should not compile - adapted from https://gist.github.com/jmhofer/5195589 + /*@Test def testSubscribeOnInt() { + val observable = Observable.from("1", "2", "3") + observable.subscribe((arg: Int) => { + println("testSubscribe: arg = " + arg) + }) + }*/ + + @Test def testDefer { + val lazyObservableFactory = () => Observable.from(1, 2) + val observable = Observable.defer(lazyObservableFactory) + assertSubscribeReceives(observable)(1, 2) + } + + @Test def testJust { + val observable = Observable.just("foo") + assertSubscribeReceives(observable)("foo") + } + + @Test def testMerge { + val observable1 = Observable.from(1, 2, 3) + val observable2 = Observable.from(4, 5, 6) + val observableList = List(observable1, observable2).asJava + val merged = Observable.merge(observableList) + assertSubscribeReceives(merged)(1, 2, 3, 4, 5, 6) + } + + @Test def testFlattenMerge { + val observable = Observable.from(Observable.from(1, 2, 3)) + val merged = Observable.merge(observable) + assertSubscribeReceives(merged)(1, 2, 3) + } + + @Test def testSequenceMerge { + val observable1 = Observable.from(1, 2, 3) + val observable2 = Observable.from(4, 5, 6) + val merged = Observable.merge(observable1, observable2) + assertSubscribeReceives(merged)(1, 2, 3, 4, 5, 6) + } + + @Test def testConcat { + val observable1 = Observable.from(1, 2, 3) + val observable2 = Observable.from(4, 5, 6) + val concatenated = Observable.concat(observable1, observable2) + assertSubscribeReceives(concatenated)(1, 2, 3, 4, 5, 6) + } + + @Test def testSynchronize { + val observable = Observable.from(1, 2, 3) + val synchronized = Observable.synchronize(observable) + assertSubscribeReceives(synchronized)(1, 2, 3) + } + + @Test def testZip3() { + val numbers = Observable.from(1, 2, 3) + val colors = Observable.from("red", "green", "blue") + val names = Observable.from("lion-o", "cheetara", "panthro") + + case class Character(id: Int, color: String, name: String) + + val liono = Character(1, "red", "lion-o") + val cheetara = Character(2, "green", "cheetara") + val panthro = Character(3, "blue", "panthro") + + val characters = Observable.zip(numbers, colors, names, Character.apply _) + assertSubscribeReceives(characters)(liono, cheetara, panthro) + } + + @Test def testZip4() { + val numbers = Observable.from(1, 2, 3) + val colors = Observable.from("red", "green", "blue") + val names = Observable.from("lion-o", "cheetara", "panthro") + val isLeader = Observable.from(true, false, false) + + case class Character(id: Int, color: String, name: String, isLeader: Boolean) + + val liono = Character(1, "red", "lion-o", true) + val cheetara = Character(2, "green", "cheetara", false) + val panthro = Character(3, "blue", "panthro", false) + + val characters = Observable.zip(numbers, colors, names, isLeader, Character.apply _) + assertSubscribeReceives(characters)(liono, cheetara, panthro) + } + + //tests of instance methods + + // missing tests for : takeUntil, groupBy, next, mostRecent + + @Test def testFilter { + val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) + val observable = numbers.filter(isEven) + assertSubscribeReceives(observable)(2, 4, 6, 8) + } + + @Test def testLast { + val observable = Observable.from(1, 2, 3, 4).toBlockingObservable + assertEquals(4, observable.toBlockingObservable.last) + } + + @Test def testLastPredicate { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(3, observable.toBlockingObservable.last(isOdd)) + } + + @Test def testLastOrDefault { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(4, observable.toBlockingObservable.lastOrDefault(5)) + assertEquals(5, Observable.from[Int]().toBlockingObservable.lastOrDefault(5)) + } + + @Test def testLastOrDefaultPredicate { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(3, observable.toBlockingObservable.lastOrDefault(5, isOdd)) + assertEquals(5, Observable.from[Int]().toBlockingObservable.lastOrDefault(5, isOdd)) + } + + @Test def testMap { + val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) + val mappedNumbers = ArrayBuffer.empty[Int] + numbers.map((x: Int) => x * x).subscribe((squareVal: Int) => { + mappedNumbers.append(squareVal) + }) + assertEquals(List(1, 4, 9, 16, 25, 36, 49, 64, 81), mappedNumbers.toList) + } + + @Test def testMapMany { + val numbers = Observable.from(1, 2, 3, 4) + val f = (i: Int) => Observable.from(List(i, -i).asJava) + val mappedNumbers = ArrayBuffer.empty[Int] + numbers.mapMany(f).subscribe((i: Int) => { + mappedNumbers.append(i) + }) + assertEquals(List(1, -1, 2, -2, 3, -3, 4, -4), mappedNumbers.toList) + } + + @Test def testMaterialize { + val observable = Observable.from(1, 2, 3, 4) + val expectedNotifications: List[Notification[Int]] = + ((1.to(4).map(i => new Notification(i))) :+ new Notification()).toList + val actualNotifications: ArrayBuffer[Notification[Int]] = ArrayBuffer.empty + observable.materialize.subscribe((n: Notification[Int]) => { + actualNotifications.append(n) + }) + assertEquals(expectedNotifications, actualNotifications.toList) + } + + @Test def testDematerialize { + val notifications: List[Notification[Int]] = + ((1.to(4).map(i => new Notification(i))) :+ new Notification()).toList + val observableNotifications: Observable[Notification[Int]] = + Observable.from(notifications.asJava) + val observable: Observable[Int] = + observableNotifications.dematerialize() + assertSubscribeReceives(observable)(1, 2, 3, 4) + } + + @Test def testOnErrorResumeNextObservableNoError { + val observable = Observable.from(1, 2, 3, 4) + val resumeObservable = Observable.from(5, 6, 7, 8) + val observableWithErrorHandler = observable.onErrorResumeNext(resumeObservable) + assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) + } + + @Test def testOnErrorResumeNextObservableErrorOccurs { + val observable = new ObservableWithException(subscription, "foo", "bar") + val resumeObservable = Observable.from("a", "b", "c", "d") + val observableWithErrorHandler = observable.onErrorResumeNext(resumeObservable) + observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) + + try { + observable.t.join() + } catch { + case ex: InterruptedException => fail(ex.getMessage) + } + + List("foo", "bar", "a", "b", "c", "d").foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } + + @Test def testOnErrorResumeNextFuncNoError { + val observable = Observable.from(1, 2, 3, 4) + val resumeFunc = (ex: Throwable) => Observable.from(5, 6, 7, 8) + val observableWithErrorHandler = observable.onErrorResumeNext(resumeFunc) + assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) + } + + @Test def testOnErrorResumeNextFuncErrorOccurs { + val observable = new ObservableWithException(subscription, "foo", "bar") + val resumeFunc = (ex: Throwable) => Observable.from("a", "b", "c", "d") + val observableWithErrorHandler = observable.onErrorResumeNext(resumeFunc) + observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) + + try { + observable.t.join() + } catch { + case ex: InterruptedException => fail(ex.getMessage) + } + + List("foo", "bar", "a", "b", "c", "d").foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } + + @Test def testOnErrorReturnFuncNoError { + val observable = Observable.from(1, 2, 3, 4) + val returnFunc = (ex: Throwable) => 87 + val observableWithErrorHandler = observable.onErrorReturn(returnFunc) + assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) + } + + @Test def testOnErrorReturnFuncErrorOccurs { + val observable = new ObservableWithException(subscription, "foo", "bar") + val returnFunc = (ex: Throwable) => "baz" + val observableWithErrorHandler = observable.onErrorReturn(returnFunc) + observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) + + try { + observable.t.join() + } catch { + case ex: InterruptedException => fail(ex.getMessage) + } + + List("foo", "bar", "baz").foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } + + @Test def testReduce { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(10, observable.reduce((a: Int, b: Int) => a + b).toBlockingObservable.single) + } + + @Test def testSkip { + val observable = Observable.from(1, 2, 3, 4) + val skipped = observable.skip(2) + assertSubscribeReceives(skipped)(3, 4) + } + + /** + * Both testTake and testTakeWhileWithIndex exposed a bug with unsubscribes not properly propagating. + * observable.take(2) produces onNext(first), onNext(second), and 4 onCompleteds + * it should produce onNext(first), onNext(second), and 1 onCompleted + * + * Switching to Observable.create(OperationTake.take(observable, 2)) works as expected + */ + @Test def testTake { + import rx.operators._ + + val observable = Observable.from(1, 2, 3, 4, 5) + val took = Observable.create(OperationTake.take(observable, 2)) + assertSubscribeReceives(took)(1, 2) + } + + @Test def testTakeWhile { + val observable = Observable.from(1, 3, 5, 6, 7, 9, 11) + val took = observable.takeWhile(isOdd) + assertSubscribeReceives(took)(1, 3, 5) + } + + /*@Test def testTakeWhileWithIndex { + val observable = Observable.from(1, 3, 5, 6, 7, 9, 11, 12, 13, 15, 17) + val took = observable.takeWhileWithIndex((i: Int, idx: Int) => isOdd(i) && idx > 4) + assertSubscribeReceives(took)(9, 11) + }*/ + + @Test def testTakeLast { + val observable = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) + val tookLast = observable.takeLast(3) + assertSubscribeReceives(tookLast)(7, 8, 9) + } + + @Test def testToList { + val observable = Observable.from(1, 2, 3, 4) + val toList = observable.toList + assertSubscribeReceives(toList)(List(1, 2, 3, 4).asJava) + } + + @Test def testToSortedList { + val observable = Observable.from(1, 3, 4, 2) + val toSortedList = observable.toSortedList + assertSubscribeReceives(toSortedList)(List(1, 2, 3, 4).asJava) + } + + @Test def testToArbitrarySortedList { + val observable = Observable.from("a", "aaa", "aaaa", "aa") + val sortByLength = (s1: String, s2: String) => s1.length.compareTo(s2.length) + val toSortedList = observable.toSortedList(sortByLength) + assertSubscribeReceives(toSortedList)(List("a", "aa", "aaa", "aaaa").asJava) + } + + @Test def testToIterable { + val observable = Observable.from(1, 2) + val it = observable.toBlockingObservable.toIterable.iterator + assertTrue(it.hasNext) + assertEquals(1, it.next) + assertTrue(it.hasNext) + assertEquals(2, it.next) + assertFalse(it.hasNext) + } + + @Test def testStartWith { + val observable = Observable.from(1, 2, 3, 4) + val newStart = observable.startWith(-1, 0) + assertSubscribeReceives(newStart)(-1, 0, 1, 2, 3, 4) + } + + @Test def testOneLineForComprehension { + val mappedObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + } yield i + 1 + assertSubscribeReceives(mappedObservable)(2, 3, 4, 5) + assertFalse(mappedObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testSimpleMultiLineForComprehension { + val flatMappedObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- Observable.from(1, 10, 100, 1000) + } yield i + j + assertSubscribeReceives(flatMappedObservable)(2, 12, 103, 1004) + assertFalse(flatMappedObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testMultiLineForComprehension { + val doubler = (i: Int) => Observable.from(i, i) + val flatMappedObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- doubler(i) + } yield j + //can't use assertSubscribeReceives since each number comes in 2x + flatMappedObservable.subscribe(observer.asInstanceOf[Observer[Int]]) + List(1, 2, 3, 4).foreach(i => verify(observer, times(2)).onNext(i)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + assertFalse(flatMappedObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testFilterInForComprehension { + val doubler = (i: Int) => Observable.from(i, i) + val filteredObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- doubler(i) if isOdd(i) + } yield j + //can't use assertSubscribeReceives since each number comes in 2x + filteredObservable.subscribe(observer.asInstanceOf[Observer[Int]]) + List(1, 3).foreach(i => verify(observer, times(2)).onNext(i)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + assertFalse(filteredObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testForEachForComprehension { + val doubler = (i: Int) => Observable.from(i, i) + val intBuffer = ArrayBuffer.empty[Int] + val forEachComprehension = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- doubler(i) if isEven(i) + } { + intBuffer.append(j) + } + assertEquals(List(2, 2, 4, 4), intBuffer.toList) + } + + private def assertSubscribeReceives[T](o: Observable[T])(values: T*) = { + o.subscribe(observer.asInstanceOf[Observer[T]]) + values.toList.foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala deleted file mode 100644 index 12418d516d..0000000000 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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 rx.lang.scala - -import rx.util.functions.FunctionLanguageAdaptor -import org.junit.{Assert, Before, Test} -import rx.Observable -import org.scalatest.junit.JUnitSuite -import org.mockito.Mockito._ -import org.mockito.{MockitoAnnotations, Mock} - -import scala.collection.JavaConverters._ -import collection.mutable.ArrayBuffer - -class ScalaAdaptor extends FunctionLanguageAdaptor { - - val ON_NEXT = "onNext" - val ON_ERROR = "onError" - val ON_COMPLETED = "onCompleted" - - def getFunctionClass: Array[Class[_]] = { - return Array(classOf[Map[String, _]], classOf[(AnyRef) => Object], classOf[(AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef) => Object], classOf[(AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) =>Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object]) - } - - def call(function: AnyRef, args: Array[AnyRef]) : Object = { - function match { - case (func: Map[String, _]) => return matchOption(func.get(ON_NEXT), args) - case _ => return matchFunction(function, args) - } - } - - private def matchOption(funcOption: Option[_], args: Array[AnyRef]) : Object = { - funcOption match { - case Some(func: AnyRef) => return matchFunction(func, args) - case _ => return None - } - } - - private def matchFunction(function: AnyRef, args: Array[AnyRef]) : Object = function match { - case (f: ((AnyRef) => Object)) => return f(args(0)) - case (f: ((AnyRef, AnyRef) => Object)) => return f(args(0), args(1)) - case (f: ((AnyRef, AnyRef, AnyRef) => Object)) => return f(args(0), args(1), args(2)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18), args(19)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18), args(19), args(20)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18), args(19), args(20), args(21)) - - } -} - -class UnitTestSuite extends JUnitSuite { - @Mock private[this] - val assertion: ScriptAssertion = null - - @Before def before { - MockitoAnnotations.initMocks(this) - } - - @Test def testTake() { - Observable.from("1", "2", "3").take(1).subscribe(Map( - "onNext" -> ((callback: String) => { - print("testTake: callback = " + callback) - assertion.received(callback) - }) - )) - verify(assertion, times(1)).received("1") - } - - @Test def testClosureVersusMap() { - // using closure - Observable.from("1", "2", "3") - .take(2) - .subscribe((callback: String) => { - println(callback) - }) - - // using Map of closures - Observable.from("1", "2", "3") - .take(2) - .subscribe(Map( - "onNext" -> ((callback: String) => { - println(callback) - }))) - } - - @Test def testFilterWithToList() { - val numbers = Observable.from[Int](1, 2, 3, 4, 5, 6, 7, 8, 9) - numbers.filter((x: Int) => 0 == (x % 2)).toList().subscribe( - (callback: java.util.List[Int]) => { - val lst = callback.asScala.toList - println("filter onNext -> got " + lst) - assertion.received(lst) - } - ) - verify(assertion, times(1)).received(List(2,4,6,8)) - } - - @Test def testTakeLast() { - val numbers = Observable.from[Int](1, 2, 3, 4, 5, 6, 7, 8, 9) - numbers.takeLast(1).subscribe((callback: Int) => { - println("testTakeLast: onNext -> got " + callback) - assertion.received(callback) - }) - verify(assertion, times(1)).received(9) - } - - @Test def testMap() { - val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) - val mappedNumbers = new ArrayBuffer[Int]() - numbers.map(((x: Int)=> { x * x })).subscribe(((squareVal: Int) => { - println("square is " + squareVal ) - mappedNumbers += squareVal - })) - Assert.assertEquals(List(1,4,9,16,25,36,49,64,81), mappedNumbers.toList) - - } - - @Test def testZip() { - val numbers = Observable.from(1, 2, 3) - val colors = Observable.from("red", "green", "blue") - val characters = Observable.from("lion-o", "cheetara", "panthro") - - Observable.zip(numbers.toList, colors.toList, characters.toList, ((n: java.util.List[Int], c: java.util.List[String], t: java.util.List[String]) => { Map( - "numbers" -> n, - "colors" -> c, - "thundercats" -> t - )})).subscribe((m: Map[String, _]) => { - println("zipped map is " + m.toString()) - }) - - - } - - trait ScriptAssertion { - def error(ex: Exception) - - def received(obj: Any) - } -} diff --git a/rxjava-contrib/rxjava-swing/build.gradle b/rxjava-contrib/rxjava-swing/build.gradle index 986f7ca6b9..ea863813a2 100644 --- a/rxjava-contrib/rxjava-swing/build.gradle +++ b/rxjava-contrib/rxjava-swing/build.gradle @@ -1,6 +1,3 @@ -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' apply plugin: 'osgi' sourceCompatibility = JavaVersion.VERSION_1_6 @@ -12,23 +9,6 @@ dependencies { provided 'org.mockito:mockito-core:1.8.5' } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } -} - -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } -} - javadoc { options { doclet = "org.benjchristensen.doclet.DocletExclude" diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java index 22a2ce78fc..ef3612b080 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java @@ -69,7 +69,7 @@ public void testObservingActionEvents() { @SuppressWarnings("unchecked") Action1 action = mock(Action1.class); @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); + Action1 error = mock(Action1.class); Action0 complete = mock(Action0.class); final ActionEvent event = new ActionEvent(this, 1, "command"); @@ -85,7 +85,7 @@ void testAction() { Subscription sub = fromActionOf(button).subscribe(action, error, complete); verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); button.testAction(); @@ -97,7 +97,7 @@ void testAction() { sub.unsubscribe(); button.testAction(); verify(action, times(2)).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java index d6f294cebe..3716b599f9 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java @@ -113,7 +113,7 @@ public void testObservingKeyEvents() { @SuppressWarnings("unchecked") Action1 action = mock(Action1.class); @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); + Action1 error = mock(Action1.class); Action0 complete = mock(Action0.class); final KeyEvent event = mock(KeyEvent.class); @@ -121,7 +121,7 @@ public void testObservingKeyEvents() { Subscription sub = fromKeyEventsOf(comp).subscribe(action, error, complete); verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); fireKeyEvent(event); @@ -133,7 +133,7 @@ public void testObservingKeyEvents() { sub.unsubscribe(); fireKeyEvent(event); verify(action, times(2)).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); } @@ -142,19 +142,19 @@ public void testObservingPressedKeys() { @SuppressWarnings("unchecked") Action1> action = mock(Action1.class); @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); + Action1 error = mock(Action1.class); Action0 complete = mock(Action0.class); Subscription sub = currentlyPressedKeysOf(comp).subscribe(action, error, complete); InOrder inOrder = inOrder(action); inOrder.verify(action, times(1)).call(Collections.emptySet()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); inOrder.verify(action, times(1)).call(new HashSet(asList(1))); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); fireKeyEvent(keyEvent(2, KeyEvent.KEY_PRESSED)); @@ -173,7 +173,7 @@ public void testObservingPressedKeys() { fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); inOrder.verify(action, never()).call(Matchers.>any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); } diff --git a/rxjava-core/build.gradle b/rxjava-core/build.gradle index 72a984c88d..a2420635f9 100644 --- a/rxjava-core/build.gradle +++ b/rxjava-core/build.gradle @@ -1,6 +1,4 @@ -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' +apply plugin: 'maven' apply plugin: 'osgi' sourceCompatibility = JavaVersion.VERSION_1_6 @@ -11,21 +9,14 @@ dependencies { provided 'org.mockito:mockito-core:1.8.5' } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } -} - -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } +uploadArchives { + repositories { + mavenDeployer { + pom { + artifactId = "rxjava" + } + } + } } javadoc { @@ -42,13 +33,12 @@ javadoc { } jar { + baseName = "rxjava" + manifest { - name = 'rxjava-core' + name = 'rxjava' instruction 'Bundle-Vendor', 'Netflix' instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' } - // commenting out for now as it's breaking the rxjava-scala build and I can't figure out why - // exclude('**/*$UnitTest*') } - diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index f6431926e9..9b75aa2a9e 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; @@ -36,6 +37,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import rx.concurrency.TestScheduler; import rx.observables.BlockingObservable; import rx.observables.ConnectableObservable; import rx.observables.GroupedObservable; @@ -91,6 +93,7 @@ import rx.util.OnErrorNotImplementedException; import rx.util.Range; import rx.util.Timestamped; +import rx.util.functions.Action; import rx.util.functions.Action0; import rx.util.functions.Action1; import rx.util.functions.Func0; @@ -268,17 +271,26 @@ private Subscription protectivelyWrapAndSubscribe(Observer o) { return subscription.wrap(subscribe(new SafeObserver(subscription, o))); } + /** + * Helper method that is especially useful for supporting dynamic languages ({@see FunctionLanguageAdaptor}). + * Given a {@code Map} of callbacks, pull the function out and use the supplied converter to get out an Rx core + * {@code Action}. + * @param callbacks {@code Map} of callback functions. "onNext" is required, and "onError"/"onCompleted" also supported as keys + * @param converter {@code Func1} that converts from some Function-y type to an Rx core {@code Action} + * @param specific dynamic language function type + * @return subscription using supplied callbacks and converter + */ @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Map callbacks) { + private Subscription subscribeWithConversion(final Map callbacks, final Func1 converter) { if (callbacks == null) { throw new RuntimeException("callbacks map can not be null"); } - Object _onNext = callbacks.get("onNext"); + F _onNext = callbacks.get("onNext"); if (_onNext == null) { throw new RuntimeException("'onNext' key must contain an implementation"); } // lookup and memoize onNext - final FuncN onNext = Functions.from(_onNext); + final Action1 onNext = (Action1) converter.call(_onNext); /** * Wrapping since raw functions provided by the user are being invoked. @@ -289,77 +301,54 @@ public Subscription subscribe(final Map callbacks) { @Override public void onCompleted() { - Object onComplete = callbacks.get("onCompleted"); - if (onComplete != null) { - Functions.from(onComplete).call(); + F _onComplete = callbacks.get("onCompleted"); + if (_onComplete != null) { + final Action0 onCompleteAction = (Action0) converter.call(_onComplete); + onCompleteAction.call(); } } @Override public void onError(Throwable e) { handleError(e); - Object onError = callbacks.get("onError"); - if (onError != null) { - Functions.from(onError).call(e); + F _onError = callbacks.get("onError"); + if (_onError != null) { + final Action1 onError = (Action1) converter.call(_onError); + onError.call(e); } else { throw new OnErrorNotImplementedException(e); } } @Override - public void onNext(Object args) { + public void onNext(Object args) { onNext.call(args); } + }); + } - }); - } - - public Subscription subscribe(final Map callbacks, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(callbacks); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Object o) { - if (o instanceof Observer) { - // in case a dynamic language is not correctly handling the overloaded methods and we receive an Observer just forward to the correct method. - return subscribe((Observer) o); - } - - if (o == null) { - throw new IllegalArgumentException("onNext can not be null"); + /** + * Used in conjunction with {@code subscribeWithConversion} to do the proper no-op conversion. + * Contrast this with the {@code Func1}s in the dynamic language support codegen package. + */ + private static Func1 actionIdentity = new Func1() { + @Override + public Action call(Action a) { + return a; } + }; - // lookup and memoize onNext - final FuncN onNext = Functions.from(o); - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - // do nothing - } - - @Override - public void onError(Throwable e) { - handleError(e); - throw new OnErrorNotImplementedException(e); - } - - @Override - public void onNext(Object args) { - onNext.call(args); - } - - }); + /** + * Given a {@code Map} of callbacks, create a {@code Subscription} + * @param callbacks {@code Map} of callback functions. "onNext" is required, and "onError"/"onCompleted" also supported as keys + * @return subscription using supplied callbacks + */ + public Subscription subscribe(final Map callbacks) { + return subscribeWithConversion(callbacks, actionIdentity); } - public Subscription subscribe(final Object o, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(o); + public Subscription subscribe(final Map callbacks, Scheduler scheduler) { + return subscribeOn(scheduler).subscribe(callbacks); } public Subscription subscribe(final Action1 onNext) { @@ -397,48 +386,6 @@ public Subscription subscribe(final Action1 onNext, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(onNext); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Object onNext, final Object onError) { - if (onNext == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - if (onError == null) { - throw new IllegalArgumentException("onError can not be null"); - } - - // lookup and memoize onNext - final FuncN onNextFunction = Functions.from(onNext); - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - // do nothing - } - - @Override - public void onError(Throwable e) { - handleError(e); - Functions.from(onError).call(e); - } - - @Override - public void onNext(Object args) { - onNextFunction.call(args); - } - - }); - } - - public Subscription subscribe(final Object onNext, final Object onError, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(onNext, onError); - } - public Subscription subscribe(final Action1 onNext, final Action1 onError) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -477,51 +424,6 @@ public Subscription subscribe(final Action1 onNext, final Action1 return subscribeOn(scheduler).subscribe(onNext, onError); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Object onNext, final Object onError, final Object onComplete) { - if (onNext == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - if (onError == null) { - throw new IllegalArgumentException("onError can not be null"); - } - if (onComplete == null) { - throw new IllegalArgumentException("onComplete can not be null"); - } - - // lookup and memoize onNext - final FuncN onNextFunction = Functions.from(onNext); - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - Functions.from(onComplete).call(); - } - - @Override - public void onError(Throwable e) { - handleError(e); - Functions.from(onError).call(e); - } - - @Override - public void onNext(Object args) { - onNextFunction.call(args); - } - - }); - } - - public Subscription subscribe(final Object onNext, final Object onError, final Object onComplete, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(onNext, onError, onComplete); - } - public Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -886,49 +788,6 @@ public static Observable create(Func1, Subscription> func) { return new Observable(func); } - /** - * Creates an Observable that will execute the given function when an {@link Observer} - * subscribes to it. - *

- * - *

- * This method accepts {@link Object} to allow different languages to pass in methods using - * {@link FunctionLanguageAdaptor}. - *

- * Write the function you pass to create so that it behaves as an Observable: It - * should invoke the Observer's {@link Observer#onNext onNext}, - * {@link Observer#onError onError}, and {@link Observer#onCompleted onCompleted} methods - * appropriately. - *

- * A well-formed Observable must invoke either the Observer's onCompleted method - * exactly once or its onError method exactly once. - *

- * See Rx Design Guidelines (PDF) - * for detailed information. - * - * @param - * the type of the items that this Observable emits - * @param func - * a function that accepts an {@code Observer}, invokes its - * {@code onNext}, {@code onError}, and {@code onCompleted} methods - * as appropriate, and returns a {@link Subscription} that allows the Observer to - * cancel the subscription - * @return an Observable that, when an {@link Observer} subscribes to it, will execute the given - * function - */ - public static Observable create(final Object func) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(func); - return create(new Func1, Subscription>() { - - @Override - public Subscription call(Observer t1) { - return (Subscription) _f.call(t1); - } - - }); - } - /** * Returns an Observable that emits no data to the {@link Observer} and immediately invokes * its {@link Observer#onCompleted onCompleted} method. @@ -978,37 +837,11 @@ public static Observable filter(Observable that, Func1 pre return create(OperationFilter.filter(that, predicate)); } - /** - * Filters an Observable by discarding any items it emits that do not satisfy some predicate - *

- * - * - * @param that - * the Observable to filter - * @param function - * a function that evaluates an item emitted by the source Observable, and - * returns {@code true} if it passes the filter - * @return an Observable that emits only those items emitted by the source Observable for which the - * predicate function evaluates to {@code true} - */ - public static Observable filter(Observable that, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return filter(that, new Func1() { - - @Override - public Boolean call(T t1) { - return (Boolean) _f.call(t1); - - } - - }); - } - /** * Filters an Observable by discarding any items it emits that do not satisfy some predicate *

* + * * * @param that * the Observable to filter @@ -1146,39 +979,6 @@ public static Observable defer(Func0> observableFactory) { return create(OperationDefer.defer(observableFactory)); } - /** - * Returns an Observable that calls an Observable factory to create its Observable for each - * new Observer that subscribes. - *

- * - *

- * The defer operator allows you to defer or delay emitting items from an Observable - * until such time as an {@link Observer} subscribes to the Observable. This allows an Observer - * to easily obtain an updates or refreshed version of the sequence. - * - * @param observableFactory - * the Observable factory function to invoke for each {@link Observer} that - * subscribes to the resulting Observable - * @param - * the type of the items emitted by the Observable - * @return an Observable whose {@link Observer}s trigger an invocation of the given Observable - * factory function - */ - public static Observable defer(Object observableFactory) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(observableFactory); - - return create(OperationDefer.defer(new Func0>() { - - @Override - @SuppressWarnings("unchecked") - public Observable call() { - return (Observable) _f.call(); - } - - })); - } - /** * Returns an Observable that emits a single item and then completes. *

@@ -1226,37 +1026,6 @@ public static Observable map(Observable sequence, Func1 func) return create(OperationMap.map(sequence, func)); } - /** - * Returns an Observable that applies the given function to each item emitted by an - * Observable and emits the result. - *

- * - * - * @param sequence - * the source Observable - * @param func - * a function to apply to each item emitted by the source Observable - * @param - * the type of items emitted by the the source Observable - * @param - * the type of items to be emitted by the resulting Observable - * @return an Observable that emits the items from the source Observable as transformed by the - * given function - */ - public static Observable map(Observable sequence, final Object func) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(func); - return map(sequence, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public R call(T t1) { - return (R) _f.call(t1); - } - - }); - } - /** * Creates a new Observable by applying a function that you supply to each item emitted by * the source Observable, where that function returns an Observable, and then merging those @@ -1285,43 +1054,6 @@ public static Observable mapMany(Observable sequence, Func1 - * - *

- * Note: {@code mapMany} and {@code flatMap} are equivalent. - * - * @param sequence - * the source Observable - * @param func - * a function that, when applied to each item emitted by the source Observable, - * generates an Observable - * @param - * the type of items emitted by the source Observable - * @param - * the type of items emitted by the Observables that are returned from - * {@code func} - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation - */ - public static Observable mapMany(Observable sequence, final Object func) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(func); - return mapMany(sequence, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public R call(T t1) { - return (R) _f.call(t1); - } - - }); - } - /** * Turns all of the notifications from a source Observable into {@link Observer#onNext onNext} * emissions, and marks them with their original notification types within {@link Notification} @@ -1498,38 +1230,8 @@ public static Observable flatMap(Observable sequence, Func1 - * - *

- * Note: {@code mapMany} and {@code flatMap} are equivalent. - * - * @param sequence - * the source Observable - * @param func - * a function that, when applied to each item emitted by the source Observable, - * generates an Observable - * @param - * the type of items emitted by the source Observable - * @param - * the type of items emitted by the Observables that are returned from - * {@code func} - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation - * @see #mapMany(Observable, Func1) - */ - public static Observable flatMap(Observable sequence, final Object func) { - return mapMany(sequence, func); - } - - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * + * * * @param source * an Observable whose items you want to group @@ -1552,51 +1254,6 @@ public static Observable> groupBy(Observable - * - * - * @param source - * an Observable whose items you want to group - * @param keySelector - * a function that extracts the key for each item omitted by the source Observable - * @param elementSelector - * a function to map each item emitted by the source Observable to an item emitted - * by a {@link GroupedObservable} - * @param - * the key type - * @param - * the type of items emitted by the source Observable - * @param - * the type of items to be emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - @SuppressWarnings("rawtypes") - public static Observable> groupBy(Observable source, final Object keySelector, final Object elementSelector) { - final FuncN _k = Functions.from(keySelector); - final FuncN _e = Functions.from(elementSelector); - - return groupBy(source, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public K call(T t1) { - return (K) _k.call(t1); - } - }, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public R call(T t1) { - return (R) _e.call(t1); - } - }); - } - /** * Groups the items emitted by an Observable according to a specified criterion, and emits these * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. @@ -1619,38 +1276,6 @@ public static Observable> groupBy(Observable s return create(OperationGroupBy.groupBy(source, keySelector)); } - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param source - * an Observable whose items you want to group - * @param keySelector - * a function that extracts the key for each item emitted by the source Observable - * @param - * the key type - * @param - * the type of items to be emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - @SuppressWarnings("rawtypes") - public static Observable> groupBy(Observable source, final Object keySelector) { - final FuncN _k = Functions.from(keySelector); - - return groupBy(source, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public K call(T t1) { - return (K) _k.call(t1); - } - }); - } - /** * This behaves like {@link #merge(java.util.List)} except that if any of the merged Observables * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will @@ -1772,45 +1397,6 @@ public static Observable onErrorResumeNext(final Observable that, fina return create(OperationOnErrorResumeNextViaFunction.onErrorResumeNextViaFunction(that, resumeFunction)); } - /** - * Instruct an Observable to pass control to another Observable (the return value of a function) - * rather than invoking {@link Observer#onError onError} if it encounters an error. - *

- * - *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its Observer, the Observable invokes its {@link Observer}'s - * methods. The {@code onErrorResumeNext} method changes this behavior. If you pass a - * function that returns an Observable ({@code resumeFunction}) to - * {@code onErrorResumeNext}, if the source Observable encounters an error, instead of - * invoking its Observer's {@code onError} function, it will instead relinquish control to - * this new Observable, which will invoke the Observer's {@link Observer#onNext onNext} method - * if it is able to do so. In such a case, because no Observable necessarily invokes - * {@code onError}, the Observer may never know that an error happened. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param that - * the source Observable - * @param resumeFunction - * a function that returns an Observable that will take over if the source Observable - * encounters an error - * @return an Observable, identical to the source Observable with its behavior modified as described - */ - public static Observable onErrorResumeNext(final Observable that, final Object resumeFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(resumeFunction); - return onErrorResumeNext(that, new Func1>() { - - @SuppressWarnings("unchecked") - @Override - public Observable call(Throwable e) { - return (Observable) _f.call(e); - } - }); - } - /** * Instruct an Observable to pass control to another Observable rather than invoking * {@link Observer#onError onError} if it encounters an error. @@ -1982,28 +1568,6 @@ public static Observable reduce(Observable sequence, Func2 ac } /** - * A version of {@code reduce()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Func2) - */ - public static Observable reduce(final Observable sequence, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return reduce(sequence, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public T call(T t1, T t2) { - return (T) _f.call(t1, t2); - } - - }); - } - - /** - * Synonymous with {@code reduce()} *

* * @@ -2013,17 +1577,6 @@ public static Observable aggregate(Observable sequence, Func2 return reduce(sequence, accumulator); } - /** - * A version of {@code aggregate()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Func2) - */ - public static Observable aggregate(Observable sequence, Object accumulator) { - return reduce(sequence, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -2058,26 +1611,6 @@ public static Observable reduce(Observable sequence, R initialValue } /** - * A version of {@code reduce()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Object, Func2) - */ - public static Observable reduce(final Observable sequence, final R initialValue, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return reduce(sequence, initialValue, new Func2() { - @SuppressWarnings("unchecked") - @Override - public R call(R r, T t) { - return (R) _f.call(r, t); - } - }); - } - - /** - * Synonymous with {@code reduce()}. *

* * @@ -2087,17 +1620,6 @@ public static Observable aggregate(Observable sequence, R initialVa return reduce(sequence, initialValue, accumulator); } - /** - * A version of {@code aggregate()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Object, Func2) - */ - public static Observable aggregate(Observable sequence, R initialValue, Object accumulator) { - return reduce(sequence, initialValue, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -2122,27 +1644,6 @@ public static Observable scan(Observable sequence, Func2 accu return create(OperationScan.scan(sequence, accumulator)); } - /** - * A version of {@code scan()} for use by dynamic languages. - *

- * - * - * @see #scan(Observable, Func2) - */ - public static Observable scan(final Observable sequence, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return scan(sequence, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public T call(T t1, T t2) { - return (T) _f.call(t1, t2); - } - - }); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -2175,27 +1676,6 @@ public static Observable scan(Observable sequence, R initialValue, } /** - * A version of {@code scan()} for use by dynamic languages. - *

- * - * - * @see #scan(Observable, Object, Func2) - */ - public static Observable scan(final Observable sequence, final R initialValue, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return scan(sequence, initialValue, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public R call(R r, T t) { - return (R) _f.call(r, t); - } - }); - } - - /** - * Returns an Observable that emits a single Boolean value that indicates whether all items emitted by a * source Observable satisfy a condition. *

* @@ -2214,34 +1694,6 @@ public static Observable all(final Observable sequence, final Fu } /** - * Returns an Observable that emits a single Boolean value that indicates whether all items emitted by a - * source Observable satisfy a condition. - *

- * - * - * @param sequence - * an Observable whose emitted items you are evaluating - * @param predicate - * a function that evaluates each emitted item and returns a Boolean - * @param - * the type of items emitted by the source Observable - * @return an Observable that emits {@code true} if all items emitted by the source - * Observable satisfy the predicate; otherwise, {@code false} - */ - public static Observable all(final Observable sequence, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return all(sequence, new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - - /** - * Returns an Observable that skips the first {@code num} items emitted by the source * Observable and emits the remaining items. *

* @@ -2358,31 +1810,6 @@ public static Observable takeWhile(final Observable items, Func1 - * - * - * @param items - * the source Observable - * @param predicate - * a function to test each item emitted by the source Observable for a condition - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return {@code true} for each item, then completes - */ - public static Observable takeWhile(final Observable items, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return takeWhile(items, new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - /** * Returns an Observable that emits the items emitted by a source Observable so long as a given * predicate remains true, where the predicate can operate on both the item and its index @@ -2402,35 +1829,6 @@ public static Observable takeWhileWithIndex(final Observable items, Fu return create(OperationTakeWhile.takeWhileWithIndex(items, predicate)); } - /** - * Returns an Observable that emits the items emitted by a source Observable so long as a given - * predicate remains true, where the predicate can operate on both the item and its index - * relative to the complete sequence. - *

- * - * - * @param items - * the source Observable - * @param predicate - * a function to test each item emitted by the source Observable for a condition; - * the second parameter of the function represents the index of the source item - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return {@code true} for each item, then completes - */ - public static Observable takeWhileWithIndex(final Observable items, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return create(OperationTakeWhile.takeWhileWithIndex(items, new Func2() - { - @Override - public Boolean call(T t, Integer integer) - { - return (Boolean) _f.call(t, integer); - } - })); - } - /** * Wraps each item emitted by a source Observable in a {@link Timestamped} object. *

@@ -2646,34 +2044,6 @@ public static Observable> toSortedList(Observable sequence, Func2 } /** - * Return an Observable that emits a single list of the items emitted by the source Observable, sorted - * by the given comparison function. - *

- * - * - * @param sequence - * the source Observable - * @param sortFunction - * a function that compares two items emitted by the source Observable and returns - * an Integer that indicates their sort order - * @return an Observable that emits a single, sorted list of the items from the source Observable - */ - public static Observable> toSortedList(Observable sequence, final Object sortFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(sortFunction); - return create(OperationToObservableSortedList.toSortedList(sequence, new Func2() { - - @Override - public Integer call(T t1, T t2) { - return (Integer) _f.call(t1, t2); - } - - })); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to pairs - * of items emitted, in sequence, by two other Observables. *

* *

@@ -2747,68 +2117,6 @@ public static Observable sequenceEqual(Observable first, Observa return zip(first, second, equality); } - /** - * Returns an Observable that emits Boolean values that indicate whether the pairs of items - * emitted by two source Observables are equal based on the results of a specified equality - * function. - *

- * - * - * @param first - * one Observable to compare - * @param second - * the second Observable to compare - * @param equality - * a function used to compare items emitted by both Observables - * @param - * the type of items emitted by each Observable - * @return an Observable that emits Booleans that indicate whether the corresponding items - * emitted by the source Observables are equal - */ - public static Observable sequenceEqual(Observable first, Observable second, Object equality) { - return zip(first, second, equality); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to pairs - * of items emitted, in sequence, by two other Observables. - *

- * - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * w0 and the first item emitted by w1; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * w0 and the second item emitted by w1; and so forth. - *

- * The resulting Observable<R> returned from zip will invoke - * {@link Observer#onNext onNext} as many times as the number of onNext invocations - * of the source Observable that emits the fewest items. - * - * @param w0 - * one source Observable - * @param w1 - * another source Observable - * @param function - * a function that, when applied to a pair of items, each emitted by one of the two - * source Observables, results in an item that will be emitted by the resulting - * Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable w0, Observable w1, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return zip(w0, w1, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public R call(T0 t0, T1 t1) { - return (R) _f.call(t0, t1); - } - - }); - } - /** * Returns an Observable that emits the results of a function of your choosing applied to * combinations of three items emitted, in sequence, by three other Observables. @@ -2841,48 +2149,6 @@ public static Observable zip(Observable w0, Observable - * - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * w0, the first item emitted by w1, and the first item emitted by - * w2; the second item emitted by the new Observable will be the result of the - * function applied to the second item emitted by w0, the second item emitted by - * w1, and the second item emitted by w2; and so forth. - *

- * The resulting Observable<R> returned from zip will invoke - * {@link Observer#onNext onNext} as many times as the number of onNext invocations - * of the source Observable that emits the fewest items. - * - * @param w0 - * one source Observable - * @param w1 - * another source Observable - * @param w2 - * a third source Observable - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable w0, Observable w1, Observable w2, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return zip(w0, w1, w2, new Func3() { - - @SuppressWarnings("unchecked") - @Override - public R call(T0 t0, T1 t1, T2 t2) { - return (R) _f.call(t0, t1, t2); - } - - }); - } - /** * Returns an Observable that emits the results of a function of your choosing applied to * combinations of four items emitted, in sequence, by four other Observables. @@ -2917,50 +2183,6 @@ public static Observable zip(Observable w0, Observabl return create(OperationZip.zip(w0, w1, w2, w3, reduceFunction)); } - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * w0, the first item emitted by w1, the first item emitted by - * w2, and the first item emitted by w3; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. - *

- * The resulting Observable<R> returned from zip will invoke - * {@link Observer#onNext onNext} as many times as the number of onNext invocations - * of the source Observable that emits the fewest items. - * - * @param w0 - * one source Observable - * @param w1 - * another source Observable - * @param w2 - * a third source Observable - * @param w3 - * a fourth source Observable - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable w0, Observable w1, Observable w2, Observable w3, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return zip(w0, w1, w2, w3, new Func4() { - - @SuppressWarnings("unchecked") - @Override - public R call(T0 t0, T1 t1, T2 t2, T3 t3) { - return (R) _f.call(t0, t1, t2, t3); - } - - }); - } - /** * Creates an Observable which produces buffers of collected values. * @@ -3168,153 +2390,7 @@ public Observable> buffer(long timespan, long timeshift, TimeUnit unit, return buffer(this, timespan, timeshift, unit, scheduler); } - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * {@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting {@code Observable} returned from {@code zip} will invoke - * {@code onNext} as many times as the number of {@code onNext} invokations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * An Observable of source Observables - * @param reduceFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable> ws, final FuncN reduceFunction) { - return ws.toList().mapMany(new Func1>, Observable>() { - @Override - public Observable call(List> wsList) { - return create(OperationZip.zip(wsList, reduceFunction)); - } - }); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting Observable returned from zip will invoke - * onNext as many times as the number of onNext invocations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * An Observable of source Observables - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable> ws, final Object function) { - @SuppressWarnings({ "unchecked" }) - final FuncN _f = Functions.from(function); - return zip(ws, _f); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * {@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting {@code Observable} returned from {@code zip} will invoke - * {@code onNext} as many times as the number of {@code onNext} invokations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * A collection of source Observables - * @param reduceFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Collection> ws, FuncN reduceFunction) { - return create(OperationZip.zip(ws, reduceFunction)); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * {@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting {@code Observable} returned from {@code zip} will invoke - * {@code onNext} as many times as the number of {@code onNext} invocations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * A collection of source Observables - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Collection> ws, final Object function) { - @SuppressWarnings({ "unchecked" }) - final FuncN _f = Functions.from(function); - return zip(ws, _f); - } - - /** - * Combines the given observables, emitting an event containing an aggregation of the latest values of each of the source observables - * each time an event is received from one of the source observables, where the aggregation is defined by the given function. - *

- * - * - * @param w0 - * The first source observable. - * @param w1 - * The second source observable. - * @param combineFunction - * The aggregation function used to combine the source observable values. - * @return An Observable that combines the source Observables with the given combine function - */ - public static Observable combineLatest(Observable w0, Observable w1, Func2 combineFunction) { - return create(OperationCombineLatest.combineLatest(w0, w1, combineFunction)); - } - - /** - * @see #combineLatest(Observable, Observable, Func2) - */ - public static Observable combineLatest(Observable w0, Observable w1, Observable w2, Func3 combineFunction) { - return create(OperationCombineLatest.combineLatest(w0, w1, w2, combineFunction)); - } - - /** - * @see #combineLatest(Observable, Observable, Func2) - */ - public static Observable combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Func4 combineFunction) { - return create(OperationCombineLatest.combineLatest(w0, w1, w2, w3, combineFunction)); - } - - /** - * Filters an Observable by discarding any of its items that do not satisfy the given predicate. + /** *

* * @@ -3344,29 +2420,6 @@ public Observable finallyDo(Action0 action) { return create(OperationFinally.finallyDo(this, action)); } - /** - * Filters an Observable by discarding any of its items that do not satisfy the given predicate. - *

- * - * - * @param callback - * a function that evaluates an item emitted by the source Observable, returning - * {@code true} if it passes the filter - * @return an Observable that emits only those items in the original Observable that the filter - * evaluates as {@code true} - */ - public Observable filter(final Object callback) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(callback); - return filter(this, new Func1() { - - @Override - public Boolean call(T t1) { - return (Boolean) _f.call(t1); - } - }); - } - /** * Creates a new Observable by applying a function that you supply to each item emitted by * the source Observable, where that function returns an Observable, and then merging those @@ -3389,28 +2442,6 @@ public Observable flatMap(Func1> func) { } /** - * Creates a new Observable by applying a function that you supply to each item emitted by - * the source Observable, where that function returns an Observable, and then merging those - * resulting Observables and emitting the results of this merger. - *

- * - *

- * Note: mapMany and flatMap are equivalent. - * - * @param callback - * a function that, when applied to an item emitted by the source Observable, returns - * an Observable - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation. - * @see #mapMany(Object) - */ - public Observable flatMap(final Object callback) { - return mapMany(callback); - } - - /** - * Filters an Observable by discarding any items it emits that do not satisfy the given predicate *

* * @@ -3440,30 +2471,6 @@ public Observable map(Func1 func) { return map(this, func); } - /** - * Returns an Observable that applies the given function to each item emitted by an - * Observable and emits the result. - *

- * - * - * @param callback - * a function to apply to each item emitted by the Observable - * @return an Observable that emits the items from the source Observable, transformed by the - * given function - */ - public Observable map(final Object callback) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(callback); - return map(this, new Func1() { - - @Override - @SuppressWarnings("unchecked") - public R call(T t1) { - return (R) _f.call(t1); - } - }); - } - /** * Creates a new Observable by applying a function that you supply to each item emitted by * the source Observable, where that function returns an Observable, and then merging those @@ -3485,36 +2492,6 @@ public Observable mapMany(Func1> func) { return mapMany(this, func); } - /** - * Creates a new Observable by applying a function that you supply to each item emitted by - * the source Observable, where that function returns an Observable, and then merging those - * resulting Observables and emitting the results of this merger. - *

- * - *

- * Note: mapMany and flatMap are equivalent. - * - * @param callback - * a function that, when applied to an item emitted by the source Observable, returns - * an Observable - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation. - * @see #flatMap(Object) - */ - public Observable mapMany(final Object callback) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(callback); - return mapMany(this, new Func1>() { - - @Override - @SuppressWarnings("unchecked") - public Observable call(T t1) { - return (Observable) _f.call(t1); - } - }); - } - /** * Turns all of the notifications from a source Observable into {@link Observer#onNext onNext} * emissions, and marks them with their original notification types within {@link Notification} @@ -3606,42 +2583,6 @@ public Observable onErrorResumeNext(final Func1> res return onErrorResumeNext(this, resumeFunction); } - /** - * Instruct an Observable to emit an item (returned by a specified function) rather than - * invoking {@link Observer#onError onError} if it encounters an error. - *

- * - *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorReturn method changes this behavior. If you pass a function - * (resumeFunction) to an Observable's onErrorReturn method, if the - * original Observable encounters an error, instead of invoking its Observer's - * onError function, it will instead pass the return value of - * resumeFunction to the Observer's {@link Observer#onNext onNext} method. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param resumeFunction - * a function that returns an item that the Observable will emit if the source - * Observable encounters an error - * @return the original Observable with appropriately modified behavior - */ - public Observable onErrorResumeNext(final Object resumeFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(resumeFunction); - return onErrorResumeNext(this, new Func1>() { - - @Override - @SuppressWarnings("unchecked") - public Observable call(Throwable e) { - return (Observable) _f.call(e); - } - }); - } - /** * Instruct an Observable to pass control to another Observable rather than invoking * {@link Observer#onError onError} if it encounters an error. @@ -3731,42 +2672,6 @@ public Observable onErrorReturn(Func1 resumeFunction) { return onErrorReturn(this, resumeFunction); } - /** - * Instruct an Observable to emit a particular item rather than invoking - * {@link Observer#onError onError} if it encounters an error. - *

- * - *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorReturn method changes this behavior. If you pass a function - * (resumeFunction) to an Observable's onErrorReturn method, if the - * original Observable encounters an error, instead of invoking its Observer's - * onError function, it will instead pass the return value of - * resumeFunction to the Observer's {@link Observer#onNext onNext} method. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param resumeFunction - * a function that returns an item that the new Observable will emit if the source - * Observable encounters an error - * @return the original Observable with appropriately modified behavior - */ - public Observable onErrorReturn(final Object resumeFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(resumeFunction); - return onErrorReturn(this, new Func1() { - - @Override - @SuppressWarnings("unchecked") - public T call(Throwable e) { - return (T) _f.call(e); - } - }); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -3840,17 +2745,6 @@ public ConnectableObservable publish() { return publish(this); } - /** - * A version of reduce() for use by dynamic languages. - *

- * - * - * @see #reduce(Func2) - */ - public Observable reduce(Object accumulator) { - return reduce(this, accumulator); - } - /** * Synonymous with reduce(). *

@@ -3862,17 +2756,6 @@ public Observable aggregate(Func2 accumulator) { return aggregate(this, accumulator); } - /** - * A version of aggregate() for use by dynamic languages. - *

- * - * - * @see #reduce(Func2) - */ - public Observable aggregate(Object accumulator) { - return aggregate(this, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -3900,17 +2783,6 @@ public Observable reduce(R initialValue, Func2 accumulator) { return reduce(this, initialValue, accumulator); } - /** - * A version of reduce() for use by dynamic languages. - *

- * - * - * @see #reduce(Object, Func2) - */ - public Observable reduce(R initialValue, Object accumulator) { - return reduce(this, initialValue, accumulator); - } - /** * Synonymous with reduce(). *

@@ -3922,17 +2794,6 @@ public Observable aggregate(R initialValue, Func2 accumulator) { return aggregate(this, initialValue, accumulator); } - /** - * A version of aggregate() for use by dynamic languages. - *

- * - * - * @see #reduce(Object, Func2) - */ - public Observable aggregate(R initialValue, Object accumulator) { - return aggregate(this, initialValue, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -3993,17 +2854,6 @@ public Observable sample(long period, TimeUnit unit, Scheduler scheduler) { return create(OperationSample.sample(this, period, unit, scheduler)); } - /** - * A version of scan() for use by dynamic languages. - *

- * - * - * @see #scan(Func2) - */ - public Observable scan(final Object accumulator) { - return scan(this, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -4031,17 +2881,6 @@ public Observable scan(R initialValue, Func2 accumulator) { return scan(this, initialValue, accumulator); } - /** - * A version of scan() for use by dynamic languages. - *

- * - * - * @see #scan(Object, Func2) - */ - public Observable scan(final R initialValue, final Object accumulator) { - return scan(this, initialValue, accumulator); - } - /** * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by * the source Observable satisfy a condition. @@ -4057,21 +2896,6 @@ public Observable all(Func1 predicate) { return all(this, predicate); } - /** - * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by - * the source Observable satisfy a condition. - *

- * - * - * @param predicate - * a function that evaluates an item and returns a Boolean - * @return an Observable that emits true if all items emitted by the source - * Observable satisfy the predicate; otherwise, false - */ - public Observable all(Object predicate) { - return all(this, predicate); - } - /** * Returns an Observable that skips the first num items emitted by the source * Observable and emits the remainder. @@ -4126,22 +2950,6 @@ public Observable takeWhile(final Func1 predicate) { return takeWhile(this, predicate); } - /** - * Returns an Observable that emits items emitted by the source Observable so long as a - * specified condition is true. - *

- * - * - * @param predicate - * a function that evaluates an item emitted by the source Observable and returns a - * Boolean - * @return an Observable that emits the items from the source Observable so long as each item - * satisfies the condition defined by predicate - */ - public Observable takeWhile(final Object predicate) { - return takeWhile(this, predicate); - } - /** * Returns an Observable that emits the items emitted by a source Observable so long as a given * predicate remains true, where the predicate can operate on both the item and its index @@ -4159,24 +2967,6 @@ public Observable takeWhileWithIndex(final Func2 predica return takeWhileWithIndex(this, predicate); } - /** - * Returns an Observable that emits the items emitted by a source Observable so long as a given - * predicate remains true, where the predicate can operate on both the item and its index - * relative to the complete sequence. - *

- * - * - * @param predicate - * a function that evaluates an item emitted by the source Observable and returns a - * Boolean; the second parameter of the function represents the index of the source - * item - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return true for each item, then completes - */ - public Observable takeWhileWithIndex(final Object predicate) { - return takeWhileWithIndex(this, predicate); - } - /** * Returns an Observable that emits only the last count items emitted by the source * Observable. @@ -4265,21 +3055,6 @@ public Observable> toSortedList(Func2 sortFunction) { return toSortedList(this, sortFunction); } - /** - * Return an Observable that emits the items emitted by the source Observable, in a sorted - * order based on a specified comparison function - *

- * - * - * @param sortFunction - * a function that compares two items emitted by the source Observable and returns - * an Integer that indicates their sort order - * @return an Observable that emits the items from the source Observable in sorted order - */ - public Observable> toSortedList(final Object sortFunction) { - return toSortedList(this, sortFunction); - } - /** * Emit a specified set of items before beginning to emit items from the source Observable. *

@@ -4316,28 +3091,6 @@ public Observable> groupBy(final Func1 keyS return groupBy(this, keySelector, elementSelector); } - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param keySelector - * a function that extracts the key from an item - * @param elementSelector - * a function to map a source item to an item in a {@link GroupedObservable} - * @param - * the key type - * @param - * the type of items emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - public Observable> groupBy(final Object keySelector, final Object elementSelector) { - return groupBy(this, keySelector, elementSelector); - } - /** * Groups the items emitted by an Observable according to a specified criterion, and emits these * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. @@ -4356,24 +3109,6 @@ public Observable> groupBy(final Func1 keySele return groupBy(this, keySelector); } - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param keySelector - * a function that extracts the key for each item - * @param - * the key type - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - public Observable> groupBy(final Object keySelector) { - return groupBy(this, keySelector); - } - /** * Converts an Observable into a {@link BlockingObservable} (an Observable with blocking * operators). @@ -4486,6 +3221,83 @@ public void testSequenceEqual() { verify(result, times(1)).onNext(false); } + @Test + public void testSubscribeWithMap() { + Observable o = toObservable(1, 2, 3); + final Observer observer = mock(Observer.class); + Action1 onNext = new Action1() { + @Override + public void call(Integer in) { + observer.onNext(in); + } + }; + Action1 onError = new Action1() { + @Override + public void call(Exception ex) { + observer.onError(ex); + } + }; + Action0 onCompleted = new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }; + Map fMap = new HashMap(); + fMap.put("onNext", onNext); + fMap.put("onError", onError); + fMap.put("onCompleted", onCompleted); + o.subscribe(fMap); + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onNext(3); + verify(observer, times(1)).onCompleted(); + verify(observer, times(0)).onError(any(Exception.class)); + } + + @Test + public void testSubscribeWithMapAndScheduler() { + Observable o = toObservable(1, 2, 3); + final Observer observer = mock(Observer.class); + Action1 onNext = new Action1() { + @Override + public void call(Integer in) { + observer.onNext(in); + } + }; + Action1 onError = new Action1() { + @Override + public void call(Exception ex) { + observer.onError(ex); + } + }; + Action0 onCompleted = new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }; + Map fMap = new HashMap(); + fMap.put("onNext", onNext); + fMap.put("onError", onError); + fMap.put("onCompleted", onCompleted); + + TestScheduler scheduler = new TestScheduler(); + + o.subscribe(fMap, scheduler); + verify(observer, times(0)).onNext(any(Integer.class)); + verify(observer, times(0)).onError(any(Exception.class)); + verify(observer, times(0)).onCompleted(); + + scheduler.advanceTimeBy(10, TimeUnit.SECONDS); + + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onNext(3); + verify(observer, times(1)).onCompleted(); + verify(observer, times(0)).onError(any(Exception.class)); + } + @Test public void testOnSubscribeFails() { @SuppressWarnings("unchecked") @@ -4905,10 +3717,10 @@ public void run() { }).start(); return Subscriptions.empty(); } - }).subscribe(new Action1() { + }).subscribe(new Action1() { @Override - public void call(Object t1) { + public void call(String t1) { } diff --git a/rxjava-core/src/main/java/rx/observables/BlockingObservable.java b/rxjava-core/src/main/java/rx/observables/BlockingObservable.java index 2d826a8fea..eb16260d2b 100644 --- a/rxjava-core/src/main/java/rx/observables/BlockingObservable.java +++ b/rxjava-core/src/main/java/rx/observables/BlockingObservable.java @@ -117,22 +117,6 @@ public static T last(final Observable source, final Func1 pre return last(source.filter(predicate)); } - /** - * Returns the last item emitted by an {@link Observable} that matches a given predicate. - *

- * - * - * @param source - * the source {@link Observable} - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} for which the predicate function - * returns true - */ - public static T last(final Observable source, final Object predicate) { - return last(source.filter(predicate)); - } - /** * Returns the last item emitted by an {@link Observable}, or a default value if no item is * emitted. @@ -173,35 +157,6 @@ public static T lastOrDefault(Observable source, T defaultValue, Func1 - * - * - * @param source - * the source {@link Observable} - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @param - * the type of items emitted by the {@link Observable} - * @return the last item emitted by an {@link Observable} that matches the predicate, or the - * default value if no matching item is emitted - */ - public static T lastOrDefault(Observable source, T defaultValue, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return lastOrDefault(source, defaultValue, new Func1() { - @Override - public Boolean call(T args) { - return (Boolean) _f.call(args); - } - }); - } - /** * Returns an {@link Iterable} that always returns the item most recently emitted by an * {@link Observable}. @@ -293,24 +248,6 @@ public static T single(Observable source, Func1 predicate) { return from(source).single(predicate); } - /** - * If the {@link Observable} completes after emitting a single item that matches a given - * predicate, return that item, otherwise throw an exception. - * - * - * @param source - * the source {@link Observable} - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the source {@link Observable} that matches the predicate - * @throws IllegalStateException - * if the {@link Observable} does not emit exactly one item that matches the - * predicate - */ - public static T single(Observable source, Object predicate) { - return from(source).single(predicate); - } - /** * If the {@link Observable} completes after emitting a single item, return that item, otherwise * return a default value. @@ -347,25 +284,6 @@ public static T singleOrDefault(Observable source, T defaultValue, Func1< return from(source).singleOrDefault(defaultValue, predicate); } - /** - * If the {@link Observable} completes after emitting a single item that matches a given - * predicate, return that item, otherwise return a default value. - *

- * - * - * @param source - * the source {@link Observable} - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the source {@link Observable} that matches the predicate, - * or a default value if no such value is emitted - */ - public static T singleOrDefault(Observable source, T defaultValue, Object predicate) { - return from(source).singleOrDefault(defaultValue, predicate); - } - /** * Returns a {@link Future} representing the single value emitted by an {@link Observable}. *

@@ -476,45 +394,6 @@ public void onNext(T args) { } } - /** - * Invoke a method on each item emitted by the {@link Observable}; block until the Observable - * completes. - *

- * NOTE: This will block even if the Observable is asynchronous. - *

- * This is similar to {@link #subscribe(Observer)}, but it blocks. Because it blocks it does - * not need the {@link Observer#onCompleted()} or {@link Observer#onError(Throwable)} methods. - *

- * - * - * @param o - * the {@link Action1} to invoke for every item emitted by the {@link Observable} - * @throws RuntimeException - * if an error occurs - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void forEach(final Object o) { - if (o instanceof Action1) { - // in case a dynamic language is not correctly handling the overloaded methods and we receive an Action1 just forward to the correct method. - forEach((Action1) o); - } - - // lookup and memoize onNext - if (o == null) { - throw new RuntimeException("onNext must be implemented"); - } - final FuncN onNext = Functions.from(o); - - forEach(new Action1() { - - @Override - public void call(Object args) { - onNext.call(args); - } - - }); - } - /** * Returns an {@link Iterator} that iterates over all items emitted by a specified * {@link Observable}. @@ -555,27 +434,6 @@ public T last(final Func1 predicate) { return last(this, predicate); } - /** - * Returns the last item emitted by a specified {@link Observable} that matches a predicate. - *

- * - * - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} that matches the predicate - */ - public T last(final Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return last(this, new Func1() { - @Override - public Boolean call(T args) { - return (Boolean) _f.call(args); - } - }); - } - /** * Returns the last item emitted by a specified {@link Observable}, or a default value if no * items are emitted. @@ -620,23 +478,6 @@ public T lastOrDefault(T defaultValue, Func1 predicate) { return lastOrDefault(this, defaultValue, predicate); } - /** - * Returns the last item emitted by a specified {@link Observable} that matches a predicate, or - * a default value if no such items are emitted. - *

- * - * - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} that matches the predicate, or the - * default value if no matching items are emitted - */ - public T lastOrDefault(T defaultValue, Object predicate) { - return lastOrDefault(this, defaultValue, predicate); - } - /** * Returns an {@link Iterable} that always returns the item most recently emitted by an * {@link Observable}. @@ -692,28 +533,6 @@ public T single(Func1 predicate) { return _singleOrDefault(from(this.filter(predicate)), false, null); } - /** - * If the {@link Observable} completes after emitting a single item that matches a given - * predicate, return that item, otherwise throw an exception. - *

- * - * - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the source {@link Observable} that matches the predicate - */ - public T single(Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return single(new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - /** * If the {@link Observable} completes after emitting a single item, return that item; if it * emits more than one item, throw an exception; if it emits no items, return a default value. @@ -747,32 +566,6 @@ public T singleOrDefault(T defaultValue, Func1 predicate) { return _singleOrDefault(from(this.filter(predicate)), true, defaultValue); } - /** - * If the {@link Observable} completes after emitting a single item that matches a predicate, - * return that item; if it emits more than one such item, throw an exception; if it emits no - * items, return a default value. - *

- * - * - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the {@link Observable} that matches the predicate, or the - * default value if no such items are emitted - */ - public T singleOrDefault(T defaultValue, final Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return singleOrDefault(defaultValue, new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - /** * Returns a {@link Future} representing the single value emitted by an {@link Observable}. *

diff --git a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java index 089a850e99..f11af77898 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java +++ b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java @@ -62,7 +62,7 @@ public class OperationCombineLatest { * The aggregation function used to combine the source observable values. * @return A function from an observer to a subscription. This can be used to create an observable from. */ - public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) { + public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); a.addObserver(new CombineObserver(a, w1)); @@ -72,7 +72,7 @@ public static Func1, Subscription> combineLatest /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Func3 combineLatestFunction) { + public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Func3 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); a.addObserver(new CombineObserver(a, w1)); @@ -83,7 +83,7 @@ public static Func1, Subscription> combineLa /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Func4 combineLatestFunction) { + public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Func4 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); a.addObserver(new CombineObserver(a, w1)); @@ -93,11 +93,11 @@ public static Func1, Subscription> combi } private static class CombineObserver implements Observer { - final Observable w; - final Aggregator a; + final Observable w; + final Aggregator a; private Subscription subscription; - public CombineObserver(Aggregator a, Observable w) { + public CombineObserver(Aggregator a, Observable w) { this.a = a; this.w = w; } @@ -130,9 +130,9 @@ public void onNext(T args) { * whenever we have received an event from one of the observables, as soon as each Observable has received * at least one event. */ - private static class Aggregator implements Func1, Subscription> { + private static class Aggregator implements Func1, Subscription> { - private volatile Observer observer; + private volatile Observer observer; private final FuncN combineLatestFunction; private final AtomicBoolean running = new AtomicBoolean(true); @@ -169,7 +169,7 @@ void addObserver(CombineObserver w) { * * @param w The observer that has completed. */ - void complete(CombineObserver w) { + void complete(CombineObserver w) { int completed = numCompleted.incrementAndGet(); // if all CombineObservers are completed, we mark the whole thing as completed if (completed == observers.size()) { @@ -199,7 +199,7 @@ void error(Throwable e) { * @param w * @param arg */ - void next(CombineObserver w, T arg) { + void next(CombineObserver w, T arg) { if (observer == null) { throw new RuntimeException("This shouldn't be running if an Observer isn't registered"); } @@ -232,7 +232,7 @@ void next(CombineObserver w, T arg) { } @Override - public Subscription call(Observer observer) { + public Subscription call(Observer observer) { if (this.observer != null) { throw new IllegalStateException("Only one Observer can subscribe to this Observable."); } diff --git a/rxjava-core/src/main/java/rx/operators/OperationZip.java b/rxjava-core/src/main/java/rx/operators/OperationZip.java index 92d987ffa7..e5b80c8be9 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationZip.java +++ b/rxjava-core/src/main/java/rx/operators/OperationZip.java @@ -299,7 +299,7 @@ public static class UnitTest { @SuppressWarnings("unchecked") @Test public void testCollectionSizeDifferentThanFunction() { - FuncN zipr = Functions.from(getConcatStringIntegerIntArrayZipr()); + FuncN zipr = Functions.fromFunction(getConcatStringIntegerIntArrayZipr()); /* define a Observer to receive aggregated events */ Observer aObserver = mock(Observer.class); diff --git a/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java b/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java index b154702ff8..c25ea56dee 100644 --- a/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java +++ b/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java @@ -130,9 +130,8 @@ public static class UnitTest { @Test public void testNeverCompleted() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -152,9 +151,8 @@ private void assertNeverCompletedObserver(Observer aObserver) @Test public void testCompleted() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -175,9 +173,8 @@ private void assertCompletedObserver(Observer aObserver) @Test public void testError() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -201,7 +198,7 @@ private void assertErrorObserver(Observer aObserver) @Test public void testUnsubscribeBeforeCompleted() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); diff --git a/rxjava-core/src/main/java/rx/subjects/PublishSubject.java b/rxjava-core/src/main/java/rx/subjects/PublishSubject.java index 5fccdb86d2..467771758d 100644 --- a/rxjava-core/src/main/java/rx/subjects/PublishSubject.java +++ b/rxjava-core/src/main/java/rx/subjects/PublishSubject.java @@ -195,12 +195,12 @@ public static class UnitTest { @Test public void test() { PublishSubject subject = PublishSubject.create(); - final AtomicReference>> actualRef = new AtomicReference>>(); + final AtomicReference>> actualRef = new AtomicReference>>(); Observable>> wNotificationsList = subject.materialize().toList(); - wNotificationsList.subscribe(new Action1>>() { + wNotificationsList.subscribe(new Action1>>() { @Override - public void call(List> actual) { + public void call(List> actual) { actualRef.set(actual); } }); @@ -245,9 +245,8 @@ public void unsubscribe() { @Test public void testCompleted() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -256,7 +255,6 @@ public void testCompleted() { subject.onNext("three"); subject.onCompleted(); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -286,9 +284,8 @@ private void assertNeverObserver(Observer aObserver) @Test public void testError() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -297,7 +294,6 @@ public void testError() { subject.onNext("three"); subject.onError(testException); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -320,9 +316,8 @@ private void assertErrorObserver(Observer aObserver) @Test public void testSubscribeMidSequence() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -331,7 +326,6 @@ public void testSubscribeMidSequence() { assertObservedUntilTwo(aObserver); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -353,9 +347,8 @@ private void assertCompletedStartingWithThreeObserver(Observer aObserver @Test public void testUnsubscribeFirstObserver() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); Subscription subscription = subject.subscribe(aObserver); @@ -365,7 +358,6 @@ public void testUnsubscribeFirstObserver() { subscription.unsubscribe(); assertObservedUntilTwo(aObserver); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -397,9 +389,8 @@ private void assertObservedUntilTwo(Observer aObserver) */ @Test public void testUnsubscribeAfterOnCompleted() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer anObserver = mock(Observer.class); subject.subscribe(anObserver); @@ -426,7 +417,7 @@ public void testUnsubscribeAfterOnCompleted() { @Test public void testUnsubscribeAfterOnError() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); RuntimeException exception = new RuntimeException("failure"); @SuppressWarnings("unchecked") diff --git a/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java b/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java index b6711c15c4..2d852f5bb0 100644 --- a/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java +++ b/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java @@ -186,7 +186,7 @@ public static class UnitTest { @SuppressWarnings("unchecked") @Test public void testCompleted() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer o1 = mock(Observer.class); subject.subscribe(o1); @@ -223,7 +223,7 @@ private void assertCompletedObserver(Observer aObserver) @SuppressWarnings("unchecked") @Test public void testError() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -256,7 +256,7 @@ private void assertErrorObserver(Observer aObserver) @SuppressWarnings("unchecked") @Test public void testSubscribeMidSequence() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -280,7 +280,7 @@ public void testSubscribeMidSequence() { @SuppressWarnings("unchecked") @Test public void testUnsubscribeFirstObserver() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer aObserver = mock(Observer.class); Subscription subscription = subject.subscribe(aObserver); diff --git a/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java b/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java index f3f1dd46c7..9ad366f579 100644 --- a/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java +++ b/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java @@ -83,23 +83,6 @@ public static CompositeSubscription create(Subscription... subscriptions) { return new CompositeSubscription(subscriptions); } - /** - * A {@link Subscription} implemented via an anonymous function (such as closures from other languages). - * - * @return {@link Subscription} - */ - public static Subscription create(final Object unsubscribe) { - final FuncN f = Functions.from(unsubscribe); - return new Subscription() { - - @Override - public void unsubscribe() { - f.call(); - } - - }; - } - /** * A {@link Subscription} that does nothing when its unsubscribe method is called. */ diff --git a/rxjava-core/src/main/java/rx/util/functions/Action.java b/rxjava-core/src/main/java/rx/util/functions/Action.java new file mode 100644 index 0000000000..59d358093a --- /dev/null +++ b/rxjava-core/src/main/java/rx/util/functions/Action.java @@ -0,0 +1,26 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.util.functions; + +/** + * All Action interfaces extend from this. + *

+ * Marker interface to allow instanceof checks. + */ +public interface Action extends Function { + +} diff --git a/rxjava-core/src/main/java/rx/util/functions/Action0.java b/rxjava-core/src/main/java/rx/util/functions/Action0.java index 62d57bd563..832b78dd52 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action0.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action0.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action0 extends Function { +public interface Action0 extends Action { public void call(); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/Action1.java b/rxjava-core/src/main/java/rx/util/functions/Action1.java index 14fa7ced8c..68c1b804ed 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action1.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action1.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action1 extends Function { +public interface Action1 extends Action { public void call(T1 t1); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/Action2.java b/rxjava-core/src/main/java/rx/util/functions/Action2.java index 8a17875a9e..ba4826bdca 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action2.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action2.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action2 extends Function { +public interface Action2 extends Action { public void call(T1 t1, T2 t2); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/Action3.java b/rxjava-core/src/main/java/rx/util/functions/Action3.java index 2b613b621e..3c2b083c6f 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action3.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action3.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action3 extends Function { +public interface Action3 extends Action { public void call(T1 t1, T2 t2, T3 t3); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/Function.java b/rxjava-core/src/main/java/rx/util/functions/Function.java index cfe85a221f..be3440323b 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Function.java +++ b/rxjava-core/src/main/java/rx/util/functions/Function.java @@ -1,9 +1,25 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 rx.util.functions; /** * All Func and Action interfaces extend from this. *

- * Marker interface to allow isntanceof checks. + * Marker interface to allow instanceof checks. */ public interface Function { diff --git a/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java b/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java index 6ec87f358a..d72f68856c 100644 --- a/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java +++ b/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java @@ -15,16 +15,10 @@ */ package rx.util.functions; -public interface FunctionLanguageAdaptor { +import java.util.Map; +import java.util.Set; - /** - * Invoke the function and return the results. - * - * @param function - * @param args - * @return Object results from function execution - */ - Object call(Object function, Object[] args); +public interface FunctionLanguageAdaptor { /** * The Class of the Function that this adaptor serves. @@ -35,5 +29,25 @@ public interface FunctionLanguageAdaptor { * * @return Class[] of classes that this adaptor should be invoked for. */ - public Class[] getFunctionClass(); + //public Class[] getFunctionClass(); + + /** + * Map detailing how to rewrite a native function class into an Rx {@code Action} + * Example: for Groovy, a 1-element Map : { groovy.lang.Closure -> GroovyActionWrapper } + * @return map for rewriting native functions to Rx {@code Action}s. + */ + public Map, Class> getActionClassRewritingMap(); + + /** + * Map detailing how to rewrite a native function class into an Rx {@code Function} + * Example: for Groovy, a 1-element Map : { groovy.lang.Closure -> GroovyFunctionWrapper } + * @return map for rewriting native functions to Rx {@code Function}s. + */ + public Map, Class> getFunctionClassRewritingMap(); + + /** + * All native function classes that require translation into the Rx world + * @return set of all native function classes + */ + public Set> getAllClassesToRewrite(); } diff --git a/rxjava-core/src/main/java/rx/util/functions/Functions.java b/rxjava-core/src/main/java/rx/util/functions/Functions.java index 4486e7bd26..218e0d2c9a 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Functions.java +++ b/rxjava-core/src/main/java/rx/util/functions/Functions.java @@ -15,209 +15,13 @@ */ package rx.util.functions; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; - /** - * Allows execution of functions from multiple different languages. - *

- * Language support is provided via implementations of {@link FunctionLanguageAdaptor}. - *

- * This class will dynamically look for known language adaptors on the classpath at startup or new ones can be registered using {@link #registerLanguageAdaptor(Class[], FunctionLanguageAdaptor)}. + * Utility class for getting concrete implementations of Functions/Actions */ public class Functions { - private final static ConcurrentHashMap, FunctionLanguageAdaptor> languageAdaptors = new ConcurrentHashMap, FunctionLanguageAdaptor>(); - - static { - /* optimistically look for supported languages if they are in the classpath */ - loadLanguageAdaptor("Groovy"); - loadLanguageAdaptor("JRuby"); - loadLanguageAdaptor("Clojure"); - loadLanguageAdaptor("Scala"); - // as new languages arise we can add them here but this does not prevent someone from using 'registerLanguageAdaptor' directly - } - - private static boolean loadLanguageAdaptor(String name) { - String className = "rx.lang." + name.toLowerCase() + "." + name + "Adaptor"; - try { - Class c = Class.forName(className); - FunctionLanguageAdaptor a = (FunctionLanguageAdaptor) c.newInstance(); - registerLanguageAdaptor(a.getFunctionClass(), a); - /* - * Using System.err/System.out as this is the only place in the library where we do logging and it's only at startup. - * I don't want to include SL4J/Log4j just for this and no one uses Java Logging. - */ - System.out.println("RxJava => Successfully loaded function language adaptor: " + name + " with path: " + className); - } catch (ClassNotFoundException e) { - System.err.println("RxJava => Could not find function language adaptor: " + name + " with path: " + className); - return false; - } catch (Throwable e) { - System.err.println("RxJava => Failed trying to initialize function language adaptor: " + className); - e.printStackTrace(); - return false; - } - return true; - } - - public static void registerLanguageAdaptor(Class[] functionClasses, FunctionLanguageAdaptor adaptor) { - for (Class functionClass : functionClasses) { - if (functionClass.getPackage().getName().startsWith("java.")) { - throw new IllegalArgumentException("FunctionLanguageAdaptor implementations can not specify java.lang.* classes."); - } - languageAdaptors.put(functionClass, adaptor); - } - } - - public static void removeLanguageAdaptor(Class functionClass) { - languageAdaptors.remove(functionClass); - } - - public static Collection getRegisteredLanguageAdaptors() { - return languageAdaptors.values(); - } - - /** - * Utility method for determining the type of closure/function and executing it. - * - * @param function - */ - @SuppressWarnings({ "rawtypes" }) - public static FuncN from(final Object function) { - if (function == null) { - throw new RuntimeException("function is null. Can't send arguments to null function."); - } - - /* check for typed Rx Function implementation first */ - if (function instanceof Function) { - return fromFunction((Function) function); - } else { - /* not an Rx Function so try language adaptors */ - - // check for language adaptor - for (final Class c : languageAdaptors.keySet()) { - if (c.isInstance(function)) { - final FunctionLanguageAdaptor la = languageAdaptors.get(c); - // found the language adaptor so wrap in FuncN and return - return new FuncN() { - - @Override - public Object call(Object... args) { - return la.call(function, args); - } - - }; - } - } - // no language adaptor found - } - - // no support found - throw new RuntimeException("Unsupported closure type: " + function.getClass().getSimpleName()); - } - - // - // @SuppressWarnings("unchecked") - // private static R executionRxFunction(Function function, Object... args) { - // // check Func* classes - // if (function instanceof Func0) { - // Func0 f = (Func0) function; - // if (args.length != 0) { - // throw new RuntimeException("The closure was Func0 and expected no arguments, but we received: " + args.length); - // } - // return (R) f.call(); - // } else if (function instanceof Func1) { - // Func1 f = (Func1) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func1 and expected 1 argument, but we received: " + args.length); - // } - // return f.call(args[0]); - // } else if (function instanceof Func2) { - // Func2 f = (Func2) function; - // if (args.length != 2) { - // throw new RuntimeException("The closure was Func2 and expected 2 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1]); - // } else if (function instanceof Func3) { - // Func3 f = (Func3) function; - // if (args.length != 3) { - // throw new RuntimeException("The closure was Func3 and expected 3 arguments, but we received: " + args.length); - // } - // return (R) f.call(args[0], args[1], args[2]); - // } else if (function instanceof Func4) { - // Func4 f = (Func4) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func4 and expected 4 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3]); - // } else if (function instanceof Func5) { - // Func5 f = (Func5) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func5 and expected 5 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4]); - // } else if (function instanceof Func6) { - // Func6 f = (Func6) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func6 and expected 6 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5]); - // } else if (function instanceof Func7) { - // Func7 f = (Func7) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func7 and expected 7 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - // } else if (function instanceof Func8) { - // Func8 f = (Func8) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func8 and expected 8 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); - // } else if (function instanceof Func9) { - // Func9 f = (Func9) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func9 and expected 9 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); - // } else if (function instanceof FuncN) { - // FuncN f = (FuncN) function; - // return f.call(args); - // } else if (function instanceof Action0) { - // Action0 f = (Action0) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action0 and expected 0 arguments, but we received: " + args.length); - // } - // f.call(); - // return null; - // } else if (function instanceof Action1) { - // Action1 f = (Action1) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action1 and expected 1 argument, but we received: " + args.length); - // } - // f.call(args[0]); - // return null; - // } else if (function instanceof Action2) { - // Action2 f = (Action2) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action2 and expected 2 argument, but we received: " + args.length); - // } - // f.call(args[0], args[1]); - // return null; - // } else if (function instanceof Action3) { - // Action3 f = (Action3) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action1 and expected 1 argument, but we received: " + args.length); - // } - // f.call(args[0], args[1], args[2]); - // return null; - // } - // - // throw new RuntimeException("Unknown implementation of Function: " + function.getClass().getSimpleName()); - // } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static FuncN fromFunction(Function function) { + public static FuncN fromFunction(Function function) { // check Func* classes if (function instanceof Func0) { return fromFunc((Func0) function); @@ -241,19 +45,27 @@ private static FuncN fromFunction(Function function) { return fromFunc((Func9) function); } else if (function instanceof FuncN) { return (FuncN) function; - } else if (function instanceof Action0) { - return fromAction((Action0) function); - } else if (function instanceof Action1) { - return fromAction((Action1) function); - } else if (function instanceof Action2) { - return fromAction((Action2) function); - } else if (function instanceof Action3) { - return fromAction((Action3) function); } throw new RuntimeException("Unknown implementation of Function: " + function.getClass().getSimpleName()); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static FuncN fromAction(Action action) { + // check Action* classes + if (action instanceof Action0) { + return fromAction((Action0) action); + } else if (action instanceof Action1) { + return fromAction((Action1) action); + } else if (action instanceof Action2) { + return fromAction((Action2) action); + } else if (action instanceof Action3) { + return fromAction((Action3) action); + } + + throw new RuntimeException("Unknown implementation of Action: " + action.getClass().getSimpleName()); + } + /** * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. * diff --git a/settings.gradle b/settings.gradle index f07f904404..d6cc324181 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,4 +4,5 @@ include 'rxjava-core', \ 'language-adaptors:rxjava-jruby', \ 'language-adaptors:rxjava-clojure', \ 'language-adaptors:rxjava-scala', \ +'language-adaptors:codegen', \ 'rxjava-contrib:rxjava-swing'