Skip to content

Commit

Permalink
MvpNullObjectBaePresenter: Scanning hierarchy by reflections works now
Browse files Browse the repository at this point in the history
  • Loading branch information
sockeqwe committed Mar 25, 2016
1 parent 04a6496 commit 66d4ee9
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hannesdorfmann.mosby.mvp;

import android.support.annotation.NonNull;
import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

Expand All @@ -17,29 +18,86 @@
* @see MvpBasePresenter
* @since 1.2.0
*/
public class MvpNullObjectBasePresenter<V extends MvpView> implements MvpPresenter<V> {
public abstract class MvpNullObjectBasePresenter<V extends MvpView> implements MvpPresenter<V> {

private V view;
private WeakReference<V> view;
private final V nullView;

public MvpNullObjectBasePresenter() {
try {

// Scan the inheritance hierarchy until we reached MvpNullObjectBasePresenter
Class<V> viewClass = null;
Class<?> currentClass = getClass();

while (viewClass == null) {

Type genericSuperType = currentClass.getGenericSuperclass();

while (!(genericSuperType instanceof ParameterizedType)) {
// Scan inheritance tree until we find ParameterizedType which is probably a MvpSubclass
currentClass = currentClass.getSuperclass();
genericSuperType = currentClass.getGenericSuperclass();
}

Type[] types = ((ParameterizedType) genericSuperType).getActualTypeArguments();

for (int i = 0; i < types.length; i++) {
Class<?> genericType = (Class<?>) types[i];
if (genericType.isInterface() && isSubTypeOfMvpView(genericType)) {
viewClass = (Class<V>) genericType;
break;
}
}

// Continue with next class in inheritance hierachy (see genericSuperType assignment at start of while loop)
currentClass = currentClass.getSuperclass();
}

nullView = NoOp.of(viewClass);
} catch (Throwable t) {
throw new IllegalArgumentException(
"The generic type <V extends MvpView> must be the first generic type argument of class "
+ getClass().getSimpleName()
+ " (per convention). Otherwise we can't determine which type of View this"
+ " Presenter coordinates.", t);
}
}

private boolean isSubTypeOfMvpView(Class<?> klass) {
do {
if (klass.equals(MvpView.class)) {
return true;
}
Class[] superInterfaces = klass.getInterfaces();
for (int i = 0; i < superInterfaces.length; i++) {
if (isSubTypeOfMvpView(superInterfaces[0])) {
klass = superInterfaces[0];
}
}
} while (klass != null);
return false;
}

@Override public void attachView(V view) {
this.view = view;
this.view = new WeakReference<V>(view);
}

@NonNull public V getView() {
if (view == null) {
throw new NullPointerException("MvpView reference is null. Have you called attachView()?");
@NonNull protected V getView() {
if (view != null) {
V realView = view.get();
if (realView != null) {
return realView;
}
}
return view;

return nullView;
}

@Override public void detachView(boolean retainInstance) {
if (view != null) {

Type[] types =
((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();

Class<V> viewClass = (Class<V>) types[0];
view = NoOp.of(viewClass);
view.clear();
view = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,49 @@ public void viewShowThat() {
}
}

@Test public void testAttachDetach() {
public static class CorrectGenericsOrderPresenter<M, I>
extends MvpNullObjectBasePresenter<TestView> {
}

TestNullObjectPresenter presenter = new TestNullObjectPresenter();
public static class ParameterlessConstructor<M> extends TestNullObjectPresenter {
}

try {
// NullPointer exception should be thrown
presenter.getView();
Assert.fail("Nullpointer Exception should be thrown but haven't");
} catch (NullPointerException e) {
// Expected exception
}
public static class SubclassConstructor extends ParameterlessConstructor<TestData> {
}

@Test public void testConstructorWorngGenericsOrder() {
// no exception should be thrown
CorrectGenericsOrderPresenter presenter =
new CorrectGenericsOrderPresenter<TestData, FooInterface>();
pickingCorrectViewInterface(presenter);
testAttachDetach(presenter);
}

@Test public void testConstructorGenericParameterless() {
// no exception should be thrown
ParameterlessConstructor<TestData> presenter = new ParameterlessConstructor<TestData>();
pickingCorrectViewInterface(presenter);
testAttachDetach(presenter);
}

@Test public void testConstructorDirectlyBaseClass() {
// no exception should be thrown
MvpNullObjectBasePresenter presenter = new MvpNullObjectBasePresenter<TestView>() {
};
pickingCorrectViewInterface(presenter);
testAttachDetach(presenter);
}

@Test public void testConstructorSubClass() {
// no exception should be thrown
SubclassConstructor presenter = new SubclassConstructor();
pickingCorrectViewInterface(presenter);
testAttachDetach(presenter);
}

private void testAttachDetach(MvpNullObjectBasePresenter<TestView> presenter) {

Assert.assertNotNull(presenter.getView());

TestView view = new TestView() {
@Override public void showFoo(TestData data) {
Expand Down Expand Up @@ -134,10 +166,9 @@ public void viewShowThat() {
Assert.assertTrue(presenter.getView() != view); // Null Object view
}

@Test public void pickingCorrectViewInterface() {
private void pickingCorrectViewInterface(MvpNullObjectBasePresenter<TestView> presenter) {

ViewWithMulitpleInterfaces view = new ViewWithMulitpleInterfaces();
TestNullObjectPresenter presenter = new TestNullObjectPresenter();

presenter.attachView(view);
Assert.assertNotNull(presenter.getView());
Expand All @@ -147,7 +178,7 @@ public void viewShowThat() {
Assert.assertNotNull(presenter.getView());
Assert.assertFalse(presenter.getView() == view);

// Invoke methods on proxy
// Invoke methods on null object
presenter.getView().showFoo(new TestData());
presenter.getView().showThat();
}
Expand Down

0 comments on commit 66d4ee9

Please sign in to comment.