WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit ec38aba

Browse files
author
Andreas Rossbacher
committed
A better simple duplication detection.
Fix snapshot publishing.
1 parent 9fd4ead commit ec38aba

File tree

3 files changed

+204
-2
lines changed

3 files changed

+204
-2
lines changed

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

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,84 @@ sealed class DeepLinkEntry(
148148
result
149149
}
150150

151-
fun templatesMatchesSameUrls(other: DeepLinkEntry) = uriTemplateWithoutPlaceholders == other.uriTemplateWithoutPlaceholders
151+
/**
152+
* Determines if two URI templates could potentially match the same URLs.
153+
*
154+
* This method checks:
155+
* 1. For placeholders with allowed values (e.g., {type(a|b)}): expands all possible values
156+
* and checks if any could create a match with the other template
157+
* 2. For regular placeholders (without allowed values): uses regex matching since
158+
* "..*" (the placeholder replacement) can match any non-empty string
159+
*
160+
* Note: Configurable path segments (<name>) are ignored as they have the same replacement
161+
* value within the same app.
162+
*
163+
* @return true if the templates could potentially match the same URLs
164+
*/
165+
fun templatesMatchesSameUrls(other: DeepLinkEntry): Boolean {
166+
// Get all possible expanded values for both templates
167+
// This expands placeholders with allowed values to all their possible concrete forms
168+
// Regular placeholders (without allowed values) are replaced with "..*"
169+
val thisExpanded = uriTemplate.allPossibleValues()
170+
val otherExpanded = other.uriTemplate.allPossibleValues()
171+
172+
// Check if any pair of expanded values could match
173+
for (thisValue in thisExpanded) {
174+
for (otherValue in otherExpanded) {
175+
if (expandedTemplatesCouldMatch(thisValue, otherValue)) {
176+
return true
177+
}
178+
}
179+
}
180+
181+
return false
182+
}
183+
184+
/**
185+
* Checks if two expanded template values could match the same URL.
186+
*
187+
* After expansion by allPossibleValues():
188+
* - Placeholders with allowed values have been replaced with each allowed value
189+
* - Regular placeholders have been replaced with "..*" (matches one or more chars)
190+
*
191+
* We use "..*" as a regex pattern - it already means "any char followed by zero or more chars"
192+
* which effectively matches one or more characters.
193+
*/
194+
private fun expandedTemplatesCouldMatch(template1: String, template2: String): Boolean {
195+
// If identical (including both having same wildcards), they match
196+
if (template1 == template2) {
197+
return true
198+
}
199+
200+
// Convert template1 to regex (escape special chars, keep ..*) and check if template2 matches
201+
if (templateMatchesAsRegex(template1, template2)) {
202+
return true
203+
}
204+
205+
// Also check reverse - template2 as regex against template1
206+
if (templateMatchesAsRegex(template2, template1)) {
207+
return true
208+
}
209+
210+
return false
211+
}
212+
213+
/**
214+
* Converts a template with "..*" wildcards to a regex and checks if the other template matches.
215+
*/
216+
private fun templateMatchesAsRegex(templateWithWildcards: String, templateToMatch: String): Boolean {
217+
// If no wildcards, simple equality (already checked in caller)
218+
if (!templateWithWildcards.contains(SIMPLE_GLOB_PATTERN_MIN_ONE_CHAR)) {
219+
return templateWithWildcards == templateToMatch
220+
}
221+
222+
// Build regex: escape everything except "..*" which becomes ".+" (one or more chars)
223+
val regexPattern = templateWithWildcards
224+
.split(SIMPLE_GLOB_PATTERN_MIN_ONE_CHAR)
225+
.joinToString(".+") { Regex.escape(it) }
226+
227+
return Regex("^$regexPattern$").matches(templateToMatch)
228+
}
152229

153230
/**
154231
* Compares two DeepLinkEntry instances by their concreteness (specificity).

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,128 @@ class DeepLinkEntryTest {
492492
assertThat(placeholderFirstEntry.compareTo(configurableFirstEntry)).isGreaterThan(0)
493493
}
494494

495+
// ============== templatesMatchesSameUrls tests ==============
496+
497+
@Test
498+
fun `templatesMatchesSameUrls returns true for identical templates`() {
499+
val entry1 = activityDeepLinkEntry("airbnb://host/path")
500+
val entry2 = activityDeepLinkEntry("airbnb://host/path")
501+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isTrue
502+
}
503+
504+
@Test
505+
fun `templatesMatchesSameUrls returns false for completely different templates`() {
506+
val entry1 = activityDeepLinkEntry("airbnb://host/path1")
507+
val entry2 = activityDeepLinkEntry("airbnb://host/path2")
508+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isFalse
509+
}
510+
511+
@Test
512+
fun `templatesMatchesSameUrls returns true for templates with same placeholders`() {
513+
val entry1 = activityDeepLinkEntry("airbnb://host/{id}")
514+
val entry2 = activityDeepLinkEntry("airbnb://host/{param}")
515+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isTrue
516+
}
517+
518+
@Test
519+
fun `templatesMatchesSameUrls returns true when placeholder matches concrete value`() {
520+
// A placeholder can match any value, so it could match "path"
521+
val entryWithPlaceholder = activityDeepLinkEntry("airbnb://host/{id}")
522+
val entryWithConcrete = activityDeepLinkEntry("airbnb://host/path")
523+
assertThat(entryWithPlaceholder.templatesMatchesSameUrls(entryWithConcrete)).isTrue
524+
assertThat(entryWithConcrete.templatesMatchesSameUrls(entryWithPlaceholder)).isTrue
525+
}
526+
527+
@Test
528+
fun `templatesMatchesSameUrls returns true when allowed value matches concrete value`() {
529+
// Placeholder with allowed values (a|b) - "a" matches concrete "a"
530+
val entryWithAllowedValues = activityDeepLinkEntry("airbnb://host/{type(a|b)}")
531+
val entryWithConcrete = activityDeepLinkEntry("airbnb://host/a")
532+
assertThat(entryWithAllowedValues.templatesMatchesSameUrls(entryWithConcrete)).isTrue
533+
assertThat(entryWithConcrete.templatesMatchesSameUrls(entryWithAllowedValues)).isTrue
534+
}
535+
536+
@Test
537+
fun `templatesMatchesSameUrls returns false when no allowed value matches concrete value`() {
538+
// Placeholder with allowed values (a|b) - neither matches concrete "c"
539+
val entryWithAllowedValues = activityDeepLinkEntry("airbnb://host/{type(a|b)}")
540+
val entryWithConcrete = activityDeepLinkEntry("airbnb://host/c")
541+
assertThat(entryWithAllowedValues.templatesMatchesSameUrls(entryWithConcrete)).isFalse
542+
assertThat(entryWithConcrete.templatesMatchesSameUrls(entryWithAllowedValues)).isFalse
543+
}
544+
545+
@Test
546+
fun `templatesMatchesSameUrls returns true when allowed values overlap`() {
547+
// Both have allowed values with "b" in common
548+
val entry1 = activityDeepLinkEntry("airbnb://host/{type1(a|b)}")
549+
val entry2 = activityDeepLinkEntry("airbnb://host/{type2(b|c)}")
550+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isTrue
551+
}
552+
553+
@Test
554+
fun `templatesMatchesSameUrls returns false when allowed values dont overlap`() {
555+
// No common allowed values
556+
val entry1 = activityDeepLinkEntry("airbnb://host/{type1(a|b)}")
557+
val entry2 = activityDeepLinkEntry("airbnb://host/{type2(c|d)}")
558+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isFalse
559+
}
560+
561+
@Test
562+
fun `templatesMatchesSameUrls returns true when placeholder could match allowed value`() {
563+
// Regular placeholder can match any value including "a" or "b"
564+
val entryWithPlaceholder = activityDeepLinkEntry("airbnb://host/{id}")
565+
val entryWithAllowedValues = activityDeepLinkEntry("airbnb://host/{type(a|b)}")
566+
assertThat(entryWithPlaceholder.templatesMatchesSameUrls(entryWithAllowedValues)).isTrue
567+
assertThat(entryWithAllowedValues.templatesMatchesSameUrls(entryWithPlaceholder)).isTrue
568+
}
569+
570+
@Test
571+
fun `templatesMatchesSameUrls handles partial placeholders with same prefix`() {
572+
// Both have "pre" prefix, placeholder can match
573+
val entry1 = activityDeepLinkEntry("airbnb://host/pre{id}post")
574+
val entry2 = activityDeepLinkEntry("airbnb://host/pre{name}post")
575+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isTrue
576+
}
577+
578+
@Test
579+
fun `templatesMatchesSameUrls returns false for partial placeholders with different prefix`() {
580+
val entry1 = activityDeepLinkEntry("airbnb://host/pre{id}post")
581+
val entry2 = activityDeepLinkEntry("airbnb://host/other{name}post")
582+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isFalse
583+
}
584+
585+
@Test
586+
fun `templatesMatchesSameUrls returns false for different number of path segments`() {
587+
val entry1 = activityDeepLinkEntry("airbnb://host/path1/path2")
588+
val entry2 = activityDeepLinkEntry("airbnb://host/path1")
589+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isFalse
590+
}
591+
592+
@Test
593+
fun `templatesMatchesSameUrls handles complex scheme placeholders`() {
594+
// http{s} scheme placeholder - "http" matches "http", "https" matches "https"
595+
val entry1 = activityDeepLinkEntry("http{scheme(|s)}://host/path")
596+
val entry2 = activityDeepLinkEntry("http://host/path")
597+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isTrue
598+
599+
val entry3 = activityDeepLinkEntry("https://host/path")
600+
assertThat(entry1.templatesMatchesSameUrls(entry3)).isTrue
601+
}
602+
603+
@Test
604+
fun `templatesMatchesSameUrls handles host placeholders with allowed values`() {
605+
// Host with allowed prefixes
606+
val entry1 = activityDeepLinkEntry("http://{prefix(|www.)}airbnb.com/path")
607+
val entry2 = activityDeepLinkEntry("http://airbnb.com/path")
608+
assertThat(entry1.templatesMatchesSameUrls(entry2)).isTrue
609+
610+
val entry3 = activityDeepLinkEntry("http://www.airbnb.com/path")
611+
assertThat(entry1.templatesMatchesSameUrls(entry3)).isTrue
612+
613+
val entry4 = activityDeepLinkEntry("http://m.airbnb.com/path")
614+
assertThat(entry1.templatesMatchesSameUrls(entry4)).isFalse
615+
}
616+
495617
companion object {
496618
private fun activityDeepLinkEntry(
497619
uriTemplate: String,

publishing.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ publishing {
99
maven {
1010
// The "name" value creates a task like `publishAllPublicationsTo[Name]Repository
1111
// In this case, publishAllPublicationsToAirbnbArtifactoryRepository
12+
def versionName = findProperty("VERSION_NAME") ?: version.toString()
1213
name = 'airbnbArtifactory'
13-
url = version.toString().endsWith("SNAPSHOT") ? findProperty("ARTIFACTORY_SNAPSHOT_URL") : findProperty("ARTIFACTORY_RELEASE_URL")
14+
// Use VERSION_NAME directly since 'version' may not be set at configuration time
15+
// with newer versions of gradle-maven-publish-plugin
16+
url = versionName.endsWith("SNAPSHOT") ? findProperty("ARTIFACTORY_SNAPSHOT_URL") : findProperty("ARTIFACTORY_RELEASE_URL")
1417
credentials {
1518
username = findProperty("ARTIFACTORY_USERNAME")
1619
password = findProperty("ARTIFACTORY_PASSWORD")

0 commit comments

Comments
 (0)