Skip to content

Handle android worker classes #1493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,10 @@ private void writeFieldsToWriter(Writer writer, JavaClass clazz) {
private void writeConstructorsToWriter(Writer writer, JavaClass clazz, DataRow dataRow, String generatedClassName, GenericHierarchyView genericHierarchyView) {
boolean isApplicationClass = androidClassChecker.isApplicationClass(clazz);
boolean isServiceClass = androidClassChecker.isServiceClass(clazz);
boolean isAndroidWorkerClass = androidClassChecker.isAndroidWorkerClass(clazz);

MethodSignatureReifier methodSignatureReifier = new MethodSignatureReifier(genericHierarchyView);
MethodsWriter methodsWriter = new MethodsWriterImpl(writer, suppressCallJSMethodExceptions, isApplicationClass, isServiceClass);
MethodsWriter methodsWriter = new MethodsWriterImpl(writer, suppressCallJSMethodExceptions, isApplicationClass, isServiceClass, isAndroidWorkerClass);
ImplementationObjectChecker implementationObjectChecker = new ImplementationObjectCheckerImpl();

List<String> implObjectMethods = Arrays.asList(dataRow.getMethods());
Expand All @@ -425,8 +426,9 @@ private void writeConstructorsToWriter(Writer writer, JavaClass clazz, DataRow d
private void writeMethodsToWriter(Writer writer, GenericHierarchyView genericHierarchyView, Map<JavaClass, GenericHierarchyView> interfaceGenericHierarchyViews, JavaClass clazz, List<String> userImplementedMethods, List<JavaClass> userImplementedInterfaces, String packageName) {
boolean isApplicationClass = androidClassChecker.isApplicationClass(clazz);
boolean isServiceClass = androidClassChecker.isServiceClass(clazz);
boolean isAndroidWorkerClass = androidClassChecker.isAndroidWorkerClass(clazz);

MethodsWriter methodsWriter = new MethodsWriterImpl(writer, suppressCallJSMethodExceptions, isApplicationClass, isServiceClass);
MethodsWriter methodsWriter = new MethodsWriterImpl(writer, suppressCallJSMethodExceptions, isApplicationClass, isServiceClass, isAndroidWorkerClass);

InheritedMethodsCollector inheritedMethodsCollector = new InheritedMethodsCollectorImpl.Builder()
.forJavaClass(clazz)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public interface AndroidClassChecker {
boolean isActivityClass(JavaClass clazz);
boolean isApplicationClass(JavaClass clazz);
boolean isServiceClass(JavaClass javaClass);
boolean isAndroidWorkerClass(JavaClass javaClass);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class AndroidClassCheckerImpl implements AndroidClassChecker {

private static final String APPLICATION_CLASS_NAME = "android.app.Application";
private static final String SERVICE_CLASS_NAME = "android.app.Service";
private static final String ANDROID_WORKER_CLASS_NAME = "androidx.work.ListenableWorker";
private static final List<String> ACTIVITY_TYPES = Arrays.asList("android.app.Activity","android.support.v7.app.AppCompatActivity","androidx.appcompat.app.AppCompatActivity");

private final ClassHierarchyParser classHierarchyParser;
Expand Down Expand Up @@ -58,4 +59,14 @@ public boolean isServiceClass(JavaClass javaClass){
HierarchyView hierarchyView = classHierarchyParser.getClassHierarchy(javaClass);
return hierarchyView.getAllParentClassesNames().contains(SERVICE_CLASS_NAME);
}

@Override
public boolean isAndroidWorkerClass(JavaClass javaClass) {
if(javaClass.getClass().equals(ANDROID_WORKER_CLASS_NAME)){
return true;
}

HierarchyView hierarchyView = classHierarchyParser.getClassHierarchy(javaClass);
return hierarchyView.getAllParentClassesNames().contains(ANDROID_WORKER_CLASS_NAME);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import org.apache.bcel.generic.Type;
import org.nativescript.staticbindinggenerator.DefaultValues;
import org.nativescript.staticbindinggenerator.Generator;
import org.nativescript.staticbindinggenerator.InputParameters;
import org.nativescript.staticbindinggenerator.Writer;
import org.nativescript.staticbindinggenerator.generating.parsing.methods.ReifiedJavaMethod;
import org.nativescript.staticbindinggenerator.generating.writing.MethodsWriter;
import org.nativescript.staticbindinggenerator.naming.BcelNamingUtil;

import java.util.Arrays;
import java.util.List;

public class MethodsWriterImpl implements MethodsWriter {
Expand All @@ -24,6 +22,8 @@ public class MethodsWriterImpl implements MethodsWriter {
private static final String GET_INSTANCE_METHOD_SIGNATURE_PATTERN = "public static %s getInstance()";
private static final String RUNTIME_CALL_JS_METHOD_CALL_PATTERN = "com.tns.Runtime.callJSMethod(this, \"%s\", %s.class, " + ARGS_VARIABLE_NAME + ")";
private static final String RUNTIME_CALL_JS_CONSTRUCTOR_METHOD_CALL_PATTERN = "com.tns.Runtime.callJSMethod(this, \"%s\", %s.class, true," + ARGS_VARIABLE_NAME + ")";
private static final String RUNTIME_CALL_JS_METHOD_FROM_POSSIBLE_NON_MAIN_THREAD_CALL_PATTERN = "com.tns.Runtime.callJSMethodFromPossibleNonMainThread(this, \"%s\", %s.class, " + ARGS_VARIABLE_NAME + ")";
private static final String RUNTIME_CALL_JS_CONSTRUCTOR_METHOD_FROM_POSSIBLE_NON_MAIN_THREAD_CALL_PATTERN = "com.tns.Runtime.callJSMethodFromPossibleNonMainThread(this, \"%s\", %s.class, true," + ARGS_VARIABLE_NAME + ")";
private static final String ARGS_VARIABLE_PATTERN = "java.lang.Object[] " + ARGS_VARIABLE_NAME + " = new java.lang.Object[%d];";

private static final String THROWS_DECLARATION_BEGINNING = " throws ";
Expand All @@ -41,6 +41,7 @@ public class MethodsWriterImpl implements MethodsWriter {
private static final String RUNTIME_INIT_METHOD_CALL_STATEMENT = "com.tns.Runtime " + RUNTIME_VARIABLE_NAME + " = com.tns.RuntimeHelper.initRuntime(this);";
private static final String RUNTIME_RUN_METHOD_CALL_STATEMENT = RUNTIME_VARIABLE_NAME + ".run();";
private static final String RUNTIME_INIT_INSTANCE_METHOD_CALL_STATEMENT = "com.tns.Runtime.initInstance(this);";
private static final String RUNTIME_INIT_INSTANCE_FROM_POSSIBLE_NON_MAIN_THREAD_METHOD_CALL_STATEMENT = "com.tns.Runtime.initInstanceFromPossibleNonMainThread(this);";

private static final String RUNTIME_IS_INITIALIZED_METHOD_CALL = "com.tns.Runtime.isInitialized()";

Expand All @@ -60,13 +61,15 @@ public class MethodsWriterImpl implements MethodsWriter {
private final boolean shouldSuppressCallJsMethodExceptions;
private final boolean isForApplicationClass;
private final boolean isForServiceClass;
private final boolean isForAndroidWorkerClass;


public MethodsWriterImpl(final Writer writer, boolean shouldSuppressCallJsMethodExceptions, boolean isForApplicationClass, boolean isForServiceClass) {
public MethodsWriterImpl(final Writer writer, boolean shouldSuppressCallJsMethodExceptions, boolean isForApplicationClass, boolean isForServiceClass, boolean isForAndroidWorkerClass) {
this.writer = writer;
this.shouldSuppressCallJsMethodExceptions = shouldSuppressCallJsMethodExceptions;
this.isForApplicationClass = isForApplicationClass;
this.isForServiceClass = isForServiceClass;
this.isForAndroidWorkerClass = isForAndroidWorkerClass;
}

@Override
Expand Down Expand Up @@ -106,8 +109,10 @@ public void writeConstructor(String className, ReifiedJavaMethod method, boolean
writer.write(CLOSING_ROUND_BRACKET_LITERAL);
writer.write(END_OF_STATEMENT_LITERAL);

if (!isForApplicationClass && !isForServiceClass) {
if (!isForApplicationClass && !isForServiceClass && !isForAndroidWorkerClass) {
writer.write(RUNTIME_INIT_INSTANCE_METHOD_CALL_STATEMENT);
} else if (isForAndroidWorkerClass) {
writer.write(RUNTIME_INIT_INSTANCE_FROM_POSSIBLE_NON_MAIN_THREAD_METHOD_CALL_STATEMENT);
}

if (hasUserImplementedInitMethod) {
Expand Down Expand Up @@ -236,9 +241,9 @@ private void writeSuppressDeprecationsToWriter() {
}

private void writeMethodSignature(ReifiedJavaMethod method, boolean isUserImplemented) {
if(method.isDeprecated() &&
if (method.isDeprecated() &&
(!isUserImplemented // we want to show warnings only for methods implemented by the user, but not for the SBG auto implemented abstract methods
|| InputParameters.getCurrent().getSuppressDeprecationWarnings())) {
|| InputParameters.getCurrent().getSuppressDeprecationWarnings())) {
writeSuppressDeprecationsToWriter();
}

Expand Down Expand Up @@ -279,7 +284,7 @@ private void writeMethodBody(ReifiedJavaMethod method) {
writeArgumentsVariableForMethodCall(method);
writeCallJsMethodExceptionsSuppressBlockBeginningIfNecessary();

String methodCallPattern = method.isConstructor() ? RUNTIME_CALL_JS_CONSTRUCTOR_METHOD_CALL_PATTERN : RUNTIME_CALL_JS_METHOD_CALL_PATTERN;
String methodCallPattern = getMethodCallPattern(method);
String runtimeCallJsMethodCall = String.format(methodCallPattern, getMethodName(method), BcelNamingUtil.resolveBcelTypeName(returnType));

if (!returnType.equals(Type.VOID)) {
Expand All @@ -292,6 +297,22 @@ private void writeMethodBody(ReifiedJavaMethod method) {
writeCallJsMethodExceptionsSuppressBlockClosingIfNecessary(returnType, getMethodName(method));
}

private String getMethodCallPattern(ReifiedJavaMethod method) {
if (method.isConstructor()) {
if (!isForAndroidWorkerClass) {
return RUNTIME_CALL_JS_CONSTRUCTOR_METHOD_CALL_PATTERN;
} else {
return RUNTIME_CALL_JS_CONSTRUCTOR_METHOD_FROM_POSSIBLE_NON_MAIN_THREAD_CALL_PATTERN;
}
} else {
if (!isForAndroidWorkerClass) {
return RUNTIME_CALL_JS_METHOD_CALL_PATTERN;
} else {
return RUNTIME_CALL_JS_METHOD_FROM_POSSIBLE_NON_MAIN_THREAD_CALL_PATTERN;
}
}
}

private String getMethodName(ReifiedJavaMethod method) {
if (method.isConstructor()) {
return "init";
Expand Down
86 changes: 77 additions & 9 deletions test-app/runtime/src/main/java/com/tns/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.atomic.AtomicInteger;

public class Runtime {
Expand Down Expand Up @@ -551,7 +555,6 @@ public static Runtime initializeRuntimeWithConfiguration(StaticConfiguration con
WorkThreadScheduler mainThreadScheduler = new WorkThreadScheduler(new MainThreadHandler(Looper.myLooper()));
DynamicConfiguration dynamicConfiguration = new DynamicConfiguration(0, mainThreadScheduler, null);
Runtime runtime = initRuntime(dynamicConfiguration);

return runtime;
}

Expand Down Expand Up @@ -737,6 +740,37 @@ public void unlock() {
unlock(runtimeId);
}

public static void initInstanceFromPossibleNonMainThread(final Object instance) {
if (isNotOnMainThread()) {
Runnable runnable = new Runnable() {
@Override
public void run() {
initInstance(instance);
}
};

RunnableFuture<Void> task = new FutureTask<>(runnable, null);
getMainThreadHandler().post(task);

try {
task.get(); // this will block until Runnable completes
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}

} else {
initInstance(instance);
}
}

private static Handler getMainThreadHandler() {
return new Handler(Looper.getMainLooper());
}

private static boolean isNotOnMainThread() {
return Looper.myLooper() != Looper.getMainLooper();
}

public static void initInstance(Object instance) {
ManualInstrumentation.Frame frame = ManualInstrumentation.start("Runtime.initInstance");
try {
Expand Down Expand Up @@ -1049,26 +1083,60 @@ private int getOrCreateJavaObjectID(Object obj) {
return result;
}

public static Object callJSMethodFromPossibleNonMainThread(Object javaObject, String methodName, Class<?> retType, Object... args) throws NativeScriptException {
return callJSMethodFromPossibleNonMainThread(javaObject, methodName, retType, false /* isConstructor */, args);
}

public static Object callJSMethodFromPossibleNonMainThread(Object javaObject, String methodName, Class<?> retType, boolean isConstructor, Object... args) throws NativeScriptException {
return callJSMethodFromPossibleNonMainThread(javaObject, methodName, retType, isConstructor, 0, args);
}

public static Object callJSMethodFromPossibleNonMainThread(Object javaObject, String methodName, boolean isConstructor, Object... args) throws NativeScriptException {
return callJSMethodFromPossibleNonMainThread(javaObject, methodName, void.class, isConstructor, 0, args);
}

public static Object callJSMethodFromPossibleNonMainThread(final Object javaObject, final String methodName, final Class<?> retType, final boolean isConstructor, final long delay, final Object... args) throws NativeScriptException {
if (isNotOnMainThread()) {
Callable<Object> callable = new Callable<Object>() {
@Override
public Object call() {
return callJSMethod(javaObject, methodName, retType, isConstructor, delay, args);
}
};

RunnableFuture<Object> task = new FutureTask<>(callable);
getMainThreadHandler().post(task);

try {
return task.get(); // this will block until Runnable completes
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}

} else {
return callJSMethod(javaObject, methodName, retType, isConstructor, delay, args);
}
}


// sends args in pairs (typeID, value, null) except for objects where its
// (typeid, javaObjectID, javaJNIClassPath)
public static Object callJSMethod(Object javaObject, String methodName, Class<?> retType, Object... args) throws NativeScriptException {
return callJSMethod(javaObject, methodName, retType, false /* isConstructor */, args);
}

public static Object callJSMethodWithDelay(Object javaObject, String methodName, Class<?> retType, long delay, Object... args) throws NativeScriptException {
return callJSMethod(javaObject, methodName, retType, false /* isConstructor */, delay, args);
}

public static Object callJSMethod(Object javaObject, String methodName, Class<?> retType, boolean isConstructor, Object... args) throws NativeScriptException {
Object ret = callJSMethod(javaObject, methodName, retType, isConstructor, 0, args);

return ret;
return callJSMethod(javaObject, methodName, retType, isConstructor, 0, args);
}

public static Object callJSMethod(Object javaObject, String methodName, boolean isConstructor, Object... args) throws NativeScriptException {
return callJSMethod(javaObject, methodName, void.class, isConstructor, 0, args);
}

public static Object callJSMethodWithDelay(Object javaObject, String methodName, Class<?> retType, long delay, Object... args) throws NativeScriptException {
return callJSMethod(javaObject, methodName, retType, false /* isConstructor */, delay, args);
}

public static Object callJSMethod(Object javaObject, String methodName, Class<?> retType, boolean isConstructor, long delay, Object... args) throws NativeScriptException {
Runtime runtime = Runtime.getCurrentRuntime();

Expand Down Expand Up @@ -1278,7 +1346,7 @@ private static Class<?> getCachedClass(String className) {
try {
clazz = classStorageService.retrieveClass(className);
return clazz;
} catch (RuntimeException e){
} catch (RuntimeException e) {
return null;
}
}
Expand Down