Skip to content
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
7 changes: 5 additions & 2 deletions deeplinkdispatch-processor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ dependencies {

testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:1.7.0'
testCompile 'com.google.android:android:4.1.1.4'
testCompile 'com.google.testing.compile:compile-testing:0.6'
testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
}

modifyPom {
Expand All @@ -24,8 +27,8 @@ modifyPom {

scm {
url PROJECT_URL
connection 'scm:https://git.musta.ch/airbnb/DeepLinkDispatch.git'
developerConnection 'scm:git@git.musta.ch:airbnb/DeepLinkDispatch.git'
connection 'scm:https://gitub.com/airbnb/DeepLinkDispatch.git'
developerConnection 'scm:git@gitub.com:airbnb/DeepLinkDispatch.git'
}

licenses {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.airbnb.deeplinkdispatch.internal;

import com.airbnb.deeplinkdispatch.DeepLinkEntry;
package com.airbnb.deeplinkdispatch;

import javax.lang.model.element.Element;

public class DeepLinkAnnotatedElement {
final class DeepLinkAnnotatedElement {

private final String host;
private final String path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.airbnb.deeplinkdispatch.internal;
package com.airbnb.deeplinkdispatch;

import com.google.auto.service.AutoService;

import com.airbnb.deeplinkdispatch.DeepLink;
import com.airbnb.deeplinkdispatch.DeepLinkEntry;
import com.airbnb.deeplinkdispatch.DeepLinkRegistry;
import com.airbnb.deeplinkdispatch.DeepLinks;
import com.airbnb.deeplinkdispatch.Loader;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
Expand Down Expand Up @@ -56,22 +50,19 @@ public class DeepLinkProcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
@Override public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}

@Override
public Set<String> getSupportedAnnotationTypes() {
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(DeepLink.class.getCanonicalName());
return annotations;
}

@Override
public SourceVersion getSupportedSourceVersion() {
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

Expand All @@ -88,7 +79,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment

String[] deepLinks = element.getAnnotation(DeepLinks.class).value();
DeepLinkEntry.Type type = kind == ElementKind.CLASS
? DeepLinkEntry.Type.CLASS : DeepLinkEntry.Type.METHOD;
? DeepLinkEntry.Type.CLASS : DeepLinkEntry.Type.METHOD;
for (String deepLink : deepLinks) {
deepLinkElements.add(new DeepLinkAnnotatedElement(deepLink, element, type));
}
Expand All @@ -103,7 +94,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment

DeepLink deepLink = element.getAnnotation(DeepLink.class);
DeepLinkEntry.Type type = kind == ElementKind.CLASS
? DeepLinkEntry.Type.CLASS : DeepLinkEntry.Type.METHOD;
? DeepLinkEntry.Type.CLASS : DeepLinkEntry.Type.METHOD;
deepLinkElements.add(new DeepLinkAnnotatedElement(deepLink.value(), element, type));
}

Expand Down Expand Up @@ -134,11 +125,11 @@ private void generateRegistry(List<DeepLinkAnnotatedElement> elements) throws IO

for (DeepLinkAnnotatedElement element : elements) {
String hostPath = element.getPath().equals("")
? element.getHost() : element.getHost() + "/" + element.getPath();
? element.getHost() : element.getHost() + "/" + element.getPath();
String type = "DeepLinkEntry.Type." + element.getAnnotationType().toString();
String activity = element.getActivity();
ClassName activity = ClassName.bestGuess(element.getActivity());
Object method = element.getMethod() == null ? null : element.getMethod();
loadMethod.addStatement("registry.registerDeepLink($S, $L, $S, $S)",
loadMethod.addStatement("registry.registerDeepLink($S, $L, $T.class, $S)",
hostPath, type, activity, method);
}

Expand Down Expand Up @@ -197,7 +188,7 @@ private void generateDeepLinkActivity() throws IOException {
.addStatement("parameterMap.put(queryParameter, uri.getQueryParameter(queryParameter))")
.endControlFlow()
.beginControlFlow("try")
.addStatement("Class<?> c = Class.forName(entry.getActivity())")
.addStatement("Class<?> c = entry.getActivityClass()")
.addStatement("$T intent", ClassName.get("android.content", "Intent"))
.beginControlFlow("if (entry.getType() == DeepLinkEntry.Type.CLASS)")
.addStatement("intent = new Intent(this, c)")
Expand Down Expand Up @@ -226,9 +217,6 @@ private void generateDeepLinkActivity() throws IOException {
.addStatement("intent.putExtra(DeepLink.IS_DEEP_LINK, true)")
.addStatement("startActivity(intent)")
.addStatement("notifyListener(false, uri, null)")
.nextControlFlow("catch (ClassNotFoundException exception)")
.addStatement(
"notifyListener(true, uri, \"Deep link to non-existent class: \" + entry.getActivity())")
.nextControlFlow("catch (NoSuchMethodException exception)")
.addStatement(
"notifyListener(true, uri, \"Deep link to non-existent method: \" + entry.getMethod())")
Expand All @@ -242,8 +230,8 @@ private void generateDeepLinkActivity() throws IOException {
.addStatement("finish()")
.endControlFlow()
.nextControlFlow("else")
.addStatement(
"notifyListener(true, uri, \"No registered entity to handle deep link: \" + uri.toString())")
.addStatement("notifyListener(true, uri, \"No registered entity to handle deep link: \"" +
" + uri.toString())")
.addStatement("finish()")
.endControlFlow()
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.airbnb.deeplinkdispatch;

import com.google.testing.compile.JavaFileObjects;

import org.junit.Test;

import javax.tools.JavaFileObject;

import static com.google.common.truth.Truth.assert_;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;

public class DeepLinkProcessorTest {
@Test public void testProcessor() {
JavaFileObject sampleActivity = JavaFileObjects
.forSourceString("SampleActivity", "package com.example;" +
"import com.airbnb.deeplinkdispatch.DeepLink; " +
"@DeepLink(\"example.com/deepLink\") public class SampleActivity {}");

assert_().about(javaSource())
.that(sampleActivity)
.processedWith(new DeepLinkProcessor())
.compilesWithoutError()
.and()
.generatesSources(
JavaFileObjects.forResource("DeepLinkActivity.java"),
JavaFileObjects.forSourceString("DeepLinkLoader",
"package com.airbnb.deeplinkdispatch;\n" +
"import com.example.SampleActivity;\n" +
"public final class DeepLinkLoader implements Loader {\n" +
" public void load(DeepLinkRegistry registry) {\n" +
" registry.registerDeepLink(\"example.com/deepLink\", DeepLinkEntry.Type.CLASS, SampleActivity.class, null);\n" +
" }\n" +
"}\n"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.airbnb.deeplinkdispatch;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import java.lang.Override;
import java.lang.String;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class DeepLinkActivity extends Activity {
private static final String TAG = DeepLinkActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Loader loader = new com.airbnb.deeplinkdispatch.DeepLinkLoader();
DeepLinkRegistry registry = new DeepLinkRegistry(loader);
Uri uri = getIntent().getData();
String hostPath = uri.getHost() + uri.getPath();
DeepLinkEntry entry = registry.parseUri(hostPath);
if (entry != null) {
Map<String, String> parameterMap = entry.getParameters(hostPath);
for (String queryParameter : uri.getQueryParameterNames()) {
if (parameterMap.containsKey(queryParameter)) {
Log.w(TAG, "Duplicate parameter name in path and query param: " + queryParameter);
}
parameterMap.put(queryParameter, uri.getQueryParameter(queryParameter));
}
try {
Class<?> c = entry.getActivityClass();
Intent intent;
if (entry.getType() == DeepLinkEntry.Type.CLASS) {
intent = new Intent(this, c);
} else {
Method method = c.getMethod(entry.getMethod(), Context.class);
intent = (Intent) method.invoke(c, this);
}
if (intent.getAction() == null) {
intent.setAction(getIntent().getAction());
}
if (intent.getData() == null) {
intent.setData(getIntent().getData());
}
Bundle parameters;
if (getIntent().getExtras() != null) {
parameters = new Bundle(getIntent().getExtras());
} else {
parameters = new Bundle();
}
for (Map.Entry<String, String> parameterEntry : parameterMap.entrySet()) {
parameters.putString(parameterEntry.getKey(), parameterEntry.getValue());
}
intent.putExtras(parameters);
intent.putExtra(DeepLink.IS_DEEP_LINK, true);
startActivity(intent);
notifyListener(false, uri, null);
} catch (NoSuchMethodException exception) {
notifyListener(true, uri, "Deep link to non-existent method: " + entry.getMethod());
} catch (IllegalAccessException exception) {
notifyListener(true, uri, "Could not deep link to method: " + entry.getMethod());
} catch(InvocationTargetException exception) {
notifyListener(true, uri, "Could not deep link to method: " + entry.getMethod());
} finally {
finish();
}
} else {
notifyListener(true, uri, "No registered entity to handle deep link: " + uri.toString());
finish();
}
}

private void notifyListener(boolean isError, Uri uri, String errorMessage) {
if (getApplication() instanceof DeepLinkCallback) {
DeepLinkCallback listener = (DeepLinkCallback) getApplication();
if (!isError) {
listener.onSuccess(uri.toString());
} else {
listener.onError(new DeepLinkError(uri.toString(), errorMessage));
}
}
}
}
4 changes: 2 additions & 2 deletions deeplinkdispatch/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ modifyPom {

scm {
url PROJECT_URL
connection 'scm:https://git.musta.ch/airbnb/DeepLinkDispatch.git'
developerConnection 'scm:git@git.musta.ch:airbnb/DeepLinkDispatch.git'
connection 'scm:https://gitub.com/airbnb/DeepLinkDispatch.git'
developerConnection 'scm:git@gitub.com:airbnb/DeepLinkDispatch.git'
}

licenses {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DeepLinkEntry {
final class DeepLinkEntry {

private static final String PARAM_VALUE = "([a-zA-Z0-9]*)";
private static final String PARAM = "([a-zA-Z][a-zA-Z0-9_-]*)";
Expand All @@ -30,19 +30,19 @@ public class DeepLinkEntry {
private final String hostPath;
private final String regex;
private final Type type;
private final String activity;
private final Class<?> activityClass;
private final String method;

public enum Type {
CLASS,
METHOD
}

public DeepLinkEntry(String hostPath, Type type, String activity, String method) {
public DeepLinkEntry(String hostPath, Type type, Class<?> activityClass, String method) {
this.hostPath = hostPath;
this.regex = generateLookupString(hostPath);
this.type = type;
this.activity = activity;
this.activityClass = activityClass;
this.method = method;
}

Expand All @@ -54,8 +54,8 @@ public Type getType() {
return type;
}

public String getActivity() {
return activity;
public Class<?> getActivityClass() {
return activityClass;
}

public String getMethod() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DeepLinkRegistry {
final class DeepLinkRegistry {

private List<DeepLinkEntry> registry = new LinkedList<>();
private final List<DeepLinkEntry> registry = new LinkedList<>();

public DeepLinkRegistry(Loader loader) {
if (loader != null) {
Expand All @@ -33,14 +33,14 @@ public DeepLinkRegistry(Loader loader) {
/**
* Registers the deep link that DeepLinkActivity will handle, along with where to delegate
*
* @param hostPath the combined host and path of the deep link
* @param type whether its a class level annotation or method level
* @param activity the activity to delegate the deep link to
* @param method the method used to generate the <code>Intent</code>
* @param hostPath the combined host and path of the deep link
* @param type whether its a class level annotation or method level
* @param activityClass the activity class to delegate the deep link to
* @param method the method used to generate the <code>Intent</code>
*/
public void registerDeepLink(String hostPath, DeepLinkEntry.Type type, String activity,
public void registerDeepLink(String hostPath, DeepLinkEntry.Type type, Class<?> activityClass,
String method) {
registry.add(new DeepLinkEntry(hostPath, type, activity, method));
registry.add(new DeepLinkEntry(hostPath, type, activityClass, method));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
*/
package com.airbnb.deeplinkdispatch;

public interface Loader {
interface Loader {
void load(DeepLinkRegistry registry);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.airbnb.deeplinkdispatch;

import org.junit.Test;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

public class DeepLinkEntryTest {
@Test public void testSingleParam() {
DeepLinkEntry entry = new DeepLinkEntry(
"foo/{bar}", DeepLinkEntry.Type.CLASS, String.class, null);

Map<String, String> parameters = entry.getParameters("foo/myData");

assertThat(parameters.get("bar")).isEqualTo("myData");
}

@Test public void testTwoParams() {
DeepLinkEntry entry = new DeepLinkEntry(
"test/{param1}/{param2}", DeepLinkEntry.Type.CLASS, String.class, null);

Map<String, String> parameters = entry.getParameters("test/12345/alice");
assertThat(parameters.get("param1")).isEqualTo("12345");
assertThat(parameters.get("param2")).isEqualTo("alice");
}
}
Loading