/*
* Copyright 2015. Hannes Dorfmann.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.example.util.mvp.base;
import android.support.annotation.UiThread;
import com.example.util.mvp.noop.NoOp;
import com.hannesdorfmann.mosby.mvp.MvpPresenter;
import com.hannesdorfmann.mosby.mvp.MvpView;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* A {@link MvpPresenter} implementation that implements the Null Object Pattern' for the attached mvp view. So whenever the view gets
* detached from this presenter by calling{@link #detachView(boolean)}, a new "null object" view gets dynamically instantiated by using
* reflections and set as the current view (instead of null). The new "null object" view simply does nothing. This avoids
* NullPointerExceptions and checks like {@code if (getView() != null)}.
*
* Please note that when creating the "null object" the first generic parameter (left depth-first search) that will be found that is
* subtype of {@link MvpView} will be used as the type of the view. So avoid having multiple generic parameters for "View" like this
* {@code MyPresenter<FooMvpView, OtherMvpView>} because we can't know whether FooMvpView or OtherMvpView is the real type of this
* presenter's view. In that case (left depth-first search) FooMvpView will be used (may cause ClassCastException if OtherMvpView was the
* desired one).
*
* @param <V> The type of the {@link MvpView}
*
* @author Jens Dirller, Hannes Dorfmann, taken from mosby project https://github.com/sockeqwe/mosby
* @see com.hannesdorfmann.mosby.mvp.MvpBasePresenter
* @since 1.2.0
*/
public abstract class MvpNullObjectBasePresenter<V extends MvpView> implements MvpPresenter<V> {
@Nullable private WeakReference<V> view;
private final V nullView;
@SuppressWarnings("checkstyle:illegalcatch")
protected MvpNullObjectBasePresenter() {
try {
// Scan the inheritance hierarchy until we reached MvpNullObjectBasePresenter
@Nullable 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 (Type type : types) {
Class<?> genericType = (Class<?>) type;
if (genericType.isInterface() && isSubTypeOfMvpView(genericType)) {
viewClass = (Class<V>) genericType;
break;
}
}
// Continue with next class in inheritance hierarchy (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);
}
}
/**
* Scans the interface inheritance hierarchy and checks if on the root is MvpView.class.
*
* @param klass The leaf interface where to begin to scan
*
* @return true if subtype of MvpView, otherwise false
*/
private boolean isSubTypeOfMvpView(Class<?> klass) {
if (klass.equals(MvpView.class)) {
return true;
}
Class[] superInterfaces = klass.getInterfaces();
for (int i = 0; i < superInterfaces.length; i++) {
if (isSubTypeOfMvpView(superInterfaces[0])) {
return true;
}
}
return false;
}
@Override
@UiThread
public void attachView(V view) {
this.view = new WeakReference<>(view);
}
@UiThread
protected V getView() {
if (view != null) {
@Nullable V realView = view.get();
if (realView != null) {
return realView;
}
}
return nullView;
}
@Override
@UiThread
public void detachView(boolean retainInstance) {
if (view != null) {
view.clear();
view = null;
}
}
}