Skip to content
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

Fixing NoClassDefFoundError when using older APIs #1164

Merged
merged 3 commits into from
Sep 3, 2018
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
78 changes: 45 additions & 33 deletions test-app/app/src/main/assets/app/tests/testsWithContext.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
exports.run = function(cntxt)
{
describe("Tests with context ", function () {

var context = cntxt;
var myCustomEquality = function(first, second) {
return first == second;
};

beforeEach(function() {
jasmine.addCustomEqualityTester(myCustomEquality);
});

it("TestConstructorOverrideForBuiltinType", function () {

__log("TEST: TestConstructorOverrideForBuiltinType");

var ctorCalled = false;
var isConstructor = false;

Expand All @@ -24,48 +24,48 @@ exports.run = function(cntxt)
isConstructor = arguments[arguments.length - 1];
}
});

var btn = new MyButton(context);

expect(ctorCalled).toEqual(true);
expect(isConstructor).toEqual(true);
});

it("TestConstructorOverrideForBuiltinTypeWithInitMethod", function () {

__log("TEST: TestConstructorOverrideForBuiltinTypeWithInitMethod");

var initInvocationCount = 0;

var MyDatePicker = android.widget.DatePicker.extend({
init: function() {
++initInvocationCount;
}
});

var datePicker = new MyDatePicker(context);

__log("datePicker=" + datePicker);

var count1 = initInvocationCount;

expect(count1).toBeGreaterThan(0);

datePicker.init(2014, 3, 25, null);

var count2 = initInvocationCount;

expect(count2).toBeGreaterThan(count1);
});

it("TestBuiltinNestedClassCreation", function () {

__log("TEST: TestBuiltinNestedClassCreation");

var loader = new android.content.Loader(context);

var observer = new android.content.Loader.ForceLoadContentObserver(loader);

expect(observer).not.toEqual(null);
});

Expand All @@ -82,35 +82,47 @@ exports.run = function(cntxt)

expect(exceptionCaught).toBe(true);
});

it("TestPublicWindowManagerImplWithoutMetadata", function () {

__log("TEST: TestPublicWindowManagerImplWithoutMetadata");

var windowManagerImpl = context.getSystemService(android.content.Context.WINDOW_SERVICE);

var display = windowManagerImpl.getDefaultDisplay();

//__log("display.isValid=" + display.isValid());

var displayInfo = display.toString();

expect(displayInfo.length).toBeGreaterThan(0);
});

it("TestCanPassCharSequenceArray", function () {

__log("TEST: TestCanPassCharSequenceArray");

var alert = new android.app.AlertDialog.Builder(context);

var builder = alert.setItems(["One", "Two" ], new android.content.DialogInterface.OnClickListener({
onClick: function (dialog, which) {
//
}
}));

expect(builder).not.toEqual(null);
});

it("TestOldAPIForGettingMethodsListForMethodsWithParametersFromMissingType", function () {
__log("TEST: TestOldAPIForGettingMethodsListForMethodsWithParametersFromMissingType");

var til = new android.support.design.widget.TextInputLayout(context);
var editText = new android.widget.EditText(context);
var relativeLayout = new android.widget.RelativeLayout(context);
var relativeLayoutParams = new android.widget.RelativeLayout.LayoutParams(android.widget.RelativeLayout.LayoutParams.MATCH_PARENT, android.widget.RelativeLayout.LayoutParams.MATCH_PARENT);
relativeLayout.setLayoutParams(relativeLayoutParams);
editText.setHint("TEST");
til.addView(editText);
});
});
};
65 changes: 59 additions & 6 deletions test-app/runtime/src/main/java/com/tns/MethodResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,19 @@ static String resolveMethodOverload(Class<?> clazz, String methodName, Object[]
methodOverloadsForClass.put(c, finder);
}

ArrayList<Method> matchingMethods = finder.getMatchingMethods(methodName);
tryFindMatches(methodName, candidates, args, argLength, matchingMethods);
if (candidates.size() > iterationIndex && candidates.get(iterationIndex).y == 0) {
// direct matching (distance 0) found
break;
if(!finder.errorGettingMethods()) {
ArrayList<Method> matchingMethods = finder.getMatchingMethods(methodName);
tryFindMatches(methodName, candidates, args, argLength, matchingMethods);
if (candidates.size() > iterationIndex && candidates.get(iterationIndex).y == 0) {
// direct matching (distance 0) found
break;
}
} else {
Method method = finder.getMatchingMethodWithArguments(methodName, args);
if(method != null) {
candidates.add(new Tuple<>(method, 0));
break;
}
}

c = c.getSuperclass();
Expand Down Expand Up @@ -479,11 +487,42 @@ private static boolean convertPrimitiveArg(Class<?> primitiveType, Object[] args
static class MethodFinder {
private Method[] declaredMethods;
private HashMap<String, ArrayList<Method>> matchingMethods = new HashMap<String, ArrayList<Method>>();
private final Class<?> clazz;
private final boolean couldNotGetMethods;

public MethodFinder(Class<?> clazz) {
this.declaredMethods = clazz.getDeclaredMethods();
this.clazz = clazz;
boolean errorGettingMethods = false;
try {
this.declaredMethods = clazz.getDeclaredMethods();
} catch (NoClassDefFoundError error) {
// get at least the public methods as it shouldn't fail with NoClassDefFoundError
// it is not a good practice to catch Errors in Java, but we have the following case:
// when using support library > 26.0.0 and android API < 23
// if we try to create android.support.design.widget.TextInputLayout and call its addView method
// such error is thrown for android.view.ViewStructure as it is not present in older APIs
// so in that case we are going to get only the public methods using getMethods which may or may not throw the same error
// depends on the java Class implementation, on some of the cases it calls getDeclaredMethods() internally and
try {
this.declaredMethods = clazz.getMethods();
} catch (NoClassDefFoundError err) {
// if an error is thrown here we would set the declared methods to an empty array
// then when searching for a method we will try to find the exact method instead of looking in the declaredMethods list
this.declaredMethods = new Method[]{};
errorGettingMethods = true;
}
}
this.couldNotGetMethods = errorGettingMethods;
}

public boolean errorGettingMethods() {
return couldNotGetMethods;
}

public ArrayList<Method> getMatchingMethods(String methodName) {
if(this.errorGettingMethods()) {
return null;
}
ArrayList<Method> matches = this.matchingMethods.get(methodName);
if (matches == null) {
matches = new ArrayList<Method>();
Expand All @@ -505,5 +544,19 @@ public ArrayList<Method> getMatchingMethods(String methodName) {

return matches;
}

public Method getMatchingMethodWithArguments(String methodName, Object[] args) {
// fallback mechanism to try to find the exact method by name and arguments
// this method is not so useful as the arguments need to match the method types directly, but still it can find a method in some cases
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i].getClass();
}
try {
return this.clazz.getDeclaredMethod(methodName, types);
} catch (NoSuchMethodException ex) {
return null;
}
}
}
}