/**
* Wire
* Copyright (C) 2016 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.waz.lintrules.issues;
import com.android.SdkConstants;
import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import lombok.ast.ClassDeclaration;
import lombok.ast.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BaseClassDetector extends Detector implements Detector.JavaScanner {
public static final Issue ISSUE_ACTIVITY = Issue.create(
"com.waz.BaseActivity",
"Every Activity should extend `com.waz.zclient.BaseActivity`",
"To ensure that our dependency framework is working correctly, every " +
"new activity needs to extend a BaseActivity.",
Category.CORRECTNESS,
7,
Severity.WARNING,
new Implementation(BaseClassDetector.class, Scope.JAVA_FILE_SCOPE));
public static final Issue ISSUE_FRAGMENT = Issue.create(
"com.waz.BaseFragment",
"Every Fragment should extend `com.waz.zclient.pages.BaseFragment`",
"To ensure that our dependency framework is working correctly, every " +
"new fragment needs to extend a BaseFragment.",
Category.CORRECTNESS,
7,
Severity.WARNING,
new Implementation(BaseClassDetector.class, Scope.JAVA_FILE_SCOPE));
public static final Issue ISSUE_DIALOG_FRAGMENT = Issue.create(
"com.waz.BaseDialogFragment",
"Every DialogFragment should extend `com.waz.zclient.pages.BaseDialogFragment`",
"To ensure that our dependency framework is working correctly, every " +
"new fragment needs to extend a BaseFragment.",
Category.CORRECTNESS,
7,
Severity.WARNING,
new Implementation(BaseClassDetector.class, Scope.JAVA_FILE_SCOPE));
public static final Issue ISSUE_PREFERENCE_FRAGMENT = Issue.create(
"com.waz.BasePreferenceFragment",
"Every PreferenceFragment should extend `com.waz.zclient.pages.BasePreferenceFragment`",
"To ensure that our dependency framework is working correctly, every " +
"new fragment needs to extend a BaseFragment.",
Category.CORRECTNESS,
7,
Severity.WARNING,
new Implementation(BaseClassDetector.class, Scope.JAVA_FILE_SCOPE));
private static final String[] BASE_CLASSES = new String[] {
"com.waz.zclient.pages.BaseDialogFragment",
"com.waz.zclient.pages.BaseFragment",
"com.waz.zclient.pages.BasePreferenceFragment",
"com.waz.zclient.BaseActivity",
"com.waz.zclient.BasePreferenceActivity"
};
private static final String CLASS_ACTIVITY = SdkConstants.CLASS_ACTIVITY;
private static final String CLASS_FRAGMENT = SdkConstants.CLASS_FRAGMENT;
private static final String CLASS_FRAGMENT_V4 = SdkConstants.CLASS_V4_FRAGMENT;
private static final String CLASS_DIALOG_FRAGMENT = "android.app.DialogFragment";
private static final String CLASS_DIALOG_FRAGMENT_V4 = "android.support.v4.app.DialogFragment";
private static final String CLASS_PREFERENCE_FRAGMENT = "android.preference.PreferenceFragment";
private static final String CONTAINER_CLASS_NAME = "com.waz.annotations.Container";
private final List<String> reportedIssues = new ArrayList<>();
public BaseClassDetector() {
}
@Override
public Speed getSpeed() {
return Speed.FAST;
}
@Override
public List<String> applicableSuperClasses() {
return Arrays.asList(CLASS_ACTIVITY,
CLASS_FRAGMENT,
CLASS_FRAGMENT_V4,
CLASS_DIALOG_FRAGMENT,
CLASS_DIALOG_FRAGMENT_V4,
CLASS_PREFERENCE_FRAGMENT);
}
@Override
public void checkClass(JavaContext context,
ClassDeclaration declaration,
Node node,
JavaParser.ResolvedClass resolvedClass) {
if (declaration == null) {
return;
}
if (isGeneratedBaseClass(resolvedClass)) {
return;
}
if (isBaseClass(resolvedClass)) {
return;
}
JavaParser.ResolvedClass superClass = resolvedClass.getSuperClass();
while (superClass != null &&
!reportedIssues.contains(resolvedClass.getSimpleName()) &&
!isBaseClass(superClass) &&
!isGeneratedBaseClass(superClass)) {
if (superClass.getPackageName().startsWith("android")) {
if (superClass.isSubclassOf(CLASS_PREFERENCE_FRAGMENT, false)) {
report(ISSUE_PREFERENCE_FRAGMENT, context, resolvedClass, node);
return;
} else if (superClass.isSubclassOf(CLASS_DIALOG_FRAGMENT_V4, false)) {
report(ISSUE_DIALOG_FRAGMENT, context, resolvedClass, node);
return;
} else if (superClass.isSubclassOf(CLASS_ACTIVITY, false)) {
report(ISSUE_ACTIVITY, context, resolvedClass, node);
return;
} else if (superClass.isSubclassOf(CLASS_FRAGMENT_V4, false)) {
report(ISSUE_FRAGMENT, context, resolvedClass, node);
return;
}
}
superClass = superClass.getSuperClass();
}
}
private void report(Issue issue, JavaContext context, JavaParser.ResolvedClass resolvedClass, Node node) {
context.report(issue, node, context.getLocation(node), "");
reportedIssues.add(resolvedClass.getSimpleName());
}
private boolean isGeneratedBaseClass(JavaParser.ResolvedClass resolvedClass) {
return resolvedClass != null && resolvedClass.getSimpleName().startsWith("BaseFragment<.Container>");
}
private boolean isBaseClass(JavaParser.ResolvedClass resolvedClass) {
for (String baseClass : BASE_CLASSES) {
if (resolvedClass.getName().startsWith(baseClass)) {
return true;
}
}
return false;
}
}