Skip to content

Commit 65cb7f0

Browse files
rossbacherAndreas Rossbacher
and
Andreas Rossbacher
authored
Fix inner class matching (matching on methods in inner classes) (#321)
* Fix inner class matching (matching on methods in inner classes) Add test to test inner class matching Remove nullability of activity class and throw exection (and crash) if class does not exist as this should never happen (unless it gets removed by Dexguard/Proguard/R8), this also removes this case from the error handler. Update kotlin and agp dependency to latest * Some more dependency cleanup to make release pipeline work. * Make findEntry public Add test test basic api Co-authored-by: Andreas Rossbacher <[email protected]>
1 parent 5b8f5d1 commit 65cb7f0

File tree

11 files changed

+122
-131
lines changed

11 files changed

+122
-131
lines changed

build.gradle

+14-10
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,39 @@ buildscript {
77
repositories {
88
google()
99
mavenCentral()
10+
jcenter() // Still needed for some old dependencies
1011
maven { url "https://oss.sonatype.org/service/local/repositories/snapshots/content/" }
1112
}
1213
dependencies {
1314
classpath deps.androidPlugin
1415
classpath deps.kotlinGradlePlugin
1516
classpath deps.gradleMavenPublishPlugin
1617
classpath deps.benchmarkGradlePlugin
18+
// Dokka is needed on classpath for vanniktech publish plugin
19+
classpath deps.dokkaPlugin
1720
}
1821
}
1922

2023
allprojects {
21-
apply from: rootProject.file("dependencies.gradle")
24+
apply from: rootProject.file("dependencies.gradle")
2225

23-
repositories {
24-
google()
25-
mavenCentral()
26-
}
26+
repositories {
27+
google()
28+
mavenCentral()
29+
jcenter() // Still needed for some old dependencies
30+
}
2731
}
2832

2933
def getReleaseRepositoryUrl() {
30-
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
31-
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
34+
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
35+
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
3236
}
3337

3438
def getSnapshotRepositoryUrl() {
35-
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
36-
: "https://oss.sonatype.org/content/repositories/snapshots/"
39+
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
40+
: "https://oss.sonatype.org/content/repositories/snapshots/"
3741
}
3842

3943
def isCi() {
40-
project.hasProperty('CI') && CI == 'true'
44+
project.hasProperty('CI') && CI == 'true'
4145
}

deeplinkdispatch-base/src/main/java/com/airbnb/deeplinkdispatch/DeepLinkEntry.kt

+7-8
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ data class DeepLinkMatchResult(val deeplinkEntry: DeepLinkEntry,
3131

3232
override fun toString(): String {
3333
return "uriTemplate: ${deeplinkEntry.uriTemplate} " +
34-
"activity: ${deeplinkEntry.activityClass?.simpleName ?: "not found (name:: ${deeplinkEntry.className})"} " +
34+
"activity: ${deeplinkEntry.activityClass.name} " +
3535
"method: ${deeplinkEntry.method} " +
3636
"parameters: $parameterMap"
3737
}
@@ -90,20 +90,19 @@ data class DeepLinkEntry(
9090
METHOD
9191
}
9292

93-
val activityClass: Class<*>? by lazy {
93+
val activityClass: Class<*> by lazy {
9494
try {
9595
Class.forName(className)
9696
} catch (e: ClassNotFoundException) {
97-
println(
98-
"Deeplink class " + className + " not found. If you are using Proguard/R8/" +
99-
"Dexguard please consult README.md for correct configuration."
97+
throw IllegalStateException(
98+
"Deeplink class $className not found. If you are using Proguard" +
99+
"/R8/Dexguard please consult README.md for correct configuration.",
100+
e
100101
)
101-
return@lazy null
102102
}
103103
}
104104

105105
override fun toString(): String {
106-
return "uriTemplate: $uriTemplate activity: ${activityClass?.simpleName ?:
107-
"not found (name: ${className})"} method: $method"
106+
return "uriTemplate: $uriTemplate activity: ${activityClass.name} method: $method"
108107
}
109108
}

deeplinkdispatch-base/src/main/java/com/airbnb/deeplinkdispatch/ErrorHandler.kt

-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,4 @@ package com.airbnb.deeplinkdispatch
22

33
interface ErrorHandler {
44
fun duplicateMatch(uriString: String, duplicatedMatches: List<DeepLinkMatchResult>)
5-
6-
fun activityClassNotFound(uriString: String, className: String) {
7-
// Empty default implementation
8-
}
95
}

deeplinkdispatch-base/src/test/java/com/airbnb/deeplinkdispatch/DeepLinkMatchTests.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package com.airbnb.deeplinkdispatch
33
import com.airbnb.deeplinkdispatch.base.MatchIndex
44
import junit.framework.TestCase.*
55
import org.junit.Test
6-
import java.lang.IllegalStateException
7-
86

97
private const val ONE_PARAM_SCHEMA = "scheme://host/one/{param}/three"
108
private const val METHOD_NAME = "methodName"
@@ -46,7 +44,7 @@ class DeepLinkMatchTests {
4644
}
4745
}
4846

49-
@Test
47+
@Test(expected = IllegalStateException::class)
5048
fun testMatchArraySerializationDeserializationNonExistantClass() {
5149
val matchByteArray = matchByteArray(UriMatch(ONE_PARAM_SCHEMA, "someNonexistantClass", null))
5250
val entryFromArray = MatchIndex(matchByteArray.toByteArray()).getMatchResultFromIndex(
@@ -58,8 +56,8 @@ class DeepLinkMatchTests {
5856
assertNotNull(entryFromArray)
5957
entryFromArray!!.let {
6058
assertEquals(ONE_PARAM_SCHEMA, it.deeplinkEntry.uriTemplate)
61-
assertEquals("someNonexistantClass", it.deeplinkEntry.className)
6259
assertNull(it.deeplinkEntry.method)
60+
it.deeplinkEntry.activityClass
6361
}
6462
}
6563

deeplinkdispatch-processor/src/main/java/com/airbnb/deeplinkdispatch/DeepLinkProcessor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ public int compare(DeepLinkAnnotatedElement element1, DeepLinkAnnotatedElement e
348348
String uriTemplate = element.getUri();
349349

350350
try {
351-
urisTrie.addToTrie(uriTemplate, activity.canonicalName(), element.getMethod());
351+
urisTrie.addToTrie(uriTemplate, activity.reflectionName(), element.getMethod());
352352
} catch (IllegalArgumentException e) {
353353
error(element.getAnnotatedElement(), e.getMessage());
354354
}

deeplinkdispatch/src/main/java/com/airbnb/deeplinkdispatch/BaseDeepLinkDelegate.java

+49-47
Original file line numberDiff line numberDiff line change
@@ -180,64 +180,58 @@ public DeepLinkResult dispatchFrom(Activity activity, Intent sourceIntent) {
180180
Class<?> c = matchedDeeplinkEntry.getActivityClass();
181181
Intent newIntent = null;
182182
TaskStackBuilder taskStackBuilder = null;
183-
if(c != null) {
184-
if (matchedDeeplinkEntry.getMethod() == null) {
185-
newIntent = new Intent(activity, c);
186-
} else {
187-
Method method;
188-
DeepLinkResult errorResult = new DeepLinkResult(false, uriString,
189-
"Could not deep link to method: " + matchedDeeplinkEntry.getMethod() + " intents length == 0",
190-
deeplinkMatchResult, new DeepLinkMethodResult(null, taskStackBuilder));
191-
try {
192-
method = c.getMethod(matchedDeeplinkEntry.getMethod(), Context.class);
193-
if (method.getReturnType().equals(TaskStackBuilder.class)) {
194-
taskStackBuilder = (TaskStackBuilder) method.invoke(c, activity);
183+
if (matchedDeeplinkEntry.getMethod() == null) {
184+
newIntent = new Intent(activity, c);
185+
} else {
186+
Method method;
187+
DeepLinkResult errorResult = new DeepLinkResult(false, uriString,
188+
"Could not deep link to method: " + matchedDeeplinkEntry.getMethod() + " intents length == 0",
189+
deeplinkMatchResult, new DeepLinkMethodResult(null, taskStackBuilder));
190+
try {
191+
method = c.getMethod(matchedDeeplinkEntry.getMethod(), Context.class);
192+
if (method.getReturnType().equals(TaskStackBuilder.class)) {
193+
taskStackBuilder = (TaskStackBuilder) method.invoke(c, activity);
194+
if (taskStackBuilder.getIntentCount() == 0) {
195+
return errorResult;
196+
}
197+
newIntent = taskStackBuilder.editIntentAt(taskStackBuilder.getIntentCount() - 1);
198+
} else if (method.getReturnType().equals(DeepLinkMethodResult.class)) {
199+
DeepLinkMethodResult methodResult = (DeepLinkMethodResult) method.invoke(c, activity);
200+
if (methodResult.getTaskStackBuilder() != null) {
201+
taskStackBuilder = methodResult.getTaskStackBuilder();
195202
if (taskStackBuilder.getIntentCount() == 0) {
196203
return errorResult;
197204
}
198205
newIntent = taskStackBuilder.editIntentAt(taskStackBuilder.getIntentCount() - 1);
199-
} else if (method.getReturnType().equals(DeepLinkMethodResult.class)) {
200-
DeepLinkMethodResult methodResult = (DeepLinkMethodResult) method.invoke(c, activity);
201-
if (methodResult.getTaskStackBuilder() != null) {
202-
taskStackBuilder = methodResult.getTaskStackBuilder();
203-
if (taskStackBuilder.getIntentCount() == 0) {
204-
return errorResult;
205-
}
206-
newIntent = taskStackBuilder.editIntentAt(taskStackBuilder.getIntentCount() - 1);
207-
} else if (methodResult.getIntent() != null) {
208-
newIntent = methodResult.getIntent();
209-
}
210-
} else {
211-
newIntent = (Intent) method.invoke(c, activity);
206+
} else if (methodResult.getIntent() != null) {
207+
newIntent = methodResult.getIntent();
212208
}
213-
} catch (NoSuchMethodException exception) {
214-
method = c.getMethod(matchedDeeplinkEntry.getMethod(), Context.class, Bundle.class);
215-
if (method.getReturnType().equals(TaskStackBuilder.class)) {
216-
taskStackBuilder = (TaskStackBuilder) method.invoke(c, activity, parameters);
209+
} else {
210+
newIntent = (Intent) method.invoke(c, activity);
211+
}
212+
} catch (NoSuchMethodException exception) {
213+
method = c.getMethod(matchedDeeplinkEntry.getMethod(), Context.class, Bundle.class);
214+
if (method.getReturnType().equals(TaskStackBuilder.class)) {
215+
taskStackBuilder = (TaskStackBuilder) method.invoke(c, activity, parameters);
216+
if (taskStackBuilder.getIntentCount() == 0) {
217+
return errorResult;
218+
}
219+
newIntent = taskStackBuilder.editIntentAt(taskStackBuilder.getIntentCount() - 1);
220+
} else if (method.getReturnType().equals(DeepLinkMethodResult.class)) {
221+
DeepLinkMethodResult methodResult = (DeepLinkMethodResult) method.invoke(c, activity, parameters);
222+
if (methodResult.getTaskStackBuilder() != null) {
223+
taskStackBuilder = methodResult.getTaskStackBuilder();
217224
if (taskStackBuilder.getIntentCount() == 0) {
218225
return errorResult;
219226
}
220227
newIntent = taskStackBuilder.editIntentAt(taskStackBuilder.getIntentCount() - 1);
221-
} else if (method.getReturnType().equals(DeepLinkMethodResult.class)) {
222-
DeepLinkMethodResult methodResult = (DeepLinkMethodResult) method.invoke(c, activity, parameters);
223-
if (methodResult.getTaskStackBuilder() != null) {
224-
taskStackBuilder = methodResult.getTaskStackBuilder();
225-
if (taskStackBuilder.getIntentCount() == 0) {
226-
return errorResult;
227-
}
228-
newIntent = taskStackBuilder.editIntentAt(taskStackBuilder.getIntentCount() - 1);
229-
} else if (methodResult.getIntent() != null) {
230-
newIntent = methodResult.getIntent();
231-
}
232-
} else {
233-
newIntent = (Intent) method.invoke(c, activity, parameters);
228+
} else if (methodResult.getIntent() != null) {
229+
newIntent = methodResult.getIntent();
234230
}
231+
} else {
232+
newIntent = (Intent) method.invoke(c, activity, parameters);
235233
}
236234
}
237-
} else {
238-
if (errorHandler != null) {
239-
errorHandler.activityClassNotFound(uriString, matchedDeeplinkEntry.getClassName());
240-
}
241235
}
242236
if (newIntent == null) {
243237
return new DeepLinkResult(false, uriString, "Destination Intent is null!",
@@ -268,7 +262,15 @@ public DeepLinkResult dispatchFrom(Activity activity, Intent sourceIntent) {
268262
}
269263
}
270264

271-
private DeepLinkMatchResult findEntry(String uriString) {
265+
/**
266+
* Returns a raw match result for the given uriString. This is null if no match is found.
267+
* There is no general use for that during normal operation but it might be useful when writing
268+
* tests.
269+
*
270+
* @param uriString Uri to be matched
271+
* @return An instance of {@link DeepLinkMatchResult} if a match was found or null if not.
272+
*/
273+
public @Nullable DeepLinkMatchResult findEntry(@NonNull String uriString) {
272274
DeepLinkEntry entryRegExpMatch = null;
273275
List<DeepLinkMatchResult> entryIdxMatches = new ArrayList<>();
274276
DeepLinkUri uri = DeepLinkUri.parse(uriString);

deeplinkdispatch/src/test/java/com/airbnb/deeplinkdispatch/BaseDeepLinkDelegateTest.kt

+20-53
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@ import org.mockito.Mockito
1010

1111
@kotlin.ExperimentalUnsignedTypes
1212
class BaseDeepLinkDelegateTest {
13+
14+
@Test
15+
fun testFindEntry() {
16+
val entry = deepLinkEntry("airbnb://foo/{bar}")
17+
val testDelegate = getOneRegistryTestDelegate(listOf(entry), null)
18+
val foundEntry = testDelegate.findEntry("airbnb://foo/1")
19+
assertThat(foundEntry).isNotNull
20+
assertThat(foundEntry?.deeplinkEntry).isEqualTo(entry)
21+
assertThat(foundEntry?.parameterMap).isEqualTo(parameterMap(
22+
"airbnb://foo/1",
23+
mapOf("bar" to "1")
24+
))
25+
assertThat( testDelegate.findEntry("airbnb://bar/1")).isNull()
26+
}
27+
1328
@Test
1429
fun testDispatchNullActivity() {
1530
val entry = deepLinkEntry("airbnb://foo/{bar}")
@@ -146,38 +161,6 @@ class BaseDeepLinkDelegateTest {
146161
assertThat(match).isEqualTo(deeplinkMatchResult)
147162
}
148163

149-
@Test
150-
fun testErrorHandlerWithNonExistingClass() {
151-
val deeplinkUrl = "airbnb://foo/{bar}"
152-
val matchUrl = "airbnb://foo/bar"
153-
val className = "nonExistingClassName"
154-
val entry = deepLinkEntry(
155-
uri = deeplinkUrl,
156-
className = className
157-
)
158-
val deeplinkMatchResult = DeepLinkMatchResult(entry, mapOf(DeepLinkUri.parse(matchUrl) to mapOf("bar" to "bar")))
159-
val uri = Mockito.mock(Uri::class.java)
160-
Mockito.`when`(uri.toString())
161-
.thenReturn(matchUrl)
162-
val intent = Mockito.mock(Intent::class.java)
163-
Mockito.`when`(intent.data)
164-
.thenReturn(uri)
165-
val appContext = Mockito.mock(Context::class.java)
166-
val activity = Mockito.mock(Activity::class.java)
167-
Mockito.`when`(activity.intent)
168-
.thenReturn(intent)
169-
Mockito.`when`(activity.applicationContext)
170-
.thenReturn(appContext)
171-
val errorHandler = ClassNotFoundTestErrorHandler()
172-
val testDelegate = getOneRegistryTestDelegate(listOf(entry), errorHandler)
173-
val (_, _, _, match) = testDelegate.dispatchFrom(activity, intent)
174-
assertThat(errorHandler.duplicateMatchCalled).isFalse()
175-
assertThat(errorHandler.duplicatedMatches).isNull()
176-
assertThat(errorHandler.uriString).isEqualTo(matchUrl)
177-
assertThat(errorHandler.className).isEqualTo(className)
178-
assertThat(match).isEqualTo(deeplinkMatchResult)
179-
}
180-
181164
@Test
182165
fun testErrorHandlerNotGettingCalled() {
183166
val deeplinkUrl1 = "airbnb://foo/{bar}"
@@ -216,6 +199,11 @@ class BaseDeepLinkDelegateTest {
216199
}
217200
}
218201

202+
private fun parameterMap(
203+
url: String,
204+
parameterMap: Map<String, String>
205+
) = mapOf(DeepLinkUri.parse(url) to parameterMap)
206+
219207
private class DuplicatedMatchTestErrorHandler : ErrorHandler {
220208
var className: String = ""
221209
var duplicatedMatches: List<DeepLinkMatchResult>? = null
@@ -229,27 +217,6 @@ class BaseDeepLinkDelegateTest {
229217
}
230218
}
231219

232-
private class ClassNotFoundTestErrorHandler : ErrorHandler {
233-
var className: String = ""
234-
var duplicatedMatches: List<DeepLinkMatchResult>? = null
235-
var duplicateMatchCalled = false
236-
var activityClassNotFound = false
237-
var uriString: String? = null
238-
239-
override fun duplicateMatch(uriString: String, duplicatedMatches: List<DeepLinkMatchResult>) {
240-
this.uriString = uriString
241-
this.duplicatedMatches = duplicatedMatches
242-
duplicateMatchCalled = true
243-
}
244-
245-
override fun activityClassNotFound(uriString: String, className: String) {
246-
this.uriString = uriString
247-
this.className = className
248-
activityClassNotFound = true
249-
}
250-
251-
}
252-
253220
private class TestDeepLinkDelegate(registries: List<BaseRegistry?>?, errorHandler: ErrorHandler?) : BaseDeepLinkDelegate(registries, errorHandler)
254221
companion object {
255222
private fun deepLinkEntry(uri: String, className: String = Any::class.java.name): DeepLinkEntry {

dependencies.gradle

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
def versions = [
2-
kotlinVersion : '1.5.0',
2+
kotlinVersion : '1.5.10',
33
appCompatVersion : '1.2.0',
44
localBroadcastManagerVersion : '1.0.0',
55
roboelectricVersion : '4.5.1',
@@ -8,7 +8,7 @@ def versions = [
88

99
ext.versions = versions
1010
ext.androidConfig = [
11-
agpVersion : '4.2.0',
11+
agpVersion : '4.2.1',
1212
compileSdkVersion : 30,
1313
minSdkVersion : 16,
1414
targetSdkVersion : 30
@@ -27,7 +27,8 @@ ext.deps = [
2727
androidXAnnotations : 'androidx.annotation:annotation:1.2.0',
2828
// Build and upload with:
2929
// ./gradlew clean assemble sourcesJar androidSourcesJar javadocsJar androidJavadocsJar uploadArchives --no-daemon --no-parallel
30-
gradleMavenPublishPlugin : 'com.vanniktech:gradle-maven-publish-plugin:0.12.0',
30+
gradleMavenPublishPlugin : 'com.vanniktech:gradle-maven-publish-plugin:0.14.2',
31+
dokkaPlugin : 'org.jetbrains.dokka:dokka-gradle-plugin:1.4.30',
3132

3233
// Testing
3334
androidxTestCore : 'androidx.test:core:1.3.0',

gradle/wrapper/gradle-wrapper.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.1-all.zip

0 commit comments

Comments
 (0)