aboutsummaryrefslogtreecommitdiffstats
path: root/lint
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2013-02-05 11:01:35 -0800
committerRaphael Moll <ralf@android.com>2013-02-06 19:56:02 -0800
commit3cf16960a67f0c8178e7bc81988fb1436e775e91 (patch)
treecaad2fa1366fc15d09c679476ae9caf5ed9a6596 /lint
parentb15ea7875a9a4101d4be7e547950b7d2b64c1464 (diff)
downloadsdk-3cf16960a67f0c8178e7bc81988fb1436e775e91.zip
sdk-3cf16960a67f0c8178e7bc81988fb1436e775e91.tar.gz
sdk-3cf16960a67f0c8178e7bc81988fb1436e775e91.tar.bz2
Remove source of prebuilts.
Sources are now located in tools/base.git. Change-Id: I9cbe1deb98f8c43e90f5fb04b668f664b9850620
Diffstat (limited to 'lint')
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java202
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java81
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/Configuration.java130
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java462
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java288
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IDomParser.java93
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IJavaParser.java76
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java304
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java1195
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintClient.java691
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintDriver.java2227
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintListener.java70
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java208
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java90
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/XmlVisitor.java221
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Category.java152
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java682
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Context.java377
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java68
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Detector.java611
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Issue.java553
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java127
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java70
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java798
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Location.java722
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Position.java50
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Project.java929
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java60
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Scope.java152
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Severity.java78
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Speed.java55
-rw-r--r--lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java154
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java130
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java239
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java205
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Api.java87
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java424
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java1569
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java971
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiParser.java122
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java259
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java382
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java782
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java128
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java540
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java105
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java197
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java329
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java240
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java175
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java171
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java115
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java673
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java167
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java143
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java267
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java164
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java117
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java83
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java95
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java114
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/IconDetector.java1906
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java349
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java280
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java565
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java168
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java225
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestOrderDetector.java557
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java136
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MathDetector.java97
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java213
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java406
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java91
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java241
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java155
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java103
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java424
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java243
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java566
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java278
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java107
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java75
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java164
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java297
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java255
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java618
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java100
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java127
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java161
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java402
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java74
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java234
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java145
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java1299
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StyleCycleDetector.java97
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java187
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java315
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java230
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java111
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java156
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java150
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java581
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java418
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java785
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java529
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java594
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java118
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java246
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java106
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java148
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java175
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java209
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java429
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java97
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java83
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java353
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java109
-rw-r--r--lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java69
118 files changed, 0 insertions, 38325 deletions
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java
deleted file mode 100644
index e8a4bb5..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/AsmVisitor.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.google.common.annotations.Beta;
-
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Specialized visitor for running detectors on a class object model.
- * <p>
- * It operates in two phases:
- * <ol>
- * <li> First, it computes a set of maps where it generates a map from each
- * significant method name to a list of detectors to consult for that method
- * name. The set of method names that a detector is interested in is provided
- * by the detectors themselves.
- * <li> Second, it iterates over the DOM a single time. For each method call it finds,
- * it dispatches to any check that has registered interest in that method name.
- * <li> Finally, it runs a full check on those class scanners that do not register
- * specific method names to be checked. This is intended for those detectors
- * that do custom work, not related specifically to method calls.
- * </ol>
- * It also notifies all the detectors before and after the document is processed
- * such that they can do pre- and post-processing.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-class AsmVisitor {
- /**
- * Number of distinct node types specified in {@link AbstractInsnNode}. Sadly
- * there isn't a max-constant there, so update this along with ASM library
- * updates.
- */
- private static final int TYPE_COUNT = AbstractInsnNode.LINE + 1;
- private final Map<String, List<ClassScanner>> mMethodNameToChecks =
- new HashMap<String, List<ClassScanner>>();
- private final Map<String, List<ClassScanner>> mMethodOwnerToChecks =
- new HashMap<String, List<ClassScanner>>();
- private final List<Detector> mFullClassChecks = new ArrayList<Detector>();
-
- private final List<? extends Detector> mAllDetectors;
- private List<ClassScanner>[] mNodeTypeDetectors;
-
- // Really want this:
- //<T extends List<Detector> & Detector.ClassScanner> ClassVisitor(T xmlDetectors) {
- // but it makes client code tricky and ugly.
- @SuppressWarnings("unchecked")
- AsmVisitor(@NonNull LintClient client, @NonNull List<? extends Detector> classDetectors) {
- mAllDetectors = classDetectors;
-
- // TODO: Check appliesTo() for files, and find a quick way to enable/disable
- // rules when running through a full project!
- for (Detector detector : classDetectors) {
- Detector.ClassScanner scanner = (Detector.ClassScanner) detector;
-
- boolean checkFullClass = true;
-
- Collection<String> names = scanner.getApplicableCallNames();
- if (names != null) {
- checkFullClass = false;
- for (String element : names) {
- List<Detector.ClassScanner> list = mMethodNameToChecks.get(element);
- if (list == null) {
- list = new ArrayList<Detector.ClassScanner>();
- mMethodNameToChecks.put(element, list);
- }
- list.add(scanner);
- }
- }
-
- Collection<String> owners = scanner.getApplicableCallOwners();
- if (owners != null) {
- checkFullClass = false;
- for (String element : owners) {
- List<Detector.ClassScanner> list = mMethodOwnerToChecks.get(element);
- if (list == null) {
- list = new ArrayList<Detector.ClassScanner>();
- mMethodOwnerToChecks.put(element, list);
- }
- list.add(scanner);
- }
- }
-
- int[] types = scanner.getApplicableAsmNodeTypes();
- if (types != null) {
- checkFullClass = false;
- for (int type : types) {
- if (type < 0 || type >= TYPE_COUNT) {
- // Can't support this node type: looks like ASM wasn't updated correctly.
- client.log(null, "Out of range node type %1$d from detector %2$s",
- type, scanner);
- continue;
- }
- if (mNodeTypeDetectors == null) {
- mNodeTypeDetectors = new List[TYPE_COUNT];
- }
- List<ClassScanner> checks = mNodeTypeDetectors[type];
- if (checks == null) {
- checks = new ArrayList<ClassScanner>();
- mNodeTypeDetectors[type] = checks;
- }
- checks.add(scanner);
- }
- }
-
- if (checkFullClass) {
- mFullClassChecks.add(detector);
- }
- }
- }
-
- @SuppressWarnings("rawtypes") // ASM API uses raw types
- void runClassDetectors(ClassContext context) {
- ClassNode classNode = context.getClassNode();
-
- for (Detector detector : mAllDetectors) {
- detector.beforeCheckFile(context);
- }
-
- for (Detector detector : mFullClassChecks) {
- Detector.ClassScanner scanner = (Detector.ClassScanner) detector;
- scanner.checkClass(context, classNode);
- detector.afterCheckFile(context);
- }
-
- if (!mMethodNameToChecks.isEmpty() || !mMethodOwnerToChecks.isEmpty() ||
- mNodeTypeDetectors != null && mNodeTypeDetectors.length > 0) {
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- InsnList nodes = method.instructions;
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- int type = instruction.getType();
- if (type == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode call = (MethodInsnNode) instruction;
-
- String owner = call.owner;
- List<ClassScanner> scanners = mMethodOwnerToChecks.get(owner);
- if (scanners != null) {
- for (ClassScanner scanner : scanners) {
- scanner.checkCall(context, classNode, method, call);
- }
- }
-
- String name = call.name;
- scanners = mMethodNameToChecks.get(name);
- if (scanners != null) {
- for (ClassScanner scanner : scanners) {
- scanner.checkCall(context, classNode, method, call);
- }
- }
- }
-
- if (mNodeTypeDetectors != null && type < mNodeTypeDetectors.length) {
- List<ClassScanner> scanners = mNodeTypeDetectors[type];
- if (scanners != null) {
- for (ClassScanner scanner : scanners) {
- scanner.checkInstruction(context, classNode, method, instruction);
- }
- }
- }
- }
- }
- }
-
- for (Detector detector : mAllDetectors) {
- detector.afterCheckFile(context);
- }
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java
deleted file mode 100644
index 337eb27..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/CircularDependencyException.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.google.common.annotations.Beta;
-
-/**
- * Exception thrown when there is a circular dependency, such as a circular dependency
- * of library mProject references
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class CircularDependencyException extends RuntimeException {
- @Nullable
- private Project mProject;
-
- @Nullable
- private Location mLocation;
-
- CircularDependencyException(@NonNull String message) {
- super(message);
- }
-
- /**
- * Returns the associated project, if any
- *
- * @return the associated project, if any
- */
- @Nullable
- public Project getProject() {
- return mProject;
- }
-
- /**
- * Sets the associated project, if any
- *
- * @param project the associated project, if any
- */
- public void setProject(@Nullable Project project) {
- mProject = project;
- }
-
- /**
- * Returns the associated location, if any
- *
- * @return the associated location, if any
- */
- @Nullable
- public Location getLocation() {
- return mLocation;
- }
-
- /**
- * Sets the associated location, if any
- *
- * @param location the associated location, if any
- */
- public void setLocation(@Nullable Location location) {
- mLocation = location;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/Configuration.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/Configuration.java
deleted file mode 100644
index d233be7..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/Configuration.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.annotations.Beta;
-
-/**
- * Lint configuration for an Android project such as which specific rules to include,
- * which specific rules to exclude, and which specific errors to ignore.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class Configuration {
- /**
- * Checks whether this issue should be ignored because the user has already
- * suppressed the error? Note that this refers to individual issues being
- * suppressed/ignored, not a whole detector being disabled via something
- * like {@link #isEnabled(Issue)}.
- *
- * @param context the context used by the detector when the issue was found
- * @param issue the issue that was found
- * @param location the location of the issue
- * @param message the associated user message
- * @param data additional information about an issue (see
- * {@link LintClient#report(Context, Issue, Severity, Location, String, Object)}
- * for more information
- * @return true if this issue should be suppressed
- */
- public boolean isIgnored(
- @NonNull Context context,
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- return false;
- }
-
- /**
- * Returns false if the given issue has been disabled. This is just
- * a convenience method for {@code getSeverity(issue) != Severity.IGNORE}.
- *
- * @param issue the issue to check
- * @return false if the issue has been disabled
- */
- public boolean isEnabled(@NonNull Issue issue) {
- return getSeverity(issue) != Severity.IGNORE;
- }
-
- /**
- * Returns the severity for a given issue. This is the same as the
- * {@link Issue#getDefaultSeverity()} unless the user has selected a custom
- * severity (which is tool context dependent).
- *
- * @param issue the issue to look up the severity from
- * @return the severity use for issues for the given detector
- */
- public Severity getSeverity(@NonNull Issue issue) {
- return issue.getDefaultSeverity();
- }
-
- // Editing configurations
-
- /**
- * Marks the given warning as "ignored".
- *
- * @param context The scanning context
- * @param issue the issue to be ignored
- * @param location The location to ignore the warning at, if any
- * @param message The message for the warning
- * @param data The corresponding data, or null
- */
- public abstract void ignore(
- @NonNull Context context,
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data);
-
- /**
- * Sets the severity to be used for this issue.
- *
- * @param issue the issue to set the severity for
- * @param severity the severity to associate with this issue, or null to
- * reset the severity to the default
- */
- public abstract void setSeverity(@NonNull Issue issue, @Nullable Severity severity);
-
- // Bulk editing support
-
- /**
- * Marks the beginning of a "bulk" editing operation with repeated calls to
- * {@link #setSeverity} or {@link #ignore}. After all the values have been
- * set, the client <b>must</b> call {@link #finishBulkEditing()}. This
- * allows configurations to avoid doing expensive I/O (such as writing out a
- * config XML file) for each and every editing operation when they are
- * applied in bulk, such as from a configuration dialog's "Apply" action.
- */
- public void startBulkEditing() {
- }
-
- /**
- * Marks the end of a "bulk" editing operation, where values should be
- * committed to persistent storage. See {@link #startBulkEditing()} for
- * details.
- */
- public void finishBulkEditing() {
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
deleted file mode 100644
index d6b925c..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultConfiguration.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.annotations.Beta;
-import com.google.common.io.Closeables;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXParseException;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-/**
- * Default implementation of a {@link Configuration} which reads and writes
- * configuration data into {@code lint.xml} in the project directory.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class DefaultConfiguration extends Configuration {
- private final LintClient mClient;
- private static final String CONFIG_FILE_NAME = "lint.xml"; //$NON-NLS-1$
-
- // Lint XML File
- @NonNull
- private static final String TAG_ISSUE = "issue"; //$NON-NLS-1$
- @NonNull
- private static final String ATTR_ID = "id"; //$NON-NLS-1$
- @NonNull
- private static final String ATTR_SEVERITY = "severity"; //$NON-NLS-1$
- @NonNull
- private static final String ATTR_PATH = "path"; //$NON-NLS-1$
- @NonNull
- private static final String TAG_IGNORE = "ignore"; //$NON-NLS-1$
- @NonNull
- private static final String VALUE_ALL = "all"; //$NON-NLS-1$
-
- private final Configuration mParent;
- private final Project mProject;
- private final File mConfigFile;
- private boolean mBulkEditing;
-
- /** Map from id to list of project-relative paths for suppressed warnings */
- private Map<String, List<String>> mSuppressed;
-
- /**
- * Map from id to custom {@link Severity} override
- */
- private Map<String, Severity> mSeverity;
-
- protected DefaultConfiguration(
- @NonNull LintClient client,
- @Nullable Project project,
- @Nullable Configuration parent,
- @NonNull File configFile) {
- mClient = client;
- mProject = project;
- mParent = parent;
- mConfigFile = configFile;
- }
-
- protected DefaultConfiguration(
- @NonNull LintClient client,
- @NonNull Project project,
- @Nullable Configuration parent) {
- this(client, project, parent, new File(project.getDir(), CONFIG_FILE_NAME));
- }
-
- /**
- * Creates a new {@link DefaultConfiguration}
- *
- * @param client the client to report errors to etc
- * @param project the associated project
- * @param parent the parent/fallback configuration or null
- * @return a new configuration
- */
- @NonNull
- public static DefaultConfiguration create(
- @NonNull LintClient client,
- @NonNull Project project,
- @Nullable Configuration parent) {
- return new DefaultConfiguration(client, project, parent);
- }
-
- /**
- * Creates a new {@link DefaultConfiguration} for the given lint config
- * file, not affiliated with a project. This is used for global
- * configurations.
- *
- * @param client the client to report errors to etc
- * @param lintFile the lint file containing the configuration
- * @return a new configuration
- */
- @NonNull
- public static DefaultConfiguration create(@NonNull LintClient client, @NonNull File lintFile) {
- return new DefaultConfiguration(client, null /*project*/, null /*parent*/, lintFile);
- }
-
- @Override
- public boolean isIgnored(
- @NonNull Context context,
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- ensureInitialized();
-
- String id = issue.getId();
- List<String> paths = mSuppressed.get(id);
- if (paths == null) {
- paths = mSuppressed.get(VALUE_ALL);
- }
- if (paths != null && location != null) {
- File file = location.getFile();
- String relativePath = context.getProject().getRelativePath(file);
- for (String suppressedPath : paths) {
- if (suppressedPath.equals(relativePath)) {
- return true;
- }
- }
- }
-
- if (mParent != null) {
- return mParent.isIgnored(context, issue, location, message, data);
- }
-
- return false;
- }
-
- @NonNull
- protected Severity getDefaultSeverity(@NonNull Issue issue) {
- if (!issue.isEnabledByDefault()) {
- return Severity.IGNORE;
- }
-
- return issue.getDefaultSeverity();
- }
-
- @Override
- @NonNull
- public Severity getSeverity(@NonNull Issue issue) {
- ensureInitialized();
-
- Severity severity = mSeverity.get(issue.getId());
- if (severity == null) {
- severity = mSeverity.get(VALUE_ALL);
- }
-
- if (severity != null) {
- return severity;
- }
-
- if (mParent != null) {
- return mParent.getSeverity(issue);
- }
-
- return getDefaultSeverity(issue);
- }
-
- private void ensureInitialized() {
- if (mSuppressed == null) {
- readConfig();
- }
- }
-
- private void formatError(String message, Object... args) {
- if (args != null && args.length > 0) {
- message = String.format(message, args);
- }
- message = "Failed to parse lint.xml configuration file: " + message;
- LintDriver driver = new LintDriver(new IssueRegistry() {
- @Override @NonNull public List<Issue> getIssues() {
- return Collections.emptyList();
- }
- }, mClient);
- mClient.report(new Context(driver, mProject, mProject, mConfigFile),
- IssueRegistry.LINT_ERROR,
- mProject.getConfiguration().getSeverity(IssueRegistry.LINT_ERROR),
- Location.create(mConfigFile), message, null);
- }
-
- private void readConfig() {
- mSuppressed = new HashMap<String, List<String>>();
- mSeverity = new HashMap<String, Severity>();
-
- if (!mConfigFile.exists()) {
- return;
- }
-
- @SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly
- BufferedInputStream input = null;
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- input = new BufferedInputStream(new FileInputStream(mConfigFile));
- InputSource source = new InputSource(input);
- factory.setNamespaceAware(false);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.parse(source);
- NodeList issues = document.getElementsByTagName(TAG_ISSUE);
- for (int i = 0, count = issues.getLength(); i < count; i++) {
- Node node = issues.item(i);
- Element element = (Element) node;
- String id = element.getAttribute(ATTR_ID);
- if (id.isEmpty()) {
- formatError("Invalid lint config file: Missing required issue id attribute");
- continue;
- }
-
- NamedNodeMap attributes = node.getAttributes();
- for (int j = 0, n = attributes.getLength(); j < n; j++) {
- Node attribute = attributes.item(j);
- String name = attribute.getNodeName();
- String value = attribute.getNodeValue();
- if (ATTR_ID.equals(name)) {
- // already handled
- } else if (ATTR_SEVERITY.equals(name)) {
- for (Severity severity : Severity.values()) {
- if (value.equalsIgnoreCase(severity.name())) {
- mSeverity.put(id, severity);
- break;
- }
- }
- } else {
- formatError("Unexpected attribute \"%1$s\"", name);
- }
- }
-
- // Look up ignored errors
- NodeList childNodes = element.getChildNodes();
- if (childNodes.getLength() > 0) {
- for (int j = 0, n = childNodes.getLength(); j < n; j++) {
- Node child = childNodes.item(j);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- Element ignore = (Element) child;
- String path = ignore.getAttribute(ATTR_PATH);
- if (path.isEmpty()) {
- formatError("Missing required %1$s attribute under %2$s",
- ATTR_PATH, id);
- } else {
- List<String> paths = mSuppressed.get(id);
- if (paths == null) {
- paths = new ArrayList<String>(n / 2 + 1);
- mSuppressed.put(id, paths);
- }
- paths.add(path);
- }
- }
- }
- }
- }
- } catch (SAXParseException e) {
- formatError(e.getMessage());
- } catch (Exception e) {
- mClient.log(e, null);
- } finally {
- Closeables.closeQuietly(input);
- }
- }
-
- private void writeConfig() {
- try {
- // Write the contents to a new file first such that we don't clobber the
- // existing file if some I/O error occurs.
- File file = new File(mConfigFile.getParentFile(),
- mConfigFile.getName() + ".new"); //$NON-NLS-1$
-
- Writer writer = new BufferedWriter(new FileWriter(file));
- writer.write(
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //$NON-NLS-1$
- "<lint>\n"); //$NON-NLS-1$
-
- if (!mSuppressed.isEmpty() || !mSeverity.isEmpty()) {
- // Process the maps in a stable sorted order such that if the
- // files are checked into version control with the project,
- // there are no random diffs just because hashing algorithms
- // differ:
- Set<String> idSet = new HashSet<String>();
- for (String id : mSuppressed.keySet()) {
- idSet.add(id);
- }
- for (String id : mSeverity.keySet()) {
- idSet.add(id);
- }
- List<String> ids = new ArrayList<String>(idSet);
- Collections.sort(ids);
-
- for (String id : ids) {
- writer.write(" <"); //$NON-NLS-1$
- writer.write(TAG_ISSUE);
- writeAttribute(writer, ATTR_ID, id);
- Severity severity = mSeverity.get(id);
- if (severity != null) {
- writeAttribute(writer, ATTR_SEVERITY,
- severity.name().toLowerCase(Locale.US));
- }
-
- List<String> paths = mSuppressed.get(id);
- if (paths != null && !paths.isEmpty()) {
- writer.write('>');
- writer.write('\n');
- // The paths are already kept in sorted order when they are modified
- // by ignore(...)
- for (String path : paths) {
- writer.write(" <"); //$NON-NLS-1$
- writer.write(TAG_IGNORE);
- writeAttribute(writer, ATTR_PATH, path);
- writer.write(" />\n"); //$NON-NLS-1$
- }
- writer.write(" </"); //$NON-NLS-1$
- writer.write(TAG_ISSUE);
- writer.write('>');
- writer.write('\n');
- } else {
- writer.write(" />\n"); //$NON-NLS-1$
- }
- }
- }
-
- writer.write("</lint>"); //$NON-NLS-1$
- writer.close();
-
- // Move file into place: move current version to lint.xml~ (removing the old ~ file
- // if it exists), then move the new version to lint.xml.
- File oldFile = new File(mConfigFile.getParentFile(),
- mConfigFile.getName() + '~'); //$NON-NLS-1$
- if (oldFile.exists()) {
- oldFile.delete();
- }
- if (mConfigFile.exists()) {
- mConfigFile.renameTo(oldFile);
- }
- boolean ok = file.renameTo(mConfigFile);
- if (ok && oldFile.exists()) {
- oldFile.delete();
- }
- } catch (Exception e) {
- mClient.log(e, null);
- }
- }
-
- private static void writeAttribute(
- @NonNull Writer writer, @NonNull String name, @NonNull String value)
- throws IOException {
- writer.write(' ');
- writer.write(name);
- writer.write('=');
- writer.write('"');
- writer.write(value);
- writer.write('"');
- }
-
- @Override
- public void ignore(
- @NonNull Context context,
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- // This configuration only supports suppressing warnings on a per-file basis
- if (location != null) {
- ignore(issue, location.getFile());
- }
- }
-
- /**
- * Marks the given issue and file combination as being ignored.
- *
- * @param issue the issue to be ignored in the given file
- * @param file the file to ignore the issue in
- */
- public void ignore(@NonNull Issue issue, @NonNull File file) {
- ensureInitialized();
-
- String path = mProject != null ? mProject.getRelativePath(file) : file.getPath();
-
- List<String> paths = mSuppressed.get(issue.getId());
- if (paths == null) {
- paths = new ArrayList<String>();
- mSuppressed.put(issue.getId(), paths);
- }
- paths.add(path);
-
- // Keep paths sorted alphabetically; makes XML output stable
- Collections.sort(paths);
-
- if (!mBulkEditing) {
- writeConfig();
- }
- }
-
- @Override
- public void setSeverity(@NonNull Issue issue, @Nullable Severity severity) {
- ensureInitialized();
-
- String id = issue.getId();
- if (severity == null) {
- mSeverity.remove(id);
- } else {
- mSeverity.put(id, severity);
- }
-
- if (!mBulkEditing) {
- writeConfig();
- }
- }
-
- @Override
- public void startBulkEditing() {
- mBulkEditing = true;
- }
-
- @Override
- public void finishBulkEditing() {
- mBulkEditing = false;
- writeConfig();
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java
deleted file mode 100644
index 2f54659..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/DefaultSdkInfo.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.ABSOLUTE_LAYOUT;
-import static com.android.SdkConstants.ABS_LIST_VIEW;
-import static com.android.SdkConstants.ABS_SEEK_BAR;
-import static com.android.SdkConstants.ABS_SPINNER;
-import static com.android.SdkConstants.ADAPTER_VIEW;
-import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.BUTTON;
-import static com.android.SdkConstants.CHECKABLE;
-import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
-import static com.android.SdkConstants.CHECK_BOX;
-import static com.android.SdkConstants.COMPOUND_BUTTON;
-import static com.android.SdkConstants.EDIT_TEXT;
-import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
-import static com.android.SdkConstants.FRAME_LAYOUT;
-import static com.android.SdkConstants.GALLERY;
-import static com.android.SdkConstants.GRID_VIEW;
-import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
-import static com.android.SdkConstants.IMAGE_BUTTON;
-import static com.android.SdkConstants.IMAGE_VIEW;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.LIST_VIEW;
-import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.PROGRESS_BAR;
-import static com.android.SdkConstants.RADIO_BUTTON;
-import static com.android.SdkConstants.RADIO_GROUP;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.SCROLL_VIEW;
-import static com.android.SdkConstants.SEEK_BAR;
-import static com.android.SdkConstants.SPINNER;
-import static com.android.SdkConstants.SURFACE_VIEW;
-import static com.android.SdkConstants.SWITCH;
-import static com.android.SdkConstants.TABLE_LAYOUT;
-import static com.android.SdkConstants.TABLE_ROW;
-import static com.android.SdkConstants.TAB_HOST;
-import static com.android.SdkConstants.TAB_WIDGET;
-import static com.android.SdkConstants.TEXT_VIEW;
-import static com.android.SdkConstants.TOGGLE_BUTTON;
-import static com.android.SdkConstants.VIEW;
-import static com.android.SdkConstants.VIEW_ANIMATOR;
-import static com.android.SdkConstants.VIEW_GROUP;
-import static com.android.SdkConstants.VIEW_PKG_PREFIX;
-import static com.android.SdkConstants.VIEW_STUB;
-import static com.android.SdkConstants.VIEW_SWITCHER;
-import static com.android.SdkConstants.WEB_VIEW;
-import static com.android.SdkConstants.WIDGET_PKG_PREFIX;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Default simple implementation of an {@link SdkInfo}
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-class DefaultSdkInfo extends SdkInfo {
- @Override
- @Nullable
- public String getParentViewName(@NonNull String name) {
- name = getRawType(name);
-
- return PARENTS.get(name);
- }
-
- @Override
- @Nullable
- public String getParentViewClass(@NonNull String fqcn) {
- int index = fqcn.lastIndexOf('.');
- if (index != -1) {
- fqcn = fqcn.substring(index + 1);
- }
-
- String parent = PARENTS.get(fqcn);
- if (parent == null) {
- return null;
- }
- // The map only stores class names internally; correct for full package paths
- if (parent.equals(VIEW) || parent.equals(VIEW_GROUP) || parent.equals(SURFACE_VIEW)) {
- return VIEW_PKG_PREFIX + parent;
- } else {
- return WIDGET_PKG_PREFIX + parent;
- }
- }
-
- @Override
- public boolean isSubViewOf(@NonNull String parentType, @NonNull String childType) {
- String parent = getRawType(parentType);
- String child = getRawType(childType);
-
- // Do analysis just on non-fqcn paths
- if (parent.indexOf('.') != -1) {
- parent = parent.substring(parent.lastIndexOf('.') + 1);
- }
- if (child.indexOf('.') != -1) {
- child = child.substring(child.lastIndexOf('.') + 1);
- }
-
- if (parent.equals(VIEW)) {
- return true;
- }
-
- while (!child.equals(VIEW)) {
- if (parent.equals(child)) {
- return true;
- }
- if (implementsInterface(child, parentType)) {
- return true;
- }
- child = PARENTS.get(child);
- if (child == null) {
- // Unknown view - err on the side of caution
- return true;
- }
- }
-
- return false;
- }
-
- private static boolean implementsInterface(String className, String interfaceName) {
- return interfaceName.equals(INTERFACES.get(className));
- }
-
- // Strip off type parameters, e.g. AdapterView<?> => AdapterView
- private static String getRawType(String type) {
- if (type != null) {
- int index = type.indexOf('<');
- if (index != -1) {
- type = type.substring(0, index);
- }
- }
-
- return type;
- }
-
- @Override
- public boolean isLayout(@NonNull String tag) {
- // TODO: Read in widgets.txt from the platform install area to look up this information
- // dynamically instead!
-
- if (super.isLayout(tag)) {
- return true;
- }
-
- return LAYOUTS.contains(tag);
- }
-
- private static final int CLASS_COUNT = 59;
- private static final int LAYOUT_COUNT = 20;
-
- private static final Map<String,String> PARENTS = Maps.newHashMapWithExpectedSize(CLASS_COUNT);
- private static final Set<String> LAYOUTS = Sets.newHashSetWithExpectedSize(CLASS_COUNT);
-
- static {
- PARENTS.put(COMPOUND_BUTTON, BUTTON);
- PARENTS.put(ABS_SPINNER, ADAPTER_VIEW);
- PARENTS.put(ABS_LIST_VIEW, ADAPTER_VIEW);
- PARENTS.put(ABS_SEEK_BAR, ADAPTER_VIEW);
- PARENTS.put(ADAPTER_VIEW, VIEW_GROUP);
- PARENTS.put(VIEW_GROUP, VIEW);
-
- PARENTS.put(TEXT_VIEW, VIEW);
- PARENTS.put(CHECKED_TEXT_VIEW, TEXT_VIEW);
- PARENTS.put(RADIO_BUTTON, COMPOUND_BUTTON);
- PARENTS.put(SPINNER, ABS_SPINNER);
- PARENTS.put(IMAGE_BUTTON, IMAGE_VIEW);
- PARENTS.put(IMAGE_VIEW, VIEW);
- PARENTS.put(EDIT_TEXT, TEXT_VIEW);
- PARENTS.put(PROGRESS_BAR, VIEW);
- PARENTS.put(TOGGLE_BUTTON, COMPOUND_BUTTON);
- PARENTS.put(VIEW_STUB, VIEW);
- PARENTS.put(BUTTON, TEXT_VIEW);
- PARENTS.put(SEEK_BAR, ABS_SEEK_BAR);
- PARENTS.put(CHECK_BOX, COMPOUND_BUTTON);
- PARENTS.put(SWITCH, COMPOUND_BUTTON);
- PARENTS.put(GALLERY, ABS_SPINNER);
- PARENTS.put(SURFACE_VIEW, VIEW);
- PARENTS.put(ABSOLUTE_LAYOUT, VIEW_GROUP);
- PARENTS.put(LINEAR_LAYOUT, VIEW_GROUP);
- PARENTS.put(RELATIVE_LAYOUT, VIEW_GROUP);
- PARENTS.put(LIST_VIEW, ABS_LIST_VIEW);
- PARENTS.put(VIEW_SWITCHER, VIEW_ANIMATOR);
- PARENTS.put(FRAME_LAYOUT, VIEW_GROUP);
- PARENTS.put(HORIZONTAL_SCROLL_VIEW, FRAME_LAYOUT);
- PARENTS.put(VIEW_ANIMATOR, FRAME_LAYOUT);
- PARENTS.put(TAB_HOST, FRAME_LAYOUT);
- PARENTS.put(TABLE_ROW, LINEAR_LAYOUT);
- PARENTS.put(RADIO_GROUP, LINEAR_LAYOUT);
- PARENTS.put(TAB_WIDGET, LINEAR_LAYOUT);
- PARENTS.put(EXPANDABLE_LIST_VIEW, LIST_VIEW);
- PARENTS.put(TABLE_LAYOUT, LINEAR_LAYOUT);
- PARENTS.put(SCROLL_VIEW, FRAME_LAYOUT);
- PARENTS.put(GRID_VIEW, ABS_LIST_VIEW);
- PARENTS.put(WEB_VIEW, ABSOLUTE_LAYOUT);
- PARENTS.put(AUTO_COMPLETE_TEXT_VIEW, EDIT_TEXT);
- PARENTS.put(MULTI_AUTO_COMPLETE_TEXT_VIEW, AUTO_COMPLETE_TEXT_VIEW);
- PARENTS.put(CHECKED_TEXT_VIEW, TEXT_VIEW);
-
- PARENTS.put("MediaController", FRAME_LAYOUT); //$NON-NLS-1$
- PARENTS.put("SlidingDrawer", VIEW_GROUP); //$NON-NLS-1$
- PARENTS.put("DialerFilter", RELATIVE_LAYOUT); //$NON-NLS-1$
- PARENTS.put("DigitalClock", TEXT_VIEW); //$NON-NLS-1$
- PARENTS.put("Chronometer", TEXT_VIEW); //$NON-NLS-1$
- PARENTS.put("ImageSwitcher", VIEW_SWITCHER); //$NON-NLS-1$
- PARENTS.put("TextSwitcher", VIEW_SWITCHER); //$NON-NLS-1$
- PARENTS.put("AnalogClock", VIEW); //$NON-NLS-1$
- PARENTS.put("TwoLineListItem", RELATIVE_LAYOUT); //$NON-NLS-1$
- PARENTS.put("ZoomControls", LINEAR_LAYOUT); //$NON-NLS-1$
- PARENTS.put("DatePicker", FRAME_LAYOUT); //$NON-NLS-1$
- PARENTS.put("TimePicker", FRAME_LAYOUT); //$NON-NLS-1$
- PARENTS.put("VideoView", SURFACE_VIEW); //$NON-NLS-1$
- PARENTS.put("ZoomButton", IMAGE_BUTTON); //$NON-NLS-1$
- PARENTS.put("RatingBar", ABS_SEEK_BAR); //$NON-NLS-1$
- PARENTS.put("ViewFlipper", VIEW_ANIMATOR); //$NON-NLS-1$
- PARENTS.put("NumberPicker", LINEAR_LAYOUT); //$NON-NLS-1$
-
- assert PARENTS.size() <= CLASS_COUNT : PARENTS.size();
-
- /*
- // Check that all widgets lead to the root view
- if (LintUtils.assertionsEnabled()) {
- for (String key : PARENTS.keySet()) {
- String parent = PARENTS.get(key);
- if (!parent.equals(VIEW)) {
- String grandParent = PARENTS.get(parent);
- assert grandParent != null : parent;
- }
- }
- }
- */
-
- LAYOUTS.add(TAB_HOST);
- LAYOUTS.add(HORIZONTAL_SCROLL_VIEW);
- LAYOUTS.add(VIEW_SWITCHER);
- LAYOUTS.add(TAB_WIDGET);
- LAYOUTS.add(VIEW_ANIMATOR);
- LAYOUTS.add(SCROLL_VIEW);
- LAYOUTS.add(GRID_VIEW);
- LAYOUTS.add(TABLE_ROW);
- LAYOUTS.add(RADIO_GROUP);
- LAYOUTS.add(LIST_VIEW);
- LAYOUTS.add(EXPANDABLE_LIST_VIEW);
- LAYOUTS.add("MediaController"); //$NON-NLS-1$
- LAYOUTS.add("DialerFilter"); //$NON-NLS-1$
- LAYOUTS.add("ViewFlipper"); //$NON-NLS-1$
- LAYOUTS.add("SlidingDrawer"); //$NON-NLS-1$
- LAYOUTS.add("StackView"); //$NON-NLS-1$
- LAYOUTS.add("SearchView"); //$NON-NLS-1$
- LAYOUTS.add("TextSwitcher"); //$NON-NLS-1$
- LAYOUTS.add("AdapterViewFlipper"); //$NON-NLS-1$
- LAYOUTS.add("ImageSwitcher"); //$NON-NLS-1$
- assert LAYOUTS.size() <= LAYOUT_COUNT : LAYOUTS.size();
- }
-
- // Currently using a map; this should really be a list, but using a map until we actually
- // start adding more than one item
- @NonNull
- private static final Map<String, String> INTERFACES = new HashMap<String, String>(2);
- static {
- INTERFACES.put(CHECKED_TEXT_VIEW, CHECKABLE);
- INTERFACES.put(COMPOUND_BUTTON, CHECKABLE);
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IDomParser.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IDomParser.java
deleted file mode 100644
index 1a70fac..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IDomParser.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.annotations.Beta;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-/**
- * A wrapper for an XML parser. This allows tools integrating lint to map directly
- * to builtin services, such as already-parsed data structures in XML editors.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public interface IDomParser {
- /**
- * Parse the file pointed to by the given context and return as a Document
- *
- * @param context the context pointing to the file to be parsed, typically
- * via {@link Context#getContents()} but the file handle (
- * {@link Context#file} can also be used to map to an existing
- * editor buffer in the surrounding tool, etc)
- * @return the parsed DOM document, or null if parsing fails
- */
- @Nullable
- Document parseXml(@NonNull XmlContext context);
-
- /**
- * Returns a {@link Location} for the given DOM node
- *
- * @param context information about the file being parsed
- * @param node the node to create a location for
- * @return a location for the given node
- */
- @NonNull
- Location getLocation(@NonNull XmlContext context, @NonNull Node node);
-
- /**
- * Returns a {@link Location} for the given DOM node. Like
- * {@link #getLocation(XmlContext, Node)}, but allows a position range that
- * is a subset of the node range.
- *
- * @param context information about the file being parsed
- * @param node the node to create a location for
- * @param start the starting position within the node, inclusive
- * @param end the ending position within the node, exclusive
- * @return a location for the given node
- */
- @NonNull
- Location getLocation(@NonNull XmlContext context, @NonNull Node node, int start, int end);
-
- /**
- * Creates a light-weight handle to a location for the given node. It can be
- * turned into a full fledged location by
- * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
- *
- * @param context the context providing the node
- * @param node the node (element or attribute) to create a location handle
- * for
- * @return a location handle
- */
- @NonNull
- Location.Handle createLocationHandle(@NonNull XmlContext context, @NonNull Node node);
-
- /**
- * Dispose any data structures held for the given context.
- * @param context information about the file previously parsed
- * @param document the document that was parsed and is now being disposed
- */
- void dispose(@NonNull XmlContext context, @NonNull Document document);
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IJavaParser.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IJavaParser.java
deleted file mode 100644
index 9b74f16..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IJavaParser.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-
-import lombok.ast.Node;
-
-/**
- * A wrapper for a Java parser. This allows tools integrating lint to map directly
- * to builtin services, such as already-parsed data structures in Java editors.
- * <p/>
- * <b>NOTE: This is not a or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-public interface IJavaParser {
- /**
- * Parse the file pointed to by the given context.
- *
- * @param context the context pointing to the file to be parsed, typically
- * via {@link Context#getContents()} but the file handle (
- * {@link Context#file} can also be used to map to an existing
- * editor buffer in the surrounding tool, etc)
- * @return the compilation unit node for the file
- */
- @Nullable
- Node parseJava(@NonNull JavaContext context);
-
- /**
- * Returns a {@link Location} for the given node
- *
- * @param context information about the file being parsed
- * @param node the node to create a location for
- * @return a location for the given node
- */
- @NonNull
- Location getLocation(@NonNull JavaContext context, @NonNull Node node);
-
- /**
- * Creates a light-weight handle to a location for the given node. It can be
- * turned into a full fledged location by
- * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}.
- *
- * @param context the context providing the node
- * @param node the node (element or attribute) to create a location handle
- * for
- * @return a location handle
- */
- @NonNull
- Location.Handle createLocationHandle(@NonNull JavaContext context, @NonNull Node node);
-
- /**
- * Dispose any data structures held for the given context.
- * @param context information about the file previously parsed
- * @param compilationUnit the compilation unit being disposed
- */
- void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit);
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
deleted file mode 100644
index 0b8b141..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Maps;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/** Registry which provides a list of checks to be performed on an Android project
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class IssueRegistry {
- private static List<Category> sCategories;
- private static Map<String, Issue> sIdToIssue;
- private static Map<EnumSet<Scope>, List<Issue>> sScopeIssues = Maps.newHashMap();
-
- /**
- * Creates a new {@linkplain IssueRegistry}
- */
- protected IssueRegistry() {
- }
-
- /**
- * Issue reported by lint (not a specific detector) when it cannot even
- * parse an XML file prior to analysis
- */
- @NonNull
- public static final Issue PARSER_ERROR = Issue.create(
- "ParserError", //$NON-NLS-1$
- "Finds files that contain fatal parser errors",
- "Lint will ignore any files that contain fatal parsing errors. These may contain " +
- "other errors, or contain code which affects issues in other files.",
- Category.CORRECTNESS,
- 10,
- Severity.ERROR,
- Detector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /**
- * Issue reported by lint for various other issues which prevents lint from
- * running normally when it's not necessarily an error in the user's code base.
- */
- @NonNull
- public static final Issue LINT_ERROR = Issue.create(
- "LintError", //$NON-NLS-1$
- "Issues related to running lint itself, such as failure to read files, etc",
- "This issue type represents a problem running lint itself. Examples include " +
- "failure to find bytecode for source files (which means certain detectors " +
- "could not be run), parsing errors in lint configuration files, etc." +
- "\n" +
- "These errors are not errors in your own code, but they are shown to make " +
- "it clear that some checks were not completed.",
-
- Category.LINT,
- 10,
- Severity.ERROR,
- Detector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /**
- * Returns the list of issues that can be found by all known detectors.
- *
- * @return the list of issues to be checked (including those that may be
- * disabled!)
- */
- @NonNull
- public abstract List<Issue> getIssues();
-
- /**
- * Returns all available issues of a given scope (regardless of whether
- * they are actually enabled for a given configuration etc)
- *
- * @param scope the applicable scope set
- * @return a list of issues
- */
- @NonNull
- private List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) {
- List<Issue> list = sScopeIssues.get(scope);
- if (list == null) {
- List<Issue> issues = getIssues();
- if (scope.equals(Scope.ALL)) {
- list = issues;
- } else {
- int initialSize = 12;
- if (scope.contains(Scope.RESOURCE_FILE)) {
- initialSize += 50;
- }
- if (scope.contains(Scope.JAVA_FILE)) {
- initialSize += 12;
- }
- if (scope.contains(Scope.CLASS_FILE)) {
- initialSize += 12;
- }
- list = new ArrayList<Issue>(initialSize);
- for (Issue issue : issues) {
- // Determine if the scope matches
- if (issue.isAdequate(scope)) {
- list.add(issue);
- }
- }
- }
- sScopeIssues.put(scope, list);
- }
-
- return list;
- }
-
- /**
- * Creates a list of detectors applicable to the given scope, and with the
- * given configuration.
- *
- * @param client the client to report errors to
- * @param configuration the configuration to look up which issues are
- * enabled etc from
- * @param scope the scope for the analysis, to filter out detectors that
- * require wider analysis than is currently being performed
- * @param scopeToDetectors an optional map which (if not null) will be
- * filled by this method to contain mappings from each scope to
- * the applicable detectors for that scope
- * @return a list of new detector instances
- */
- @NonNull
- final List<? extends Detector> createDetectors(
- @NonNull LintClient client,
- @NonNull Configuration configuration,
- @NonNull EnumSet<Scope> scope,
- @Nullable Map<Scope, List<Detector>> scopeToDetectors) {
-
- List<Issue> issues = getIssuesForScope(scope);
- if (issues.isEmpty()) {
- return Collections.emptyList();
- }
-
- Set<Class<? extends Detector>> detectorClasses = new HashSet<Class<? extends Detector>>();
- Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope =
- new HashMap<Class<? extends Detector>, EnumSet<Scope>>();
-
- for (Issue issue : issues) {
- Class<? extends Detector> detectorClass = issue.getDetectorClass();
- EnumSet<Scope> issueScope = issue.getScope();
- if (!detectorClasses.contains(detectorClass)) {
- // Determine if the issue is enabled
- if (!configuration.isEnabled(issue)) {
- continue;
- }
-
- assert issue.isAdequate(scope); // Ensured by getIssuesForScope above
-
- detectorClass = client.replaceDetector(detectorClass);
-
- assert detectorClass != null : issue.getId();
- detectorClasses.add(detectorClass);
- }
-
- if (scopeToDetectors != null) {
- EnumSet<Scope> s = detectorToScope.get(detectorClass);
- if (s == null) {
- detectorToScope.put(detectorClass, issueScope);
- } else if (!s.containsAll(issueScope)) {
- EnumSet<Scope> union = EnumSet.copyOf(s);
- union.addAll(issueScope);
- detectorToScope.put(detectorClass, union);
- }
- }
- }
-
- List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size());
- for (Class<? extends Detector> clz : detectorClasses) {
- try {
- Detector detector = clz.newInstance();
- detectors.add(detector);
-
- if (scopeToDetectors != null) {
- EnumSet<Scope> union = detectorToScope.get(clz);
- for (Scope s : union) {
- List<Detector> list = scopeToDetectors.get(s);
- if (list == null) {
- list = new ArrayList<Detector>();
- scopeToDetectors.put(s, list);
- }
- list.add(detector);
- }
-
- }
- } catch (Throwable t) {
- client.log(t, "Can't initialize detector %1$s", clz.getName()); //$NON-NLS-1$
- }
- }
-
- return detectors;
- }
-
- /**
- * Returns true if the given id represents a valid issue id
- *
- * @param id the id to be checked
- * @return true if the given id is valid
- */
- public final boolean isIssueId(@NonNull String id) {
- return getIssue(id) != null;
- }
-
- /**
- * Returns true if the given category is a valid category
- *
- * @param name the category name to be checked
- * @return true if the given string is a valid category
- */
- public final boolean isCategoryName(@NonNull String name) {
- for (Category category : getCategories()) {
- if (category.getName().equals(name) || category.getFullName().equals(name)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns the available categories
- *
- * @return an iterator for all the categories, never null
- */
- @NonNull
- public List<Category> getCategories() {
- if (sCategories == null) {
- final Set<Category> categories = new HashSet<Category>();
- for (Issue issue : getIssues()) {
- categories.add(issue.getCategory());
- }
- List<Category> sorted = new ArrayList<Category>(categories);
- Collections.sort(sorted);
- sCategories = Collections.unmodifiableList(sorted);
- }
-
- return sCategories;
- }
-
- /**
- * Returns the issue for the given id, or null if it's not a valid id
- *
- * @param id the id to be checked
- * @return the corresponding issue, or null
- */
- @Nullable
- public final Issue getIssue(@NonNull String id) {
- if (sIdToIssue == null) {
- List<Issue> issues = getIssues();
- sIdToIssue = new HashMap<String, Issue>(issues.size());
- for (Issue issue : issues) {
- sIdToIssue.put(issue.getId(), issue);
- }
-
- sIdToIssue.put(PARSER_ERROR.getId(), PARSER_ERROR);
- sIdToIssue.put(LINT_ERROR.getId(), LINT_ERROR);
- }
- return sIdToIssue.get(id);
- }
-
- /**
- * Reset the registry such that it recomputes its available issues.
- * <p>
- * NOTE: This is only intended for testing purposes.
- */
- @VisibleForTesting
- protected static void reset() {
- sIdToIssue = null;
- sCategories = null;
- sScopeIssues = Maps.newHashMap();
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
deleted file mode 100644
index 81a0339..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
+++ /dev/null
@@ -1,1195 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.ANDROID_PKG;
-import static com.android.SdkConstants.R_CLASS;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Detector.XmlScanner;
-import com.android.tools.lint.detector.api.JavaContext;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import lombok.ast.AlternateConstructorInvocation;
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationDeclaration;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationMethodDeclaration;
-import lombok.ast.AnnotationValueArray;
-import lombok.ast.ArrayAccess;
-import lombok.ast.ArrayCreation;
-import lombok.ast.ArrayDimension;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.Assert;
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.Block;
-import lombok.ast.BooleanLiteral;
-import lombok.ast.Break;
-import lombok.ast.Case;
-import lombok.ast.Cast;
-import lombok.ast.Catch;
-import lombok.ast.CharLiteral;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ClassLiteral;
-import lombok.ast.Comment;
-import lombok.ast.CompilationUnit;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Continue;
-import lombok.ast.Default;
-import lombok.ast.DoWhile;
-import lombok.ast.EmptyDeclaration;
-import lombok.ast.EmptyStatement;
-import lombok.ast.EnumConstant;
-import lombok.ast.EnumDeclaration;
-import lombok.ast.EnumTypeBody;
-import lombok.ast.Expression;
-import lombok.ast.ExpressionStatement;
-import lombok.ast.FloatingPointLiteral;
-import lombok.ast.For;
-import lombok.ast.ForEach;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Identifier;
-import lombok.ast.If;
-import lombok.ast.ImportDeclaration;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.InstanceInitializer;
-import lombok.ast.InstanceOf;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.InterfaceDeclaration;
-import lombok.ast.KeywordModifier;
-import lombok.ast.LabelledStatement;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.NullLiteral;
-import lombok.ast.PackageDeclaration;
-import lombok.ast.Return;
-import lombok.ast.Select;
-import lombok.ast.StaticInitializer;
-import lombok.ast.StringLiteral;
-import lombok.ast.Super;
-import lombok.ast.SuperConstructorInvocation;
-import lombok.ast.Switch;
-import lombok.ast.Synchronized;
-import lombok.ast.This;
-import lombok.ast.Throw;
-import lombok.ast.Try;
-import lombok.ast.TypeReference;
-import lombok.ast.TypeReferencePart;
-import lombok.ast.TypeVariable;
-import lombok.ast.UnaryExpression;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-import lombok.ast.While;
-
-
-/**
- * Specialized visitor for running detectors on a Java AST.
- * It operates in three phases:
- * <ol>
- * <li> First, it computes a set of maps where it generates a map from each
- * significant AST attribute (such as method call names) to a list
- * of detectors to consult whenever that attribute is encountered.
- * Examples of "attributes" are method names, Android resource identifiers,
- * and general AST node types such as "cast" nodes etc. These are
- * defined on the {@link JavaScanner} interface.
- * <li> Second, it iterates over the document a single time, delegating to
- * the detectors found at each relevant AST attribute.
- * <li> Finally, it calls the remaining visitors (those that need to process a
- * whole document on their own).
- * </ol>
- * It also notifies all the detectors before and after the document is processed
- * such that they can do pre- and post-processing.
- */
-public class JavaVisitor {
- /** Default size of lists holding detectors of the same type for a given node type */
- private static final int SAME_TYPE_COUNT = 8;
-
- private final Map<String, List<VisitingDetector>> mMethodDetectors =
- new HashMap<String, List<VisitingDetector>>();
- private final List<VisitingDetector> mResourceFieldDetectors =
- new ArrayList<VisitingDetector>();
- private final List<VisitingDetector> mAllDetectors;
- private final List<VisitingDetector> mFullTreeDetectors;
- private final Map<Class<? extends Node>, List<VisitingDetector>> mNodeTypeDetectors =
- new HashMap<Class<? extends Node>, List<VisitingDetector>>();
- private final IJavaParser mParser;
-
- JavaVisitor(@NonNull IJavaParser parser, @NonNull List<Detector> detectors) {
- mParser = parser;
- mAllDetectors = new ArrayList<VisitingDetector>(detectors.size());
- mFullTreeDetectors = new ArrayList<VisitingDetector>(detectors.size());
-
- for (Detector detector : detectors) {
- VisitingDetector v = new VisitingDetector(detector, (JavaScanner) detector);
- mAllDetectors.add(v);
-
- List<Class<? extends Node>> nodeTypes = detector.getApplicableNodeTypes();
- if (nodeTypes != null) {
- for (Class<? extends Node> type : nodeTypes) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(type);
- if (list == null) {
- list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
- mNodeTypeDetectors.put(type, list);
- }
- list.add(v);
- }
- }
-
- List<String> names = detector.getApplicableMethodNames();
- if (names != null) {
- // not supported in Java visitors; adding a method invocation node is trivial
- // for that case.
- assert names != XmlScanner.ALL;
-
- for (String name : names) {
- List<VisitingDetector> list = mMethodDetectors.get(name);
- if (list == null) {
- list = new ArrayList<VisitingDetector>(SAME_TYPE_COUNT);
- mMethodDetectors.put(name, list);
- }
- list.add(v);
- }
- }
-
- if (detector.appliesToResourceRefs()) {
- mResourceFieldDetectors.add(v);
- } else if ((names == null || names.isEmpty())
- && (nodeTypes == null || nodeTypes.isEmpty())) {
- mFullTreeDetectors.add(v);
- }
- }
- }
-
- void visitFile(@NonNull JavaContext context, @NonNull File file) {
- context.parser = mParser;
-
- Node compilationUnit = null;
- try {
- compilationUnit = mParser.parseJava(context);
- if (compilationUnit == null) {
- // No need to log this; the parser should be reporting
- // a full warning (such as IssueRegistry#PARSER_ERROR)
- // with details, location, etc.
- return;
- }
- context.compilationUnit = compilationUnit;
-
- for (VisitingDetector v : mAllDetectors) {
- v.setContext(context);
- v.getDetector().beforeCheckFile(context);
- }
-
- for (VisitingDetector v : mFullTreeDetectors) {
- AstVisitor visitor = v.getVisitor();
- if (visitor != null) {
- compilationUnit.accept(visitor);
- }
- }
-
- if (!mMethodDetectors.isEmpty() || !mResourceFieldDetectors.isEmpty()) {
- AstVisitor visitor = new DelegatingJavaVisitor(context);
- compilationUnit.accept(visitor);
- } else if (!mNodeTypeDetectors.isEmpty()) {
- AstVisitor visitor = new DispatchVisitor();
- compilationUnit.accept(visitor);
- }
-
- for (VisitingDetector v : mAllDetectors) {
- v.getDetector().afterCheckFile(context);
- }
- } finally {
- if (compilationUnit != null) {
- mParser.dispose(context, compilationUnit);
- }
- }
- }
-
- private static class VisitingDetector {
- private AstVisitor mVisitor; // construct lazily, and clear out on context switch!
- private JavaContext mContext;
- public final Detector mDetector;
- public final JavaScanner mJavaScanner;
-
- public VisitingDetector(@NonNull Detector detector, @NonNull JavaScanner javaScanner) {
- mDetector = detector;
- mJavaScanner = javaScanner;
- }
-
- @NonNull
- public Detector getDetector() {
- return mDetector;
- }
-
- @NonNull
- public JavaScanner getJavaScanner() {
- return mJavaScanner;
- }
-
- public void setContext(@NonNull JavaContext context) {
- mContext = context;
-
- // The visitors are one-per-context, so clear them out here and construct
- // lazily only if needed
- mVisitor = null;
- }
-
- @NonNull
- AstVisitor getVisitor() {
- if (mVisitor == null) {
- mVisitor = mDetector.createJavaVisitor(mContext);
- if (mVisitor == null) {
- mVisitor = new ForwardingAstVisitor() {
- };
- }
- }
- return mVisitor;
- }
- }
-
- /**
- * Generic dispatcher which visits all nodes (once) and dispatches to
- * specific visitors for each node. Each visitor typically only wants to
- * look at a small part of a tree, such as a method call or a class
- * declaration, so this means we avoid visiting all "uninteresting" nodes in
- * the tree repeatedly.
- */
- private class DispatchVisitor extends AstVisitor {
- @Override
- public void endVisit(Node node) {
- for (VisitingDetector v : mAllDetectors) {
- v.getVisitor().endVisit(node);
- }
- }
-
- @Override
- public boolean visitAlternateConstructorInvocation(AlternateConstructorInvocation node) {
- List<VisitingDetector> list =
- mNodeTypeDetectors.get(AlternateConstructorInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAlternateConstructorInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotation(Annotation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Annotation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationDeclaration(AnnotationDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationElement(AnnotationElement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationElement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationElement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationMethodDeclaration(AnnotationMethodDeclaration node) {
- List<VisitingDetector> list =
- mNodeTypeDetectors.get(AnnotationMethodDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationMethodDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAnnotationValueArray(AnnotationValueArray node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(AnnotationValueArray.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAnnotationValueArray(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayAccess(ArrayAccess node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayAccess.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayAccess(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayCreation(ArrayCreation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayCreation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayCreation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayDimension(ArrayDimension node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayDimension.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayDimension(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitArrayInitializer(ArrayInitializer node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ArrayInitializer.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitArrayInitializer(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitAssert(Assert node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Assert.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitAssert(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBinaryExpression(BinaryExpression node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(BinaryExpression.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBinaryExpression(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBlock(Block node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Block.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBlock(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBooleanLiteral(BooleanLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(BooleanLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBooleanLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitBreak(Break node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Break.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitBreak(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCase(Case node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Case.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCase(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCast(Cast node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Cast.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCast(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCatch(Catch node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Catch.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCatch(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCharLiteral(CharLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(CharLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCharLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ClassDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitClassDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitClassLiteral(ClassLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ClassLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitClassLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitComment(Comment node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Comment.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitComment(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitCompilationUnit(CompilationUnit node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(CompilationUnit.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitCompilationUnit(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitConstructorDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ConstructorInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitConstructorInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitContinue(Continue node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Continue.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitContinue(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitDefault(Default node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Default.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitDefault(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitDoWhile(DoWhile node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(DoWhile.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitDoWhile(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEmptyDeclaration(EmptyDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEmptyDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEmptyStatement(EmptyStatement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EmptyStatement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEmptyStatement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEnumConstant(EnumConstant node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EnumConstant.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEnumConstant(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEnumDeclaration(EnumDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EnumDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEnumDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitEnumTypeBody(EnumTypeBody node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(EnumTypeBody.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitEnumTypeBody(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitExpressionStatement(ExpressionStatement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ExpressionStatement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitExpressionStatement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitFloatingPointLiteral(FloatingPointLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(FloatingPointLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitFloatingPointLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitFor(For node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(For.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitFor(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitForEach(ForEach node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ForEach.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitForEach(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitIdentifier(Identifier node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Identifier.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitIdentifier(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitIf(If node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(If.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitIf(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitImportDeclaration(ImportDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(ImportDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitImportDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInlineIfExpression(InlineIfExpression node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InlineIfExpression.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInlineIfExpression(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInstanceInitializer(InstanceInitializer node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceInitializer.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInstanceInitializer(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInstanceOf(InstanceOf node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InstanceOf.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInstanceOf(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitIntegralLiteral(IntegralLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(IntegralLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitIntegralLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitInterfaceDeclaration(InterfaceDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(InterfaceDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitInterfaceDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitKeywordModifier(KeywordModifier node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(KeywordModifier.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitKeywordModifier(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitLabelledStatement(LabelledStatement node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(LabelledStatement.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitLabelledStatement(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(MethodDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitMethodDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(MethodInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitMethodInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitModifiers(Modifiers node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Modifiers.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitModifiers(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitNormalTypeBody(NormalTypeBody node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(NormalTypeBody.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitNormalTypeBody(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitNullLiteral(NullLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(NullLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitNullLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitPackageDeclaration(PackageDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(PackageDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitPackageDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitParseArtefact(Node node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Node.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitParseArtefact(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitReturn(Return node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Return.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitReturn(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSelect(Select node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Select.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSelect(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitStaticInitializer(StaticInitializer node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(StaticInitializer.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitStaticInitializer(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitStringLiteral(StringLiteral node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(StringLiteral.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitStringLiteral(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSuper(Super node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Super.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSuper(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSuperConstructorInvocation(SuperConstructorInvocation node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(SuperConstructorInvocation.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSuperConstructorInvocation(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSwitch(Switch node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Switch.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSwitch(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitSynchronized(Synchronized node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Synchronized.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitSynchronized(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitThis(This node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(This.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitThis(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitThrow(Throw node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Throw.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitThrow(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTry(Try node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(Try.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTry(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTypeReference(TypeReference node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReference.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTypeReference(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTypeReferencePart(TypeReferencePart node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(TypeReferencePart.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTypeReferencePart(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitTypeVariable(TypeVariable node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(TypeVariable.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitTypeVariable(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitUnaryExpression(UnaryExpression node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(UnaryExpression.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitUnaryExpression(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableDeclaration(VariableDeclaration node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDeclaration.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableDeclaration(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableDefinition(VariableDefinition node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinition.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableDefinition(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableDefinitionEntry.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableDefinitionEntry(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitVariableReference(VariableReference node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(VariableReference.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitVariableReference(node);
- }
- }
- return false;
- }
-
- @Override
- public boolean visitWhile(While node) {
- List<VisitingDetector> list = mNodeTypeDetectors.get(While.class);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getVisitor().visitWhile(node);
- }
- }
- return false;
- }
- }
-
- /** Performs common AST searches for method calls and R-type-field references.
- * Note that this is a specialized form of the {@link DispatchVisitor}. */
- private class DelegatingJavaVisitor extends DispatchVisitor {
- private final JavaContext mContext;
- private final boolean mVisitResources;
- private final boolean mVisitMethods;
-
- public DelegatingJavaVisitor(JavaContext context) {
- mContext = context;
-
- mVisitMethods = !mMethodDetectors.isEmpty();
- mVisitResources = !mResourceFieldDetectors.isEmpty();
- }
-
- @Override
- public boolean visitSelect(Select node) {
- if (mVisitResources) {
- // R.type.name
- if (node.astOperand() instanceof Select) {
- Select select = (Select) node.astOperand();
- if (select.astOperand() instanceof VariableReference) {
- VariableReference reference = (VariableReference) select.astOperand();
- if (reference.astIdentifier().astValue().equals(R_CLASS)) {
- String type = select.astIdentifier().astValue();
- String name = node.astIdentifier().astValue();
-
- // R -could- be referenced locally and really have been
- // imported as "import android.R;" in the import statements,
- // but this is not recommended (and in fact there's a specific
- // lint rule warning against it)
- boolean isFramework = false;
-
- for (VisitingDetector v : mResourceFieldDetectors) {
- JavaScanner detector = v.getJavaScanner();
- detector.visitResourceReference(mContext, v.getVisitor(),
- node, type, name, isFramework);
- }
-
- return super.visitSelect(node);
- }
- }
- }
-
- // Arbitrary packages -- android.R.type.name, foo.bar.R.type.name
- if (node.astIdentifier().astValue().equals(R_CLASS)) {
- Node parent = node.getParent();
- if (parent instanceof Select) {
- Node grandParent = parent.getParent();
- if (grandParent instanceof Select) {
- Select select = (Select) grandParent;
- String name = select.astIdentifier().astValue();
- Expression typeOperand = select.astOperand();
- if (typeOperand instanceof Select) {
- Select typeSelect = (Select) typeOperand;
- String type = typeSelect.astIdentifier().astValue();
- boolean isFramework =
- node.astIdentifier().astValue().equals(ANDROID_PKG);
- for (VisitingDetector v : mResourceFieldDetectors) {
- JavaScanner detector = v.getJavaScanner();
- detector.visitResourceReference(mContext, v.getVisitor(),
- node, type, name, isFramework);
- }
- }
- }
- }
- }
- }
-
- return super.visitSelect(node);
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (mVisitMethods) {
- String methodName = node.astName().getDescription();
- List<VisitingDetector> list = mMethodDetectors.get(methodName);
- if (list != null) {
- for (VisitingDetector v : list) {
- v.getJavaScanner().visitMethod(mContext, v.getVisitor(), node);
- }
- }
- }
-
- return super.visitMethodInvocation(node);
- }
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintClient.java
deleted file mode 100644
index b8118b7..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintClient.java
+++ /dev/null
@@ -1,691 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.CLASS_FOLDER;
-import static com.android.SdkConstants.DOT_JAR;
-import static com.android.SdkConstants.GEN_FOLDER;
-import static com.android.SdkConstants.LIBS_FOLDER;
-import static com.android.SdkConstants.RES_FOLDER;
-import static com.android.SdkConstants.SRC_FOLDER;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.SdkManager;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.utils.StdLogger;
-import com.android.utils.StdLogger.Level;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-import org.xml.sax.InputSource;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-/**
- * Information about the tool embedding the lint analyzer. IDEs and other tools
- * implementing lint support will extend this to integrate logging, displaying errors,
- * etc.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class LintClient {
- private static final String PROP_BIN_DIR = "com.android.tools.lint.bindir"; //$NON-NLS-1$
-
- /**
- * Returns a configuration for use by the given project. The configuration
- * provides information about which issues are enabled, any customizations
- * to the severity of an issue, etc.
- * <p>
- * By default this method returns a {@link DefaultConfiguration}.
- *
- * @param project the project to obtain a configuration for
- * @return a configuration, never null.
- */
- public Configuration getConfiguration(@NonNull Project project) {
- return DefaultConfiguration.create(this, project, null);
- }
-
- /**
- * Report the given issue. This method will only be called if the configuration
- * provided by {@link #getConfiguration(Project)} has reported the corresponding
- * issue as enabled and has not filtered out the issue with its
- * {@link Configuration#ignore(Context, Issue, Location, String, Object)} method.
- * <p>
- *
- * @param context the context used by the detector when the issue was found
- * @param issue the issue that was found
- * @param severity the severity of the issue
- * @param location the location of the issue
- * @param message the associated user message
- * @param data optional extra data for a discovered issue, or null. The
- * content depends on the specific issue. Detectors can pass
- * extra info here which automatic fix tools etc can use to
- * extract relevant information instead of relying on parsing the
- * error message text. See each detector for details on which
- * data if any is supplied for a given issue.
- */
- public abstract void report(
- @NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data);
-
- /**
- * Send an exception or error message (with warning severity) to the log
- *
- * @param exception the exception, possibly null
- * @param format the error message using {@link String#format} syntax, possibly null
- * (though in that case the exception should not be null)
- * @param args any arguments for the format string
- */
- public void log(
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args) {
- log(Severity.WARNING, exception, format, args);
- }
-
- /**
- * Send an exception or error message to the log
- *
- * @param severity the severity of the warning
- * @param exception the exception, possibly null
- * @param format the error message using {@link String#format} syntax, possibly null
- * (though in that case the exception should not be null)
- * @param args any arguments for the format string
- */
- public abstract void log(
- @NonNull Severity severity,
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args);
-
- /**
- * Returns a {@link IDomParser} to use to parse XML
- *
- * @return a new {@link IDomParser}, or null if this client does not support
- * XML analysis
- */
- @Nullable
- public abstract IDomParser getDomParser();
-
- /**
- * Returns a {@link IJavaParser} to use to parse Java
- *
- * @return a new {@link IJavaParser}, or null if this client does not
- * support Java analysis
- */
- @Nullable
- public abstract IJavaParser getJavaParser();
-
- /**
- * Returns an optimal detector, if applicable. By default, just returns the
- * original detector, but tools can replace detectors using this hook with a version
- * that takes advantage of native capabilities of the tool.
- *
- * @param detectorClass the class of the detector to be replaced
- * @return the new detector class, or just the original detector (not null)
- */
- @NonNull
- public Class<? extends Detector> replaceDetector(
- @NonNull Class<? extends Detector> detectorClass) {
- return detectorClass;
- }
-
- /**
- * Reads the given text file and returns the content as a string
- *
- * @param file the file to read
- * @return the string to return, never null (will be empty if there is an
- * I/O error)
- */
- @NonNull
- public abstract String readFile(@NonNull File file);
-
- /**
- * Reads the given binary file and returns the content as a byte array.
- * By default this method will read the bytes from the file directly,
- * but this can be customized by a client if for example I/O could be
- * held in memory and not flushed to disk yet.
- *
- * @param file the file to read
- * @return the bytes in the file, never null
- * @throws IOException if the file does not exist, or if the file cannot be
- * read for some reason
- */
- @NonNull
- public byte[] readBytes(@NonNull File file) throws IOException {
- return Files.toByteArray(file);
- }
-
- /**
- * Returns the list of source folders for Java source files
- *
- * @param project the project to look up Java source file locations for
- * @return a list of source folders to search for .java files
- */
- @NonNull
- public List<File> getJavaSourceFolders(@NonNull Project project) {
- return getClassPath(project).getSourceFolders();
- }
-
- /**
- * Returns the list of output folders for class files
- *
- * @param project the project to look up class file locations for
- * @return a list of output folders to search for .class files
- */
- @NonNull
- public List<File> getJavaClassFolders(@NonNull Project project) {
- return getClassPath(project).getClassFolders();
-
- }
-
- /**
- * Returns the list of Java libraries
- *
- * @param project the project to look up jar dependencies for
- * @return a list of jar dependencies containing .class files
- */
- @NonNull
- public List<File> getJavaLibraries(@NonNull Project project) {
- return getClassPath(project).getLibraries();
- }
-
- /**
- * Returns the resource folders.
- *
- * @param project the project to look up the resource folder for
- * @return a list of files pointing to the resource folders, possibly empty
- */
- @NonNull
- public List<File> getResourceFolders(@NonNull Project project) {
- File res = new File(project.getDir(), RES_FOLDER);
- if (res.exists()) {
- return Collections.singletonList(res);
- }
-
- return Collections.emptyList();
- }
-
- /**
- * Returns the {@link SdkInfo} to use for the given project.
- *
- * @param project the project to look up an {@link SdkInfo} for
- * @return an {@link SdkInfo} for the project
- */
- @NonNull
- public SdkInfo getSdkInfo(@NonNull Project project) {
- // By default no per-platform SDK info
- return new DefaultSdkInfo();
- }
-
- /**
- * Returns a suitable location for storing cache files. Note that the
- * directory may not exist.
- *
- * @param create if true, attempt to create the cache dir if it does not
- * exist
- * @return a suitable location for storing cache files, which may be null if
- * the create flag was false, or if for some reason the directory
- * could not be created
- */
- @Nullable
- public File getCacheDir(boolean create) {
- String home = System.getProperty("user.home");
- String relative = ".android" + File.separator + "cache"; //$NON-NLS-1$ //$NON-NLS-2$
- File dir = new File(home, relative);
- if (create && !dir.exists()) {
- if (!dir.mkdirs()) {
- return null;
- }
- }
- return dir;
- }
-
- /**
- * Returns the File corresponding to the system property or the environment variable
- * for {@link #PROP_BIN_DIR}.
- * This property is typically set by the SDK/tools/lint[.bat] wrapper.
- * It denotes the path of the wrapper on disk.
- *
- * @return A new File corresponding to {@link LintClient#PROP_BIN_DIR} or null.
- */
- @Nullable
- private static File getLintBinDir() {
- // First check the Java properties (e.g. set using "java -jar ... -Dname=value")
- String path = System.getProperty(PROP_BIN_DIR);
- if (path == null || path.isEmpty()) {
- // If not found, check environment variables.
- path = System.getenv(PROP_BIN_DIR);
- }
- if (path != null && !path.isEmpty()) {
- return new File(path);
- }
- return null;
- }
-
- /**
- * Returns the File pointing to the user's SDK install area. This is generally
- * the root directory containing the lint tool (but also platforms/ etc).
- *
- * @return a file pointing to the user's install area
- */
- @Nullable
- public File getSdkHome() {
- File binDir = getLintBinDir();
- if (binDir != null) {
- assert binDir.getName().equals("tools");
-
- File root = binDir.getParentFile();
- if (root != null && root.isDirectory()) {
- return root;
- }
- }
-
- String home = System.getenv("ANDROID_HOME"); //$NON-NLS-1$
- if (home != null) {
- return new File(home);
- }
-
- return null;
- }
-
- /**
- * Locates an SDK resource (relative to the SDK root directory).
- * <p>
- * TODO: Consider switching to a {@link URL} return type instead.
- *
- * @param relativePath A relative path (using {@link File#separator} to
- * separate path components) to the given resource
- * @return a {@link File} pointing to the resource, or null if it does not
- * exist
- */
- @Nullable
- public File findResource(@NonNull String relativePath) {
- File top = getSdkHome();
- if (top == null) {
- throw new IllegalArgumentException("Lint must be invoked with the System property "
- + PROP_BIN_DIR + " pointing to the ANDROID_SDK tools directory");
- }
-
- File file = new File(top, relativePath);
- if (file.exists()) {
- return file;
- } else {
- return null;
- }
- }
-
- private Map<Project, ClassPathInfo> mProjectInfo;
-
- /**
- * Information about class paths (sources, class files and libraries)
- * usually associated with a project.
- */
- protected static class ClassPathInfo {
- private final List<File> mClassFolders;
- private final List<File> mSourceFolders;
- private final List<File> mLibraries;
-
- public ClassPathInfo(
- @NonNull List<File> sourceFolders,
- @NonNull List<File> classFolders,
- @NonNull List<File> libraries) {
- mSourceFolders = sourceFolders;
- mClassFolders = classFolders;
- mLibraries = libraries;
- }
-
- @NonNull
- public List<File> getSourceFolders() {
- return mSourceFolders;
- }
-
- @NonNull
- public List<File> getClassFolders() {
- return mClassFolders;
- }
-
- @NonNull
- public List<File> getLibraries() {
- return mLibraries;
- }
- }
-
- /**
- * Considers the given project as an Eclipse project and returns class path
- * information for the project - the source folder(s), the output folder and
- * any libraries.
- * <p>
- * Callers will not cache calls to this method, so if it's expensive to compute
- * the classpath info, this method should perform its own caching.
- *
- * @param project the project to look up class path info for
- * @return a class path info object, never null
- */
- @NonNull
- protected ClassPathInfo getClassPath(@NonNull Project project) {
- ClassPathInfo info;
- if (mProjectInfo == null) {
- mProjectInfo = Maps.newHashMap();
- info = null;
- } else {
- info = mProjectInfo.get(project);
- }
-
- if (info == null) {
- List<File> sources = new ArrayList<File>(2);
- List<File> classes = new ArrayList<File>(1);
- List<File> libraries = new ArrayList<File>();
-
- File projectDir = project.getDir();
- File classpathFile = new File(projectDir, ".classpath"); //$NON-NLS-1$
- if (classpathFile.exists()) {
- String classpathXml = readFile(classpathFile);
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- InputSource is = new InputSource(new StringReader(classpathXml));
- factory.setNamespaceAware(false);
- factory.setValidating(false);
- try {
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.parse(is);
- NodeList tags = document.getElementsByTagName("classpathentry"); //$NON-NLS-1$
- for (int i = 0, n = tags.getLength(); i < n; i++) {
- Element element = (Element) tags.item(i);
- String kind = element.getAttribute("kind"); //$NON-NLS-1$
- List<File> addTo = null;
- if (kind.equals("src")) { //$NON-NLS-1$
- addTo = sources;
- } else if (kind.equals("output")) { //$NON-NLS-1$
- addTo = classes;
- } else if (kind.equals("lib")) { //$NON-NLS-1$
- addTo = libraries;
- }
- if (addTo != null) {
- String path = element.getAttribute("path"); //$NON-NLS-1$
- File folder = new File(projectDir, path);
- if (folder.exists()) {
- addTo.add(folder);
- }
- }
- }
- } catch (Exception e) {
- log(null, null);
- }
- }
-
- // Add in libraries that aren't specified in the .classpath file
- File libs = new File(project.getDir(), LIBS_FOLDER);
- if (libs.isDirectory()) {
- File[] jars = libs.listFiles();
- if (jars != null) {
- for (File jar : jars) {
- if (LintUtils.endsWith(jar.getPath(), DOT_JAR)
- && !libraries.contains(jar)) {
- libraries.add(jar);
- }
- }
- }
- }
-
- if (classes.isEmpty()) {
- File folder = new File(projectDir, CLASS_FOLDER);
- if (folder.exists()) {
- classes.add(folder);
- } else {
- // Maven checks
- folder = new File(projectDir,
- "target" + File.separator + "classes"); //$NON-NLS-1$ //$NON-NLS-2$
- if (folder.exists()) {
- classes.add(folder);
-
- // If it's maven, also correct the source path, "src" works but
- // it's in a more specific subfolder
- if (sources.isEmpty()) {
- File src = new File(projectDir,
- "src" + File.separator //$NON-NLS-1$
- + "main" + File.separator //$NON-NLS-1$
- + "java"); //$NON-NLS-1$
- if (src.exists()) {
- sources.add(src);
- } else {
- src = new File(projectDir, SRC_FOLDER);
- if (src.exists()) {
- sources.add(src);
- }
- }
-
- File gen = new File(projectDir,
- "target" + File.separator //$NON-NLS-1$
- + "generated-sources" + File.separator //$NON-NLS-1$
- + "r"); //$NON-NLS-1$
- if (gen.exists()) {
- sources.add(gen);
- }
- }
- }
- }
- }
-
- // Fallback, in case there is no Eclipse project metadata here
- if (sources.isEmpty()) {
- File src = new File(projectDir, SRC_FOLDER);
- if (src.exists()) {
- sources.add(src);
- }
- File gen = new File(projectDir, GEN_FOLDER);
- if (gen.exists()) {
- sources.add(gen);
- }
- }
-
- info = new ClassPathInfo(sources, classes, libraries);
- mProjectInfo.put(project, info);
- }
-
- return info;
- }
-
- /**
- * A map from directory to existing projects, or null. Used to ensure that
- * projects are unique for a directory (in case we process a library project
- * before its including project for example)
- */
- private Map<File, Project> mDirToProject;
-
- /**
- * Returns a project for the given directory. This should return the same
- * project for the same directory if called repeatedly.
- *
- * @param dir the directory containing the project
- * @param referenceDir See {@link Project#getReferenceDir()}.
- * @return a project, never null
- */
- @NonNull
- public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
- if (mDirToProject == null) {
- mDirToProject = new HashMap<File, Project>();
- }
-
- File canonicalDir = dir;
- try {
- // Attempt to use the canonical handle for the file, in case there
- // are symlinks etc present (since when handling library projects,
- // we also call getCanonicalFile to compute the result of appending
- // relative paths, which can then resolve symlinks and end up with
- // a different prefix)
- canonicalDir = dir.getCanonicalFile();
- } catch (IOException ioe) {
- // pass
- }
-
- Project project = mDirToProject.get(canonicalDir);
- if (project != null) {
- return project;
- }
-
- project = createProject(dir, referenceDir);
- mDirToProject.put(canonicalDir, project);
- return project;
- }
-
- private Set<File> mProjectDirs = Sets.newHashSet();
-
- /**
- * Create a project for the given directory
- * @param dir the root directory of the project
- * @param referenceDir See {@link Project#getReferenceDir()}.
- * @return a new project
- */
- @NonNull
- protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
- if (mProjectDirs.contains(dir)) {
- throw new CircularDependencyException(
- "Circular library dependencies; check your project.properties files carefully");
- }
- mProjectDirs.add(dir);
- return Project.create(this, dir, referenceDir);
- }
-
- /**
- * Returns the name of the given project
- *
- * @param project the project to look up
- * @return the name of the project
- */
- @NonNull
- public String getProjectName(@NonNull Project project) {
- return project.getDir().getName();
- }
-
- private IAndroidTarget[] mTargets;
-
- /**
- * Returns all the {@link IAndroidTarget} versions installed in the user's SDK install
- * area.
- *
- * @return all the installed targets
- */
- @NonNull
- public IAndroidTarget[] getTargets() {
- if (mTargets == null) {
- File sdkHome = getSdkHome();
- if (sdkHome != null) {
- StdLogger log = new StdLogger(Level.WARNING);
- SdkManager manager = SdkManager.createManager(sdkHome.getPath(), log);
- mTargets = manager.getTargets();
- } else {
- mTargets = new IAndroidTarget[0];
- }
- }
-
- return mTargets;
- }
-
- /**
- * Returns the highest known API level.
- *
- * @return the highest known API level
- */
- public int getHighestKnownApiLevel() {
- int max = SdkConstants.HIGHEST_KNOWN_API;
-
- for (IAndroidTarget target : getTargets()) {
- if (target.isPlatform()) {
- int api = target.getVersion().getApiLevel();
- if (api > max && !target.getVersion().isPreview()) {
- max = api;
- }
- }
- }
-
- return max;
- }
-
- /**
- * Returns the super class for the given class name, which should be in VM
- * format (e.g. java/lang/Integer, not java.lang.Integer, and using $ rather
- * than . for inner classes). If the super class is not known, returns null.
- * <p>
- * This is typically not necessary, since lint analyzes all the available
- * classes. However, if this lint client is invoking lint in an incremental
- * context (for example, an IDE offering incremental analysis of a single
- * source file), then lint may not see all the classes, and the client can
- * provide its own super class lookup.
- *
- * @param project the project containing the class
- * @param name the fully qualified class name
- * @return the corresponding super class name (in VM format), or null if not
- * known
- */
- @Nullable
- public String getSuperClass(@NonNull Project project, @NonNull String name) {
- return null;
- }
-
- /**
- * Checks whether the given name is a subclass of the given super class. If
- * the method does not know, it should return null, and otherwise return
- * {@link Boolean#TRUE} or {@link Boolean#FALSE}.
- * <p>
- * Note that the class names are in internal VM format (java/lang/Integer,
- * not java.lang.Integer, and using $ rather than . for inner classes).
- *
- * @param project the project context to look up the class in
- * @param name the name of the class to be checked
- * @param superClassName the name of the super class to compare to
- * @return true if the class of the given name extends the given super class
- */
- @Nullable
- public Boolean isSubclassOf(
- @NonNull Project project,
- @NonNull String name, @NonNull
- String superClassName) {
- return null;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
deleted file mode 100644
index daf2a07..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
+++ /dev/null
@@ -1,2227 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ATTR_IGNORE;
-import static com.android.SdkConstants.DOT_CLASS;
-import static com.android.SdkConstants.DOT_JAR;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
-import static com.android.SdkConstants.OLD_PROGUARD_FILE;
-import static com.android.SdkConstants.RES_FOLDER;
-import static com.android.SdkConstants.SUPPRESS_ALL;
-import static com.android.SdkConstants.SUPPRESS_LINT;
-import static com.android.SdkConstants.TOOLS_URI;
-import static org.objectweb.asm.Opcodes.ASM4;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.resources.ResourceFolderType;
-import com.android.sdklib.IAndroidTarget;
-import com.android.tools.lint.client.api.LintListener.EventType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.annotations.Beta;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Splitter;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.AnnotationNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationValue;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.Expression;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.TypeReference;
-import lombok.ast.VariableDefinition;
-
-/**
- * Analyzes Android projects and files
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class LintDriver {
- /**
- * Max number of passes to run through the lint runner if requested by
- * {@link #requestRepeat}
- */
- private static final int MAX_PHASES = 3;
- private static final String SUPPRESS_LINT_VMSIG = '/' + SUPPRESS_LINT + ';';
-
- private final LintClient mClient;
- private final IssueRegistry mRegistry;
- private volatile boolean mCanceled;
- private EnumSet<Scope> mScope;
- private List<? extends Detector> mApplicableDetectors;
- private Map<Scope, List<Detector>> mScopeDetectors;
- private List<LintListener> mListeners;
- private int mPhase;
- private List<Detector> mRepeatingDetectors;
- private EnumSet<Scope> mRepeatScope;
- private Project[] mCurrentProjects;
- private Project mCurrentProject;
- private boolean mAbbreviating = true;
- private boolean mParserErrors;
- private Map<Object,Object> mProperties;
-
- /**
- * Creates a new {@link LintDriver}
- *
- * @param registry The registry containing issues to be checked
- * @param client the tool wrapping the analyzer, such as an IDE or a CLI
- */
- public LintDriver(@NonNull IssueRegistry registry, @NonNull LintClient client) {
- mRegistry = registry;
- mClient = new LintClientWrapper(client);
- }
-
- /** Cancels the current lint run as soon as possible */
- public void cancel() {
- mCanceled = true;
- }
-
- /**
- * Returns the scope for the lint job
- *
- * @return the scope, never null
- */
- @NonNull
- public EnumSet<Scope> getScope() {
- return mScope;
- }
-
- /**
- * Returns the lint client requesting the lint check
- *
- * @return the client, never null
- */
- @NonNull
- public LintClient getClient() {
- return mClient;
- }
-
- /**
- * Records a property for later retrieval by {@link #getProperty(Object)}
- *
- * @param key the key to associate the value with
- * @param value the value, or null to remove a previous binding
- */
- public void putProperty(@NonNull Object key, @Nullable Object value) {
- if (mProperties == null) {
- mProperties = Maps.newHashMap();
- }
- if (value == null) {
- mProperties.remove(key);
- } else {
- mProperties.put(key, value);
- }
- }
-
- /**
- * Returns the property previously stored with the given key, or null
- *
- * @param key the key
- * @return the value or null if not found
- */
- @Nullable
- public Object getProperty(@NonNull Object key) {
- if (mProperties != null) {
- return mProperties.get(key);
- }
-
- return null;
- }
-
- /**
- * Returns the current phase number. The first pass is numbered 1. Only one pass
- * will be performed, unless a {@link Detector} calls {@link #requestRepeat}.
- *
- * @return the current phase, usually 1
- */
- public int getPhase() {
- return mPhase;
- }
-
- /**
- * Returns the current {@link IssueRegistry}.
- *
- * @return the current {@link IssueRegistry}
- */
- @NonNull
- public IssueRegistry getRegistry() {
- return mRegistry;
- }
-
- /**
- * Returns the project containing a given file, or null if not found. This searches
- * only among the currently checked project and its library projects, not among all
- * possible projects being scanned sequentially.
- *
- * @param file the file to be checked
- * @return the corresponding project, or null if not found
- */
- @Nullable
- public Project findProjectFor(@NonNull File file) {
- if (mCurrentProjects != null) {
- if (mCurrentProjects.length == 1) {
- return mCurrentProjects[0];
- }
- String path = file.getPath();
- for (Project project : mCurrentProjects) {
- if (path.startsWith(project.getDir().getPath())) {
- return project;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Sets whether lint should abbreviate output when appropriate.
- *
- * @param abbreviating true to abbreviate output, false to include everything
- */
- public void setAbbreviating(boolean abbreviating) {
- mAbbreviating = abbreviating;
- }
-
- /**
- * Returns whether lint should abbreviate output when appropriate.
- *
- * @return true if lint should abbreviate output, false when including everything
- */
- public boolean isAbbreviating() {
- return mAbbreviating;
- }
-
- /**
- * Returns whether lint has encountered any files with fatal parser errors
- * (e.g. broken source code, or even broken parsers)
- * <p>
- * This is useful for checks that need to make sure they've seen all data in
- * order to be conclusive (such as an unused resource check).
- *
- * @return true if any files were not properly processed because they
- * contained parser errors
- */
- public boolean hasParserErrors() {
- return mParserErrors;
- }
-
- /**
- * Sets whether lint has encountered files with fatal parser errors.
- *
- * @see #hasParserErrors()
- * @param hasErrors whether parser errors have been encountered
- */
- public void setHasParserErrors(boolean hasErrors) {
- mParserErrors = hasErrors;
- }
-
- /**
- * Returns the projects being analyzed
- *
- * @return the projects being analyzed
- */
- @NonNull
- public List<Project> getProjects() {
- if (mCurrentProjects != null) {
- return Arrays.asList(mCurrentProjects);
- }
- return Collections.emptyList();
- }
-
- /**
- * Analyze the given file (which can point to an Android project). Issues found
- * are reported to the associated {@link LintClient}.
- *
- * @param files the files and directories to be analyzed
- * @param scope the scope of the analysis; detectors with a wider scope will
- * not be run. If null, the scope will be inferred from the files.
- */
- public void analyze(@NonNull List<File> files, @Nullable EnumSet<Scope> scope) {
- mCanceled = false;
- mScope = scope;
-
- Collection<Project> projects;
- try {
- projects = computeProjects(files);
- } catch (CircularDependencyException e) {
- mCurrentProject = e.getProject();
- if (mCurrentProject != null) {
- File file = e.getLocation().getFile();
- Context context = new Context(this, mCurrentProject, null, file);
- context.report(IssueRegistry.LINT_ERROR, e.getLocation(), e.getMessage(), null);
- mCurrentProject = null;
- }
- return;
- }
- if (projects.isEmpty()) {
- mClient.log(null, "No projects found for %1$s", files.toString());
- return;
- }
- if (mCanceled) {
- return;
- }
-
- if (mScope == null) {
- // Infer the scope
- mScope = EnumSet.noneOf(Scope.class);
- for (Project project : projects) {
- List<File> subset = project.getSubset();
- if (subset != null) {
- for (File file : subset) {
- String name = file.getName();
- if (name.equals(ANDROID_MANIFEST_XML)) {
- mScope.add(Scope.MANIFEST);
- } else if (name.endsWith(DOT_XML)) {
- mScope.add(Scope.RESOURCE_FILE);
- } else if (name.equals(RES_FOLDER)
- || file.getParent().equals(RES_FOLDER)) {
- mScope.add(Scope.ALL_RESOURCE_FILES);
- mScope.add(Scope.RESOURCE_FILE);
- } else if (name.endsWith(DOT_JAVA)) {
- mScope.add(Scope.JAVA_FILE);
- } else if (name.endsWith(DOT_CLASS)) {
- mScope.add(Scope.CLASS_FILE);
- } else if (name.equals(OLD_PROGUARD_FILE)
- || name.equals(FN_PROJECT_PROGUARD_FILE)) {
- mScope.add(Scope.PROGUARD_FILE);
- }
- }
- } else {
- // Specified a full project: just use the full project scope
- mScope = Scope.ALL;
- break;
- }
- }
- }
-
- fireEvent(EventType.STARTING, null);
-
- for (Project project : projects) {
- mPhase = 1;
-
- // The set of available detectors varies between projects
- computeDetectors(project);
-
- if (mApplicableDetectors.isEmpty()) {
- // No detectors enabled in this project: skip it
- continue;
- }
-
- checkProject(project);
- if (mCanceled) {
- break;
- }
-
- runExtraPhases(project);
- }
-
- fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null);
- }
-
- private void runExtraPhases(Project project) {
- // Did any detectors request another phase?
- if (mRepeatingDetectors != null) {
- // Yes. Iterate up to MAX_PHASES times.
-
- // During the extra phases, we might be narrowing the scope, and setting it in the
- // scope field such that detectors asking about the available scope will get the
- // correct result. However, we need to restore it to the original scope when this
- // is done in case there are other projects that will be checked after this, since
- // the repeated phases is done *per project*, not after all projects have been
- // processed.
- EnumSet<Scope> oldScope = mScope;
-
- do {
- mPhase++;
- fireEvent(EventType.NEW_PHASE,
- new Context(this, project, null, project.getDir()));
-
- // Narrow the scope down to the set of scopes requested by
- // the rules.
- if (mRepeatScope == null) {
- mRepeatScope = Scope.ALL;
- }
- mScope = Scope.intersect(mScope, mRepeatScope);
- if (mScope.isEmpty()) {
- break;
- }
-
- // Compute the detectors to use for this pass.
- // Unlike the normal computeDetectors(project) call,
- // this is going to use the existing instances, and include
- // those that apply for the configuration.
- computeRepeatingDetectors(mRepeatingDetectors, project);
-
- if (mApplicableDetectors.isEmpty()) {
- // No detectors enabled in this project: skip it
- continue;
- }
-
- checkProject(project);
- if (mCanceled) {
- break;
- }
- } while (mPhase < MAX_PHASES && mRepeatingDetectors != null);
-
- mScope = oldScope;
- }
- }
-
- private void computeRepeatingDetectors(List<Detector> detectors, Project project) {
- // Ensure that the current visitor is recomputed
- mCurrentFolderType = null;
- mCurrentVisitor = null;
-
- // Create map from detector class to issue such that we can
- // compute applicable issues for each detector in the list of detectors
- // to be repeated
- List<Issue> issues = mRegistry.getIssues();
- Multimap<Class<? extends Detector>, Issue> issueMap =
- ArrayListMultimap.create(issues.size(), 3);
- for (Issue issue : issues) {
- issueMap.put(issue.getDetectorClass(), issue);
- }
-
- Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope =
- new HashMap<Class<? extends Detector>, EnumSet<Scope>>();
- Map<Scope, List<Detector>> scopeToDetectors =
- new EnumMap<Scope, List<Detector>>(Scope.class);
-
- List<Detector> detectorList = new ArrayList<Detector>();
- // Compute the list of detectors (narrowed down from mRepeatingDetectors),
- // and simultaneously build up the detectorToScope map which tracks
- // the scopes each detector is affected by (this is used to populate
- // the mScopeDetectors map which is used during iteration).
- Configuration configuration = project.getConfiguration();
- for (Detector detector : detectors) {
- Class<? extends Detector> detectorClass = detector.getClass();
- Collection<Issue> detectorIssues = issueMap.get(detectorClass);
- if (detectorIssues != null) {
- boolean add = false;
- for (Issue issue : detectorIssues) {
- // The reason we have to check whether the detector is enabled
- // is that this is a per-project property, so when running lint in multiple
- // projects, a detector enabled only in a different project could have
- // requested another phase, and we end up in this project checking whether
- // the detector is enabled here.
- if (!configuration.isEnabled(issue)) {
- continue;
- }
-
- add = true; // Include detector if any of its issues are enabled
-
- EnumSet<Scope> s = detectorToScope.get(detectorClass);
- EnumSet<Scope> issueScope = issue.getScope();
- if (s == null) {
- detectorToScope.put(detectorClass, issueScope);
- } else if (!s.containsAll(issueScope)) {
- EnumSet<Scope> union = EnumSet.copyOf(s);
- union.addAll(issueScope);
- detectorToScope.put(detectorClass, union);
- }
- }
-
- if (add) {
- detectorList.add(detector);
- EnumSet<Scope> union = detectorToScope.get(detector.getClass());
- for (Scope s : union) {
- List<Detector> list = scopeToDetectors.get(s);
- if (list == null) {
- list = new ArrayList<Detector>();
- scopeToDetectors.put(s, list);
- }
- list.add(detector);
- }
- }
- }
- }
-
- mApplicableDetectors = detectorList;
- mScopeDetectors = scopeToDetectors;
- mRepeatingDetectors = null;
- mRepeatScope = null;
-
- validateScopeList();
- }
-
- private void computeDetectors(@NonNull Project project) {
- // Ensure that the current visitor is recomputed
- mCurrentFolderType = null;
- mCurrentVisitor = null;
-
- Configuration configuration = project.getConfiguration();
- mScopeDetectors = new EnumMap<Scope, List<Detector>>(Scope.class);
- mApplicableDetectors = mRegistry.createDetectors(mClient, configuration,
- mScope, mScopeDetectors);
-
- validateScopeList();
- }
-
- /** Development diagnostics only, run with assertions on */
- @SuppressWarnings("all") // Turn off warnings for the intentional assertion side effect below
- private void validateScopeList() {
- boolean assertionsEnabled = false;
- assert assertionsEnabled = true; // Intentional side-effect
- if (assertionsEnabled) {
- List<Detector> resourceFileDetectors = mScopeDetectors.get(Scope.RESOURCE_FILE);
- if (resourceFileDetectors != null) {
- for (Detector detector : resourceFileDetectors) {
- assert detector instanceof ResourceXmlDetector : detector;
- }
- }
-
- List<Detector> manifestDetectors = mScopeDetectors.get(Scope.MANIFEST);
- if (manifestDetectors != null) {
- for (Detector detector : manifestDetectors) {
- assert detector instanceof Detector.XmlScanner : detector;
- }
- }
- List<Detector> javaCodeDetectors = mScopeDetectors.get(Scope.ALL_JAVA_FILES);
- if (javaCodeDetectors != null) {
- for (Detector detector : javaCodeDetectors) {
- assert detector instanceof Detector.JavaScanner : detector;
- }
- }
- List<Detector> javaFileDetectors = mScopeDetectors.get(Scope.JAVA_FILE);
- if (javaFileDetectors != null) {
- for (Detector detector : javaFileDetectors) {
- assert detector instanceof Detector.JavaScanner : detector;
- }
- }
-
- List<Detector> classDetectors = mScopeDetectors.get(Scope.CLASS_FILE);
- if (classDetectors != null) {
- for (Detector detector : classDetectors) {
- assert detector instanceof Detector.ClassScanner : detector;
- }
- }
-
- List<Detector> classCodeDetectors = mScopeDetectors.get(Scope.ALL_CLASS_FILES);
- if (classCodeDetectors != null) {
- for (Detector detector : classCodeDetectors) {
- assert detector instanceof Detector.ClassScanner : detector;
- }
- }
-
- List<Detector> otherDetectors = mScopeDetectors.get(Scope.OTHER_SCOPE);
- if (otherDetectors != null) {
- for (Detector detector : otherDetectors) {
- assert detector instanceof Detector.OtherFileScanner : detector;
- }
- }
- }
- }
-
- private void registerProjectFile(
- @NonNull Map<File, Project> fileToProject,
- @NonNull File file,
- @NonNull File projectDir,
- @NonNull File rootDir) {
- fileToProject.put(file, mClient.getProject(projectDir, rootDir));
- }
-
- private Collection<Project> computeProjects(@NonNull List<File> files) {
- // Compute list of projects
- Map<File, Project> fileToProject = new HashMap<File, Project>();
-
- File sharedRoot = null;
-
- // Ensure that we have absolute paths such that if you lint
- // "foo bar" in "baz" we can show baz/ as the root
- if (files.size() > 1) {
- List<File> absolute = new ArrayList<File>(files.size());
- for (File file : files) {
- absolute.add(file.getAbsoluteFile());
- }
- files = absolute;
-
- sharedRoot = LintUtils.getCommonParent(files);
- if (sharedRoot != null && sharedRoot.getParentFile() == null) { // "/" ?
- sharedRoot = null;
- }
- }
-
-
- for (File file : files) {
- if (file.isDirectory()) {
- File rootDir = sharedRoot;
- if (rootDir == null) {
- rootDir = file;
- if (files.size() > 1) {
- rootDir = file.getParentFile();
- if (rootDir == null) {
- rootDir = file;
- }
- }
- }
-
- // Figure out what to do with a directory. Note that the meaning of the
- // directory can be ambiguous:
- // If you pass a directory which is unknown, we don't know if we should
- // search upwards (in case you're pointing at a deep java package folder
- // within the project), or if you're pointing at some top level directory
- // containing lots of projects you want to scan. We attempt to do the
- // right thing, which is to see if you're pointing right at a project or
- // right within it (say at the src/ or res/) folder, and if not, you're
- // hopefully pointing at a project tree that you want to scan recursively.
- if (LintUtils.isProjectDir(file)) {
- registerProjectFile(fileToProject, file, file, rootDir);
- continue;
- } else {
- File parent = file.getParentFile();
- if (parent != null) {
- if (LintUtils.isProjectDir(parent)) {
- registerProjectFile(fileToProject, file, parent, parent);
- continue;
- } else {
- parent = parent.getParentFile();
- if (parent != null && LintUtils.isProjectDir(parent)) {
- registerProjectFile(fileToProject, file, parent, parent);
- continue;
- }
- }
- }
-
- // Search downwards for nested projects
- addProjects(file, fileToProject, rootDir);
- }
- } else {
- // Pointed at a file: Search upwards for the containing project
- File parent = file.getParentFile();
- while (parent != null) {
- if (LintUtils.isProjectDir(parent)) {
- registerProjectFile(fileToProject, file, parent, parent);
- break;
- }
- parent = parent.getParentFile();
- }
- }
-
- if (mCanceled) {
- return Collections.emptySet();
- }
- }
-
- for (Map.Entry<File, Project> entry : fileToProject.entrySet()) {
- File file = entry.getKey();
- Project project = entry.getValue();
- if (!file.equals(project.getDir())) {
- if (file.isDirectory()) {
- try {
- File dir = file.getCanonicalFile();
- if (dir.equals(project.getDir())) {
- continue;
- }
- } catch (IOException ioe) {
- // pass
- }
- }
-
- project.addFile(file);
- }
- }
-
- // Partition the projects up such that we only return projects that aren't
- // included by other projects (e.g. because they are library projects)
-
- Collection<Project> allProjects = fileToProject.values();
- Set<Project> roots = new HashSet<Project>(allProjects);
- for (Project project : allProjects) {
- roots.removeAll(project.getAllLibraries());
- }
-
- // Report issues for all projects that are explicitly referenced. We need to
- // do this here, since the project initialization will mark all library
- // projects as no-report projects by default.
- for (Project project : allProjects) {
- // Report issues for all projects explicitly listed or found via a directory
- // traversal -- including library projects.
- project.setReportIssues(true);
- }
-
- if (LintUtils.assertionsEnabled()) {
- // Make sure that all the project directories are unique. This ensures
- // that we didn't accidentally end up with different project instances
- // for a library project discovered as a directory as well as one
- // initialized from the library project dependency list
- IdentityHashMap<Project, Project> projects =
- new IdentityHashMap<Project, Project>();
- for (Project project : roots) {
- projects.put(project, project);
- for (Project library : project.getAllLibraries()) {
- projects.put(library, library);
- }
- }
- Set<File> dirs = new HashSet<File>();
- for (Project project : projects.keySet()) {
- assert !dirs.contains(project.getDir());
- dirs.add(project.getDir());
- }
- }
-
- return roots;
- }
-
- private void addProjects(
- @NonNull File dir,
- @NonNull Map<File, Project> fileToProject,
- @NonNull File rootDir) {
- if (mCanceled) {
- return;
- }
-
- if (LintUtils.isProjectDir(dir)) {
- registerProjectFile(fileToProject, dir, dir, rootDir);
- } else {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isDirectory()) {
- addProjects(file, fileToProject, rootDir);
- }
- }
- }
- }
- }
-
- private void checkProject(@NonNull Project project) {
- File projectDir = project.getDir();
-
- Context projectContext = new Context(this, project, null, projectDir);
- fireEvent(EventType.SCANNING_PROJECT, projectContext);
-
- List<Project> allLibraries = project.getAllLibraries();
- Set<Project> allProjects = new HashSet<Project>(allLibraries.size() + 1);
- allProjects.add(project);
- allProjects.addAll(allLibraries);
- mCurrentProjects = allProjects.toArray(new Project[allProjects.size()]);
-
- mCurrentProject = project;
-
- for (Detector check : mApplicableDetectors) {
- check.beforeCheckProject(projectContext);
- if (mCanceled) {
- return;
- }
- }
-
- assert mCurrentProject == project;
- runFileDetectors(project, project);
-
- if (!Scope.checkSingleFile(mScope)) {
- List<Project> libraries = project.getDirectLibraries();
- for (Project library : libraries) {
- Context libraryContext = new Context(this, library, project, projectDir);
- fireEvent(EventType.SCANNING_LIBRARY_PROJECT, libraryContext);
- mCurrentProject = library;
-
- for (Detector check : mApplicableDetectors) {
- check.beforeCheckLibraryProject(libraryContext);
- if (mCanceled) {
- return;
- }
- }
- assert mCurrentProject == library;
-
- runFileDetectors(library, project);
- if (mCanceled) {
- return;
- }
-
- assert mCurrentProject == library;
-
- for (Detector check : mApplicableDetectors) {
- check.afterCheckLibraryProject(libraryContext);
- if (mCanceled) {
- return;
- }
- }
- }
- }
-
- mCurrentProject = project;
-
- for (Detector check : mApplicableDetectors) {
- check.afterCheckProject(projectContext);
- if (mCanceled) {
- return;
- }
- }
-
- if (mCanceled) {
- mClient.report(
- projectContext,
- // Must provide an issue since API guarantees that the issue parameter
- // is valid
- Issue.create("Lint", "", "", Category.PERFORMANCE, 0, Severity.INFORMATIONAL, //$NON-NLS-1$
- Detector.class, EnumSet.noneOf(Scope.class)),
- Severity.INFORMATIONAL,
- null /*range*/,
- "Lint canceled by user", null);
- }
-
- mCurrentProjects = null;
- }
-
- private void runFileDetectors(@NonNull Project project, @Nullable Project main) {
- // Look up manifest information (but not for library projects)
- File manifestFile = project.getManifestFile();
- if (manifestFile != null) {
- XmlContext context = new XmlContext(this, project, main, manifestFile, null);
- IDomParser parser = mClient.getDomParser();
- if (parser != null) {
- context.document = parser.parseXml(context);
- if (context.document != null) {
- try {
- project.readManifest(context.document);
-
- if ((!project.isLibrary() || (main != null && main.isMergingManifests()))
- && mScope.contains(Scope.MANIFEST)) {
- List<Detector> detectors = mScopeDetectors.get(Scope.MANIFEST);
- if (detectors != null) {
- XmlVisitor v = new XmlVisitor(parser, detectors);
- fireEvent(EventType.SCANNING_FILE, context);
- v.visitFile(context, manifestFile);
- }
- }
- } finally {
- if (context.document != null) { // else: freed by XmlVisitor above
- parser.dispose(context, context.document);
- }
- }
- }
- }
- }
-
- // Process both Scope.RESOURCE_FILE and Scope.ALL_RESOURCE_FILES detectors together
- // in a single pass through the resource directories.
- if (mScope.contains(Scope.ALL_RESOURCE_FILES) || mScope.contains(Scope.RESOURCE_FILE)) {
- List<Detector> checks = union(mScopeDetectors.get(Scope.RESOURCE_FILE),
- mScopeDetectors.get(Scope.ALL_RESOURCE_FILES));
- if (checks != null && !checks.isEmpty()) {
- List<ResourceXmlDetector> xmlDetectors =
- new ArrayList<ResourceXmlDetector>(checks.size());
- for (Detector detector : checks) {
- if (detector instanceof ResourceXmlDetector) {
- xmlDetectors.add((ResourceXmlDetector) detector);
- }
- }
- if (!xmlDetectors.isEmpty()) {
- List<File> files = project.getSubset();
- if (files != null) {
- checkIndividualResources(project, main, xmlDetectors, files);
- } else {
- List<File> resourceFolders = project.getResourceFolders();
- if (!resourceFolders.isEmpty() && !xmlDetectors.isEmpty()) {
- for (File res : resourceFolders) {
- checkResFolder(project, main, res, xmlDetectors);
- }
- }
- }
- }
- }
- }
-
- if (mCanceled) {
- return;
- }
-
- if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) {
- List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE),
- mScopeDetectors.get(Scope.ALL_JAVA_FILES));
- if (checks != null && !checks.isEmpty()) {
- List<File> files = project.getSubset();
- if (files != null) {
- checkIndividualJavaFiles(project, main, checks, files);
- } else {
- List<File> sourceFolders = project.getJavaSourceFolders();
- checkJava(project, main, sourceFolders, checks);
- }
- }
- }
-
- if (mCanceled) {
- return;
- }
-
- if (mScope.contains(Scope.CLASS_FILE)
- || mScope.contains(Scope.ALL_CLASS_FILES)
- || mScope.contains(Scope.JAVA_LIBRARIES)) {
- checkClasses(project, main);
- }
-
- if (mScope.contains(Scope.OTHER)) {
- List<Detector> checks = mScopeDetectors.get(Scope.OTHER);
- if (checks != null) {
- OtherFileVisitor visitor = new OtherFileVisitor(checks);
- visitor.scan(this, project, main);
- }
- }
-
- if (mCanceled) {
- return;
- }
-
- if (project == main && mScope.contains(Scope.PROGUARD_FILE)) {
- checkProGuard(project, main);
- }
- }
-
- private void checkProGuard(Project project, Project main) {
- List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE);
- if (detectors != null) {
- Project p = main != null ? main : project;
- List<File> files = new ArrayList<File>();
- String paths = p.getProguardPath();
- if (paths != null) {
- Splitter splitter = Splitter.on(CharMatcher.anyOf(":;")); //$NON-NLS-1$
- for (String path : splitter.split(paths)) {
- if (path.contains("${")) { //$NON-NLS-1$
- // Don't analyze the global/user proguard files
- continue;
- }
- File file = new File(path);
- if (!file.isAbsolute()) {
- file = new File(project.getDir(), path);
- }
- if (file.exists()) {
- files.add(file);
- }
- }
- }
- if (files.isEmpty()) {
- File file = new File(project.getDir(), OLD_PROGUARD_FILE);
- if (file.exists()) {
- files.add(file);
- }
- file = new File(project.getDir(), FN_PROJECT_PROGUARD_FILE);
- if (file.exists()) {
- files.add(file);
- }
- }
- for (File file : files) {
- Context context = new Context(this, project, main, file);
- fireEvent(EventType.SCANNING_FILE, context);
- for (Detector detector : detectors) {
- if (detector.appliesTo(context, file)) {
- detector.beforeCheckFile(context);
- detector.run(context);
- detector.afterCheckFile(context);
- }
- }
- }
- }
- }
-
- /** True if execution has been canceled */
- boolean isCanceled() {
- return mCanceled;
- }
-
- /**
- * Map from VM class name to corresponding super class VM name, if available.
- * This map is typically null except <b>during</b> class processing.
- */
- private Map<String, String> mSuperClassMap;
-
- /**
- * Returns the super class for the given class name,
- * which should be in VM format (e.g. java/lang/Integer, not java.lang.Integer).
- * If the super class is not known, returns null. This can happen if
- * the given class is not a known class according to the project or its
- * libraries, for example because it refers to one of the core libraries which
- * are not analyzed by lint.
- *
- * @param name the fully qualified class name
- * @return the corresponding super class name (in VM format), or null if not known
- */
- @Nullable
- public String getSuperClass(@NonNull String name) {
- if (mSuperClassMap == null) {
- throw new IllegalStateException("Only callable during ClassScanner#checkClass");
- }
- assert name.indexOf('.') == -1 : "Use VM signatures, e.g. java/lang/Integer";
-
- String superClass = mSuperClassMap.get(name);
- if (superClass == null && mCurrentProject != null) {
- if ("java/lang/Object".equals(name)) { //$NON-NLS-1$
- return null;
- }
- superClass = mClient.getSuperClass(mCurrentProject, name);
- if (superClass != null) {
- mSuperClassMap.put(name, superClass);
- }
- }
-
- return superClass;
- }
-
- /**
- * Returns true if the given class is a subclass of the given super class.
- *
- * @param classNode the class to check whether it is a subclass of the given
- * super class name
- * @param superClassName the fully qualified super class name (in VM format,
- * e.g. java/lang/Integer, not java.lang.Integer.
- * @return true if the given class is a subclass of the given super class
- */
- public boolean isSubclassOf(@NonNull ClassNode classNode, @NonNull String superClassName) {
- if (superClassName.equals(classNode.superName)) {
- return true;
- }
-
- if (mCurrentProject != null) {
- Boolean isSub = mClient.isSubclassOf(mCurrentProject, classNode.name, superClassName);
- if (isSub != null) {
- return isSub.booleanValue();
- }
- }
-
- String className = classNode.name;
- while (className != null) {
- if (className.equals(superClassName)) {
- return true;
- }
- className = getSuperClass(className);
- }
-
- return false;
- }
- @Nullable
- private static List<Detector> union(
- @Nullable List<Detector> list1,
- @Nullable List<Detector> list2) {
- if (list1 == null) {
- return list2;
- } else if (list2 == null) {
- return list1;
- } else {
- // Use set to pick out unique detectors, since it's possible for there to be overlap,
- // e.g. the DuplicateIdDetector registers both a cross-resource issue and a
- // single-file issue, so it shows up on both scope lists:
- Set<Detector> set = new HashSet<Detector>(list1.size() + list2.size());
- if (list1 != null) {
- set.addAll(list1);
- }
- if (list2 != null) {
- set.addAll(list2);
- }
-
- return new ArrayList<Detector>(set);
- }
- }
-
- /** Check the classes in this project (and if applicable, in any library projects */
- private void checkClasses(Project project, Project main) {
- List<File> files = project.getSubset();
- if (files != null) {
- checkIndividualClassFiles(project, main, files);
- return;
- }
-
- // We need to read in all the classes up front such that we can initialize
- // the parent chains (such that for example for a virtual dispatch, we can
- // also check the super classes).
-
- List<File> libraries = project.getJavaLibraries();
- List<ClassEntry> libraryEntries;
- if (!libraries.isEmpty()) {
- libraryEntries = new ArrayList<ClassEntry>(64);
- findClasses(libraryEntries, libraries);
- Collections.sort(libraryEntries);
- } else {
- libraryEntries = Collections.emptyList();
- }
-
- List<File> classFolders = project.getJavaClassFolders();
- List<ClassEntry> classEntries;
- if (classFolders.isEmpty()) {
- String message = String.format("No .class files were found in project \"%1$s\", "
- + "so none of the classfile based checks could be run. "
- + "Does the project need to be built first?", project.getName());
- Location location = Location.create(project.getDir());
- mClient.report(new Context(this, project, main, project.getDir()),
- IssueRegistry.LINT_ERROR,
- project.getConfiguration().getSeverity(IssueRegistry.LINT_ERROR),
- location, message, null);
- classEntries = Collections.emptyList();
- } else {
- classEntries = new ArrayList<ClassEntry>(64);
- findClasses(classEntries, classFolders);
- Collections.sort(classEntries);
- }
-
- if (getPhase() == 1) {
- mSuperClassMap = getSuperMap(libraryEntries, classEntries);
- }
-
- // Actually run the detectors. Libraries should be called before the
- // main classes.
- runClassDetectors(Scope.JAVA_LIBRARIES, libraryEntries, project, main);
-
- if (mCanceled) {
- return;
- }
-
- runClassDetectors(Scope.CLASS_FILE, classEntries, project, main);
- runClassDetectors(Scope.ALL_CLASS_FILES, classEntries, project, main);
- }
-
- private void checkIndividualClassFiles(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<File> files) {
- List<ClassEntry> entries = new ArrayList<ClassEntry>(files.size());
-
- List<File> classFolders = project.getJavaClassFolders();
- if (!classFolders.isEmpty()) {
- for (File file : files) {
- String path = file.getPath();
- if (file.isFile() && path.endsWith(DOT_CLASS)) {
- try {
- byte[] bytes = mClient.readBytes(file);
- if (bytes != null) {
- for (File dir : classFolders) {
- if (path.startsWith(dir.getPath())) {
- entries.add(new ClassEntry(file, null /* jarFile*/, dir,
- bytes));
- break;
- }
- }
- }
- } catch (IOException e) {
- mClient.log(e, null);
- continue;
- }
-
- if (mCanceled) {
- return;
- }
- }
- }
-
- if (!entries.isEmpty()) {
- Collections.sort(entries);
- // No superclass info available on individual lint runs, unless
- // the client can provide it
- mSuperClassMap = Maps.newHashMap();
- runClassDetectors(Scope.CLASS_FILE, entries, project, main);
- }
- }
- }
-
- /**
- * Stack of {@link ClassNode} nodes for outer classes of the currently
- * processed class, including that class itself. Populated by
- * {@link #runClassDetectors(Scope, List, Project, Project)} and used by
- * {@link #getOuterClassNode(ClassNode)}
- */
- private Deque<ClassNode> mOuterClasses;
-
- private void runClassDetectors(Scope scope, List<ClassEntry> entries,
- Project project, Project main) {
- if (mScope.contains(scope)) {
- List<Detector> classDetectors = mScopeDetectors.get(scope);
- if (classDetectors != null && !classDetectors.isEmpty() && !entries.isEmpty()) {
- AsmVisitor visitor = new AsmVisitor(mClient, classDetectors);
-
- String sourceContents = null;
- String sourceName = "";
- mOuterClasses = new ArrayDeque<ClassNode>();
- for (ClassEntry entry : entries) {
- ClassReader reader;
- ClassNode classNode;
- try {
- reader = new ClassReader(entry.bytes);
- classNode = new ClassNode();
- reader.accept(classNode, 0 /* flags */);
- } catch (Throwable t) {
- mClient.log(null, "Error processing %1$s: broken class file?",
- entry.path());
- continue;
- }
-
- ClassNode peek;
- while ((peek = mOuterClasses.peek()) != null) {
- if (classNode.name.startsWith(peek.name)) {
- break;
- } else {
- mOuterClasses.pop();
- }
- }
- mOuterClasses.push(classNode);
-
- if (isSuppressed(null, classNode)) {
- // Class was annotated with suppress all -- no need to look any further
- continue;
- }
-
- if (sourceContents != null) {
- // Attempt to reuse the source buffer if initialized
- // This means making sure that the source files
- // foo/bar/MyClass and foo/bar/MyClass$Bar
- // and foo/bar/MyClass$3 and foo/bar/MyClass$3$1 have the same prefix.
- String newName = classNode.name;
- int newRootLength = newName.indexOf('$');
- if (newRootLength == -1) {
- newRootLength = newName.length();
- }
- int oldRootLength = sourceName.indexOf('$');
- if (oldRootLength == -1) {
- oldRootLength = sourceName.length();
- }
- if (newRootLength != oldRootLength ||
- !sourceName.regionMatches(0, newName, 0, newRootLength)) {
- sourceContents = null;
- }
- }
-
- ClassContext context = new ClassContext(this, project, main,
- entry.file, entry.jarFile, entry.binDir, entry.bytes,
- classNode, scope == Scope.JAVA_LIBRARIES /*fromLibrary*/,
- sourceContents);
-
- try {
- visitor.runClassDetectors(context);
- } catch (Exception e) {
- mClient.log(e, null);
- }
-
- if (mCanceled) {
- return;
- }
-
- sourceContents = context.getSourceContents(false/*read*/);
- sourceName = classNode.name;
- }
-
- mOuterClasses = null;
- }
- }
- }
-
- /** Returns the outer class node of the given class node
- * @param classNode the inner class node
- * @return the outer class node */
- public ClassNode getOuterClassNode(@NonNull ClassNode classNode) {
- String outerName = classNode.outerClass;
-
- Iterator<ClassNode> iterator = mOuterClasses.iterator();
- while (iterator.hasNext()) {
- ClassNode node = iterator.next();
- if (outerName != null) {
- if (node.name.equals(outerName)) {
- return node;
- }
- } else if (node == classNode) {
- return iterator.hasNext() ? iterator.next() : null;
- }
- }
-
- return null;
- }
-
- private Map<String, String> getSuperMap(List<ClassEntry> libraryEntries,
- List<ClassEntry> classEntries) {
- int size = libraryEntries.size() + classEntries.size();
- Map<String, String> map = new HashMap<String, String>(size);
-
- SuperclassVisitor visitor = new SuperclassVisitor(map);
- addSuperClasses(visitor, libraryEntries);
- addSuperClasses(visitor, classEntries);
-
- return map;
- }
-
- private void addSuperClasses(SuperclassVisitor visitor, List<ClassEntry> entries) {
- for (ClassEntry entry : entries) {
- try {
- ClassReader reader = new ClassReader(entry.bytes);
- int flags = ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
- | ClassReader.SKIP_FRAMES;
- reader.accept(visitor, flags);
- } catch (Throwable t) {
- mClient.log(null, "Error processing %1$s: broken class file?", entry.path());
- }
- }
- }
-
- /** Visitor skimming classes and initializing a map of super classes */
- private static class SuperclassVisitor extends ClassVisitor {
- private final Map<String, String> mMap;
-
- public SuperclassVisitor(Map<String, String> map) {
- super(ASM4);
- mMap = map;
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- if (superName != null) {
- mMap.put(name, superName);
- }
- }
- }
-
- private void findClasses(
- @NonNull List<ClassEntry> entries,
- @NonNull List<File> classPath) {
- for (File classPathEntry : classPath) {
- if (classPathEntry.getName().endsWith(DOT_JAR)) {
- File jarFile = classPathEntry;
- if (!jarFile.exists()) {
- continue;
- }
- ZipInputStream zis = null;
- try {
- FileInputStream fis = new FileInputStream(jarFile);
- zis = new ZipInputStream(fis);
- ZipEntry entry = zis.getNextEntry();
- while (entry != null) {
- String name = entry.getName();
- if (name.endsWith(DOT_CLASS)) {
- try {
- byte[] bytes = ByteStreams.toByteArray(zis);
- if (bytes != null) {
- File file = new File(entry.getName());
- entries.add(new ClassEntry(file, jarFile, jarFile, bytes));
- }
- } catch (Exception e) {
- mClient.log(e, null);
- continue;
- }
- }
-
- if (mCanceled) {
- return;
- }
-
- entry = zis.getNextEntry();
- }
- } catch (IOException e) {
- mClient.log(e, "Could not read jar file contents from %1$s", jarFile);
- } finally {
- Closeables.closeQuietly(zis);
- }
-
- continue;
- } else if (classPathEntry.isDirectory()) {
- File binDir = classPathEntry;
- List<File> classFiles = new ArrayList<File>();
- addClassFiles(binDir, classFiles);
-
- for (File file : classFiles) {
- try {
- byte[] bytes = mClient.readBytes(file);
- if (bytes != null) {
- entries.add(new ClassEntry(file, null /* jarFile*/, binDir, bytes));
- }
- } catch (IOException e) {
- mClient.log(e, null);
- continue;
- }
-
- if (mCanceled) {
- return;
- }
- }
- } else {
- mClient.log(null, "Ignoring class path entry %1$s", classPathEntry);
- }
- }
- }
-
- private static void addClassFiles(@NonNull File dir, @NonNull List<File> classFiles) {
- // Process the resource folder
- File[] files = dir.listFiles();
- if (files != null && files.length > 0) {
- for (File file : files) {
- if (file.isFile() && file.getName().endsWith(DOT_CLASS)) {
- classFiles.add(file);
- } else if (file.isDirectory()) {
- // Recurse
- addClassFiles(file, classFiles);
- }
- }
- }
- }
-
- private void checkJava(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<File> sourceFolders,
- @NonNull List<Detector> checks) {
- IJavaParser javaParser = mClient.getJavaParser();
- if (javaParser == null) {
- mClient.log(null, "No java parser provided to lint: not running Java checks");
- return;
- }
-
- assert !checks.isEmpty();
-
- // Gather all Java source files in a single pass; more efficient.
- List<File> sources = new ArrayList<File>(100);
- for (File folder : sourceFolders) {
- gatherJavaFiles(folder, sources);
- }
- if (!sources.isEmpty()) {
- JavaVisitor visitor = new JavaVisitor(javaParser, checks);
- for (File file : sources) {
- JavaContext context = new JavaContext(this, project, main, file);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- if (mCanceled) {
- return;
- }
- }
- }
- }
-
- private void checkIndividualJavaFiles(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<Detector> checks,
- @NonNull List<File> files) {
-
- IJavaParser javaParser = mClient.getJavaParser();
- if (javaParser == null) {
- mClient.log(null, "No java parser provided to lint: not running Java checks");
- return;
- }
-
- JavaVisitor visitor = new JavaVisitor(javaParser, checks);
-
- for (File file : files) {
- if (file.isFile() && file.getPath().endsWith(DOT_JAVA)) {
- JavaContext context = new JavaContext(this, project, main, file);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- if (mCanceled) {
- return;
- }
- }
- }
- }
-
- private static void gatherJavaFiles(@NonNull File dir, @NonNull List<File> result) {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File file : files) {
- if (file.isFile() && file.getName().endsWith(".java")) { //$NON-NLS-1$
- result.add(file);
- } else if (file.isDirectory()) {
- gatherJavaFiles(file, result);
- }
- }
- }
- }
-
- private ResourceFolderType mCurrentFolderType;
- private List<ResourceXmlDetector> mCurrentXmlDetectors;
- private XmlVisitor mCurrentVisitor;
-
- @Nullable
- private XmlVisitor getVisitor(
- @NonNull ResourceFolderType type,
- @NonNull List<ResourceXmlDetector> checks) {
- if (type != mCurrentFolderType) {
- mCurrentFolderType = type;
-
- // Determine which XML resource detectors apply to the given folder type
- List<ResourceXmlDetector> applicableChecks =
- new ArrayList<ResourceXmlDetector>(checks.size());
- for (ResourceXmlDetector check : checks) {
- if (check.appliesTo(type)) {
- applicableChecks.add(check);
- }
- }
-
- // If the list of detectors hasn't changed, then just use the current visitor!
- if (mCurrentXmlDetectors != null && mCurrentXmlDetectors.equals(applicableChecks)) {
- return mCurrentVisitor;
- }
-
- if (applicableChecks.isEmpty()) {
- mCurrentVisitor = null;
- return null;
- }
-
- IDomParser parser = mClient.getDomParser();
- if (parser != null) {
- mCurrentVisitor = new XmlVisitor(parser, applicableChecks);
- }
- }
-
- return mCurrentVisitor;
- }
-
- private void checkResFolder(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File res,
- @NonNull List<ResourceXmlDetector> checks) {
- assert res.isDirectory();
- File[] resourceDirs = res.listFiles();
- if (resourceDirs == null) {
- return;
- }
-
- // Sort alphabetically such that we can process related folder types at the
- // same time
-
- Arrays.sort(resourceDirs);
- for (File dir : resourceDirs) {
- ResourceFolderType type = ResourceFolderType.getFolderType(dir.getName());
- if (type != null) {
- checkResourceFolder(project, main, dir, type, checks);
- }
-
- if (mCanceled) {
- return;
- }
- }
- }
-
- private void checkResourceFolder(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File dir,
- @NonNull ResourceFolderType type,
- @NonNull List<ResourceXmlDetector> checks) {
- // Process the resource folder
- File[] xmlFiles = dir.listFiles();
- if (xmlFiles != null && xmlFiles.length > 0) {
- XmlVisitor visitor = getVisitor(type, checks);
- if (visitor != null) { // if not, there are no applicable rules in this folder
- for (File file : xmlFiles) {
- if (LintUtils.isXmlFile(file)) {
- XmlContext context = new XmlContext(this, project, main, file, type);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- if (mCanceled) {
- return;
- }
- }
- }
- }
- }
- }
-
- /** Checks individual resources */
- private void checkIndividualResources(
- @NonNull Project project,
- @Nullable Project main,
- @NonNull List<ResourceXmlDetector> xmlDetectors,
- @NonNull List<File> files) {
- for (File file : files) {
- if (file.isDirectory()) {
- // Is it a resource folder?
- ResourceFolderType type = ResourceFolderType.getFolderType(file.getName());
- if (type != null && new File(file.getParentFile(), RES_FOLDER).exists()) {
- // Yes.
- checkResourceFolder(project, main, file, type, xmlDetectors);
- } else if (file.getName().equals(RES_FOLDER)) { // Is it the res folder?
- // Yes
- checkResFolder(project, main, file, xmlDetectors);
- } else {
- mClient.log(null, "Unexpected folder %1$s; should be project, " +
- "\"res\" folder or resource folder", file.getPath());
- continue;
- }
- } else if (file.isFile() && LintUtils.isXmlFile(file)) {
- // Yes, find out its resource type
- String folderName = file.getParentFile().getName();
- ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
- if (type != null) {
- XmlVisitor visitor = getVisitor(type, xmlDetectors);
- if (visitor != null) {
- XmlContext context = new XmlContext(this, project, main, file, type);
- fireEvent(EventType.SCANNING_FILE, context);
- visitor.visitFile(context, file);
- }
- }
- }
- }
- }
-
- /**
- * Adds a listener to be notified of lint progress
- *
- * @param listener the listener to be added
- */
- public void addLintListener(@NonNull LintListener listener) {
- if (mListeners == null) {
- mListeners = new ArrayList<LintListener>(1);
- }
- mListeners.add(listener);
- }
-
- /**
- * Removes a listener such that it is no longer notified of progress
- *
- * @param listener the listener to be removed
- */
- public void removeLintListener(@NonNull LintListener listener) {
- mListeners.remove(listener);
- if (mListeners.isEmpty()) {
- mListeners = null;
- }
- }
-
- /** Notifies listeners, if any, that the given event has occurred */
- private void fireEvent(@NonNull LintListener.EventType type, @Nullable Context context) {
- if (mListeners != null) {
- for (LintListener listener : mListeners) {
- listener.update(this, type, context);
- }
- }
- }
-
- /**
- * Wrapper around the lint client. This sits in the middle between a
- * detector calling for example {@link LintClient#report} and
- * the actual embedding tool, and performs filtering etc such that detectors
- * and lint clients don't have to make sure they check for ignored issues or
- * filtered out warnings.
- */
- private class LintClientWrapper extends LintClient {
- @NonNull
- private final LintClient mDelegate;
-
- public LintClientWrapper(@NonNull LintClient delegate) {
- mDelegate = delegate;
- }
-
- @Override
- public void report(
- @NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- assert mCurrentProject != null;
- if (!mCurrentProject.getReportIssues()) {
- return;
- }
-
- Configuration configuration = context.getConfiguration();
- if (!configuration.isEnabled(issue)) {
- if (issue != IssueRegistry.PARSER_ERROR && issue != IssueRegistry.LINT_ERROR) {
- mDelegate.log(null, "Incorrect detector reported disabled issue %1$s",
- issue.toString());
- }
- return;
- }
-
- if (configuration.isIgnored(context, issue, location, message, data)) {
- return;
- }
-
- if (severity == Severity.IGNORE) {
- return;
- }
-
- mDelegate.report(context, issue, severity, location, message, data);
- }
-
- // Everything else just delegates to the embedding lint client
-
- @Override
- @NonNull
- public Configuration getConfiguration(@NonNull Project project) {
- return mDelegate.getConfiguration(project);
- }
-
-
- @Override
- public void log(@NonNull Severity severity, @Nullable Throwable exception,
- @Nullable String format, @Nullable Object... args) {
- mDelegate.log(exception, format, args);
- }
-
- @Override
- @NonNull
- public String readFile(@NonNull File file) {
- return mDelegate.readFile(file);
- }
-
- @Override
- @NonNull
- public byte[] readBytes(@NonNull File file) throws IOException {
- return mDelegate.readBytes(file);
- }
-
- @Override
- @NonNull
- public List<File> getJavaSourceFolders(@NonNull Project project) {
- return mDelegate.getJavaSourceFolders(project);
- }
-
- @Override
- @NonNull
- public List<File> getJavaClassFolders(@NonNull Project project) {
- return mDelegate.getJavaClassFolders(project);
- }
-
- @NonNull
- @Override
- public List<File> getJavaLibraries(@NonNull Project project) {
- return mDelegate.getJavaLibraries(project);
- }
-
- @Override
- @NonNull
- public List<File> getResourceFolders(@NonNull Project project) {
- return mDelegate.getResourceFolders(project);
- }
-
- @Override
- @Nullable
- public IDomParser getDomParser() {
- return mDelegate.getDomParser();
- }
-
- @Override
- @NonNull
- public Class<? extends Detector> replaceDetector(
- @NonNull Class<? extends Detector> detectorClass) {
- return mDelegate.replaceDetector(detectorClass);
- }
-
- @Override
- @NonNull
- public SdkInfo getSdkInfo(@NonNull Project project) {
- return mDelegate.getSdkInfo(project);
- }
-
- @Override
- @NonNull
- public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
- return mDelegate.getProject(dir, referenceDir);
- }
-
- @Override
- @Nullable
- public IJavaParser getJavaParser() {
- return mDelegate.getJavaParser();
- }
-
- @Override
- public File findResource(@NonNull String relativePath) {
- return mDelegate.findResource(relativePath);
- }
-
- @Override
- @Nullable
- public File getCacheDir(boolean create) {
- return mDelegate.getCacheDir(create);
- }
-
- @Override
- @NonNull
- protected ClassPathInfo getClassPath(@NonNull Project project) {
- return mDelegate.getClassPath(project);
- }
-
- @Override
- public void log(@Nullable Throwable exception, @Nullable String format,
- @Nullable Object... args) {
- mDelegate.log(exception, format, args);
- }
-
- @Override
- @Nullable
- public File getSdkHome() {
- return mDelegate.getSdkHome();
- }
-
- @Override
- @NonNull
- public IAndroidTarget[] getTargets() {
- return mDelegate.getTargets();
- }
-
- @Override
- public int getHighestKnownApiLevel() {
- return mDelegate.getHighestKnownApiLevel();
- }
-
- @Override
- @Nullable
- public String getSuperClass(@NonNull Project project, @NonNull String name) {
- return mDelegate.getSuperClass(project, name);
- }
-
- @Override
- @Nullable
- public Boolean isSubclassOf(@NonNull Project project, @NonNull String name,
- @NonNull String superClassName) {
- return mDelegate.isSubclassOf(project, name, superClassName);
- }
-
- @Override
- @NonNull
- public String getProjectName(@NonNull Project project) {
- return mDelegate.getProjectName(project);
- }
- }
-
- /**
- * Requests another pass through the data for the given detector. This is
- * typically done when a detector needs to do more expensive computation,
- * but it only wants to do this once it <b>knows</b> that an error is
- * present, or once it knows more specifically what to check for.
- *
- * @param detector the detector that should be included in the next pass.
- * Note that the lint runner may refuse to run more than a couple
- * of runs.
- * @param scope the scope to be revisited. This must be a subset of the
- * current scope ({@link #getScope()}, and it is just a performance hint;
- * in particular, the detector should be prepared to be called on other
- * scopes as well (since they may have been requested by other detectors).
- * You can pall null to indicate "all".
- */
- public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) {
- if (mRepeatingDetectors == null) {
- mRepeatingDetectors = new ArrayList<Detector>();
- }
- mRepeatingDetectors.add(detector);
-
- if (scope != null) {
- if (mRepeatScope == null) {
- mRepeatScope = scope;
- } else {
- mRepeatScope = EnumSet.copyOf(mRepeatScope);
- mRepeatScope.addAll(scope);
- }
- } else {
- mRepeatScope = Scope.ALL;
- }
- }
-
- // Unfortunately, ASMs nodes do not extend a common DOM node type with parent
- // pointers, so we have to have multiple methods which pass in each type
- // of node (class, method, field) to be checked.
-
- /**
- * Returns whether the given issue is suppressed in the given method.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param classNode the class containing the issue
- * @param method the method containing the issue
- * @param instruction the instruction within the method, if any
- * @return true if there is a suppress annotation covering the specific
- * issue on this method
- */
- public boolean isSuppressed(
- @Nullable Issue issue,
- @NonNull ClassNode classNode,
- @NonNull MethodNode method,
- @Nullable AbstractInsnNode instruction) {
- if (method.invisibleAnnotations != null) {
- @SuppressWarnings("unchecked")
- List<AnnotationNode> annotations = method.invisibleAnnotations;
- return isSuppressed(issue, annotations);
- }
-
- // Initializations of fields end up placed in generated methods (<init>
- // for members and <clinit> for static fields).
- if (instruction != null && method.name.charAt(0) == '<') {
- AbstractInsnNode next = LintUtils.getNextInstruction(instruction);
- if (next != null && next.getType() == AbstractInsnNode.FIELD_INSN) {
- FieldInsnNode fieldRef = (FieldInsnNode) next;
- FieldNode field = findField(classNode, fieldRef.owner, fieldRef.name);
- if (field != null && isSuppressed(issue, field)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- @Nullable
- private FieldNode findField(ClassNode classNode, String owner, String name) {
- while (classNode != null) {
- if (owner.equals(classNode.name)) {
- @SuppressWarnings("rawtypes") // ASM API
- List fieldList = classNode.fields;
- for (Object f : fieldList) {
- FieldNode field = (FieldNode) f;
- if (field.name.equals(name)) {
- return field;
- }
- }
- return null;
- }
- classNode = getOuterClassNode(classNode);
- }
- return null;
- }
-
- /**
- * Returns whether the given issue is suppressed for the given field.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param field the field potentially annotated with a suppress annotation
- * @return true if there is a suppress annotation covering the specific
- * issue on this field
- */
- public boolean isSuppressed(@Nullable Issue issue, @NonNull FieldNode field) {
- if (field.invisibleAnnotations != null) {
- @SuppressWarnings("unchecked")
- List<AnnotationNode> annotations = field.invisibleAnnotations;
- return isSuppressed(issue, annotations);
- }
-
- return false;
- }
-
- /**
- * Returns whether the given issue is suppressed in the given class.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param classNode the class containing the issue
- * @return true if there is a suppress annotation covering the specific
- * issue in this class
- */
- public boolean isSuppressed(@Nullable Issue issue, @NonNull ClassNode classNode) {
- if (classNode.invisibleAnnotations != null) {
- @SuppressWarnings("unchecked")
- List<AnnotationNode> annotations = classNode.invisibleAnnotations;
- return isSuppressed(issue, annotations);
- }
-
- return false;
- }
-
- private static boolean isSuppressed(@Nullable Issue issue, List<AnnotationNode> annotations) {
- for (AnnotationNode annotation : annotations) {
- String desc = annotation.desc;
-
- // We could obey @SuppressWarnings("all") too, but no need to look for it
- // because that annotation only has source retention.
-
- if (desc.endsWith(SUPPRESS_LINT_VMSIG)) {
- if (annotation.values != null) {
- for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
- String key = (String) annotation.values.get(i);
- if (key.equals("value")) { //$NON-NLS-1$
- Object value = annotation.values.get(i + 1);
- if (value instanceof String) {
- String id = (String) value;
- if (id.equalsIgnoreCase(SUPPRESS_ALL) ||
- issue != null && id.equalsIgnoreCase(issue.getId())) {
- return true;
- }
- } else if (value instanceof List) {
- @SuppressWarnings("rawtypes")
- List list = (List) value;
- for (Object v : list) {
- if (v instanceof String) {
- String id = (String) v;
- if (id.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && id.equalsIgnoreCase(issue.getId()))) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns whether the given issue is suppressed in the given parse tree node.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param scope the AST node containing the issue
- * @return true if there is a suppress annotation covering the specific
- * issue in this class
- */
- public boolean isSuppressed(@NonNull Issue issue, @Nullable Node scope) {
- while (scope != null) {
- Class<? extends Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == VariableDefinition.class) {
- // Variable
- VariableDefinition declaration = (VariableDefinition) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- } else if (type == MethodDeclaration.class) {
- // Method
- // Look for annotations on the method
- MethodDeclaration declaration = (MethodDeclaration) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- } else if (type == ConstructorDeclaration.class) {
- // Constructor
- // Look for annotations on the method
- ConstructorDeclaration declaration = (ConstructorDeclaration) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- } else if (type == ClassDeclaration.class) {
- // Class
- ClassDeclaration declaration = (ClassDeclaration) scope;
- if (isSuppressed(issue, declaration.astModifiers())) {
- return true;
- }
- }
-
- scope = scope.getParent();
- }
-
- return false;
- }
-
- /**
- * Returns true if the given AST modifier has a suppress annotation for the
- * given issue (which can be null to check for the "all" annotation)
- *
- * @param issue the issue to be checked
- * @param modifiers the modifier to check
- * @return true if the issue or all issues should be suppressed for this
- * modifier
- */
- private static boolean isSuppressed(@Nullable Issue issue, @Nullable Modifiers modifiers) {
- if (modifiers == null) {
- return false;
- }
- StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations();
- if (annotations == null) {
- return false;
- }
-
- Iterator<Annotation> iterator = annotations.iterator();
- while (iterator.hasNext()) {
- Annotation annotation = iterator.next();
- TypeReference t = annotation.astAnnotationTypeReference();
- String typeName = t.getTypeName();
- if (typeName.endsWith(SUPPRESS_LINT)
- || typeName.endsWith("SuppressWarnings")) { //$NON-NLS-1$
- StrictListAccessor<AnnotationElement, Annotation> values =
- annotation.astElements();
- if (values != null) {
- Iterator<AnnotationElement> valueIterator = values.iterator();
- while (valueIterator.hasNext()) {
- AnnotationElement element = valueIterator.next();
- AnnotationValue valueNode = element.astValue();
- if (valueNode == null) {
- continue;
- }
- if (valueNode instanceof StringLiteral) {
- StringLiteral literal = (StringLiteral) valueNode;
- String value = literal.astValue();
- if (value.equalsIgnoreCase(SUPPRESS_ALL) ||
- issue != null && issue.getId().equalsIgnoreCase(value)) {
- return true;
- }
- } else if (valueNode instanceof ArrayInitializer) {
- ArrayInitializer array = (ArrayInitializer) valueNode;
- StrictListAccessor<Expression, ArrayInitializer> expressions =
- array.astExpressions();
- if (expressions == null) {
- continue;
- }
- Iterator<Expression> arrayIterator = expressions.iterator();
- while (arrayIterator.hasNext()) {
- Expression arrayElement = arrayIterator.next();
- if (arrayElement instanceof StringLiteral) {
- String value = ((StringLiteral) arrayElement).astValue();
- if (value.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && issue.getId().equalsIgnoreCase(value))) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns whether the given issue is suppressed in the given XML DOM node.
- *
- * @param issue the issue to be checked, or null to just check for "all"
- * @param node the DOM node containing the issue
- * @return true if there is a suppress annotation covering the specific
- * issue in this class
- */
- public boolean isSuppressed(@NonNull Issue issue, @Nullable org.w3c.dom.Node node) {
- if (node instanceof Attr) {
- node = ((Attr) node).getOwnerElement();
- }
- while (node != null) {
- if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
- Element element = (Element) node;
- if (element.hasAttributeNS(TOOLS_URI, ATTR_IGNORE)) {
- String ignore = element.getAttributeNS(TOOLS_URI, ATTR_IGNORE);
- if (ignore.indexOf(',') == -1) {
- if (ignore.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && issue.getId().equalsIgnoreCase(ignore))) {
- return true;
- }
- } else {
- for (String id : ignore.split(",")) { //$NON-NLS-1$
- if (id.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null
- && issue.getId().equalsIgnoreCase(id))) {
- return true;
- }
- }
- }
- }
- }
-
- node = node.getParentNode();
- }
-
- return false;
- }
-
- /** A pending class to be analyzed by {@link #checkClasses} */
- @VisibleForTesting
- static class ClassEntry implements Comparable<ClassEntry> {
- public final File file;
- public final File jarFile;
- public final File binDir;
- public final byte[] bytes;
-
- public ClassEntry(File file, File jarFile, File binDir, byte[] bytes) {
- super();
- this.file = file;
- this.jarFile = jarFile;
- this.binDir = binDir;
- this.bytes = bytes;
- }
-
- public String path() {
- if (jarFile != null) {
- return jarFile.getPath() + ':' + file.getPath();
- } else {
- return file.getPath();
- }
- }
-
- @Override
- public int compareTo(ClassEntry other) {
- String p1 = file.getPath();
- String p2 = other.file.getPath();
- int m1 = p1.length();
- int m2 = p2.length();
- int m = Math.min(m1, m2);
-
- for (int i = 0; i < m; i++) {
- char c1 = p1.charAt(i);
- char c2 = p2.charAt(i);
- if (c1 != c2) {
- // Sort Foo$Bar.class *after* Foo.class, even though $ < .
- if (c1 == '.' && c2 == '$') {
- return -1;
- }
- if (c1 == '$' && c2 == '.') {
- return 1;
- }
- return c1 - c2;
- }
- }
-
- return (m == m1) ? -1 : 1;
- }
-
- @Override
- public String toString() {
- return file.getPath();
- }
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintListener.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintListener.java
deleted file mode 100644
index 8195d36..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/LintListener.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.google.common.annotations.Beta;
-
-/**
- * Interface implemented by listeners to be notified of lint events
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public interface LintListener {
- /** The various types of events provided to lint listeners */
- enum EventType {
- /** A lint check is about to begin */
- STARTING,
-
- /** Lint is about to check the given project, see {@link Context#getProject()} */
- SCANNING_PROJECT,
-
- /** Lint is about to check the given library project, see {@link Context#getProject()} */
- SCANNING_LIBRARY_PROJECT,
-
- /** Lint is about to check the given file, see {@link Context#file} */
- SCANNING_FILE,
-
- /** A new pass was initiated */
- NEW_PHASE,
-
- /** The lint check was canceled */
- CANCELED,
-
- /** The lint check is done */
- COMPLETED,
- }
-
- /**
- * Notifies listeners that the event of the given type has occurred.
- * Additional information, such as the file being scanned, or the project
- * being scanned, is available in the {@link Context} object (except for the
- * {@link EventType#STARTING}, {@link EventType#CANCELED} or
- * {@link EventType#COMPLETED} events which are fired outside of project
- * contexts.)
- *
- * @param driver the driver running through the checks
- * @param type the type of event that occurred
- * @param context the context providing additional information
- */
- void update(@NonNull LintDriver driver, @NonNull EventType type,
- @Nullable Context context);
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
deleted file mode 100644
index 573d4f0..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.DOT_CLASS;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.FD_ASSETS;
-import static com.android.tools.lint.detector.api.Detector.OtherFileScanner;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.utils.SdkUtils;
-import com.google.common.collect.Lists;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Visitor for "other" files: files that aren't java sources,
- * XML sources, etc -- or which should have custom handling in some
- * other way.
- */
-class OtherFileVisitor {
- @NonNull
- private final List<Detector> mDetectors;
-
- @NonNull
- private Map<Scope, List<File>> mFiles = new EnumMap<Scope, List<File>>(Scope.class);
-
- OtherFileVisitor(@NonNull List<Detector> detectors) {
- mDetectors = detectors;
- }
-
- /** Analyze other files in the given project */
- void scan(
- @NonNull LintDriver driver,
- @NonNull Project project,
- @Nullable Project main) {
- // Collect all project files
- File projectFolder = project.getDir();
-
- EnumSet<Scope> scopes = EnumSet.noneOf(Scope.class);
- for (Detector detector : mDetectors) {
- OtherFileScanner fileScanner = (OtherFileScanner) detector;
- EnumSet<Scope> applicable = fileScanner.getApplicableFiles();
- if (applicable.contains(Scope.OTHER)) {
- scopes = Scope.ALL;
- break;
- }
- scopes.addAll(applicable);
- }
-
- List<File> subset = project.getSubset();
-
- if (scopes.contains(Scope.RESOURCE_FILE)) {
- if (subset != null && !subset.isEmpty()) {
- List<File> files = new ArrayList<File>(subset.size());
- for (File file : subset) {
- if (SdkUtils.endsWith(file.getPath(), DOT_XML) &&
- !file.getName().equals(ANDROID_MANIFEST_XML)) {
- files.add(file);
- }
- }
- if (!files.isEmpty()) {
- mFiles.put(Scope.RESOURCE_FILE, files);
- }
- } else {
- List<File> files = Lists.newArrayListWithExpectedSize(100);
- for (File res : project.getResourceFolders()) {
- collectFiles(files, res);
- }
- File assets = new File(projectFolder, FD_ASSETS);
- if (assets.exists()) {
- collectFiles(files, assets);
- }
- if (!files.isEmpty()) {
- mFiles.put(Scope.RESOURCE_FILE, files);
- }
- }
- }
-
- if (scopes.contains(Scope.JAVA_FILE)) {
- if (subset != null && !subset.isEmpty()) {
- List<File> files = new ArrayList<File>(subset.size());
- for (File file : subset) {
- if (file.getPath().endsWith(DOT_JAVA)) {
- files.add(file);
- }
- }
- if (!files.isEmpty()) {
- mFiles.put(Scope.JAVA_FILE, files);
- }
- } else {
- List<File> files = Lists.newArrayListWithExpectedSize(100);
- for (File srcFolder : project.getJavaSourceFolders()) {
- collectFiles(files, srcFolder);
- }
- if (!files.isEmpty()) {
- mFiles.put(Scope.JAVA_FILE, files);
- }
- }
- }
-
- if (scopes.contains(Scope.CLASS_FILE)) {
- if (subset != null && !subset.isEmpty()) {
- List<File> files = new ArrayList<File>(subset.size());
- for (File file : subset) {
- if (file.getPath().endsWith(DOT_CLASS)) {
- files.add(file);
- }
- }
- if (!files.isEmpty()) {
- mFiles.put(Scope.CLASS_FILE, files);
- }
- } else {
- List<File> files = Lists.newArrayListWithExpectedSize(100);
- for (File classFolder : project.getJavaClassFolders()) {
- collectFiles(files, classFolder);
- }
- if (!files.isEmpty()) {
- mFiles.put(Scope.CLASS_FILE, files);
- }
- }
- }
-
- if (scopes.contains(Scope.MANIFEST)) {
- if (subset != null && !subset.isEmpty()) {
- List<File> files = new ArrayList<File>(subset.size());
- for (File file : subset) {
- if (file.getName().equals(ANDROID_MANIFEST_XML)) {
- files.add(file);
- }
- }
- if (!files.isEmpty()) {
- mFiles.put(Scope.MANIFEST, files);
- }
- } else {
- File manifestFile = project.getManifestFile();
- if (manifestFile != null) {
- mFiles.put(Scope.MANIFEST, Collections.<File>singletonList(manifestFile));
- }
- }
- }
-
- for (Map.Entry<Scope, List<File>> entry : mFiles.entrySet()) {
- Scope scope = entry.getKey();
- List<File> files = entry.getValue();
- List<Detector> applicable = new ArrayList<Detector>(mDetectors.size());
- for (Detector detector : mDetectors) {
- OtherFileScanner fileScanner = (OtherFileScanner) detector;
- EnumSet<Scope> appliesTo = fileScanner.getApplicableFiles();
- if (appliesTo.contains(Scope.OTHER) || appliesTo.contains(scope)) {
- applicable.add(detector);
- }
- }
- if (!applicable.isEmpty()) {
- for (File file : files) {
- Context context = new Context(driver, project, main, file);
- for (Detector detector : applicable) {
- detector.beforeCheckFile(context);
- detector.run(context);
- detector.afterCheckFile(context);
- }
- if (driver.isCanceled()) {
- return;
- }
- }
- }
- }
- }
-
- private static void collectFiles(List<File> files, File file) {
- if (file.isDirectory()) {
- File[] children = file.listFiles();
- if (children != null) {
- for (File child : children) {
- collectFiles(files, child);
- }
- }
- } else {
- files.add(file);
- }
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java
deleted file mode 100644
index 8b3d1e9..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/SdkInfo.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.annotations.Beta;
-
-/**
- * Information about SDKs
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class SdkInfo {
- /**
- * Returns true if the given child view is the same class or a sub class of
- * the given parent view class
- *
- * @param parentViewFqcn the fully qualified class name of the parent view
- * @param childViewFqcn the fully qualified class name of the child view
- * @return true if the child view is a sub view of (or the same class as)
- * the parent view
- */
- public boolean isSubViewOf(@NonNull String parentViewFqcn, @NonNull String childViewFqcn) {
- while (!childViewFqcn.equals("android.view.View")) { //$NON-NLS-1$
- if (parentViewFqcn.equals(childViewFqcn)) {
- return true;
- }
- String parent = getParentViewClass(childViewFqcn);
- if (parent == null) {
- // Unknown view - err on the side of caution
- return true;
- }
- childViewFqcn = parent;
- }
-
- return false;
- }
-
-
- /**
- * Returns the fully qualified name of the parent view, or null if the view
- * is the root android.view.View class.
- *
- * @param fqcn the fully qualified class name of the view
- * @return the fully qualified class name of the parent view, or null
- */
- @Nullable
- public abstract String getParentViewClass(@NonNull String fqcn);
-
- /**
- * Returns the class name of the parent view, or null if the view is the
- * root android.view.View class. This is the same as the
- * {@link #getParentViewClass(String)} but without the package.
- *
- * @param name the view class name to look up the parent for (not including
- * package)
- * @return the view name of the parent
- */
- @Nullable
- public abstract String getParentViewName(@NonNull String name);
-
- /**
- * Returns true if the given widget name is a layout
- *
- * @param tag the XML tag for the view
- * @return true if the given tag corresponds to a layout
- */
- public boolean isLayout(@NonNull String tag) {
- return tag.endsWith("Layout"); //$NON-NLS-1$
- }
-
- // TODO: Add access to resource resolution here.
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/XmlVisitor.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/XmlVisitor.java
deleted file mode 100644
index 2e64118..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/client/api/XmlVisitor.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.client.api;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.XmlScanner;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.annotations.Beta;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.RandomAccess;
-
-/**
- * Specialized visitor for running detectors on an XML document.
- * It operates in two phases:
- * <ol>
- * <li> First, it computes a set of maps where it generates a map from each
- * significant element name, and each significant attribute name, to a list
- * of detectors to consult for that element or attribute name.
- * The set of element names or attribute names (or both) that a detector
- * is interested in is provided by the detectors themselves.
- * <li> Second, it iterates over the document a single time. For each element and
- * attribute it looks up the list of interested detectors, and runs them.
- * </ol>
- * It also notifies all the detectors before and after the document is processed
- * such that they can do pre- and post-processing.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-class XmlVisitor {
- private final Map<String, List<Detector.XmlScanner>> mElementToCheck =
- new HashMap<String, List<Detector.XmlScanner>>();
- private final Map<String, List<Detector.XmlScanner>> mAttributeToCheck =
- new HashMap<String, List<Detector.XmlScanner>>();
- private final List<Detector.XmlScanner> mDocumentDetectors =
- new ArrayList<Detector.XmlScanner>();
- private final List<Detector.XmlScanner> mAllElementDetectors =
- new ArrayList<Detector.XmlScanner>();
- private final List<Detector.XmlScanner> mAllAttributeDetectors =
- new ArrayList<Detector.XmlScanner>();
- private final List<? extends Detector> mAllDetectors;
- private final IDomParser mParser;
-
- // Really want this:
- //<T extends List<Detector> & Detector.XmlScanner> XmlVisitor(IDomParser parser,
- // T xmlDetectors) {
- // but it makes client code tricky and ugly.
- XmlVisitor(@NonNull IDomParser parser, @NonNull List<? extends Detector> xmlDetectors) {
- mParser = parser;
- mAllDetectors = xmlDetectors;
-
- // TODO: Check appliesTo() for files, and find a quick way to enable/disable
- // rules when running through a full project!
- for (Detector detector : xmlDetectors) {
- Detector.XmlScanner xmlDetector = (XmlScanner) detector;
- Collection<String> attributes = xmlDetector.getApplicableAttributes();
- if (attributes == XmlScanner.ALL) {
- mAllAttributeDetectors.add(xmlDetector);
- } else if (attributes != null) {
- for (String attribute : attributes) {
- List<Detector.XmlScanner> list = mAttributeToCheck.get(attribute);
- if (list == null) {
- list = new ArrayList<Detector.XmlScanner>();
- mAttributeToCheck.put(attribute, list);
- }
- list.add(xmlDetector);
- }
- }
- Collection<String> elements = xmlDetector.getApplicableElements();
- if (elements == XmlScanner.ALL) {
- mAllElementDetectors.add(xmlDetector);
- } else if (elements != null) {
- for (String element : elements) {
- List<Detector.XmlScanner> list = mElementToCheck.get(element);
- if (list == null) {
- list = new ArrayList<Detector.XmlScanner>();
- mElementToCheck.put(element, list);
- }
- list.add(xmlDetector);
- }
- }
-
- if ((attributes == null || (attributes.isEmpty()
- && attributes != XmlScanner.ALL))
- && (elements == null || (elements.isEmpty()
- && elements != XmlScanner.ALL))) {
- mDocumentDetectors.add(xmlDetector);
- }
- }
- }
-
- void visitFile(@NonNull XmlContext context, @NonNull File file) {
- assert LintUtils.isXmlFile(file);
- context.parser = mParser;
-
- try {
- if (context.document == null) {
- context.document = mParser.parseXml(context);
- if (context.document == null) {
- // No need to log this; the parser should be reporting
- // a full warning (such as IssueRegistry#PARSER_ERROR)
- // with details, location, etc.
- return;
- }
- if (context.document.getDocumentElement() == null) {
- // Ignore empty documents
- return;
- }
- }
-
- for (Detector check : mAllDetectors) {
- check.beforeCheckFile(context);
- }
-
- for (Detector.XmlScanner check : mDocumentDetectors) {
- check.visitDocument(context, context.document);
- }
-
- if (!mElementToCheck.isEmpty() || !mAttributeToCheck.isEmpty()
- || !mAllAttributeDetectors.isEmpty() || !mAllElementDetectors.isEmpty()) {
- visitElement(context, context.document.getDocumentElement());
- }
-
- for (Detector check : mAllDetectors) {
- check.afterCheckFile(context);
- }
- } finally {
- if (context.document != null) {
- mParser.dispose(context, context.document);
- context.document = null;
- }
- }
- }
-
- private void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- List<Detector.XmlScanner> elementChecks = mElementToCheck.get(element.getTagName());
- if (elementChecks != null) {
- assert elementChecks instanceof RandomAccess;
- for (XmlScanner check : elementChecks) {
- check.visitElement(context, element);
- }
- }
- if (!mAllElementDetectors.isEmpty()) {
- for (XmlScanner check : mAllElementDetectors) {
- check.visitElement(context, element);
- }
- }
-
- if (!mAttributeToCheck.isEmpty() || !mAllAttributeDetectors.isEmpty()) {
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- String name = attribute.getLocalName();
- if (name == null) {
- name = attribute.getName();
- }
- List<Detector.XmlScanner> list = mAttributeToCheck.get(name);
- if (list != null) {
- for (XmlScanner check : list) {
- check.visitAttribute(context, attribute);
- }
- }
- if (!mAllAttributeDetectors.isEmpty()) {
- for (XmlScanner check : mAllAttributeDetectors) {
- check.visitAttribute(context, attribute);
- }
- }
- }
- }
-
- // Visit children
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- visitElement(context, (Element) child);
- }
- }
-
- // Post hooks
- if (elementChecks != null) {
- for (XmlScanner check : elementChecks) {
- check.visitElementAfter(context, element);
- }
- }
- if (!mAllElementDetectors.isEmpty()) {
- for (XmlScanner check : mAllElementDetectors) {
- check.visitElementAfter(context, element);
- }
- }
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Category.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Category.java
deleted file mode 100644
index c267420..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Category.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.annotations.Beta;
-
-/**
- * A category is a container for related issues.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public final class Category implements Comparable<Category> {
- private final String mName;
- private final int mPriority;
- private final Category mParent;
-
- /**
- * Creates a new {@link Category}.
- *
- * @param parent the name of a parent category, or null
- * @param name the name of the category
- * @param priority a sorting priority, with higher being more important
- */
- private Category(
- @Nullable Category parent,
- @NonNull String name,
- int priority) {
- mParent = parent;
- mName = name;
- mPriority = priority;
- }
-
- /**
- * Creates a new top level {@link Category} with the given sorting priority.
- *
- * @param name the name of the category
- * @param priority a sorting priority, with higher being more important
- * @return a new category
- */
- @NonNull
- public static Category create(@NonNull String name, int priority) {
- return new Category(null, name, priority);
- }
-
- /**
- * Creates a new top level {@link Category} with the given sorting priority.
- *
- * @param parent the name of a parent category, or null
- * @param name the name of the category
- * @param priority a sorting priority, with higher being more important
- * @return a new category
- */
- @NonNull
- public static Category create(@Nullable Category parent, @NonNull String name, int priority) {
- return new Category(parent, name, priority);
- }
-
- /**
- * Returns the parent category, or null if this is a top level category
- *
- * @return the parent category, or null if this is a top level category
- */
- public Category getParent() {
- return mParent;
- }
-
- /**
- * Returns the name of this category
- *
- * @return the name of this category
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns a full name for this category. For a top level category, this is just
- * the {@link #getName()} value, but for nested categories it will include the parent
- * names as well.
- *
- * @return a full name for this category
- */
- public String getFullName() {
- if (mParent != null) {
- return mParent.getFullName() + ':' + mName;
- } else {
- return mName;
- }
- }
-
- @Override
- public int compareTo(Category other) {
- if (other.mPriority == mPriority) {
- if (mParent == other) {
- return 1;
- } else if (other.mParent == this) {
- return -1;
- }
- }
- return other.mPriority - mPriority;
- }
-
- /** Issues related to running lint itself */
- public static final Category LINT = create("Lint", 110);
-
- /** Issues related to correctness */
- public static final Category CORRECTNESS = create("Correctness", 100);
-
- /** Issues related to security */
- public static final Category SECURITY = create("Security", 90);
-
- /** Issues related to performance */
- public static final Category PERFORMANCE = create("Performance", 80);
-
- /** Issues related to usability */
- public static final Category USABILITY = create("Usability", 70);
-
- /** Issues related to accessibility */
- public static final Category A11Y = create("Accessibility", 60);
-
- /** Issues related to internationalization */
- public static final Category I18N = create("Internationalization", 50);
-
- // Sub categories
-
- /** Issues related to icons */
- public static final Category ICONS = create(USABILITY, "Icons", 73);
-
- /** Issues related to typography */
- public static final Category TYPOGRAPHY = create(USABILITY, "Typography", 76);
-
- /** Issues related to messages/strings */
- public static final Category MESSAGES = create(CORRECTNESS, "Messages", 95);
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
deleted file mode 100644
index 5150eff..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ClassContext.java
+++ /dev/null
@@ -1,682 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.DOT_CLASS;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.EOL_BACKWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Location.SearchDirection;
-import com.android.tools.lint.detector.api.Location.SearchHints;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
-
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.objectweb.asm.tree.LineNumberNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * A {@link Context} used when checking .class files.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class ClassContext extends Context {
- private final File mBinDir;
- /** The class file DOM root node */
- private final ClassNode mClassNode;
- /** The class file byte data */
- private final byte[] mBytes;
- /** The source file, if known/found */
- private File mSourceFile;
- /** The contents of the source file, if source file is known/found */
- private String mSourceContents;
- /** Whether we've searched for the source file (used to avoid repeated failed searches) */
- private boolean mSearchedForSource;
- /** If the file is a relative path within a jar file, this is the jar file, otherwise null */
- private final File mJarFile;
- /** Whether this class is part of a library (rather than corresponding to one of the
- * source files in this project */
- private final boolean mFromLibrary;
-
- /**
- * Construct a new {@link ClassContext}
- *
- * @param driver the driver running through the checks
- * @param project the project containing the file being checked
- * @param main the main project if this project is a library project, or
- * null if this is not a library project. The main project is the
- * root project of all library projects, not necessarily the
- * directly including project.
- * @param file the file being checked
- * @param jarFile If the file is a relative path within a jar file, this is
- * the jar file, otherwise null
- * @param binDir the root binary directory containing this .class file.
- * @param bytes the bytecode raw data
- * @param classNode the bytecode object model
- * @param fromLibrary whether this class is from a library rather than part
- * of this project
- * @param sourceContents initial contents of the Java source, if known, or
- * null
- */
- public ClassContext(
- @NonNull LintDriver driver,
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File file,
- @Nullable File jarFile,
- @NonNull File binDir,
- @NonNull byte[] bytes,
- @NonNull ClassNode classNode,
- boolean fromLibrary,
- @Nullable String sourceContents) {
- super(driver, project, main, file);
- mJarFile = jarFile;
- mBinDir = binDir;
- mBytes = bytes;
- mClassNode = classNode;
- mFromLibrary = fromLibrary;
- mSourceContents = sourceContents;
- }
-
- /**
- * Returns the raw bytecode data for this class file
- *
- * @return the byte array containing the bytecode data
- */
- @NonNull
- public byte[] getBytecode() {
- return mBytes;
- }
-
- /**
- * Returns the bytecode object model
- *
- * @return the bytecode object model, never null
- */
- @NonNull
- public ClassNode getClassNode() {
- return mClassNode;
- }
-
- /**
- * Returns the jar file, if any. If this is null, the .class file is a real file
- * on disk, otherwise it represents a relative path within the jar file.
- *
- * @return the jar file, or null
- */
- @Nullable
- public File getJarFile() {
- return mJarFile;
- }
-
- /**
- * Returns whether this class is part of a library (not this project).
- *
- * @return true if this class is part of a library
- */
- public boolean isFromClassLibrary() {
- return mFromLibrary;
- }
-
- /**
- * Returns the source file for this class file, if possible.
- *
- * @return the source file, or null
- */
- @Nullable
- public File getSourceFile() {
- if (mSourceFile == null && !mSearchedForSource) {
- mSearchedForSource = true;
-
- String source = mClassNode.sourceFile;
- if (source == null) {
- source = file.getName();
- if (source.endsWith(DOT_CLASS)) {
- source = source.substring(0, source.length() - DOT_CLASS.length()) + DOT_JAVA;
- }
- int index = source.indexOf('$');
- if (index != -1) {
- source = source.substring(0, index) + DOT_JAVA;
- }
- }
- if (source != null) {
- if (mJarFile != null) {
- String relative = file.getParent() + File.separator + source;
- List<File> sources = getProject().getJavaSourceFolders();
- for (File dir : sources) {
- File sourceFile = new File(dir, relative);
- if (sourceFile.exists()) {
- mSourceFile = sourceFile;
- break;
- }
- }
- } else {
- // Determine package
- String topPath = mBinDir.getPath();
- String parentPath = file.getParentFile().getPath();
- if (parentPath.startsWith(topPath)) {
- int start = topPath.length() + 1;
- String relative = start > parentPath.length() ? // default package?
- "" : parentPath.substring(start);
- List<File> sources = getProject().getJavaSourceFolders();
- for (File dir : sources) {
- File sourceFile = new File(dir, relative + File.separator + source);
- if (sourceFile.exists()) {
- mSourceFile = sourceFile;
- break;
- }
- }
- }
- }
- }
- }
-
- return mSourceFile;
- }
-
- /**
- * Returns the contents of the source file for this class file, if found.
- *
- * @return the source contents, or ""
- */
- @NonNull
- public String getSourceContents() {
- if (mSourceContents == null) {
- File sourceFile = getSourceFile();
- if (sourceFile != null) {
- mSourceContents = getClient().readFile(mSourceFile);
- }
-
- if (mSourceContents == null) {
- mSourceContents = "";
- }
- }
-
- return mSourceContents;
- }
-
- /**
- * Returns the contents of the source file for this class file, if found. If
- * {@code read} is false, do not read the source contents if it has not
- * already been read. (This is primarily intended for the lint
- * infrastructure; most client code would call {@link #getSourceContents()}
- * .)
- *
- * @param read whether to read the source contents if it has not already
- * been initialized
- * @return the source contents, which will never be null if {@code read} is
- * true, or null if {@code read} is false and the source contents
- * hasn't already been read.
- */
- @Nullable
- public String getSourceContents(boolean read) {
- if (read) {
- return getSourceContents();
- } else {
- return mSourceContents;
- }
- }
-
- /**
- * Returns a location for the given source line number in this class file's
- * source file, if available.
- *
- * @param line the line number (1-based, which is what ASM uses)
- * @param patternStart optional pattern to search for in the source for
- * range start
- * @param patternEnd optional pattern to search for in the source for range
- * end
- * @param hints additional hints about the pattern search (provided
- * {@code patternStart} is non null)
- * @return a location, never null
- */
- @NonNull
- public Location getLocationForLine(int line, @Nullable String patternStart,
- @Nullable String patternEnd, @Nullable SearchHints hints) {
- File sourceFile = getSourceFile();
- if (sourceFile != null) {
- // ASM line numbers are 1-based, and lint line numbers are 0-based
- if (line != -1) {
- return Location.create(sourceFile, getSourceContents(), line - 1,
- patternStart, patternEnd, hints);
- } else {
- return Location.create(sourceFile);
- }
- }
-
- return Location.create(file);
- }
-
- /**
- * Reports an issue.
- * <p>
- * Detectors should only call this method if an error applies to the whole class
- * scope and there is no specific method or field that applies to the error.
- * If so, use
- * {@link #report(Issue, MethodNode, AbstractInsnNode, Location, String, Object)} or
- * {@link #report(Issue, FieldNode, Location, String, Object)}, such that
- * suppress annotations are checked.
- *
- * @param issue the issue to report
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- * @param data any associated data, or null
- */
- @Override
- public void report(
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- if (mDriver.isSuppressed(issue, mClassNode)) {
- return;
- }
- ClassNode curr = mClassNode;
- while (curr != null) {
- ClassNode prev = curr;
- curr = mDriver.getOuterClassNode(curr);
- if (curr != null) {
- if (prev.outerMethod != null) {
- @SuppressWarnings("rawtypes") // ASM API
- List methods = curr.methods;
- for (Object m : methods) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(prev.outerMethod)
- && method.desc.equals(prev.outerMethodDesc)) {
- // Found the outer method for this anonymous class; continue
- // reporting on it (which will also work its way up the parent
- // class hierarchy)
- if (method != null && mDriver.isSuppressed(issue, mClassNode, method,
- null)) {
- return;
- }
- break;
- }
- }
- }
- if (mDriver.isSuppressed(issue, curr)) {
- return;
- }
- }
- }
-
- super.report(issue, location, message, data);
- }
-
- // Unfortunately, ASMs nodes do not extend a common DOM node type with parent
- // pointers, so we have to have multiple methods which pass in each type
- // of node (class, method, field) to be checked.
-
- /**
- * Reports an issue applicable to a given method node.
- *
- * @param issue the issue to report
- * @param method the method scope the error applies to. The lint
- * infrastructure will check whether there are suppress
- * annotations on this method (or its enclosing class) and if so
- * suppress the warning without involving the client.
- * @param instruction the instruction within the method the error applies
- * to. You cannot place annotations on individual method
- * instructions (for example, annotations on local variables are
- * allowed, but are not kept in the .class file). However, this
- * instruction is needed to handle suppressing errors on field
- * initializations; in that case, the errors may be reported in
- * the {@code <clinit>} method, but the annotation is found not
- * on that method but for the {@link FieldNode}'s.
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- * @param data any associated data, or null
- */
- public void report(
- @NonNull Issue issue,
- @Nullable MethodNode method,
- @Nullable AbstractInsnNode instruction,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- if (method != null && mDriver.isSuppressed(issue, mClassNode, method, instruction)) {
- return;
- }
- report(issue, location, message, data); // also checks the class node
- }
-
- /**
- * Reports an issue applicable to a given method node.
- *
- * @param issue the issue to report
- * @param field the scope the error applies to. The lint infrastructure
- * will check whether there are suppress annotations on this field (or its enclosing
- * class) and if so suppress the warning without involving the client.
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- * @param data any associated data, or null
- */
- public void report(
- @NonNull Issue issue,
- @Nullable FieldNode field,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- if (field != null && mDriver.isSuppressed(issue, field)) {
- return;
- }
- report(issue, location, message, data); // also checks the class node
- }
-
- /**
- * Finds the line number closest to the given node
- *
- * @param node the instruction node to get a line number for
- * @return the closest line number, or -1 if not known
- */
- public static int findLineNumber(@NonNull AbstractInsnNode node) {
- AbstractInsnNode curr = node;
-
- // First search backwards
- while (curr != null) {
- if (curr.getType() == AbstractInsnNode.LINE) {
- return ((LineNumberNode) curr).line;
- }
- curr = curr.getPrevious();
- }
-
- // Then search forwards
- curr = node;
- while (curr != null) {
- if (curr.getType() == AbstractInsnNode.LINE) {
- return ((LineNumberNode) curr).line;
- }
- curr = curr.getNext();
- }
-
- return -1;
- }
-
- /**
- * Finds the line number closest to the given method declaration
- *
- * @param node the method node to get a line number for
- * @return the closest line number, or -1 if not known
- */
- public static int findLineNumber(@NonNull MethodNode node) {
- if (node.instructions != null && node.instructions.size() > 0) {
- return findLineNumber(node.instructions.get(0));
- }
-
- return -1;
- }
-
- /**
- * Finds the line number closest to the given class declaration
- *
- * @param node the method node to get a line number for
- * @return the closest line number, or -1 if not known
- */
- public static int findLineNumber(@NonNull ClassNode node) {
- if (node.methods != null && !node.methods.isEmpty()) {
- MethodNode firstMethod = getFirstRealMethod(node);
- if (firstMethod != null) {
- return findLineNumber(firstMethod);
- }
- }
-
- return -1;
- }
-
- /**
- * Returns a location for the given {@link ClassNode}, where class node is
- * either the top level class, or an inner class, in the current context.
- *
- * @param classNode the class in the current context
- * @return a location pointing to the class declaration, or as close to it
- * as possible
- */
- @NonNull
- public Location getLocation(@NonNull ClassNode classNode) {
- // Attempt to find a proper location for this class. This is tricky
- // since classes do not have line number entries in the class file; we need
- // to find a method, look up the corresponding line number then search
- // around it for a suitable tag, such as the class name.
- String pattern;
- if (isAnonymousClass(classNode.name)) {
- pattern = classNode.superName;
- } else {
- pattern = classNode.name;
- }
- int index = pattern.lastIndexOf('$');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
- index = pattern.lastIndexOf('/');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
-
- return getLocationForLine(findLineNumber(classNode), pattern, null,
- SearchHints.create(BACKWARD).matchJavaSymbol());
- }
-
- @Nullable
- private static MethodNode getFirstRealMethod(@NonNull ClassNode classNode) {
- // Return the first method in the class for line number purposes. Skip <init>,
- // since it's typically not located near the real source of the method.
- if (classNode.methods != null) {
- @SuppressWarnings("rawtypes") // ASM API
- List methods = classNode.methods;
- for (Object m : methods) {
- MethodNode method = (MethodNode) m;
- if (method.name.charAt(0) != '<') {
- return method;
- }
- }
-
- if (!classNode.methods.isEmpty()) {
- return (MethodNode) classNode.methods.get(0);
- }
- }
-
- return null;
- }
-
- /**
- * Returns a location for the given {@link MethodNode}.
- *
- * @param methodNode the class in the current context
- * @param classNode the class containing the method
- * @return a location pointing to the class declaration, or as close to it
- * as possible
- */
- @NonNull
- public Location getLocation(@NonNull MethodNode methodNode,
- @NonNull ClassNode classNode) {
- // Attempt to find a proper location for this class. This is tricky
- // since classes do not have line number entries in the class file; we need
- // to find a method, look up the corresponding line number then search
- // around it for a suitable tag, such as the class name.
- String pattern;
- SearchDirection searchMode;
- if (methodNode.name.equals(CONSTRUCTOR_NAME)) {
- searchMode = EOL_BACKWARD;
- if (isAnonymousClass(classNode.name)) {
- pattern = classNode.superName.substring(classNode.superName.lastIndexOf('/') + 1);
- } else {
- pattern = classNode.name.substring(classNode.name.lastIndexOf('$') + 1);
- }
- } else {
- searchMode = BACKWARD;
- pattern = methodNode.name;
- }
-
- return getLocationForLine(findLineNumber(methodNode), pattern, null,
- SearchHints.create(searchMode).matchJavaSymbol());
- }
-
- /**
- * Returns a location for the given {@link AbstractInsnNode}.
- *
- * @param instruction the instruction to look up the location for
- * @return a location pointing to the instruction, or as close to it
- * as possible
- */
- @NonNull
- public Location getLocation(@NonNull AbstractInsnNode instruction) {
- SearchHints hints = SearchHints.create(FORWARD).matchJavaSymbol();
- String pattern = null;
- if (instruction instanceof MethodInsnNode) {
- MethodInsnNode call = (MethodInsnNode) instruction;
- if (call.name.equals(CONSTRUCTOR_NAME)) {
- pattern = call.owner;
- hints = hints.matchConstructor();
- } else {
- pattern = call.name;
- }
- int index = pattern.lastIndexOf('$');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
- index = pattern.lastIndexOf('/');
- if (index != -1) {
- pattern = pattern.substring(index + 1);
- }
- }
-
- int line = findLineNumber(instruction);
- return getLocationForLine(line, pattern, null, hints);
- }
-
- private static boolean isAnonymousClass(@NonNull String fqcn) {
- int lastIndex = fqcn.lastIndexOf('$');
- if (lastIndex != -1 && lastIndex < fqcn.length() - 1) {
- if (Character.isDigit(fqcn.charAt(lastIndex + 1))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Converts from a VM owner name (such as foo/bar/Foo$Baz) to a
- * fully qualified class name (such as foo.bar.Foo.Baz).
- *
- * @param owner the owner name to convert
- * @return the corresponding fully qualified class name
- */
- @NonNull
- public static String getFqcn(@NonNull String owner) {
- return owner.replace('/', '.').replace('$','.');
- }
-
- /**
- * Computes a user-readable type signature from the given class owner, name
- * and description. For example, for owner="foo/bar/Foo$Baz", name="foo",
- * description="(I)V", it returns "void foo.bar.Foo.Bar#foo(int)".
- *
- * @param owner the class name
- * @param name the method name
- * @param desc the method description
- * @return a user-readable string
- */
- public static String createSignature(String owner, String name, String desc) {
- StringBuilder sb = new StringBuilder(100);
-
- if (desc != null) {
- Type returnType = Type.getReturnType(desc);
- sb.append(getTypeString(returnType));
- sb.append(' ');
- }
-
- if (owner != null) {
- sb.append(getFqcn(owner));
- }
- if (name != null) {
- sb.append('#');
- sb.append(name);
- if (desc != null) {
- Type[] argumentTypes = Type.getArgumentTypes(desc);
- if (argumentTypes != null && argumentTypes.length > 0) {
- sb.append('(');
- boolean first = true;
- for (Type type : argumentTypes) {
- if (first) {
- first = false;
- } else {
- sb.append(", ");
- }
- sb.append(getTypeString(type));
- }
- sb.append(')');
- }
- }
- }
-
- return sb.toString();
- }
-
- private static String getTypeString(Type type) {
- String s = type.getClassName();
- if (s.startsWith("java.lang.")) { //$NON-NLS-1$
- s = s.substring("java.lang.".length()); //$NON-NLS-1$
- }
-
- return s;
- }
-
- /**
- * Computes the internal class name of the given fully qualified class name.
- * For example, it converts foo.bar.Foo.Bar into foo/bar/Foo$Bar
- *
- * @param fqcn the fully qualified class name
- * @return the internal class name
- */
- @NonNull
- public static String getInternalName(@NonNull String fqcn) {
- if (fqcn.indexOf('.') == -1) {
- return fqcn;
- }
-
- StringBuilder sb = new StringBuilder(fqcn.length());
- String prev = null;
- for (String part : Splitter.on('.').split(fqcn)) {
- if (prev != null && prev.length() > 0) {
- if (Character.isUpperCase(prev.charAt(0))) {
- sb.append('$');
- } else {
- sb.append('/');
- }
- }
- sb.append(part);
- prev = part;
- }
-
- return sb.toString();
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Context.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Context.java
deleted file mode 100644
index a379dd0..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Context.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.SdkInfo;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
-
-import java.io.File;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Context passed to the detectors during an analysis run. It provides
- * information about the file being analyzed, it allows shared properties (so
- * the detectors can share results), etc.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class Context {
- /**
- * The file being checked. Note that this may not always be to a concrete
- * file. For example, in the {@link Detector#beforeCheckProject(Context)}
- * method, the context file is the directory of the project.
- */
- public final File file;
-
- /** The driver running through the checks */
- protected final LintDriver mDriver;
-
- /** The project containing the file being checked */
- @NonNull
- private final Project mProject;
-
- /**
- * The "main" project. For normal projects, this is the same as {@link #mProject},
- * but for library projects, it's the root project that includes (possibly indirectly)
- * the various library projects and their library projects.
- * <p>
- * Note that this is a property on the {@link Context}, not the
- * {@link Project}, since a library project can be included from multiple
- * different top level projects, so there isn't <b>one</b> main project,
- * just one per main project being analyzed with its library projects.
- */
- private final Project mMainProject;
-
- /** The current configuration controlling which checks are enabled etc */
- private final Configuration mConfiguration;
-
- /** The contents of the file */
- private String mContents;
-
- /**
- * Whether the lint job has been canceled.
- * <p>
- * Slow-running detectors should check this flag via
- * {@link AtomicBoolean#get()} and abort if canceled
- */
- @NonNull
- public final AtomicBoolean canceled = new AtomicBoolean();
-
- /** Map of properties to share results between detectors */
- private Map<String, Object> mProperties;
-
- /**
- * Construct a new {@link Context}
- *
- * @param driver the driver running through the checks
- * @param project the project containing the file being checked
- * @param main the main project if this project is a library project, or
- * null if this is not a library project. The main project is
- * the root project of all library projects, not necessarily the
- * directly including project.
- * @param file the file being checked
- */
- public Context(
- @NonNull LintDriver driver,
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File file) {
- this.file = file;
-
- mDriver = driver;
- mProject = project;
- mMainProject = main;
- mConfiguration = project.getConfiguration();
- }
-
- /**
- * Returns the scope for the lint job
- *
- * @return the scope, never null
- */
- @NonNull
- public EnumSet<Scope> getScope() {
- return mDriver.getScope();
- }
-
- /**
- * Returns the configuration for this project.
- *
- * @return the configuration, never null
- */
- @NonNull
- public Configuration getConfiguration() {
- return mConfiguration;
- }
-
- /**
- * Returns the project containing the file being checked
- *
- * @return the project, never null
- */
- @NonNull
- public Project getProject() {
- return mProject;
- }
-
- /**
- * Returns the main project if this project is a library project, or self
- * if this is not a library project. The main project is the root project
- * of all library projects, not necessarily the directly including project.
- *
- * @return the main project, never null
- */
- @NonNull
- public Project getMainProject() {
- return mMainProject != null ? mMainProject : mProject;
- }
-
- /**
- * Returns the lint client requesting the lint check
- *
- * @return the client, never null
- */
- @NonNull
- public LintClient getClient() {
- return mDriver.getClient();
- }
-
- /**
- * Returns the driver running through the lint checks
- *
- * @return the driver
- */
- @NonNull
- public LintDriver getDriver() {
- return mDriver;
- }
-
- /**
- * Returns the contents of the file. This may not be the contents of the
- * file on disk, since it delegates to the {@link LintClient}, which in turn
- * may decide to return the current edited contents of the file open in an
- * editor.
- *
- * @return the contents of the given file, or null if an error occurs.
- */
- @Nullable
- public String getContents() {
- if (mContents == null) {
- mContents = mDriver.getClient().readFile(file);
- }
-
- return mContents;
- }
-
- /**
- * Returns the value of the given named property, or null.
- *
- * @param name the name of the property
- * @return the corresponding value, or null
- */
- @Nullable
- public Object getProperty(String name) {
- if (mProperties == null) {
- return null;
- }
-
- return mProperties.get(name);
- }
-
- /**
- * Sets the value of the given named property.
- *
- * @param name the name of the property
- * @param value the corresponding value
- */
- public void setProperty(@NonNull String name, @Nullable Object value) {
- if (value == null) {
- if (mProperties != null) {
- mProperties.remove(name);
- }
- } else {
- if (mProperties == null) {
- mProperties = new HashMap<String, Object>();
- }
- mProperties.put(name, value);
- }
- }
-
- /**
- * Gets the SDK info for the current project.
- *
- * @return the SDK info for the current project, never null
- */
- @NonNull
- public SdkInfo getSdkInfo() {
- return mProject.getSdkInfo();
- }
-
- // ---- Convenience wrappers ---- (makes the detector code a bit leaner)
-
- /**
- * Returns false if the given issue has been disabled. Convenience wrapper
- * around {@link Configuration#getSeverity(Issue)}.
- *
- * @param issue the issue to check
- * @return false if the issue has been disabled
- */
- public boolean isEnabled(@NonNull Issue issue) {
- return mConfiguration.isEnabled(issue);
- }
-
- /**
- * Reports an issue. Convenience wrapper around {@link LintClient#report}
- *
- * @param issue the issue to report
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- * @param data any associated data, or null
- */
- public void report(
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- Configuration configuration = mConfiguration;
-
- // If this error was computed for a context where the context corresponds to
- // a project instead of a file, the actual error may be in a different project (e.g.
- // a library project), so adjust the configuration as necessary.
- if (location != null && location.getFile() != null) {
- Project project = mDriver.findProjectFor(location.getFile());
- if (project != null) {
- configuration = project.getConfiguration();
- }
- }
-
- // If an error occurs in a library project, but you've disabled that check in the
- // main project, disable it in the library project too. (In some cases you don't
- // control the lint.xml of a library project, and besides, if you're not interested in
- // a check for your main project you probably don't care about it in the library either.)
- if (configuration != mConfiguration
- && mConfiguration.getSeverity(issue) == Severity.IGNORE) {
- return;
- }
-
- Severity severity = configuration.getSeverity(issue);
- if (severity == Severity.IGNORE) {
- return;
- }
-
- mDriver.getClient().report(this, issue, severity, location, message, data);
- }
-
- /**
- * Send an exception to the log. Convenience wrapper around {@link LintClient#log}.
- *
- * @param exception the exception, possibly null
- * @param format the error message using {@link String#format} syntax, possibly null
- * @param args any arguments for the format string
- */
- public void log(
- @Nullable Throwable exception,
- @Nullable String format,
- @Nullable Object... args) {
- mDriver.getClient().log(exception, format, args);
- }
-
- /**
- * Returns the current phase number. The first pass is numbered 1. Only one pass
- * will be performed, unless a {@link Detector} calls {@link #requestRepeat}.
- *
- * @return the current phase, usually 1
- */
- public int getPhase() {
- return mDriver.getPhase();
- }
-
- /**
- * Requests another pass through the data for the given detector. This is
- * typically done when a detector needs to do more expensive computation,
- * but it only wants to do this once it <b>knows</b> that an error is
- * present, or once it knows more specifically what to check for.
- *
- * @param detector the detector that should be included in the next pass.
- * Note that the lint runner may refuse to run more than a couple
- * of runs.
- * @param scope the scope to be revisited. This must be a subset of the
- * current scope ({@link #getScope()}, and it is just a performance hint;
- * in particular, the detector should be prepared to be called on other
- * scopes as well (since they may have been requested by other detectors).
- * You can pall null to indicate "all".
- */
- public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) {
- mDriver.requestRepeat(detector, scope);
- }
-
- /** Pattern for version qualifiers */
- private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)$"); //$NON-NLS-1$
-
- private static File sCachedFolder = null;
- private static int sCachedFolderVersion = -1;
-
- /**
- * Returns the folder version. For example, for the file values-v14/foo.xml,
- * it returns 14.
- *
- * @return the folder version, or -1 if no specific version was specified
- */
- public int getFolderVersion() {
- return getFolderVersion(file);
- }
-
- /**
- * Returns the folder version of the given file. For example, for the file values-v14/foo.xml,
- * it returns 14.
- *
- * @param file the file to be checked
- * @return the folder version, or -1 if no specific version was specified
- */
- public static int getFolderVersion(File file) {
- File parent = file.getParentFile();
- if (parent.equals(sCachedFolder)) {
- return sCachedFolderVersion;
- }
-
- sCachedFolder = parent;
- sCachedFolderVersion = -1;
-
- for (String qualifier : Splitter.on('-').split(parent.getName())) {
- Matcher matcher = VERSION_PATTERN.matcher(qualifier);
- if (matcher.matches()) {
- sCachedFolderVersion = Integer.parseInt(matcher.group(1));
- break;
- }
- }
-
- return sCachedFolderVersion;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java
deleted file mode 100644
index 72c8ee7..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/DefaultPosition.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.google.common.annotations.Beta;
-
-/**
- * A simple offset-based position *
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class DefaultPosition extends Position {
- /** The line number (0-based where the first line is line 0) */
- private final int mLine;
-
- /**
- * The column number (where the first character on the line is 0), or -1 if
- * unknown
- */
- private final int mColumn;
-
- /** The character offset */
- private final int mOffset;
-
- /**
- * Creates a new {@link DefaultPosition}
- *
- * @param line the 0-based line number, or -1 if unknown
- * @param column the 0-based column number, or -1 if unknown
- * @param offset the offset, or -1 if unknown
- */
- public DefaultPosition(int line, int column, int offset) {
- mLine = line;
- mColumn = column;
- mOffset = offset;
- }
-
- @Override
- public int getLine() {
- return mLine;
- }
-
- @Override
- public int getOffset() {
- return mOffset;
- }
-
- @Override
- public int getColumn() {
- return mColumn;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Detector.java
deleted file mode 100644
index a3412cf..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Detector.java
+++ /dev/null
@@ -1,611 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.LintDriver;
-import com.google.common.annotations.Beta;
-import lombok.ast.AstVisitor;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-
-/**
- * A detector is able to find a particular problem. It might also be thought of as enforcing
- * a rule, but "rule" is a bit overloaded in ADT terminology since ViewRules are used in
- * the Rules API to allow views to specify designtime behavior in the graphical layout editor.
- * <p>
- * Each detector provides information about the issues it can find, such as an explanation
- * of how to fix the issue, the priority, the category, etc. It also has an id which is
- * used to persistently identify a particular type of error.
- * <p>
- * Detectors will be called in a predefined order:
- * <ol>
- * <li> Manifest file
- * <li> Resource files, in alphabetical order by resource type
- * (therefore, "layout" is checked before "values", "values-de" is checked before
- * "values-en" but after "values", and so on.
- * <li> Java sources
- * <li> Java classes
- * <li> Proguard files
- * </ol>
- * If a detector needs information when processing a file type that comes from a type of
- * file later in the order above, they can request a second phase; see
- * {@link LintDriver#requestRepeat}.
- * <p>
- * NOTE: Detectors might be constructed just once and shared between lint runs, so
- * any per-detector state should be initialized and reset via the before/after
- * methods.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class Detector {
- /** Specialized interface for detectors that scan Java source file parse trees */
- public interface JavaScanner {
- /**
- * Create a parse tree visitor to process the parse tree. All
- * {@link JavaScanner} detectors must provide a visitor, unless they
- * either return true from {@link #appliesToResourceRefs()} or return
- * non null from {@link #getApplicableMethodNames()}.
- * <p>
- * If you return specific AST node types from
- * {@link #getApplicableNodeTypes()}, then the visitor will <b>only</b>
- * be called for the specific requested node types. This is more
- * efficient, since it allows many detectors that apply to only a small
- * part of the AST (such as method call nodes) to share iteration of the
- * majority of the parse tree.
- * <p>
- * If you return null from {@link #getApplicableNodeTypes()}, then your
- * visitor will be called from the top and all node types visited.
- * <p>
- * Note that a new visitor is created for each separate compilation
- * unit, so you can store per file state in the visitor.
- *
- * @param context the {@link Context} for the file being analyzed
- * @return a visitor, or null.
- */
- @Nullable
- AstVisitor createJavaVisitor(@NonNull JavaContext context);
-
- /**
- * Return the types of AST nodes that the visitor returned from
- * {@link #createJavaVisitor(JavaContext)} should visit. See the
- * documentation for {@link #createJavaVisitor(JavaContext)} for details
- * on how the shared visitor is used.
- * <p>
- * If you return null from this method, then the visitor will process
- * the full tree instead.
- * <p>
- * Note that for the shared visitor, the return codes from the visit
- * methods are ignored: returning true will <b>not</b> prune iteration
- * of the subtree, since there may be other node types interested in the
- * children. If you need to ensure that your visitor only processes a
- * part of the tree, use a full visitor instead. See the
- * OverdrawDetector implementation for an example of this.
- *
- * @return the list of applicable node types (AST node classes), or null
- */
- @Nullable
- List<Class<? extends Node>> getApplicableNodeTypes();
-
- /**
- * Return the list of method names this detector is interested in, or
- * null. If this method returns non-null, then any AST nodes that match
- * a method call in the list will be passed to the
- * {@link #visitMethod(JavaContext, AstVisitor, MethodInvocation)}
- * method for processing. The visitor created by
- * {@link #createJavaVisitor(JavaContext)} is also passed to that
- * method, although it can be null.
- * <p>
- * This makes it easy to write detectors that focus on some fixed calls.
- * For example, the StringFormatDetector uses this mechanism to look for
- * "format" calls, and when found it looks around (using the AST's
- * {@link Node#getParent()} method) to see if it's called on
- * a String class instance, and if so do its normal processing. Note
- * that since it doesn't need to do any other AST processing, that
- * detector does not actually supply a visitor.
- *
- * @return a set of applicable method names, or null.
- */
- @Nullable
- List<String> getApplicableMethodNames();
-
- /**
- * Method invoked for any method calls found that matches any names
- * returned by {@link #getApplicableMethodNames()}. This also passes
- * back the visitor that was created by
- * {@link #createJavaVisitor(JavaContext)}, but a visitor is not
- * required. It is intended for detectors that need to do additional AST
- * processing, but also want the convenience of not having to look for
- * method names on their own.
- *
- * @param context the context of the lint request
- * @param visitor the visitor created from
- * {@link #createJavaVisitor(JavaContext)}, or null
- * @param node the {@link MethodInvocation} node for the invoked method
- */
- void visitMethod(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node);
-
- /**
- * Returns whether this detector cares about Android resource references
- * (such as {@code R.layout.main} or {@code R.string.app_name}). If it
- * does, then the visitor will look for these patterns, and if found, it
- * will invoke {@link #visitResourceReference} passing the resource type
- * and resource name. It also passes the visitor, if any, that was
- * created by {@link #createJavaVisitor(JavaContext)}, such that a
- * detector can do more than just look for resources.
- *
- * @return true if this detector wants to be notified of R resource
- * identifiers found in the code.
- */
- boolean appliesToResourceRefs();
-
- /**
- * Called for any resource references (such as {@code R.layout.main}
- * found in Java code, provided this detector returned {@code true} from
- * {@link #appliesToResourceRefs()}.
- *
- * @param context the lint scanning context
- * @param visitor the visitor created from
- * {@link #createJavaVisitor(JavaContext)}, or null
- * @param node the variable reference for the resource
- * @param type the resource type, such as "layout" or "string"
- * @param name the resource name, such as "main" from
- * {@code R.layout.main}
- * @param isFramework whether the resource is a framework resource
- * (android.R) or a local project resource (R)
- */
- void visitResourceReference(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull Node node,
- @NonNull String type,
- @NonNull String name,
- boolean isFramework);
- }
-
- /** Specialized interface for detectors that scan Java class files */
- public interface ClassScanner {
- /**
- * Checks the given class' bytecode for issues.
- *
- * @param context the context of the lint check, pointing to for example
- * the file
- * @param classNode the root class node
- */
- void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode);
-
- /**
- * Returns the list of node types (corresponding to the constants in the
- * {@link AbstractInsnNode} class) that this scanner applies to. The
- * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)}
- * method will be called for each match.
- *
- * @return an array containing all the node types this detector should be
- * called for, or null if none.
- */
- @Nullable
- int[] getApplicableAsmNodeTypes();
-
- /**
- * Process a given instruction node, and register lint issues if
- * applicable.
- *
- * @param context the context of the lint check, pointing to for example
- * the file
- * @param classNode the root class node
- * @param method the method node containing the call
- * @param instruction the actual instruction
- */
- void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull AbstractInsnNode instruction);
-
- /**
- * Return the list of method call names (in VM format, e.g. "<init>" for
- * constructors, etc) for method calls this detector is interested in,
- * or null. T his will be used to dispatch calls to
- * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
- * for only the method calls in owners that the detector is interested
- * in.
- * <p>
- * <b>NOTE</b>: If you return non null from this method, then <b>only</b>
- * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
- * will be called if a suitable method is found;
- * {@link #checkClass(ClassContext, ClassNode)} will not be called under
- * any circumstances.
- * <p>
- * This makes it easy to write detectors that focus on some fixed calls,
- * and allows lint to make a single pass over the bytecode over a class,
- * and efficiently dispatch method calls to any detectors that are
- * interested in it. Without this, each new lint check interested in a
- * single method, would be doing a complete pass through all the
- * bytecode instructions of the class via the
- * {@link #checkClass(ClassContext, ClassNode)} method, which would make
- * each newly added lint check make lint slower. Now a single dispatch
- * map is used instead, and for each encountered call in the single
- * dispatch, it looks up in the map which if any detectors are
- * interested in the given call name, and dispatches to each one in
- * turn.
- *
- * @return a list of applicable method names, or null.
- */
- @Nullable
- List<String> getApplicableCallNames();
-
- /**
- * Just like {@link Detector#getApplicableCallNames()}, but for the owner
- * field instead. The
- * {@link #checkCall(ClassContext, ClassNode, MethodNode, MethodInsnNode)}
- * method will be called for all {@link MethodInsnNode} instances where the
- * owner field matches any of the members returned in this node.
- * <p>
- * Note that if your detector provides both a name and an owner, the
- * method will be called for any nodes matching either the name <b>or</b>
- * the owner, not only where they match <b>both</b>. Note also that it will
- * be called twice - once for the name match, and (at least once) for the owner
- * match.
- *
- * @return a list of applicable owner names, or null.
- */
- @Nullable
- List<String> getApplicableCallOwners();
-
- /**
- * Process a given method call node, and register lint issues if
- * applicable. This is similar to the
- * {@link #checkInstruction(ClassContext, ClassNode, MethodNode, AbstractInsnNode)}
- * method, but has the additional advantage that it is only called for known
- * method names or method owners, according to
- * {@link #getApplicableCallNames()} and {@link #getApplicableCallOwners()}.
- *
- * @param context the context of the lint check, pointing to for example
- * the file
- * @param classNode the root class node
- * @param method the method node containing the call
- * @param call the actual method call node
- */
- void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call);
- }
-
- /** Specialized interface for detectors that scan XML files */
- public interface XmlScanner {
- /**
- * Visit the given document. The detector is responsible for its own iteration
- * through the document.
- * @param context information about the document being analyzed
- * @param document the document to examine
- */
- void visitDocument(@NonNull XmlContext context, @NonNull Document document);
-
- /**
- * Visit the given element.
- * @param context information about the document being analyzed
- * @param element the element to examine
- */
- void visitElement(@NonNull XmlContext context, @NonNull Element element);
-
- /**
- * Visit the given element after its children have been analyzed.
- * @param context information about the document being analyzed
- * @param element the element to examine
- */
- void visitElementAfter(@NonNull XmlContext context, @NonNull Element element);
-
- /**
- * Visit the given attribute.
- * @param context information about the document being analyzed
- * @param attribute the attribute node to examine
- */
- void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute);
-
- /**
- * Returns the list of elements that this detector wants to analyze. If non
- * null, this detector will be called (specifically, the
- * {@link #visitElement} method) for each matching element in the document.
- * <p>
- * If this method returns null, and {@link #getApplicableAttributes()} also returns
- * null, then the {@link #visitDocument} method will be called instead.
- *
- * @return a collection of elements, or null, or the special
- * {@link XmlScanner#ALL} marker to indicate that every single
- * element should be analyzed.
- */
- @Nullable
- Collection<String> getApplicableElements();
-
- /**
- * Returns the list of attributes that this detector wants to analyze. If non
- * null, this detector will be called (specifically, the
- * {@link #visitAttribute} method) for each matching attribute in the document.
- * <p>
- * If this method returns null, and {@link #getApplicableElements()} also returns
- * null, then the {@link #visitDocument} method will be called instead.
- *
- * @return a collection of attributes, or null, or the special
- * {@link XmlScanner#ALL} marker to indicate that every single
- * attribute should be analyzed.
- */
- @Nullable
- Collection<String> getApplicableAttributes();
-
- /**
- * Special marker collection returned by {@link #getApplicableElements()} or
- * {@link #getApplicableAttributes()} to indicate that the check should be
- * invoked on all elements or all attributes
- */
- @NonNull
- List<String> ALL = new ArrayList<String>(0); // NOT Collections.EMPTY!
- // We want to distinguish this from just an *empty* list returned by the caller!
- }
-
- /** Specialized interface for detectors that scan other files */
- public interface OtherFileScanner {
- /**
- * Returns the set of files this scanner wants to consider. If this includes
- * {@link Scope#OTHER} then all source files will be checked. Note that the
- * set of files will not just include files of the indicated type, but all files
- * within the relevant source folder. For example, returning {@link Scope#JAVA_FILE}
- * will not just return {@code .java} files, but also other resource files such as
- * {@code .html} and other files found within the Java source folders.
- * <p>
- * Lint will call the {@link #run(Context)}} method when the file should be checked.
- *
- * @return set of scopes that define the types of source files the
- * detector wants to consider
- */
- @NonNull
- EnumSet<Scope> getApplicableFiles();
- }
-
- /**
- * Runs the detector. This method will not be called for certain specialized
- * detectors, such as {@link XmlScanner} and {@link JavaScanner}, where
- * there are specialized analysis methods instead such as
- * {@link XmlScanner#visitElement(XmlContext, Element)}.
- *
- * @param context the context describing the work to be done
- */
- public void run(@NonNull Context context) {
- }
-
- /**
- * Returns true if this detector applies to the given file
- *
- * @param context the context to check
- * @param file the file in the context to check
- * @return true if this detector applies to the given context and file
- */
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return false;
- }
-
- /**
- * Analysis is about to begin, perform any setup steps.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void beforeCheckProject(@NonNull Context context) {
- }
-
- /**
- * Analysis has just been finished for the whole project, perform any
- * cleanup or report issues that require project-wide analysis.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void afterCheckProject(@NonNull Context context) {
- }
-
- /**
- * Analysis is about to begin for the given library project, perform any setup steps.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void beforeCheckLibraryProject(@NonNull Context context) {
- }
-
- /**
- * Analysis has just been finished for the given library project, perform any
- * cleanup or report issues that require library-project-wide analysis.
- *
- * @param context the context for the check referencing the project, lint
- * client, etc
- */
- public void afterCheckLibraryProject(@NonNull Context context) {
- }
-
- /**
- * Analysis is about to be performed on a specific file, perform any setup
- * steps.
- * <p>
- * Note: When this method is called at the beginning of checking an XML
- * file, the context is guaranteed to be an instance of {@link XmlContext},
- * and similarly for a Java source file, the context will be a
- * {@link JavaContext} and so on.
- *
- * @param context the context for the check referencing the file to be
- * checked, the project, etc.
- */
- public void beforeCheckFile(@NonNull Context context) {
- }
-
- /**
- * Analysis has just been finished for a specific file, perform any cleanup
- * or report issues found
- * <p>
- * Note: When this method is called at the end of checking an XML
- * file, the context is guaranteed to be an instance of {@link XmlContext},
- * and similarly for a Java source file, the context will be a
- * {@link JavaContext} and so on.
- *
- * @param context the context for the check referencing the file to be
- * checked, the project, etc.
- */
- public void afterCheckFile(@NonNull Context context) {
- }
-
- /**
- * Returns the expected speed of this detector
- *
- * @return the expected speed of this detector
- */
- @NonNull
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- // ---- Dummy implementations to make implementing XmlScanner easier: ----
-
- @SuppressWarnings("javadoc")
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- // This method must be overridden if your detector does
- // not return something from getApplicableElements or
- // getApplicableAttributes
- assert false;
- }
-
- @SuppressWarnings("javadoc")
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- // This method must be overridden if your detector returns
- // tag names from getApplicableElements
- assert false;
- }
-
- @SuppressWarnings("javadoc")
- public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) {
- }
-
- @SuppressWarnings("javadoc")
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- // This method must be overridden if your detector returns
- // attribute names from getApplicableAttributes
- assert false;
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public Collection<String> getApplicableElements() {
- return null;
- }
-
- @Nullable
- @SuppressWarnings("javadoc")
- public Collection<String> getApplicableAttributes() {
- return null;
- }
-
- // ---- Dummy implementations to make implementing JavaScanner easier: ----
-
- @Nullable @SuppressWarnings("javadoc")
- public List<String> getApplicableMethodNames() {
- return null;
- }
-
- @Nullable @SuppressWarnings("javadoc")
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return null;
- }
-
- @Nullable @SuppressWarnings("javadoc")
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- }
-
- @SuppressWarnings("javadoc")
- public boolean appliesToResourceRefs() {
- return false;
- }
-
- @SuppressWarnings("javadoc")
- public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull Node node, @NonNull String type, @NonNull String name,
- boolean isFramework) {
- }
-
- // ---- Dummy implementations to make implementing a ClassScanner easier: ----
-
- @SuppressWarnings("javadoc")
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public List<String> getApplicableCallNames() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public List<String> getApplicableCallOwners() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- }
-
- @SuppressWarnings("javadoc")
- @Nullable
- public int[] getApplicableAsmNodeTypes() {
- return null;
- }
-
- @SuppressWarnings("javadoc")
- public void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull AbstractInsnNode instruction) {
- }
-
- // ---- Dummy implementations to make implementing an OtherFileScanner easier: ----
-
- public boolean appliesToFolder(@NonNull Scope scope, @Nullable ResourceFolderType folderType) {
- return false;
- }
-
- @NonNull
- public EnumSet<Scope> getApplicableFiles() {
- return Scope.OTHER_SCOPE;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Issue.java
deleted file mode 100644
index ceca9ca..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Issue.java
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.google.common.annotations.Beta;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-
-
-/**
- * An issue is a potential bug in an Android application. An issue is discovered
- * by a {@link Detector}, and has an associated {@link Severity}.
- * <p>
- * Issues and detectors are separate classes because a detector can discover
- * multiple different issues as it's analyzing code, and we want to be able to
- * different severities for different issues, the ability to suppress one but
- * not other issues from the same detector, and so on.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public final class Issue implements Comparable<Issue> {
- private static final String HTTP_PREFIX = "http://"; //$NON-NLS-1$
-
- private final String mId;
- private final String mDescription;
- private final String mExplanation;
- private final Category mCategory;
- private final int mPriority;
- private final Severity mSeverity;
- private String mMoreInfoUrl;
- private boolean mEnabledByDefault = true;
- private final EnumSet<Scope> mScope;
- private List<EnumSet<Scope>> mAnalysisScopes;
- private final Class<? extends Detector> mClass;
-
- // Use factory methods
- private Issue(
- @NonNull String id,
- @NonNull String description,
- @NonNull String explanation,
- @NonNull Category category,
- int priority,
- @NonNull Severity severity,
- @NonNull Class<? extends Detector> detectorClass,
- @NonNull EnumSet<Scope> scope) {
- super();
- mId = id;
- mDescription = description;
- mExplanation = explanation;
- mCategory = category;
- mPriority = priority;
- mSeverity = severity;
- mClass = detectorClass;
- mScope = scope;
- }
-
- /**
- * Creates a new issue
- *
- * @param id the fixed id of the issue
- * @param description the quick summary of the issue (one line)
- * @param explanation a full explanation of the issue, with suggestions for
- * how to fix it
- * @param category the associated category, if any
- * @param priority the priority, a number from 1 to 10 with 10 being most
- * important/severe
- * @param severity the default severity of the issue
- * @param detectorClass the class of the detector to find this issue
- * @param scope the scope of files required to analyze this issue
- * @return a new {@link Issue}
- */
- @NonNull
- public static Issue create(
- @NonNull String id,
- @NonNull String description,
- @NonNull String explanation,
- @NonNull Category category,
- int priority,
- @NonNull Severity severity,
- @NonNull Class<? extends Detector> detectorClass,
- @NonNull EnumSet<Scope> scope) {
- return new Issue(id, description, explanation, category, priority, severity,
- detectorClass, scope);
- }
-
- /**
- * Returns the unique id of this issue. These should not change over time
- * since they are used to persist the names of issues suppressed by the user
- * etc. It is typically a single camel-cased word.
- *
- * @return the associated fixed id, never null and always unique
- */
- @NonNull
- public String getId() {
- return mId;
- }
-
- /**
- * Briefly (one line) describes the kinds of checks performed by this rule
- *
- * @return a quick summary of the issue, never null
- */
- @NonNull
- public String getDescription() {
- return mDescription;
- }
-
- /**
- * Describes the error found by this rule, e.g.
- * "Buttons must define contentDescriptions". Preferably the explanation
- * should also contain a description of how the problem should be solved.
- * Additional info can be provided via {@link #getMoreInfo()}.
- * <p>
- * Note that the text may contain some simple markup, such as *'s around sentences
- * for bold text, and back quotes (`) for code fragments. You can obtain
- * the text without this markup by calling {@link #getExplanationAsSimpleText()},
- * and you can obtain the text as annotated HTML by calling
- * {@link #getExplanationAsHtml()}.
- *
- * @return an explanation of the issue, never null.
- */
- @NonNull
- public String getExplanation() {
- return mExplanation;
- }
-
- /**
- * Like {@link #getExplanation()}, but returns the text as properly escaped
- * and marked up HTML, where http URLs are linked, where words with asterisks
- * such as *this* are shown in bold, etc.
- *
- * @return the explanation of the issue, never null
- */
- @NonNull
- public String getExplanationAsHtml() {
- return convertMarkup(mExplanation, true /* html */);
- }
-
- /**
- * Like {@link #getExplanation()}, but returns the text as properly escaped
- * and marked up HTML, where http URLs are linked, where words with asterisks
- * such as *this* are shown in bold, etc.
- *
- * @return the explanation of the issue, never null
- */
- @NonNull
- public String getExplanationAsSimpleText() {
- return convertMarkup(mExplanation, false /* not html = text */);
- }
-
- /**
- * The primary category of the issue
- *
- * @return the primary category of the issue, never null
- */
- @NonNull
- public Category getCategory() {
- return mCategory;
- }
-
- /**
- * Returns a priority, in the range 1-10, with 10 being the most severe and
- * 1 the least
- *
- * @return a priority from 1 to 10
- */
- public int getPriority() {
- return mPriority;
- }
-
- /**
- * Returns the default severity of the issues found by this detector (some
- * tools may allow the user to specify custom severities for detectors).
- * <p>
- * Note that even though the normal way for an issue to be disabled is for
- * the {@link Configuration} to return {@link Severity#IGNORE}, there is a
- * {@link #isEnabledByDefault()} method which can be used to turn off issues
- * by default. This is done rather than just having the severity as the only
- * attribute on the issue such that an issue can be configured with an
- * appropriate severity (such as {@link Severity#ERROR}) even when issues
- * are disabled by default for example because they are experimental or not
- * yet stable.
- *
- * @return the severity of the issues found by this detector
- */
- @NonNull
- public Severity getDefaultSeverity() {
- return mSeverity;
- }
-
- /**
- * Returns a link (a URL string) to more information, or null
- *
- * @return a link to more information, or null
- */
- @Nullable
- public String getMoreInfo() {
- return mMoreInfoUrl;
- }
-
- /**
- * Returns whether this issue should be enabled by default, unless the user
- * has explicitly disabled it.
- *
- * @return true if this issue should be enabled by default
- */
- public boolean isEnabledByDefault() {
- return mEnabledByDefault;
- }
-
- /**
- * Returns the scope required to analyze the code to detect this issue.
- * This is determined by the detectors which reports the issue.
- *
- * @return the required scope
- */
- @NonNull
- public EnumSet<Scope> getScope() {
- return mScope;
- }
-
- /**
- * Sorts the detectors alphabetically by id. This is intended to make it
- * convenient to store settings for detectors in a fixed order. It is not
- * intended as the order to be shown to the user; for that, a tool embedding
- * lint might consider the priorities, categories, severities etc of the
- * various detectors.
- *
- * @param other the {@link Issue} to compare this issue to
- */
- @Override
- public int compareTo(Issue other) {
- return getId().compareTo(other.getId());
- }
-
- /**
- * Sets a more info URL string
- *
- * @param moreInfoUrl url string
- * @return this, for constructor chaining
- */
- @NonNull
- public Issue setMoreInfo(@NonNull String moreInfoUrl) {
- mMoreInfoUrl = moreInfoUrl;
- return this;
- }
-
- /**
- * Sets whether this issue is enabled by default.
- *
- * @param enabledByDefault whether the issue should be enabled by default
- * @return this, for constructor chaining
- */
- @NonNull
- public Issue setEnabledByDefault(boolean enabledByDefault) {
- mEnabledByDefault = enabledByDefault;
- return this;
- }
-
- /**
- * Returns the sets of scopes required to analyze this issue, or null if all
- * scopes named by {@link Issue#getScope()} are necessary. Note that only
- * <b>one</b> match out of this collection is required, not all, and that
- * the scope set returned by {@link #getScope()} does not have to be returned
- * by this method, but is always implied to be included.
- * <p>
- * The scopes returned by {@link Issue#getScope()} list all the various
- * scopes that are <b>affected</b> by this issue, meaning the detector
- * should consider it. Frequently, the detector must analyze all these
- * scopes in order to properly decide whether an issue is found. For
- * example, the unused resource detector needs to consider both the XML
- * resource files and the Java source files in order to decide if a resource
- * is unused. If it analyzes just the Java files for example, it might
- * incorrectly conclude that a resource is unused because it did not
- * discover a resource reference in an XML file.
- * <p>
- * However, there are other issues where the issue can occur in a variety of
- * files, but the detector can consider each in isolation. For example, the
- * API checker is affected by both XML files and Java class files (detecting
- * both layout constructor references in XML layout files as well as code
- * references in .class files). It doesn't have to analyze both; it is
- * capable of incrementally analyzing just an XML file, or just a class
- * file, without considering the other.
- * <p>
- * The required scope list provides a list of scope sets that can be used to
- * analyze this issue. For each scope set, all the scopes must be matched by
- * the incremental analysis, but any one of the scope sets can be analyzed
- * in isolation.
- * <p>
- * The required scope list is not required to include the full scope set
- * returned by {@link #getScope()}; that set is always assumed to be
- * included.
- * <p>
- * NOTE: You would normally call {@link #isAdequate(EnumSet)} rather
- * than calling this method directly.
- *
- * @return a list of required scopes, or null.
- */
- @Nullable
- public Collection<EnumSet<Scope>> getAnalysisScopes() {
- return mAnalysisScopes;
- }
-
- /**
- * Sets the collection of scopes that are allowed to be analyzed independently.
- * See the {@link #getAnalysisScopes()} method for a full explanation.
- * Note that you usually want to just call {@link #addAnalysisScope(EnumSet)}
- * instead of constructing a list up front and passing it in here. This
- * method exists primarily such that commonly used share sets of analysis
- * scopes can be reused and set directly.
- *
- * @param required the collection of scopes
- * @return this, for constructor chaining
- */
- @NonNull
- public Issue setAnalysisScopes(@Nullable List<EnumSet<Scope>> required) {
- mAnalysisScopes = required;
-
- return this;
- }
-
- /**
- * Returns true if the given scope is adequate for analyzing this issue.
- * This looks through the analysis scopes (see
- * {@link #addAnalysisScope(EnumSet)}) and if the scope passed in fully
- * covers at least one of them, or if it covers the scope of the issue
- * itself (see {@link #getScope()}, which should be a superset of all the
- * analysis scopes) returns true.
- * <p>
- * The scope set returned by {@link Issue#getScope()} lists all the various
- * scopes that are <b>affected</b> by this issue, meaning the detector
- * should consider it. Frequently, the detector must analyze all these
- * scopes in order to properly decide whether an issue is found. For
- * example, the unused resource detector needs to consider both the XML
- * resource files and the Java source files in order to decide if a resource
- * is unused. If it analyzes just the Java files for example, it might
- * incorrectly conclude that a resource is unused because it did not
- * discover a resource reference in an XML file.
- * <p>
- * However, there are other issues where the issue can occur in a variety of
- * files, but the detector can consider each in isolation. For example, the
- * API checker is affected by both XML files and Java class files (detecting
- * both layout constructor references in XML layout files as well as code
- * references in .class files). It doesn't have to analyze both; it is
- * capable of incrementally analyzing just an XML file, or just a class
- * file, without considering the other.
- * <p>
- * An issue can register additional scope sets that can are adequate
- * for analyzing the issue, by calling {@link #addAnalysisScope(EnumSet)}.
- * This method returns true if the given scope matches one or more analysis
- * scope, or the overall scope.
- *
- * @param scope the scope available for analysis
- * @return true if this issue can be analyzed with the given available scope
- */
- public boolean isAdequate(@NonNull EnumSet<Scope> scope) {
- if (scope.containsAll(mScope)) {
- return true;
- }
-
- if (mAnalysisScopes != null) {
- for (EnumSet<Scope> analysisScope : mAnalysisScopes) {
- if (mScope.containsAll(analysisScope)) {
- return true;
- }
- }
- }
-
- if (this == IssueRegistry.LINT_ERROR || this == IssueRegistry.PARSER_ERROR) {
- return true;
- }
-
- return false;
- }
-
- /**
- * Adds a scope set that can be analyzed independently to uncover this issue.
- * See the {@link #getAnalysisScopes()} method for a full explanation.
- * Note that the {@link #getScope()} does not have to be added here; it is
- * always considered an analysis scope.
- *
- * @param scope the additional scope which can analyze this issue independently
- * @return this, for constructor chaining
- */
- public Issue addAnalysisScope(@Nullable EnumSet<Scope> scope) {
- if (mAnalysisScopes == null) {
- mAnalysisScopes = new ArrayList<EnumSet<Scope>>(2);
- }
- mAnalysisScopes.add(scope);
-
- return this;
- }
-
- /**
- * Returns the class of the detector to use to find this issue
- *
- * @return the class of the detector to use to find this issue
- */
- @NonNull
- public Class<? extends Detector> getDetectorClass() {
- return mClass;
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- /**
- * Converts the given markup text to HTML or text, depending on the.
- * <p>
- * This will recognize the following formatting conventions:
- * <ul>
- * <li>HTTP urls (http://...)
- * <li>Sentences immediately surrounded by * will be shown as bold.
- * <li>Sentences immediately surrounded by ` will be shown using monospace
- * fonts
- * </ul>
- * Furthermore, newlines are converted to br's when converting newlines.
- * Note: It does not insert {@code <html>} tags around the fragment for HTML output.
- * <p>
- * TODO: Consider switching to the restructured text format -
- * http://docutils.sourceforge.net/docs/user/rst/quickstart.html
- *
- * @param text the text to be formatted
- * @param html whether to convert into HTML or text
- * @return the corresponding HTML or text properly formatted
- */
- @NonNull
- public static String convertMarkup(@NonNull String text, boolean html) {
- StringBuilder sb = new StringBuilder(3 * text.length() / 2);
-
- char prev = 0;
- int flushIndex = 0;
- int n = text.length();
- for (int i = 0; i < n; i++) {
- char c = text.charAt(i);
- if ((c == '*' || c == '`' && i < n - 1)) {
- // Scout ahead for range end
- if (!Character.isLetterOrDigit(prev)
- && !Character.isWhitespace(text.charAt(i + 1))) {
- // Found * or ~ immediately before a letter, and not in the middle of a word
- // Find end
- int end = text.indexOf(c, i + 1);
- if (end != -1 && (end == n - 1 || !Character.isLetter(text.charAt(end + 1)))) {
- if (i > flushIndex) {
- appendEscapedText(sb, text, html, flushIndex, i);
- }
- if (html) {
- String tag = c == '*' ? "b" : "code"; //$NON-NLS-1$ //$NON-NLS-2$
- sb.append('<').append(tag).append('>');
- appendEscapedText(sb, text, html, i + 1, end);
- sb.append('<').append('/').append(tag).append('>');
- } else {
- appendEscapedText(sb, text, html, i + 1, end);
- }
- flushIndex = end + 1;
- i = flushIndex - 1; // -1: account for the i++ in the loop
- }
- }
- } else if (html && c == 'h' && i < n - 1 && text.charAt(i + 1) == 't'
- && text.startsWith(HTTP_PREFIX, i) && !Character.isLetterOrDigit(prev)) {
- // Find url end
- int end = i + HTTP_PREFIX.length();
- while (end < n) {
- char d = text.charAt(end);
- if (Character.isWhitespace(d)) {
- break;
- }
- end++;
- }
- char last = text.charAt(end - 1);
- if (last == '.' || last == ')' || last == '!') {
- end--;
- }
- if (end > i + HTTP_PREFIX.length()) {
- if (i > flushIndex) {
- appendEscapedText(sb, text, html, flushIndex, i);
- }
-
- String url = text.substring(i, end);
- sb.append("<a href=\""); //$NON-NLS-1$
- sb.append(url);
- sb.append('"').append('>');
- sb.append(url);
- sb.append("</a>"); //$NON-NLS-1$
-
- flushIndex = end;
- i = flushIndex - 1; // -1: account for the i++ in the loop
- }
- }
- prev = c;
- }
-
- if (flushIndex < n) {
- appendEscapedText(sb, text, html, flushIndex, n);
- }
-
- return sb.toString();
- }
-
- private static void appendEscapedText(StringBuilder sb, String text, boolean html,
- int start, int end) {
- if (html) {
- for (int i = start; i < end; i++) {
- char c = text.charAt(i);
- if (c == '<') {
- sb.append("&lt;"); //$NON-NLS-1$
- } else if (c == '&') {
- sb.append("&amp;"); //$NON-NLS-1$
- } else if (c == '\n') {
- sb.append("<br/>\n");
- } else {
- if (c > 255) {
- sb.append("&#"); //$NON-NLS-1$
- sb.append(Integer.toString(c));
- sb.append(';');
- } else {
- sb.append(c);
- }
- }
- }
- } else {
- for (int i = start; i < end; i++) {
- char c = text.charAt(i);
- sb.append(c);
- }
- }
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
deleted file mode 100644
index d91f423..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.IJavaParser;
-import com.android.tools.lint.client.api.LintDriver;
-
-import java.io.File;
-
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.Node;
-
-/**
- * A {@link Context} used when checking Java files.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-public class JavaContext extends Context {
- /** The parse tree */
- public Node compilationUnit;
- /** The parser which produced the parse tree */
- public IJavaParser parser;
-
- /**
- * Constructs a {@link JavaContext} for running lint on the given file, with
- * the given scope, in the given project reporting errors to the given
- * client.
- *
- * @param driver the driver running through the checks
- * @param project the project to run lint on which contains the given file
- * @param main the main project if this project is a library project, or
- * null if this is not a library project. The main project is
- * the root project of all library projects, not necessarily the
- * directly including project.
- * @param file the file to be analyzed
- */
- public JavaContext(
- @NonNull LintDriver driver,
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File file) {
- super(driver, project, main, file);
- }
-
- /**
- * Returns a location for the given node
- *
- * @param node the AST node to get a location for
- * @return a location for the given node
- */
- @NonNull
- public Location getLocation(@NonNull Node node) {
- if (parser != null) {
- return parser.getLocation(this, node);
- }
-
- return new Location(file, null, null);
- }
-
- @Override
- public void report(@NonNull Issue issue, @Nullable Location location,
- @NonNull String message, @Nullable Object data) {
- if (mDriver.isSuppressed(issue, compilationUnit)) {
- return;
- }
- super.report(issue, location, message, data);
- }
-
- /**
- * Reports an issue applicable to a given AST node. The AST node is used as the
- * scope to check for suppress lint annotations.
- *
- * @param issue the issue to report
- * @param scope the AST node scope the error applies to. The lint infrastructure
- * will check whether there are suppress annotations on this node (or its enclosing
- * nodes) and if so suppress the warning without involving the client.
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- * @param data any associated data, or null
- */
- public void report(
- @NonNull Issue issue,
- @Nullable Node scope,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- if (scope != null && mDriver.isSuppressed(issue, scope)) {
- return;
- }
- super.report(issue, location, message, data);
- }
-
-
- @Nullable
- public static Node findSurroundingMethod(Node scope) {
- while (scope != null) {
- Class<? extends Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) {
- return scope;
- }
-
- scope = scope.getParent();
- }
-
- return null;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java
deleted file mode 100644
index b24c1a9..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LayoutDetector.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_PADDING;
-import static com.android.SdkConstants.ATTR_PADDING_BOTTOM;
-import static com.android.SdkConstants.ATTR_PADDING_LEFT;
-import static com.android.SdkConstants.ATTR_PADDING_RIGHT;
-import static com.android.SdkConstants.ATTR_PADDING_TOP;
-import static com.android.SdkConstants.VALUE_FILL_PARENT;
-import static com.android.SdkConstants.VALUE_MATCH_PARENT;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.google.common.annotations.Beta;
-
-import org.w3c.dom.Element;
-
-/**
- * Abstract class specifically intended for layout detectors which provides some
- * common utility methods shared by layout detectors.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class LayoutDetector extends ResourceXmlDetector {
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT;
- }
-
- private static boolean isFillParent(@NonNull Element element, @NonNull String dimension) {
- String width = element.getAttributeNS(ANDROID_URI, dimension);
- return width.equals(VALUE_MATCH_PARENT) || width.equals(VALUE_FILL_PARENT);
- }
-
- protected static boolean isWidthFillParent(@NonNull Element element) {
- return isFillParent(element, ATTR_LAYOUT_WIDTH);
- }
-
- protected static boolean isHeightFillParent(@NonNull Element element) {
- return isFillParent(element, ATTR_LAYOUT_HEIGHT);
- }
-
- protected boolean hasPadding(@NonNull Element root) {
- return root.hasAttributeNS(ANDROID_URI, ATTR_PADDING)
- || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_LEFT)
- || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_RIGHT)
- || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_TOP)
- || root.hasAttributeNS(ANDROID_URI, ATTR_PADDING_BOTTOM);
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
deleted file mode 100644
index 9dec569..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
+++ /dev/null
@@ -1,798 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.BIN_FOLDER;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.FolderTypeRelationship;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.utils.PositionXmlParser;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Splitter;
-import com.google.common.collect.Iterables;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldNode;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-
-import lombok.ast.ImportDeclaration;
-
-
-/**
- * Useful utility methods related to lint.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class LintUtils {
- // Utility class, do not instantiate
- private LintUtils() {
- }
-
- /**
- * Format a list of strings, and cut of the list at {@code maxItems} if the
- * number of items are greater.
- *
- * @param strings the list of strings to print out as a comma separated list
- * @param maxItems the maximum number of items to print
- * @return a comma separated list
- */
- @NonNull
- public static String formatList(@NonNull List<String> strings, int maxItems) {
- StringBuilder sb = new StringBuilder(20 * strings.size());
-
- for (int i = 0, n = strings.size(); i < n; i++) {
- if (sb.length() > 0) {
- sb.append(", "); //$NON-NLS-1$
- }
- sb.append(strings.get(i));
-
- if (maxItems > 0 && i == maxItems - 1 && n > maxItems) {
- sb.append(String.format("... (%1$d more)", n - i - 1));
- break;
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Determine if the given type corresponds to a resource that has a unique
- * file
- *
- * @param type the resource type to check
- * @return true if the given type corresponds to a file-type resource
- */
- public static boolean isFileBasedResourceType(@NonNull ResourceType type) {
- List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);
- for (ResourceFolderType folderType : folderTypes) {
- if (folderType != ResourceFolderType.VALUES) {
- if (type == ResourceType.ID) {
- return false;
- }
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if the given file represents an XML file
- *
- * @param file the file to be checked
- * @return true if the given file is an xml file
- */
- public static boolean isXmlFile(@NonNull File file) {
- String string = file.getName();
- return string.regionMatches(true, string.length() - DOT_XML.length(),
- DOT_XML, 0, DOT_XML.length());
- }
-
- /**
- * Case insensitive ends with
- *
- * @param string the string to be tested whether it ends with the given
- * suffix
- * @param suffix the suffix to check
- * @return true if {@code string} ends with {@code suffix},
- * case-insensitively.
- */
- public static boolean endsWith(@NonNull String string, @NonNull String suffix) {
- return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
- suffix, 0, suffix.length());
- }
-
- /**
- * Case insensitive starts with
- *
- * @param string the string to be tested whether it starts with the given prefix
- * @param prefix the prefix to check
- * @param offset the offset to start checking with
- * @return true if {@code string} starts with {@code prefix},
- * case-insensitively.
- */
- public static boolean startsWith(@NonNull String string, @NonNull String prefix, int offset) {
- return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
- }
-
- /**
- * Returns the basename of the given filename, unless it's a dot-file such as ".svn".
- *
- * @param fileName the file name to extract the basename from
- * @return the basename (the filename without the file extension)
- */
- public static String getBaseName(@NonNull String fileName) {
- int extension = fileName.indexOf('.');
- if (extension > 0) {
- return fileName.substring(0, extension);
- } else {
- return fileName;
- }
- }
-
- /**
- * Returns the children elements of the given node
- *
- * @param node the parent node
- * @return a list of element children, never null
- */
- @NonNull
- public static List<Element> getChildren(@NonNull Node node) {
- NodeList childNodes = node.getChildNodes();
- List<Element> children = new ArrayList<Element>(childNodes.getLength());
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- children.add((Element) child);
- }
- }
-
- return children;
- }
-
- /**
- * Returns the <b>number</b> of children of the given node
- *
- * @param node the parent node
- * @return the count of element children
- */
- public static int getChildCount(@NonNull Node node) {
- NodeList childNodes = node.getChildNodes();
- int childCount = 0;
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- childCount++;
- }
- }
-
- return childCount;
- }
-
- /**
- * Returns true if the given element is the root element of its document
- *
- * @param element the element to test
- * @return true if the element is the root element
- */
- public static boolean isRootElement(Element element) {
- return element == element.getOwnerDocument().getDocumentElement();
- }
-
- /**
- * Returns the given id without an {@code @id/} or {@code @+id} prefix
- *
- * @param id the id to strip
- * @return the stripped id, never null
- */
- @NonNull
- public static String stripIdPrefix(@Nullable String id) {
- if (id == null) {
- return "";
- } else if (id.startsWith(NEW_ID_PREFIX)) {
- return id.substring(NEW_ID_PREFIX.length());
- } else if (id.startsWith(ID_PREFIX)) {
- return id.substring(ID_PREFIX.length());
- }
-
- return id;
- }
-
- /**
- * Returns true if the given two id references match. This is similar to
- * String equality, but it also considers "{@code @+id/foo == @id/foo}.
- *
- * @param id1 the first id to compare
- * @param id2 the second id to compare
- * @return true if the two id references refer to the same id
- */
- public static boolean idReferencesMatch(String id1, String id2) {
- if (id1.startsWith(NEW_ID_PREFIX)) {
- if (id2.startsWith(NEW_ID_PREFIX)) {
- return id1.equals(id2);
- } else {
- assert id2.startsWith(ID_PREFIX);
- return ((id1.length() - id2.length())
- == (NEW_ID_PREFIX.length() - ID_PREFIX.length()))
- && id1.regionMatches(NEW_ID_PREFIX.length(), id2,
- ID_PREFIX.length(),
- id2.length() - ID_PREFIX.length());
- }
- } else {
- assert id1.startsWith(ID_PREFIX);
- if (id2.startsWith(ID_PREFIX)) {
- return id1.equals(id2);
- } else {
- assert id2.startsWith(NEW_ID_PREFIX);
- return (id2.length() - id1.length()
- == (NEW_ID_PREFIX.length() - ID_PREFIX.length()))
- && id2.regionMatches(NEW_ID_PREFIX.length(), id1,
- ID_PREFIX.length(),
- id1.length() - ID_PREFIX.length());
- }
- }
- }
-
- /**
- * Computes the edit distance (number of insertions, deletions or substitutions
- * to edit one string into the other) between two strings. In particular,
- * this will compute the Levenshtein distance.
- * <p>
- * See http://en.wikipedia.org/wiki/Levenshtein_distance for details.
- *
- * @param s the first string to compare
- * @param t the second string to compare
- * @return the edit distance between the two strings
- */
- public static int editDistance(@NonNull String s, @NonNull String t) {
- int m = s.length();
- int n = t.length();
- int[][] d = new int[m + 1][n + 1];
- for (int i = 0; i <= m; i++) {
- d[i][0] = i;
- }
- for (int j = 0; j <= n; j++) {
- d[0][j] = j;
- }
- for (int j = 1; j <= n; j++) {
- for (int i = 1; i <= m; i++) {
- if (s.charAt(i - 1) == t.charAt(j - 1)) {
- d[i][j] = d[i - 1][j - 1];
- } else {
- int deletion = d[i - 1][j] + 1;
- int insertion = d[i][j - 1] + 1;
- int substitution = d[i - 1][j - 1] + 1;
- d[i][j] = Math.min(deletion, Math.min(insertion, substitution));
- }
- }
- }
-
- return d[m][n];
- }
-
- /**
- * Returns true if assertions are enabled
- *
- * @return true if assertions are enabled
- */
- @SuppressWarnings("all")
- public static boolean assertionsEnabled() {
- boolean assertionsEnabled = false;
- assert assertionsEnabled = true; // Intentional side-effect
- return assertionsEnabled;
- }
-
- /**
- * Returns the layout resource name for the given layout file
- *
- * @param layoutFile the file pointing to the layout
- * @return the layout resource name, not including the {@code @layout}
- * prefix
- */
- public static String getLayoutName(File layoutFile) {
- String name = layoutFile.getName();
- int dotIndex = name.indexOf('.');
- if (dotIndex != -1) {
- name = name.substring(0, dotIndex);
- }
- return name;
- }
-
- /**
- * Splits the given path into its individual parts, attempting to be
- * tolerant about path separators (: or ;). It can handle possibly ambiguous
- * paths, such as {@code c:\foo\bar:\other}, though of course these are to
- * be avoided if possible.
- *
- * @param path the path variable to split, which can use both : and ; as
- * path separators.
- * @return the individual path components as an Iterable of strings
- */
- public static Iterable<String> splitPath(String path) {
- if (path.indexOf(';') != -1) {
- return Splitter.on(';').omitEmptyStrings().trimResults().split(path);
- }
-
- List<String> combined = new ArrayList<String>();
- Iterables.addAll(combined, Splitter.on(':').omitEmptyStrings().trimResults().split(path));
- for (int i = 0, n = combined.size(); i < n; i++) {
- String p = combined.get(i);
- if (p.length() == 1 && i < n - 1 && Character.isLetter(p.charAt(0))
- // Technically, Windows paths do not have to have a \ after the :,
- // which means it would be using the current directory on that drive,
- // but that's unlikely to be the case in a path since it would have
- // unpredictable results
- && !combined.get(i+1).isEmpty() && combined.get(i+1).charAt(0) == '\\') {
- combined.set(i, p + ':' + combined.get(i+1));
- combined.remove(i+1);
- n--;
- continue;
- }
- }
-
- return combined;
- }
-
- /**
- * Computes the shared parent among a set of files (which may be null).
- *
- * @param files the set of files to be checked
- * @return the closest common ancestor file, or null if none was found
- */
- @Nullable
- public static File getCommonParent(@NonNull List<File> files) {
- int fileCount = files.size();
- if (fileCount == 0) {
- return null;
- } else if (fileCount == 1) {
- return files.get(0);
- } else if (fileCount == 2) {
- return getCommonParent(files.get(0), files.get(1));
- } else {
- File common = files.get(0);
- for (int i = 1; i < fileCount; i++) {
- common = getCommonParent(common, files.get(i));
- if (common == null) {
- return null;
- }
- }
-
- return common;
- }
- }
-
- /**
- * Computes the closest common parent path between two files.
- *
- * @param file1 the first file to be compared
- * @param file2 the second file to be compared
- * @return the closest common ancestor file, or null if the two files have
- * no common parent
- */
- @Nullable
- public static File getCommonParent(@NonNull File file1, @NonNull File file2) {
- if (file1.equals(file2)) {
- return file1;
- } else if (file1.getPath().startsWith(file2.getPath())) {
- return file2;
- } else if (file2.getPath().startsWith(file1.getPath())) {
- return file1;
- } else {
- // Dumb and simple implementation
- File first = file1.getParentFile();
- while (first != null) {
- File second = file2.getParentFile();
- while (second != null) {
- if (first.equals(second)) {
- return first;
- }
- second = second.getParentFile();
- }
-
- first = first.getParentFile();
- }
- }
- return null;
- }
-
- private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
- private static final String UTF_16 = "UTF_16"; //$NON-NLS-1$
- private static final String UTF_16LE = "UTF_16LE"; //$NON-NLS-1$
-
- /**
- * Returns the encoded String for the given file. This is usually the
- * same as {@code Files.toString(file, Charsets.UTF8}, but if there's a UTF byte order mark
- * (for UTF8, UTF_16 or UTF_16LE), use that instead.
- *
- * @param client the client to use for I/O operations
- * @param file the file to read from
- * @return the string
- * @throws IOException if the file cannot be read properly
- */
- @NonNull
- public static String getEncodedString(
- @NonNull LintClient client,
- @NonNull File file) throws IOException {
- byte[] bytes = client.readBytes(file);
- if (endsWith(file.getName(), DOT_XML)) {
- return PositionXmlParser.getXmlString(bytes);
- }
-
- return getEncodedString(bytes);
- }
-
- /**
- * Returns the String corresponding to the given data. This is usually the
- * same as {@code new String(data)}, but if there's a UTF byte order mark
- * (for UTF8, UTF_16 or UTF_16LE), use that instead.
- * <p>
- * NOTE: For XML files, there is the additional complication that there
- * could be a {@code encoding=} attribute in the prologue. For those files,
- * use {@link PositionXmlParser#getXmlString(byte[])} instead.
- *
- * @param data the byte array to construct the string from
- * @return the string
- */
- @NonNull
- public static String getEncodedString(@Nullable byte[] data) {
- if (data == null) {
- return "";
- }
-
- int offset = 0;
- String defaultCharset = UTF_8;
- String charset = null;
- // Look for the byte order mark, to see if we need to remove bytes from
- // the input stream (and to determine whether files are big endian or little endian) etc
- // for files which do not specify the encoding.
- // See http://unicode.org/faq/utf_bom.html#BOM for more.
- if (data.length > 4) {
- if (data[0] == (byte)0xef && data[1] == (byte)0xbb && data[2] == (byte)0xbf) {
- // UTF-8
- defaultCharset = charset = UTF_8;
- offset += 3;
- } else if (data[0] == (byte)0xfe && data[1] == (byte)0xff) {
- // UTF-16, big-endian
- defaultCharset = charset = UTF_16;
- offset += 2;
- } else if (data[0] == (byte)0x0 && data[1] == (byte)0x0
- && data[2] == (byte)0xfe && data[3] == (byte)0xff) {
- // UTF-32, big-endian
- defaultCharset = charset = "UTF_32"; //$NON-NLS-1$
- offset += 4;
- } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe
- && data[2] == (byte)0x0 && data[3] == (byte)0x0) {
- // UTF-32, little-endian. We must check for this *before* looking for
- // UTF_16LE since UTF_32LE has the same prefix!
- defaultCharset = charset = "UTF_32LE"; //$NON-NLS-1$
- offset += 4;
- } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe) {
- // UTF-16, little-endian
- defaultCharset = charset = UTF_16LE;
- offset += 2;
- }
- }
- int length = data.length - offset;
-
- // Guess encoding by searching for an encoding= entry in the first line.
- boolean seenOddZero = false;
- boolean seenEvenZero = false;
- for (int lineEnd = offset; lineEnd < data.length; lineEnd++) {
- if (data[lineEnd] == 0) {
- if ((lineEnd - offset) % 2 == 0) {
- seenEvenZero = true;
- } else {
- seenOddZero = true;
- }
- } else if (data[lineEnd] == '\n' || data[lineEnd] == '\r') {
- break;
- }
- }
-
- if (charset == null) {
- charset = seenOddZero ? UTF_16LE : seenEvenZero ? UTF_16 : UTF_8;
- }
-
- String text = null;
- try {
- text = new String(data, offset, length, charset);
- } catch (UnsupportedEncodingException e) {
- try {
- if (charset != defaultCharset) {
- text = new String(data, offset, length, defaultCharset);
- }
- } catch (UnsupportedEncodingException u) {
- // Just use the default encoding below
- }
- }
- if (text == null) {
- text = new String(data, offset, length);
- }
- return text;
- }
-
- /**
- * Returns true if the given class node represents a static inner class.
- *
- * @param classNode the inner class to be checked
- * @return true if the class node represents an inner class that is static
- */
- public static boolean isStaticInnerClass(@NonNull ClassNode classNode) {
- // Note: We can't just filter out static inner classes like this:
- // (classNode.access & Opcodes.ACC_STATIC) != 0
- // because the static flag only appears on methods and fields in the class
- // file. Instead, look for the synthetic this pointer.
-
- @SuppressWarnings("rawtypes") // ASM API
- List fieldList = classNode.fields;
- for (Object f : fieldList) {
- FieldNode field = (FieldNode) f;
- if (field.name.startsWith("this$") && (field.access & Opcodes.ACC_SYNTHETIC) != 0) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Returns the previous opcode prior to the given node, ignoring label and
- * line number nodes
- *
- * @param node the node to look up the previous opcode for
- * @return the previous opcode, or {@link Opcodes#NOP} if no previous node
- * was found
- */
- public static int getPrevOpcode(@NonNull AbstractInsnNode node) {
- AbstractInsnNode prev = getPrevInstruction(node);
- if (prev != null) {
- return prev.getOpcode();
- } else {
- return Opcodes.NOP;
- }
- }
-
- /**
- * Returns the previous instruction prior to the given node, ignoring label
- * and line number nodes.
- *
- * @param node the node to look up the previous instruction for
- * @return the previous instruction, or null if no previous node was found
- */
- @Nullable
- public static AbstractInsnNode getPrevInstruction(@NonNull AbstractInsnNode node) {
- AbstractInsnNode prev = node;
- while (true) {
- prev = prev.getPrevious();
- if (prev == null) {
- return null;
- } else {
- int type = prev.getType();
- if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL
- && type != AbstractInsnNode.FRAME) {
- return prev;
- }
- }
- }
- }
-
- /**
- * Returns the next opcode after to the given node, ignoring label and line
- * number nodes
- *
- * @param node the node to look up the next opcode for
- * @return the next opcode, or {@link Opcodes#NOP} if no next node was found
- */
- public static int getNextOpcode(@NonNull AbstractInsnNode node) {
- AbstractInsnNode next = getNextInstruction(node);
- if (next != null) {
- return next.getOpcode();
- } else {
- return Opcodes.NOP;
- }
- }
-
- /**
- * Returns the next instruction after to the given node, ignoring label and
- * line number nodes.
- *
- * @param node the node to look up the next node for
- * @return the next instruction, or null if no next node was found
- */
- @Nullable
- public static AbstractInsnNode getNextInstruction(@NonNull AbstractInsnNode node) {
- AbstractInsnNode next = node;
- while (true) {
- next = next.getNext();
- if (next == null) {
- return null;
- } else {
- int type = next.getType();
- if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL
- && type != AbstractInsnNode.FRAME) {
- return next;
- }
- }
- }
- }
-
- /**
- * Returns true if the given directory represents an Android project
- * directory. Note: This doesn't necessarily mean it's an Eclipse directory,
- * only that it looks like it contains a logical Android project -- one
- * including a manifest file, a resource folder, etc.
- *
- * @param dir the directory to check
- * @return true if the directory looks like an Android project
- */
- public static boolean isProjectDir(@NonNull File dir) {
- boolean hasManifest = new File(dir, ANDROID_MANIFEST_XML).exists();
- if (hasManifest) {
- // Special case: the bin/ folder can also contain a copy of the
- // manifest file, but this is *not* a project directory
- if (dir.getName().equals(BIN_FOLDER)) {
- // ...unless of course it just *happens* to be a project named bin, in
- // which case we peek at its parent to see if this is the case
- dir = dir.getParentFile();
- if (dir != null && isProjectDir(dir)) {
- // Yes, it's a bin/ directory inside a real project: ignore this dir
- return false;
- }
- }
- } else if (Project.isAospFrameworksProject(dir)) {
- // Hardcoded AOSP support for the frameworks project
- return true;
- }
-
- return hasManifest;
- }
-
- /**
- * Look up the locale and region from the given parent folder name and
- * return it as a combined string, such as "en", "en-rUS", etc, or null if
- * no language is specified.
- *
- * @param folderName the folder name
- * @return the locale+region string or null
- */
- @Nullable
- public static String getLocaleAndRegion(@NonNull String folderName) {
- if (folderName.equals("values")) { //$NON-NLS-1$
- return null;
- }
-
- String locale = null;
-
- for (String qualifier : Splitter.on('-').split(folderName)) {
- int qualifierLength = qualifier.length();
- if (qualifierLength == 2) {
- char first = qualifier.charAt(0);
- char second = qualifier.charAt(1);
- if (first >= 'a' && first <= 'z' && second >= 'a' && second <= 'z') {
- locale = qualifier;
- }
- } else if (qualifierLength == 3 && qualifier.charAt(0) == 'r' && locale != null) {
- char first = qualifier.charAt(1);
- char second = qualifier.charAt(2);
- if (first >= 'A' && first <= 'Z' && second >= 'A' && second <= 'Z') {
- return locale + '-' + qualifier;
- }
- break;
- }
- }
-
- return locale;
- }
-
- /**
- * Returns true if the given class (specified by a fully qualified class
- * name) name is imported in the given compilation unit either through a fully qualified
- * import or by a wildcard import.
- *
- * @param compilationUnit the compilation unit
- * @param fullyQualifiedName the fully qualified class name
- * @return true if the given imported name refers to the given fully
- * qualified name
- */
- public static boolean isImported(
- @NonNull lombok.ast.Node compilationUnit,
- @NonNull String fullyQualifiedName) {
- int dotIndex = fullyQualifiedName.lastIndexOf('.');
- int dotLength = fullyQualifiedName.length() - dotIndex;
-
- boolean imported = false;
- for (lombok.ast.Node rootNode : compilationUnit.getChildren()) {
- if (rootNode instanceof ImportDeclaration) {
- ImportDeclaration importDeclaration = (ImportDeclaration) rootNode;
- String fqn = importDeclaration.asFullyQualifiedName();
- if (fqn.equals(fullyQualifiedName)) {
- return true;
- } else if (fullyQualifiedName.regionMatches(dotIndex, fqn,
- fqn.length() - dotLength, dotLength)) {
- // This import is importing the class name using some other prefix, so there
- // fully qualified class name cannot be imported under that name
- return false;
- } else if (importDeclaration.astStarImport()
- && fqn.regionMatches(0, fqn, 0, dotIndex + 1)) {
- imported = true;
- // but don't break -- keep searching in case there's a non-wildcard
- // import of the specific class name, e.g. if we're looking for
- // android.content.SharedPreferences.Editor, don't match on the following:
- // import android.content.SharedPreferences.*;
- // import foo.bar.Editor;
- }
- }
- }
-
- return imported;
- }
-
- /**
- * Returns the applicable build code (for
- * {@code android.os.Build.VERSION_CODES}) for the corresponding API level,
- * or null if it's unknown.
- *
- * @param api the API level to look up a version code for
- * @return the corresponding build code field name, or null
- */
- @Nullable
- public static String getBuildCode(int api) {
- // See http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
- switch (api) {
- case 1: return "BASE"; //$NON-NLS-1$
- case 2: return "BASE_1_1"; //$NON-NLS-1$
- case 3: return "CUPCAKE"; //$NON-NLS-1$
- case 4: return "DONUT"; //$NON-NLS-1$
- case 5: return "ECLAIR"; //$NON-NLS-1$
- case 6: return "ECLAIR_0_1"; //$NON-NLS-1$
- case 7: return "ECLAIR_MR1"; //$NON-NLS-1$
- case 8: return "FROYO"; //$NON-NLS-1$
- case 9: return "GINGERBREAD"; //$NON-NLS-1$
- case 10: return "GINGERBREAD_MR1"; //$NON-NLS-1$
- case 11: return "HONEYCOMB"; //$NON-NLS-1$
- case 12: return "HONEYCOMB_MR1"; //$NON-NLS-1$
- case 13: return "HONEYCOMB_MR2"; //$NON-NLS-1$
- case 14: return "ICE_CREAM_SANDWICH"; //$NON-NLS-1$
- case 15: return "ICE_CREAM_SANDWICH_MR1"; //$NON-NLS-1$
- case 16: return "JELLY_BEAN"; //$NON-NLS-1$
- case 17: return "JELLY_BEAN_MR1"; //$NON-NLS-1$
- // If you add more versions here, also update AdtUtils#getAndroidName and
- // SdkConstants#HIGHEST_KNOWN_API
- }
-
- return null;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Location.java
deleted file mode 100644
index 34116d0..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Location.java
+++ /dev/null
@@ -1,722 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.annotations.Beta;
-
-import java.io.File;
-
-/**
- * Location information for a warning
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class Location {
- private static final String SUPER_KEYWORD = "super"; //$NON-NLS-1$
-
- private final File mFile;
- private final Position mStart;
- private final Position mEnd;
- private String mMessage;
- private Location mSecondary;
- private Object mClientData;
-
- /**
- * (Private constructor, use one of the factory methods
- * {@link Location#create(File)},
- * {@link Location#create(File, Position, Position)}, or
- * {@link Location#create(File, String, int, int)}.
- * <p>
- * Constructs a new location range for the given file, from start to end. If
- * the length of the range is not known, end may be null.
- *
- * @param file the associated file (but see the documentation for
- * {@link #getFile()} for more information on what the file
- * represents)
- * @param start the starting position, or null
- * @param end the ending position, or null
- */
- protected Location(@NonNull File file, @Nullable Position start, @Nullable Position end) {
- super();
- mFile = file;
- mStart = start;
- mEnd = end;
- }
-
- /**
- * Returns the file containing the warning. Note that the file *itself* may
- * not yet contain the error. When editing a file in the IDE for example,
- * the tool could generate warnings in the background even before the
- * document is saved. However, the file is used as a identifying token for
- * the document being edited, and the IDE integration can map this back to
- * error locations in the editor source code.
- *
- * @return the file handle for the location
- */
- @NonNull
- public File getFile() {
- return mFile;
- }
-
- /**
- * The start position of the range
- *
- * @return the start position of the range, or null
- */
- @Nullable
- public Position getStart() {
- return mStart;
- }
-
- /**
- * The end position of the range
- *
- * @return the start position of the range, may be null for an empty range
- */
- @Nullable
- public Position getEnd() {
- return mEnd;
- }
-
- /**
- * Returns a secondary location associated with this location (if
- * applicable), or null.
- *
- * @return a secondary location or null
- */
- @Nullable
- public Location getSecondary() {
- return mSecondary;
- }
-
- /**
- * Sets a secondary location for this location.
- *
- * @param secondary a secondary location associated with this location
- */
- public void setSecondary(@Nullable Location secondary) {
- mSecondary = secondary;
- }
-
- /**
- * Sets a custom message for this location. This is typically used for
- * secondary locations, to describe the significance of this alternate
- * location. For example, for a duplicate id warning, the primary location
- * might say "This is a duplicate id", pointing to the second occurrence of
- * id declaration, and then the secondary location could point to the
- * original declaration with the custom message "Originally defined here".
- *
- * @param message the message to apply to this location
- */
- public void setMessage(@NonNull String message) {
- mMessage = message;
- }
-
- /**
- * Returns the custom message for this location, if any. This is typically
- * used for secondary locations, to describe the significance of this
- * alternate location. For example, for a duplicate id warning, the primary
- * location might say "This is a duplicate id", pointing to the second
- * occurrence of id declaration, and then the secondary location could point
- * to the original declaration with the custom message
- * "Originally defined here".
- *
- * @return the custom message for this location, or null
- */
- @Nullable
- public String getMessage() {
- return mMessage;
- }
-
- /**
- * Sets the client data associated with this location. This is an optional
- * field which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @param clientData the data to store with this location
- */
- public void setClientData(@Nullable Object clientData) {
- mClientData = clientData;
- }
-
- /**
- * Returns the client data associated with this location - an optional field
- * which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @return the data associated with this location
- */
- @Nullable
- public Object getClientData() {
- return mClientData;
- }
-
- @Override
- public String toString() {
- return "Location [file=" + mFile + ", start=" + mStart + ", end=" + mEnd + ", message="
- + mMessage + ']';
- }
-
- /**
- * Creates a new location for the given file
- *
- * @param file the file to create a location for
- * @return a new location
- */
- @NonNull
- public static Location create(@NonNull File file) {
- return new Location(file, null /*start*/, null /*end*/);
- }
-
- /**
- * Creates a new location for the given file and starting and ending
- * positions.
- *
- * @param file the file containing the positions
- * @param start the starting position
- * @param end the ending position
- * @return a new location
- */
- @NonNull
- public static Location create(
- @NonNull File file,
- @NonNull Position start,
- @Nullable Position end) {
- return new Location(file, start, end);
- }
-
- /**
- * Creates a new location for the given file, with the given contents, for
- * the given offset range.
- *
- * @param file the file containing the location
- * @param contents the current contents of the file
- * @param startOffset the starting offset
- * @param endOffset the ending offset
- * @return a new location
- */
- @NonNull
- public static Location create(
- @NonNull File file,
- @Nullable String contents,
- int startOffset,
- int endOffset) {
- if (startOffset < 0 || endOffset < startOffset) {
- throw new IllegalArgumentException("Invalid offsets");
- }
-
- if (contents == null) {
- return new Location(file,
- new DefaultPosition(-1, -1, startOffset),
- new DefaultPosition(-1, -1, endOffset));
- }
-
- int size = contents.length();
- endOffset = Math.min(endOffset, size);
- startOffset = Math.min(startOffset, endOffset);
- Position start = null;
- int line = 0;
- int lineOffset = 0;
- char prev = 0;
- for (int offset = 0; offset <= size; offset++) {
- if (offset == startOffset) {
- start = new DefaultPosition(line, offset - lineOffset, offset);
- }
- if (offset == endOffset) {
- Position end = new DefaultPosition(line, offset - lineOffset, offset);
- return new Location(file, start, end);
- }
- char c = contents.charAt(offset);
- if (c == '\n') {
- lineOffset = offset + 1;
- if (prev != '\r') {
- line++;
- }
- } else if (c == '\r') {
- line++;
- lineOffset = offset + 1;
- }
- prev = c;
- }
- return create(file);
- }
-
- /**
- * Creates a new location for the given file, with the given contents, for
- * the given line number.
- *
- * @param file the file containing the location
- * @param contents the current contents of the file
- * @param line the line number (0-based) for the position
- * @return a new location
- */
- @NonNull
- public static Location create(@NonNull File file, @NonNull String contents, int line) {
- return create(file, contents, line, null, null, null);
- }
-
- /**
- * Creates a new location for the given file, with the given contents, for
- * the given line number.
- *
- * @param file the file containing the location
- * @param contents the current contents of the file
- * @param line the line number (0-based) for the position
- * @param patternStart an optional pattern to search for from the line
- * match; if found, adjust the column and offsets to begin at the
- * pattern start
- * @param patternEnd an optional pattern to search for behind the start
- * pattern; if found, adjust the end offset to match the end of
- * the pattern
- * @param hints optional additional information regarding the pattern search
- * @return a new location
- */
- @NonNull
- public static Location create(@NonNull File file, @NonNull String contents, int line,
- @Nullable String patternStart, @Nullable String patternEnd,
- @Nullable SearchHints hints) {
- int currentLine = 0;
- int offset = 0;
- while (currentLine < line) {
- offset = contents.indexOf('\n', offset);
- if (offset == -1) {
- return create(file);
- }
- currentLine++;
- offset++;
- }
-
- if (line == currentLine) {
- if (patternStart != null) {
- SearchDirection direction = SearchDirection.NEAREST;
- if (hints != null) {
- direction = hints.mDirection;
- }
-
- int index;
- if (direction == SearchDirection.BACKWARD) {
- index = findPreviousMatch(contents, offset, patternStart, hints);
- line = adjustLine(contents, line, offset, index);
- } else if (direction == SearchDirection.EOL_BACKWARD) {
- int lineEnd = contents.indexOf('\n', offset);
- if (lineEnd == -1) {
- lineEnd = contents.length();
- }
-
- index = findPreviousMatch(contents, lineEnd, patternStart, hints);
- line = adjustLine(contents, line, offset, index);
- } else if (direction == SearchDirection.FORWARD) {
- index = findNextMatch(contents, offset, patternStart, hints);
- line = adjustLine(contents, line, offset, index);
- } else {
- assert direction == SearchDirection.NEAREST;
-
- int before = findPreviousMatch(contents, offset, patternStart, hints);
- int after = findNextMatch(contents, offset, patternStart, hints);
-
- if (before == -1) {
- index = after;
- line = adjustLine(contents, line, offset, index);
- } else if (after == -1) {
- index = before;
- line = adjustLine(contents, line, offset, index);
- } else if (offset - before < after - offset) {
- index = before;
- line = adjustLine(contents, line, offset, index);
- } else {
- index = after;
- line = adjustLine(contents, line, offset, index);
- }
- }
-
- if (index != -1) {
- int lineStart = contents.lastIndexOf('\n', index);
- if (lineStart == -1) {
- lineStart = 0;
- } else {
- lineStart++; // was pointing to the previous line's CR, not line start
- }
- int column = index - lineStart;
- if (patternEnd != null) {
- int end = contents.indexOf(patternEnd, offset + patternStart.length());
- if (end != -1) {
- return new Location(file, new DefaultPosition(line, column, index),
- new DefaultPosition(line, -1, end + patternEnd.length()));
- }
- } else if (hints != null && (hints.isJavaSymbol() || hints.isWholeWord())) {
- if (hints.isConstructor() && contents.startsWith(SUPER_KEYWORD, index)) {
- patternStart = SUPER_KEYWORD;
- }
- return new Location(file, new DefaultPosition(line, column, index),
- new DefaultPosition(line, column + patternStart.length(),
- index + patternStart.length()));
- }
- return new Location(file, new DefaultPosition(line, column, index),
- new DefaultPosition(line, column, index + patternStart.length()));
- }
- }
-
- Position position = new DefaultPosition(line, -1, offset);
- return new Location(file, position, position);
- }
-
- return create(file);
- }
-
- private static int findPreviousMatch(@NonNull String contents, int offset, String pattern,
- @Nullable SearchHints hints) {
- while (true) {
- int index = contents.lastIndexOf(pattern, offset);
- if (index == -1) {
- return -1;
- } else {
- if (isMatch(contents, index, pattern, hints)) {
- return index;
- } else {
- offset = index - pattern.length();
- }
- }
- }
- }
-
- private static int findNextMatch(@NonNull String contents, int offset, String pattern,
- @Nullable SearchHints hints) {
- int constructorIndex = -1;
- if (hints != null && hints.isConstructor()) {
- // Special condition: See if the call is referenced as "super" instead.
- assert hints.isWholeWord();
- int index = contents.indexOf(SUPER_KEYWORD, offset);
- if (index != -1 && isMatch(contents, index, SUPER_KEYWORD, hints)) {
- constructorIndex = index;
- }
- }
-
- while (true) {
- int index = contents.indexOf(pattern, offset);
- if (index == -1) {
- return constructorIndex;
- } else {
- if (isMatch(contents, index, pattern, hints)) {
- if (constructorIndex != -1) {
- return Math.min(constructorIndex, index);
- }
- return index;
- } else {
- offset = index + pattern.length();
- }
- }
- }
- }
-
- private static boolean isMatch(@NonNull String contents, int offset, String pattern,
- @Nullable SearchHints hints) {
- if (!contents.startsWith(pattern, offset)) {
- return false;
- }
-
- if (hints != null) {
- char prevChar = offset > 0 ? contents.charAt(offset - 1) : 0;
- int lastIndex = offset + pattern.length() - 1;
- char nextChar = lastIndex < contents.length() - 1 ? contents.charAt(lastIndex + 1) : 0;
-
- if (hints.isWholeWord() && (Character.isLetter(prevChar)
- || Character.isLetter(nextChar))) {
- return false;
-
- }
-
- if (hints.isJavaSymbol()) {
- if (Character.isJavaIdentifierPart(prevChar)
- || Character.isJavaIdentifierPart(nextChar)) {
- return false;
- }
-
- if (prevChar == '"') {
- return false;
- }
-
- // TODO: Additional validation to see if we're in a comment, string, etc.
- // This will require lexing from the beginning of the buffer.
- }
-
- if (hints.isConstructor() && SUPER_KEYWORD.equals(pattern)) {
- // Only looking for super(), not super.x, so assert that the next
- // non-space character is (
- int index = lastIndex + 1;
- while (index < contents.length() - 1) {
- char c = contents.charAt(index);
- if (c == '(') {
- break;
- } else if (!Character.isWhitespace(c)) {
- return false;
- }
- index++;
- }
- }
- }
-
- return true;
- }
-
- private static int adjustLine(String doc, int line, int offset, int newOffset) {
- if (newOffset == -1) {
- return line;
- }
-
- if (newOffset < offset) {
- return line - countLines(doc, newOffset, offset);
- } else {
- return line + countLines(doc, offset, newOffset);
- }
- }
-
- private static int countLines(String doc, int start, int end) {
- int lines = 0;
- for (int offset = start; offset < end; offset++) {
- char c = doc.charAt(offset);
- if (c == '\n') {
- lines++;
- }
- }
-
- return lines;
- }
-
- /**
- * Reverses the secondary location list initiated by the given location
- *
- * @param location the first location in the list
- * @return the first location in the reversed list
- */
- public static Location reverse(Location location) {
- Location next = location.getSecondary();
- location.setSecondary(null);
- while (next != null) {
- Location nextNext = next.getSecondary();
- next.setSecondary(location);
- location = next;
- next = nextNext;
- }
-
- return location;
- }
-
- /**
- * A {@link Handle} is a reference to a location. The point of a location
- * handle is to be able to create them cheaply, and then resolve them into
- * actual locations later (if needed). This makes it possible to for example
- * delay looking up line numbers, for locations that are offset based.
- */
- public interface Handle {
- /**
- * Compute a full location for the given handle
- *
- * @return create a location for this handle
- */
- @NonNull
- Location resolve();
-
- /**
- * Sets the client data associated with this location. This is an optional
- * field which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @param clientData the data to store with this location
- */
- void setClientData(@Nullable Object clientData);
-
- /**
- * Returns the client data associated with this location - an optional field
- * which can be used by the creator of the {@link Location} to store
- * temporary state associated with the location.
- *
- * @return the data associated with this location
- */
- @Nullable
- Object getClientData();
- }
-
- /** A default {@link Handle} implementation for simple file offsets */
- public static class DefaultLocationHandle implements Handle {
- private final File mFile;
- private final String mContents;
- private final int mStartOffset;
- private final int mEndOffset;
- private Object mClientData;
-
- /**
- * Constructs a new {@link DefaultLocationHandle}
- *
- * @param context the context pointing to the file and its contents
- * @param startOffset the start offset within the file
- * @param endOffset the end offset within the file
- */
- public DefaultLocationHandle(@NonNull Context context, int startOffset, int endOffset) {
- mFile = context.file;
- mContents = context.getContents();
- mStartOffset = startOffset;
- mEndOffset = endOffset;
- }
-
- @Override
- @NonNull
- public Location resolve() {
- return create(mFile, mContents, mStartOffset, mEndOffset);
- }
-
- @Override
- public void setClientData(@Nullable Object clientData) {
- mClientData = clientData;
- }
-
- @Override
- @Nullable
- public Object getClientData() {
- return mClientData;
- }
- }
-
- /**
- * Whether to look forwards, or backwards, or in both directions, when
- * searching for a pattern in the source code to determine the right
- * position range for a given symbol.
- * <p>
- * When dealing with bytecode for example, there are only line number entries
- * within method bodies, so when searching for the method declaration, we should only
- * search backwards from the first line entry in the method.
- */
- public enum SearchDirection {
- /** Only search forwards */
- FORWARD,
-
- /** Only search backwards */
- BACKWARD,
-
- /** Search backwards from the current end of line (normally it's the beginning of
- * the current line) */
- EOL_BACKWARD,
-
- /**
- * Search both forwards and backwards from the given line, and prefer
- * the match that is closest
- */
- NEAREST,
- }
-
- /**
- * Extra information pertaining to finding a symbol in a source buffer,
- * used by {@link Location#create(File, String, int, String, String, SearchHints)}
- */
- public static class SearchHints {
- /**
- * the direction to search for the nearest match in (provided
- * {@code patternStart} is non null)
- */
- @NonNull
- private final SearchDirection mDirection;
-
- /** Whether the matched pattern should be a whole word */
- private boolean mWholeWord;
-
- /**
- * Whether the matched pattern should be a Java symbol (so for example,
- * a match inside a comment or string literal should not be used)
- */
- private boolean mJavaSymbol;
-
- /**
- * Whether the matched pattern corresponds to a constructor; if so, look for
- * some other possible source aliases too, such as "super".
- */
- private boolean mConstructor;
-
- private SearchHints(@NonNull SearchDirection direction) {
- super();
- mDirection = direction;
- }
-
- /**
- * Constructs a new {@link SearchHints} object
- *
- * @param direction the direction to search in for the pattern
- * @return a new @link SearchHints} object
- */
- @NonNull
- public static SearchHints create(@NonNull SearchDirection direction) {
- return new SearchHints(direction);
- }
-
- /**
- * Indicates that pattern matches should apply to whole words only
-
- * @return this, for constructor chaining
- */
- @NonNull
- public SearchHints matchWholeWord() {
- mWholeWord = true;
-
- return this;
- }
-
- /** @return true if the pattern match should be for whole words only */
- public boolean isWholeWord() {
- return mWholeWord;
- }
-
- /**
- * Indicates that pattern matches should apply to Java symbols only
- *
- * @return this, for constructor chaining
- */
- @NonNull
- public SearchHints matchJavaSymbol() {
- mJavaSymbol = true;
- mWholeWord = true;
-
- return this;
- }
-
- /** @return true if the pattern match should be for Java symbols only */
- public boolean isJavaSymbol() {
- return mJavaSymbol;
- }
-
- /**
- * Indicates that pattern matches should apply to constructors. If so, look for
- * some other possible source aliases too, such as "super".
- *
- * @return this, for constructor chaining
- */
- @NonNull
- public SearchHints matchConstructor() {
- mConstructor = true;
- mWholeWord = true;
- mJavaSymbol = true;
-
- return this;
- }
-
- /** @return true if the pattern match should be for a constructor */
- public boolean isConstructor() {
- return mConstructor;
- }
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Position.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Position.java
deleted file mode 100644
index 6407cc9..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Position.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.google.common.annotations.Beta;
-
-/**
- * Information about a position in a file/document.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class Position {
- /**
- * Returns the line number (0-based where the first line is line 0)
- *
- * @return the 0-based line number
- */
- public abstract int getLine();
-
- /**
- * The character offset
- *
- * @return the 0-based character offset
- */
- public abstract int getOffset();
-
- /**
- * Returns the column number (where the first character on the line is 0),
- * or -1 if unknown
- *
- * @return the 0-based column number
- */
- public abstract int getColumn();
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Project.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Project.java
deleted file mode 100644
index 968b9b1..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Project.java
+++ /dev/null
@@ -1,929 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import static com.android.SdkConstants.ANDROID_LIBRARY;
-import static com.android.SdkConstants.ANDROID_LIBRARY_REFERENCE_FORMAT;
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
-import static com.android.SdkConstants.ATTR_PACKAGE;
-import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
-import static com.android.SdkConstants.PROGUARD_CONFIG;
-import static com.android.SdkConstants.PROJECT_PROPERTIES;
-import static com.android.SdkConstants.RES_FOLDER;
-import static com.android.SdkConstants.TAG_USES_SDK;
-import static com.android.SdkConstants.VALUE_TRUE;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.CircularDependencyException;
-import com.android.tools.lint.client.api.Configuration;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.client.api.SdkInfo;
-import com.google.common.annotations.Beta;
-import com.google.common.base.Charsets;
-import com.google.common.io.Closeables;
-import com.google.common.io.Files;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A project contains information about an Android project being scanned for
- * Lint errors.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class Project {
- private final LintClient mClient;
- private final File mDir;
- private final File mReferenceDir;
- private Configuration mConfiguration;
- private String mPackage;
- private int mMinSdk = 1;
- private int mTargetSdk = -1;
- private int mBuildSdk = -1;
- private boolean mLibrary;
- private String mName;
- private String mProguardPath;
- private boolean mMergeManifests;
-
- /** The SDK info, if any */
- private SdkInfo mSdkInfo;
-
- /**
- * If non null, specifies a non-empty list of specific files under this
- * project which should be checked.
- */
- private List<File> mFiles;
- private List<File> mJavaSourceFolders;
- private List<File> mJavaClassFolders;
- private List<File> mJavaLibraries;
- private List<Project> mDirectLibraries;
- private List<Project> mAllLibraries;
- private boolean mReportIssues = true;
-
- /**
- * Creates a new {@link Project} for the given directory.
- *
- * @param client the tool running the lint check
- * @param dir the root directory of the project
- * @param referenceDir See {@link #getReferenceDir()}.
- * @return a new {@link Project}
- */
- @NonNull
- public static Project create(
- @NonNull LintClient client,
- @NonNull File dir,
- @NonNull File referenceDir) {
- return new Project(client, dir, referenceDir);
- }
-
- /** Creates a new Project. Use one of the factory methods to create. */
- private Project(
- @NonNull LintClient client,
- @NonNull File dir,
- @NonNull File referenceDir) {
- mClient = client;
- mDir = dir;
- mReferenceDir = referenceDir;
-
- try {
- // Read properties file and initialize library state
- Properties properties = new Properties();
- File propFile = new File(dir, PROJECT_PROPERTIES);
- if (propFile.exists()) {
- @SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly
- BufferedInputStream is = new BufferedInputStream(new FileInputStream(propFile));
- try {
- properties.load(is);
- String value = properties.getProperty(ANDROID_LIBRARY);
- mLibrary = VALUE_TRUE.equals(value);
- mProguardPath = properties.getProperty(PROGUARD_CONFIG);
- mMergeManifests = VALUE_TRUE.equals(properties.getProperty(
- "manifestmerger.enabled")); //$NON-NLS-1$
- String target = properties.getProperty("target"); //$NON-NLS-1$
- if (target != null) {
- int index = target.lastIndexOf('-');
- if (index == -1) {
- index = target.lastIndexOf(':');
- }
- if (index != -1) {
- String versionString = target.substring(index + 1);
- try {
- mBuildSdk = Integer.parseInt(versionString);
- } catch (NumberFormatException nufe) {
- mClient.log(Severity.WARNING, null,
- "Unexpected build target format: %1$s", target);
- }
- }
- }
-
- for (int i = 1; i < 1000; i++) {
- String key = String.format(ANDROID_LIBRARY_REFERENCE_FORMAT, i);
- String library = properties.getProperty(key);
- if (library == null || library.isEmpty()) {
- // No holes in the numbering sequence is allowed
- break;
- }
-
- File libraryDir = new File(dir, library).getCanonicalFile();
-
- if (mDirectLibraries == null) {
- mDirectLibraries = new ArrayList<Project>();
- }
-
- // Adjust the reference dir to be a proper prefix path of the
- // library dir
- File libraryReferenceDir = referenceDir;
- if (!libraryDir.getPath().startsWith(referenceDir.getPath())) {
- // Symlinks etc might have been resolved, so do those to
- // the reference dir as well
- libraryReferenceDir = libraryReferenceDir.getCanonicalFile();
- if (!libraryDir.getPath().startsWith(referenceDir.getPath())) {
- File file = libraryReferenceDir;
- while (file != null && !file.getPath().isEmpty()) {
- if (libraryDir.getPath().startsWith(file.getPath())) {
- libraryReferenceDir = file;
- break;
- }
- file = file.getParentFile();
- }
- }
- }
-
- try {
- Project libraryPrj = client.getProject(libraryDir, libraryReferenceDir);
- mDirectLibraries.add(libraryPrj);
- // By default, we don't report issues in inferred library projects.
- // The driver will set report = true for those library explicitly
- // requested.
- libraryPrj.setReportIssues(false);
- } catch (CircularDependencyException e) {
- e.setProject(this);
- e.setLocation(Location.create(propFile));
- throw e;
- }
- }
- } finally {
- Closeables.closeQuietly(is);
- }
- }
- } catch (IOException ioe) {
- client.log(ioe, "Initializing project state");
- }
-
- if (mDirectLibraries != null) {
- mDirectLibraries = Collections.unmodifiableList(mDirectLibraries);
- } else {
- mDirectLibraries = Collections.emptyList();
- }
- }
-
- @Override
- public String toString() {
- return "Project [dir=" + mDir + ']';
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mDir == null) ? 0 : mDir.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Project other = (Project) obj;
- if (mDir == null) {
- if (other.mDir != null)
- return false;
- } else if (!mDir.equals(other.mDir))
- return false;
- return true;
- }
-
- /**
- * Adds the given file to the list of files which should be checked in this
- * project. If no files are added, the whole project will be checked.
- *
- * @param file the file to be checked
- */
- public void addFile(@NonNull File file) {
- if (mFiles == null) {
- mFiles = new ArrayList<File>();
- }
- mFiles.add(file);
- }
-
- /**
- * The list of files to be checked in this project. If null, the whole
- * project should be checked.
- *
- * @return the subset of files to be checked, or null for the whole project
- */
- @Nullable
- public List<File> getSubset() {
- return mFiles;
- }
-
- /**
- * Returns the list of source folders for Java source files
- *
- * @return a list of source folders to search for .java files
- */
- @NonNull
- public List<File> getJavaSourceFolders() {
- if (mJavaSourceFolders == null) {
- if (isAospFrameworksProject(mDir)) {
- return Collections.singletonList(new File(mDir, "java")); //$NON-NLS-1$
- }
- if (isAospBuildEnvironment()) {
- String top = getAospTop();
- if (mDir.getAbsolutePath().startsWith(top)) {
- mJavaSourceFolders = getAospJavaSourcePath();
- return mJavaSourceFolders;
- }
- }
-
- mJavaSourceFolders = mClient.getJavaSourceFolders(this);
- }
-
- return mJavaSourceFolders;
- }
-
- /**
- * Returns the list of output folders for class files
- * @return a list of output folders to search for .class files
- */
- @NonNull
- public List<File> getJavaClassFolders() {
- if (mJavaClassFolders == null) {
- if (isAospFrameworksProject(mDir)) {
- File top = mDir.getParentFile().getParentFile().getParentFile();
- if (top != null) {
- File out = new File(top, "out");
- if (out.exists()) {
- String relative =
- "target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar";
- File jar = new File(out, relative.replace('/', File.separatorChar));
- if (jar.exists()) {
- mJavaClassFolders = Collections.singletonList(jar);
- return mJavaClassFolders;
- }
- }
- }
- }
- if (isAospBuildEnvironment()) {
- String top = getAospTop();
- if (mDir.getAbsolutePath().startsWith(top)) {
- mJavaClassFolders = getAospJavaClassPath();
- return mJavaClassFolders;
- }
- }
-
- mJavaClassFolders = mClient.getJavaClassFolders(this);
- }
- return mJavaClassFolders;
- }
-
- /**
- * Returns the list of Java libraries (typically .jar files) that this
- * project depends on. Note that this refers to jar libraries, not Android
- * library projects which are processed in a separate pass with their own
- * source and class folders.
- *
- * @return a list of .jar files (or class folders) that this project depends
- * on.
- */
- @NonNull
- public List<File> getJavaLibraries() {
- if (mJavaLibraries == null) {
- // AOSP builds already merge libraries and class folders into
- // the single classes.jar file, so these have already been processed
- // in getJavaClassFolders.
-
- mJavaLibraries = mClient.getJavaLibraries(this);
- }
-
- return mJavaLibraries;
- }
-
- /**
- * Returns the resource folder.
- *
- * @return a file pointing to the resource folder, or null if the project
- * does not contain any resources
- */
- @NonNull
- public List<File> getResourceFolders() {
- List<File> folders = mClient.getResourceFolders(this);
-
- if (folders.size() == 1 && isAospFrameworksProject(mDir)) {
- // No manifest file for this project: just init the manifest values here
- mMinSdk = mTargetSdk = SdkConstants.HIGHEST_KNOWN_API;
- File folder = new File(folders.get(0), RES_FOLDER);
- if (!folder.exists()) {
- folders = Collections.emptyList();
- }
- }
-
- return folders;
- }
-
- /**
- * Returns the relative path of a given file relative to the user specified
- * directory (which is often the project directory but sometimes a higher up
- * directory when a directory tree is being scanned
- *
- * @param file the file under this project to check
- * @return the path relative to the reference directory (often the project directory)
- */
- @NonNull
- public String getDisplayPath(@NonNull File file) {
- String path = file.getPath();
- String referencePath = mReferenceDir.getPath();
- if (path.startsWith(referencePath)) {
- int length = referencePath.length();
- if (path.length() > length && path.charAt(length) == File.separatorChar) {
- length++;
- }
-
- return path.substring(length);
- }
-
- return path;
- }
-
- /**
- * Returns the relative path of a given file within the current project.
- *
- * @param file the file under this project to check
- * @return the path relative to the project
- */
- @NonNull
- public String getRelativePath(@NonNull File file) {
- String path = file.getPath();
- String referencePath = mDir.getPath();
- if (path.startsWith(referencePath)) {
- int length = referencePath.length();
- if (path.length() > length && path.charAt(length) == File.separatorChar) {
- length++;
- }
-
- return path.substring(length);
- }
-
- return path;
- }
-
- /**
- * Returns the project root directory
- *
- * @return the dir
- */
- @NonNull
- public File getDir() {
- return mDir;
- }
-
- /**
- * Returns the original user supplied directory where the lint search
- * started. For example, if you run lint against {@code /tmp/foo}, and it
- * finds a project to lint in {@code /tmp/foo/dev/src/project1}, then the
- * {@code dir} is {@code /tmp/foo/dev/src/project1} and the
- * {@code referenceDir} is {@code /tmp/foo/}.
- *
- * @return the reference directory, never null
- */
- @NonNull
- public File getReferenceDir() {
- return mReferenceDir;
- }
-
- /**
- * Gets the configuration associated with this project
- *
- * @return the configuration associated with this project
- */
- @NonNull
- public Configuration getConfiguration() {
- if (mConfiguration == null) {
- mConfiguration = mClient.getConfiguration(this);
- }
- return mConfiguration;
- }
-
- /**
- * Returns the application package specified by the manifest
- *
- * @return the application package, or null if unknown
- */
- @Nullable
- public String getPackage() {
- //assert !mLibrary; // Should call getPackage on the master project, not the library
- // Assertion disabled because you might be running lint on a standalone library project.
-
- return mPackage;
- }
-
- /**
- * Returns the minimum API level requested by the manifest, or -1 if not
- * specified
- *
- * @return the minimum API level or -1 if unknown
- */
- public int getMinSdk() {
- //assert !mLibrary; // Should call getMinSdk on the master project, not the library
- // Assertion disabled because you might be running lint on a standalone library project.
-
- return mMinSdk;
- }
-
- /**
- * Returns the target API level specified by the manifest, or -1 if not
- * specified
- *
- * @return the target API level or -1 if unknown
- */
- public int getTargetSdk() {
- //assert !mLibrary; // Should call getTargetSdk on the master project, not the library
- // Assertion disabled because you might be running lint on a standalone library project.
-
- return mTargetSdk;
- }
-
- /**
- * Returns the target API used to build the project, or -1 if not known
- *
- * @return the build target API or -1 if unknown
- */
- public int getBuildSdk() {
- return mBuildSdk;
- }
-
- /**
- * Initialized the manifest state from the given manifest model
- *
- * @param document the DOM document for the manifest XML document
- */
- public void readManifest(@NonNull Document document) {
- Element root = document.getDocumentElement();
- if (root == null) {
- return;
- }
-
- mPackage = root.getAttribute(ATTR_PACKAGE);
-
- // Initialize minSdk and targetSdk
- NodeList usesSdks = root.getElementsByTagName(TAG_USES_SDK);
- if (usesSdks.getLength() > 0) {
- Element element = (Element) usesSdks.item(0);
-
- String minSdk = null;
- if (element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
- minSdk = element.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
- }
- if (minSdk != null) {
- try {
- mMinSdk = Integer.valueOf(minSdk);
- } catch (NumberFormatException e) {
- mMinSdk = 1;
- }
- }
-
- String targetSdk = null;
- if (element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
- targetSdk = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
- } else if (minSdk != null) {
- targetSdk = minSdk;
- }
- if (targetSdk != null) {
- try {
- mTargetSdk = Integer.valueOf(targetSdk);
- } catch (NumberFormatException e) {
- // TODO: Handle codenames?
- mTargetSdk = -1;
- }
- }
- } else if (isAospBuildEnvironment()) {
- extractAospMinSdkVersion();
- }
- }
-
- /**
- * Returns true if this project is an Android library project
- *
- * @return true if this project is an Android library project
- */
- public boolean isLibrary() {
- return mLibrary;
- }
-
- /**
- * Returns the list of library projects referenced by this project
- *
- * @return the list of library projects referenced by this project, never
- * null
- */
- @NonNull
- public List<Project> getDirectLibraries() {
- return mDirectLibraries;
- }
-
- /**
- * Returns the transitive closure of the library projects for this project
- *
- * @return the transitive closure of the library projects for this project
- */
- @NonNull
- public List<Project> getAllLibraries() {
- if (mAllLibraries == null) {
- if (mDirectLibraries.isEmpty()) {
- return mDirectLibraries;
- }
-
- List<Project> all = new ArrayList<Project>();
- addLibraryProjects(all);
- mAllLibraries = all;
- }
-
- return mAllLibraries;
- }
-
- /**
- * Adds this project's library project and their library projects
- * recursively into the given collection of projects
- *
- * @param collection the collection to add the projects into
- */
- private void addLibraryProjects(@NonNull Collection<Project> collection) {
- for (Project library : mDirectLibraries) {
- collection.add(library);
- // Recurse
- library.addLibraryProjects(collection);
- }
- }
-
- /**
- * Gets the SDK info for the current project.
- *
- * @return the SDK info for the current project, never null
- */
- @NonNull
- public SdkInfo getSdkInfo() {
- if (mSdkInfo == null) {
- mSdkInfo = mClient.getSdkInfo(this);
- }
-
- return mSdkInfo;
- }
-
- /**
- * Gets the path to the manifest file in this project, if it exists
- *
- * @return the path to the manifest file, or null if it does not exist
- */
- @Nullable
- public File getManifestFile() {
- File manifestFile = new File(mDir, ANDROID_MANIFEST_XML);
- if (manifestFile.exists()) {
- return manifestFile;
- }
-
- return null;
- }
-
- /**
- * Returns the proguard path configured for this project, or null if ProGuard is
- * not configured.
- *
- * @return the proguard path, or null
- */
- @Nullable
- public String getProguardPath() {
- return mProguardPath;
- }
-
- /**
- * Returns the name of the project
- *
- * @return the name of the project, never null
- */
- @NonNull
- public String getName() {
- if (mName == null) {
- mName = mClient.getProjectName(this);
- }
-
- return mName;
- }
-
- /**
- * Sets the name of the project
- *
- * @param name the name of the project, never null
- */
- public void setName(@NonNull String name) {
- assert !name.isEmpty();
- mName = name;
- }
-
- /**
- * Sets whether lint should report issues in this project. See
- * {@link #getReportIssues()} for a full description of what that means.
- *
- * @param reportIssues whether lint should report issues in this project
- */
- public void setReportIssues(boolean reportIssues) {
- mReportIssues = reportIssues;
- }
-
- /**
- * Returns whether lint should report issues in this project.
- * <p>
- * If a user specifies a project and its library projects for analysis, then
- * those library projects are all "included", and all errors found in all
- * the projects are reported. But if the user is only running lint on the
- * main project, we shouldn't report errors in any of the library projects.
- * We still need to <b>consider</b> them for certain types of checks, such
- * as determining whether resources found in the main project are unused, so
- * the detectors must still get a chance to look at these projects. The
- * {@code #getReportIssues()} attribute is used for this purpose.
- *
- * @return whether lint should report issues in this project
- */
- public boolean getReportIssues() {
- return mReportIssues;
- }
-
- /**
- * Sets whether manifest merging is in effect.
- *
- * @param merging whether manifest merging is in effect
- */
- public void setMergingManifests(boolean merging) {
- mMergeManifests = merging;
- }
-
- /**
- * Returns whether manifest merging is in effect
- *
- * @return true if manifests in library projects should be merged into main projects
- */
- public boolean isMergingManifests() {
- return mMergeManifests;
- }
-
-
- // ---------------------------------------------------------------------------
- // Support for running lint on the AOSP source tree itself
-
- private static Boolean sAospBuild;
-
- /** Is lint running in an AOSP build environment */
- private static boolean isAospBuildEnvironment() {
- if (sAospBuild == null) {
- sAospBuild = getAospTop() != null;
- }
-
- return sAospBuild.booleanValue();
- }
-
- /**
- * Is this the frameworks AOSP project? Needs some hardcoded support since
- * it doesn't have a manifest file, etc.
- *
- * @param dir the project directory to check
- * @return true if this looks like the frameworks/base/core project
- */
- static boolean isAospFrameworksProject(@NonNull File dir) {
- if (!dir.getPath().endsWith("core")) { //$NON-NLS-1$
- return false;
- }
-
- File parent = dir.getParentFile();
- if (parent == null || !parent.getName().equals("base")) { //$NON-NLS-1$
- return false;
- }
-
- parent = parent.getParentFile();
- if (parent == null || !parent.getName().equals("frameworks")) { //$NON-NLS-1$
- return false;
- }
-
- return true;
- }
-
- /** Get the root AOSP dir, if any */
- private static String getAospTop() {
- return System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
- }
-
- /** Get the host out directory in AOSP, if any */
- private static String getAospHostOut() {
- return System.getenv("ANDROID_HOST_OUT"); //$NON-NLS-1$
- }
-
- /** Get the product out directory in AOSP, if any */
- private static String getAospProductOut() {
- return System.getenv("ANDROID_PRODUCT_OUT"); //$NON-NLS-1$
- }
-
- private List<File> getAospJavaSourcePath() {
- List<File> sources = new ArrayList<File>(2);
- // Normal sources
- File src = new File(mDir, "src"); //$NON-NLS-1$
- if (src.exists()) {
- sources.add(src);
- }
-
- // Generates sources
- for (File dir : getIntermediateDirs()) {
- File classes = new File(dir, "src"); //$NON-NLS-1$
- if (classes.exists()) {
- sources.add(classes);
- }
- }
-
- if (sources.isEmpty()) {
- mClient.log(null,
- "Warning: Could not find sources or generated sources for project %1$s",
- getName());
- }
-
- return sources;
- }
-
- private List<File> getAospJavaClassPath() {
- List<File> classDirs = new ArrayList<File>(1);
-
- for (File dir : getIntermediateDirs()) {
- File classes = new File(dir, "classes"); //$NON-NLS-1$
- if (classes.exists()) {
- classDirs.add(classes);
- } else {
- classes = new File(dir, "classes.jar"); //$NON-NLS-1$
- if (classes.exists()) {
- classDirs.add(classes);
- }
- }
- }
-
- if (classDirs.isEmpty()) {
- mClient.log(null,
- "No bytecode found: Has the project been built? (%1$s)", getName());
- }
-
- return classDirs;
- }
-
- /** Find the _intermediates directories for a given module name */
- private List<File> getIntermediateDirs() {
- // See build/core/definitions.mk and in particular the "intermediates-dir-for" definition
- List<File> intermediates = new ArrayList<File>();
-
- // TODO: Look up the module name, e.g. LOCAL_MODULE. However,
- // some Android.mk files do some complicated things with it - and most
- // projects use the same module name as the directory name.
- String moduleName = mDir.getName();
-
- String top = getAospTop();
- final String[] outFolders = new String[] {
- top + "/out/host/common/obj", //$NON-NLS-1$
- top + "/out/target/common/obj", //$NON-NLS-1$
- getAospHostOut() + "/obj", //$NON-NLS-1$
- getAospProductOut() + "/obj" //$NON-NLS-1$
- };
- final String[] moduleClasses = new String[] {
- "APPS", //$NON-NLS-1$
- "JAVA_LIBRARIES", //$NON-NLS-1$
- };
-
- for (String out : outFolders) {
- assert new File(out.replace('/', File.separatorChar)).exists() : out;
- for (String moduleClass : moduleClasses) {
- String path = out + '/' + moduleClass + '/' + moduleName
- + "_intermediates"; //$NON-NLS-1$
- File file = new File(path.replace('/', File.separatorChar));
- if (file.exists()) {
- intermediates.add(file);
- }
- }
- }
-
- return intermediates;
- }
-
- private void extractAospMinSdkVersion() {
- // Is the SDK level specified by a Makefile?
- boolean found = false;
- File makefile = new File(mDir, "Android.mk"); //$NON-NLS-1$
- if (makefile.exists()) {
- try {
- List<String> lines = Files.readLines(makefile, Charsets.UTF_8);
- Pattern p = Pattern.compile("LOCAL_SDK_VERSION\\s*:=\\s*(.*)"); //$NON-NLS-1$
- for (String line : lines) {
- line = line.trim();
- Matcher matcher = p.matcher(line);
- if (matcher.matches()) {
- found = true;
- String version = matcher.group(1);
- if (version.equals("current")) { //$NON-NLS-1$
- mMinSdk = findCurrentAospVersion();
- } else {
- try {
- mMinSdk = Integer.valueOf(version);
- } catch (NumberFormatException e) {
- // Codename - just use current
- mMinSdk = findCurrentAospVersion();
- }
- }
- break;
- }
- }
- } catch (IOException ioe) {
- mClient.log(ioe, null);
- }
- }
-
- if (!found) {
- mMinSdk = findCurrentAospVersion();
- }
- }
-
- /** Cache for {@link #findCurrentAospVersion()} */
- private static int sCurrentVersion;
-
- /** In an AOSP build environment, identify the currently built image version, if available */
- private static int findCurrentAospVersion() {
- if (sCurrentVersion < 1) {
- File apiDir = new File(getAospTop(), "frameworks/base/api" //$NON-NLS-1$
- .replace('/', File.separatorChar));
- File[] apiFiles = apiDir.listFiles();
- if (apiFiles == null) {
- sCurrentVersion = 1;
- return sCurrentVersion;
- }
- int max = 1;
- for (File apiFile : apiFiles) {
- String name = apiFile.getName();
- int index = name.indexOf('.');
- if (index > 0) {
- String base = name.substring(0, index);
- if (Character.isDigit(base.charAt(0))) {
- try {
- int version = Integer.parseInt(base);
- if (version > max) {
- max = version;
- }
- } catch (NumberFormatException nufe) {
- // pass
- }
- }
- }
- }
- sCurrentVersion = max;
- }
-
- return sCurrentVersion;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
deleted file mode 100644
index 68685c6..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.google.common.annotations.Beta;
-
-import java.io.File;
-
-/**
- * Specialized detector intended for XML resources. Detectors that apply to XML
- * resources should extend this detector instead since it provides special
- * iteration hooks that are more efficient.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public abstract class ResourceXmlDetector extends Detector implements Detector.XmlScanner {
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return LintUtils.isXmlFile(file);
- }
-
- /**
- * Returns whether this detector applies to the given folder type. This
- * allows the detectors to be pruned from iteration, so for example when we
- * are analyzing a string value file we don't need to look up detectors
- * related to layout.
- *
- * @param folderType the folder type to be visited
- * @return true if this detector can apply to resources in folders of the
- * given type
- */
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return true;
- }
-
- @Override
- public void run(@NonNull Context context) {
- // The infrastructure should never call this method on an xml detector since
- // it will run the various visitors instead
- assert false;
- }
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Scope.java
deleted file mode 100644
index 0e33f1b..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Scope.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.google.common.annotations.Beta;
-
-import java.util.EnumSet;
-
-/**
- * The scope of a detector is the set of files a detector must consider when
- * performing its analysis. This can be used to determine when issues are
- * potentially obsolete, whether a detector should re-run on a file save, etc.
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public enum Scope {
- /**
- * The analysis only considers a single XML resource file at a time.
- * <p>
- * Issues which are only affected by a single resource file can be checked
- * for incrementally when a file is edited.
- */
- RESOURCE_FILE,
-
- /**
- * The analysis considers <b>all</b> the resource file. This scope must not
- * be used in conjunction with {@link #RESOURCE_FILE}; an issue scope is
- * either considering just a single resource file or all the resources, not
- * both.
- */
- ALL_RESOURCE_FILES,
-
- /**
- * The analysis only considers a single Java source file at a time.
- * <p>
- * Issues which are only affected by a single Java source file can be
- * checked for incrementally when a Java source file is edited.
- */
- JAVA_FILE,
-
- /**
- * The analysis considers <b>all</b> the Java source files together.
- * <p>
- * This flag is mutually exclusive with {@link #JAVA_FILE}.
- */
- ALL_JAVA_FILES,
-
- /**
- * The analysis only considers a single Java class file at a time.
- * <p>
- * Issues which are only affected by a single Java class file can be checked
- * for incrementally when a Java source file is edited and then recompiled.
- */
- CLASS_FILE,
-
- /**
- * The analysis considers <b>all</b> the Java class files together.
- * <p>
- * This flag is mutually exclusive with {@link #CLASS_FILE}.
- */
- ALL_CLASS_FILES,
-
- /** The analysis considers the manifest file */
- MANIFEST,
-
- /** The analysis considers the Proguard configuration file */
- PROGUARD_FILE,
-
- /**
- * The analysis considers classes in the libraries for this project. These
- * will be analyzed before the classes themselves.
- */
- JAVA_LIBRARIES,
-
- /**
- * Scope for other files. Issues that specify a custom scope will be called unconditionally.
- * This will call {@link Detector#run(Context)}} on the detectors unconditionally.
- */
- OTHER;
-
- /**
- * Returns true if the given scope set corresponds to scanning a single file
- * rather than a whole project
- *
- * @param scopes the scope set to check
- * @return true if the scope set references a single file
- */
- public static boolean checkSingleFile(@NonNull EnumSet<Scope> scopes) {
- int size = scopes.size();
- if (size == 2) {
- // When single checking a Java source file, we check both its Java source
- // and the associated class files
- return scopes.contains(JAVA_FILE) && scopes.contains(CLASS_FILE);
- } else {
- return size == 1 &&
- (scopes.contains(JAVA_FILE)
- || scopes.contains(CLASS_FILE)
- || scopes.contains(RESOURCE_FILE)
- || scopes.contains(PROGUARD_FILE)
- || scopes.contains(MANIFEST));
- }
- }
-
- /**
- * Returns the intersection of two scope sets
- *
- * @param scope1 the first set to intersect
- * @param scope2 the second set to intersect
- * @return the intersection of the two sets
- */
- @NonNull
- public static EnumSet<Scope> intersect(
- @NonNull EnumSet<Scope> scope1,
- @NonNull EnumSet<Scope> scope2) {
- EnumSet<Scope> scope = EnumSet.copyOf(scope1);
- scope.retainAll(scope2);
-
- return scope;
- }
-
- /** All scopes: running lint on a project will check these scopes */
- public static final EnumSet<Scope> ALL = EnumSet.allOf(Scope.class);
- /** Scope-set used for detectors which are affected by a single resource file */
- public static final EnumSet<Scope> RESOURCE_FILE_SCOPE = EnumSet.of(RESOURCE_FILE);
- /** Scope-set used for detectors which scan all resources */
- public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
- /** Scope-set used for detectors which are affected by a single Java source file */
- public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE);
- /** Scope-set used for detectors which are affected by a single Java class file */
- public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE);
- /** Scope-set used for detectors which are affected by the manifest only */
- public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST);
- /** Scope-set used for detectors which correspond to some other context */
- public static final EnumSet<Scope> OTHER_SCOPE = EnumSet.of(OTHER);
-}
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Severity.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Severity.java
deleted file mode 100644
index f74e6b5..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Severity.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.google.common.annotations.Beta;
-
-/**
- * Severity of an issue found by lint
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public enum Severity {
- /**
- * Fatal: Use sparingly because a warning marked as fatal will be
- * considered critical and will abort Export APK etc in ADT
- */
- @NonNull
- FATAL("Fatal"),
-
- /**
- * Errors: The issue is known to be a real error that must be addressed.
- */
- @NonNull
- ERROR("Error"),
-
- /**
- * Warning: Probably a problem.
- */
- @NonNull
- WARNING("Warning"),
-
- /**
- * Information only: Might not be a problem, but the check has found
- * something interesting to say about the code.
- */
- @NonNull
- INFORMATIONAL("Information"),
-
- /**
- * Ignore: The user doesn't want to see this issue
- */
- @NonNull
- IGNORE("Ignore");
-
- @NonNull
- private final String mDisplay;
-
- Severity(@NonNull String display) {
- mDisplay = display;
- }
-
- /**
- * Returns a description of this severity suitable for display to the user
- *
- * @return a description of the severity
- */
- @NonNull
- public String getDescription() {
- return mDisplay;
- }
-} \ No newline at end of file
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Speed.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Speed.java
deleted file mode 100644
index c68dab0..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/Speed.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.google.common.annotations.Beta;
-
-/**
- * Enum which describes the different computation speeds of various detectors
- * <p>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public enum Speed {
- /** The detector can run very quickly */
- FAST("Fast"),
-
- /** The detector runs reasonably fast */
- NORMAL("Normal"),
-
- /** The detector might take a long time to run */
- SLOW("Slow");
-
- private final String mDisplayName;
-
- Speed(@NonNull String displayName) {
- mDisplayName = displayName;
- }
-
- /**
- * Returns the user-visible description of the speed of the given
- * detector
- *
- * @return the description of the speed to display to the user
- */
- @NonNull
- public String getDisplayName() {
- return mDisplayName;
- }
-} \ No newline at end of file
diff --git a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java b/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
deleted file mode 100644
index 34d6816..0000000
--- a/lint/libs/lint_api/src/main/java/com/android/tools/lint/detector/api/XmlContext.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.detector.api;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.IDomParser;
-import com.android.tools.lint.client.api.LintDriver;
-import com.google.common.annotations.Beta;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.io.File;
-
-/**
- * A {@link Context} used when checking XML files.
- * <p/>
- * <b>NOTE: This is not a public or final API; if you rely on this be prepared
- * to adjust your code for the next tools release.</b>
- */
-@Beta
-public class XmlContext extends Context {
- /** The XML parser */
- public IDomParser parser;
- /** The XML document */
- public Document document;
- private final ResourceFolderType mFolderType;
-
- /**
- * Construct a new {@link XmlContext}
- *
- * @param driver the driver running through the checks
- * @param project the project containing the file being checked
- * @param main the main project if this project is a library project, or
- * null if this is not a library project. The main project is
- * the root project of all library projects, not necessarily the
- * directly including project.
- * @param file the file being checked
- * @param folderType the {@link ResourceFolderType} of this file, if any
- */
- public XmlContext(
- @NonNull LintDriver driver,
- @NonNull Project project,
- @Nullable Project main,
- @NonNull File file,
- @Nullable ResourceFolderType folderType) {
- super(driver, project, main, file);
- mFolderType = folderType;
- }
-
- /**
- * Returns the location for the given node, which may be an element or an attribute.
- *
- * @param node the node to look up the location for
- * @return the location for the node
- */
- @NonNull
- public Location getLocation(@NonNull Node node) {
- if (parser != null) {
- return parser.getLocation(this, node);
- }
-
- return Location.create(file);
- }
-
- /**
- * Creates a new location within an XML text node
- *
- * @param textNode the text node
- * @param begin the start offset within the text node (inclusive)
- * @param end the end offset within the text node (exclusive)
- * @return a new location
- */
- @NonNull
- public Location getLocation(@NonNull Node textNode, int begin, int end) {
- assert textNode.getNodeType() == Node.TEXT_NODE;
- if (parser != null) {
- return parser.getLocation(this, textNode, begin, end);
- }
-
- return Location.create(file);
- }
-
-
- /**
- * Reports an issue applicable to a given DOM node. The DOM node is used as the
- * scope to check for suppress lint annotations.
- *
- * @param issue the issue to report
- * @param scope the DOM node scope the error applies to. The lint infrastructure
- * will check whether there are suppress directives on this node (or its enclosing
- * nodes) and if so suppress the warning without involving the client.
- * @param location the location of the issue, or null if not known
- * @param message the message for this warning
- * @param data any associated data, or null
- */
- public void report(
- @NonNull Issue issue,
- @Nullable Node scope,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- if (scope != null && mDriver.isSuppressed(issue, scope)) {
- return;
- }
- super.report(issue, location, message, data);
- }
-
- @Override
- public void report(
- @NonNull Issue issue,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- // Warn if clients use the non-scoped form? No, there are cases where an
- // XML detector's error isn't applicable to one particular location (or it's
- // not feasible to compute it cheaply)
- //mDriver.getClient().log(null, "Warning: Issue " + issue
- // + " was reported without a scope node: Can't be suppressed.");
-
- // For now just check the document root itself
- if (document != null && mDriver.isSuppressed(issue, document)) {
- return;
- }
-
- super.report(issue, location, message, data);
- }
-
- /**
- * Returns the resource folder type of this XML file, if any.
- *
- * @return the resource folder type or null
- */
- @Nullable
- public ResourceFolderType getResourceFolderType() {
- return mFolderType;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java
deleted file mode 100644
index 4d069cc..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AccessibilityDetector.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CONTENT_DESCRIPTION;
-import static com.android.SdkConstants.ATTR_HINT;
-import static com.android.SdkConstants.ATTR_IMPORTANT_FOR_ACCESSIBILITY;
-import static com.android.SdkConstants.IMAGE_BUTTON;
-import static com.android.SdkConstants.IMAGE_VIEW;
-import static com.android.SdkConstants.VALUE_NO;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check which looks for accessibility problems like missing content descriptions
- * <p>
- * TODO: Resolve styles and don't warn where styles are defining the content description
- * (though this seems unusual; content descriptions are not typically generic enough to
- * put in styles)
- */
-public class AccessibilityDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ContentDescription", //$NON-NLS-1$
- "Ensures that image widgets provide a contentDescription",
- "Non-textual widgets like ImageViews and ImageButtons should use the " +
- "`contentDescription` attribute to specify a textual description of " +
- "the widget such that screen readers and other accessibility tools " +
- "can adequately describe the user interface.\n" +
- "\n" +
- "Note that elements in application screens that are purely decorative " +
- "and do not provide any content or enable a user action should not " +
- "have accessibility content descriptions. In this case, just suppress the " +
- "lint warning with a tools:ignore=\"ContentDescription\" attribute.\n" +
- "\n" +
- "Note that for text fields, you should not set both the `hint` and the " +
- "`contentDescription` attributes since the hint will never be shown. Just " +
- "set the `hint`. See " +
- "http://developer.android.com/guide/topics/ui/accessibility/checklist.html#special-cases.",
-
- Category.A11Y,
- 3,
- Severity.WARNING,
- AccessibilityDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link AccessibilityDetector} */
- public AccessibilityDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- IMAGE_BUTTON,
- IMAGE_VIEW
- );
- }
-
- @Override
- @Nullable
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_CONTENT_DESCRIPTION);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- Element element = attribute.getOwnerElement();
- if (element.hasAttributeNS(ANDROID_URI, ATTR_HINT)) {
- context.report(ISSUE, element, context.getLocation(attribute),
- "Do not set both contentDescription and hint: the contentDescription " +
- "will mask the hint", null);
- }
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (!element.hasAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION)) {
- // Ignore views that are explicitly not important for accessibility
- if (VALUE_NO.equals(element.getAttributeNS(ANDROID_URI,
- ATTR_IMPORTANT_FOR_ACCESSIBILITY))) {
- return;
- }
- context.report(ISSUE, element, context.getLocation(element),
- "[Accessibility] Missing contentDescription attribute on image", null);
- } else {
- Attr attributeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION);
- String attribute = attributeNode.getValue();
- if (attribute.isEmpty() || attribute.equals("TODO")) { //$NON-NLS-1$
- context.report(ISSUE, attributeNode, context.getLocation(attributeNode),
- "[Accessibility] Empty contentDescription attribute on image", null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
deleted file mode 100644
index 34a3f10..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AlwaysShowActionDetector.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_SHOW_AS_ACTION;
-import static com.android.SdkConstants.VALUE_ALWAYS;
-import static com.android.SdkConstants.VALUE_IF_ROOM;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Node;
-import lombok.ast.Select;
-
-/**
- * Check which looks for usage of showAsAction="always" in menus (or
- * MenuItem.SHOW_AS_ACTION_ALWAYS in code), which is usually a style guide violation.
- * (Use ifRoom instead).
- */
-public class AlwaysShowActionDetector extends ResourceXmlDetector implements JavaScanner {
-
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "AlwaysShowAction", //$NON-NLS-1$
- "Checks for uses of showAsAction=\"always\" and suggests showAsAction=\"ifRoom\" " +
- "instead",
-
- "Using `showAsAction=\"always\"` in menu XML, or `MenuItem.SHOW_AS_ACTION_ALWAYS` in " +
- "Java code is usually a deviation from the user interface style guide." +
- "Use `ifRoom` or the corresponding `MenuItem.SHOW_AS_ACTION_IF_ROOM` instead.\n" +
- "\n" +
- "If `always` is used sparingly there are usually no problems and behavior is " +
- "roughly equivalent to `ifRoom` but with preference over other `ifRoom` " +
- "items. Using it more than twice in the same menu is a bad idea.\n" +
- "\n" +
- "This check looks for menu XML files that contain more than two `always` " +
- "actions, or some `always` actions and no `ifRoom` actions. In Java code, " +
- "it looks for projects that contain references to `MenuItem.SHOW_AS_ACTION_ALWAYS` " +
- "and no references to `MenuItem.SHOW_AS_ACTION_IF_ROOM`.",
-
- Category.USABILITY,
- 3,
- Severity.WARNING,
- AlwaysShowActionDetector.class,
- EnumSet.of(Scope.RESOURCE_FILE, Scope.JAVA_FILE)).setMoreInfo(
- "http://developer.android.com/design/patterns/actionbar.html"); //$NON-NLS-1$
-
- /** List of showAsAction attributes appearing in the current menu XML file */
- private List<Attr> mFileAttributes;
- /** If at least one location has been marked ignore in this file, ignore all */
- private boolean mIgnoreFile;
- /** List of locations of MenuItem.SHOW_AS_ACTION_ALWAYS references in Java code */
- private List<Location> mAlwaysFields;
- /** True if references to MenuItem.SHOW_AS_ACTION_IF_ROOM were found */
- private boolean mHasIfRoomRefs;
-
- /** Constructs a new {@link AlwaysShowActionDetector} */
- public AlwaysShowActionDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.MENU;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_SHOW_AS_ACTION);
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- mFileAttributes = null;
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- if (mIgnoreFile) {
- mFileAttributes = null;
- return;
- }
- if (mFileAttributes != null) {
- assert context instanceof XmlContext; // mFileAttributes is only set in XML files
-
- List<Attr> always = new ArrayList<Attr>();
- List<Attr> ifRoom = new ArrayList<Attr>();
- for (Attr attribute : mFileAttributes) {
- String value = attribute.getValue();
- if (value.equals(VALUE_ALWAYS)) {
- always.add(attribute);
- } else if (value.equals(VALUE_IF_ROOM)) {
- ifRoom.add(attribute);
- } else if (value.indexOf('|') != -1) {
- String[] flags = value.split("\\|"); //$NON-NLS-1$
- for (String flag : flags) {
- if (flag.equals(VALUE_ALWAYS)) {
- always.add(attribute);
- break;
- } else if (flag.equals(VALUE_IF_ROOM)) {
- ifRoom.add(attribute);
- break;
- }
- }
- }
- }
-
- if (!always.isEmpty() && mFileAttributes.size() > 1) {
- // Complain if you're using more than one "always", or if you're
- // using "always" and aren't using "ifRoom" at all (and provided you
- // have more than a single item)
- if (always.size() > 2 || ifRoom.isEmpty()) {
- XmlContext xmlContext = (XmlContext) context;
- Location location = null;
- for (int i = always.size() - 1; i >= 0; i--) {
- Location next = location;
- location = xmlContext.getLocation(always.get(i));
- if (next != null) {
- location.setSecondary(next);
- }
- }
- context.report(ISSUE, location,
- "Prefer \"ifRoom\" instead of \"always\"", null);
- }
- }
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mAlwaysFields != null && !mHasIfRoomRefs) {
- for (Location location : mAlwaysFields) {
- context.report(ISSUE, location,
- "Prefer \"SHOW_AS_ACTION_IF_ROOM\" instead of \"SHOW_AS_ACTION_ALWAYS\"",
- null);
- }
- }
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (context.getDriver().isSuppressed(ISSUE, attribute)) {
- mIgnoreFile = true;
- return;
- }
-
- if (mFileAttributes == null) {
- mFileAttributes = new ArrayList<Attr>();
- }
- mFileAttributes.add(attribute);
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public
- List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>>singletonList(Select.class);
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new FieldAccessChecker(context);
- }
-
- private class FieldAccessChecker extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public FieldAccessChecker(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitSelect(Select node) {
- String description = node.astIdentifier().getDescription();
- boolean isIfRoom = description.equals("SHOW_AS_ACTION_IF_ROOM"); //$NON-NLS-1$
- boolean isAlways = description.equals("SHOW_AS_ACTION_ALWAYS"); //$NON-NLS-1$
- if ((isIfRoom || isAlways)
- && node.astOperand().toString().equals("MenuItem")) { //$NON-NLS-1$
- if (isAlways) {
- if (mContext.getDriver().isSuppressed(ISSUE, node)) {
- return super.visitSelect(node);
- }
- if (mAlwaysFields == null) {
- mAlwaysFields = new ArrayList<Location>();
- }
- mAlwaysFields.add(mContext.getLocation(node));
- } else {
- mHasIfRoomRefs = true;
- }
- }
-
- return super.visitSelect(node);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
deleted file mode 100644
index eca9256..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.FQCN_SUPPRESS_LINT;
-import static com.android.SdkConstants.SUPPRESS_LINT;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-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 java.io.File;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationValue;
-import lombok.ast.ArrayInitializer;
-import lombok.ast.AstVisitor;
-import lombok.ast.Block;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.Modifiers;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.TypeBody;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-
-/**
- * Checks annotations to make sure they are valid
- */
-public class AnnotationDetector extends Detector implements Detector.JavaScanner {
- /** Placing SuppressLint on a local variable doesn't work for class-file based checks */
- public static final Issue ISSUE = Issue.create(
- "LocalSuppress", //$NON-NLS-1$
- "Looks for @SuppressLint annotations in locations where it doesn't work for class based checks",
-
- "The `@SuppressAnnotation` is used to suppress Lint warnings in Java files. However, " +
- "while many lint checks analyzes the Java source code, where they can find " +
- "annotations on (for example) local variables, some checks are analyzing the " +
- "`.class` files. And in class files, annotations only appear on classes, fields " +
- "and methods. Annotations placed on local variables disappear. If you attempt " +
- "to suppress a lint error for a class-file based lint check, the suppress " +
- "annotation not work. You must move the annotation out to the surrounding method.",
-
- Category.CORRECTNESS,
- 3,
- Severity.ERROR,
- AnnotationDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Constructs a new {@link AnnotationDetector} check */
- public AnnotationDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>>singletonList(Annotation.class);
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new AnnotationChecker(context);
- }
-
- private static class AnnotationChecker extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public AnnotationChecker(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitAnnotation(Annotation node) {
- String type = node.astAnnotationTypeReference().getTypeName();
- if (SUPPRESS_LINT.equals(type) || FQCN_SUPPRESS_LINT.equals(type)) {
- Node parent = node.getParent();
- if (parent instanceof Modifiers) {
- parent = parent.getParent();
- if (parent instanceof VariableDefinition) {
- for (AnnotationElement element : node.astElements()) {
- AnnotationValue valueNode = element.astValue();
- if (valueNode == null) {
- continue;
- }
- if (valueNode instanceof StringLiteral) {
- StringLiteral literal = (StringLiteral) valueNode;
- String id = literal.astValue();
- if (!checkId(node, id)) {
- return super.visitAnnotation(node);
- }
- } else if (valueNode instanceof ArrayInitializer) {
- ArrayInitializer array = (ArrayInitializer) valueNode;
- StrictListAccessor<Expression, ArrayInitializer> expressions =
- array.astExpressions();
- if (expressions == null) {
- continue;
- }
- Iterator<Expression> arrayIterator = expressions.iterator();
- while (arrayIterator.hasNext()) {
- Expression arrayElement = arrayIterator.next();
- if (arrayElement instanceof StringLiteral) {
- String id = ((StringLiteral) arrayElement).astValue();
- if (!checkId(node, id)) {
- return super.visitAnnotation(node);
- }
- }
- }
- }
- }
- }
- }
- }
-
- return super.visitAnnotation(node);
- }
-
- private boolean checkId(Annotation node, String id) {
- IssueRegistry registry = mContext.getDriver().getRegistry();
- Issue issue = registry.getIssue(id);
- // Special-case the ApiDetector issue, since it does both source file analysis
- // only on field references, and class file analysis on the rest, so we allow
- // annotations outside of methods only on fields
- if (issue != null && !issue.getScope().contains(Scope.JAVA_FILE)
- || issue == ApiDetector.UNSUPPORTED) {
- // Ensure that this isn't a field
- Node parent = node.getParent();
- while (parent != null) {
- if (parent instanceof MethodDeclaration
- || parent instanceof ConstructorDeclaration
- || parent instanceof Block) {
- break;
- } else if (parent instanceof TypeBody) { // It's a field
- return true;
- } else if (issue == ApiDetector.UNSUPPORTED
- && parent instanceof VariableDefinition) {
- VariableDefinition definition = (VariableDefinition) parent;
- for (VariableDefinitionEntry entry : definition.astVariables()) {
- Expression initializer = entry.astInitializer();
- if (initializer instanceof Select) {
- return true;
- }
- }
- }
- parent = parent.getParent();
- if (parent == null) {
- return true;
- }
- }
-
- // This issue doesn't have AST access: annotations are not
- // available for local variables or parameters
- mContext.report(ISSUE, node, mContext.getLocation(node), String.format(
- "The @SuppressLint annotation cannot be used on a local " +
- "variable with the lint check '%1$s': move out to the " +
- "surrounding method", id),
- null);
- return false;
- }
-
- return true;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Api.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Api.java
deleted file mode 100644
index ca84b27..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Api.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-
-import org.xml.sax.SAXException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Main entry point for API description.
- *
- * To create the {@link Api}, use {@link #parseApi(File)}
- *
- */
-public class Api {
-
- /**
- * Parses simplified API file.
- * @param apiFile the file to read
- * @return a new ApiInfo
- */
- public static Api parseApi(File apiFile) {
- FileInputStream fileInputStream = null;
- try {
- fileInputStream = new FileInputStream(apiFile);
- SAXParserFactory parserFactory = SAXParserFactory.newInstance();
- SAXParser parser = parserFactory.newSAXParser();
- ApiParser apiParser = new ApiParser();
- parser.parse(fileInputStream, apiParser);
- return new Api(apiParser.getClasses());
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fileInputStream != null) {
- try {
- fileInputStream.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- return null;
- }
-
- private final Map<String, ApiClass> mClasses;
-
- private Api(Map<String, ApiClass> classes) {
- mClasses = new HashMap<String, ApiClass>(classes);
- }
-
- ApiClass getClass(String fqcn) {
- return mClasses.get(fqcn);
- }
-
- Map<String, ApiClass> getClasses() {
- return Collections.unmodifiableMap(mClasses);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
deleted file mode 100644
index 3e0fb9d..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-
-import com.android.annotations.Nullable;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Represents a class and its methods/fields.
- *
- * {@link #getSince()} gives the API level it was introduced.
- *
- * {@link #getMethod} returns when the method was introduced.
- * {@link #getField} returns when the field was introduced.
- */
-public class ApiClass {
-
- private final String mName;
- private final int mSince;
-
- private final List<Pair<String, Integer>> mSuperClasses = Lists.newArrayList();
- private final List<Pair<String, Integer>> mInterfaces = Lists.newArrayList();
-
- private final Map<String, Integer> mFields = new HashMap<String, Integer>();
- private final Map<String, Integer> mMethods = new HashMap<String, Integer>();
-
- ApiClass(String name, int since) {
- mName = name;
- mSince = since;
- }
-
- /**
- * Returns the name of the class.
- * @return the name of the class
- */
- String getName() {
- return mName;
- }
-
- /**
- * Returns when the class was introduced.
- * @return the api level the class was introduced.
- */
- int getSince() {
- return mSince;
- }
-
- /**
- * Returns when a field was added, or null if it doesn't exist.
- * @param name the name of the field.
- * @param info the corresponding info
- */
- Integer getField(String name, Api info) {
- // The field can come from this class or from a super class or an interface
- // The value can never be lower than this introduction of this class.
- // When looking at super classes and interfaces, it can never be lower than when the
- // super class or interface was added as a super class or interface to this class.
- // Look at all the values and take the lowest.
- // For instance:
- // This class A is introduced in 5 with super class B.
- // In 10, the interface C was added.
- // Looking for SOME_FIELD we get the following:
- // Present in A in API 15
- // Present in B in API 11
- // Present in C in API 7.
- // The answer is 10, which is when C became an interface
- int min = Integer.MAX_VALUE;
- Integer i = mFields.get(name);
- if (i != null) {
- min = i;
- }
-
- // now look at the super classes
- for (Pair<String, Integer> superClassPair : mSuperClasses) {
- ApiClass superClass = info.getClass(superClassPair.getFirst());
- if (superClass != null) {
- i = superClass.getField(name, info);
- if (i != null) {
- int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- // now look at the interfaces
- for (Pair<String, Integer> superClassPair : mInterfaces) {
- ApiClass superClass = info.getClass(superClassPair.getFirst());
- if (superClass != null) {
- i = superClass.getField(name, info);
- if (i != null) {
- int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- return min;
- }
-
- /**
- * Returns when a method was added, or null if it doesn't exist. This goes through the super
- * class to find method only present there.
- * @param methodSignature the method signature
- */
- int getMethod(String methodSignature, Api info) {
- // The method can come from this class or from a super class.
- // The value can never be lower than this introduction of this class.
- // When looking at super classes, it can never be lower than when the super class became
- // a super class of this class.
- // Look at all the values and take the lowest.
- // For instance:
- // This class A is introduced in 5 with super class B.
- // In 10, the super class changes to C.
- // Looking for foo() we get the following:
- // Present in A in API 15
- // Present in B in API 11
- // Present in C in API 7.
- // The answer is 10, which is when C became the super class.
- int min = Integer.MAX_VALUE;
- Integer i = mMethods.get(methodSignature);
- if (i != null) {
- min = i;
-
- // Constructors aren't inherited
- if (methodSignature.startsWith(CONSTRUCTOR_NAME)) {
- return i;
- }
- }
-
- // now look at the super classes
- for (Pair<String, Integer> superClassPair : mSuperClasses) {
- ApiClass superClass = info.getClass(superClassPair.getFirst());
- if (superClass != null) {
- i = superClass.getMethod(methodSignature, info);
- if (i != null) {
- int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- // now look at the interfaces classes
- for (Pair<String, Integer> interfacePair : mInterfaces) {
- ApiClass superClass = info.getClass(interfacePair.getFirst());
- if (superClass != null) {
- i = superClass.getMethod(methodSignature, info);
- if (i != null) {
- int tmp = interfacePair.getSecond() > i ? interfacePair.getSecond() : i;
- if (tmp < min) {
- min = tmp;
- }
- }
- }
- }
-
- return min;
- }
-
- void addField(String name, int since) {
- Integer i = mFields.get(name);
- if (i == null || i.intValue() > since) {
- mFields.put(name, Integer.valueOf(since));
- }
- }
-
- void addMethod(String name, int since) {
- // Strip off the method type at the end to ensure that the code which
- // produces inherited methods doesn't get confused and end up multiple entries.
- // For example, java/nio/Buffer has the method "array()Ljava/lang/Object;",
- // and the subclass java/nio/ByteBuffer has the method "array()[B". We want
- // the lookup on mMethods to associate the ByteBuffer array method to be
- // considered overriding the Buffer method.
- int index = name.indexOf(')');
- if (index != -1) {
- name = name.substring(0, index + 1);
- }
-
- Integer i = mMethods.get(name);
- if (i == null || i.intValue() > since) {
- mMethods.put(name, Integer.valueOf(since));
- }
- }
-
- void addSuperClass(String superClass, int since) {
- addToArray(mSuperClasses, superClass, since);
- }
-
- void addInterface(String interfaceClass, int since) {
- addToArray(mInterfaces, interfaceClass, since);
- }
-
- void addToArray(List<Pair<String, Integer>> list, String name, int value) {
- // check if we already have that name (at a lower level)
- for (Pair<String, Integer> pair : list) {
- if (name.equals(pair.getFirst())) {
- return;
- }
- }
-
- list.add(Pair.of(name, Integer.valueOf(value)));
-
- }
-
- @Nullable
- public String getPackage() {
- int index = mName.lastIndexOf('/');
- if (index != -1) {
- return mName.substring(0, index);
- }
-
- return null;
- }
-
- @Override
- public String toString() {
- return mName;
- }
-
- /**
- * Returns the set of all methods, including inherited
- * ones.
- *
- * @param info the api to look up super classes from
- * @return a set containing all the members fields
- */
- Set<String> getAllMethods(Api info) {
- Set<String> members = new HashSet<String>(100);
- addAllMethods(info, members, true /*includeConstructors*/);
-
- return members;
- }
-
- private void addAllMethods(Api info, Set<String> set, boolean includeConstructors) {
- if (!includeConstructors) {
- for (String method : mMethods.keySet()) {
- if (!method.startsWith(CONSTRUCTOR_NAME)) {
- set.add(method);
- }
- }
- } else {
- for (String method : mMethods.keySet()) {
- set.add(method);
- }
- }
-
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllMethods(info, set, false);
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllMethods(info, set, false);
- }
- }
- }
-
- /**
- * Returns the set of all fields, including inherited
- * ones.
- *
- * @param info the api to look up super classes from
- * @return a set containing all the fields
- */
- Set<String> getAllFields(Api info) {
- Set<String> members = new HashSet<String>(100);
- addAllFields(info, members);
-
- return members;
- }
-
- private void addAllFields(Api info, Set<String> set) {
- for (String field : mFields.keySet()) {
- set.add(field);
- }
-
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllFields(info, set);
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- clz.addAllFields(info, set);
- }
- }
- }
-
- /* This code can be used to scan through all the fields and look for fields
- that have moved to a higher class:
- Field android/view/MotionEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
- Field android/provider/ContactsContract$CommonDataKinds$Organization#PHONETIC_NAME has api=5 but parent android/provider/ContactsContract$ContactNameColumns provides it as 11
- Field android/widget/ListView#CHOICE_MODE_MULTIPLE has api=1 but parent android/widget/AbsListView provides it as 11
- Field android/widget/ListView#CHOICE_MODE_NONE has api=1 but parent android/widget/AbsListView provides it as 11
- Field android/widget/ListView#CHOICE_MODE_SINGLE has api=1 but parent android/widget/AbsListView provides it as 11
- Field android/view/KeyEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
- This is used for example in the ApiDetector to filter out warnings which result
- when people follow Eclipse's advice to replace
- ListView.CHOICE_MODE_MULTIPLE
- references with
- AbsListView.CHOICE_MODE_MULTIPLE
- since the latter has API=11 and the former has API=1; since the constant is unchanged
- between the two, and the literal is copied into the class, using the AbsListView
- reference works.
- public void checkFields(Api info) {
- fieldLoop:
- for (String field : mFields.keySet()) {
- Integer since = getField(field, info);
- if (since == null || since == Integer.MAX_VALUE) {
- continue;
- }
-
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- Integer superSince = clz.getField(field, info);
- if (superSince == Integer.MAX_VALUE) {
- continue;
- }
-
- if (superSince != null && superSince > since) {
- String declaredIn = clz.findFieldDeclaration(info, field);
- System.out.println("Field " + getName() + "#" + field + " has api="
- + since + " but parent " + declaredIn + " provides it as "
- + superSince);
- continue fieldLoop;
- }
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- Integer superSince = clz.getField(field, info);
- if (superSince == Integer.MAX_VALUE) {
- continue;
- }
- if (superSince != null && superSince > since) {
- String declaredIn = clz.findFieldDeclaration(info, field);
- System.out.println("Field " + getName() + "#" + field + " has api="
- + since + " but parent " + declaredIn + " provides it as "
- + superSince);
- continue fieldLoop;
- }
- }
- }
- }
- }
-
- private String findFieldDeclaration(Api info, String name) {
- if (mFields.containsKey(name)) {
- return getName();
- }
- for (Pair<String, Integer> superClass : mSuperClasses) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- String declaredIn = clz.findFieldDeclaration(info, name);
- if (declaredIn != null) {
- return declaredIn;
- }
- }
- }
-
- // Get methods from implemented interfaces as well;
- for (Pair<String, Integer> superClass : mInterfaces) {
- ApiClass clz = info.getClass(superClass.getFirst());
- assert clz != null : superClass.getSecond();
- if (clz != null) {
- String declaredIn = clz.findFieldDeclaration(info, name);
- if (declaredIn != null) {
- return declaredIn;
- }
- }
- }
-
- return null;
- }
- */
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
deleted file mode 100644
index cc2b212..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
+++ /dev/null
@@ -1,1569 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PREFIX;
-import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TARGET_API;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.PREFIX_ANDROID;
-import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.TARGET_API;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.SdkConstants.VIEW_TAG;
-import static com.android.tools.lint.detector.api.ClassContext.getFqcn;
-import static com.android.tools.lint.detector.api.ClassContext.getInternalName;
-import static com.android.tools.lint.detector.api.LintUtils.getNextInstruction;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.BACKWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.FORWARD;
-import static com.android.tools.lint.detector.api.Location.SearchDirection.NEAREST;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.DefaultPosition;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.SearchHints;
-import com.android.tools.lint.detector.api.Position;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.AnnotationNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.IntInsnNode;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.LocalVariableNode;
-import org.objectweb.asm.tree.LookupSwitchInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.Annotation;
-import lombok.ast.AnnotationElement;
-import lombok.ast.AnnotationValue;
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.Case;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.If;
-import lombok.ast.ImportDeclaration;
-import lombok.ast.InlineIfExpression;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Modifiers;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.SuperConstructorInvocation;
-import lombok.ast.Switch;
-import lombok.ast.TypeReference;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Looks for usages of APIs that are not supported in all the versions targeted
- * by this application (according to its minimum API requirement in the manifest).
- */
-public class ApiDetector extends ResourceXmlDetector
- implements Detector.ClassScanner, Detector.JavaScanner {
- /**
- * Whether we flag variable, field, parameter and return type declarations of a type
- * not yet available. It appears Dalvik is very forgiving and doesn't try to preload
- * classes until actually needed, so there is no need to flag these, and in fact,
- * patterns used for supporting new and old versions sometimes declares these methods
- * and only conditionally end up actually accessing methods and fields, so only check
- * method and field accesses.
- */
- private static final boolean CHECK_DECLARATIONS = false;
-
- private static final boolean AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null; //$NON-NLS-1$
-
- /** Accessing an unsupported API */
- public static final Issue UNSUPPORTED = Issue.create("NewApi", //$NON-NLS-1$
- "Finds API accesses to APIs that are not supported in all targeted API versions",
-
- "This check scans through all the Android API calls in the application and " +
- "warns about any calls that are not available on *all* versions targeted " +
- "by this application (according to its minimum SDK attribute in the manifest).\n"
- +
- "\n" +
- "If you really want to use this API and don't need to support older devices just "
- +
- "set the `minSdkVersion` in your `AndroidManifest.xml` file." +
- "\n" +
- "If your code is *deliberately* accessing newer APIs, and you have ensured " +
- "(e.g. with conditional execution) that this code will only ever be called on a "
- +
- "supported platform, then you can annotate your class or method with the " +
- "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
- "`@TargetApi(11)`, such that this check considers 11 rather than your manifest "
- +
- "file's minimum SDK as the required API level.\n" +
- "\n" +
- "If you are deliberately setting `android:` attributes in style definitions, " +
- "make sure you place this in a `values-v11` folder in order to avoid running " +
- "into runtime conflicts on certain devices where manufacturers have added " +
- "custom attributes whose ids conflict with the new ones on later platforms.\n" +
- "\n" +
- "Similarly, you can use tools:targetApi=\"11\" in an XML file to indicate that "
- +
- "the element will only be inflated in an adequate context.",
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- ApiDetector.class,
- EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE, Scope.MANIFEST))
- .addAnalysisScope(Scope.RESOURCE_FILE_SCOPE)
- .addAnalysisScope(Scope.CLASS_FILE_SCOPE);
-
- /** Accessing an inlined API on older platforms */
- public static final Issue INLINED = Issue.create("InlinedApi", //$NON-NLS-1$
- "Finds inlined fields that may or may not work on older platforms",
-
- "This check scans through all the Android API field references in the application " +
- "and flags certain constants, such as static final integers and Strings, " +
- "which were introduced in later versions. These will actually be copied " +
- "into the class files rather than being referenced, which means that " +
- "the value is available even when running on older devices. In some " +
- "cases that's fine, and in other cases it can result in a runtime " +
- "crash or incorrect behavior. It depends on the context, so consider " +
- "the code carefully and device whether it's safe and can be suppressed " +
- "or whether the code needs tbe guarded.\n" +
- "\n" +
- "If you really want to use this API and don't need to support older devices just "
- +
- "set the `minSdkVersion` in your `AndroidManifest.xml` file." +
- "\n" +
- "If your code is *deliberately* accessing newer APIs, and you have ensured " +
- "(e.g. with conditional execution) that this code will only ever be called on a "
- +
- "supported platform, then you can annotate your class or method with the " +
- "`@TargetApi` annotation specifying the local minimum SDK to apply, such as " +
- "`@TargetApi(11)`, such that this check considers 11 rather than your manifest "
- +
- "file's minimum SDK as the required API level.\n",
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- ApiDetector.class,
- EnumSet.of(Scope.JAVA_FILE))
- .addAnalysisScope(Scope.JAVA_FILE_SCOPE);
-
- /** Accessing an unsupported API */
- public static final Issue OVERRIDE = Issue.create("Override", //$NON-NLS-1$
- "Finds method declarations that will accidentally override methods in later versions",
-
- "Suppose you are building against Android API 8, and you've subclassed Activity. " +
- "In your subclass you add a new method called `isDestroyed`(). At some later point, " +
- "a method of the same name and signature is added to Android. Your method will " +
- "now override the Android method, and possibly break its contract. Your method " +
- "is not calling `super.isDestroyed()`, since your compilation target doesn't " +
- "know about the method.\n" +
- "\n" +
- "The above scenario is what this lint detector looks for. The above example is " +
- "real, since `isDestroyed()` was added in API 17, but it will be true for *any* " +
- "method you have added to a subclass of an Android class where your build target " +
- "is lower than the version the method was introduced in.\n" +
- "\n" +
- "To fix this, either rename your method, or if you are really trying to augment " +
- "the builtin method if available, switch to a higher build target where you can " +
- "deliberately add `@Override` on your overriding method, and call `super` if " +
- "appropriate etc.\n",
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- ApiDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- private static final String TARGET_API_VMSIG = '/' + TARGET_API + ';';
- private static final String SWITCH_TABLE_PREFIX = "$SWITCH_TABLE$"; //$NON-NLS-1$
- private static final String ORDINAL_METHOD = "ordinal"; //$NON-NLS-1$
-
- protected ApiLookup mApiDatabase;
- private int mMinApi = -1;
- private Map<String, List<Pair<String, Location>>> mPendingFields;
-
- /** Constructs a new API check */
- public ApiDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- mApiDatabase = ApiLookup.get(context.getClient());
- // We can't look up the minimum API required by the project here:
- // The manifest file hasn't been processed yet in the -before- project hook.
- // For now it's initialized lazily in getMinSdk(Context), but the
- // lint infrastructure should be fixed to parse manifest file up front.
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return true;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return ALL;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (mApiDatabase == null) {
- return;
- }
-
- String value = attribute.getValue();
-
- String owner = null;
- String name = null;
-
- String prefix;
- if (value.startsWith(ANDROID_PREFIX)) {
- prefix = ANDROID_PREFIX;
- } else if (value.startsWith(ANDROID_THEME_PREFIX)) {
- prefix = ANDROID_THEME_PREFIX;
- } else if (value.startsWith(PREFIX_ANDROID) && ATTR_NAME.equals(attribute.getName())
- && TAG_ITEM.equals(attribute.getOwnerElement().getTagName())
- && attribute.getOwnerElement().getParentNode() != null
- && TAG_STYLE.equals(attribute.getOwnerElement().getParentNode().getNodeName())) {
- owner = "android/R$attr"; //$NON-NLS-1$
- name = value.substring(PREFIX_ANDROID.length());
- prefix = PREFIX_ANDROID;
- } else {
- return;
- }
-
- if (owner == null) {
- // Convert @android:type/foo into android/R$type and "foo"
- int index = value.indexOf('/', prefix.length());
- if (index != -1) {
- owner = "android/R$" //$NON-NLS-1$
- + value.substring(prefix.length(), index);
- name = value.substring(index + 1);
- if (name.indexOf('.') != -1) {
- name = name.replace('.', '_');
- }
- } else if (value.startsWith(ANDROID_THEME_PREFIX)) {
- owner = "android/R$attr"; //$NON-NLS-1$
- name = value.substring(ANDROID_THEME_PREFIX.length());
- } else {
- return;
- }
- }
- assert name != null; // Eclipse can't infer this
- int api = mApiDatabase.getFieldVersion(owner, name);
- int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(attribute.getOwnerElement())) {
- // Don't complain about resource references in the tools namespace,
- // such as for example "tools:layout="@android:layout/list_content",
- // used only for designtime previews
- if (TOOLS_URI.equals(attribute.getNamespaceURI())) {
- return;
- }
-
- Location location = context.getLocation(attribute);
- String message = String.format(
- "%1$s requires API level %2$d (current min is %3$d)",
- value, api, minSdk);
- context.report(UNSUPPORTED, attribute, location, message, null);
- }
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (mApiDatabase == null) {
- return;
- }
-
- String tag = element.getTagName();
-
- ResourceFolderType folderType = context.getResourceFolderType();
- if (folderType != ResourceFolderType.LAYOUT) {
- if (element.getParentNode().getNodeType() != Node.ELEMENT_NODE) {
- // Root node
- return;
- }
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node textNode = childNodes.item(i);
- if (textNode.getNodeType() == Node.TEXT_NODE) {
- String text = textNode.getNodeValue();
- if (text.indexOf(ANDROID_PREFIX) != -1) {
- text = text.trim();
- // Convert @android:type/foo into android/R$type and "foo"
- int index = text.indexOf('/', ANDROID_PREFIX.length());
- if (index != -1) {
- String owner = "android/R$" //$NON-NLS-1$
- + text.substring(ANDROID_PREFIX.length(), index);
- String name = text.substring(index + 1);
- if (name.indexOf('.') != -1) {
- name = name.replace('.', '_');
- }
- int api = mApiDatabase.getFieldVersion(owner, name);
- int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
- Location location = context.getLocation(textNode);
- String message = String.format(
- "%1$s requires API level %2$d (current min is %3$d)",
- text, api, minSdk);
- context.report(UNSUPPORTED, element, location, message, null);
- }
- }
- }
- }
- }
- } else if (folderType == ResourceFolderType.LAYOUT) {
- if (VIEW_TAG.equals(tag)) {
- tag = element.getAttribute(ATTR_CLASS);
- if (tag == null || tag.isEmpty()) {
- return;
- }
- }
-
- // Check widgets to make sure they're available in this version of the SDK.
- if (tag.indexOf('.') != -1 ||
- folderType != ResourceFolderType.LAYOUT) {
- // Custom views aren't in the index
- return;
- }
- // TODO: Consider other widgets outside of android.widget.*
- int api = mApiDatabase.getCallVersion("android/widget/" + tag, //$NON-NLS-1$
- CONSTRUCTOR_NAME,
- // Not all views provided this constructor right away, for example,
- // LinearLayout added it in API 11 yet LinearLayout is much older:
- // "(Landroid/content/Context;Landroid/util/AttributeSet;I)V"); //$NON-NLS-1$
- "(Landroid/content/Context;)"); //$NON-NLS-1$
- int minSdk = getMinSdk(context);
- if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
- Location location = context.getLocation(element);
- String message = String.format(
- "View requires API level %1$d (current min is %2$d): <%3$s>",
- api, minSdk, tag);
- context.report(UNSUPPORTED, element, location, message, null);
- }
- }
- }
-
- protected int getMinSdk(Context context) {
- if (mMinApi == -1) {
- mMinApi = context.getMainProject().getMinSdk();
- }
-
- return mMinApi;
- }
-
- // ---- Implements ClassScanner ----
-
- @SuppressWarnings("rawtypes") // ASM API
- @Override
- public void checkClass(@NonNull final ClassContext context, @NonNull ClassNode classNode) {
- if (mApiDatabase == null) {
- return;
- }
-
- if (AOSP_BUILD && classNode.name.startsWith("android/support/")) { //$NON-NLS-1$
- return;
- }
-
- // Requires util package (add prebuilts/tools/common/asm-tools/asm-debug-all-4.0.jar)
- //classNode.accept(new TraceClassVisitor(new PrintWriter(System.out)));
-
- int classMinSdk = getClassMinSdk(context, classNode);
- if (classMinSdk == -1) {
- classMinSdk = getMinSdk(context);
- }
-
- List methodList = classNode.methods;
- if (methodList.isEmpty()) {
- return;
- }
-
- boolean checkCalls = context.isEnabled(UNSUPPORTED)
- || context.isEnabled(INLINED);
- boolean checkMethods = context.isEnabled(OVERRIDE)
- && context.getMainProject().getBuildSdk() >= 1;
- String frameworkParent = null;
- if (checkMethods) {
- LintDriver driver = context.getDriver();
- String owner = classNode.superName;
- while (owner != null) {
- // For virtual dispatch, walk up the inheritance chain checking
- // each inherited method
- if ((owner.startsWith("android/") //$NON-NLS-1$
- && !owner.startsWith("android/support/")) //$NON-NLS-1$
- || owner.startsWith("java/") //$NON-NLS-1$
- || owner.startsWith("javax/")) { //$NON-NLS-1$
- frameworkParent = owner;
- break;
- }
- owner = driver.getSuperClass(owner);
- }
- if (frameworkParent == null) {
- checkMethods = false;
- }
- }
-
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
-
- int minSdk = getLocalMinSdk(method.invisibleAnnotations);
- if (minSdk == -1) {
- minSdk = classMinSdk;
- }
-
- InsnList nodes = method.instructions;
-
- if (checkMethods && Character.isJavaIdentifierStart(method.name.charAt(0))) {
- int buildSdk = context.getMainProject().getBuildSdk();
- String name = method.name;
- assert frameworkParent != null;
- int api = mApiDatabase.getCallVersion(frameworkParent, name, method.desc);
- if (api > buildSdk && buildSdk != -1) {
- // TODO: Don't complain if it's annotated with @Override; that means
- // somehow the build target isn't correct.
- String fqcn;
- String owner = classNode.name;
- if (CONSTRUCTOR_NAME.equals(name)) {
- fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$
- } else {
- fqcn = ClassContext.getFqcn(owner) + '#' + name;
- }
- String message = String.format(
- "This method is not overriding anything with the current build " +
- "target, but will in API level %1$d (current target is %2$d): %3$s",
- api, buildSdk, fqcn);
-
- Location location = context.getLocation(method, classNode);
- context.report(OVERRIDE, method, null, location, message, null);
- }
- }
-
- if (!checkCalls) {
- continue;
- }
-
- if (CHECK_DECLARATIONS) {
- // Check types in parameter list and types of local variables
- List localVariables = method.localVariables;
- if (localVariables != null) {
- for (Object v : localVariables) {
- LocalVariableNode var = (LocalVariableNode) v;
- String desc = var.desc;
- if (desc.charAt(0) == 'L') {
- // "Lpackage/Class;" => "package/Bar"
- String className = desc.substring(1, desc.length() - 1);
- int api = mApiDatabase.getClassVersion(className);
- if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(className);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): %3$s",
- api, minSdk, fqcn);
- report(context, message, var.start, method,
- className.substring(className.lastIndexOf('/') + 1), null,
- SearchHints.create(NEAREST).matchJavaSymbol());
- }
- }
- }
- }
-
- // Check return type
- // The parameter types are already handled as local variables so we can skip
- // right to the return type.
- // Check types in parameter list
- String signature = method.desc;
- if (signature != null) {
- int args = signature.indexOf(')');
- if (args != -1 && signature.charAt(args + 1) == 'L') {
- String type = signature.substring(args + 2, signature.length() - 1);
- int api = mApiDatabase.getClassVersion(type);
- if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(type);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): %3$s",
- api, minSdk, fqcn);
- AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
- report(context, message, first, method, method.name, null,
- SearchHints.create(BACKWARD).matchJavaSymbol());
- }
- }
- }
- }
-
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- int type = instruction.getType();
- if (type == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode node = (MethodInsnNode) instruction;
- String name = node.name;
- String owner = node.owner;
- String desc = node.desc;
-
- // No need to check methods in this local class; we know they
- // won't be an API match
- if (node.getOpcode() == Opcodes.INVOKEVIRTUAL
- && owner.equals(classNode.name)) {
- owner = classNode.superName;
- }
-
- boolean checkingSuperClass = false;
- while (owner != null) {
- int api = mApiDatabase.getCallVersion(owner, name, desc);
- if (api > minSdk) {
- if (method.name.startsWith(SWITCH_TABLE_PREFIX)) {
- // We're in a compiler-generated method to generate an
- // array indexed by enum ordinal values to enum values. The enum
- // itself must be requiring a higher API number than is
- // currently used, but the call site for the switch statement
- // will also be referencing it, so no need to report these
- // calls.
- break;
- }
-
- if (!checkingSuperClass
- && node.getOpcode() == Opcodes.INVOKEVIRTUAL
- && methodDefinedLocally(classNode, name, desc)) {
- break;
- }
-
- String fqcn;
- if (CONSTRUCTOR_NAME.equals(name)) {
- fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$
- } else {
- fqcn = ClassContext.getFqcn(owner) + '#' + name;
- }
- String message = String.format(
- "Call requires API level %1$d (current min is %2$d): %3$s",
- api, minSdk, fqcn);
-
- if (name.equals(ORDINAL_METHOD)
- && instruction.getNext() != null
- && instruction.getNext().getNext() != null
- && instruction.getNext().getOpcode() == Opcodes.IALOAD
- && instruction.getNext().getNext().getOpcode()
- == Opcodes.TABLESWITCH) {
- message = String.format(
- "Enum for switch requires API level %1$d " +
- "(current min is %2$d): %3$s",
- api, minSdk, ClassContext.getFqcn(owner));
- }
-
- report(context, message, node, method, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
- }
-
- // For virtual dispatch, walk up the inheritance chain checking
- // each inherited method
- if (owner.startsWith("android/") //$NON-NLS-1$
- || owner.startsWith("javax/")) { //$NON-NLS-1$
- // The API map has already inlined all inherited methods
- // so no need to keep checking up the chain
- // -- unless it's the support library which is also in
- // the android/ namespace:
- if (owner.startsWith("android/support/")) { //$NON-NLS-1$
- owner = context.getDriver().getSuperClass(owner);
- } else {
- owner = null;
- }
- } else if (owner.startsWith("java/")) { //$NON-NLS-1$
- if (owner.equals(LocaleDetector.DATE_FORMAT_OWNER)) {
- checkSimpleDateFormat(context, method, node, minSdk);
- }
- // Already inlined; see comment above
- owner = null;
- } else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
- owner = context.getDriver().getSuperClass(owner);
- } else if (node.getOpcode() == Opcodes.INVOKESTATIC && api == -1) {
- // Inherit through static classes as well
- owner = context.getDriver().getSuperClass(owner);
- } else {
- owner = null;
- }
-
- checkingSuperClass = true;
- }
- } else if (type == AbstractInsnNode.FIELD_INSN) {
- FieldInsnNode node = (FieldInsnNode) instruction;
- String name = node.name;
- String owner = node.owner;
- int api = mApiDatabase.getFieldVersion(owner, name);
- if (api > minSdk) {
- if (method.name.startsWith(SWITCH_TABLE_PREFIX)) {
- checkSwitchBlock(context, classNode, node, method, name, owner,
- api, minSdk);
- continue;
- }
- String fqcn = ClassContext.getFqcn(owner) + '#' + name;
- if (mPendingFields != null) {
- mPendingFields.remove(fqcn);
- }
- String message = String.format(
- "Field requires API level %1$d (current min is %2$d): %3$s",
- api, minSdk, fqcn);
- report(context, message, node, method, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
- }
- } else if (type == AbstractInsnNode.LDC_INSN) {
- LdcInsnNode node = (LdcInsnNode) instruction;
- if (node.cst instanceof Type) {
- Type t = (Type) node.cst;
- String className = t.getInternalName();
-
- int api = mApiDatabase.getClassVersion(className);
- if (api > minSdk) {
- String fqcn = ClassContext.getFqcn(className);
- String message = String.format(
- "Class requires API level %1$d (current min is %2$d): %3$s",
- api, minSdk, fqcn);
- report(context, message, node, method,
- className.substring(className.lastIndexOf('/') + 1), null,
- SearchHints.create(FORWARD).matchJavaSymbol());
- }
- }
- }
- }
- }
- }
-
- private static void checkSimpleDateFormat(ClassContext context, MethodNode method,
- MethodInsnNode node, int minSdk) {
- if (minSdk >= 9) {
- // Already OK
- return;
- }
- if (node.name.equals(CONSTRUCTOR_NAME) && !node.desc.equals("()V")) { //$NON-NLS-1$
- // Check first argument
- AbstractInsnNode prev = LintUtils.getPrevInstruction(node);
- if (prev != null && !node.desc.equals("(Ljava/lang/String;)V")) { //$NON-NLS-1$
- prev = LintUtils.getPrevInstruction(prev);
- }
- if (prev != null && prev.getOpcode() == Opcodes.LDC) {
- LdcInsnNode ldc = (LdcInsnNode) prev;
- Object cst = ldc.cst;
- if (cst instanceof String) {
- String pattern = (String) cst;
- boolean isEscaped = false;
- for (int i = 0; i < pattern.length(); i++) {
- char c = pattern.charAt(i);
- if (c == '\'') {
- isEscaped = !isEscaped;
- } else if (!isEscaped && (c == 'L' || c == 'c')) {
- String message = String.format(
- "The pattern character '%1$c' requires API level 9 (current " +
- "min is %2$d) : \"%3$s\"", c, minSdk, pattern);
- report(context, message, node, method, pattern, null,
- SearchHints.create(FORWARD));
- return;
- }
- }
- }
- }
- }
- }
-
- @SuppressWarnings("rawtypes") // ASM API
- private static boolean methodDefinedLocally(ClassNode classNode, String name, String desc) {
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- if (name.equals(method.name) && desc.equals(method.desc)) {
- return true;
- }
- }
-
- return false;
- }
-
- @SuppressWarnings("rawtypes") // ASM API
- private static void checkSwitchBlock(ClassContext context, ClassNode classNode,
- FieldInsnNode field, MethodNode method, String name, String owner, int api,
- int minSdk) {
- // Switch statements on enums are tricky. The compiler will generate a method
- // which returns an array of the enum constants, indexed by their ordinal() values.
- // However, we only want to complain if the code is actually referencing one of
- // the non-available enum fields.
- //
- // For the android.graphics.PorterDuff.Mode enum for example, the first few items
- // in the array are populated like this:
- //
- // L0
- // ALOAD 0
- // GETSTATIC android/graphics/PorterDuff$Mode.ADD : Landroid/graphics/PorterDuff$Mode;
- // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
- // ICONST_1
- // IASTORE
- // L1
- // GOTO L3
- // L2
- // FRAME FULL [[I] [java/lang/NoSuchFieldError]
- // POP
- // L3
- // FRAME SAME
- // ALOAD 0
- // GETSTATIC android/graphics/PorterDuff$Mode.CLEAR : Landroid/graphics/PorterDuff$Mode;
- // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
- // ICONST_2
- // IASTORE
- // ...
- // So if we for example find that the "ADD" field isn't accessible, since it requires
- // API 11, we need to
- // (1) First find out what its ordinal number is. We can look at the following
- // instructions to discover this; it's the "ICONST_1" and "IASTORE" instructions.
- // (After ICONST_5 it moves on to BIPUSH 6, BIPUSH 7, etc.)
- // (2) Find the corresponding *usage* of this switch method. For the above enum,
- // the switch ordinal lookup method will be called
- // "$SWITCH_TABLE$android$graphics$PorterDuff$Mode" with desc "()[I".
- // This means we will be looking for an invocation in some other method which looks
- // like this:
- // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I
- // (obviously, it can be invoked more than once)
- // Note that it can be used more than once in this class and all sites should be
- // checked!
- // (3) Look up the corresponding table switch, which should look something like this:
- // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I
- // ALOAD 0
- // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I
- // IALOAD
- // LOOKUPSWITCH
- // 2: L1
- // 11: L2
- // default: L3
- // Here we need to see if the LOOKUPSWITCH instruction is referencing our target
- // case. Above we were looking for the "ADD" case which had ordinal 1. Since this
- // isn't explicitly referenced, we can ignore this field reference.
- AbstractInsnNode next = field.getNext();
- if (next == null || next.getOpcode() != Opcodes.INVOKEVIRTUAL) {
- return;
- }
- next = next.getNext();
- if (next == null) {
- return;
- }
- int ordinal;
- switch (next.getOpcode()) {
- case Opcodes.ICONST_0: ordinal = 0; break;
- case Opcodes.ICONST_1: ordinal = 1; break;
- case Opcodes.ICONST_2: ordinal = 2; break;
- case Opcodes.ICONST_3: ordinal = 3; break;
- case Opcodes.ICONST_4: ordinal = 4; break;
- case Opcodes.ICONST_5: ordinal = 5; break;
- case Opcodes.BIPUSH: {
- IntInsnNode iin = (IntInsnNode) next;
- ordinal = iin.operand;
- break;
- }
- default:
- return;
- }
-
- // Find usages of this call site
- List methodList = classNode.methods;
- for (Object m : methodList) {
- InsnList nodes = ((MethodNode) m).instructions;
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- if (instruction.getOpcode() != Opcodes.INVOKESTATIC){
- continue;
- }
- MethodInsnNode node = (MethodInsnNode) instruction;
- if (node.name.equals(method.name)
- && node.desc.equals(method.desc)
- && node.owner.equals(classNode.name)) {
- // Find lookup switch
- AbstractInsnNode target = getNextInstruction(node);
- while (target != null) {
- if (target.getOpcode() == Opcodes.LOOKUPSWITCH) {
- LookupSwitchInsnNode lookup = (LookupSwitchInsnNode) target;
- @SuppressWarnings("unchecked") // ASM API
- List<Integer> keys = lookup.keys;
- if (keys != null && keys.contains(ordinal)) {
- String fqcn = ClassContext.getFqcn(owner) + '#' + name;
- String message = String.format(
- "Enum value requires API level %1$d " +
- "(current min is %2$d): %3$s",
- api, minSdk, fqcn);
- report(context, message, lookup, (MethodNode) m, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
-
- // Break out of the inner target search only; the switch
- // statement could be used in other places in this class as
- // well and we want to report all problematic usages.
- break;
- }
- }
- target = getNextInstruction(target);
- }
- }
- }
- }
- }
-
- /**
- * Return the {@code @TargetApi} level to use for the given {@code classNode};
- * this will be the {@code @TargetApi} annotation on the class, or any outer
- * methods (for anonymous inner classes) or outer classes (for inner classes)
- * of the given class.
- */
- private static int getClassMinSdk(ClassContext context, ClassNode classNode) {
- int classMinSdk = getLocalMinSdk(classNode.invisibleAnnotations);
- if (classMinSdk != -1) {
- return classMinSdk;
- }
-
- LintDriver driver = context.getDriver();
- while (classNode != null) {
- ClassNode prev = classNode;
- classNode = driver.getOuterClassNode(classNode);
- if (classNode != null) {
- // TODO: Should this be "curr" instead?
- if (prev.outerMethod != null) {
- @SuppressWarnings("rawtypes") // ASM API
- List methods = classNode.methods;
- for (Object m : methods) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(prev.outerMethod)
- && method.desc.equals(prev.outerMethodDesc)) {
- // Found the outer method for this anonymous class; check method
- // annotations on it, then continue up the class hierarchy
- int methodMinSdk = getLocalMinSdk(method.invisibleAnnotations);
- if (methodMinSdk != -1) {
- return methodMinSdk;
- }
-
- break;
- }
- }
- }
-
- classMinSdk = getLocalMinSdk(classNode.invisibleAnnotations);
- if (classMinSdk != -1) {
- return classMinSdk;
- }
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the minimum SDK to use according to the given annotation list, or
- * -1 if no annotation was found.
- *
- * @param annotations a list of annotation nodes from ASM
- * @return the API level to use for this node, or -1
- */
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static int getLocalMinSdk(List annotations) {
- if (annotations != null) {
- for (AnnotationNode annotation : (List<AnnotationNode>)annotations) {
- String desc = annotation.desc;
- if (desc.endsWith(TARGET_API_VMSIG)) {
- if (annotation.values != null) {
- for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
- String key = (String) annotation.values.get(i);
- if (key.equals("value")) { //$NON-NLS-1$
- Object value = annotation.values.get(i + 1);
- if (value instanceof Integer) {
- return ((Integer) value).intValue();
- }
- }
- }
- }
- }
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the minimum SDK to use in the given element context, or -1 if no
- * {@code tools:targetApi} attribute was found.
- *
- * @param element the element to look at, including parents
- * @return the API level to use for this element, or -1
- */
- private static int getLocalMinSdk(@NonNull Element element) {
- while (element != null) {
- String targetApi = element.getAttributeNS(TOOLS_URI, ATTR_TARGET_API);
- if (targetApi != null && !targetApi.isEmpty()) {
- if (Character.isDigit(targetApi.charAt(0))) {
- try {
- return Integer.parseInt(targetApi);
- } catch (NumberFormatException nufe) {
- break;
- }
- }
-
- for (int api = 1; api <= SdkConstants.HIGHEST_KNOWN_API; api++) {
- String code = LintUtils.getBuildCode(api);
- if (code != null && code.equalsIgnoreCase(targetApi)) {
- return api;
- }
- }
- }
-
- Node parent = element.getParentNode();
- if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
- element = (Element) parent;
- } else {
- break;
- }
- }
-
- return -1;
- }
-
- private static void report(final ClassContext context, String message, AbstractInsnNode node,
- MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
- int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
-
- // If looking for a constructor, the string we'll see in the source is not the
- // method name (<init>) but the class name
- if (patternStart != null && patternStart.equals(CONSTRUCTOR_NAME)
- && node instanceof MethodInsnNode) {
- if (hints != null) {
- hints = hints.matchConstructor();
- }
- patternStart = ((MethodInsnNode) node).owner;
- }
-
- if (patternStart != null) {
- int index = patternStart.lastIndexOf('$');
- if (index != -1) {
- patternStart = patternStart.substring(index + 1);
- }
- index = patternStart.lastIndexOf('/');
- if (index != -1) {
- patternStart = patternStart.substring(index + 1);
- }
- }
-
- Location location = context.getLocationForLine(lineNumber, patternStart, patternEnd,
- hints);
- context.report(UNSUPPORTED, method, node, location, message, null);
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mPendingFields != null) {
- for (List<Pair<String, Location>> list : mPendingFields.values()) {
- for (Pair<String, Location> pair : list) {
- String message = pair.getFirst();
- Location location = pair.getSecond();
- context.report(INLINED, location, message, null);
- }
- }
- }
-
- super.afterCheckProject(context);
- }
-
-// ---- Implements JavaScanner ----
-
- @Nullable
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new ApiVisitor(context);
- }
-
- @Nullable
- @Override
- public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
- List<Class<? extends lombok.ast.Node>> types =
- new ArrayList<Class<? extends lombok.ast.Node>>(2);
- types.add(ImportDeclaration.class);
- types.add(Select.class);
- types.add(MethodDeclaration.class);
- types.add(ConstructorDeclaration.class);
- types.add(VariableDefinitionEntry.class);
- types.add(VariableReference.class);
- return types;
- }
-
- /**
- * Checks whether the given instruction is a benign usage of a constant defined in
- * a later version of Android than the application's {@code minSdkVersion}.
- *
- * @param node the instruction to check
- * @param name the name of the constant
- * @param owner the field owner
- * @return true if the given usage is safe on older versions than the introduction
- * level of the constant
- */
- public boolean isBenignConstantUsage(
- @Nullable lombok.ast.Node node,
- @NonNull String name,
- @NonNull String owner) {
- if (owner.equals("android/os/Build$VERSION_CODES")) { //$NON-NLS-1$
- // These constants are required for compilation, not execution
- // and valid code checks it even on older platforms
- return true;
- }
- if (owner.equals("android/view/ViewGroup$LayoutParams") //$NON-NLS-1$
- && name.equals("MATCH_PARENT")) { //$NON-NLS-1$
- return true;
- }
- if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$
- && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$
- || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$
- || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$
- // android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1,
- // but in API 11 it was moved up to the parent class AbsListView.
- // Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11,
- // but the constant is the same as the older version, so accept this without
- // warning.
- return true;
- }
-
- if (node == null) {
- return false;
- }
-
- // It's okay to reference the constant as a case constant (since that
- // code path won't be taken) or in a condition of an if statement
- lombok.ast.Node curr = node.getParent();
- while (curr != null) {
- Class<? extends lombok.ast.Node> nodeType = curr.getClass();
- if (nodeType == Case.class) {
- Case caseStatement = (Case) curr;
- Expression condition = caseStatement.astCondition();
- return condition != null && isAncestor(condition, node);
- } else if (nodeType == If.class) {
- If ifStatement = (If) curr;
- Expression condition = ifStatement.astCondition();
- return condition != null && isAncestor(condition, node);
- } else if (nodeType == InlineIfExpression.class) {
- InlineIfExpression ifStatement = (InlineIfExpression) curr;
- Expression condition = ifStatement.astCondition();
- return condition != null && isAncestor(condition, node);
- }
- curr = curr.getParent();
- }
-
- return false;
- }
-
- private static boolean isAncestor(
- @NonNull lombok.ast.Node ancestor,
- @Nullable lombok.ast.Node node) {
- while (node != null) {
- if (node == ancestor) {
- return true;
- }
- node = node.getParent();
- }
-
- return false;
- }
-
- private final class ApiVisitor extends ForwardingAstVisitor {
- private JavaContext mContext;
- private Map<String, String> mClassToImport = Maps.newHashMap();
- private List<String> mStarImports;
- private Set<String> mLocalVars;
- private lombok.ast.Node mCurrentMethod;
- private Set<String> mFields;
- private List<String> mStaticStarImports;
-
- private ApiVisitor(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitImportDeclaration(ImportDeclaration node) {
- if (node.astStarImport()) {
- // Similarly, if you're inheriting from a constants class, figure out
- // how that works... :=(
- String fqcn = node.asFullyQualifiedName();
- int strip = fqcn.lastIndexOf('*');
- if (strip != -1) {
- strip = fqcn.lastIndexOf('.', strip);
- if (strip != -1) {
- String pkgName = getInternalName(fqcn.substring(0, strip));
- if (ApiLookup.isRelevantOwner(pkgName)) {
- if (node.astStaticImport()) {
- if (mStaticStarImports == null) {
- mStaticStarImports = Lists.newArrayList();
- }
- mStaticStarImports.add(pkgName);
- } else {
- if (mStarImports == null) {
- mStarImports = Lists.newArrayList();
- }
- mStarImports.add(pkgName);
- }
- }
- }
- }
- } else if (node.astStaticImport()) {
- String fqcn = node.asFullyQualifiedName();
- String fieldName = getInternalName(fqcn);
- int index = fieldName.lastIndexOf('$');
- if (index != -1) {
- String owner = fieldName.substring(0, index);
- String name = fieldName.substring(index + 1);
- checkField(node, name, owner);
- }
- } else {
- // Store in map -- if it's "one of ours"
- // Use override detector's map for that purpose
- String fqcn = node.asFullyQualifiedName();
-
- int last = fqcn.lastIndexOf('.');
- if (last != -1) {
- String className = fqcn.substring(last + 1);
- mClassToImport.put(className, fqcn);
- }
- }
-
- return super.visitImportDeclaration(node);
- }
-
- @Override
- public boolean visitSelect(Select node) {
- boolean result = super.visitSelect(node);
-
- if (node.getParent() instanceof Select) {
- // We only want to look at the leaf expressions; e.g. if you have
- // "foo.bar.baz" we only care about the select foo.bar.baz, not foo.bar
- return result;
- }
-
- // See if this corresponds to a field reference. We assume it's a field if
- // it's a select (x.y) and either the identifier y is capitalized (e.g.
- // foo.VIEW_MASK) or if it's a member of an R class (R.id.foo).
- String name = node.astIdentifier().astValue();
- boolean isField = Character.isUpperCase(name.charAt(0));
- if (!isField) {
- // See if there's an R class
- Select current = node;
- while (current != null) {
- Expression operand = current.astOperand();
- if (operand instanceof Select) {
- current = (Select) operand;
- if (R_CLASS.equals(current.astIdentifier().astValue())) {
- isField = true;
- break;
- }
- } else if (operand instanceof VariableReference) {
- VariableReference reference = (VariableReference) operand;
- if (R_CLASS.equals(reference.astIdentifier().astValue())) {
- isField = true;
- }
- break;
- } else {
- break;
- }
- }
- }
-
- if (isField) {
- Expression operand = node.astOperand();
- if (operand.getClass() == Select.class) {
- // Possibly a fully qualified name in place
- String cls = operand.toString();
-
- // See if it's an imported class with an inner class
- // (e.g. Manifest.permission.FIELD)
- if (Character.isUpperCase(cls.charAt(0))) {
- int firstDot = cls.indexOf('.');
- if (firstDot != -1) {
- String base = cls.substring(0, firstDot);
- String fqcn = mClassToImport.get(base);
- if (fqcn != null) {
- // Yes imported
- String owner = getInternalName(fqcn + cls.substring(firstDot));
- checkField(node, name, owner);
- return result;
- }
-
- // Might be a star import: have to iterate and check here
- if (mStarImports != null) {
- for (String packagePrefix : mStarImports) {
- String owner = getInternalName(packagePrefix + '/' + cls);
- if (checkField(node, name, owner)) {
- mClassToImport.put(name, owner);
- return result;
- }
- }
- }
- }
- }
-
- // See if it's a fully qualified reference in place
- String owner = getInternalName(cls);
- checkField(node, name, owner);
- return result;
- } else if (operand.getClass() == VariableReference.class) {
- String className = ((VariableReference) operand).astIdentifier().astValue();
- // Not a FQCN that we care about: look in imports
- String fqcn = mClassToImport.get(className);
- if (fqcn != null) {
- // Yes imported
- String owner = getInternalName(fqcn);
- checkField(node, name, owner);
- return result;
- }
-
- if (Character.isUpperCase(className.charAt(0))) {
- // Might be a star import: have to iterate and check here
- if (mStarImports != null) {
- for (String packagePrefix : mStarImports) {
- String owner = getInternalName(packagePrefix) + '/' + className;
- if (checkField(node, name, owner)) {
- mClassToImport.put(name, owner);
- return result;
- }
- }
- }
- }
- }
- }
- return result;
- }
-
- @Override
- public boolean visitVariableReference(VariableReference node) {
- boolean result = super.visitVariableReference(node);
-
- if (node.getParent() != null) {
- lombok.ast.Node parent = node.getParent();
- Class<? extends lombok.ast.Node> parentClass = parent.getClass();
- if (parentClass == Select.class
- || parentClass == Switch.class // look up on the switch expression type
- || parentClass == Case.class
- || parentClass == ConstructorInvocation.class
- || parentClass == SuperConstructorInvocation.class
- || parentClass == AnnotationElement.class) {
- return result;
- }
-
- if (parent instanceof MethodInvocation &&
- ((MethodInvocation) parent).astOperand() == node) {
- return result;
- } else if (parent instanceof BinaryExpression) {
- BinaryExpression expression = (BinaryExpression) parent;
- if (expression.astLeft() == node) {
- return result;
- }
- }
- }
-
- String name = node.astIdentifier().astValue();
- if (Character.isUpperCase(name.charAt(0))
- && (mLocalVars == null || !mLocalVars.contains(name))
- && (mFields == null || !mFields.contains(name))) {
- // Potential field reference: check it
- if (mStaticStarImports != null) {
- for (String owner : mStaticStarImports) {
- if (checkField(node, name, owner)) {
- break;
- }
- }
- }
- }
-
- return result;
- }
-
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- if (mCurrentMethod != null) {
- if (mLocalVars == null) {
- mLocalVars = Sets.newHashSet();
- }
- mLocalVars.add(node.astName().astValue());
- } else {
- if (mFields == null) {
- mFields = Sets.newHashSet();
- }
- mFields.add(node.astName().astValue());
- }
- return super.visitVariableDefinitionEntry(node);
- }
-
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- mLocalVars = null;
- mCurrentMethod = node;
- return super.visitMethodDeclaration(node);
- }
-
- @Override
- public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
- mLocalVars = null;
- mCurrentMethod = node;
- return super.visitConstructorDeclaration(node);
- }
-
- @Override
- public void endVisit(lombok.ast.Node node) {
- if (node == mCurrentMethod) {
- mCurrentMethod = null;
- }
- super.endVisit(node);
- }
-
- /**
- * Checks a Java source field reference. Returns true if the field is known
- * regardless of whether it's an invalid field or not
- */
- private boolean checkField(
- @NonNull lombok.ast.Node node,
- @NonNull String name,
- @NonNull String owner) {
- int api = mApiDatabase.getFieldVersion(owner, name);
- if (api != -1) {
- int minSdk = getMinSdk(mContext);
- if (api > minSdk
- && api > getLocalMinSdk(node)) {
- if (isBenignConstantUsage(node, name, owner)) {
- return true;
- }
-
- Location location = mContext.getLocation(node);
- String fqcn = getFqcn(owner) + '#' + name;
-
- if (node instanceof ImportDeclaration) {
- // Replace import statement location range with just
- // the identifier part
- ImportDeclaration d = (ImportDeclaration) node;
- int startOffset = d.astParts().first().getPosition().getStart();
- Position start = location.getStart();
- int startColumn = start.getColumn();
- int startLine = start.getLine();
- start = new DefaultPosition(startLine,
- startColumn + startOffset - start.getOffset(), startOffset);
- int fqcnLength = fqcn.length();
- Position end = new DefaultPosition(startLine,
- start.getColumn() + fqcnLength,
- start.getOffset() + fqcnLength);
- location = Location.create(location.getFile(), start, end);
- }
-
- String message = String.format(
- "Field requires API level %1$d (current min is %2$d): %3$s",
- api, minSdk, fqcn);
-
- LintDriver driver = mContext.getDriver();
- if (driver.isSuppressed(INLINED, node)) {
- return true;
- }
-
- // Also allow to suppress these issues with NewApi, since some
- // fields used to get identified that way
- if (driver.isSuppressed(UNSUPPORTED, node)) {
- return true;
- }
-
- // We can't report the issue right away; we don't yet know if
- // this is an actual inlined (static primitive or String) yet.
- // So just make a note of it, and report these after the project
- // checking has finished; any fields that aren't inlined will be
- // cleared when they're noticed by the class check.
- if (mPendingFields == null) {
- mPendingFields = Maps.newHashMapWithExpectedSize(20);
- }
- List<Pair<String, Location>> list = mPendingFields.get(fqcn);
- if (list == null) {
- list = new ArrayList<Pair<String, Location>>();
- mPendingFields.put(fqcn, list);
- }
- list.add(Pair.of(message, location));
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Returns the minimum SDK to use according to the given AST node, or null
- * if no {@code TargetApi} annotations were found
- *
- * @return the API level to use for this node, or -1
- */
- public int getLocalMinSdk(@Nullable lombok.ast.Node scope) {
- while (scope != null) {
- Class<? extends lombok.ast.Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == VariableDefinition.class) {
- // Variable
- VariableDefinition declaration = (VariableDefinition) scope;
- int targetApi = getLocalMinSdk(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- } else if (type == MethodDeclaration.class) {
- // Method
- // Look for annotations on the method
- MethodDeclaration declaration = (MethodDeclaration) scope;
- int targetApi = getLocalMinSdk(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- } else if (type == ConstructorDeclaration.class) {
- // Constructor
- // Look for annotations on the method
- ConstructorDeclaration declaration = (ConstructorDeclaration) scope;
- int targetApi = getLocalMinSdk(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- } else if (type == ClassDeclaration.class) {
- // Class
- ClassDeclaration declaration = (ClassDeclaration) scope;
- int targetApi = getLocalMinSdk(declaration.astModifiers());
- if (targetApi != -1) {
- return targetApi;
- }
- }
-
- scope = scope.getParent();
- }
-
- return -1;
- }
-
- /**
- * Returns true if the given AST modifier has a suppress annotation for the
- * given issue (which can be null to check for the "all" annotation)
- *
- * @param modifiers the modifier to check
- * @return true if the issue or all issues should be suppressed for this
- * modifier
- */
- private int getLocalMinSdk(@Nullable Modifiers modifiers) {
- if (modifiers == null) {
- return -1;
- }
- StrictListAccessor<Annotation, Modifiers> annotations = modifiers.astAnnotations();
- if (annotations == null) {
- return -1;
- }
-
- Iterator<Annotation> iterator = annotations.iterator();
- while (iterator.hasNext()) {
- Annotation annotation = iterator.next();
- TypeReference t = annotation.astAnnotationTypeReference();
- String typeName = t.getTypeName();
- if (typeName.endsWith(TARGET_API)) {
- StrictListAccessor<AnnotationElement, Annotation> values =
- annotation.astElements();
- if (values != null) {
- Iterator<AnnotationElement> valueIterator = values.iterator();
- while (valueIterator.hasNext()) {
- AnnotationElement element = valueIterator.next();
- AnnotationValue valueNode = element.astValue();
- if (valueNode == null) {
- continue;
- }
- if (valueNode instanceof IntegralLiteral) {
- IntegralLiteral literal = (IntegralLiteral) valueNode;
- return literal.astIntValue();
- } else if (valueNode instanceof StringLiteral) {
- String value = ((StringLiteral) valueNode).astValue();
- return codeNameToApi(value);
- } else if (valueNode instanceof Select) {
- Select select = (Select) valueNode;
- String codename = select.astIdentifier().astValue();
- return codeNameToApi(codename);
- } else if (valueNode instanceof VariableReference) {
- VariableReference reference = (VariableReference) valueNode;
- String codename = reference.astIdentifier().astValue();
- return codeNameToApi(codename);
- }
- }
- }
- }
- }
-
- return -1;
- }
- }
-
- private static int codeNameToApi(String codename) {
- for (int api = 1; api <= SdkConstants.HIGHEST_KNOWN_API; api++) {
- String code = LintUtils.getBuildCode(api);
- if (code != null && code.equalsIgnoreCase(codename)) {
- return api;
- }
- }
-
- return -1;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
deleted file mode 100644
index 6a4de6e..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
+++ /dev/null
@@ -1,971 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PKG;
-import static com.android.SdkConstants.DOT_XML;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-import com.google.common.primitives.UnsignedBytes;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel.MapMode;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Database for API checking: Allows quick lookup of a given class, method or field
- * to see which API level it was introduced in.
- * <p>
- * This class is optimized for quick bytecode lookup used in conjunction with the
- * ASM library: It has lookup methods that take internal JVM signatures, and for a method
- * call for example it processes the owner, name and description parameters separately
- * the way they are provided from ASM.
- * <p>
- * The {@link Api} class provides access to the full Android API along with version
- * information, initialized from an XML file. This lookup class adds a binary cache around
- * the API to make initialization faster and to require fewer objects. It creates
- * a binary cache data structure, which fits in a single byte array, which means that
- * to open the database you can just read in the byte array and go. On one particular
- * machine, this takes about 30-50 ms versus 600-800ms for the full parse. It also
- * helps memory by placing everything in a compact byte array instead of needing separate
- * strings (2 bytes per character in a char[] for the 25k method entries, 11k field entries
- * and 6k class entries) - and it also avoids the same number of Map.Entry objects.
- * When creating the memory data structure it performs a few other steps to help memory:
- * <ul>
- * <li> It stores the strings as single bytes, since all the JVM signatures are in ASCII
- * <li> It strips out the method return types (which takes the binary size down from
- * about 4.7M to 4.0M)
- * <li> It strips out all APIs that have since=1, since the lookup only needs to find
- * classes, methods and fields that have an API level *higher* than 1. This drops
- * the memory use down from 4.0M to 1.7M.
- * </ul>
- */
-public class ApiLookup {
- /** Relative path to the api-versions.xml database file within the Lint installation */
- private static final String XML_FILE_PATH = "platform-tools/api/api-versions.xml"; //$NON-NLS-1$
- private static final String FILE_HEADER = "API database used by Android lint\000";
- private static final int BINARY_FORMAT_VERSION = 5;
- private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
- private static final boolean DEBUG_SEARCH = false;
- private static final boolean WRITE_STATS = false;
- /** Default size to reserve for each API entry when creating byte buffer to build up data */
- private static final int BYTES_PER_ENTRY = 36;
-
- private final LintClient mClient;
- private final File mXmlFile;
- private final File mBinaryFile;
- private final Api mInfo;
- private byte[] mData;
- private int[] mIndices;
- private int mClassCount;
- private String[] mJavaPackages;
-
- private static WeakReference<ApiLookup> sInstance =
- new WeakReference<ApiLookup>(null);
-
- /**
- * Returns an instance of the API database
- *
- * @param client the client to associate with this database - used only for
- * logging. The database object may be shared among repeated invocations,
- * and in that case client used will be the one originally passed in.
- * In other words, this parameter may be ignored if the client created
- * is not new.
- * @return a (possibly shared) instance of the API database, or null
- * if its data can't be found
- */
- public static ApiLookup get(LintClient client) {
- synchronized (ApiLookup.class) {
- ApiLookup db = sInstance.get();
- if (db == null) {
- File file = client.findResource(XML_FILE_PATH);
- if (file == null) {
- // AOSP build environment?
- String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
- if (build != null) {
- file = new File(build, "development/sdk/api-versions.xml" //$NON-NLS-1$
- .replace('/', File.separatorChar));
- }
- }
-
- if (file == null || !file.exists()) {
- client.log(null, "Fatal error: No API database found at %1$s", file);
- return null;
- } else {
- db = get(client, file);
- }
- sInstance = new WeakReference<ApiLookup>(db);
- }
-
- return db;
- }
- }
-
- @VisibleForTesting
- static String getCacheFileName(String xmlFileName) {
- if (LintUtils.endsWith(xmlFileName, DOT_XML)) {
- xmlFileName = xmlFileName.substring(0, xmlFileName.length() - DOT_XML.length());
- }
-
- // Incorporate version number in the filename to avoid upgrade filename
- // conflicts on Windows (such as issue #26663)
- return xmlFileName + '-' + BINARY_FORMAT_VERSION + ".bin"; //$NON-NLS-1$
- }
-
- /**
- * Returns an instance of the API database
- *
- * @param client the client to associate with this database - used only for
- * logging
- * @param xmlFile the XML file containing configuration data to use for this
- * database
- * @return a (possibly shared) instance of the API database, or null
- * if its data can't be found
- */
- private static ApiLookup get(LintClient client, File xmlFile) {
- if (!xmlFile.exists()) {
- client.log(null, "The API database file %1$s does not exist", xmlFile);
- return null;
- }
-
- File cacheDir = client.getCacheDir(true/*create*/);
- if (cacheDir == null) {
- cacheDir = xmlFile.getParentFile();
- }
-
- File binaryData = new File(cacheDir, getCacheFileName(xmlFile.getName()));
-
- if (DEBUG_FORCE_REGENERATE_BINARY) {
- System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom "
- + xmlFile + "\nto " + binaryData);
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified()
- || binaryData.length() == 0) {
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- }
-
- if (!binaryData.exists()) {
- client.log(null, "The API database file %1$s does not exist", binaryData);
- return null;
- }
-
- return new ApiLookup(client, xmlFile, binaryData, null);
- }
-
- private static boolean createCache(LintClient client, File xmlFile, File binaryData) {
- long begin = 0;
- if (WRITE_STATS) {
- begin = System.currentTimeMillis();
- }
-
- Api info = Api.parseApi(xmlFile);
-
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("Reading XML data structures took " + (end - begin) + " ms)");
- }
-
- if (info != null) {
- try {
- writeDatabase(binaryData, info);
- return true;
- } catch (IOException ioe) {
- client.log(ioe, "Can't write API cache file");
- }
- }
-
- return false;
- }
-
- /** Use one of the {@link #get} factory methods instead */
- private ApiLookup(
- @NonNull LintClient client,
- @NonNull File xmlFile,
- @Nullable File binaryFile,
- @Nullable Api info) {
- mClient = client;
- mXmlFile = xmlFile;
- mBinaryFile = binaryFile;
- mInfo = info;
-
- if (binaryFile != null) {
- readData();
- }
- }
-
- /**
- * Database format:
- * <pre>
- * 1. A file header, which is the exact contents of {@link #FILE_HEADER} encoded
- * as ASCII characters. The purpose of the header is to identify what the file
- * is for, for anyone attempting to open the file.
- * 2. A file version number. If the binary file does not match the reader's expected
- * version, it can ignore it (and regenerate the cache from XML).
- * 3. The number of classes [1 int]
- * 4. The number of members (across all classes) [1 int].
- * 5. The number of java/javax packages [1 int]
- * 6. The java/javax package name table. Each item consists of a byte count for
- * the package string (as 1 byte) followed by the UTF-8 encoded bytes for each package.
- * These are in sorted order.
- * 7. Class offset table (one integer per class, pointing to the byte offset in the
- * file (relative to the beginning of the file) where each class begins.
- * The classes are always sorted alphabetically by fully qualified name.
- * 8. Member offset table (one integer per member, pointing to the byte offset in the
- * file (relative to the beginning of the file) where each member entry begins.
- * The members are always sorted alphabetically.
- * 9. Class entry table. Each class entry consists of the fully qualified class name,
- * in JVM format (using / instead of . in package names and $ for inner classes),
- * followed by the byte 0 as a terminator, followed by the API version as a byte.
- * 10. Member entry table. Each member entry consists of the class number (as a short),
- * followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
- * signature terminator, followed by the API level as a byte.
- * <p>
- * TODO: Pack the offsets: They increase by a small amount for each entry, so no need
- * to spend 4 bytes on each. These will need to be processed when read back in anyway,
- * so consider storing the offset -deltas- as single bytes and adding them up cumulatively
- * in readData().
- * </pre>
- */
- private void readData() {
- if (!mBinaryFile.exists()) {
- mClient.log(null, "%1$s does not exist", mBinaryFile);
- return;
- }
- long start = System.currentTimeMillis();
- try {
- MappedByteBuffer buffer = Files.map(mBinaryFile, MapMode.READ_ONLY);
- assert buffer.order() == ByteOrder.BIG_ENDIAN;
-
- // First skip the header
- byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII);
- buffer.rewind();
- for (int offset = 0; offset < expectedHeader.length; offset++) {
- if (expectedHeader[offset] != buffer.get()) {
- mClient.log(null, "Incorrect file header: not an API database cache " +
- "file, or a corrupt cache file");
- return;
- }
- }
-
- // Read in the format number
- if (buffer.get() != BINARY_FORMAT_VERSION) {
- // Force regeneration of new binary data with up to date format
- if (createCache(mClient, mXmlFile, mBinaryFile)) {
- readData(); // Recurse
- }
-
- return;
- }
-
- mClassCount = buffer.getInt();
- int methodCount = buffer.getInt();
-
- int javaPackageCount = buffer.getInt();
- // Read in the Java packages
- mJavaPackages = new String[javaPackageCount];
- for (int i = 0; i < javaPackageCount; i++) {
- int count = UnsignedBytes.toInt(buffer.get());
- byte[] bytes = new byte[count];
- buffer.get(bytes, 0, count);
- mJavaPackages[i] = new String(bytes, Charsets.UTF_8);
- }
-
- // Read in the class table indices;
- int count = mClassCount + methodCount;
- int[] offsets = new int[count];
-
- // Another idea: I can just store the DELTAS in the file (and add them up
- // when reading back in) such that it takes just ONE byte instead of four!
-
- for (int i = 0; i < count; i++) {
- offsets[i] = buffer.getInt();
- }
-
- // No need to read in the rest -- we'll just keep the whole byte array in memory
- // TODO: Make this code smarter/more efficient.
- int size = buffer.limit();
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- mData = b;
- mIndices = offsets;
-
- // TODO: We only need to keep the data portion here since we've initialized
- // the offset array separately.
- // TODO: Investigate (profile) accessing the byte buffer directly instead of
- // accessing a byte array.
- } catch (Throwable e) {
- mClient.log(null, "Failure reading binary cache file %1$s", mBinaryFile.getPath());
- mClient.log(null, "Please delete the file and restart the IDE/lint: %1$s",
- mBinaryFile.getPath());
- mClient.log(e, null);
- }
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("\nRead API database in " + (end - start)
- + " milliseconds.");
- System.out.println("Size of data table: " + mData.length + " bytes ("
- + Integer.toString(mData.length / 1024) + "k)\n");
- }
- }
-
- /** See the {@link #readData()} for documentation on the data format. */
- private static void writeDatabase(File file, Api info) throws IOException {
- /*
- * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- * as ASCII characters. The purpose of the header is to identify what the file
- * is for, for anyone attempting to open the file.
- * 2. A file version number. If the binary file does not match the reader's expected
- * version, it can ignore it (and regenerate the cache from XML).
- */
- Map<String, ApiClass> classMap = info.getClasses();
- // Write the class table
-
- List<String> classes = new ArrayList<String>(classMap.size());
- Map<ApiClass, List<String>> memberMap =
- Maps.newHashMapWithExpectedSize(classMap.size());
- int memberCount = 0;
- Set<String> javaPackageSet = Sets.newHashSetWithExpectedSize(70);
- for (Map.Entry<String, ApiClass> entry : classMap.entrySet()) {
- String className = entry.getKey();
- ApiClass apiClass = entry.getValue();
-
- if (className.startsWith("java/") //$NON-NLS-1$
- || className.startsWith("javax/")) { //$NON-NLS-1$
- String pkg = apiClass.getPackage();
- javaPackageSet.add(pkg);
- }
-
- if (!isRelevantOwner(className)) {
- System.out.println("Warning: The isRelevantOwner method does not pass "
- + className);
- }
-
- Set<String> allMethods = apiClass.getAllMethods(info);
- Set<String> allFields = apiClass.getAllFields(info);
-
- // Strip out all members that have been supported since version 1.
- // This makes the database *much* leaner (down from about 4M to about
- // 1.7M), and this just fills the table with entries that ultimately
- // don't help the API checker since it just needs to know if something
- // requires a version *higher* than the minimum. If in the future the
- // database needs to answer queries about whether a method is public
- // or not, then we'd need to put this data back in.
- List<String> members = new ArrayList<String>(allMethods.size() + allFields.size());
- for (String member : allMethods) {
-
- Integer since = apiClass.getMethod(member, info);
- if (since == null) {
- assert false : className + ':' + member;
- since = 1;
- }
- if (since != 1) {
- members.add(member);
- }
- }
-
- // Strip out all members that have been supported since version 1.
- // This makes the database *much* leaner (down from about 4M to about
- // 1.7M), and this just fills the table with entries that ultimately
- // don't help the API checker since it just needs to know if something
- // requires a version *higher* than the minimum. If in the future the
- // database needs to answer queries about whether a method is public
- // or not, then we'd need to put this data back in.
- for (String member : allFields) {
- Integer since = apiClass.getField(member, info);
- if (since == null) {
- assert false : className + ':' + member;
- since = 1;
- }
- if (since != 1) {
- members.add(member);
- }
- }
-
- // Only include classes that have one or more members requiring version 2 or higher:
- if (!members.isEmpty()) {
- classes.add(className);
- memberMap.put(apiClass, members);
- memberCount += members.size();
- }
- }
- Collections.sort(classes);
-
- List<String> javaPackages = Lists.newArrayList(javaPackageSet);
- Collections.sort(javaPackages);
- int javaPackageCount = javaPackages.size();
-
- int entryCount = classMap.size() + memberCount;
- int capacity = entryCount * BYTES_PER_ENTRY;
- ByteBuffer buffer = ByteBuffer.allocate(capacity);
- buffer.order(ByteOrder.BIG_ENDIAN);
- // 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- // as ASCII characters. The purpose of the header is to identify what the file
- // is for, for anyone attempting to open the file.
-
- buffer.put(FILE_HEADER.getBytes(Charsets.US_ASCII));
-
- // 2. A file version number. If the binary file does not match the reader's expected
- // version, it can ignore it (and regenerate the cache from XML).
- buffer.put((byte) BINARY_FORMAT_VERSION);
-
- // 3. The number of classes [1 int]
- buffer.putInt(classes.size());
-
- // 4. The number of members (across all classes) [1 int].
- buffer.putInt(memberCount);
-
- // 5. The number of Java packages [1 int].
- buffer.putInt(javaPackageCount);
-
- // 6. The Java package table. There are javaPackage.size() entries, where each entry
- // consists of a string length, as a byte, followed by the bytes in the package.
- // There is no terminating 0.
- for (String pkg : javaPackages) {
- byte[] bytes = pkg.getBytes(Charsets.UTF_8);
- assert bytes.length < 255 : pkg;
- buffer.put((byte) bytes.length);
- buffer.put(bytes);
- }
-
- // 7. Class offset table (one integer per class, pointing to the byte offset in the
- // file (relative to the beginning of the file) where each class begins.
- // The classes are always sorted alphabetically by fully qualified name.
- int classOffsetTable = buffer.position();
-
- // Reserve enough room for the offset table here: we will backfill it with pointers
- // as we're writing out the data structures below
- for (int i = 0, n = classes.size(); i < n; i++) {
- buffer.putInt(0);
- }
-
- // 8. Member offset table (one integer per member, pointing to the byte offset in the
- // file (relative to the beginning of the file) where each member entry begins.
- // The members are always sorted alphabetically.
- int methodOffsetTable = buffer.position();
- for (int i = 0, n = memberCount; i < n; i++) {
- buffer.putInt(0);
- }
-
- int nextEntry = buffer.position();
- int nextOffset = classOffsetTable;
-
- // 9. Class entry table. Each class entry consists of the fully qualified class name,
- // in JVM format (using / instead of . in package names and $ for inner classes),
- // followed by the byte 0 as a terminator, followed by the API version as a byte.
- for (String clz : classes) {
- buffer.position(nextOffset);
- buffer.putInt(nextEntry);
- nextOffset = buffer.position();
- buffer.position(nextEntry);
- buffer.put(clz.getBytes(Charsets.UTF_8));
- buffer.put((byte) 0);
-
- ApiClass apiClass = classMap.get(clz);
- assert apiClass != null : clz;
- int since = apiClass.getSince();
- assert since == UnsignedBytes.toInt((byte) since) : since; // make sure it fits
- buffer.put((byte) since);
-
- nextEntry = buffer.position();
- }
-
- // 10. Member entry table. Each member entry consists of the class number (as a short),
- // followed by the JVM method/field signature, encoded as UTF-8, followed by a 0 byte
- // signature terminator, followed by the API level as a byte.
- assert nextOffset == methodOffsetTable;
-
- for (int classNumber = 0, n = classes.size(); classNumber < n; classNumber++) {
- String clz = classes.get(classNumber);
- ApiClass apiClass = classMap.get(clz);
- assert apiClass != null : clz;
- List<String> members = memberMap.get(apiClass);
- Collections.sort(members);
-
- for (String member : members) {
- buffer.position(nextOffset);
- buffer.putInt(nextEntry);
- nextOffset = buffer.position();
- buffer.position(nextEntry);
-
- Integer since;
- if (member.indexOf('(') != -1) {
- since = apiClass.getMethod(member, info);
- } else {
- since = apiClass.getField(member, info);
- }
- if (since == null) {
- assert false : clz + ':' + member;
- since = 1;
- }
-
- assert classNumber == (short) classNumber;
- buffer.putShort((short) classNumber);
- byte[] signature = member.getBytes(Charsets.UTF_8);
- for (int i = 0; i < signature.length; i++) {
- // Make sure all signatures are really just simple ASCII
- byte b = signature[i];
- assert b == (b & 0x7f) : member;
- buffer.put(b);
- // Skip types on methods
- if (b == (byte) ')') {
- break;
- }
- }
- buffer.put((byte) 0);
- int api = since;
- assert api == UnsignedBytes.toInt((byte) api);
- //assert api >= 1 && api < 0xFF; // max that fits in a byte
- buffer.put((byte) api);
- nextEntry = buffer.position();
- }
- }
-
- int size = buffer.position();
- assert size <= buffer.limit();
- buffer.mark();
-
- if (WRITE_STATS) {
- System.out.println("Wrote " + classes.size() + " classes and "
- + memberCount + " member entries");
- System.out.print("Actual binary size: " + size + " bytes");
- System.out.println(String.format(" (%.1fM)", size/(1024*1024.f)));
-
- System.out.println("Allocated size: " + (entryCount * BYTES_PER_ENTRY) + " bytes");
- System.out.println("Required bytes per entry: " + (size/ entryCount) + " bytes");
- }
-
- // Now dump this out as a file
- // There's probably an API to do this more efficiently; TODO: Look into this.
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- if (file.exists()) {
- file.delete();
- }
- FileOutputStream output = Files.newOutputStreamSupplier(file).getOutput();
- output.write(b);
- output.close();
- }
-
- // For debugging only
- private String dumpEntry(int offset) {
- if (DEBUG_SEARCH) {
- StringBuilder sb = new StringBuilder(200);
- for (int i = offset; i < mData.length; i++) {
- if (mData[i] == 0) {
- break;
- }
- char c = (char) UnsignedBytes.toInt(mData[i]);
- sb.append(c);
- }
-
- return sb.toString();
- } else {
- return "<disabled>"; //$NON-NLS-1$
- }
- }
-
- private static int compare(byte[] data, int offset, byte terminator, String s, int max) {
- int i = offset;
- int j = 0;
- for (; j < max; i++, j++) {
- byte b = data[i];
- char c = s.charAt(j);
- // TODO: Check somewhere that the strings are purely in the ASCII range; if not
- // they're not a match in the database
- byte cb = (byte) c;
- int delta = b - cb;
- if (delta != 0) {
- return delta;
- }
- }
-
- return data[i] - terminator;
- }
-
- /**
- * Quick determination whether a given class name is possibly interesting; this
- * is a quick package prefix check to determine whether we need to consider
- * the class at all. This let's us do less actual searching for the vast majority
- * of APIs (in libraries, application code etc) that have nothing to do with the
- * APIs in our packages.
- * @param name the class name in VM format (e.g. using / instead of .)
- * @return true if the owner is <b>possibly</b> relevant
- */
- public boolean isRelevantClass(String name) {
- // TODO: Add quick switching here. This is tied to the database file so if
- // we end up with unexpected prefixes there, this could break. For that reason,
- // for now we consider everything relevant.
- return true;
- }
-
- /**
- * Returns the API version required by the given class reference,
- * or -1 if this is not a known API class. Note that it may return -1
- * for classes introduced in version 1; internally the database only
- * stores version data for version 2 and up.
- *
- * @param className the internal name of the class, e.g. its
- * fully qualified name (as returned by Class.getName(), but with
- * '.' replaced by '/'.
- * @return the minimum API version the method is supported for, or -1 if
- * it's unknown <b>or version 1</b>.
- */
- public int getClassVersion(@NonNull String className) {
- if (!isRelevantClass(className)) {
- return -1;
- }
-
- if (mData != null) {
- int classNumber = findClass(className);
- if (classNumber != -1) {
- int offset = mIndices[classNumber];
- while (mData[offset] != 0) {
- offset++;
- }
- offset++;
- return UnsignedBytes.toInt(mData[offset]);
- }
- } else {
- ApiClass clz = mInfo.getClass(className);
- if (clz != null) {
- int since = clz.getSince();
- if (since == Integer.MAX_VALUE) {
- since = -1;
- }
- return since;
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the API version required by the given method call. The method is
- * referred to by its {@code owner}, {@code name} and {@code desc} fields.
- * If the method is unknown it returns -1. Note that it may return -1 for
- * classes introduced in version 1; internally the database only stores
- * version data for version 2 and up.
- *
- * @param owner the internal name of the method's owner class, e.g. its
- * fully qualified name (as returned by Class.getName(), but with
- * '.' replaced by '/'.
- * @param name the method's name
- * @param desc the method's descriptor - see {@link org.objectweb.asm.Type}
- * @return the minimum API version the method is supported for, or -1 if
- * it's unknown <b>or version 1</b>.
- */
- public int getCallVersion(
- @NonNull String owner,
- @NonNull String name,
- @NonNull String desc) {
- if (!isRelevantClass(owner)) {
- return -1;
- }
-
- if (mData != null) {
- int classNumber = findClass(owner);
- if (classNumber != -1) {
- return findMember(classNumber, name, desc);
- }
- } else {
- ApiClass clz = mInfo.getClass(owner);
- if (clz != null) {
- String signature = name + desc;
- int since = clz.getMethod(signature, mInfo);
- if (since == Integer.MAX_VALUE) {
- since = -1;
- }
- return since;
- }
- }
-
- return -1;
- }
-
- /**
- * Returns the API version required to access the given field, or -1 if this
- * is not a known API method. Note that it may return -1 for classes
- * introduced in version 1; internally the database only stores version data
- * for version 2 and up.
- *
- * @param owner the internal name of the method's owner class, e.g. its
- * fully qualified name (as returned by Class.getName(), but with
- * '.' replaced by '/'.
- * @param name the method's name
- * @return the minimum API version the method is supported for, or -1 if
- * it's unknown <b>or version 1</b>
- */
- public int getFieldVersion(
- @NonNull String owner,
- @NonNull String name) {
- if (!isRelevantClass(owner)) {
- return -1;
- }
-
- if (mData != null) {
- int classNumber = findClass(owner);
- if (classNumber != -1) {
- return findMember(classNumber, name, null);
- }
- } else {
- ApiClass clz = mInfo.getClass(owner);
- if (clz != null) {
- int since = clz.getField(name, mInfo);
- if (since == Integer.MAX_VALUE) {
- since = -1;
- }
- return since;
- }
- }
-
- return -1;
- }
-
- /**
- * Returns true if the given owner (in VM format) is relevant to the database.
- * This allows quick filtering out of owners that won't return any data
- * for the various {@code #getFieldVersion} etc methods.
- *
- * @param owner the owner to look up
- * @return true if the owner might be relevant to the API database
- */
- public static boolean isRelevantOwner(@NonNull String owner) {
- if (owner.startsWith("java")) { //$NON-NLS-1$ // includes javax/
- return true;
- }
- if (owner.startsWith(ANDROID_PKG)) {
- if (owner.startsWith("/support/", 7)) { //$NON-NLS-1$
- return false;
- }
- return true;
- } else if (owner.startsWith("org/")) { //$NON-NLS-1$
- if (owner.startsWith("xml", 4) //$NON-NLS-1$
- || owner.startsWith("w3c/", 4) //$NON-NLS-1$
- || owner.startsWith("json/", 4) //$NON-NLS-1$
- || owner.startsWith("apache/", 4)) { //$NON-NLS-1$
- return true;
- }
- } else if (owner.startsWith("com/")) { //$NON-NLS-1$
- if (owner.startsWith("google/", 4) //$NON-NLS-1$
- || owner.startsWith("android/", 4)) { //$NON-NLS-1$
- return true;
- }
- } else if (owner.startsWith("junit") //$NON-NLS-1$
- || owner.startsWith("dalvik")) { //$NON-NLS-1$
- return true;
- }
-
- return false;
- }
-
-
- /**
- * Returns true if the given owner (in VM format) is a valid Java package supported
- * in any version of Android.
- *
- * @param owner the package, in VM format
- * @return true if the package is included in one or more versions of Android
- */
- public boolean isValidJavaPackage(@NonNull String owner) {
- int packageLength = owner.lastIndexOf('/');
- if (packageLength == -1) {
- return false;
- }
-
- // The index array contains class indexes from 0 to classCount and
- // member indices from classCount to mIndices.length.
- int low = 0;
- int high = mJavaPackages.length - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = middle;
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + owner + " with entry at " + offset
- + ": " + mJavaPackages[offset]);
- }
-
- // Compare the api info at the given index.
- int compare = comparePackage(mJavaPackages[offset], owner, packageLength);
- if (compare == 0) {
- return true;
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return false;
- }
- }
-
- return false;
- }
-
- private static int comparePackage(String s1, String s2, int max) {
- for (int i = 0; i < max; i++) {
- if (i == s1.length()) {
- return -1;
- }
- char c1 = s1.charAt(i);
- char c2 = s2.charAt(i);
- if (c1 != c2) {
- return c1 - c2;
- }
- }
-
- if (s1.length() > max) {
- return 1;
- }
-
- return 0;
- }
-
- /** Returns the class number of the given class, or -1 if it is unknown */
- private int findClass(@NonNull String owner) {
- assert owner.indexOf('.') == -1 : "Should use / instead of . in owner: " + owner;
-
- // The index array contains class indexes from 0 to classCount and
- // member indices from classCount to mIndices.length.
- int low = 0;
- int high = mClassCount - 1;
- // Compare the api info at the given index.
- int classNameLength = owner.length();
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + owner + " with entry at " + offset
- + ": " + dumpEntry(offset));
- }
-
- int compare = compare(mData, offset, (byte) 0, owner, classNameLength);
- if (compare == 0) {
- return middle;
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return -1;
- }
- }
-
- return -1;
- }
-
- private int findMember(int classNumber, @NonNull String name, @Nullable String desc) {
- // The index array contains class indexes from 0 to classCount and
- // member indices from classCount to mIndices.length.
- int low = mClassCount;
- int high = mIndices.length - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + (name + ';' + desc) +
- " with entry at " + offset + ": " + dumpEntry(offset));
- }
-
- // Check class number: read short. The byte data is always big endian.
- int entryClass = (mData[offset++] & 0xFF) << 8 | (mData[offset++] & 0xFF);
- int compare = entryClass - classNumber;
- if (compare == 0) {
- if (desc != null) {
- // Method
- int nameLength = name.length();
- compare = compare(mData, offset, (byte) '(', name, nameLength);
- if (compare == 0) {
- offset += nameLength;
- int argsEnd = desc.indexOf(')');
- // Only compare up to the ) -- after that we have a return value in the
- // input description, which isn't there in the database
- compare = compare(mData, offset, (byte) ')', desc, argsEnd);
- if (compare == 0) {
- offset += argsEnd + 1;
-
- if (mData[offset++] == 0) {
- // Yes, terminated argument list: get the API level
- return UnsignedBytes.toInt(mData[offset]);
- }
- }
- }
- } else {
- // Field
- int nameLength = name.length();
- compare = compare(mData, offset, (byte) 0, name, nameLength);
- if (compare == 0) {
- offset += nameLength;
- if (mData[offset++] == 0) {
- // Yes, terminated argument list: get the API level
- return UnsignedBytes.toInt(mData[offset]);
- }
- }
- }
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return -1;
- }
- }
-
- return -1;
- }
-
- /** Clears out any existing lookup instances */
- @VisibleForTesting
- static void dispose() {
- sInstance.clear();
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiParser.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
deleted file mode 100644
index b3c2f2a..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiParser.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Parser for the simplified XML API format version 1.
- */
-public class ApiParser extends DefaultHandler {
-
- private static final String NODE_API = "api";
- private static final String NODE_CLASS = "class";
- private static final String NODE_FIELD = "field";
- private static final String NODE_METHOD = "method";
- private static final String NODE_EXTENDS = "extends";
- private static final String NODE_IMPLEMENTS = "implements";
-
- private static final String ATTR_NAME = "name";
- private static final String ATTR_SINCE = "since";
-
- private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>();
-
- private ApiClass mCurrentClass;
-
- ApiParser() {
- }
-
- Map<String, ApiClass> getClasses() {
- return mClasses;
- }
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
-
- if (localName == null || localName.isEmpty()) {
- localName = qName;
- }
-
- try {
- if (NODE_API.equals(localName)) {
- // do nothing.
-
- } else if (NODE_CLASS.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = Integer.parseInt(attributes.getValue(ATTR_SINCE));
-
- mCurrentClass = addClass(name, since);
-
- } else if (NODE_EXTENDS.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addSuperClass(name, since);
-
- } else if (NODE_IMPLEMENTS.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addInterface(name, since);
-
- } else if (NODE_METHOD.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addMethod(name, since);
-
- } else if (NODE_FIELD.equals(localName)) {
- String name = attributes.getValue(ATTR_NAME);
- int since = getSince(attributes);
-
- mCurrentClass.addField(name, since);
-
- }
-
- } finally {
- super.startElement(uri, localName, qName, attributes);
- }
- }
-
- private ApiClass addClass(String name, int apiLevel) {
- ApiClass theClass = mClasses.get(name);
- if (theClass == null) {
- theClass = new ApiClass(name, apiLevel);
- mClasses.put(name, theClass);
- }
-
- return theClass;
- }
-
- private int getSince(Attributes attributes) {
- int since = mCurrentClass.getSince();
- String sinceAttr = attributes.getValue(ATTR_SINCE);
-
- if (sinceAttr != null) {
- since = Integer.parseInt(sinceAttr);
- }
-
- return since;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
deleted file mode 100644
index 8bbcffb..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ArraySizeDetector.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.TAG_ARRAY;
-import static com.android.SdkConstants.TAG_INTEGER_ARRAY;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks for arrays with inconsistent item counts
- */
-public class ArraySizeDetector extends ResourceXmlDetector {
-
- /** Are there differences in how many array elements are declared? */
- public static final Issue INCONSISTENT = Issue.create(
- "InconsistentArrays", //$NON-NLS-1$
- "Checks for inconsistencies in the number of elements in arrays",
- "When an array is translated in a different locale, it should normally have " +
- "the same number of elements as the original array. When adding or removing " +
- "elements to an array, it is easy to forget to update all the locales, and this " +
- "lint warning finds inconsistencies like these.\n" +
- "\n" +
- "Note however that there may be cases where you really want to declare a " +
- "different number of array items in each configuration (for example where " +
- "the array represents available options, and those options differ for " +
- "different layout orientations and so on), so use your own judgement to " +
- "decide if this is really an error.\n" +
- "\n" +
- "You can suppress this error type if it finds false errors in your project.",
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- ArraySizeDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- private Multimap<File, Pair<String, Integer>> mFileToArrayCount;
-
- /** Locations for each array name. Populated during phase 2, if necessary */
- private Map<String, Location> mLocations;
-
- /** Error messages for each array name. Populated during phase 2, if necessary */
- private Map<String, String> mDescriptions;
-
- /** Constructs a new {@link ArraySizeDetector} */
- public ArraySizeDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_ARRAY,
- TAG_STRING_ARRAY,
- TAG_INTEGER_ARRAY
- );
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mFileToArrayCount = ArrayListMultimap.create(30, 5);
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- // Check that all arrays for the same name have the same number of translations
-
- Set<String> alreadyReported = new HashSet<String>();
- Map<String, Integer> countMap = new HashMap<String, Integer>();
- Map<String, File> fileMap = new HashMap<String, File>();
-
- // Process the file in sorted file order to ensure stable output
- List<File> keys = new ArrayList<File>(mFileToArrayCount.keySet());
- Collections.sort(keys);
-
- for (File file : keys) {
- Collection<Pair<String, Integer>> pairs = mFileToArrayCount.get(file);
- for (Pair<String, Integer> pair : pairs) {
- String name = pair.getFirst();
-
- if (alreadyReported.contains(name)) {
- continue;
- }
- Integer count = pair.getSecond();
-
- Integer current = countMap.get(name);
- if (current == null) {
- countMap.put(name, count);
- fileMap.put(name, file);
- } else if (!count.equals(current)) {
- alreadyReported.add(name);
-
- if (mLocations == null) {
- mLocations = new HashMap<String, Location>();
- mDescriptions = new HashMap<String, String>();
- }
- mLocations.put(name, null);
-
- String thisName = file.getParentFile().getName() + File.separator
- + file.getName();
- File otherFile = fileMap.get(name);
- String otherName = otherFile.getParentFile().getName() + File.separator
- + otherFile.getName();
- String message = String.format(
- "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)",
- name, count, thisName, current, otherName);
- mDescriptions.put(name, message);
- }
- }
- }
-
- if (mLocations != null) {
- // Request another scan through the resources such that we can
- // gather the actual locations
- context.getDriver().requestRepeat(this, Scope.ALL_RESOURCES_SCOPE);
- }
- mFileToArrayCount = null;
- } else {
- if (mLocations != null) {
- List<String> names = new ArrayList<String>(mLocations.keySet());
- Collections.sort(names);
- for (String name : names) {
- Location location = mLocations.get(name);
- // We were prepending locations, but we want to prefer the base folders
- location = Location.reverse(location);
-
- // Make sure we still have a conflict, in case one or more of the
- // elements were marked with tools:ignore
- int count = -1;
- LintDriver driver = context.getDriver();
- boolean foundConflict = false;
- Location curr;
- for (curr = location; curr != null; curr = curr.getSecondary()) {
- Object clientData = curr.getClientData();
- if (clientData instanceof Node) {
- Node node = (Node) clientData;
- if (driver.isSuppressed(INCONSISTENT, node)) {
- continue;
- }
- int newCount = LintUtils.getChildCount(node);
- if (newCount != count) {
- if (count == -1) {
- count = newCount; // first number encountered
- } else {
- foundConflict = true;
- break;
- }
- }
- } else {
- foundConflict = true;
- break;
- }
- }
-
- // Through one or more tools:ignore, there is no more conflict so
- // ignore this element
- if (!foundConflict) {
- continue;
- }
-
- String message = mDescriptions.get(name);
- context.report(INCONSISTENT, location, message, null);
- }
- }
-
- mLocations = null;
- mDescriptions = null;
- }
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- int phase = context.getPhase();
-
- Attr attribute = element.getAttributeNode(ATTR_NAME);
- if (attribute == null || attribute.getValue().isEmpty()) {
- if (phase != 1) {
- return;
- }
- context.report(INCONSISTENT, element, context.getLocation(element),
- String.format("Missing name attribute in %1$s declaration", element.getTagName()),
- null);
- } else {
- String name = attribute.getValue();
- if (phase == 1) {
- if (context.getProject().getReportIssues()) {
- int childCount = LintUtils.getChildCount(element);
- mFileToArrayCount.put(context.file, Pair.of(name, childCount));
- }
- } else {
- assert phase == 2;
- if (mLocations.containsKey(name)) {
- if (context.getDriver().isSuppressed(INCONSISTENT, element)) {
- return;
- }
- Location location = context.getLocation(element);
- location.setClientData(element);
- location.setMessage(String.format("Declaration with array size (%1$d)",
- LintUtils.getChildCount(element)));
- location.setSecondary(mLocations.get(name));
- mLocations.put(name, location);
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
deleted file mode 100644
index 1ebb701..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.detector.api.Issue;
-import com.google.common.annotations.Beta;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.jar.Attributes;
-import java.util.jar.JarFile;
-import java.util.jar.Manifest;
-
-/** Registry which provides a list of checks to be performed on an Android project */
-public class BuiltinIssueRegistry extends IssueRegistry {
- /** Folder name in the .android dir where additional detector jars are found */
- private static final String LINT_FOLDER = "lint"; //$NON-NLS-1$
-
- /**
- * Manifest constant for declaring an issue provider. Example:
- * Lint-Registry: foo.bar.CustomIssueRegistry
- */
- private static final String MF_LINT_REGISTRY = "Lint-Registry"; //$NON-NLS-1$
-
- private static final List<Issue> sIssues;
-
- static {
- final int initialCapacity = 141;
- List<Issue> issues = new ArrayList<Issue>(initialCapacity);
-
- issues.add(AccessibilityDetector.ISSUE);
- issues.add(LabelForDetector.ISSUE);
- issues.add(MathDetector.ISSUE);
- issues.add(FieldGetterDetector.ISSUE);
- issues.add(SdCardDetector.ISSUE);
- issues.add(ApiDetector.UNSUPPORTED);
- issues.add(ApiDetector.INLINED);
- issues.add(ApiDetector.OVERRIDE);
- issues.add(InvalidPackageDetector.ISSUE);
- issues.add(DuplicateIdDetector.CROSS_LAYOUT);
- issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
- issues.add(DuplicateResourceDetector.ISSUE);
- issues.add(WrongIdDetector.UNKNOWN_ID);
- issues.add(WrongIdDetector.UNKNOWN_ID_LAYOUT);
- issues.add(StateListDetector.ISSUE);
- issues.add(StyleCycleDetector.ISSUE);
- issues.add(InefficientWeightDetector.INEFFICIENT_WEIGHT);
- issues.add(InefficientWeightDetector.NESTED_WEIGHTS);
- issues.add(InefficientWeightDetector.BASELINE_WEIGHTS);
- issues.add(InefficientWeightDetector.WRONG_0DP);
- issues.add(InefficientWeightDetector.ORIENTATION);
- issues.add(ScrollViewChildDetector.ISSUE);
- issues.add(DeprecationDetector.ISSUE);
- issues.add(ObsoleteLayoutParamsDetector.ISSUE);
- issues.add(MergeRootFrameLayoutDetector.ISSUE);
- issues.add(NestedScrollingWidgetDetector.ISSUE);
- issues.add(ChildCountDetector.SCROLLVIEW_ISSUE);
- issues.add(ChildCountDetector.ADAPTERVIEW_ISSUE);
- issues.add(UseCompoundDrawableDetector.ISSUE);
- issues.add(UselessViewDetector.USELESS_PARENT);
- issues.add(UselessViewDetector.USELESS_LEAF);
- issues.add(TooManyViewsDetector.TOO_MANY);
- issues.add(TooManyViewsDetector.TOO_DEEP);
- issues.add(GridLayoutDetector.ISSUE);
- issues.add(OverrideDetector.ISSUE);
- issues.add(OnClickDetector.ISSUE);
- issues.add(ViewTagDetector.ISSUE);
- issues.add(LocaleDetector.STRING_LOCALE);
- issues.add(LocaleDetector.DATE_FORMAT);
- issues.add(RegistrationDetector.ISSUE);
- issues.add(MissingClassDetector.MISSING);
- issues.add(MissingClassDetector.INSTANTIATABLE);
- issues.add(MissingClassDetector.INNERCLASS);
- issues.add(MissingIdDetector.ISSUE);
- issues.add(WrongCaseDetector.WRONGCASE);
- issues.add(HandlerDetector.ISSUE);
- issues.add(FragmentDetector.ISSUE);
- issues.add(TranslationDetector.EXTRA);
- issues.add(TranslationDetector.MISSING);
- issues.add(HardcodedValuesDetector.ISSUE);
- issues.add(Utf8Detector.ISSUE);
- issues.add(DosLineEndingDetector.ISSUE);
- issues.add(CommentDetector.EASTEREGG);
- issues.add(CommentDetector.STOPSHIP);
- issues.add(ProguardDetector.WRONGKEEP);
- issues.add(ProguardDetector.SPLITCONFIG);
- issues.add(PxUsageDetector.PX_ISSUE);
- issues.add(PxUsageDetector.DP_ISSUE);
- issues.add(PxUsageDetector.IN_MM_ISSUE);
- issues.add(PxUsageDetector.SMALL_SP_ISSUE);
- issues.add(TextFieldDetector.ISSUE);
- issues.add(TextViewDetector.ISSUE);
- issues.add(TextViewDetector.SELECTABLE);
- issues.add(UnusedResourceDetector.ISSUE);
- issues.add(UnusedResourceDetector.ISSUE_IDS);
- issues.add(ExtraTextDetector.ISSUE);
- issues.add(PrivateResourceDetector.ISSUE);
- issues.add(ArraySizeDetector.INCONSISTENT);
- issues.add(HardcodedDebugModeDetector.ISSUE);
- issues.add(ManifestOrderDetector.ORDER);
- issues.add(ManifestOrderDetector.USES_SDK);
- issues.add(ManifestOrderDetector.MULTIPLE_USES_SDK);
- issues.add(ManifestOrderDetector.WRONG_PARENT);
- issues.add(ManifestOrderDetector.DUPLICATE_ACTIVITY);
- issues.add(ManifestOrderDetector.TARGET_NEWER);
- issues.add(ManifestOrderDetector.ALLOW_BACKUP);
- issues.add(ManifestOrderDetector.UNIQUE_PERMISSION);
- issues.add(ManifestOrderDetector.SET_VERSION);
- issues.add(ManifestOrderDetector.ILLEGAL_REFERENCE);
- issues.add(ManifestTypoDetector.ISSUE);
- issues.add(SecurityDetector.EXPORTED_PROVIDER);
- issues.add(SecurityDetector.EXPORTED_SERVICE);
- issues.add(SecurityDetector.EXPORTED_RECEIVER);
- issues.add(SecurityDetector.OPEN_PROVIDER);
- issues.add(SecurityDetector.WORLD_READABLE);
- issues.add(SecurityDetector.WORLD_WRITEABLE);
- issues.add(SecureRandomDetector.ISSUE);
- issues.add(IconDetector.GIF_USAGE);
- issues.add(IconDetector.ICON_DENSITIES);
- issues.add(IconDetector.ICON_MISSING_FOLDER);
- issues.add(IconDetector.ICON_DIP_SIZE);
- issues.add(IconDetector.ICON_EXPECTED_SIZE);
- issues.add(IconDetector.ICON_LOCATION);
- issues.add(IconDetector.DUPLICATES_NAMES);
- issues.add(IconDetector.DUPLICATES_CONFIGURATIONS);
- issues.add(IconDetector.ICON_NODPI);
- issues.add(IconDetector.ICON_EXTENSION);
- issues.add(IconDetector.ICON_COLORS);
- issues.add(IconDetector.ICON_XML_AND_PNG);
- issues.add(IconDetector.ICON_LAUNCHER_SHAPE);
- issues.add(TypographyDetector.DASHES);
- issues.add(TypographyDetector.QUOTES);
- issues.add(TypographyDetector.FRACTIONS);
- issues.add(TypographyDetector.ELLIPSIS);
- issues.add(TypographyDetector.OTHER);
- issues.add(ButtonDetector.ORDER);
- issues.add(ButtonDetector.CASE);
- issues.add(ButtonDetector.BACKBUTTON);
- issues.add(ButtonDetector.STYLE);
- issues.add(DetectMissingPrefix.MISSING_NAMESPACE);
- issues.add(OverdrawDetector.ISSUE);
- issues.add(StringFormatDetector.INVALID);
- issues.add(StringFormatDetector.ARG_COUNT);
- issues.add(StringFormatDetector.ARG_TYPES);
- issues.add(TypoDetector.ISSUE);
- issues.add(ViewTypeDetector.ISSUE);
- issues.add(WrongImportDetector.ISSUE);
- issues.add(WrongLocationDetector.ISSUE);
- issues.add(ViewConstructorDetector.ISSUE);
- issues.add(NamespaceDetector.CUSTOMVIEW);
- issues.add(NamespaceDetector.UNUSED);
- issues.add(NamespaceDetector.TYPO);
- issues.add(AlwaysShowActionDetector.ISSUE);
- issues.add(TitleDetector.ISSUE);
- issues.add(ColorUsageDetector.ISSUE);
- issues.add(JavaPerformanceDetector.PAINT_ALLOC);
- issues.add(JavaPerformanceDetector.USE_VALUEOF);
- issues.add(JavaPerformanceDetector.USE_SPARSEARRAY);
- issues.add(WakelockDetector.ISSUE);
- issues.add(CleanupDetector.RECYCLE_RESOURCE);
- issues.add(CleanupDetector.COMMIT_FRAGMENT);
- issues.add(SetJavaScriptEnabledDetector.ISSUE);
- issues.add(ToastDetector.ISSUE);
- issues.add(SharedPrefsDetector.ISSUE);
- issues.add(CutPasteDetector.ISSUE);
- issues.add(NonInternationalizedSmsDetector.ISSUE);
- issues.add(PrivateKeyDetector.ISSUE);
- issues.add(AnnotationDetector.ISSUE);
- issues.add(SystemPermissionsDetector.ISSUE);
- issues.add(RequiredAttributeDetector.ISSUE);
- issues.add(WrongCallDetector.ISSUE);
-
- assert initialCapacity >= issues.size() : issues.size();
-
- addCustomIssues(issues);
-
- sIssues = Collections.unmodifiableList(issues);
-
- // Check that ids are unique
- if (assertionsEnabled()) {
- Set<String> ids = new HashSet<String>();
- for (Issue issue : sIssues) {
- String id = issue.getId();
- assert !ids.contains(id) : "Duplicate id " + id; //$NON-NLS-1$
- ids.add(id);
- }
- }
- }
-
- /**
- * Constructs a new {@link BuiltinIssueRegistry}
- */
- public BuiltinIssueRegistry() {
- }
-
- @NonNull
- @Override
- public List<Issue> getIssues() {
- return sIssues;
- }
-
- /**
- * Add in custom issues registered by the user - via an environment variable
- * or in the .android/lint directory.
- */
- private static void addCustomIssues(List<Issue> issues) {
- // Look for additional detectors registered by the user, via
- // (1) an environment variable (useful for build servers etc), and
- // (2) via jar files in the .android/lint directory
- Set<File> files = null;
- try {
- File lint = new File(AndroidLocation.getFolder() + File.separator + LINT_FOLDER);
- if (lint.exists()) {
- File[] list = lint.listFiles();
- if (list != null) {
- for (File jarFile : list) {
- if (endsWith(jarFile.getName(), ".jar")) { //$NON-NLS-1$
- if (files == null) {
- files = new HashSet<File>();
- }
- files.add(jarFile);
- addIssuesFromJar(jarFile, issues);
- }
- }
- }
- }
- } catch (AndroidLocationException e) {
- // Ignore -- no android dir, so no rules to load.
- }
-
- String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$
- if (lintClassPath != null && !lintClassPath.isEmpty()) {
- String[] paths = lintClassPath.split(File.pathSeparator);
- for (String path : paths) {
- File jarFile = new File(path);
- if (jarFile.exists() && (files == null || !files.contains(jarFile))) {
- addIssuesFromJar(jarFile, issues);
- }
- }
- }
- }
-
- /** Add the issues found in the given jar file into the given list of issues */
- private static void addIssuesFromJar(File jarFile, List<Issue> issues) {
- JarFile jarfile = null;
- try {
- jarfile = new JarFile(jarFile);
- Manifest manifest = jarfile.getManifest();
- Attributes attrs = manifest.getMainAttributes();
- Object object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY));
- if (object instanceof String) {
- String className = (String) object;
-
- // Make a class loader for this jar
- try {
- URL url = jarFile.toURI().toURL();
- URLClassLoader loader = new URLClassLoader(new URL[] { url },
- BuiltinIssueRegistry.class.getClassLoader());
- try {
- Class<?> registryClass = Class.forName(className, true, loader);
- IssueRegistry registry = (IssueRegistry) registryClass.newInstance();
- for (Issue issue : registry.getIssues()) {
- issues.add(issue);
- }
- } catch (Throwable e) {
- log(e);
- }
- } catch (MalformedURLException e) {
- log(e);
- }
- }
- } catch (IOException e) {
- log(e);
- } finally {
- if (jarfile != null) {
- try {
- jarfile.close();
- } catch (IOException e) {
- // Nothing to be done
- }
- }
- }
- }
-
- private static void log(Throwable e) {
- // TODO: Where do we log this? There's no embedding tool context here. For now,
- // just dump to the console so detector developers get some feedback on what went
- // wrong.
- e.printStackTrace();
- }
-
- private static Set<Issue> sAdtFixes;
-
- /**
- * Returns true if the given issue has an automatic IDE fix.
- *
- * @param tool the name of the tool to be checked
- * @param issue the issue to be checked
- * @return true if the given tool is known to have an automatic fix for the
- * given issue
- */
- @Beta
- public boolean hasAutoFix(String tool, Issue issue) {
- assert tool.equals("adt"); // This is not yet a generic facility;
- // the primary purpose right now is to allow for example the HTML report
- // to give a hint to the user that some fixes don't require manual work
-
- return getIssuesWithFixes().contains(issue);
- }
-
- private static Set<Issue> getIssuesWithFixes() {
- if (sAdtFixes == null) {
- sAdtFixes = new HashSet<Issue>(25);
- sAdtFixes.add(InefficientWeightDetector.INEFFICIENT_WEIGHT);
- sAdtFixes.add(AccessibilityDetector.ISSUE);
- sAdtFixes.add(InefficientWeightDetector.BASELINE_WEIGHTS);
- sAdtFixes.add(HardcodedValuesDetector.ISSUE);
- sAdtFixes.add(UselessViewDetector.USELESS_LEAF);
- sAdtFixes.add(UselessViewDetector.USELESS_PARENT);
- sAdtFixes.add(PxUsageDetector.PX_ISSUE);
- sAdtFixes.add(TextFieldDetector.ISSUE);
- sAdtFixes.add(SecurityDetector.EXPORTED_SERVICE);
- sAdtFixes.add(DetectMissingPrefix.MISSING_NAMESPACE);
- sAdtFixes.add(ScrollViewChildDetector.ISSUE);
- sAdtFixes.add(ObsoleteLayoutParamsDetector.ISSUE);
- sAdtFixes.add(TypographyDetector.DASHES);
- sAdtFixes.add(TypographyDetector.ELLIPSIS);
- sAdtFixes.add(TypographyDetector.FRACTIONS);
- sAdtFixes.add(TypographyDetector.OTHER);
- sAdtFixes.add(TypographyDetector.QUOTES);
- sAdtFixes.add(UseCompoundDrawableDetector.ISSUE);
- sAdtFixes.add(ApiDetector.UNSUPPORTED);
- sAdtFixes.add(TypoDetector.ISSUE);
- sAdtFixes.add(ManifestOrderDetector.ALLOW_BACKUP);
- sAdtFixes.add(MissingIdDetector.ISSUE);
- sAdtFixes.add(TranslationDetector.MISSING);
- sAdtFixes.add(DosLineEndingDetector.ISSUE);
- }
-
- return sAdtFixes;
- }
-
- /**
- * Reset the registry such that it recomputes its available issues.
- * <p>
- * NOTE: This is only intended for testing purposes.
- */
- @VisibleForTesting
- public static void reset() {
- IssueRegistry.reset();
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
deleted file mode 100644
index e756e03..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
+++ /dev/null
@@ -1,782 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BACKGROUND;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_ORIENTATION;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.ATTR_TEXT;
-import static com.android.SdkConstants.BUTTON;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.STRING_PREFIX;
-import static com.android.SdkConstants.TABLE_ROW;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.VALUE_SELECTABLE_ITEM_BACKGROUND;
-import static com.android.SdkConstants.VALUE_TRUE;
-import static com.android.SdkConstants.VALUE_VERTICAL;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Check which looks at the order of buttons in dialogs and makes sure that
- * "the dismissive action of a dialog is always on the left whereas the affirmative actions
- * are on the right."
- * <p>
- * This only looks for the affirmative and dismissive actions named "OK" and "Cancel";
- * "Cancel" usually works, but the affirmative action often has many other names -- "Done",
- * "Send", "Go", etc.
- * <p>
- * TODO: Perhaps we should look for Yes/No dialogs and suggested they be rephrased as
- * Cancel/OK dialogs? Similarly, consider "Abort" a synonym for "Cancel" ?
- */
-public class ButtonDetector extends ResourceXmlDetector {
- /** Name of cancel value ("Cancel") */
- private static final String CANCEL_LABEL = "Cancel";
- /** Name of OK value ("Cancel") */
- private static final String OK_LABEL = "OK";
- /** Name of Back value ("Back") */
- private static final String BACK_LABEL = "Back";
-
- /** Layout text attribute reference to {@code @android:string/ok} */
- private static final String ANDROID_OK_RESOURCE =
- ANDROID_STRING_PREFIX + "ok"; //$NON-NLS-1$
- /** Layout text attribute reference to {@code @android:string/cancel} */
- private static final String ANDROID_CANCEL_RESOURCE =
- ANDROID_STRING_PREFIX + "cancel"; //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue ORDER = Issue.create(
- "ButtonOrder", //$NON-NLS-1$
- "Ensures the dismissive action of a dialog is on the left and affirmative on " +
- "the right",
-
- "According to the Android Design Guide,\n" +
- "\n" +
- "\"Action buttons are typically Cancel and/or OK, with OK indicating the preferred " +
- "or most likely action. However, if the options consist of specific actions such " +
- "as Close or Wait rather than a confirmation or cancellation of the action " +
- "described in the content, then all the buttons should be active verbs. As a rule, " +
- "the dismissive action of a dialog is always on the left whereas the affirmative " +
- "actions are on the right.\"\n" +
- "\n" +
- "This check looks for button bars and buttons which look like cancel buttons, " +
- "and makes sure that these are on the left.",
-
- Category.USABILITY,
- 8,
- Severity.WARNING,
- ButtonDetector.class,
- Scope.RESOURCE_FILE_SCOPE)
- .setMoreInfo(
- "http://developer.android.com/design/building-blocks/dialogs.html"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue STYLE = Issue.create(
- "ButtonStyle", //$NON-NLS-1$
- "Ensures that buttons in button bars are borderless",
-
- "Button bars typically use a borderless style for the buttons. Set the " +
- "`style=\"?android:attr/buttonBarButtonStyle\"` attribute " +
- "on each of the buttons, and set `style=\"?android:attr/buttonBarStyle\"` on " +
- "the parent layout",
-
- Category.USABILITY,
- 5,
- Severity.WARNING,
- ButtonDetector.class,
- Scope.RESOURCE_FILE_SCOPE)
- .setMoreInfo(
- "http://developer.android.com/design/building-blocks/buttons.html"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue BACKBUTTON = Issue.create(
- "BackButton", //$NON-NLS-1$
- "Looks for Back buttons, which are not common on the Android platform.",
- // TODO: Look for ">" as label suffixes as well
-
- "According to the Android Design Guide,\n" +
- "\n" +
- "\"Other platforms use an explicit back button with label to allow the user " +
- "to navigate up the application's hierarchy. Instead, Android uses the main " +
- "action bar's app icon for hierarchical navigation and the navigation bar's " +
- "back button for temporal navigation.\"" +
- "\n" +
- "This check is not very sophisticated (it just looks for buttons with the " +
- "label \"Back\"), so it is disabled by default to not trigger on common " +
- "scenarios like pairs of Back/Next buttons to paginate through screens.",
-
- Category.USABILITY,
- 6,
- Severity.WARNING,
- ButtonDetector.class,
- Scope.RESOURCE_FILE_SCOPE)
- .setEnabledByDefault(false)
- .setMoreInfo(
- "http://developer.android.com/design/patterns/pure-android.html"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue CASE = Issue.create(
- "ButtonCase", //$NON-NLS-1$
- "Ensures that Cancel/OK dialog buttons use the canonical capitalization",
-
- "The standard capitalization for OK/Cancel dialogs is \"OK\" and \"Cancel\". " +
- "To ensure that your dialogs use the standard strings, you can use " +
- "the resource strings @android:string/ok and @android:string/cancel.",
-
- Category.USABILITY,
- 2,
- Severity.WARNING,
- ButtonDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Set of resource names whose value was either OK or Cancel */
- private Set<String> mApplicableResources;
-
- /**
- * Map of resource names we'd like resolved into strings in phase 2. The
- * values should be filled in with the actual string contents.
- */
- private Map<String, String> mKeyToLabel;
-
- /**
- * Set of elements we've already warned about. If we've already complained
- * about a cancel button, don't also report the OK button (since it's listed
- * for the warnings on OK buttons).
- */
- private Set<Element> mIgnore;
-
- /** Constructs a new {@link ButtonDetector} */
- public ButtonDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(BUTTON, TAG_STRING);
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- int phase = context.getPhase();
- if (phase == 1 && mApplicableResources != null) {
- // We found resources for the string "Cancel"; perform a second pass
- // where we check layout text attributes against these strings.
- context.getDriver().requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
- }
- }
-
- private static String stripLabel(String text) {
- text = text.trim();
- if (text.length() > 2
- && (text.charAt(0) == '"' || text.charAt(0) == '\'')
- && (text.charAt(0) == text.charAt(text.length() - 1))) {
- text = text.substring(1, text.length() - 1);
- }
-
- return text;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- // This detector works in two passes.
- // In pass 1, it looks in layout files for hardcoded strings of "Cancel", or
- // references to @string/cancel or @android:string/cancel.
- // It also looks in values/ files for strings whose value is "Cancel",
- // and if found, stores the corresponding keys in a map. (This is necessary
- // since value files are processed after layout files).
- // Then, if at the end of phase 1 any "Cancel" string resources were
- // found in the value files, then it requests a *second* phase,
- // where it looks only for <Button>'s whose text matches one of the
- // cancel string resources.
- int phase = context.getPhase();
- String tagName = element.getTagName();
- if (phase == 1 && tagName.equals(TAG_STRING)) {
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = child.getNodeValue();
- for (int j = 0, len = text.length(); j < len; j++) {
- char c = text.charAt(j);
- if (!Character.isWhitespace(c)) {
- if (c == '"' || c == '\'') {
- continue;
- }
- if (LintUtils.startsWith(text, CANCEL_LABEL, j)) {
- String label = stripLabel(text);
- if (label.equalsIgnoreCase(CANCEL_LABEL)) {
- String name = element.getAttribute(ATTR_NAME);
- foundResource(context, name, element);
-
- if (!label.equals(CANCEL_LABEL)
- && isEnglishResource(context)
- && context.isEnabled(CASE)) {
- assert label.equalsIgnoreCase(CANCEL_LABEL);
- context.report(CASE, child, context.getLocation(child),
- String.format(
- "The standard Android way to capitalize %1$s " +
- "is \"Cancel\" (tip: use @android:string/ok instead)",
- label), null);
- }
- }
- } else if (LintUtils.startsWith(text, OK_LABEL, j)) {
- String label = stripLabel(text);
- if (label.equalsIgnoreCase(OK_LABEL)) {
- String name = element.getAttribute(ATTR_NAME);
- foundResource(context, name, element);
-
- if (!label.equals(OK_LABEL)
- && isEnglishResource(context)
- && context.isEnabled(CASE)) {
- assert text.trim().equalsIgnoreCase(OK_LABEL);
- context.report(CASE, child, context.getLocation(child),
- String.format(
- "The standard Android way to capitalize %1$s " +
- "is \"OK\" (tip: use @android:string/ok instead)",
- label), null);
- }
- }
- } else if (LintUtils.startsWith(text, BACK_LABEL, j) &&
- stripLabel(text).equalsIgnoreCase(BACK_LABEL)) {
- String name = element.getAttribute(ATTR_NAME);
- foundResource(context, name, element);
- }
- break;
- }
- }
- }
- }
- } else if (tagName.equals(BUTTON)) {
- if (phase == 1) {
- if (isInButtonBar(element)
- && !element.hasAttribute(ATTR_STYLE)
- && !VALUE_SELECTABLE_ITEM_BACKGROUND.equals(
- element.getAttributeNS(ANDROID_URI, ATTR_BACKGROUND))
- && (context.getProject().getMinSdk() >= 11
- || context.getFolderVersion() >= 11)
- && context.isEnabled(STYLE)
- && !parentDefinesSelectableItem(element)) {
- context.report(STYLE, element, context.getLocation(element),
- "Buttons in button bars should be borderless; use " +
- "style=\"?android:attr/buttonBarButtonStyle\" (and " +
- "?android:attr/buttonBarStyle on the parent)",
- null);
- }
- }
-
- String text = element.getAttributeNS(ANDROID_URI, ATTR_TEXT);
- if (phase == 2) {
- if (mApplicableResources.contains(text)) {
- String key = text;
- if (key.startsWith(STRING_PREFIX)) {
- key = key.substring(STRING_PREFIX.length());
- }
- String label = mKeyToLabel.get(key);
- boolean isCancel = CANCEL_LABEL.equalsIgnoreCase(label);
- if (isCancel) {
- if (isWrongCancelPosition(element)) {
- reportCancelPosition(context, element);
- }
- } else if (OK_LABEL.equalsIgnoreCase(label)) {
- if (isWrongOkPosition(element)) {
- reportOkPosition(context, element);
- }
- } else {
- assert BACK_LABEL.equalsIgnoreCase(label) : label + ':' + context.file;
- Location location = context.getLocation(element);
- if (context.isEnabled(BACKBUTTON)) {
- context.report(BACKBUTTON, element, location,
- "Back buttons are not standard on Android; see design guide's " +
- "navigation section", null);
- }
- }
- }
- } else if (text.equals(CANCEL_LABEL) || text.equals(ANDROID_CANCEL_RESOURCE)) {
- if (isWrongCancelPosition(element)) {
- reportCancelPosition(context, element);
- }
- } else if (text.equals(OK_LABEL) || text.equals(ANDROID_OK_RESOURCE)) {
- if (isWrongOkPosition(element)) {
- reportOkPosition(context, element);
- }
- }
- }
- }
-
- private static boolean parentDefinesSelectableItem(Element element) {
- String background = element.getAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
- if (VALUE_SELECTABLE_ITEM_BACKGROUND.equals(background)) {
- return true;
- }
-
- Node parent = element.getParentNode();
- if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) {
- return parentDefinesSelectableItem((Element) parent);
- }
-
- return false;
- }
-
- /** Report the given OK button as being in the wrong position */
- private void reportOkPosition(XmlContext context, Element element) {
- report(context, element, false /*isCancel*/);
- }
-
- /** Report the given Cancel button as being in the wrong position */
- private void reportCancelPosition(XmlContext context, Element element) {
- report(context, element, true /*isCancel*/);
- }
-
-
- /** The Ok/Cancel detector only works with default and English locales currently.
- * TODO: Add in patterns for other languages. We can use the
- * @android:string/ok and @android:string/cancel localizations to look
- * up the canonical ones. */
- private static boolean isEnglishResource(XmlContext context) {
- String folder = context.file.getParentFile().getName();
- if (folder.indexOf('-') != -1) {
- String[] qualifiers = folder.split("-"); //$NON-NLS-1$
- for (String qualifier : qualifiers) {
- if (qualifier.equals("en")) { //$NON-NLS-1$
- return true;
- }
- }
- return false;
- }
-
- // Default folder ("values") - may not be English but we'll consider matches
- // on "OK", "Cancel" and "Back" as matches there
- return true;
- }
-
- /**
- * We've found a resource reference to some label we're interested in ("OK",
- * "Cancel", "Back", ...). Record the corresponding name such that in the
- * next pass through the layouts we can check the context (for OK/Cancel the
- * button order etc).
- */
- private void foundResource(XmlContext context, String name, Element element) {
- if (!isEnglishResource(context)) {
- return;
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- if (mApplicableResources == null) {
- mApplicableResources = new HashSet<String>();
- }
-
- mApplicableResources.add(STRING_PREFIX + name);
-
- // ALSO record all the other string resources in this file to pick up other
- // labels. If you define "OK" in one resource file and "Cancel" in another
- // this won't work, but that's probably not common and has lower overhead.
- Node parentNode = element.getParentNode();
-
- List<Element> items = LintUtils.getChildren(parentNode);
- if (mKeyToLabel == null) {
- mKeyToLabel = new HashMap<String, String>(items.size());
- }
- for (Element item : items) {
- String itemName = item.getAttribute(ATTR_NAME);
- NodeList childNodes = item.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = stripLabel(child.getNodeValue());
- if (!text.isEmpty()) {
- mKeyToLabel.put(itemName, text);
- break;
- }
- }
- }
- }
- }
-
- /** Report the given OK/Cancel button as being in the wrong position */
- private void report(XmlContext context, Element element, boolean isCancel) {
- if (!context.isEnabled(ORDER)) {
- return;
- }
-
- if (mIgnore != null && mIgnore.contains(element)) {
- return;
- }
-
- int target = context.getProject().getTargetSdk();
- if (target < 14) {
- // If you're only targeting pre-ICS UI's, this is not an issue
- return;
- }
-
- boolean mustCreateIcsLayout = false;
- if (context.getProject().getMinSdk() < 14) {
- // If you're *also* targeting pre-ICS UIs, then this reverse button
- // order is correct for layouts intended for pre-ICS and incorrect for
- // ICS layouts.
- //
- // Therefore, we need to know if this layout is an ICS layout or
- // a pre-ICS layout.
- boolean isIcsLayout = context.getFolderVersion() >= 14;
- if (!isIcsLayout) {
- // This layout is not an ICS layout. However, there *must* also be
- // an ICS layout here, or this button order will be wrong:
- File res = context.file.getParentFile().getParentFile();
- File[] resFolders = res.listFiles();
- String fileName = context.file.getName();
- if (resFolders != null) {
- for (File folder : resFolders) {
- String folderName = folder.getName();
- if (folderName.startsWith(SdkConstants.FD_RES_LAYOUT)
- && folderName.contains("-v14")) { //$NON-NLS-1$
- File layout = new File(folder, fileName);
- if (layout.exists()) {
- // Yes, a v14 specific layout is available so this pre-ICS
- // layout order is not a problem
- return;
- }
- }
- }
- }
- mustCreateIcsLayout = true;
- }
- }
-
- List<Element> buttons = LintUtils.getChildren(element.getParentNode());
-
- if (mIgnore == null) {
- mIgnore = new HashSet<Element>();
- }
- for (Element button : buttons) {
- // Mark all the siblings in the ignore list to ensure that we don't
- // report *both* the Cancel and the OK button in "OK | Cancel"
- mIgnore.add(button);
- }
-
- String message;
- if (isCancel) {
- message = "Cancel button should be on the left";
- } else {
- message = "OK button should be on the right";
- }
-
- if (mustCreateIcsLayout) {
- message = String.format(
- "Layout uses the wrong button order for API >= 14: Create a " +
- "layout-v14/%1$s file with opposite order: %2$s",
- context.file.getName(), message);
- }
-
- // Show existing button order? We can only do that for LinearLayouts
- // since in for example a RelativeLayout the order of the elements may
- // not be the same as the visual order
- String layout = element.getParentNode().getNodeName();
- if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
- List<String> labelList = getLabelList(buttons);
- String wrong = describeButtons(labelList);
- sortButtons(labelList);
- String right = describeButtons(labelList);
- message += String.format(" (was \"%1$s\", should be \"%2$s\")", wrong, right);
- }
-
- Location location = context.getLocation(element);
- context.report(ORDER, element, location, message, null);
- }
-
- /**
- * Sort a list of label buttons into the expected order (Cancel on the left,
- * OK on the right
- */
- private static void sortButtons(List<String> labelList) {
- for (int i = 0, n = labelList.size(); i < n; i++) {
- String label = labelList.get(i);
- if (label.equalsIgnoreCase(CANCEL_LABEL) && i > 0) {
- swap(labelList, 0, i);
- } else if (label.equalsIgnoreCase(OK_LABEL) && i < n - 1) {
- swap(labelList, n - 1, i);
- }
- }
- }
-
- /** Swaps the strings at positions i and j */
- private static void swap(List<String> strings, int i, int j) {
- if (i != j) {
- String temp = strings.get(i);
- strings.set(i, strings.get(j));
- strings.set(j, temp);
- }
- }
-
- /** Creates a display string for a list of button labels, such as "Cancel | OK" */
- private static String describeButtons(List<String> labelList) {
- StringBuilder sb = new StringBuilder(80);
- for (String label : labelList) {
- if (sb.length() > 0) {
- sb.append(" | "); //$NON-NLS-1$
- }
- sb.append(label);
- }
-
- return sb.toString();
- }
-
- /** Returns the ordered list of button labels */
- private List<String> getLabelList(List<Element> views) {
- List<String> labels = new ArrayList<String>();
-
- if (mIgnore == null) {
- mIgnore = new HashSet<Element>();
- }
-
- for (Element view : views) {
- if (view.getTagName().equals(BUTTON)) {
- String text = view.getAttributeNS(ANDROID_URI, ATTR_TEXT);
- String label = getLabel(text);
- labels.add(label);
-
- // Mark all the siblings in the ignore list to ensure that we don't
- // report *both* the Cancel and the OK button in "OK | Cancel"
- mIgnore.add(view);
- }
- }
-
- return labels;
- }
-
- private String getLabel(String key) {
- String label = null;
- if (key.startsWith(ANDROID_STRING_PREFIX)) {
- if (key.equals(ANDROID_OK_RESOURCE)) {
- label = OK_LABEL;
- } else if (key.equals(ANDROID_CANCEL_RESOURCE)) {
- label = CANCEL_LABEL;
- }
- } else if (mKeyToLabel != null) {
- if (key.startsWith(STRING_PREFIX)) {
- label = mKeyToLabel.get(key.substring(STRING_PREFIX.length()));
- }
- }
-
- if (label == null) {
- label = key;
- }
-
- if (label.indexOf(' ') != -1 && label.indexOf('"') == -1) {
- label = '"' + label + '"';
- }
-
- return label;
- }
-
- /** Is the cancel button in the wrong position? It has to be on the left. */
- private boolean isWrongCancelPosition(Element element) {
- return isWrongPosition(element, true /*isCancel*/);
- }
-
- /** Is the OK button in the wrong position? It has to be on the right. */
- private boolean isWrongOkPosition(Element element) {
- return isWrongPosition(element, false /*isCancel*/);
- }
-
- private static boolean isInButtonBar(Element element) {
- assert element.getTagName().equals(BUTTON) : element.getTagName();
- Node parentNode = element.getParentNode();
- if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
- return false;
- }
- Element parent = (Element) parentNode;
-
- String style = parent.getAttribute(ATTR_STYLE);
- if (style != null && style.contains("buttonBarStyle")) { //$NON-NLS-1$
- return true;
- }
-
- // Don't warn about single Cancel / OK buttons
- if (LintUtils.getChildCount(parent) < 2) {
- return false;
- }
-
- String layout = parent.getTagName();
- if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
- String orientation = parent.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
- if (VALUE_VERTICAL.equals(orientation)) {
- return false;
- }
- } else {
- return false;
- }
-
- // Ensure that all the children are buttons
- Node n = parent.getFirstChild();
- while (n != null) {
- if (n.getNodeType() == Node.ELEMENT_NODE) {
- if (!BUTTON.equals(n.getNodeName())) {
- return false;
- }
- }
- n = n.getNextSibling();
- }
-
- return true;
- }
-
- /** Is the given button in the wrong position? */
- private static boolean isWrongPosition(Element element, boolean isCancel) {
- Node parentNode = element.getParentNode();
- if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
- return false;
- }
- Element parent = (Element) parentNode;
-
- // Don't warn about single Cancel / OK buttons
- if (LintUtils.getChildCount(parent) < 2) {
- return false;
- }
-
- String layout = parent.getTagName();
- if (layout.equals(LINEAR_LAYOUT) || layout.equals(TABLE_ROW)) {
- String orientation = parent.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
- if (VALUE_VERTICAL.equals(orientation)) {
- return false;
- }
-
- if (isCancel) {
- Node n = element.getPreviousSibling();
- while (n != null) {
- if (n.getNodeType() == Node.ELEMENT_NODE) {
- return true;
- }
- n = n.getPreviousSibling();
- }
- } else {
- Node n = element.getNextSibling();
- while (n != null) {
- if (n.getNodeType() == Node.ELEMENT_NODE) {
- return true;
- }
- n = n.getNextSibling();
- }
- }
-
- return false;
- } else if (layout.equals(RELATIVE_LAYOUT)) {
- // In RelativeLayouts, look for attachments which look like a clear sign
- // that the OK or Cancel buttons are out of order:
- // -- a left attachment on a Cancel button (where the left attachment
- // is a button; we don't want to complain if it's pointing to a spacer
- // or image or progress indicator etc)
- // -- a right-side parent attachment on a Cancel button (unless it's also
- // attached on the left, e.g. a cancel button stretching across the
- // layout)
- // etc.
- if (isCancel) {
- if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_TO_RIGHT_OF)
- && isButtonId(parent, element.getAttributeNS(ANDROID_URI,
- ATTR_LAYOUT_TO_RIGHT_OF))) {
- return true;
- }
- if (isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_RIGHT) &&
- !isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_LEFT)) {
- return true;
- }
- } else {
- if (element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_TO_LEFT_OF)
- && isButtonId(parent, element.getAttributeNS(ANDROID_URI,
- ATTR_LAYOUT_TO_RIGHT_OF))) {
- return true;
- }
- if (isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_LEFT) &&
- !isTrue(element, ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) {
- return true;
- }
- }
-
- return false;
- } else {
- // TODO: Consider other button layouts - GridLayouts, custom views extending
- // LinearLayout etc?
- return false;
- }
- }
-
- /**
- * Returns true if the given attribute (in the Android namespace) is set to
- * true on the given element
- */
- private static boolean isTrue(Element element, String attribute) {
- return VALUE_TRUE.equals(element.getAttributeNS(ANDROID_URI, attribute));
- }
-
- /** Is the given target id the id of a {@code <Button>} within this RelativeLayout? */
- private static boolean isButtonId(Element parent, String targetId) {
- for (Element child : LintUtils.getChildren(parent)) {
- String id = child.getAttributeNS(ANDROID_URI, ATTR_ID);
- if (LintUtils.idReferencesMatch(id, targetId)) {
- return child.getTagName().equals(BUTTON);
- }
- }
- return false;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java
deleted file mode 100644
index bc1e30d..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ChildCountDetector.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.GRID_VIEW;
-import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
-import static com.android.SdkConstants.LIST_VIEW;
-import static com.android.SdkConstants.REQUEST_FOCUS;
-import static com.android.SdkConstants.SCROLL_VIEW;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Check which makes sure that views have the expected number of declared
- * children (e.g. at most one in ScrollViews and none in AdapterViews)
- */
-public class ChildCountDetector extends LayoutDetector {
-
- /** The main issue discovered by this detector */
- public static final Issue SCROLLVIEW_ISSUE = Issue.create(
- "ScrollViewCount", //$NON-NLS-1$
- "Checks that ScrollViews have exactly one child widget",
- "ScrollViews can only have one child widget. If you want more children, wrap them " +
- "in a container layout.",
- Category.CORRECTNESS,
- 8,
- Severity.WARNING,
- ChildCountDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** The main issue discovered by this detector */
- public static final Issue ADAPTERVIEW_ISSUE = Issue.create(
- "AdapterViewChildren", //$NON-NLS-1$
- "Checks that AdapterViews do not define their children in XML",
- "AdapterViews such as ListViews must be configured with data from Java code, " +
- "such as a ListAdapter.",
- Category.CORRECTNESS,
- 10,
- Severity.WARNING,
- ChildCountDetector.class,
- Scope.RESOURCE_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/reference/android/widget/AdapterView.html"); //$NON-NLS-1$
-
- /** Constructs a new {@link ChildCountDetector} */
- public ChildCountDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- SCROLL_VIEW,
- HORIZONTAL_SCROLL_VIEW,
- LIST_VIEW,
- GRID_VIEW
- // TODO: Shouldn't Spinner be in this list too? (Was not there in layoutopt)
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- int childCount = LintUtils.getChildCount(element);
- String tagName = element.getTagName();
- if (tagName.equals(SCROLL_VIEW) || tagName.equals(HORIZONTAL_SCROLL_VIEW)) {
- if (childCount > 1 && getAccurateChildCount(element) > 1) {
- context.report(SCROLLVIEW_ISSUE, element,
- context.getLocation(element), "A scroll view can have only one child",
- null);
- }
- } else {
- // Adapter view
- if (childCount > 0 && getAccurateChildCount(element) > 0) {
- context.report(ADAPTERVIEW_ISSUE, element,
- context.getLocation(element),
- "A list/grid should have no children declared in XML", null);
- }
- }
- }
-
- /** Counts the number of children, but skips certain tags like {@code <requestFocus>} */
- private static int getAccurateChildCount(Element element) {
- NodeList childNodes = element.getChildNodes();
- int childCount = 0;
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- !REQUEST_FOCUS.equals(child.getNodeName())) {
- childCount++;
- }
- }
-
- return childCount;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
deleted file mode 100644
index 1541d23..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CleanupDetector.java
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicValue;
-import org.objectweb.asm.tree.analysis.Frame;
-import org.objectweb.asm.tree.analysis.Interpreter;
-import org.objectweb.asm.tree.analysis.Value;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Checks for missing {@code recycle} calls on resources that encourage it, and
- * for missing {@code commit} calls on FragmentTransactions, etc.
- */
-public class CleanupDetector extends Detector implements ClassScanner {
- /** Problems with missing recycle calls */
- public static final Issue RECYCLE_RESOURCE = Issue.create(
- "Recycle", //$NON-NLS-1$
- "Looks for missing recycle() calls on resources",
-
- "Many resources, such as TypedArrays, VelocityTrackers, etc., " +
- "should be recycled (with a `recycle()` call) after use. This lint check looks " +
- "for missing `recycle()` calls.",
-
- Category.PERFORMANCE,
- 7,
- Severity.WARNING,
- CleanupDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- /** Problems with missing commit calls. */
- public static final Issue COMMIT_FRAGMENT = Issue.create(
- "CommitTransaction", //$NON-NLS-1$
- "Looks for missing commit() calls on FragmentTransactions",
-
- "After creating a `FragmentTransaction`, you typically need to commit it as well",
-
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- CleanupDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- // Target method names
- private static final String RECYCLE = "recycle"; //$NON-NLS-1$
- private static final String OBTAIN = "obtain"; //$NON-NLS-1$
- private static final String OBTAIN_NO_HISTORY = "obtainNoHistory"; //$NON-NLS-1$
- private static final String OBTAIN_ATTRIBUTES = "obtainAttributes"; //$NON-NLS-1$
- private static final String OBTAIN_TYPED_ARRAY = "obtainTypedArray"; //$NON-NLS-1$
- private static final String OBTAIN_STYLED_ATTRIBUTES = "obtainStyledAttributes"; //$NON-NLS-1$
- private static final String BEGIN_TRANSACTION = "beginTransaction"; //$NON-NLS-1$
- private static final String COMMIT = "commit"; //$NON-NLS-1$
- private static final String COMMIT_ALLOWING_LOSS = "commitAllowingStateLoss"; //$NON-NLS-1$
-
- // Target owners
- private static final String VELOCITY_TRACKER_CLS = "android/view/VelocityTracker";//$NON-NLS-1$
- private static final String TYPED_ARRAY_CLS = "android/content/res/TypedArray"; //$NON-NLS-1$
- private static final String CONTEXT_CLS = "android/content/Context"; //$NON-NLS-1$
- private static final String MOTION_EVENT_CLS = "android/view/MotionEvent"; //$NON-NLS-1$
- private static final String HANDLER_CLS = "android/os/Handler"; //$NON-NLS-1$
- private static final String RESOURCES_CLS = "android/content/res/Resources"; //$NON-NLS-1$
- private static final String PARCEL_CLS = "android/os/Parcel"; //$NON-NLS-1$
- private static final String FRAGMENT_MANAGER_CLS = "android/app/FragmentManager"; //$NON-NLS-1$
- private static final String FRAGMENT_MANAGER_V4_CLS = "android/support/v4/app/FragmentManager"; //$NON-NLS-1$
- private static final String FRAGMENT_TRANSACTION_CLS = "android/app/FragmentTransaction"; //$NON-NLS-1$
- private static final String FRAGMENT_TRANSACTION_V4_CLS = "android/support/v4/app/FragmentTransaction"; //$NON-NLS-1$
-
- // Target description signatures
- private static final String TYPED_ARRAY_SIG = "Landroid/content/res/TypedArray;"; //$NON-NLS-1$
-
- private boolean mObtainsTypedArray;
- private boolean mRecyclesTypedArray;
- private boolean mObtainsTracker;
- private boolean mRecyclesTracker;
- private boolean mObtainsMotionEvent;
- private boolean mRecyclesMotionEvent;
- private boolean mObtainsParcel;
- private boolean mRecyclesParcel;
- private boolean mObtainsTransaction;
- private boolean mCommitsTransaction;
-
- /** Constructs a new {@link CleanupDetector} */
- public CleanupDetector() {
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- int phase = context.getDriver().getPhase();
- if (phase == 1) {
- if (mObtainsTypedArray && !mRecyclesTypedArray
- || mObtainsTracker && !mRecyclesTracker
- || mObtainsParcel && !mRecyclesParcel
- || mObtainsMotionEvent && !mRecyclesMotionEvent
- || mObtainsTransaction && !mCommitsTransaction) {
- context.getDriver().requestRepeat(this, Scope.CLASS_FILE_SCOPE);
- }
- }
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(
- RECYCLE,
- OBTAIN_STYLED_ATTRIBUTES,
- OBTAIN,
- OBTAIN_ATTRIBUTES,
- OBTAIN_TYPED_ARRAY,
- OBTAIN_NO_HISTORY,
- BEGIN_TRANSACTION,
- COMMIT,
- COMMIT_ALLOWING_LOSS
- );
- }
-
- @Override
- public void checkCall(
- @NonNull ClassContext context,
- @NonNull ClassNode classNode,
- @NonNull MethodNode method,
- @NonNull MethodInsnNode call) {
- String name = call.name;
- String owner = call.owner;
- String desc = call.desc;
- int phase = context.getDriver().getPhase();
- if (RECYCLE.equals(name) && desc.equals("()V")) { //$NON-NLS-1$
- if (owner.equals(TYPED_ARRAY_CLS)) {
- mRecyclesTypedArray = true;
- } else if (owner.equals(VELOCITY_TRACKER_CLS)) {
- mRecyclesTracker = true;
- } else if (owner.equals(MOTION_EVENT_CLS)) {
- mRecyclesMotionEvent = true;
- } else if (owner.equals(PARCEL_CLS)) {
- mRecyclesParcel = true;
- }
- } else if ((COMMIT.equals(name) || COMMIT_ALLOWING_LOSS.equals(name))
- && desc.equals("()I")) { //$NON-NLS-1$
- if (owner.equals(FRAGMENT_TRANSACTION_CLS)
- || owner.equals(FRAGMENT_TRANSACTION_V4_CLS)) {
- mCommitsTransaction = true;
- }
- } else if (owner.equals(MOTION_EVENT_CLS)) {
- if (OBTAIN.equals(name) || OBTAIN_NO_HISTORY.equals(name)) {
- mObtainsMotionEvent = true;
- if (phase == 2 && !mRecyclesMotionEvent) {
- context.report(RECYCLE_RESOURCE, method, call, context.getLocation(call),
- getErrorMessage(MOTION_EVENT_CLS),
- null);
- } else if (phase == 1
- && checkMethodFlow(context, classNode, method, call, MOTION_EVENT_CLS)) {
- // Already reported error above; don't do global check
- mRecyclesMotionEvent = true;
- }
- }
- } else if (OBTAIN.equals(name)) {
- if (owner.equals(VELOCITY_TRACKER_CLS)) {
- mObtainsTracker = true;
- if (phase == 2 && !mRecyclesTracker) {
- context.report(RECYCLE_RESOURCE, method, call, context.getLocation(call),
- getErrorMessage(VELOCITY_TRACKER_CLS),
- null);
- }
- } else if (owner.equals(PARCEL_CLS)) {
- mObtainsParcel = true;
- if (phase == 2 && !mRecyclesParcel) {
- context.report(RECYCLE_RESOURCE, method, call, context.getLocation(call),
- getErrorMessage(PARCEL_CLS),
- null);
- } else if (phase == 1
- && checkMethodFlow(context, classNode, method, call, PARCEL_CLS)) {
- // Already reported error above; don't do global check
- mRecyclesParcel = true;
- }
- }
- } else if (OBTAIN_STYLED_ATTRIBUTES.equals(name)
- || OBTAIN_ATTRIBUTES.equals(name)
- || OBTAIN_TYPED_ARRAY.equals(name)) {
- if ((owner.equals(CONTEXT_CLS) || owner.equals(RESOURCES_CLS))
- && desc.endsWith(TYPED_ARRAY_SIG)) {
- mObtainsTypedArray = true;
- if (phase == 2 && !mRecyclesTypedArray) {
- context.report(RECYCLE_RESOURCE, method, call, context.getLocation(call),
- getErrorMessage(TYPED_ARRAY_CLS),
- null);
- } else if (phase == 1
- && checkMethodFlow(context, classNode, method, call, TYPED_ARRAY_CLS)) {
- // Already reported error above; don't do global check
- mRecyclesTypedArray = true;
- }
- }
- } else if (BEGIN_TRANSACTION.equals(name)
- && (owner.equals(FRAGMENT_MANAGER_CLS) || owner.equals(FRAGMENT_MANAGER_V4_CLS))) {
- mObtainsTransaction = true;
- if (phase == 2 && !mCommitsTransaction) {
- context.report(COMMIT_FRAGMENT, method, call, context.getLocation(call),
- getErrorMessage(FRAGMENT_MANAGER_CLS), null);
- } else if (phase == 1
- && checkMethodFlow(context, classNode, method, call,
- owner.equals(FRAGMENT_MANAGER_CLS)
- ? FRAGMENT_TRANSACTION_CLS : FRAGMENT_TRANSACTION_V4_CLS)) {
- // Already reported error above; don't do global check
- mCommitsTransaction = true;
- }
- }
- }
-
- /** Computes an error message for a missing recycle of the given type */
- private static String getErrorMessage(String owner) {
- if (FRAGMENT_TRANSACTION_CLS.equals(owner) || FRAGMENT_TRANSACTION_V4_CLS.equals(owner)) {
- return "This transaction should be completed with a commit() call";
- }
- String className = owner.substring(owner.lastIndexOf('/') + 1);
- return String.format("This %1$s should be recycled after use with #recycle()",
- className);
- }
-
- /**
- * Ensures that the given allocate call in the given method has a
- * corresponding recycle method, also within the same method, OR, the
- * allocated resource flows out of the method (either as a return value, or
- * into a field, or into some other method (with some known exceptions; e.g.
- * passing a MotionEvent into another MotionEvent's constructor is fine)
- * <p>
- * Returns true if an error was found
- */
- private static boolean checkMethodFlow(ClassContext context, ClassNode classNode,
- MethodNode method, MethodInsnNode call, String recycleOwner) {
- CleanupTracker interpreter = new CleanupTracker(context, method, call, recycleOwner);
- ResourceAnalyzer analyzer = new ResourceAnalyzer(interpreter);
- interpreter.setAnalyzer(analyzer);
- try {
- analyzer.analyze(classNode.name, method);
- if (!interpreter.isCleanedUp() && !interpreter.isEscaped()) {
- Location location = context.getLocation(call);
- String message = getErrorMessage(recycleOwner);
- Issue issue = call.owner.equals(FRAGMENT_MANAGER_CLS)
- ? COMMIT_FRAGMENT : RECYCLE_RESOURCE;
- context.report(issue, method, call, location, message, null);
- return true;
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
-
- return false;
- }
-
- @VisibleForTesting
- static boolean hasReturnType(String owner, String desc) {
- int descLen = desc.length();
- int ownerLen = owner.length();
- if (descLen < ownerLen + 3) {
- return false;
- }
- if (desc.charAt(descLen - 1) != ';') {
- return false;
- }
- int typeBegin = descLen - 2 - ownerLen;
- if (desc.charAt(typeBegin - 1) != ')' || desc.charAt(typeBegin) != 'L') {
- return false;
- }
- return desc.regionMatches(typeBegin + 1, owner, 0, ownerLen);
- }
-
- /**
- * ASM interpreter which tracks the instances of the allocated resource, and
- * checks whether it is eventually passed to a {@code recycle()} call. If the
- * value flows out of the method (to a field, or a method call), it will
- * also consider the resource recycled.
- */
- private static class CleanupTracker extends Interpreter {
- // Only identity matters, not value
- private static final Value INSTANCE = BasicValue.INT_VALUE;
- private static final Value RECYCLED = BasicValue.FLOAT_VALUE;
- private static final Value UNKNOWN = BasicValue.UNINITIALIZED_VALUE;
-
- private final ClassContext mContext;
- private final MethodNode mMethod;
- private final MethodInsnNode mObtainNode;
- private boolean mIsCleanedUp;
- private boolean mEscapes;
- private final String mRecycleOwner;
- private ResourceAnalyzer mAnalyzer;
-
- public CleanupTracker(
- @NonNull ClassContext context,
- @NonNull MethodNode method,
- @NonNull MethodInsnNode obtainNode,
- @NonNull String recycleOwner) {
- super(Opcodes.ASM4);
- mContext = context;
- mMethod = method;
- mObtainNode = obtainNode;
- mRecycleOwner = recycleOwner;
- }
-
- /**
- * Sets the analyzer associated with the interpreter, such that it can
- * get access to the execution frames
- */
- void setAnalyzer(ResourceAnalyzer analyzer) {
- mAnalyzer = analyzer;
- }
-
- /**
- * Returns whether a recycle call was found for the given method
- *
- * @return true if the resource was recycled
- */
- public boolean isCleanedUp() {
- return mIsCleanedUp;
- }
-
- /**
- * Returns whether the target resource escapes from the method, for
- * example as a return value, or a field assignment, or getting passed
- * to another method
- *
- * @return true if the resource escapes
- */
- public boolean isEscaped() {
- return mEscapes;
- }
-
- @Override
- public Value newOperation(AbstractInsnNode node) throws AnalyzerException {
- return UNKNOWN;
- }
-
- @Override
- public Value newValue(final Type type) {
- if (type != null && type.getSort() == Type.VOID) {
- return null;
- } else {
- return UNKNOWN;
- }
- }
-
- @Override
- public Value copyOperation(AbstractInsnNode node, Value value) throws AnalyzerException {
- return value;
- }
-
- @Override
- public Value binaryOperation(AbstractInsnNode node, Value value1, Value value2)
- throws AnalyzerException {
- if (node.getOpcode() == Opcodes.PUTFIELD) {
- if (value2 == INSTANCE) {
- mEscapes = true;
- }
- }
- return merge(value1, value2);
- }
-
- @Override
- public Value naryOperation(AbstractInsnNode node, List values) throws AnalyzerException {
- if (node == mObtainNode) {
- return INSTANCE;
- }
-
- MethodInsnNode call = null;
- if (node.getType() == AbstractInsnNode.METHOD_INSN) {
- call = (MethodInsnNode) node;
- if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
- if (call.name.equals(RECYCLE) && call.owner.equals(mRecycleOwner)) {
- if (values != null && values.size() == 1 && values.get(0) == INSTANCE) {
- mIsCleanedUp = true;
- Frame frame = mAnalyzer.getCurrentFrame();
- if (frame != null) {
- int localSize = frame.getLocals();
- for (int i = 0; i < localSize; i++) {
- Value local = frame.getLocal(i);
- if (local == INSTANCE) {
- frame.setLocal(i, RECYCLED);
- }
- }
- int stackSize = frame.getStackSize();
- if (stackSize == 1 && frame.getStack(0) == INSTANCE) {
- frame.pop();
- frame.push(RECYCLED);
- }
- }
- return RECYCLED;
- }
- } else if ((call.name.equals(COMMIT) || call.name.equals(COMMIT_ALLOWING_LOSS))
- && call.owner.equals(mRecycleOwner)) {
- if (values != null && values.size() == 1 && values.get(0) == INSTANCE) {
- mIsCleanedUp = true;
- return INSTANCE;
- }
- } else if (call.owner.equals(mRecycleOwner)
- && hasReturnType(mRecycleOwner, call.desc)) {
- // Called method which returns self. This helps handle cases where you call
- // createTransaction().method1().method2().method3().commit() -- if
- // method1, 2 and 3 all return "this" then the commit call is really
- // called on the createTransaction instance
- return INSTANCE;
- }
- }
- }
-
- if (values != null && values.size() >= 1) {
- // Skip the first element: method calls *on* the TypedArray are okay
- int start = node.getOpcode() == Opcodes.INVOKESTATIC ? 0 : 1;
- for (int i = 0, n = values.size(); i < n; i++) {
- Object v = values.get(i);
- if (v == INSTANCE && i >= start) {
- // Known special cases
- if (node.getOpcode() == Opcodes.INVOKESTATIC) {
- assert call != null;
- if (call.name.equals(OBTAIN) &&
- call.owner.equals(MOTION_EVENT_CLS)) {
- return UNKNOWN;
- }
- }
-
- // Passing the instance to another method: could leak
- // the instance out of this method (for example calling
- // a method which recycles it on our behalf, or store it
- // in some holder which will recycle it later). In this
- // case, just assume that things are okay.
- mEscapes = true;
- } else if (v == RECYCLED && call != null) {
- Location location = mContext.getLocation(call);
- String message = String.format("This %1$s has already been recycled",
- mRecycleOwner.substring(mRecycleOwner.lastIndexOf('/') + 1));
- mContext.report(RECYCLE_RESOURCE, mMethod, call, location, message, null);
- }
- }
- }
-
- return UNKNOWN;
- }
-
- @Override
- public Value unaryOperation(AbstractInsnNode node, Value value) throws AnalyzerException {
- return value;
- }
-
- @Override
- public Value ternaryOperation(AbstractInsnNode node, Value value1, Value value2,
- Value value3) throws AnalyzerException {
- if (value1 == RECYCLED || value2 == RECYCLED || value3 == RECYCLED) {
- return RECYCLED;
- } else if (value1 == INSTANCE || value2 == INSTANCE || value3 == INSTANCE) {
- return INSTANCE;
- }
- return UNKNOWN;
- }
-
- @Override
- public void returnOperation(AbstractInsnNode node, Value value1, Value value2)
- throws AnalyzerException {
- if (value1 == INSTANCE || value2 == INSTANCE) {
- mEscapes = true;
- }
- }
-
- @Override
- public Value merge(Value value1, Value value2) {
- if (value1 == RECYCLED || value2 == RECYCLED) {
- return RECYCLED;
- } else if (value1 == INSTANCE || value2 == INSTANCE) {
- return INSTANCE;
- }
- return UNKNOWN;
- }
- }
-
- private static class ResourceAnalyzer extends Analyzer {
- private Frame mCurrent;
- private Frame mFrame1;
- private Frame mFrame2;
-
- public ResourceAnalyzer(Interpreter interpreter) {
- super(interpreter);
- }
-
- Frame getCurrentFrame() {
- return mCurrent;
- }
-
- @Override
- protected void init(String owner, MethodNode m) throws AnalyzerException {
- mCurrent = mFrame2;
- super.init(owner, m);
- }
-
- @Override
- protected Frame newFrame(int nLocals, int nStack) {
- // Stash the two most recent frame allocations. When init is called the second
- // most recently seen frame is the current frame used during execution, which
- // is where we need to replace INSTANCE with RECYCLED when the void
- // recycle method is called.
- Frame newFrame = super.newFrame(nLocals, nStack);
- mFrame2 = mFrame1;
- mFrame1 = newFrame;
- return newFrame;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java
deleted file mode 100644
index e7ffcb6..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.RESOURCE_CLZ_COLOR;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-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 java.io.File;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Select;
-
-/**
- * Looks for cases where the code attempts to set a resource id, rather than
- * a resolved color, as the RGB int.
- */
-public class ColorUsageDetector extends Detector implements Detector.JavaScanner {
- /** Attempting to set a resource id as a color */
- public static final Issue ISSUE = Issue.create(
- "ResourceAsColor", //$NON-NLS-1$
- "Looks for calls to setColor where a resource id is passed instead of a " +
- "resolved color",
-
- "Methods that take a color in the form of an integer should be passed " +
- "an RGB triple, not the actual color resource id. You must call " +
- "`getResources().getColor(resource)` to resolve the actual color value first.",
-
- Category.CORRECTNESS,
- 7,
- Severity.ERROR,
- ColorUsageDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Constructs a new {@link ColorUsageDetector} check */
- public ColorUsageDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public boolean appliesToResourceRefs() {
- return true;
- }
-
- @Override
- public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull Node select, @NonNull String type, @NonNull String name, boolean isFramework) {
- if (type.equals(RESOURCE_CLZ_COLOR)) {
- while (select.getParent() instanceof Select) {
- select = select.getParent();
- }
-
- // See if this method is being called on a setter
- if (select.getParent() instanceof MethodInvocation) {
- MethodInvocation call = (MethodInvocation) select.getParent();
- String methodName = call.astName().astValue();
- if (methodName.endsWith("Color") //$NON-NLS-1$
- && methodName.startsWith("set")) { //$NON-NLS-1$
- context.report(
- ISSUE, select, context.getLocation(select), String.format(
- "Should pass resolved color instead of resource id here: " +
- "getResources().getColor(%1$s)", select.toString()),
- null);
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java
deleted file mode 100644
index 6f07e77..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CommentDetector.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-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 java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Comment;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Node;
-
-/**
- * Looks for issues in Java comments
- */
-public class CommentDetector extends Detector implements Detector.JavaScanner {
- private static final String STOPSHIP_COMMENT = "STOPSHIP"; //$NON-NLS-1$
-
- /** Looks for hidden code */
- public static final Issue EASTEREGG = Issue.create(
- "EasterEgg", //$NON-NLS-1$
- "Looks for hidden easter eggs",
- "An \"easter egg\" is code deliberately hidden in the code, both from potential " +
- "users and even from other developers. This lint check looks for code which " +
- "looks like it may be hidden from sight.",
- Category.SECURITY,
- 6,
- Severity.WARNING,
- CommentDetector.class,
- Scope.JAVA_FILE_SCOPE).setEnabledByDefault(false);
-
- /** Looks for special comment markers intended to stop shipping the code */
- public static final Issue STOPSHIP = Issue.create(
- "StopShip", //$NON-NLS-1$
- "Looks for comment markers of the form \"STOPSHIP\" which indicates that code " +
- "should not be released yet",
-
- "Using the comment `// STOPSHIP` can be used to flag code that is incomplete but " +
- "checked in. This comment marker can be used to indicate that the code should not " +
- "be shipped until the issue is addressed, and lint will look for these.",
- Category.CORRECTNESS,
- 10,
- Severity.WARNING,
- CommentDetector.class,
- Scope.JAVA_FILE_SCOPE).setEnabledByDefault(false);
-
- private static final String ESCAPE_STRING = "\\u002a\\u002f"; //$NON-NLS-1$
-
- /** Lombok's AST only passes comment nodes for Javadoc so I need to do manual token scanning
- instead */
- private static final boolean USE_AST = false;
-
-
- /** Constructs a new {@link CommentDetector} check */
- public CommentDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- if (USE_AST) {
- return Collections.<Class<? extends Node>>singletonList(Comment.class);
- } else {
- return null;
- }
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- // Lombok does not generate comment nodes for block and line comments, only for
- // javadoc comments!
- if (USE_AST) {
- return new CommentChecker(context);
- } else {
- String source = context.getContents();
- if (source == null) {
- return null;
- }
- // Process the Java source such that we pass tokens to it
-
- for (int i = 0, n = source.length() - 1; i < n; i++) {
- char c = source.charAt(i);
- if (c == '\\') {
- i += 1;
- } else if (c == '/') {
- char next = source.charAt(i + 1);
- if (next == '/') {
- // Line comment
- int start = i + 2;
- int end = source.indexOf('\n', start);
- if (end == -1) {
- end = n;
- }
- checkComment(context, source, 0, start, end);
- } else if (next == '*') {
- // Block comment
- int start = i + 2;
- int end = source.indexOf("*/", start);
- if (end == -1) {
- end = n;
- }
- checkComment(context, source, 0, start, end);
- }
- }
- }
- return null;
- }
- }
-
- private static class CommentChecker extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public CommentChecker(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitComment(Comment node) {
- String contents = node.astContent();
- checkComment(mContext, contents, node.getPosition().getStart(), 0, contents.length());
- return super.visitComment(node);
- }
- }
-
- private static void checkComment(
- @NonNull Context context,
- @NonNull String source,
- int offset,
- int start,
- int end) {
- char prev = 0;
- char c;
- for (int i = start; i < end - 2; i++, prev = c) {
- c = source.charAt(i);
- if (prev == '\\') {
- if (c == 'u' || c == 'U') {
- if (source.regionMatches(true, i - 1, ESCAPE_STRING,
- 0, ESCAPE_STRING.length())) {
- Location location = Location.create(context.file, source,
- offset + i - 1, offset + i - 1 + ESCAPE_STRING.length());
- context.report(EASTEREGG, location,
- "Code might be hidden here; found unicode escape sequence " +
- "which is interpreted as comment end, compiled code follows",
- null);
- }
- } else {
- i++;
- }
- } else if (prev == 'S' && c == 'T' &&
- source.regionMatches(i - 1, STOPSHIP_COMMENT, 0, STOPSHIP_COMMENT.length())) {
- // TODO: Only flag this issue in release mode??
- Location location = Location.create(context.file, source,
- offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length());
- context.report(STOPSHIP, location,
- "STOPSHIP comment found; points to code which must be fixed prior " +
- "to release",
- null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
deleted file mode 100644
index cbafe28..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ControlFlowGraph.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.google.common.collect.Maps;
-
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FrameNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.LabelNode;
-import org.objectweb.asm.tree.LineNumberNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.TryCatchBlockNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A {@linkplain ControlFlowGraph} is a graph containing a node for each
- * instruction in a method, and an edge for each possible control flow; usually
- * just "next" for the instruction following the current instruction, but in the
- * case of a branch such as an "if", multiple edges to each successive location,
- * or with a "goto", a single edge to the jumped-to instruction.
- * <p>
- * It also adds edges for abnormal control flow, such as the possibility of a
- * method call throwing a runtime exception.
- */
-public class ControlFlowGraph {
- /** Map from instructions to nodes */
- private Map<AbstractInsnNode, Node> mNodeMap;
-
- /**
- * Creates a new {@link ControlFlowGraph} and populates it with the flow
- * control for the given method. If the optional {@code initial} parameter is
- * provided with an existing graph, then the graph is simply populated, not
- * created. This allows subclassing of the graph instance, if necessary.
- *
- * @param initial usually null, but can point to an existing instance of a
- * {@link ControlFlowGraph} in which that graph is reused (but
- * populated with new edges)
- * @param classNode the class containing the method to be analyzed
- * @param method the method to be analyzed
- * @return a {@link ControlFlowGraph} with nodes for the control flow in the
- * given method
- * @throws AnalyzerException if the underlying bytecode library is unable to
- * analyze the method bytecode
- */
- @NonNull
- public static ControlFlowGraph create(
- @Nullable ControlFlowGraph initial,
- @NonNull ClassNode classNode,
- @NonNull MethodNode method) throws AnalyzerException {
- final ControlFlowGraph graph = initial != null ? initial : new ControlFlowGraph();
- final InsnList instructions = method.instructions;
- graph.mNodeMap = Maps.newHashMapWithExpectedSize(instructions.size());
-
- // Create a flow control graph using ASM4's analyzer. According to the ASM 4 guide
- // (download.forge.objectweb.org/asm/asm4-guide.pdf) there are faster ways to construct
- // it, but those require a lot more code.
- Analyzer analyzer = new Analyzer(new BasicInterpreter()) {
- @Override
- protected void newControlFlowEdge(int insn, int successor) {
- // Update the information as of whether the this object has been
- // initialized at the given instruction.
- AbstractInsnNode from = instructions.get(insn);
- AbstractInsnNode to = instructions.get(successor);
- graph.add(from, to);
- }
-
- @Override
- protected boolean newControlFlowExceptionEdge(int insn, TryCatchBlockNode tcb) {
- AbstractInsnNode from = instructions.get(insn);
- graph.exception(from, tcb);
- return super.newControlFlowExceptionEdge(insn, tcb);
- }
-
- @Override
- protected boolean newControlFlowExceptionEdge(int insn, int successor) {
- AbstractInsnNode from = instructions.get(insn);
- AbstractInsnNode to = instructions.get(successor);
- graph.exception(from, to);
- return super.newControlFlowExceptionEdge(insn, successor);
- }
- };
-
- analyzer.analyze(classNode.name, method);
- return graph;
- }
-
- /** A {@link Node} is a node in the control flow graph for a method, pointing to
- * the instruction and its possible successors */
- public static class Node {
- /** The instruction */
- public final AbstractInsnNode instruction;
- /** Any normal successors (e.g. following instruction, or goto or conditional flow) */
- public final List<Node> successors = new ArrayList<Node>(2);
- /** Any abnormal successors (e.g. the handler to go to following an exception) */
- public final List<Node> exceptions = new ArrayList<Node>(1);
-
- /** A tag for use during depth-first-search iteration of the graph etc */
- public int visit;
-
- /**
- * Constructs a new control graph node
- *
- * @param instruction the instruction to associate with this node
- */
- public Node(@NonNull AbstractInsnNode instruction) {
- this.instruction = instruction;
- }
-
- void addSuccessor(@NonNull Node node) {
- if (!successors.contains(node)) {
- successors.add(node);
- }
- }
-
- void addExceptionPath(@NonNull Node node) {
- if (!exceptions.contains(node)) {
- exceptions.add(node);
- }
- }
-
- /**
- * Represents this instruction as a string, for debugging purposes
- *
- * @param includeAdjacent whether it should include a display of
- * adjacent nodes as well
- * @return a string representation
- */
- @NonNull
- public String toString(boolean includeAdjacent) {
- StringBuilder sb = new StringBuilder(100);
-
- sb.append(getId(instruction));
- sb.append(':');
-
- if (instruction instanceof LabelNode) {
- //LabelNode l = (LabelNode) instruction;
- //sb.append('L' + l.getLabel().getOffset() + ":");
- //sb.append('L' + l.getLabel().info + ":");
- sb.append("LABEL");
- } else if (instruction instanceof LineNumberNode) {
- sb.append("LINENUMBER ").append(((LineNumberNode)instruction).line);
- } else if (instruction instanceof FrameNode) {
- sb.append("FRAME");
- } else {
- int opcode = instruction.getOpcode();
- // AbstractVisitor isn't available unless debug/util is included,
- boolean printed = false;
- try {
- Class<?> cls = Class.forName("org.objectweb.asm.util"); //$NON-NLS-1$
- Field field = cls.getField("OPCODES");
- String[] OPCODES = (String[]) field.get(null);
- printed = true;
- if (opcode > 0 && opcode <= OPCODES.length) {
- sb.append(OPCODES[opcode]);
- if (instruction.getType() == AbstractInsnNode.METHOD_INSN) {
- sb.append('(').append(((MethodInsnNode)instruction).name).append(')');
- }
- }
- } catch (Throwable t) {
- // debug not installed: just do toString() on the instructions
- }
- if (!printed) {
- if (instruction.getType() == AbstractInsnNode.METHOD_INSN) {
- sb.append('(').append(((MethodInsnNode)instruction).name).append(')');
- } else {
- sb.append(instruction.toString());
- }
- }
- }
-
- if (includeAdjacent) {
- if (successors != null && !successors.isEmpty()) {
- sb.append(" Next:");
- for (Node successor : successors) {
- sb.append(' ');
- sb.append(successor.toString(false));
- }
- }
-
- if (exceptions != null && !exceptions.isEmpty()) {
- sb.append(" Exceptions:");
- for (Node exception : exceptions) {
- sb.append(' ');
- sb.append(exception.toString(false));
- }
- }
- sb.append('\n');
- }
-
- return sb.toString();
- }
- }
-
- /** Adds an exception flow to this graph */
- protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- getNode(from).addSuccessor(getNode(to));
- }
-
- /** Adds an exception flow to this graph */
- protected void exception(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- // For now, these edges appear useless; we also get more specific
- // information via the TryCatchBlockNode which we use instead.
- //getNode(from).addExceptionPath(getNode(to));
- }
-
- /** Adds an exception try block node to this graph */
- protected void exception(@NonNull AbstractInsnNode from, @NonNull TryCatchBlockNode tcb) {
- // Add tcb's to all instructions in the range
- LabelNode start = tcb.start;
- LabelNode end = tcb.end; // exclusive
-
- // Add exception edges for all method calls in the range
- AbstractInsnNode curr = start;
- Node handlerNode = getNode(tcb.handler);
- while (curr != end && curr != null) {
- if (curr.getType() == AbstractInsnNode.METHOD_INSN) {
- // Method call; add exception edge to handler
- if (tcb.type == null) {
- // finally block: not an exception path
- getNode(curr).addSuccessor(handlerNode);
- }
- getNode(curr).addExceptionPath(handlerNode);
- }
- curr = curr.getNext();
- }
- }
-
- /**
- * Looks up (and if necessary) creates a graph node for the given instruction
- *
- * @param instruction the instruction
- * @return the control flow graph node corresponding to the given
- * instruction
- */
- @NonNull
- public Node getNode(@NonNull AbstractInsnNode instruction) {
- Node node = mNodeMap.get(instruction);
- if (node == null) {
- node = new Node(instruction);
- mNodeMap.put(instruction, node);
- }
-
- return node;
- }
-
- /**
- * Creates a human readable version of the graph
- *
- * @param start the starting instruction, or null if not known or to use the
- * first instruction
- * @return a string version of the graph
- */
- @NonNull
- public String toString(@Nullable Node start) {
- StringBuilder sb = new StringBuilder(400);
-
- AbstractInsnNode curr;
- if (start != null) {
- curr = start.instruction;
- } else {
- if (mNodeMap.isEmpty()) {
- return "<empty>";
- } else {
- curr = mNodeMap.keySet().iterator().next();
- while (curr.getPrevious() != null) {
- curr = curr.getPrevious();
- }
- }
- }
-
- while (curr != null) {
- Node node = mNodeMap.get(curr);
- if (node != null) {
- sb.append(node.toString(true));
- }
- curr = curr.getNext();
- }
-
- return sb.toString();
- }
-
- @Override
- public String toString() {
- return toString(null);
- }
-
- // ---- For debugging only ----
-
- private static Map<Object, String> sIds = null;
- private static int sNextId = 1;
- private static String getId(Object object) {
- if (sIds == null) {
- sIds = Maps.newHashMap();
- }
- String id = sIds.get(object);
- if (id == null) {
- id = Integer.toString(sNextId++);
- sIds.put(object, id);
- }
- return id;
- }
-}
-
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java
deleted file mode 100644
index 84fb6b6..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/CutPasteDetector.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.RESOURCE_CLZ_ID;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.google.common.collect.Maps;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import lombok.ast.ArrayAccess;
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.Cast;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.If;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.Statement;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Detector looking for cut & paste issues
- */
-public class CutPasteDetector extends Detector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "CutPasteId", //$NON-NLS-1$
- "Looks for code cut & paste mistakes in findViewById() calls",
-
- "This lint check looks for cases where you have cut & pasted calls to " +
- "`findViewById` but have forgotten to update the R.id field. It's possible " +
- "that your code is simply (redundantly) looking up the field repeatedly, " +
- "but lint cannot distinguish that from a case where you for example want to " +
- "initialize fields `prev` and `next` and you cut & pasted `findViewById(R.id.prev)` " +
- "and forgot to update the second initialization to `R.id.next`.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- CutPasteDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- private Node mLastMethod;
- private Map<String, MethodInvocation> mIds;
- private Map<String, String> mLhs;
- private Map<String, String> mCallOperands;
-
- /** Constructs a new {@link CutPasteDetector} check */
- public CutPasteDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("findViewById"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation call) {
- String lhs = getLhs(call);
- if (lhs == null) {
- return;
- }
-
- Node method = JavaContext.findSurroundingMethod(call);
- if (method == null) {
- return;
- } else if (method != mLastMethod) {
- mIds = Maps.newHashMap();
- mLhs = Maps.newHashMap();
- mCallOperands = Maps.newHashMap();
- mLastMethod = method;
- }
-
- String callOperand = call.astOperand() != null ? call.astOperand().toString() : "";
-
- Expression first = call.astArguments().first();
- if (first instanceof Select) {
- Select select = (Select) first;
- String id = select.astIdentifier().astValue();
- Expression operand = select.astOperand();
- if (operand instanceof Select) {
- Select type = (Select) operand;
- if (type.astIdentifier().astValue().equals(RESOURCE_CLZ_ID)) {
- if (mIds.containsKey(id)) {
- if (lhs.equals(mLhs.get(id))) {
- return;
- }
- if (!callOperand.equals(mCallOperands.get(id))) {
- return;
- }
- MethodInvocation earlierCall = mIds.get(id);
- if (!isReachableFrom(method, earlierCall, call)) {
- return;
- }
- Location location = context.getLocation(call);
- Location secondary = context.getLocation(earlierCall);
- secondary.setMessage("First usage here");
- location.setSecondary(secondary);
- context.report(ISSUE, call, location, String.format(
- "The id %1$s has already been looked up in this method; possible " +
- "cut & paste error?", first.toString()), null);
- } else {
- mIds.put(id, call);
- mLhs.put(id, lhs);
- mCallOperands.put(id, callOperand);
- }
- }
- }
- }
- }
-
- @Nullable
- private static String getLhs(@NonNull MethodInvocation call) {
- Node parent = call.getParent();
- if (parent instanceof Cast) {
- parent = parent.getParent();
- }
-
- if (parent instanceof VariableDefinitionEntry) {
- VariableDefinitionEntry vde = (VariableDefinitionEntry) parent;
- return vde.astName().astValue();
- } else if (parent instanceof BinaryExpression) {
- BinaryExpression be = (BinaryExpression) parent;
- Expression left = be.astLeft();
- if (left instanceof VariableReference || left instanceof Select) {
- return be.astLeft().toString();
- } else if (left instanceof ArrayAccess) {
- ArrayAccess aa = (ArrayAccess) left;
- return aa.astOperand().toString();
- }
- }
-
- return null;
- }
-
- private static boolean isReachableFrom(
- @NonNull Node method,
- @NonNull MethodInvocation from,
- @NonNull MethodInvocation to) {
- ReachableVisitor visitor = new ReachableVisitor(from, to);
- method.accept(visitor);
-
- return visitor.isReachable();
- }
-
- private static class ReachableVisitor extends ForwardingAstVisitor {
- @NonNull private final MethodInvocation mFrom;
- @NonNull private final MethodInvocation mTo;
- private boolean mReachable;
- private boolean mSeenEnd;
-
- public ReachableVisitor(@NonNull MethodInvocation from, @NonNull MethodInvocation to) {
- mFrom = from;
- mTo = to;
- }
-
- boolean isReachable() {
- return mReachable;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node == mFrom) {
- mReachable = true;
- } else if (node == mTo) {
- mSeenEnd = true;
-
- }
- return super.visitMethodInvocation(node);
- }
-
- @Override
- public boolean visitIf(If node) {
- Expression condition = node.astCondition();
- Statement body = node.astStatement();
- Statement elseBody = node.astElseStatement();
- if (condition != null) {
- condition.accept(this);
- }
- if (body != null) {
- boolean wasReachable = mReachable;
- body.accept(this);
- mReachable = wasReachable;
- }
- if (elseBody != null) {
- boolean wasReachable = mReachable;
- elseBody.accept(this);
- mReachable = wasReachable;
- }
-
- endVisit(node);
-
- return false;
- }
-
- @Override
- public boolean visitNode(Node node) {
- return mSeenEnd;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
deleted file mode 100644
index 6475a7a..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ABSOLUTE_LAYOUT;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_AUTO_TEXT;
-import static com.android.SdkConstants.ATTR_CAPITALIZE;
-import static com.android.SdkConstants.ATTR_EDITABLE;
-import static com.android.SdkConstants.ATTR_ENABLED;
-import static com.android.SdkConstants.ATTR_INPUT_METHOD;
-import static com.android.SdkConstants.ATTR_NUMERIC;
-import static com.android.SdkConstants.ATTR_PASSWORD;
-import static com.android.SdkConstants.ATTR_PHONE_NUMBER;
-import static com.android.SdkConstants.ATTR_SINGLE_LINE;
-import static com.android.SdkConstants.EDIT_TEXT;
-import static com.android.SdkConstants.VALUE_TRUE;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check which looks for usage of deprecated tags, attributes, etc.
- */
-public class DeprecationDetector extends LayoutDetector {
- /** Usage of deprecated views or attributes */
- public static final Issue ISSUE = Issue.create(
- "Deprecated", //$NON-NLS-1$
- "Looks for usages of deprecated layouts, attributes, and so on.",
- "Deprecated views, attributes and so on are deprecated because there " +
- "is a better way to do something. Do it that new way. You've been warned.",
- Category.CORRECTNESS,
- 2,
- Severity.WARNING,
- DeprecationDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link DeprecationDetector} */
- public DeprecationDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(
- ABSOLUTE_LAYOUT
- );
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Arrays.asList(
- // TODO: fill_parent is deprecated as of API 8.
- // We could warn about it, but it will probably be very noisy
- // and make people disable the deprecation check; let's focus on
- // some older flags for now
- //"fill_parent",
-
- ATTR_EDITABLE,
- ATTR_INPUT_METHOD,
- ATTR_AUTO_TEXT,
- ATTR_CAPITALIZE,
-
- // This flag is still used a lot and is still properly handled by TextView
- // so in the interest of not being too noisy and make people ignore all the
- // output, keep quiet about this one -for now-.
- //ATTR_SINGLE_LINE,
-
- // This attribute is marked deprecated in android.R.attr but apparently
- // using the suggested replacement of state_enabled doesn't work, see issue 27613
- //ATTR_ENABLED,
-
- ATTR_NUMERIC,
- ATTR_PHONE_NUMBER,
- ATTR_PASSWORD
-
- // These attributes are also deprecated; not yet enabled until we
- // know the API level to apply the deprecation for:
-
- // "ignored as of ICS (but deprecated earlier)"
- //"fadingEdge",
-
- // "This attribute is not used by the Android operating system."
- //"restoreNeedsApplication",
-
- // "This will create a non-standard UI appearance, because the search bar UI is
- // changing to use only icons for its buttons."
- //"searchButtonText",
-
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- context.report(ISSUE, element, context.getLocation(element),
- String.format("%1$s is deprecated", element.getTagName()), null);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
- return;
- }
-
- String name = attribute.getLocalName();
- String fix;
- int minSdk = 1;
- if (name.equals(ATTR_EDITABLE)) {
- if (!EDIT_TEXT.equals(attribute.getOwnerElement().getTagName())) {
- fix = "Use an <EditText> to make it editable";
- } else {
- if (VALUE_TRUE.equals(attribute.getValue())) {
- fix = "<EditText> is already editable";
- } else {
- fix = "Use inputType instead";
- }
- }
- } else if (name.equals(ATTR_ENABLED)) {
- fix = "Use state_enabled instead";
- } else if (name.equals(ATTR_SINGLE_LINE)) {
- fix = "Use maxLines=\"1\" instead";
- } else {
- assert name.equals(ATTR_INPUT_METHOD)
- || name.equals(ATTR_CAPITALIZE)
- || name.equals(ATTR_NUMERIC)
- || name.equals(ATTR_PHONE_NUMBER)
- || name.equals(ATTR_PASSWORD)
- || name.equals(ATTR_AUTO_TEXT);
- fix = "Use inputType instead";
- // The inputType attribute was introduced in API 3 so don't warn about
- // deprecation if targeting older platforms
- minSdk = 3;
- }
-
- if (context.getProject().getMinSdk() < minSdk) {
- return;
- }
-
- context.report(ISSUE, attribute, context.getLocation(attribute),
- String.format("%1$s is deprecated: %2$s",
- attribute.getName(), fix), null);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
deleted file mode 100644
index 2b24732..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DetectMissingPrefix.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ATTR_CORE_APP;
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ATTR_PACKAGE;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.SdkConstants.VIEW_TAG;
-import static com.android.SdkConstants.XMLNS_PREFIX;
-import static com.android.resources.ResourceFolderType.ANIM;
-import static com.android.resources.ResourceFolderType.ANIMATOR;
-import static com.android.resources.ResourceFolderType.COLOR;
-import static com.android.resources.ResourceFolderType.DRAWABLE;
-import static com.android.resources.ResourceFolderType.INTERPOLATOR;
-import static com.android.resources.ResourceFolderType.LAYOUT;
-import static com.android.resources.ResourceFolderType.MENU;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Detects layout attributes on builtin Android widgets that do not specify
- * a prefix but probably should.
- */
-public class DetectMissingPrefix extends LayoutDetector {
-
- /** Attributes missing the android: prefix */
- public static final Issue MISSING_NAMESPACE = Issue.create(
- "MissingPrefix", //$NON-NLS-1$
- "Detect XML attributes not using the Android namespace",
- "Most Android views have attributes in the Android namespace. When referencing " +
- "these attributes you *must* include the namespace prefix, or your attribute will " +
- "be interpreted by `aapt` as just a custom attribute.\n" +
- "\n" +
- "Similarly, in manifest files, nearly all attributes should be in the `android:` " +
- "namespace.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- DetectMissingPrefix.class,
- EnumSet.of(Scope.MANIFEST, Scope.RESOURCE_FILE))
- .addAnalysisScope(Scope.MANIFEST_SCOPE)
- .addAnalysisScope(Scope.RESOURCE_FILE_SCOPE);
-
- private static final Set<String> NO_PREFIX_ATTRS = new HashSet<String>();
- static {
- NO_PREFIX_ATTRS.add(ATTR_CLASS);
- NO_PREFIX_ATTRS.add(ATTR_STYLE);
- NO_PREFIX_ATTRS.add(ATTR_LAYOUT);
- NO_PREFIX_ATTRS.add(ATTR_PACKAGE);
- NO_PREFIX_ATTRS.add(ATTR_CORE_APP);
- }
-
- /** Constructs a new {@link DetectMissingPrefix} */
- public DetectMissingPrefix() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == LAYOUT
- || folderType == MENU
- || folderType == DRAWABLE
- || folderType == ANIM
- || folderType == ANIMATOR
- || folderType == COLOR
- || folderType == INTERPOLATOR;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String uri = attribute.getNamespaceURI();
- if (uri == null || uri.isEmpty()) {
- String name = attribute.getName();
- if (name == null) {
- return;
- }
- if (NO_PREFIX_ATTRS.contains(name)) {
- return;
- }
-
- Element element = attribute.getOwnerElement();
- if (isCustomView(element) && context.getResourceFolderType() != null) {
- return;
- }
-
- if (name.startsWith(XMLNS_PREFIX)) {
- return;
- }
-
- context.report(MISSING_NAMESPACE, attribute,
- context.getLocation(attribute),
- "Attribute is missing the Android namespace prefix",
- null);
- } else if (!ANDROID_URI.equals(uri)
- && !TOOLS_URI.equals(uri)
- && context.getResourceFolderType() == ResourceFolderType.LAYOUT
- && !isCustomView(attribute.getOwnerElement())
- && !attribute.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
- // TODO: Consider not enforcing that the parent is a custom view
- // too, though in that case we should filter out views that are
- // layout params for the custom view parent:
- // ....&& !attribute.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
- && attribute.getOwnerElement().getParentNode().getNodeType() == Node.ELEMENT_NODE
- && !isCustomView((Element) attribute.getOwnerElement().getParentNode())) {
- context.report(MISSING_NAMESPACE, attribute,
- context.getLocation(attribute),
- String.format("Unexpected namespace prefix \"%1$s\" found for tag %2$s",
- attribute.getPrefix(), attribute.getOwnerElement().getTagName()),
- null);
- }
- }
-
- private static boolean isCustomView(Element element) {
- // If this is a custom view, the usage of custom attributes can be legitimate
- String tag = element.getTagName();
- if (tag.equals(VIEW_TAG)) {
- // <view class="my.custom.view" ...>
- return true;
- }
-
- return tag.indexOf('.') != -1 && !tag.startsWith(ANDROID_PKG_PREFIX);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
deleted file mode 100644
index 1a2a720..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DosLineEndingDetector.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Document;
-
-/**
- * Checks that the line endings in DOS files are consistent
- */
-public class DosLineEndingDetector extends LayoutDetector {
- /** Detects mangled DOS line ending documents */
- public static final Issue ISSUE = Issue.create(
- "MangledCRLF", //$NON-NLS-1$
- "Checks that files with DOS line endings are consistent",
-
- "On Windows, line endings are typically recorded as carriage return plus " +
- "newline: \\r\\n.\n" +
- "\n" +
- "This detector looks for invalid line endings with repeated carriage return " +
- "characters (without newlines). Previous versions of the ADT plugin could " +
- "accidentally introduce these into the file, and when editing the file, the " +
- "editor could produce confusing visual artifacts.",
-
- Category.CORRECTNESS,
- 2,
- Severity.ERROR,
- DosLineEndingDetector.class,
- Scope.RESOURCE_FILE_SCOPE)
- .setMoreInfo("https://bugs.eclipse.org/bugs/show_bug.cgi?id=375421"); //$NON-NLS-1$
-
- /** Constructs a new {@link DosLineEndingDetector} */
- public DosLineEndingDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- String contents = context.getContents();
- if (contents == null) {
- return;
- }
-
- // We could look for *consistency* and complain if you mix \n and \r\n too,
- // but that isn't really a problem (most editors handle it) so let's
- // not complain needlessly.
-
- char prev = 0;
- for (int i = 0, n = contents.length(); i < n; i++) {
- char c = contents.charAt(i);
- if (c == '\r' && prev == '\r') {
- String message = "Incorrect line ending: found carriage return (\\r) without " +
- "corresponding newline (\\n)";
-
- // Mark the whole line as the error range, since pointing just to the
- // line ending makes the error invisible in IDEs and error reports etc
- // Find the most recent non-blank line
- boolean blankLine = true;
- for (int index = i - 2; index < i; index++) {
- char d = contents.charAt(index);
- if (!Character.isWhitespace(d)) {
- blankLine = false;
- }
- }
-
- int lineBegin = i;
- for (int index = i - 2; index >= 0; index--) {
- char d = contents.charAt(index);
- if (d == '\n') {
- lineBegin = index + 1;
- if (!blankLine) {
- break;
- }
- } else if (!Character.isWhitespace(d)) {
- blankLine = false;
- }
- }
-
- int lineEnd = Math.min(contents.length(), i + 1);
- Location location = Location.create(context.file, contents, lineBegin, lineEnd);
- context.report(ISSUE, document.getDocumentElement(), location, message, null);
- return;
- }
- prev = c;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
deleted file mode 100644
index de3e4d2..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
+++ /dev/null
@@ -1,673 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks for duplicate ids within a layout and within an included layout
- */
-public class DuplicateIdDetector extends LayoutDetector {
- private Set<String> mIds;
- private Map<File, Set<String>> mFileToIds;
- private Map<File, List<String>> mIncludes;
-
- // Data structures used for location collection in phase 2
-
- // Map from include files to include names to pairs of message and location
- // Map from file defining id, to the id to be defined, to a pair of location and message
- private Multimap<File, Multimap<String, Occurrence>> mLocations;
- private List<Occurrence> mErrors;
-
- /** The main issue discovered by this detector */
- public static final Issue WITHIN_LAYOUT = Issue.create(
- "DuplicateIds", //$NON-NLS-1$
- "Checks for duplicate ids within a single layout",
- "Within a layout, id's should be unique since otherwise `findViewById()` can " +
- "return an unexpected view.",
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- DuplicateIdDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** The main issue discovered by this detector */
- public static final Issue CROSS_LAYOUT = Issue.create(
- "DuplicateIncludedIds", //$NON-NLS-1$
- "Checks for duplicate ids across layouts that are combined with include tags",
- "It's okay for two independent layouts to use the same ids. However, if " +
- "layouts are combined with include tags, then the id's need to be unique " +
- "within any chain of included layouts, or `Activity#findViewById()` can " +
- "return an unexpected view.",
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- DuplicateIdDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Constructs a duplicate id check */
- public DuplicateIdDetector() {
- }
-
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.MENU;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_ID);
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(VIEW_INCLUDE);
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mIds = new HashSet<String>();
- }
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- if (context.getPhase() == 1) {
- // Store this layout's set of ids for full project analysis in afterCheckProject
- mFileToIds.put(context.file, mIds);
-
- mIds = null;
- }
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mFileToIds = new HashMap<File, Set<String>>();
- mIncludes = new HashMap<File, List<String>>();
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- // Look for duplicates
- if (!mIncludes.isEmpty()) {
- // Traverse all the include chains and ensure that there are no duplicates
- // across.
- if (context.isEnabled(CROSS_LAYOUT)
- && context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
- IncludeGraph graph = new IncludeGraph(context);
- graph.check();
- }
- }
- } else {
- assert context.getPhase() == 2;
-
- if (mErrors != null) {
- for (Occurrence occurrence : mErrors) {
- //assert location != null : occurrence;
- Location location = occurrence.location;
- if (location == null) {
- location = Location.create(occurrence.file);
- } else {
- Object clientData = location.getClientData();
- if (clientData instanceof Node) {
- Node node = (Node) clientData;
- if (context.getDriver().isSuppressed(CROSS_LAYOUT, node)) {
- continue;
- }
- }
- }
-
- List<Occurrence> sorted = new ArrayList<Occurrence>();
- Occurrence curr = occurrence.next;
- while (curr != null) {
- sorted.add(curr);
- curr = curr.next;
- }
- Collections.sort(sorted);
- Location prev = location;
- for (Occurrence o : sorted) {
- if (o.location != null) {
- prev.setSecondary(o.location);
- prev = o.location;
- }
- }
-
- context.report(CROSS_LAYOUT, location, occurrence.message, null);
- }
- }
- }
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- // Record include graph such that we can look for inter-layout duplicates after the
- // project has been fully checked
-
- String layout = element.getAttribute(ATTR_LAYOUT); // NOTE: Not in android: namespace
- if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { // Ignore @android:layout/ layouts
- layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length());
-
- if (context.getPhase() == 1) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- List<String> to = mIncludes.get(context.file);
- if (to == null) {
- to = new ArrayList<String>();
- mIncludes.put(context.file, to);
- }
- to.add(layout);
- } else {
- assert context.getPhase() == 2;
-
- Collection<Multimap<String, Occurrence>> maps = mLocations.get(context.file);
- if (maps != null && !maps.isEmpty()) {
- for (Multimap<String, Occurrence> map : maps) {
- if (!maps.isEmpty()) {
- Collection<Occurrence> occurrences = map.get(layout);
- if (occurrences != null && !occurrences.isEmpty()) {
- for (Occurrence occurrence : occurrences) {
- Location location = context.getLocation(element);
- location.setClientData(element);
- location.setMessage(occurrence.message);
- location.setSecondary(occurrence.location);
- occurrence.location = location;
- }
- }
- }
- }
- }
- }
- }
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- assert attribute.getName().equals(ATTR_ID) || attribute.getLocalName().equals(ATTR_ID);
- String id = attribute.getValue();
- if (context.getPhase() == 1) {
- if (mIds.contains(id)) {
- Location location = context.getLocation(attribute);
-
- Attr first = findIdAttribute(attribute.getOwnerDocument(), id);
- if (first != null && first != attribute) {
- Location secondLocation = context.getLocation(first);
- secondLocation.setMessage(String.format("%1$s originally defined here", id));
- location.setSecondary(secondLocation);
- }
-
- context.report(WITHIN_LAYOUT, attribute, location,
- String.format("Duplicate id %1$s, already defined earlier in this layout",
- id), null);
- } else if (id.startsWith(NEW_ID_PREFIX)) {
- // Skip id's on include tags
- if (attribute.getOwnerElement().getTagName().equals(VIEW_INCLUDE)) {
- return;
- }
-
- mIds.add(id);
- }
- } else {
- Collection<Multimap<String, Occurrence>> maps = mLocations.get(context.file);
- if (maps != null && !maps.isEmpty()) {
- for (Multimap<String, Occurrence> map : maps) {
- if (!maps.isEmpty()) {
- Collection<Occurrence> occurrences = map.get(id);
- if (occurrences != null && !occurrences.isEmpty()) {
- for (Occurrence occurrence : occurrences) {
- if (context.getDriver().isSuppressed(CROSS_LAYOUT, attribute)) {
- return;
- }
- Location location = context.getLocation(attribute);
- location.setClientData(attribute);
- location.setMessage(occurrence.message);
- location.setSecondary(occurrence.location);
- occurrence.location = location;
- }
- }
- }
- }
- }
- }
- }
-
- /** Find the first id attribute with the given value below the given node */
- private static Attr findIdAttribute(Node node, String targetValue) {
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- Attr attribute = ((Element) node).getAttributeNodeNS(ANDROID_URI, ATTR_ID);
- if (attribute != null && attribute.getValue().equals(targetValue)) {
- return attribute;
- }
- }
-
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- Attr result = findIdAttribute(child, targetValue);
- if (result != null) {
- return result;
- }
- }
-
- return null;
- }
-
- /** Include Graph Node */
- private static class Layout {
- private final File mFile;
- private final Set<String> mIds;
- private List<Layout> mIncludes;
- private List<Layout> mIncludedBy;
-
- Layout(File file, Set<String> ids) {
- mFile = file;
- mIds = ids;
- }
-
- Set<String> getIds() {
- return mIds;
- }
-
- String getLayoutName() {
- return LintUtils.getLayoutName(mFile);
- }
-
- String getDisplayName() {
- return mFile.getParentFile().getName() + File.separator + mFile.getName();
- }
-
- void include(Layout target) {
- if (mIncludes == null) {
- mIncludes = new ArrayList<Layout>();
- }
- mIncludes.add(target);
-
- if (target.mIncludedBy == null) {
- target.mIncludedBy = new ArrayList<Layout>();
- }
- target.mIncludedBy.add(this);
- }
-
- boolean isIncluded() {
- return mIncludedBy != null && !mIncludedBy.isEmpty();
- }
-
- File getFile() {
- return mFile;
- }
-
- List<Layout> getIncludes() {
- return mIncludes;
- }
-
- @Override
- public String toString() {
- return getDisplayName();
- }
- }
-
- private class IncludeGraph {
- private final Context mContext;
- private final Map<File, Layout> mFileToLayout;
-
- public IncludeGraph(Context context) {
- mContext = context;
-
- // Produce a DAG of the files to be included, and compute edges to all eligible
- // includes.
- // Then visit the DAG and whenever you find a duplicate emit a warning about the
- // include path which reached it.
- mFileToLayout = new HashMap<File, Layout>(2 * mIncludes.size());
- for (File file : mIncludes.keySet()) {
- if (!mFileToLayout.containsKey(file)) {
- mFileToLayout.put(file, new Layout(file, mFileToIds.get(file)));
- }
- }
- for (File file : mFileToIds.keySet()) {
- Set<String> ids = mFileToIds.get(file);
- if (ids != null && !ids.isEmpty()) {
- if (!mFileToLayout.containsKey(file)) {
- mFileToLayout.put(file, new Layout(file, ids));
- }
- }
- }
- Multimap<String, Layout> nameToLayout =
- ArrayListMultimap.create(mFileToLayout.size(), 4);
- for (File file : mFileToLayout.keySet()) {
- String name = LintUtils.getLayoutName(file);
- nameToLayout.put(name, mFileToLayout.get(file));
- }
-
- // Build up the DAG
- for (File file : mIncludes.keySet()) {
- Layout from = mFileToLayout.get(file);
- assert from != null : file;
-
- List<String> includedLayouts = mIncludes.get(file);
- for (String name : includedLayouts) {
- Collection<Layout> layouts = nameToLayout.get(name);
- if (layouts != null && !layouts.isEmpty()) {
- if (layouts.size() == 1) {
- from.include(layouts.iterator().next());
- } else {
- // See if we have an obvious match
- File folder = from.getFile().getParentFile();
- File candidate = new File(folder, name + DOT_XML);
- Layout candidateLayout = mFileToLayout.get(candidate);
- if (candidateLayout != null) {
- from.include(candidateLayout);
- } else if (mFileToIds.containsKey(candidate)) {
- // We had an entry in mFileToIds, but not a layout: this
- // means that the file exists, but had no includes or ids.
- // This can't be a valid match: there is a layout that we know
- // the include will pick, but it has no includes (to other layouts)
- // and no ids, so no need to look at it
- continue;
- } else {
- for (Layout to : layouts) {
- // Decide if the two targets are compatible
- if (isCompatible(from, to)) {
- from.include(to);
- }
- }
- }
- }
- } else {
- // The layout is including some layout which has no ids or other includes
- // so it's not relevant for a duplicate id search
- continue;
- }
- }
- }
- }
-
- /** Determine whether two layouts are compatible. They are not if they (for example)
- * specify conflicting qualifiers such as {@code -land} and {@code -port}.
- * @param from the include from
- * @param to the include to
- * @return true if the two are compatible */
- boolean isCompatible(Layout from, Layout to) {
- File fromFolder = from.mFile.getParentFile();
- File toFolder = to.mFile.getParentFile();
- if (fromFolder.equals(toFolder)) {
- return true;
- }
-
- String[] fromQualifiers = fromFolder.getName().split("-"); //$NON-NLS-1$
- String[] toQualifiers = toFolder.getName().split("-"); //$NON-NLS-1$
-
- if (isPortrait(fromQualifiers) != isPortrait(toQualifiers)) {
- return false;
- }
-
- return true;
- }
-
- private boolean isPortrait(String[] qualifiers) {
- for (String qualifier : qualifiers) {
- if (qualifier.equals("port")) { //$NON-NLS-1$
- return true;
- } else if (qualifier.equals("land")) { //$NON-NLS-1$
- return false;
- }
- }
-
- return true; // it's the default
- }
-
- public void check() {
- // Visit the DAG, looking for conflicts
- for (Layout layout : mFileToLayout.values()) {
- if (!layout.isIncluded()) { // Only check from "root" nodes
- Deque<Layout> stack = new ArrayDeque<Layout>();
- getIds(layout, stack, new HashSet<Layout>());
- }
- }
- }
-
- /**
- * Computes the cumulative set of ids used in a given layout. We can't
- * just depth-first-search the graph and check the set of ids
- * encountered along the way, because we need to detect when multiple
- * includes contribute the same ids. For example, if a file is included
- * more than once, that would result in duplicates.
- */
- private Set<String> getIds(Layout layout, Deque<Layout> stack, Set<Layout> seen) {
- seen.add(layout);
-
- Set<String> layoutIds = layout.getIds();
- List<Layout> includes = layout.getIncludes();
- if (includes != null) {
- Set<String> ids = new HashSet<String>();
- if (layoutIds != null) {
- ids.addAll(layoutIds);
- }
-
- stack.push(layout);
-
- Multimap<String, Set<String>> nameToIds =
- ArrayListMultimap.create(includes.size(), 4);
-
- for (Layout included : includes) {
- if (seen.contains(included)) {
- continue;
- }
- Set<String> includedIds = getIds(included, stack, seen);
- if (includedIds != null) {
- String layoutName = included.getLayoutName();
-
- idCheck:
- for (String id : includedIds) {
- if (ids.contains(id)) {
- Collection<Set<String>> idSets = nameToIds.get(layoutName);
- if (idSets != null) {
- for (Set<String> siblingIds : idSets) {
- if (siblingIds.contains(id)) {
- // The id reference was added by a sibling,
- // so no need to complain (again)
- continue idCheck;
- }
- }
- }
-
- // Duplicate! Record location request for new phase.
- if (mLocations == null) {
- mErrors = new ArrayList<Occurrence>();
- mLocations = ArrayListMultimap.create();
- mContext.getDriver().requestRepeat(DuplicateIdDetector.this,
- Scope.ALL_RESOURCES_SCOPE);
- }
-
- Map<Layout, Occurrence> occurrences =
- new HashMap<Layout, Occurrence>();
- findId(layout, id, new ArrayDeque<Layout>(), occurrences,
- new HashSet<Layout>());
- assert occurrences.size() >= 2;
-
- // Stash a request to find the given include
- Collection<Occurrence> values = occurrences.values();
- List<Occurrence> sorted = new ArrayList<Occurrence>(values);
- Collections.sort(sorted);
- String msg = String.format(
- "Duplicate id %1$s, defined or included multiple " +
- "times in %2$s: %3$s",
- id, layout.getDisplayName(),
- sorted.toString());
-
- // Store location request for the <include> tag
- Occurrence primary = new Occurrence(layout.getFile(), msg, null);
- Multimap<String, Occurrence> m = ArrayListMultimap.create();
- m.put(layoutName, primary);
- mLocations.put(layout.getFile(), m);
- mErrors.add(primary);
-
- Occurrence prev = primary;
-
- // Now store all the included occurrences of the id
- for (Occurrence occurrence : values) {
- if (occurrence.file.equals(layout.getFile())) {
- occurrence.message = "Defined here";
- } else {
- occurrence.message = String.format(
- "Defined here, included via %1$s",
- occurrence.includePath);
- }
-
- m = ArrayListMultimap.create();
- m.put(id, occurrence);
- mLocations.put(occurrence.file, m);
-
- // Link locations together
- prev.next = occurrence;
- prev = occurrence;
- }
- }
- ids.add(id);
- }
-
- // Store these ids such that on a conflict, we can tell when
- // an id was added by a single variation of this file
- nameToIds.put(layoutName, includedIds);
- }
- }
- Layout visited = stack.pop();
- assert visited == layout;
- return ids;
- } else {
- return layoutIds;
- }
- }
-
- private void findId(Layout layout, String id, Deque<Layout> stack,
- Map<Layout, Occurrence> occurrences, Set<Layout> seen) {
- seen.add(layout);
-
- Set<String> layoutIds = layout.getIds();
- if (layoutIds != null && layoutIds.contains(id)) {
- StringBuilder path = new StringBuilder(80);
-
- if (!stack.isEmpty()) {
- Iterator<Layout> iterator = stack.descendingIterator();
- while (iterator.hasNext()) {
- path.append(iterator.next().getDisplayName());
- path.append(" => ");
- }
- }
- path.append(layout.getDisplayName());
- path.append(" defines ");
- path.append(id);
-
- assert occurrences.get(layout) == null : id + ',' + layout;
- occurrences.put(layout, new Occurrence(layout.getFile(), null, path.toString()));
- }
-
- List<Layout> includes = layout.getIncludes();
- if (includes != null) {
- stack.push(layout);
- for (Layout included : includes) {
- if (!seen.contains(included)) {
- findId(included, id, stack, occurrences, seen);
- }
- }
- Layout visited = stack.pop();
- assert visited == layout;
- }
- }
- }
-
- private static class Occurrence implements Comparable<Occurrence> {
- public final File file;
- public final String includePath;
- public Occurrence next;
- public Location location;
- public String message;
-
- public Occurrence(File file, String message, String includePath) {
- this.file = file;
- this.message = message;
- this.includePath = includePath;
- }
-
- @Override
- public String toString() {
- return includePath != null ? includePath : message;
- }
-
- @Override
- public int compareTo(Occurrence other) {
- // First sort by length, then sort by name
- int delta = toString().length() - other.toString().length();
- if (delta != 0) {
- return delta;
- }
-
- return toString().compareTo(other.toString());
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
deleted file mode 100644
index 004303c..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/DuplicateResourceDetector.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.TAG_ITEM;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * This detector identifies cases where a resource is defined multiple times in the
- * same resource folder
- */
-public class DuplicateResourceDetector extends ResourceXmlDetector {
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "DuplicateDefinition", //$NON-NLS-1$
- "Discovers duplicate definitions of resources",
-
- "You can define a resource multiple times in different resource folders; that's how " +
- "string translations are done, for example. However, defining the same resource " +
- "more than once in the same resource folder is likely an error, for example " +
- "attempting to add a new resource without realizing that the name is already used, " +
- "and so on.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- DuplicateResourceDetector.class,
- Scope.ALL_RESOURCES_SCOPE).addAnalysisScope(Scope.RESOURCE_FILE_SCOPE);
-
- private static final String PRODUCT = "product"; //$NON-NLS-1$
- private Map<ResourceType, Set<String>> mTypeMap;
- private Map<ResourceType, List<Pair<String, Location.Handle>>> mLocations;
- private File mParent;
-
- /** Constructs a new {@link DuplicateResourceDetector} */
- public DuplicateResourceDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- @Nullable
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_NAME);
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- File parent = context.file.getParentFile();
- if (!parent.equals(mParent)) {
- mParent = parent;
- mTypeMap = Maps.newEnumMap(ResourceType.class);
- mLocations = Maps.newEnumMap(ResourceType.class);
- }
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- Element element = attribute.getOwnerElement();
-
- if (element.hasAttribute(PRODUCT)) {
- return;
- }
-
- String tag = element.getTagName();
- String typeString = tag;
- if (tag.equals(TAG_ITEM)) {
- typeString = element.getAttribute(ATTR_TYPE);
- }
- ResourceType type = ResourceType.getEnum(typeString);
- if (type == null) {
- return;
- }
-
- if (type == ResourceType.ATTR
- && element.getParentNode().getNodeName().equals(
- ResourceType.DECLARE_STYLEABLE.getName())) {
- return;
- }
-
- Set<String> names = mTypeMap.get(type);
- if (names == null) {
- names = Sets.newHashSetWithExpectedSize(40);
- mTypeMap.put(type, names);
- }
-
- String name = attribute.getValue();
- if (names.contains(name)) {
- String message = String.format("%1$s has already been defined in this folder",
- name);
- Location location = context.getLocation(attribute);
- List<Pair<String, Handle>> list = mLocations.get(type);
- for (Pair<String, Handle> pair : list) {
- if (name.equals(pair.getFirst())) {
- Location secondary = pair.getSecond().resolve();
- secondary.setMessage("Previously defined here");
- location.setSecondary(secondary);
- }
- }
- context.report(ISSUE, attribute, location, message, null);
- } else {
- names.add(name);
- List<Pair<String, Handle>> list = mLocations.get(type);
- if (list == null) {
- list = Lists.newArrayList();
- mLocations.put(type, list);
- }
- Location.Handle handle = context.parser.createLocationHandle(context, attribute);
- list.add(Pair.of(name, handle));
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java
deleted file mode 100644
index e0781db..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ExtraTextDetector.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.DefaultPosition;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Position;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-/**
- * Check which looks for invalid resources. Aapt already performs some validation,
- * such as making sure that resource references point to resources that exist, but this
- * detector looks for additional issues.
- */
-public class ExtraTextDetector extends ResourceXmlDetector {
- private boolean mFoundText;
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ExtraText", //$NON-NLS-1$
- "Looks for extraneous text in layout files",
- "Layout resource files should only contain elements and attributes. Any XML " +
- "text content found in the file is likely accidental (and potentially " +
- "dangerous if the text resembles XML and the developer believes the text " +
- "to be functional)",
- Category.CORRECTNESS,
- 3,
- Severity.WARNING,
- ExtraTextDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new detector */
- public ExtraTextDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT
- || folderType == ResourceFolderType.MENU
- || folderType == ResourceFolderType.ANIM
- || folderType == ResourceFolderType.ANIMATOR
- || folderType == ResourceFolderType.DRAWABLE
- || folderType == ResourceFolderType.COLOR;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- mFoundText = false;
- visitNode(context, document);
- }
-
- private void visitNode(XmlContext context, Node node) {
- short nodeType = node.getNodeType();
- if (nodeType == Node.TEXT_NODE && !mFoundText) {
- String text = node.getNodeValue();
- for (int i = 0, n = text.length(); i < n; i++) {
- char c = text.charAt(i);
- if (!Character.isWhitespace(c)) {
- String snippet = text.trim();
- int maxLength = 100;
- if (snippet.length() > maxLength) {
- snippet = snippet.substring(0, maxLength) + "...";
- }
- Location location = context.getLocation(node);
- if (i > 0) {
- // Adjust the error position to point to the beginning of
- // the text rather than the beginning of the text node
- // (which is often the newline at the end of the previous
- // line and the indentation)
- Position start = location.getStart();
- if (start != null) {
- int line = start.getLine();
- int column = start.getColumn();
- int offset = start.getOffset();
-
- for (int j = 0; j < i; j++) {
- offset++;
-
- if (text.charAt(j) == '\n') {
- if (line != -1) {
- line++;
- }
- if (column != -1) {
- column = 0;
- }
- } else if (column != -1) {
- column++;
- }
- }
-
- start = new DefaultPosition(line, column, offset);
- location = Location.create(context.file, start, location.getEnd());
- }
- }
- context.report(ISSUE, node, location,
- String.format("Unexpected text found in layout file: \"%1$s\"",
- snippet), null);
- mFoundText = true;
- break;
- }
- }
- }
-
- // Visit children
- NodeList childNodes = node.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- visitNode(context, child);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java
deleted file mode 100644
index 04841ab..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FieldGetterDetector.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-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 com.google.common.collect.Maps;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.VarInsnNode;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Looks for getter calls within the same class that could be replaced by
- * direct field references instead.
- */
-public class FieldGetterDetector extends Detector implements Detector.ClassScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "FieldGetter", //$NON-NLS-1$
- "Suggests replacing uses of getters with direct field access within a class",
-
- "Accessing a field within the class that defines a getter for that field is " +
- "at least 3 times faster than calling the getter. For simple getters that do " +
- "nothing other than return the field, you might want to just reference the " +
- "local field directly instead.\n" +
- "\n" +
- "NOTE: As of Android 2.3 (Gingerbread), this optimization is performed " +
- "automatically by Dalvik, so there is no need to change your code; this is " +
- "only relevant if you are targeting older versions of Android.",
-
- Category.PERFORMANCE,
- 4,
- Severity.WARNING,
- FieldGetterDetector.class,
- Scope.CLASS_FILE_SCOPE).
- // This is a micro-optimization: not enabled by default
- setEnabledByDefault(false).setMoreInfo(
- "http://developer.android.com/guide/practices/design/performance.html#internal_get_set"); //$NON-NLS-1$
- private ArrayList<Entry> mPendingCalls;
-
- /** Constructs a new {@link FieldGetterDetector} check */
- public FieldGetterDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public int[] getApplicableAsmNodeTypes() {
- return new int[] { AbstractInsnNode.METHOD_INSN };
- }
-
- @Override
- public void checkInstruction(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull AbstractInsnNode instruction) {
- // As of Gingerbread/API 9, Dalvik performs this optimization automatically
- if (context.getProject().getMinSdk() >= 9) {
- return;
- }
-
- if ((method.access & Opcodes.ACC_STATIC) != 0) {
- // Not an instance method
- return;
- }
-
- if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) {
- return;
- }
-
- MethodInsnNode node = (MethodInsnNode) instruction;
- String name = node.name;
- String owner = node.owner;
-
- AbstractInsnNode prev = LintUtils.getPrevInstruction(instruction);
- if (prev == null || prev.getOpcode() != Opcodes.ALOAD) {
- return;
- }
- VarInsnNode prevVar = (VarInsnNode) prev;
- if (prevVar.var != 0) { // Not on "this", variable 0 in instance methods?
- return;
- }
-
- if (((name.startsWith("get") && name.length() > 3 //$NON-NLS-1$
- && Character.isUpperCase(name.charAt(3)))
- || (name.startsWith("is") && name.length() > 2 //$NON-NLS-1$
- && Character.isUpperCase(name.charAt(2))))
- && owner.equals(classNode.name)) {
- // Calling a potential getter method on self. We now need to
- // investigate the method body of the getter call and make sure
- // it's really a plain getter, not just a method which happens
- // to have a method name like a getter, or a method which not
- // only returns a field but possibly computes it or performs
- // other initialization or side effects. This is done in a
- // second pass over the bytecode, initiated by the finish()
- // method.
- if (mPendingCalls == null) {
- mPendingCalls = new ArrayList<Entry>();
- }
-
- mPendingCalls.add(new Entry(name, node, method));
- }
-
- super.checkInstruction(context, classNode, method, instruction);
- }
-
- @Override
- public void afterCheckFile(@NonNull Context c) {
- ClassContext context = (ClassContext) c;
-
- if (mPendingCalls != null) {
- Set<String> names = new HashSet<String>(mPendingCalls.size());
- for (Entry entry : mPendingCalls) {
- names.add(entry.name);
- }
-
- Map<String, String> getters = checkMethods(context.getClassNode(), names);
- if (!getters.isEmpty()) {
- for (String getter : getters.keySet()) {
- for (Entry entry : mPendingCalls) {
- String name = entry.name;
- // There can be more than one reference to the same name:
- // one for each call site
- if (name.equals(getter)) {
- Location location = context.getLocation(entry.call);
- String fieldName = getters.get(getter);
- if (fieldName == null) {
- fieldName = "";
- }
- context.report(ISSUE, entry.method, entry.call, location,
- String.format(
- "Calling getter method %1$s() on self is " +
- "slower than field access (%2$s)", getter, fieldName), fieldName);
- }
- }
- }
- }
- }
-
- mPendingCalls = null;
- }
-
- // Holder class for getters to be checked
- private static class Entry {
- public final String name;
- public final MethodNode method;
- public final MethodInsnNode call;
-
- public Entry(String name, MethodInsnNode call, MethodNode method) {
- super();
- this.name = name;
- this.call = call;
- this.method = method;
- }
- }
-
- // Validate that these getter methods are really just simple field getters
- // like these int and String getters:
- // public int getFoo();
- // Code:
- // 0: aload_0
- // 1: getfield #21; //Field mFoo:I
- // 4: ireturn
- //
- // public java.lang.String getBar();
- // Code:
- // 0: aload_0
- // 1: getfield #25; //Field mBar:Ljava/lang/String;
- // 4: areturn
- //
- // Returns a map of valid getters as keys, and if the field name is found, the field name
- // for each getter as its value.
- private static Map<String, String> checkMethods(ClassNode classNode, Set<String> names) {
- Map<String, String> validGetters = Maps.newHashMap();
- @SuppressWarnings("rawtypes")
- List methods = classNode.methods;
- String fieldName = null;
- checkMethod:
- for (Object methodObject : methods) {
- MethodNode method = (MethodNode) methodObject;
- if (names.contains(method.name)
- && method.desc.startsWith("()")) { //$NON-NLS-1$ // (): No arguments
- InsnList instructions = method.instructions;
- int mState = 1;
- for (AbstractInsnNode curr = instructions.getFirst();
- curr != null;
- curr = curr.getNext()) {
- switch (curr.getOpcode()) {
- case -1:
- // Skip label and line number nodes
- continue;
- case Opcodes.ALOAD:
- if (mState == 1) {
- fieldName = null;
- mState = 2;
- } else {
- continue checkMethod;
- }
- break;
- case Opcodes.GETFIELD:
- if (mState == 2) {
- FieldInsnNode field = (FieldInsnNode) curr;
- fieldName = field.name;
- mState = 3;
- } else {
- continue checkMethod;
- }
- break;
- case Opcodes.ARETURN:
- case Opcodes.FRETURN:
- case Opcodes.IRETURN:
- case Opcodes.DRETURN:
- case Opcodes.LRETURN:
- case Opcodes.RETURN:
- if (mState == 3) {
- validGetters.put(method.name, fieldName);
- }
- continue checkMethod;
- default:
- continue checkMethod;
- }
- }
- }
- }
-
- return validGetters;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
deleted file mode 100644
index f6ebcd6..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/FragmentDetector.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.FRAGMENT;
-import static com.android.SdkConstants.FRAGMENT_V4;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-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 org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.List;
-
-/**
- * Checks that Fragment subclasses can be instantiated via
- * {link {@link Class#newInstance()}}: the class is public, static, and has
- * a public null constructor.
- * <p>
- * This helps track down issues like
- * http://stackoverflow.com/questions/8058809/fragment-activity-crashes-on-screen-rotate
- * (and countless duplicates)
- */
-public class FragmentDetector extends Detector implements ClassScanner {
- private static final String FRAGMENT_NAME_SUFFIX = "Fragment"; //$NON-NLS-1$
-
- /** Are fragment subclasses instantiatable? */
- public static final Issue ISSUE = Issue.create(
- "ValidFragment", //$NON-NLS-1$
- "Ensures that Fragment subclasses can be instantiated",
-
- "From the Fragment documentation:\n" +
- "*Every* fragment must have an empty constructor, so it can be instantiated when " +
- "restoring its activity's state. It is strongly recommended that subclasses do not " +
- "have other constructors with parameters, since these constructors will not be " +
- "called when the fragment is re-instantiated; instead, arguments can be supplied " +
- "by the caller with `setArguments(Bundle)` and later retrieved by the Fragment " +
- "with `getArguments()`.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- FragmentDetector.class,
- Scope.CLASS_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/reference/android/app/Fragment.html#Fragment()"); //$NON-NLS-1$
-
-
- /** Constructs a new {@link FragmentDetector} */
- public FragmentDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
- // Ignore abstract classes since they are clearly (and by definition) not intended to
- // be instantiated. We're looking for accidental non-static or missing constructor
- // scenarios here.
- return;
- }
-
- LintDriver driver = context.getDriver();
-
- if (!(driver.isSubclassOf(classNode, FRAGMENT)
- || driver.isSubclassOf(classNode, FRAGMENT_V4))) {
- if (!context.getScope().contains(Scope.ALL_JAVA_FILES)) {
- // Single file checking: Just check that it looks like a fragment class
- // (since we don't have a full superclass map)
- if (!classNode.name.endsWith(FRAGMENT_NAME_SUFFIX) ||
- classNode.superName == null) {
- return;
- }
- } else {
- return;
- }
- }
-
- if ((classNode.access & Opcodes.ACC_PUBLIC) == 0) {
- context.report(ISSUE, context.getLocation(classNode), String.format(
- "This fragment class should be public (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- return;
- }
-
- if (classNode.name.indexOf('$') != -1 && !LintUtils.isStaticInnerClass(classNode)) {
- context.report(ISSUE, context.getLocation(classNode), String.format(
- "This fragment inner class should be static (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- return;
- }
-
- boolean hasDefaultConstructor = false;
- @SuppressWarnings("rawtypes") // ASM API
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(CONSTRUCTOR_NAME)) {
- if (method.desc.equals("()V")) { //$NON-NLS-1$
- // The constructor must be public
- if ((method.access & Opcodes.ACC_PUBLIC) != 0) {
- hasDefaultConstructor = true;
- } else {
- context.report(ISSUE, context.getLocation(method, classNode),
- "The default constructor must be public",
- null);
- // Also mark that we have a constructor so we don't complain again
- // below since we've already emitted a more specific error related
- // to the default constructor
- hasDefaultConstructor = true;
- }
- } else if (!method.desc.contains("()")) { //$NON-NLS-1$
- context.report(ISSUE, context.getLocation(method, classNode),
- // TODO: Use separate issue for this which isn't an error
- "Avoid non-default constructors in fragments: use a default constructor " +
- "plus Fragment#setArguments(Bundle) instead",
- null);
- }
- }
- }
-
- if (!hasDefaultConstructor) {
- context.report(ISSUE, context.getLocation(classNode), String.format(
- "This fragment should provide a default constructor (a public " +
- "constructor with no arguments) (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
deleted file mode 100644
index c348502..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/GridLayoutDetector.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_COLUMN_COUNT;
-import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
-import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
-import static com.android.SdkConstants.ATTR_ROW_COUNT;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check which looks for potential errors in declarations of GridLayouts, such as specifying
- * row/column numbers outside the declared dimensions of the grid.
- */
-public class GridLayoutDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "GridLayout", //$NON-NLS-1$
- "Checks for potential GridLayout errors like declaring rows and columns outside " +
- "the declared grid dimensions",
- "Declaring a layout_row or layout_column that falls outside the declared size " +
- "of a GridLayout's `rowCount` or `columnCount` is usually an unintentional error.",
- Category.CORRECTNESS,
- 4,
- Severity.FATAL,
- GridLayoutDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link GridLayoutDetector} check */
- public GridLayoutDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(
- "GridLayout" //$NON-NLS-1$
- );
- }
-
- private static int getInt(Element element, String attribute, int defaultValue) {
- String valueString = element.getAttributeNS(ANDROID_URI, attribute);
- if (valueString != null && !valueString.isEmpty()) {
- try {
- return Integer.decode(valueString);
- } catch (NumberFormatException nufe) {
- // Ignore - error in user's XML
- }
- }
-
- return defaultValue;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- int declaredRowCount = getInt(element, ATTR_ROW_COUNT, -1);
- int declaredColumnCount = getInt(element, ATTR_COLUMN_COUNT, -1);
-
- if (declaredColumnCount != -1 || declaredRowCount != -1) {
- for (Element child : LintUtils.getChildren(element)) {
- if (declaredColumnCount != -1) {
- int column = getInt(child, ATTR_LAYOUT_COLUMN, -1);
- if (column >= declaredColumnCount) {
- Attr node = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_COLUMN);
- context.report(ISSUE, node, context.getLocation(node),
- String.format("Column attribute (%1$d) exceeds declared grid column count (%2$d)",
- column, declaredColumnCount), null);
- }
- }
- if (declaredRowCount != -1) {
- int row = getInt(child, ATTR_LAYOUT_ROW, -1);
- if (row > declaredRowCount) {
- Attr node = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_ROW);
- context.report(ISSUE, node, context.getLocation(node),
- String.format("Row attribute (%1$d) exceeds declared grid row count (%2$d)",
- row, declaredRowCount), null);
- }
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
deleted file mode 100644
index cfe8f0b..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-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 org.objectweb.asm.tree.ClassNode;
-
-/**
- * Checks that Handler implementations are top level classes or static.
- * See the corresponding check in the android.os.Handler source code.
- */
-public class HandlerDetector extends Detector implements ClassScanner {
-
- /** Potentially leaking handlers */
- public static final Issue ISSUE = Issue.create(
- "HandlerLeak", //$NON-NLS-1$
- "Ensures that Handler classes do not hold on to a reference to an outer class",
-
- "In Android, Handler classes should be static or leaks might occur. " +
- "Messages enqueued on the application thread's MessageQueue also retain their " +
- "target Handler. If the Handler is an inner class, its outer class will be " +
- "retained as well. To avoid leaking the outer class, declare the Handler as a " +
- "static nested class with a WeakReference to its outer class.",
-
- Category.PERFORMANCE,
- 4,
- Severity.WARNING,
- HandlerDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- /** Constructs a new {@link HandlerDetector} */
- public HandlerDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if (classNode.name.indexOf('$') == -1) {
- return;
- }
-
- if (context.getDriver().isSubclassOf(classNode, "android/os/Handler") //$NON-NLS-1$
- && !LintUtils.isStaticInnerClass(classNode)) {
- Location location = context.getLocation(classNode);
- context.report(ISSUE, location, String.format(
- "This Handler class should be static or leaks might occur (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
deleted file mode 100644
index fd678ca..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_DEBUGGABLE;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Checks for hardcoded debug mode in manifest files
- */
-public class HardcodedDebugModeDetector extends Detector implements Detector.XmlScanner {
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "HardcodedDebugMode", //$NON-NLS-1$
- "Checks for hardcoded values of android:debuggable in the manifest",
-
- "It's best to leave out the `android:debuggable` attribute from the manifest. " +
- "If you do, then the tools will automatically insert `android:debuggable=true` when " +
- "building an APK to debug on an emulator or device. And when you perform a " +
- "release build, such as Exporting APK, it will automatically set it to `false`.\n" +
- "\n" +
- "If on the other hand you specify a specific value in the manifest file, then " +
- "the tools will always use it. This can lead to accidentally publishing " +
- "your app with debug information.",
-
- Category.SECURITY,
- 5,
- Severity.WARNING,
- HardcodedDebugModeDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Constructs a new {@link HardcodedDebugModeDetector} check */
- public HardcodedDebugModeDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- // ---- Implements Detector.XmlScanner ----
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singleton(ATTR_DEBUGGABLE);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (attribute.getNamespaceURI().equals(ANDROID_URI)) {
- //if (attribute.getOwnerElement().getTagName().equals(TAG_APPLICATION)) {
- context.report(ISSUE, attribute, context.getLocation(attribute),
- "Avoid hardcoding the debug mode; leaving it out allows debug and " +
- "release builds to automatically assign one", null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
deleted file mode 100644
index 11cc19d..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/HardcodedValuesDetector.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CONTENT_DESCRIPTION;
-import static com.android.SdkConstants.ATTR_HINT;
-import static com.android.SdkConstants.ATTR_LABEL;
-import static com.android.SdkConstants.ATTR_PROMPT;
-import static com.android.SdkConstants.ATTR_TEXT;
-import static com.android.SdkConstants.ATTR_TITLE;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Check which looks at the children of ScrollViews and ensures that they fill/match
- * the parent width instead of setting wrap_content.
- */
-public class HardcodedValuesDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "HardcodedText", //$NON-NLS-1$
- "Looks for hardcoded text attributes which should be converted to resource lookup",
- "Hardcoding text attributes directly in layout files is bad for several reasons:\n" +
- "\n" +
- "* When creating configuration variations (for example for landscape or portrait)" +
- "you have to repeat the actual text (and keep it up to date when making changes)\n" +
- "\n" +
- "* The application cannot be translated to other languages by just adding new " +
- "translations for existing string resources.\n" +
- "\n" +
- "In Eclipse there is a quickfix to automatically extract this hardcoded string into " +
- "a resource lookup.",
-
- Category.I18N,
- 5,
- Severity.WARNING,
- HardcodedValuesDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- // TODO: Add additional issues here, such as hardcoded colors, hardcoded sizes, etc
-
- /** Constructs a new {@link HardcodedValuesDetector} */
- public HardcodedValuesDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Arrays.asList(
- // Layouts
- ATTR_TEXT,
- ATTR_CONTENT_DESCRIPTION,
- ATTR_HINT,
- ATTR_LABEL,
- ATTR_PROMPT,
-
- // Menus
- ATTR_TITLE
- );
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.MENU;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String value = attribute.getValue();
- if (!value.isEmpty() && (value.charAt(0) != '@' && value.charAt(0) != '?')) {
- // Make sure this is really one of the android: attributes
- if (!ANDROID_URI.equals(attribute.getNamespaceURI())) {
- return;
- }
-
- context.report(ISSUE, attribute, context.getLocation(attribute),
- String.format("[I18N] Hardcoded string \"%1$s\", should use @string resource",
- value), null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
deleted file mode 100644
index 7f26c96..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/IconDetector.java
+++ /dev/null
@@ -1,1906 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ICON;
-import static com.android.SdkConstants.DOT_9PNG;
-import static com.android.SdkConstants.DOT_GIF;
-import static com.android.SdkConstants.DOT_JPEG;
-import static com.android.SdkConstants.DOT_JPG;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.DRAWABLE_FOLDER;
-import static com.android.SdkConstants.DRAWABLE_HDPI;
-import static com.android.SdkConstants.DRAWABLE_LDPI;
-import static com.android.SdkConstants.DRAWABLE_MDPI;
-import static com.android.SdkConstants.DRAWABLE_PREFIX;
-import static com.android.SdkConstants.DRAWABLE_XHDPI;
-import static com.android.SdkConstants.MENU_TYPE;
-import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.R_DRAWABLE_PREFIX;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Element;
-
-import java.awt.Dimension;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-import javax.imageio.ImageIO;
-import javax.imageio.ImageReader;
-import javax.imageio.stream.ImageInputStream;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.TypeReference;
-import lombok.ast.TypeReferencePart;
-import lombok.ast.VariableReference;
-
-/**
- * Checks for common icon problems, such as wrong icon sizes, placing icons in the
- * density independent drawable folder, etc.
- */
-public class IconDetector extends ResourceXmlDetector implements Detector.JavaScanner {
-
- private static final boolean INCLUDE_LDPI;
- static {
- boolean includeLdpi = false;
-
- String value = System.getenv("ANDROID_LINT_INCLUDE_LDPI"); //$NON-NLS-1$
- if (value != null) {
- includeLdpi = Boolean.valueOf(value);
- }
- INCLUDE_LDPI = includeLdpi;
- }
-
- /** Pattern for the expected density folders to be found in the project */
- private static final Pattern DENSITY_PATTERN = Pattern.compile(
- "^drawable-(nodpi|xhdpi|hdpi|mdpi" //$NON-NLS-1$
- + (INCLUDE_LDPI ? "|ldpi" : "") + ")$"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
- private static final String[] REQUIRED_DENSITIES = INCLUDE_LDPI
- ? new String[] { DRAWABLE_LDPI, DRAWABLE_MDPI, DRAWABLE_HDPI, DRAWABLE_XHDPI }
- : new String[] { DRAWABLE_MDPI, DRAWABLE_HDPI, DRAWABLE_XHDPI };
-
- private static final String[] DENSITY_QUALIFIERS =
- new String[] {
- "-ldpi", //$NON-NLS-1$
- "-mdpi", //$NON-NLS-1$
- "-hdpi", //$NON-NLS-1$
- "-xhdpi" //$NON-NLS-1$
- };
-
- /** Scope needed to detect the types of icons (which involves scanning .java files,
- * the manifest, menu files etc to see how icons are used
- */
- private static final EnumSet<Scope> ICON_TYPE_SCOPE = EnumSet.of(Scope.ALL_RESOURCE_FILES,
- Scope.JAVA_FILE, Scope.MANIFEST);
-
- /** Wrong icon size according to published conventions */
- public static final Issue ICON_EXPECTED_SIZE = Issue.create(
- "IconExpectedSize", //$NON-NLS-1$
- "Ensures that launcher icons, notification icons etc have the correct size",
- "There are predefined sizes (for each density) for launcher icons. You " +
- "should follow these conventions to make sure your icons fit in with the " +
- "overall look of the platform.",
- Category.ICONS,
- 5,
- Severity.WARNING,
- IconDetector.class,
- ICON_TYPE_SCOPE)
- // Still some potential false positives:
- .setEnabledByDefault(false)
- .setMoreInfo(
- "http://developer.android.com/design/style/iconography.html"); //$NON-NLS-1$
-
- /** Inconsistent dip size across densities */
- public static final Issue ICON_DIP_SIZE = Issue.create(
- "IconDipSize", //$NON-NLS-1$
- "Ensures that icons across densities provide roughly the same density-independent size",
- "Checks the all icons which are provided in multiple densities, all compute to " +
- "roughly the same density-independent pixel (`dip`) size. This catches errors where " +
- "images are either placed in the wrong folder, or icons are changed to new sizes " +
- "but some folders are forgotten.",
- Category.ICONS,
- 5,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Images in res/drawable folder */
- public static final Issue ICON_LOCATION = Issue.create(
- "IconLocation", //$NON-NLS-1$
- "Ensures that images are not defined in the density-independent drawable folder",
- "The res/drawable folder is intended for density-independent graphics such as " +
- "shapes defined in XML. For bitmaps, move it to `drawable-mdpi` and consider " +
- "providing higher and lower resolution versions in `drawable-ldpi`, `drawable-hdpi` " +
- "and `drawable-xhdpi`. If the icon *really* is density independent (for example " +
- "a solid color) you can place it in `drawable-nodpi`.",
- Category.ICONS,
- 5,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/practices/screens_support.html"); //$NON-NLS-1$
-
- /** Missing density versions of image */
- public static final Issue ICON_DENSITIES = Issue.create(
- "IconDensities", //$NON-NLS-1$
- "Ensures that icons provide custom versions for all supported densities",
- "Icons will look best if a custom version is provided for each of the " +
- "major screen density classes (low, medium, high, extra high). " +
- "This lint check identifies icons which do not have complete coverage " +
- "across the densities.\n" +
- "\n" +
- "Low density is not really used much anymore, so this check ignores " +
- "the ldpi density. To force lint to include it, set the environment " +
- "variable `ANDROID_LINT_INCLUDE_LDPI=true`. For more information on " +
- "current density usage, see " +
- "http://developer.android.com/resources/dashboard/screens.html",
- Category.ICONS,
- 4,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/practices/screens_support.html"); //$NON-NLS-1$
-
- /** Missing density folders */
- public static final Issue ICON_MISSING_FOLDER = Issue.create(
- "IconMissingDensityFolder", //$NON-NLS-1$
- "Ensures that all the density folders are present",
- "Icons will look best if a custom version is provided for each of the " +
- "major screen density classes (low, medium, high, extra high). " +
- "This lint check identifies folders which are missing, such as `drawable-hdpi`." +
- "\n" +
- "Low density is not really used much anymore, so this check ignores " +
- "the ldpi density. To force lint to include it, set the environment " +
- "variable `ANDROID_LINT_INCLUDE_LDPI=true`. For more information on " +
- "current density usage, see " +
- "http://developer.android.com/resources/dashboard/screens.html",
- Category.ICONS,
- 3,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/practices/screens_support.html"); //$NON-NLS-1$
-
- /** Using .gif bitmaps */
- public static final Issue GIF_USAGE = Issue.create(
- "GifUsage", //$NON-NLS-1$
- "Checks for images using the GIF file format which is discouraged",
- "The `.gif` file format is discouraged. Consider using `.png` (preferred) " +
- "or `.jpg` (acceptable) instead.",
- Category.ICONS,
- 5,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap"); //$NON-NLS-1$
-
- /** Duplicated icons across different names */
- public static final Issue DUPLICATES_NAMES = Issue.create(
- "IconDuplicates", //$NON-NLS-1$
- "Finds duplicated icons under different names",
- "If an icon is repeated under different names, you can consolidate and just " +
- "use one of the icons and delete the others to make your application smaller. " +
- "However, duplicated icons usually are not intentional and can sometimes point " +
- "to icons that were accidentally overwritten or accidentally not updated.",
- Category.ICONS,
- 3,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Duplicated contents across configurations for a given name */
- public static final Issue DUPLICATES_CONFIGURATIONS = Issue.create(
- "IconDuplicatesConfig", //$NON-NLS-1$
- "Finds icons that have identical bitmaps across various configuration parameters",
- "If an icon is provided under different configuration parameters such as " +
- "`drawable-hdpi` or `-v11`, they should typically be different. This detector " +
- "catches cases where the same icon is provided in different configuration folder " +
- "which is usually not intentional.",
- Category.ICONS,
- 5,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Icons appearing in both -nodpi and a -Ndpi folder */
- public static final Issue ICON_NODPI = Issue.create(
- "IconNoDpi", //$NON-NLS-1$
- "Finds icons that appear in both a -nodpi folder and a dpi folder",
- "Bitmaps that appear in `drawable-nodpi` folders will not be scaled by the " +
- "Android framework. If a drawable resource of the same name appears *both* in " +
- "a `-nodpi` folder as well as a dpi folder such as `drawable-hdpi`, then " +
- "the behavior is ambiguous and probably not intentional. Delete one or the " +
- "other, or use different names for the icons.",
- Category.ICONS,
- 7,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Icons appearing as both drawable xml files and bitmaps */
- public static final Issue ICON_XML_AND_PNG = Issue.create(
- "IconXmlAndPng", //$NON-NLS-1$
- "Finds icons that appear both as a drawable .xml file and as bitmaps",
- "If a drawable resource appears as an .xml file in the drawable/ folder, " +
- "it's usually not intentional for it to also appear as a bitmap using the " +
- "same name; generally you expect the drawable XML file to define states " +
- "and each state has a corresponding drawable bitmap.",
- Category.ICONS,
- 7,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Wrong filename according to the format */
- public static final Issue ICON_EXTENSION = Issue.create(
- "IconExtension", //$NON-NLS-1$
- "Checks that the icon file extension matches the actual image format in the file",
-
- "Ensures that icons have the correct file extension (e.g. a .png file is " +
- "really in the PNG format and not for example a GIF file named .png.)",
- Category.ICONS,
- 3,
- Severity.WARNING,
- IconDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Wrong filename according to the format */
- public static final Issue ICON_COLORS = Issue.create(
- "IconColors", //$NON-NLS-1$
- "Checks that icons follow the recommended visual style",
-
- "Notification icons and Action Bar icons should only white and shades of gray. " +
- "See the Android Design Guide for more details. " +
- "Note that the way Lint decides whether an icon is an action bar icon or " +
- "a notification icon is based on the filename prefix: `ic_menu_` for " +
- "action bar icons, `ic_stat_` for notification icons etc. These correspond " +
- "to the naming conventions documented in " +
- "http://developer.android.com/guide/practices/ui_guidelines/icon_design.html",
- Category.ICONS,
- 6,
- Severity.WARNING,
- IconDetector.class,
- ICON_TYPE_SCOPE).setMoreInfo(
- "http://developer.android.com/design/style/iconography.html"); //$NON-NLS-1$
-
- /** Wrong launcher icon shape */
- public static final Issue ICON_LAUNCHER_SHAPE = Issue.create(
- "IconLauncherShape", //$NON-NLS-1$
- "Checks that launcher icons follow the recommended visual style",
-
- "According to the Android Design Guide " +
- "(http://developer.android.com/design/style/iconography.html) " +
- "your launcher icons should \"use a distinct silhouette\", " +
- "a \"three-dimensional, front view, with a slight perspective as if viewed " +
- "from above, so that users perceive some depth.\"\n" +
- "\n" +
- "The unique silhouette implies that your launcher icon should not be a filled " +
- "square.",
- Category.ICONS,
- 6,
- Severity.WARNING,
- IconDetector.class,
- ICON_TYPE_SCOPE).setMoreInfo(
- "http://developer.android.com/design/style/iconography.html"); //$NON-NLS-1$
-
- /** Constructs a new {@link IconDetector} check */
- public IconDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- mLauncherIcons = null;
- mActionBarIcons = null;
- mNotificationIcons = null;
- }
-
- @Override
- public void afterCheckLibraryProject(@NonNull Context context) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- checkResourceFolder(context, context.getProject());
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- checkResourceFolder(context, context.getProject());
- }
-
- private void checkResourceFolder(Context context, @NonNull Project project) {
- List<File> resourceFolders = project.getResourceFolders();
- for (File res : resourceFolders) {
- File[] folders = res.listFiles();
- if (folders != null) {
- boolean checkFolders = context.isEnabled(ICON_DENSITIES)
- || context.isEnabled(ICON_MISSING_FOLDER)
- || context.isEnabled(ICON_NODPI)
- || context.isEnabled(ICON_XML_AND_PNG);
- boolean checkDipSizes = context.isEnabled(ICON_DIP_SIZE);
- boolean checkDuplicates = context.isEnabled(DUPLICATES_NAMES)
- || context.isEnabled(DUPLICATES_CONFIGURATIONS);
-
- Map<File, Dimension> pixelSizes = null;
- Map<File, Long> fileSizes = null;
- if (checkDipSizes || checkDuplicates) {
- pixelSizes = new HashMap<File, Dimension>();
- fileSizes = new HashMap<File, Long>();
- }
- Map<File, Set<String>> folderToNames = new HashMap<File, Set<String>>();
- Map<File, Set<String>> nonDpiFolderNames = new HashMap<File, Set<String>>();
- for (File folder : folders) {
- String folderName = folder.getName();
- if (folderName.startsWith(DRAWABLE_FOLDER)) {
- File[] files = folder.listFiles();
- if (files != null) {
- checkDrawableDir(context, folder, files, pixelSizes, fileSizes);
-
- if (checkFolders && DENSITY_PATTERN.matcher(folderName).matches()) {
- Set<String> names = new HashSet<String>(files.length);
- for (File f : files) {
- String name = f.getName();
- if (isDrawableFile(name)) {
- names.add(name);
- }
- }
- folderToNames.put(folder, names);
- } else if (checkFolders) {
- Set<String> names = new HashSet<String>(files.length);
- for (File f : files) {
- String name = f.getName();
- if (isDrawableFile(name)) {
- names.add(name);
- }
- }
- nonDpiFolderNames.put(folder, names);
- }
- }
- }
- }
-
- if (checkDipSizes) {
- checkDipSizes(context, pixelSizes);
- }
-
- if (checkDuplicates) {
- checkDuplicates(context, pixelSizes, fileSizes);
- }
-
- if (checkFolders && !folderToNames.isEmpty()) {
- checkDensities(context, res, folderToNames, nonDpiFolderNames);
- }
- }
- }
- }
-
- private static boolean isDrawableFile(String name) {
- // endsWith(name, DOT_PNG) is also true for endsWith(name, DOT_9PNG)
- return endsWith(name, DOT_PNG)|| endsWith(name, DOT_JPG) || endsWith(name, DOT_GIF)
- || endsWith(name, DOT_XML) || endsWith(name, DOT_JPEG);
- }
-
- // This method looks for duplicates in the assets. This uses two pieces of information
- // (file sizes and image dimensions) to quickly reject candidates, such that it only
- // needs to check actual file contents on a small subset of the available files.
- private static void checkDuplicates(Context context, Map<File, Dimension> pixelSizes,
- Map<File, Long> fileSizes) {
- Map<Long, Set<File>> sameSizes = new HashMap<Long, Set<File>>();
- Map<Long, File> seenSizes = new HashMap<Long, File>(fileSizes.size());
- for (Map.Entry<File, Long> entry : fileSizes.entrySet()) {
- File file = entry.getKey();
- Long size = entry.getValue();
- if (seenSizes.containsKey(size)) {
- Set<File> set = sameSizes.get(size);
- if (set == null) {
- set = new HashSet<File>();
- set.add(seenSizes.get(size));
- sameSizes.put(size, set);
- }
- set.add(file);
- } else {
- seenSizes.put(size, file);
- }
- }
-
- if (sameSizes.isEmpty()) {
- return;
- }
-
- // Now go through the files that have the same size and check to see if we can
- // split them apart based on image dimensions
- // Note: we may not have file sizes on all the icons; in particular,
- // we don't have file sizes for ninepatch files.
- Collection<Set<File>> candidateLists = sameSizes.values();
- for (Set<File> candidates : candidateLists) {
- Map<Dimension, Set<File>> sameDimensions = new HashMap<Dimension, Set<File>>(
- candidates.size());
- List<File> noSize = new ArrayList<File>();
- for (File file : candidates) {
- Dimension dimension = pixelSizes.get(file);
- if (dimension != null) {
- Set<File> set = sameDimensions.get(dimension);
- if (set == null) {
- set = new HashSet<File>();
- sameDimensions.put(dimension, set);
- }
- set.add(file);
- } else {
- noSize.add(file);
- }
- }
-
-
- // Files that we have no dimensions for must be compared against everything
- Collection<Set<File>> sets = sameDimensions.values();
- if (!noSize.isEmpty()) {
- if (!sets.isEmpty()) {
- for (Set<File> set : sets) {
- set.addAll(noSize);
- }
- } else {
- // Must just test the noSize elements against themselves
- HashSet<File> noSizeSet = new HashSet<File>(noSize);
- sets = Collections.<Set<File>>singletonList(noSizeSet);
- }
- }
-
- // Map from file to actual byte contents of the file.
- // We store this in a map such that for repeated files, such as noSize files
- // which can appear in multiple buckets, we only need to read them once
- Map<File, byte[]> fileContents = new HashMap<File, byte[]>();
-
- // Now we're ready for the final check where we actually check the
- // bits. We have to partition the files into buckets of files that
- // are identical.
- for (Set<File> set : sets) {
- if (set.size() < 2) {
- continue;
- }
-
- // Read all files in this set and store in map
- for (File file : set) {
- byte[] bits = fileContents.get(file);
- if (bits == null) {
- try {
- bits = context.getClient().readBytes(file);
- fileContents.put(file, bits);
- } catch (IOException e) {
- context.log(e, null);
- }
- }
- }
-
- // Map where the key file is known to be equal to the value file.
- // After we check individual files for equality this will be used
- // to look for transitive equality.
- Map<File, File> equal = new HashMap<File, File>();
-
- // Now go and compare all the files. This isn't an efficient algorithm
- // but the number of candidates should be very small
-
- List<File> files = new ArrayList<File>(set);
- Collections.sort(files);
- for (int i = 0; i < files.size() - 1; i++) {
- for (int j = i + 1; j < files.size(); j++) {
- File file1 = files.get(i);
- File file2 = files.get(j);
- byte[] contents1 = fileContents.get(file1);
- byte[] contents2 = fileContents.get(file2);
- if (contents1 == null || contents2 == null) {
- // File couldn't be read: ignore
- continue;
- }
- if (contents1.length != contents2.length) {
- // Sizes differ: not identical.
- // This shouldn't happen since we've already partitioned based
- // on File.length(), but just make sure here since the file
- // system could have lied, or cached a value that has changed
- // if the file was just overwritten
- continue;
- }
- boolean same = true;
- for (int k = 0; k < contents1.length; k++) {
- if (contents1[k] != contents2[k]) {
- same = false;
- break;
- }
- }
- if (same) {
- equal.put(file1, file2);
- }
- }
- }
-
- if (!equal.isEmpty()) {
- Map<File, Set<File>> partitions = new HashMap<File, Set<File>>();
- List<Set<File>> sameSets = new ArrayList<Set<File>>();
- for (Map.Entry<File, File> entry : equal.entrySet()) {
- File file1 = entry.getKey();
- File file2 = entry.getValue();
- Set<File> set1 = partitions.get(file1);
- Set<File> set2 = partitions.get(file2);
- if (set1 != null) {
- set1.add(file2);
- } else if (set2 != null) {
- set2.add(file1);
- } else {
- set = new HashSet<File>();
- sameSets.add(set);
- set.add(file1);
- set.add(file2);
- partitions.put(file1, set);
- partitions.put(file2, set);
- }
- }
-
- // We've computed the partitions of equal files. Now sort them
- // for stable output.
- List<List<File>> lists = new ArrayList<List<File>>();
- for (Set<File> same : sameSets) {
- assert !same.isEmpty();
- ArrayList<File> sorted = new ArrayList<File>(same);
- Collections.sort(sorted);
- lists.add(sorted);
- }
- // Sort overall partitions by the first item in each list
- Collections.sort(lists, new Comparator<List<File>>() {
- @Override
- public int compare(List<File> list1, List<File> list2) {
- return list1.get(0).compareTo(list2.get(0));
- }
- });
-
- for (List<File> sameFiles : lists) {
- Location location = null;
- boolean sameNames = true;
- String lastName = null;
- for (File file : sameFiles) {
- if (lastName != null && !lastName.equals(file.getName())) {
- sameNames = false;
- }
- lastName = file.getName();
- // Chain locations together
- Location linkedLocation = location;
- location = Location.create(file);
- location.setSecondary(linkedLocation);
- }
-
- if (sameNames) {
- StringBuilder sb = new StringBuilder(sameFiles.size() * 16);
- for (File file : sameFiles) {
- if (sb.length() > 0) {
- sb.append(", "); //$NON-NLS-1$
- }
- sb.append(file.getParentFile().getName());
- }
- String message = String.format(
- "The %1$s icon has identical contents in the following configuration folders: %2$s",
- lastName, sb.toString());
- context.report(DUPLICATES_CONFIGURATIONS, location, message, null);
- } else {
- StringBuilder sb = new StringBuilder(sameFiles.size() * 16);
- for (File file : sameFiles) {
- if (sb.length() > 0) {
- sb.append(", "); //$NON-NLS-1$
- }
- sb.append(file.getName());
- }
- String message = String.format(
- "The following unrelated icon files have identical contents: %1$s",
- sb.toString());
- context.report(DUPLICATES_NAMES, location, message, null);
- }
- }
- }
- }
- }
-
- }
-
- // This method checks the given map from resource file to pixel dimensions for each
- // such image and makes sure that the normalized dip sizes across all the densities
- // are mostly the same.
- private static void checkDipSizes(Context context, Map<File, Dimension> pixelSizes) {
- // Partition up the files such that I can look at a series by name. This
- // creates a map from filename (such as foo.png) to a list of files
- // providing that icon in various folders: drawable-mdpi/foo.png, drawable-hdpi/foo.png
- // etc.
- Map<String, List<File>> nameToFiles = new HashMap<String, List<File>>();
- for (File file : pixelSizes.keySet()) {
- String name = file.getName();
- List<File> list = nameToFiles.get(name);
- if (list == null) {
- list = new ArrayList<File>();
- nameToFiles.put(name, list);
- }
- list.add(file);
- }
-
- ArrayList<String> names = new ArrayList<String>(nameToFiles.keySet());
- Collections.sort(names);
-
- // We have to partition the files further because it's possible for the project
- // to have different configurations for an icon, such as this:
- // drawable-large-hdpi/foo.png, drawable-large-mdpi/foo.png,
- // drawable-hdpi/foo.png, drawable-mdpi/foo.png,
- // drawable-hdpi-v11/foo.png and drawable-mdpi-v11/foo.png.
- // In this case we don't want to compare across categories; we want to
- // ensure that the drawable-large-{density} icons are consistent,
- // that the drawable-{density}-v11 icons are consistent, and that
- // the drawable-{density} icons are consistent.
-
- // Map from name to list of map from parent folder to list of files
- Map<String, Map<String, List<File>>> configMap =
- new HashMap<String, Map<String,List<File>>>();
- for (Map.Entry<String, List<File>> entry : nameToFiles.entrySet()) {
- String name = entry.getKey();
- List<File> files = entry.getValue();
- for (File file : files) {
- String parentName = file.getParentFile().getName();
- // Strip out the density part
- int index = -1;
- for (String qualifier : DENSITY_QUALIFIERS) {
- index = parentName.indexOf(qualifier);
- if (index != -1) {
- parentName = parentName.substring(0, index)
- + parentName.substring(index + qualifier.length());
- break;
- }
- }
- if (index == -1) {
- // No relevant qualifier found in the parent directory name,
- // e.g. it's just "drawable" or something like "drawable-nodpi".
- continue;
- }
-
- Map<String, List<File>> folderMap = configMap.get(name);
- if (folderMap == null) {
- folderMap = new HashMap<String,List<File>>();
- configMap.put(name, folderMap);
- }
- // Map from name to a map from parent folder to files
- List<File> list = folderMap.get(parentName);
- if (list == null) {
- list = new ArrayList<File>();
- folderMap.put(parentName, list);
- }
- list.add(file);
- }
- }
-
- for (String name : names) {
- //List<File> files = nameToFiles.get(name);
- Map<String, List<File>> configurations = configMap.get(name);
- if (configurations == null) {
- // Nothing in this configuration: probably only found in drawable/ or
- // drawable-nodpi etc directories.
- continue;
- }
-
- for (Map.Entry<String, List<File>> entry : configurations.entrySet()) {
- List<File> files = entry.getValue();
-
- // Ensure that all the dip sizes are *roughly* the same
- Map<File, Dimension> dipSizes = new HashMap<File, Dimension>();
- int dipWidthSum = 0; // Incremental computation of average
- int dipHeightSum = 0; // Incremental computation of average
- int count = 0;
- for (File file : files) {
- float factor = getMdpiScalingFactor(file.getParentFile().getName());
- if (factor > 0) {
- Dimension size = pixelSizes.get(file);
- if (size == null) {
- continue;
- }
- Dimension dip = new Dimension(
- Math.round(size.width / factor),
- Math.round(size.height / factor));
- dipWidthSum += dip.width;
- dipHeightSum += dip.height;
- dipSizes.put(file, dip);
- count++;
- }
- }
- if (count == 0) {
- // Icons in drawable/ and drawable-nodpi/
- continue;
- }
- int meanWidth = dipWidthSum / count;
- int meanHeight = dipHeightSum / count;
-
- // Compute standard deviation?
- int squareWidthSum = 0;
- int squareHeightSum = 0;
- for (Dimension size : dipSizes.values()) {
- squareWidthSum += (size.width - meanWidth) * (size.width - meanWidth);
- squareHeightSum += (size.height - meanHeight) * (size.height - meanHeight);
- }
- double widthStdDev = Math.sqrt(squareWidthSum / count);
- double heightStdDev = Math.sqrt(squareHeightSum / count);
-
- if (widthStdDev > meanWidth / 10 || heightStdDev > meanHeight) {
- Location location = null;
- StringBuilder sb = new StringBuilder(100);
-
- // Sort entries by decreasing dip size
- List<Map.Entry<File, Dimension>> entries =
- new ArrayList<Map.Entry<File,Dimension>>();
- for (Map.Entry<File, Dimension> entry2 : dipSizes.entrySet()) {
- entries.add(entry2);
- }
- Collections.sort(entries,
- new Comparator<Map.Entry<File, Dimension>>() {
- @Override
- public int compare(Entry<File, Dimension> e1,
- Entry<File, Dimension> e2) {
- Dimension d1 = e1.getValue();
- Dimension d2 = e2.getValue();
- if (d1.width != d2.width) {
- return d2.width - d1.width;
- }
-
- return d2.height - d1.height;
- }
- });
- for (Map.Entry<File, Dimension> entry2 : entries) {
- if (sb.length() > 0) {
- sb.append(", ");
- }
- File file = entry2.getKey();
-
- // Chain locations together
- Location linkedLocation = location;
- location = Location.create(file);
- location.setSecondary(linkedLocation);
- Dimension dip = entry2.getValue();
- Dimension px = pixelSizes.get(file);
- String fileName = file.getParentFile().getName() + File.separator
- + file.getName();
- sb.append(String.format("%1$s: %2$dx%3$d dp (%4$dx%5$d px)",
- fileName, dip.width, dip.height, px.width, px.height));
- }
- String message = String.format(
- "The image %1$s varies significantly in its density-independent (dip) " +
- "size across the various density versions: %2$s",
- name, sb.toString());
- context.report(ICON_DIP_SIZE, location, message, null);
- }
- }
- }
- }
-
- private static void checkDensities(Context context, File res,
- Map<File, Set<String>> folderToNames,
- Map<File, Set<String>> nonDpiFolderNames) {
- // TODO: Is there a way to look at the manifest and figure out whether
- // all densities are expected to be needed?
- // Note: ldpi is probably not needed; it has very little usage
- // (about 2%; http://developer.android.com/resources/dashboard/screens.html)
- // TODO: Use the matrix to check out if we can eliminate densities based
- // on the target screens?
-
- Set<String> definedDensities = new HashSet<String>();
- for (File f : folderToNames.keySet()) {
- definedDensities.add(f.getName());
- }
-
- // Look for missing folders -- if you define say drawable-mdpi then you
- // should also define -hdpi and -xhdpi.
- if (context.isEnabled(ICON_MISSING_FOLDER)) {
- List<String> missing = new ArrayList<String>();
- for (String density : REQUIRED_DENSITIES) {
- if (!definedDensities.contains(density)) {
- missing.add(density);
- }
- }
- if (!missing.isEmpty()) {
- context.report(
- ICON_MISSING_FOLDER,
- Location.create(res),
- String.format("Missing density variation folders in %1$s: %2$s",
- context.getProject().getDisplayPath(res),
- LintUtils.formatList(missing, -1)),
- null);
- }
- }
-
- if (context.isEnabled(ICON_NODPI)) {
- Set<String> noDpiNames = new HashSet<String>();
- for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) {
- if (isNoDpiFolder(entry.getKey())) {
- noDpiNames.addAll(entry.getValue());
- }
- }
- if (!noDpiNames.isEmpty()) {
- // Make sure that none of the nodpi names appear in a non-nodpi folder
- Set<String> inBoth = new HashSet<String>();
- List<File> files = new ArrayList<File>();
- for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) {
- File folder = entry.getKey();
- String folderName = folder.getName();
- if (!isNoDpiFolder(folder)) {
- assert DENSITY_PATTERN.matcher(folderName).matches();
- Set<String> overlap = nameIntersection(noDpiNames, entry.getValue());
- inBoth.addAll(overlap);
- for (String name : overlap) {
- files.add(new File(folder, name));
- }
- }
- }
-
- if (!inBoth.isEmpty()) {
- List<String> list = new ArrayList<String>(inBoth);
- Collections.sort(list);
-
- // Chain locations together
- Collections.sort(files);
- Location location = null;
- for (File file : files) {
- Location linkedLocation = location;
- location = Location.create(file);
- location.setSecondary(linkedLocation);
- }
-
- context.report(ICON_NODPI, location,
- String.format(
- "The following images appear in both -nodpi and in a density folder: %1$s",
- LintUtils.formatList(list,
- context.getDriver().isAbbreviating() ? 10 : -1)),
- null);
- }
- }
- }
-
- if (context.isEnabled(ICON_XML_AND_PNG)) {
- Map<File, Set<String>> folderMap = Maps.newHashMap(folderToNames);
- folderMap.putAll(nonDpiFolderNames);
- Set<String> xmlNames = Sets.newHashSetWithExpectedSize(100);
- Set<String> bitmapNames = Sets.newHashSetWithExpectedSize(100);
-
- for (Map.Entry<File, Set<String>> entry : folderMap.entrySet()) {
- Set<String> names = entry.getValue();
- for (String name : names) {
- if (endsWith(name, DOT_XML)) {
- xmlNames.add(name);
- } else if (isDrawableFile(name)) {
- bitmapNames.add(name);
- }
- }
- }
- if (!xmlNames.isEmpty() && !bitmapNames.isEmpty()) {
- // Make sure that none of the nodpi names appear in a non-nodpi folder
- Set<String> overlap = nameIntersection(xmlNames, bitmapNames);
- if (!overlap.isEmpty()) {
- Multimap<String, File> map = ArrayListMultimap.create();
- Set<String> bases = Sets.newHashSetWithExpectedSize(overlap.size());
- for (String name : overlap) {
- bases.add(LintUtils.getBaseName(name));
- }
-
- for (String base : bases) {
- for (Map.Entry<File, Set<String>> entry : folderMap.entrySet()) {
- File folder = entry.getKey();
- for (String n : entry.getValue()) {
- if (base.equals(LintUtils.getBaseName(n))) {
- map.put(base, new File(folder, n));
- }
- }
- }
- }
- List<String> sorted = new ArrayList<String>(map.keySet());
- Collections.sort(sorted);
- for (String name : sorted) {
- List<File> lists = Lists.newArrayList(map.get(name));
- Collections.sort(lists);
-
- // Chain locations together
- Location location = null;
- for (File file : lists) {
- Location linkedLocation = location;
- location = Location.create(file);
- location.setSecondary(linkedLocation);
- }
-
- List<String> fileNames = Lists.newArrayList();
- boolean seenXml = false;
- boolean seenNonXml = false;
- for (File f : lists) {
- boolean isXml = endsWith(f.getPath(), DOT_XML);
- if (isXml && !seenXml) {
- fileNames.add(context.getProject().getDisplayPath(f));
- seenXml = true;
- } else if (!isXml && !seenNonXml) {
- fileNames.add(context.getProject().getDisplayPath(f));
- seenNonXml = true;
- }
- }
-
- context.report(ICON_XML_AND_PNG, location,
- String.format(
- "The following images appear both as density independent .xml files and as bitmap files: %1$s",
- LintUtils.formatList(fileNames,
- context.getDriver().isAbbreviating() ? 10 : -1)),
- null);
- }
- }
- }
- }
-
- if (context.isEnabled(ICON_DENSITIES)) {
- // Look for folders missing some of the specific assets
- Set<String> allNames = new HashSet<String>();
- for (Entry<File,Set<String>> entry : folderToNames.entrySet()) {
- if (!isNoDpiFolder(entry.getKey())) {
- Set<String> names = entry.getValue();
- allNames.addAll(names);
- }
- }
-
- for (Map.Entry<File, Set<String>> entry : folderToNames.entrySet()) {
- File file = entry.getKey();
- if (isNoDpiFolder(file)) {
- continue;
- }
- Set<String> names = entry.getValue();
- if (names.size() != allNames.size()) {
- List<String> delta = new ArrayList<String>(nameDifferences(allNames, names));
- if (delta.isEmpty()) {
- continue;
- }
- Collections.sort(delta);
- String foundIn = "";
- if (delta.size() == 1) {
- // Produce list of where the icon is actually defined
- List<String> defined = new ArrayList<String>();
- String name = delta.get(0);
- for (Map.Entry<File, Set<String>> e : folderToNames.entrySet()) {
- if (e.getValue().contains(name)) {
- defined.add(e.getKey().getName());
- }
- }
- if (!defined.isEmpty()) {
- foundIn = String.format(" (found in %1$s)",
- LintUtils.formatList(defined,
- context.getDriver().isAbbreviating() ? 5 : -1));
- }
- }
-
- context.report(ICON_DENSITIES, Location.create(file),
- String.format(
- "Missing the following drawables in %1$s: %2$s%3$s",
- file.getName(),
- LintUtils.formatList(delta,
- context.getDriver().isAbbreviating() ? 5 : -1),
- foundIn),
- null);
- }
- }
- }
- }
-
- /**
- * Compute the difference in names between a and b. This is not just
- * Sets.difference(a, b) because we want to make the comparisons <b>without
- * file extensions</b> and return the result <b>with</b>..
- */
- private static Set<String> nameDifferences(Set<String> a, Set<String> b) {
- Set<String> names1 = new HashSet<String>(a.size());
- for (String s : a) {
- names1.add(LintUtils.getBaseName(s));
- }
- Set<String> names2 = new HashSet<String>(b.size());
- for (String s : b) {
- names2.add(LintUtils.getBaseName(s));
- }
-
- names1.removeAll(names2);
-
- if (!names1.isEmpty()) {
- // Map filenames back to original filenames with extensions
- Set<String> result = new HashSet<String>(names1.size());
- for (String s : a) {
- if (names1.contains(LintUtils.getBaseName(s))) {
- result.add(s);
- }
- }
- for (String s : b) {
- if (names1.contains(LintUtils.getBaseName(s))) {
- result.add(s);
- }
- }
-
- return result;
- }
-
- return Collections.emptySet();
- }
-
- /**
- * Compute the intersection in names between a and b. This is not just
- * Sets.intersection(a, b) because we want to make the comparisons <b>without
- * file extensions</b> and return the result <b>with</b>.
- */
- private static Set<String> nameIntersection(Set<String> a, Set<String> b) {
- Set<String> names1 = new HashSet<String>(a.size());
- for (String s : a) {
- names1.add(LintUtils.getBaseName(s));
- }
- Set<String> names2 = new HashSet<String>(b.size());
- for (String s : b) {
- names2.add(LintUtils.getBaseName(s));
- }
-
- names1.retainAll(names2);
-
- if (!names1.isEmpty()) {
- // Map filenames back to original filenames with extensions
- Set<String> result = new HashSet<String>(names1.size());
- for (String s : a) {
- if (names1.contains(LintUtils.getBaseName(s))) {
- result.add(s);
- }
- }
- for (String s : b) {
- if (names1.contains(LintUtils.getBaseName(s))) {
- result.add(s);
- }
- }
-
- return result;
- }
-
- return Collections.emptySet();
- }
-
- private static boolean isNoDpiFolder(File file) {
- return file.getName().contains("-nodpi");
- }
-
- private Map<File, BufferedImage> mImageCache;
-
- @Nullable
- private BufferedImage getImage(@Nullable File file) throws IOException {
- if (mImageCache == null) {
- mImageCache = Maps.newHashMap();
- } else {
- BufferedImage image = mImageCache.get(file);
- if (image != null) {
- return image;
- }
- }
-
- BufferedImage image = ImageIO.read(file);
- mImageCache.put(file, image);
-
- return image;
- }
-
- private void checkDrawableDir(Context context, File folder, File[] files,
- Map<File, Dimension> pixelSizes, Map<File, Long> fileSizes) {
- if (folder.getName().equals(DRAWABLE_FOLDER)
- && context.isEnabled(ICON_LOCATION) &&
- // If supporting older versions than Android 1.6, it's not an error
- // to include bitmaps in drawable/
- context.getProject().getMinSdk() >= 4) {
- for (File file : files) {
- String name = file.getName();
- if (name.endsWith(DOT_XML)) {
- // pass - most common case, avoids checking other extensions
- } else if (endsWith(name, DOT_PNG)
- || endsWith(name, DOT_JPG)
- || endsWith(name, DOT_JPEG)
- || endsWith(name, DOT_GIF)) {
- context.report(ICON_LOCATION,
- Location.create(file),
- String.format("Found bitmap drawable res/drawable/%1$s in " +
- "densityless folder",
- file.getName()),
- null);
- }
- }
- }
-
- if (context.isEnabled(GIF_USAGE)) {
- for (File file : files) {
- String name = file.getName();
- if (endsWith(name, DOT_GIF)) {
- context.report(GIF_USAGE, Location.create(file),
- "Using the .gif format for bitmaps is discouraged",
- null);
- }
- }
- }
-
- if (context.isEnabled(ICON_EXTENSION)) {
- for (File file : files) {
- String path = file.getPath();
- if (isDrawableFile(path) && !endsWith(path, DOT_XML)) {
- checkExtension(context, file);
- }
- }
- }
-
- if (context.isEnabled(ICON_COLORS)) {
- for (File file : files) {
- String name = file.getName();
-
- if (isDrawableFile(name)
- && !endsWith(name, DOT_XML)
- && !endsWith(name, DOT_9PNG)) {
- String baseName = getBaseName(name);
- boolean isActionBarIcon = isActionBarIcon(context, baseName, file);
- if (isActionBarIcon || isNotificationIcon(baseName)) {
- Dimension size = checkColor(context, file, isActionBarIcon);
-
- // Store dimension for size check if we went to the trouble of reading image
- if (size != null && pixelSizes != null) {
- pixelSizes.put(file, size);
- }
- }
- }
- }
- }
-
- if (context.isEnabled(ICON_LAUNCHER_SHAPE)) {
- // Look up launcher icon name
- for (File file : files) {
- String name = file.getName();
- if (isLauncherIcon(getBaseName(name))
- && !endsWith(name, DOT_XML)
- && !endsWith(name, DOT_9PNG)) {
- checkLauncherShape(context, file);
- }
- }
- }
-
- // Check icon sizes
- if (context.isEnabled(ICON_EXPECTED_SIZE)) {
- checkExpectedSizes(context, folder, files);
- }
-
- if (pixelSizes != null || fileSizes != null) {
- for (File file : files) {
- // TODO: Combine this check with the check for expected sizes such that
- // I don't check file sizes twice!
- String fileName = file.getName();
-
- if (endsWith(fileName, DOT_PNG) || endsWith(fileName, DOT_JPG)
- || endsWith(fileName, DOT_JPEG)) {
- // Only scan .png files (except 9-patch png's) and jpg files for
- // dip sizes. Duplicate checks can also be performed on ninepatch files.
- if (pixelSizes != null && !endsWith(fileName, DOT_9PNG)
- && !pixelSizes.containsKey(file)) { // already read by checkColor?
- Dimension size = getSize(file);
- pixelSizes.put(file, size);
- }
- if (fileSizes != null) {
- fileSizes.put(file, file.length());
- }
- }
- }
- }
-
- mImageCache = null;
- }
-
- /**
- * Check that launcher icons do not fill every pixel in the image
- */
- private void checkLauncherShape(Context context, File file) {
- try {
- BufferedImage image = getImage(file);
- if (image != null) {
- // TODO: see if the shape is rectangular but inset from outer rectangle; if so
- // that's probably not right either!
- for (int y = 0, height = image.getHeight(); y < height; y++) {
- for (int x = 0, width = image.getWidth(); x < width; x++) {
- int rgb = image.getRGB(x, y);
- if ((rgb & 0xFF000000) == 0) {
- return;
- }
- }
- }
-
- String message = "Launcher icons should not fill every pixel of their square " +
- "region; see the design guide for details";
- context.report(ICON_LAUNCHER_SHAPE, Location.create(file),
- message, null);
- }
- } catch (IOException e) {
- // Pass: ignore files we can't read
- }
- }
-
- /**
- * Check whether the icons in the file are okay. Also return the image size
- * if known (for use by other checks)
- */
- private Dimension checkColor(Context context, File file, boolean isActionBarIcon) {
- int folderVersion = Context.getFolderVersion(file);
- if (isActionBarIcon) {
- if (folderVersion != -1 && folderVersion < 11
- || !isAndroid30(context, folderVersion)) {
- return null;
- }
- } else {
- if (folderVersion != -1 && folderVersion < 9
- || !isAndroid23(context, folderVersion)
- && !isAndroid30(context, folderVersion)) {
- return null;
- }
- }
-
- // TODO: This only checks icons that are known to be using the Holo style.
- // However, if the user has minSdk < 11 as well as targetSdk > 11, we should
- // also check that they actually include a -v11 or -v14 folder with proper
- // icons, since the below won't flag the older icons.
- try {
- BufferedImage image = getImage(file);
- if (image != null) {
- if (isActionBarIcon) {
- checkPixels:
- for (int y = 0, height = image.getHeight(); y < height; y++) {
- for (int x = 0, width = image.getWidth(); x < width; x++) {
- int rgb = image.getRGB(x, y);
- if ((rgb & 0xFF000000) != 0) { // else: transparent
- int r = (rgb & 0xFF0000) >>> 16;
- int g = (rgb & 0x00FF00) >>> 8;
- int b = (rgb & 0x0000FF);
- if (r != g || r != b) {
- String message = "Action Bar icons should use a single gray "
- + "color (#333333 for light themes (with 60%/30% "
- + "opacity for enabled/disabled), and #FFFFFF with "
- + "opacity 80%/30% for dark themes";
- context.report(ICON_COLORS, Location.create(file),
- message, null);
- break checkPixels;
- }
- }
- }
- }
- } else {
- if (folderVersion >= 11 || isAndroid30(context, folderVersion)) {
- // Notification icons. Should be white as of API 14
- checkPixels:
- for (int y = 0, height = image.getHeight(); y < height; y++) {
- for (int x = 0, width = image.getWidth(); x < width; x++) {
- int rgb = image.getRGB(x, y);
- if ((rgb & 0xFF000000) != 0
- && rgb != 0xFFFFFFFF) {
- int r = (rgb & 0xFF0000) >>> 16;
- int g = (rgb & 0x00FF00) >>> 8;
- int b = (rgb & 0x0000FF);
- if (r == g && r == b) {
- // If the pixel is not white, it might be because of
- // anti-aliasing. In that case, at least one neighbor
- // should be of a different color
- if (x < width - 1 && rgb != image.getRGB(x + 1, y)) {
- continue;
- }
- if (x > 0 && rgb != image.getRGB(x - 1, y)) {
- continue;
- }
- if (y < height - 1 && rgb != image.getRGB(x, y + 1)) {
- continue;
- }
- if (y > 0 && rgb != image.getRGB(x, y - 1)) {
- continue;
- }
- }
-
- String message = "Notification icons must be entirely white";
- context.report(ICON_COLORS, Location.create(file),
- message, null);
- break checkPixels;
- }
- }
- }
- } else {
- // As of API 9, should be gray.
- checkPixels:
- for (int y = 0, height = image.getHeight(); y < height; y++) {
- for (int x = 0, width = image.getWidth(); x < width; x++) {
- int rgb = image.getRGB(x, y);
- if ((rgb & 0xFF000000) != 0) { // else: transparent
- int r = (rgb & 0xFF0000) >>> 16;
- int g = (rgb & 0x00FF00) >>> 8;
- int b = (rgb & 0x0000FF);
- if (r != g || r != b) {
- String message = "Notification icons should not use "
- + "colors";
- context.report(ICON_COLORS, Location.create(file),
- message, null);
- break checkPixels;
- }
- }
- }
- }
- }
- }
-
- return new Dimension(image.getWidth(), image.getHeight());
- }
- } catch (IOException e) {
- // Pass: ignore files we can't read
- }
-
- return null;
- }
-
- private static void checkExtension(Context context, File file) {
- try {
- ImageInputStream input = ImageIO.createImageInputStream(file);
- if (input != null) {
- try {
- Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
- while (readers.hasNext()) {
- ImageReader reader = readers.next();
- try {
- reader.setInput(input);
-
- // Check file extension
- String formatName = reader.getFormatName();
- if (formatName != null && !formatName.isEmpty()) {
- String path = file.getPath();
- int index = path.lastIndexOf('.');
- String extension = path.substring(index+1).toLowerCase(Locale.US);
-
- if (!formatName.equalsIgnoreCase(extension)) {
- if (endsWith(path, DOT_JPG)
- && formatName.equals("JPEG")) { //$NON-NLS-1$
- return;
- }
- String message = String.format(
- "Misleading file extension; named .%1$s but the " +
- "file format is %2$s", extension, formatName);
- Location location = Location.create(file);
- context.report(ICON_EXTENSION, location, message, null);
- }
- break;
- }
- } finally {
- reader.dispose();
- }
- }
- } finally {
- if (input != null) {
- input.close();
- }
- }
- }
- } catch (IOException e) {
- // Pass -- we can't handle all image types, warn about those we can
- }
- }
-
- private static String getBaseName(String name) {
- String baseName = name;
- int index = baseName.indexOf('.');
- if (index != -1) {
- baseName = baseName.substring(0, index);
- }
-
- return baseName;
- }
-
- private void checkExpectedSizes(Context context, File folder, File[] files) {
- if (files == null || files.length == 0) {
- return;
- }
-
- String folderName = folder.getName();
- int folderVersion = Context.getFolderVersion(files[0]);
-
- for (File file : files) {
- String name = file.getName();
-
- // TODO: Look up exact app icon from the manifest rather than simply relying on
- // the naming conventions described here:
- // http://developer.android.com/guide/practices/ui_guidelines/icon_design.html#design-tips
- // See if we can figure out other types of icons from usage too.
-
- String baseName = getBaseName(name);
-
- if (isLauncherIcon(baseName)) {
- // Launcher icons
- checkSize(context, folderName, file, 48, 48, true /*exact*/);
- } else if (isActionBarIcon(baseName)) {
- checkSize(context, folderName, file, 32, 32, true /*exact*/);
- } else if (name.startsWith("ic_dialog_")) { //$NON-NLS-1$
- // Dialog
- checkSize(context, folderName, file, 32, 32, true /*exact*/);
- } else if (name.startsWith("ic_tab_")) { //$NON-NLS-1$
- // Tab icons
- checkSize(context, folderName, file, 32, 32, true /*exact*/);
- } else if (isNotificationIcon(baseName)) {
- // Notification icons
- if (isAndroid30(context, folderVersion)) {
- checkSize(context, folderName, file, 24, 24, true /*exact*/);
- } else if (isAndroid23(context, folderVersion)) {
- checkSize(context, folderName, file, 16, 25, false /*exact*/);
- } else {
- // Android 2.2 or earlier
- // TODO: Should this be done for each folder size?
- checkSize(context, folderName, file, 25, 25, true /*exact*/);
- }
- } else if (name.startsWith("ic_menu_")) { //$NON-NLS-1$
- if (isAndroid30(context, folderVersion)) {
- // Menu icons (<=2.3 only: Replaced by action bar icons (ic_action_ in 3.0).
- // However the table halfway down the page on
- // http://developer.android.com/guide/practices/ui_guidelines/icon_design.html
- // and the README in the icon template download says that convention is ic_menu
- checkSize(context, folderName, file, 32, 32, true);
- } else if (isAndroid23(context, folderVersion)) {
- // The icon should be 32x32 inside the transparent image; should
- // we check that this is mostly the case (a few pixels are allowed to
- // overlap for anti-aliasing etc)
- checkSize(context, folderName, file, 48, 48, true /*exact*/);
- } else {
- // Android 2.2 or earlier
- // TODO: Should this be done for each folder size?
- checkSize(context, folderName, file, 48, 48, true /*exact*/);
- }
- }
- // TODO: ListView icons?
- }
- }
-
- /**
- * Is this drawable folder for an Android 3.0 drawable? This will be the
- * case if it specifies -v11+, or if the minimum SDK version declared in the
- * manifest is at least 11.
- */
- private static boolean isAndroid30(Context context, int folderVersion) {
- return folderVersion >= 11 || context.getMainProject().getMinSdk() >= 11;
- }
-
- /**
- * Is this drawable folder for an Android 2.3 drawable? This will be the
- * case if it specifies -v9 or -v10, or if the minimum SDK version declared in the
- * manifest is 9 or 10 (and it does not specify some higher version like -v11
- */
- private static boolean isAndroid23(Context context, int folderVersion) {
- if (isAndroid30(context, folderVersion)) {
- return false;
- }
-
- if (folderVersion == 9 || folderVersion == 10) {
- return true;
- }
-
- int minSdk = context.getMainProject().getMinSdk();
-
- return minSdk == 9 || minSdk == 10;
- }
-
- private static float getMdpiScalingFactor(String folderName) {
- // Can't do startsWith(DRAWABLE_MDPI) because the folder could
- // be something like "drawable-sw600dp-mdpi".
- if (folderName.contains("-mdpi")) { //$NON-NLS-1$
- return 1.0f;
- } else if (folderName.contains("-hdpi")) { //$NON-NLS-1$
- return 1.5f;
- } else if (folderName.contains("-xhdpi")) { //$NON-NLS-1$
- return 2.0f;
- } else if (folderName.contains("-ldpi")) { //$NON-NLS-1$
- return 0.75f;
- } else {
- return 0f;
- }
- }
-
- private static void checkSize(Context context, String folderName, File file,
- int mdpiWidth, int mdpiHeight, boolean exactMatch) {
- String fileName = file.getName();
- // Only scan .png files (except 9-patch png's) and jpg files
- if (!((endsWith(fileName, DOT_PNG) && !endsWith(fileName, DOT_9PNG)) ||
- endsWith(fileName, DOT_JPG) || endsWith(fileName, DOT_JPEG))) {
- return;
- }
-
- int width;
- int height;
- // Use 3:4:6:8 scaling ratio to look up the other expected sizes
- if (folderName.startsWith(DRAWABLE_MDPI)) {
- width = mdpiWidth;
- height = mdpiHeight;
- } else if (folderName.startsWith(DRAWABLE_HDPI)) {
- // Perform math using floating point; if we just do
- // width = mdpiWidth * 3 / 2;
- // then for mdpiWidth = 25 (as in notification icons on pre-GB) we end up
- // with width = 37, instead of 38 (with floating point rounding we get 37.5 = 38)
- width = Math.round(mdpiWidth * 3.f / 2);
- height = Math.round(mdpiHeight * 3f / 2);
- } else if (folderName.startsWith(DRAWABLE_XHDPI)) {
- width = mdpiWidth * 2;
- height = mdpiHeight * 2;
- } else if (folderName.startsWith(DRAWABLE_LDPI)) {
- width = Math.round(mdpiWidth * 3f / 4);
- height = Math.round(mdpiHeight * 3f / 4);
- } else {
- return;
- }
-
- Dimension size = getSize(file);
- if (size != null) {
- if (exactMatch && size.width != width || size.height != height) {
- context.report(
- ICON_EXPECTED_SIZE,
- Location.create(file),
- String.format(
- "Incorrect icon size for %1$s: expected %2$dx%3$d, but was %4$dx%5$d",
- folderName + File.separator + file.getName(),
- width, height, size.width, size.height),
- null);
- } else if (!exactMatch && size.width > width || size.height > height) {
- context.report(
- ICON_EXPECTED_SIZE,
- Location.create(file),
- String.format(
- "Incorrect icon size for %1$s: icon size should be at most %2$dx%3$d, but was %4$dx%5$d",
- folderName + File.separator + file.getName(),
- width, height, size.width, size.height),
- null);
- }
- }
- }
-
- private static Dimension getSize(File file) {
- try {
- ImageInputStream input = ImageIO.createImageInputStream(file);
- if (input != null) {
- try {
- Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
- if (readers.hasNext()) {
- ImageReader reader = readers.next();
- try {
- reader.setInput(input);
- return new Dimension(reader.getWidth(0), reader.getHeight(0));
- } finally {
- reader.dispose();
- }
- }
- } finally {
- if (input != null) {
- input.close();
- }
- }
- }
-
- // Fallback: read the image using the normal means
- BufferedImage image = ImageIO.read(file);
- if (image != null) {
- return new Dimension(image.getWidth(), image.getHeight());
- } else {
- return null;
- }
- } catch (IOException e) {
- // Pass -- we can't handle all image types, warn about those we can
- return null;
- }
- }
-
-
- private Set<String> mActionBarIcons;
- private Set<String> mNotificationIcons;
- private Set<String> mLauncherIcons;
- private Multimap<String, String> mMenuToIcons;
-
- private boolean isLauncherIcon(String name) {
- assert name.indexOf('.') == -1; // Should supply base name
-
- // Naming convention
- if (name.startsWith("ic_launcher")) { //$NON-NLS-1$
- return true;
- }
- return mLauncherIcons != null && mLauncherIcons.contains(name);
- }
-
- private boolean isNotificationIcon(String name) {
- assert name.indexOf('.') == -1; // Should supply base name
-
- // Naming convention
- if (name.startsWith("ic_stat_")) { //$NON-NLS-1$
- return true;
- }
-
- return mNotificationIcons != null && mNotificationIcons.contains(name);
- }
-
- private boolean isActionBarIcon(String name) {
- assert name.indexOf('.') == -1; // Should supply base name
-
- // Naming convention
- if (name.startsWith("ic_action_")) { //$NON-NLS-1$
- return true;
- }
-
- // Naming convention
-
- return mActionBarIcons != null && mActionBarIcons.contains(name);
- }
-
- private boolean isActionBarIcon(Context context, String name, File file) {
- if (isActionBarIcon(name)) {
- return true;
- }
-
- // As of Android 3.0 ic_menu_ are action icons
- if (file != null && name.startsWith("ic_menu_") //$NON-NLS-1$
- && isAndroid30(context, Context.getFolderVersion(file))) {
- // Naming convention
- return true;
- }
-
- return false;
- }
-
- // XML detector: Skim manifest and menu files
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.MENU;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- // Manifest
- TAG_APPLICATION,
- TAG_ACTIVITY,
- TAG_SERVICE,
- TAG_PROVIDER,
- TAG_RECEIVER,
-
- // Menu
- TAG_ITEM
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String icon = element.getAttributeNS(ANDROID_URI, ATTR_ICON);
- if (icon != null && icon.startsWith(DRAWABLE_PREFIX)) {
- icon = icon.substring(DRAWABLE_PREFIX.length());
-
- String tagName = element.getTagName();
- if (tagName.equals(TAG_ITEM)) {
- if (mMenuToIcons == null) {
- mMenuToIcons = ArrayListMultimap.create();
- }
- String menu = getBaseName(context.file.getName());
- mMenuToIcons.put(menu, icon);
- } else {
- // Manifest tags: launcher icons
- if (mLauncherIcons == null) {
- mLauncherIcons = Sets.newHashSet();
- }
- mLauncherIcons.add(icon);
- }
- }
- }
-
-
- // ---- Implements JavaScanner ----
-
- private static final String NOTIFICATION_CLASS = "Notification"; //$NON-NLS-1$
- private static final String NOTIFICATION_COMPAT_CLASS = "NotificationCompat"; //$NON-NLS-1$
- private static final String BUILDER_CLASS = "Builder"; //$NON-NLS-1$
- private static final String SET_SMALL_ICON = "setSmallIcon"; //$NON-NLS-1$
- private static final String ON_CREATE_OPTIONS_MENU = "onCreateOptionsMenu"; //$NON-NLS-1$
-
- @Override
- @Nullable
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new NotificationFinder();
- }
-
- @Override
- @Nullable
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- List<Class<? extends Node>> types = new ArrayList<Class<? extends Node>>(3);
- types.add(MethodDeclaration.class);
- types.add(ConstructorInvocation.class);
- return types;
- }
-
- private final class NotificationFinder extends ForwardingAstVisitor {
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- if (ON_CREATE_OPTIONS_MENU.equals(node.astMethodName().astValue())) {
- // Gather any R.menu references found in this method
- node.accept(new MenuFinder());
- }
-
- return super.visitMethodDeclaration(node);
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- TypeReference reference = node.astTypeReference();
- StrictListAccessor<TypeReferencePart, TypeReference> parts = reference.astParts();
- String typeName = parts.last().astIdentifier().astValue();
- if (NOTIFICATION_CLASS.equals(typeName)) {
- StrictListAccessor<Expression, ConstructorInvocation> args = node.astArguments();
- if (args.size() == 3) {
- if (args.first() instanceof Select && handleSelect((Select) args.first())) {
- return super.visitConstructorInvocation(node);
- }
-
- Node method = StringFormatDetector.getParentMethod(node);
- if (method != null) {
- // Must track local types
- String name = StringFormatDetector.getResourceForFirstArg(method, node);
- if (name != null) {
- if (mNotificationIcons == null) {
- mNotificationIcons = Sets.newHashSet();
- }
- mNotificationIcons.add(name);
- }
- }
- }
- } else if (BUILDER_CLASS.equals(typeName)) {
- boolean isBuilder = false;
- if (parts.size() == 1) {
- isBuilder = true;
- } else if (parts.size() == 2) {
- String clz = parts.first().astIdentifier().astValue();
- if (NOTIFICATION_CLASS.equals(clz) || NOTIFICATION_COMPAT_CLASS.equals(clz)) {
- isBuilder = true;
- }
- }
- if (isBuilder) {
- Node method = StringFormatDetector.getParentMethod(node);
- if (method != null) {
- SetIconFinder finder = new SetIconFinder();
- method.accept(finder);
- }
- }
- }
-
- return super.visitConstructorInvocation(node);
- }
- }
-
- private boolean handleSelect(Select select) {
- if (select.toString().startsWith(R_DRAWABLE_PREFIX)) {
- String name = select.astIdentifier().astValue();
- if (mNotificationIcons == null) {
- mNotificationIcons = Sets.newHashSet();
- }
- mNotificationIcons.add(name);
-
- return true;
- }
-
- return false;
- }
-
- private final class SetIconFinder extends ForwardingAstVisitor {
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (SET_SMALL_ICON.equals(node.astName().astValue())) {
- StrictListAccessor<Expression,MethodInvocation> arguments = node.astArguments();
- if (arguments.size() == 1 && arguments.first() instanceof Select) {
- handleSelect((Select) arguments.first());
- }
- }
- return super.visitMethodInvocation(node);
- }
- }
-
- private final class MenuFinder extends ForwardingAstVisitor {
- @Override
- public boolean visitSelect(Select node) {
- // R.type.name
- if (node.astOperand() instanceof Select) {
- Select select = (Select) node.astOperand();
- if (select.astOperand() instanceof VariableReference) {
- VariableReference reference = (VariableReference) select.astOperand();
- if (reference.astIdentifier().astValue().equals(R_CLASS)) {
- String type = select.astIdentifier().astValue();
-
- if (type.equals(MENU_TYPE)) {
- String name = node.astIdentifier().astValue();
- // Reclassify icons in the given menu as action bar icons
- if (mMenuToIcons != null) {
- Collection<String> icons = mMenuToIcons.get(name);
- if (icons != null) {
- if (mActionBarIcons == null) {
- mActionBarIcons = Sets.newHashSet();
- }
- mActionBarIcons.addAll(icons);
- }
- }
- }
- }
- }
- }
-
- return super.visitSelect(node);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
deleted file mode 100644
index 0608de1..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InefficientWeightDetector.java
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BASELINE_ALIGNED;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_ORIENTATION;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.RADIO_GROUP;
-import static com.android.SdkConstants.VALUE_FILL_PARENT;
-import static com.android.SdkConstants.VALUE_MATCH_PARENT;
-import static com.android.SdkConstants.VALUE_VERTICAL;
-import static com.android.SdkConstants.VIEW;
-import static com.android.SdkConstants.VIEW_FRAGMENT;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-import static com.android.SdkConstants.VIEW_TAG;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.SdkInfo;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Checks whether a layout_weight is declared inefficiently.
- */
-public class InefficientWeightDetector extends LayoutDetector {
-
- /** Can a weight be replaced with 0dp instead for better performance? */
- public static final Issue INEFFICIENT_WEIGHT = Issue.create(
- "InefficientWeight", //$NON-NLS-1$
- "Looks for inefficient weight declarations in LinearLayouts",
- "When only a single widget in a LinearLayout defines a weight, it is more " +
- "efficient to assign a width/height of `0dp` to it since it will absorb all " +
- "the remaining space anyway. With a declared width/height of `0dp` it " +
- "does not have to measure its own size first.",
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- InefficientWeightDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Are weights nested? */
- public static final Issue NESTED_WEIGHTS = Issue.create(
- "NestedWeights", //$NON-NLS-1$
- "Looks for nested layout weights, which are costly",
- "Layout weights require a widget to be measured twice. When a LinearLayout with " +
- "non-zero weights is nested inside another LinearLayout with non-zero weights, " +
- "then the number of measurements increase exponentially.",
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- InefficientWeightDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Should a LinearLayout set android:baselineAligned? */
- public static final Issue BASELINE_WEIGHTS = Issue.create(
- "DisableBaselineAlignment", //$NON-NLS-1$
- "Looks for LinearLayouts which should set android:baselineAligned=false",
- "When a LinearLayout is used to distribute the space proportionally between " +
- "nested layouts, the baseline alignment property should be turned off to " +
- "make the layout computation faster.",
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- InefficientWeightDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Using 0dp on the wrong dimension */
- public static final Issue WRONG_0DP = Issue.create(
- "Suspicious0dp", //$NON-NLS-1$
- "Looks for 0dp as the width in a vertical LinearLayout or as the height in a " +
- "horizontal",
-
- "Using 0dp as the width in a horizontal LinearLayout with weights is a useful " +
- "trick to ensure that only the weights (and not the intrinsic sizes) are used " +
- "when sizing the children.\n" +
- "\n" +
- "However, if you use 0dp for the opposite dimension, the view will be invisible. " +
- "This can happen if you change the orientation of a layout without also flipping " +
- "the 0dp dimension in all the children.",
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- InefficientWeightDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Missing explicit orientation */
- public static final Issue ORIENTATION = Issue.create(
- "Orientation", //$NON-NLS-1$
- "Checks that LinearLayouts with multiple children set the orientation",
-
- "The default orientation of a LinearLayout is horizontal. It's pretty easy to "
- + "believe that the layout is vertical, add multiple children to it, and wonder "
- + "why only the first child is visible (when the subsequent children are "
- + "off screen to the right). This lint rule helps pinpoint this issue by "
- + "warning whenever a LinearLayout is used with an implicit orientation "
- + "and multiple children.",
-
- Category.CORRECTNESS,
- 2,
- Severity.ERROR,
- InefficientWeightDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /**
- * Map from element to whether that element has a non-zero linear layout
- * weight or has an ancestor which does
- */
- private final Map<Node, Boolean> mInsideWeight = new IdentityHashMap<Node, Boolean>();
-
- /** Constructs a new {@link InefficientWeightDetector} */
- public InefficientWeightDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(LINEAR_LAYOUT);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- List<Element> children = LintUtils.getChildren(element);
- // See if there is exactly one child with a weight
- boolean multipleWeights = false;
- Element weightChild = null;
- boolean checkNesting = context.isEnabled(NESTED_WEIGHTS);
- for (Element child : children) {
- if (child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) {
- if (weightChild != null) {
- // More than one child defining a weight!
- multipleWeights = true;
- } else if (!multipleWeights) {
- weightChild = child;
- }
-
- if (checkNesting) {
- mInsideWeight.put(child, Boolean.TRUE);
-
- Boolean inside = mInsideWeight.get(element);
- if (inside == null) {
- mInsideWeight.put(element, Boolean.FALSE);
- } else if (inside) {
- Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT);
- context.report(NESTED_WEIGHTS, sizeNode,
- context.getLocation(sizeNode),
- "Nested weights are bad for performance", null);
- // Don't warn again
- checkNesting = false;
- }
- }
- }
- }
-
- String orientation = element.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
- if (children.size() >= 2 && (orientation == null || orientation.isEmpty())
- && context.isEnabled(ORIENTATION)) {
- // See if at least one of the children, except the last one, sets layout_width
- // to match_parent (or fill_parent), in an implicitly horizontal layout, since
- // that might mean the last child won't be visible. This is a source of confusion
- // for new Android developers.
- boolean maxWidthSet = false;
- Iterator<Element> iterator = children.iterator();
- while (iterator.hasNext()) {
- Element child = iterator.next();
- if (!iterator.hasNext()) { // Don't check the last one
- break;
- }
- String width = child.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
- if (VALUE_MATCH_PARENT.equals(width) || VALUE_FILL_PARENT.equals(width)) {
- // Also check that weights are not set here; this affects the computation
- // a bit and the child may not fill up the whole linear layout
- if (!child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) {
- maxWidthSet = true;
- break;
- }
- }
- }
- if (maxWidthSet && !element.hasAttribute(ATTR_STYLE)) {
- String message = "Wrong orientation? No orientation specified, and the default "
- + "is horizontal, yet this layout has multiple children where at "
- + "least one has layout_width=\"match_parent\"";
- context.report(ORIENTATION, element, context.getLocation(element), message, null);
- }
- }
-
- if (context.isEnabled(BASELINE_WEIGHTS) && weightChild != null
- && !VALUE_VERTICAL.equals(orientation)
- && !element.hasAttributeNS(ANDROID_URI, ATTR_BASELINE_ALIGNED)) {
- // See if all the children are layouts
- boolean allChildrenAreLayouts = !children.isEmpty();
- SdkInfo sdkInfo = context.getClient().getSdkInfo(context.getProject());
- for (Element child : children) {
- String tagName = child.getTagName();
- if (!(sdkInfo.isLayout(tagName)
- // RadioGroup is a layout, but one which possibly should be base aligned
- && !tagName.equals(RADIO_GROUP)
- // Consider <fragment> tags as layouts for the purposes of this check
- || VIEW_FRAGMENT.equals(tagName)
- // Ditto for <include> tags
- || VIEW_INCLUDE.equals(tagName))) {
- allChildrenAreLayouts = false;
- }
- }
- if (allChildrenAreLayouts) {
- context.report(BASELINE_WEIGHTS,
- element,
- context.getLocation(element),
- "Set android:baselineAligned=\"false\" on this element for better performance",
- null);
- }
- }
-
- if (context.isEnabled(INEFFICIENT_WEIGHT)
- && weightChild != null && !multipleWeights) {
- String dimension;
- if (VALUE_VERTICAL.equals(orientation)) {
- dimension = ATTR_LAYOUT_HEIGHT;
- } else {
- dimension = ATTR_LAYOUT_WIDTH;
- }
- Attr sizeNode = weightChild.getAttributeNodeNS(ANDROID_URI, dimension);
- String size = sizeNode != null ? sizeNode.getValue() : "(undefined)";
- if (!size.startsWith("0")) { //$NON-NLS-1$
- String msg = String.format(
- "Use a %1$s of 0dip instead of %2$s for better performance",
- dimension, size);
- context.report(INEFFICIENT_WEIGHT,
- weightChild,
- context.getLocation(sizeNode != null ? sizeNode : weightChild), msg, null);
-
- }
- }
-
- if (context.isEnabled(WRONG_0DP)) {
- checkWrong0Dp(context, element, children);
- }
- }
-
- private static void checkWrong0Dp(XmlContext context, Element element,
- List<Element> children) {
- boolean isVertical = false;
- String orientation = element.getAttributeNS(ANDROID_URI, ATTR_ORIENTATION);
- if (VALUE_VERTICAL.equals(orientation)) {
- isVertical = true;
- }
-
- for (Element child : children) {
- String tagName = child.getTagName();
- if (tagName.equals(VIEW)) {
- // Might just used for spacing
- return;
- }
- if (tagName.indexOf('.') != -1 || tagName.equals(VIEW_TAG)) {
- // Custom views might perform their own dynamic sizing or ignore the layout
- // attributes all together
- return;
- }
-
- boolean hasWeight = child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT);
-
- Attr widthNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
- Attr heightNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
-
- boolean noWidth = false;
- boolean noHeight = false;
- if (widthNode != null && widthNode.getValue().startsWith("0")) { //$NON-NLS-1$
- noWidth = true;
- }
- if (heightNode != null && heightNode.getValue().startsWith("0")) { //$NON-NLS-1$
- noHeight = true;
- } else if (!noWidth) {
- return;
- }
-
- // If you're specifying 0dp for both the width and height you are probably
- // trying to hide it deliberately
- if (noWidth && noHeight) {
- return;
- }
- assert noWidth || noHeight;
-
- if (noWidth) {
- assert widthNode != null;
- if (!hasWeight) {
- context.report(WRONG_0DP, widthNode, context.getLocation(widthNode),
- "Suspicious size: this will make the view invisible, should be " +
- "used with layout_weight", null);
- } else if (isVertical) {
- context.report(WRONG_0DP, widthNode, context.getLocation(widthNode),
- "Suspicious size: this will make the view invisible, probably " +
- "intended for layout_height", null);
- }
- } else {
- assert noHeight;
- assert heightNode != null;
- if (!hasWeight) {
- context.report(WRONG_0DP, widthNode, context.getLocation(heightNode),
- "Suspicious size: this will make the view invisible, should be " +
- "used with layout_weight", null);
- } else if (!isVertical) {
- context.report(WRONG_0DP, widthNode, context.getLocation(heightNode),
- "Suspicious size: this will make the view invisible, probably " +
- "intended for layout_width", null);
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
deleted file mode 100644
index 048f6ae..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/InvalidPackageDetector.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 com.google.common.collect.Sets;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.FieldInsnNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.io.File;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Set;
-
-import lombok.ast.libs.org.parboiled.google.collect.Lists;
-
-/**
- * Looks for usages of Java packages that are not included in Android.
- */
-public class InvalidPackageDetector extends Detector implements Detector.ClassScanner {
- /** Accessing an invalid package */
- public static final Issue ISSUE = Issue.create("InvalidPackage", //$NON-NLS-1$
- "Finds API accesses to APIs that are not supported in Android",
-
- "This check scans through libraries looking for calls to APIs that are not included " +
- "in Android.\n" +
- "\n" +
- "When you create Android projects, the classpath is set up such that you can only " +
- "access classes in the API packages that are included in Android. However, if you " +
- "add other projects to your libs/ folder, there is no guarantee that those .jar " +
- "files were built with an Android specific classpath, and in particular, they " +
- "could be accessing unsupported APIs such as java.applet.\n" +
- "\n" +
- "This check scans through library jars and looks for references to API packages " +
- "that are not included in Android and flags these. This is only an error if your " +
- "code calls one of the library classes which wind up referencing the unsupported " +
- "package.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- InvalidPackageDetector.class,
- EnumSet.of(Scope.JAVA_LIBRARIES));
-
- private static final String JAVA_PKG_PREFIX = "java/"; //$NON-NLS-1$
- private static final String JAVAX_PKG_PREFIX = "javax/"; //$NON-NLS-1$
-
- private ApiLookup mApiDatabase;
-
- /**
- * List of candidates that are potential package violations. These are
- * recorded as candidates rather than flagged immediately such that we can
- * filter out hits for classes that are also defined as libraries (possibly
- * encountered later in the library traversal).
- */
- private List<Candidate> mCandidates;
- /**
- * Set of Java packages defined in the libraries; this means that if the
- * user has added libraries in this package namespace (such as the
- * null annotations jars) we don't flag these.
- */
- private final Set<String> mJavaxLibraryClasses = Sets.newHashSetWithExpectedSize(64);
-
- /** Constructs a new package check */
- public InvalidPackageDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- mApiDatabase = ApiLookup.get(context.getClient());
- }
-
- // ---- Implements ClassScanner ----
-
- @SuppressWarnings("rawtypes") // ASM API
- @Override
- public void checkClass(@NonNull final ClassContext context, @NonNull ClassNode classNode) {
- if (!context.isFromClassLibrary() || shouldSkip(context.file)) {
- return;
- }
-
- if (mApiDatabase == null) {
- return;
- }
-
- if (classNode.name.startsWith(JAVAX_PKG_PREFIX)) {
- mJavaxLibraryClasses.add(classNode.name);
- }
-
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
-
- InsnList nodes = method.instructions;
-
- // Check return type
- // The parameter types are already handled as local variables so we can skip
- // right to the return type.
- // Check types in parameter list
- String signature = method.desc;
- if (signature != null) {
- int args = signature.indexOf(')');
- if (args != -1 && signature.charAt(args + 1) == 'L') {
- String type = signature.substring(args + 2, signature.length() - 1);
- if (isInvalidPackage(type)) {
- AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
- record(context, method, first, type);
- }
- }
- }
-
- for (int i = 0, n = nodes.size(); i < n; i++) {
- AbstractInsnNode instruction = nodes.get(i);
- int type = instruction.getType();
- if (type == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode node = (MethodInsnNode) instruction;
- String owner = node.owner;
-
- // No need to check methods in this local class; we know they
- // won't be an API match
- if (node.getOpcode() == Opcodes.INVOKEVIRTUAL
- && owner.equals(classNode.name)) {
- owner = classNode.superName;
- }
-
- while (owner != null) {
- if (isInvalidPackage(owner)) {
- record(context, method, instruction, owner);
- }
-
- // For virtual dispatch, walk up the inheritance chain checking
- // each inherited method
- if (owner.startsWith("android/") //$NON-NLS-1$
- || owner.startsWith(JAVA_PKG_PREFIX)
- || owner.startsWith(JAVAX_PKG_PREFIX)) {
- owner = null;
- } else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) {
- owner = context.getDriver().getSuperClass(owner);
- } else if (node.getOpcode() == Opcodes.INVOKESTATIC) {
- // Inherit through static classes as well
- owner = context.getDriver().getSuperClass(owner);
- } else {
- owner = null;
- }
- }
- } else if (type == AbstractInsnNode.FIELD_INSN) {
- FieldInsnNode node = (FieldInsnNode) instruction;
- String owner = node.owner;
- if (isInvalidPackage(owner)) {
- record(context, method, instruction, owner);
- }
- } else if (type == AbstractInsnNode.LDC_INSN) {
- LdcInsnNode node = (LdcInsnNode) instruction;
- if (node.cst instanceof Type) {
- Type t = (Type) node.cst;
- String className = t.getInternalName();
- if (isInvalidPackage(className)) {
- record(context, method, instruction, className);
- }
- }
- }
- }
- }
- }
-
- private boolean isInvalidPackage(String owner) {
- if (owner.startsWith(JAVA_PKG_PREFIX)
- || owner.startsWith(JAVAX_PKG_PREFIX)) {
- return !mApiDatabase.isValidJavaPackage(owner);
- }
-
- return false;
- }
-
- private void record(ClassContext context, MethodNode method,
- AbstractInsnNode instruction, String owner) {
- if (owner.indexOf('$') != -1) {
- // Don't report inner classes too; there will pretty much always be an outer class
- // reference as well
- return;
- }
-
- if (mCandidates == null) {
- mCandidates = Lists.newArrayList();
- }
- mCandidates.add(new Candidate(owner, context.getClassNode().name, context.getJarFile()));
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mCandidates == null) {
- return;
- }
-
- for (Candidate candidate : mCandidates) {
- String type = candidate.mClass;
- if (mJavaxLibraryClasses.contains(type)) {
- continue;
- }
- File jarFile = candidate.mJarFile;
- String referencedIn = candidate.mReferencedIn;
-
- Location location = Location.create(jarFile);
- Object pkg = getPackageName(type);
- String message = String.format(
- "Invalid package reference in library; not included in Android: %1$s. " +
- "Referenced from %2$s.", pkg, ClassContext.getFqcn(referencedIn));
- context.report(ISSUE, location, message, null);
- }
- }
-
- private static Object getPackageName(String owner) {
- String pkg = owner;
- int index = pkg.lastIndexOf('/');
- if (index != -1) {
- pkg = pkg.substring(0, index);
- }
-
- return ClassContext.getFqcn(pkg);
- }
-
- private static boolean shouldSkip(File file) {
- // No need to do work on this library, which is included in pretty much all new ADT
- // projects
- if (file.getPath().endsWith("android-support-v4.jar")) { //$NON-NLS-1$
- return true;
- }
-
- return false;
- }
-
- private static class Candidate {
- private final String mReferencedIn;
- private final File mJarFile;
- private final String mClass;
-
- public Candidate(String className, String referencedIn, File jarFile) {
- mClass = className;
- mReferencedIn = referencedIn;
- mJarFile = jarFile;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
deleted file mode 100644
index b161699..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-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 com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.BinaryExpression;
-import lombok.ast.BinaryOperator;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.If;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.This;
-import lombok.ast.Throw;
-import lombok.ast.TypeReference;
-import lombok.ast.TypeReferencePart;
-import lombok.ast.UnaryExpression;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableReference;
-
-/**
- * Looks for performance issues in Java files, such as memory allocations during
- * drawing operations and using HashMap instead of SparseArray.
- */
-public class JavaPerformanceDetector extends Detector implements Detector.JavaScanner {
- /** Allocating objects during a paint method */
- public static final Issue PAINT_ALLOC = Issue.create(
- "DrawAllocation", //$NON-NLS-1$
- "Looks for memory allocations within drawing code",
-
- "You should avoid allocating objects during a drawing or layout operation. These " +
- "are called frequently, so a smooth UI can be interrupted by garbage collection " +
- "pauses caused by the object allocations.\n" +
- "\n" +
- "The way this is generally handled is to allocate the needed objects up front " +
- "and to reuse them for each drawing operation.\n" +
- "\n" +
- "Some methods allocate memory on your behalf (such as `Bitmap.create`), and these " +
- "should be handled in the same way.",
-
- Category.PERFORMANCE,
- 9,
- Severity.WARNING,
- JavaPerformanceDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Using HashMaps where SparseArray would be better */
- public static final Issue USE_SPARSEARRAY = Issue.create(
- "UseSparseArrays", //$NON-NLS-1$
- "Looks for opportunities to replace HashMaps with the more efficient SparseArray",
-
- "For maps where the keys are of type integer, it's typically more efficient to " +
- "use the Android `SparseArray` API. This check identifies scenarios where you might " +
- "want to consider using `SparseArray` instead of `HashMap` for better performance.\n" +
- "\n" +
- "This is *particularly* useful when the value types are primitives like ints, " +
- "where you can use `SparseIntArray` and avoid auto-boxing the values from `int` to " +
- "`Integer`.\n" +
- "\n" +
- "If you need to construct a `HashMap` because you need to call an API outside of " +
- "your control which requires a `Map`, you can suppress this warning using for " +
- "example the `@SuppressLint` annotation.",
-
- Category.PERFORMANCE,
- 4,
- Severity.WARNING,
- JavaPerformanceDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Using {@code new Integer()} instead of the more efficient {@code Integer.valueOf} */
- public static final Issue USE_VALUEOF = Issue.create(
- "UseValueOf", //$NON-NLS-1$
- "Looks for usages of \"new\" for wrapper classes which should use \"valueOf\" instead",
-
- "You should not call the constructor for wrapper classes directly, such as" +
- "`new Integer(42)`. Instead, call the `valueOf` factory method, such as " +
- "`Integer.valueOf(42)`. This will typically use less memory because common integers " +
- "such as 0 and 1 will share a single instance.",
-
- Category.PERFORMANCE,
- 4,
- Severity.WARNING,
- JavaPerformanceDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- static final String ON_MEASURE = "onMeasure"; //$NON-NLS-1$
- static final String ON_DRAW = "onDraw"; //$NON-NLS-1$
- static final String ON_LAYOUT = "onLayout"; //$NON-NLS-1$
- private static final String INT = "int"; //$NON-NLS-1$
- private static final String INTEGER = "Integer"; //$NON-NLS-1$
- private static final String BOOL = "boolean"; //$NON-NLS-1$
- private static final String BOOLEAN = "Boolean"; //$NON-NLS-1$
- private static final String LONG = "Long"; //$NON-NLS-1$
- private static final String CHARACTER = "Character"; //$NON-NLS-1$
- private static final String DOUBLE = "Double"; //$NON-NLS-1$
- private static final String FLOAT = "Float"; //$NON-NLS-1$
- private static final String HASH_MAP = "HashMap"; //$NON-NLS-1$
- private static final String SPARSE_ARRAY = "SparseArray"; //$NON-NLS-1$
- private static final String CANVAS = "Canvas"; //$NON-NLS-1$
- private static final String LAYOUT = "layout"; //$NON-NLS-1$
-
- /** Constructs a new {@link JavaPerformanceDetector} check */
- public JavaPerformanceDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- List<Class<? extends Node>> types = new ArrayList<Class<? extends Node>>(3);
- types.add(ConstructorInvocation.class);
- types.add(MethodDeclaration.class);
- types.add(MethodInvocation.class);
- return types;
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new PerformanceVisitor(context);
- }
-
- private static class PerformanceVisitor extends ForwardingAstVisitor {
- private final JavaContext mContext;
- private final boolean mCheckMaps;
- private final boolean mCheckAllocations;
- private final boolean mCheckValueOf;
- /** Whether allocations should be "flagged" in the current method */
- private boolean mFlagAllocations;
-
- public PerformanceVisitor(JavaContext context) {
- mContext = context;
-
- mCheckAllocations = context.isEnabled(PAINT_ALLOC);
- mCheckMaps = context.isEnabled(USE_SPARSEARRAY);
- mCheckValueOf = context.isEnabled(USE_VALUEOF);
- }
-
- @Override
- public boolean visitMethodDeclaration(MethodDeclaration node) {
- mFlagAllocations = isBlockedAllocationMethod(node);
-
- return super.visitMethodDeclaration(node);
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- String typeName = null;
- if (mCheckMaps) {
- TypeReference reference = node.astTypeReference();
- typeName = reference.astParts().last().astIdentifier().astValue();
- // TODO: Should we handle factory method constructions of HashMaps as well,
- // e.g. via Guava? This is a bit trickier since we need to infer the type
- // arguments from the calling context.
- if (typeName.equals(HASH_MAP)) {
- checkHashMap(node, reference);
- } else if (typeName.equals(SPARSE_ARRAY)) {
- checkSparseArray(node, reference);
- }
- }
-
- if (mCheckValueOf) {
- if (typeName == null) {
- TypeReference reference = node.astTypeReference();
- typeName = reference.astParts().last().astIdentifier().astValue();
- }
- if ((typeName.equals(INTEGER)
- || typeName.equals(BOOLEAN)
- || typeName.equals(FLOAT)
- || typeName.equals(CHARACTER)
- || typeName.equals(LONG)
- || typeName.equals(DOUBLE))
- && node.astTypeReference().astParts().size() == 1
- && node.astArguments().size() == 1) {
- String argument = node.astArguments().first().toString();
- mContext.report(USE_VALUEOF, node, mContext.getLocation(node),
- String.format("Use %1$s.valueOf(%2$s) instead", typeName, argument),
- null);
- }
- }
-
- if (mFlagAllocations && !(node.getParent() instanceof Throw) && mCheckAllocations) {
- // Make sure we're still inside the method declaration that marked
- // mInDraw as true, in case we've left it and we're in a static
- // block or something:
- Node method = node;
- while (method != null) {
- if (method instanceof MethodDeclaration) {
- break;
- }
- method = method.getParent();
- }
- if (method != null && isBlockedAllocationMethod(((MethodDeclaration) method))
- && !isLazilyInitialized(node)) {
- reportAllocation(node);
- }
- }
-
- return super.visitConstructorInvocation(node);
- }
-
- private void reportAllocation(Node node) {
- mContext.report(PAINT_ALLOC, node, mContext.getLocation(node),
- "Avoid object allocations during draw/layout operations (preallocate and " +
- "reuse instead)", null);
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (mFlagAllocations && node.astOperand() != null) {
- // Look for forbidden methods
- String methodName = node.astName().astValue();
- if (methodName.equals("createBitmap") //$NON-NLS-1$
- || methodName.equals("createScaledBitmap")) { //$NON-NLS-1$
- String operand = node.astOperand().toString();
- if (operand.equals("Bitmap") //$NON-NLS-1$
- || operand.equals("android.graphics.Bitmap")) { //$NON-NLS-1$
- if (!isLazilyInitialized(node)) {
- reportAllocation(node);
- }
- }
- } else if (methodName.startsWith("decode")) { //$NON-NLS-1$
- // decodeFile, decodeByteArray, ...
- String operand = node.astOperand().toString();
- if (operand.equals("BitmapFactory") //$NON-NLS-1$
- || operand.equals("android.graphics.BitmapFactory")) { //$NON-NLS-1$
- if (!isLazilyInitialized(node)) {
- reportAllocation(node);
- }
- }
- } else if (methodName.equals("getClipBounds")) { //$NON-NLS-1$
- if (node.astArguments().isEmpty()) {
- mContext.report(PAINT_ALLOC, node, mContext.getLocation(node),
- "Avoid object allocations during draw operations: Use " +
- "Canvas.getClipBounds(Rect) instead of Canvas.getClipBounds() " +
- "which allocates a temporary Rect", null);
- }
- }
- }
-
- return super.visitMethodInvocation(node);
- }
-
- /**
- * Check whether the given invocation is done as a lazy initialization,
- * e.g. {@code if (foo == null) foo = new Foo();}.
- * <p>
- * This tries to also handle the scenario where the check is on some
- * <b>other</b> variable - e.g.
- * <pre>
- * if (foo == null) {
- * foo == init1();
- * bar = new Bar();
- * }
- * </pre>
- * or
- * <pre>
- * if (!initialized) {
- * initialized = true;
- * bar = new Bar();
- * }
- * </pre>
- */
- private static boolean isLazilyInitialized(Node node) {
- Node curr = node.getParent();
- while (curr != null) {
- if (curr instanceof MethodDeclaration) {
- return false;
- } else if (curr instanceof If) {
- If ifNode = (If) curr;
- // See if the if block represents a lazy initialization:
- // compute all variable names seen in the condition
- // (e.g. for "if (foo == null || bar != foo)" the result is "foo,bar"),
- // and then compute all variables assigned to in the if body,
- // and if there is an overlap, we'll consider the whole if block
- // guarded (so lazily initialized and an allocation we won't complain
- // about.)
- List<String> assignments = new ArrayList<String>();
- AssignmentTracker visitor = new AssignmentTracker(assignments);
- ifNode.astStatement().accept(visitor);
- if (!assignments.isEmpty()) {
- List<String> references = new ArrayList<String>();
- addReferencedVariables(references, ifNode.astCondition());
- if (!references.isEmpty()) {
- SetView<String> intersection = Sets.intersection(
- new HashSet<String>(assignments),
- new HashSet<String>(references));
- return !intersection.isEmpty();
- }
- }
- return false;
-
- }
- curr = curr.getParent();
- }
-
- return false;
- }
-
- /** Adds any variables referenced in the given expression into the given list */
- private static void addReferencedVariables(Collection<String> variables,
- Expression expression) {
- if (expression instanceof BinaryExpression) {
- BinaryExpression binary = (BinaryExpression) expression;
- addReferencedVariables(variables, binary.astLeft());
- addReferencedVariables(variables, binary.astRight());
- } else if (expression instanceof UnaryExpression) {
- UnaryExpression unary = (UnaryExpression) expression;
- addReferencedVariables(variables, unary.astOperand());
- } else if (expression instanceof VariableReference) {
- VariableReference reference = (VariableReference) expression;
- variables.add(reference.astIdentifier().astValue());
- } else if (expression instanceof Select) {
- Select select = (Select) expression;
- if (select.astOperand() instanceof This) {
- variables.add(select.astIdentifier().astValue());
- }
- }
- }
-
- /**
- * Returns whether the given method declaration represents a method
- * where allocating objects is not allowed for performance reasons
- */
- private static boolean isBlockedAllocationMethod(MethodDeclaration node) {
- return isOnDrawMethod(node) || isOnMeasureMethod(node) || isOnLayoutMethod(node)
- || isLayoutMethod(node);
- }
-
- /**
- * Returns true if this method looks like it's overriding android.view.View's
- * {@code protected void onDraw(Canvas canvas)}
- */
- private static boolean isOnDrawMethod(MethodDeclaration node) {
- if (ON_DRAW.equals(node.astMethodName().astValue())) {
- StrictListAccessor<VariableDefinition, MethodDeclaration> parameters =
- node.astParameters();
- if (parameters != null && parameters.size() == 1) {
- VariableDefinition arg0 = parameters.first();
- TypeReferencePart type = arg0.astTypeReference().astParts().last();
- String typeName = type.getTypeName();
- if (typeName.equals(CANVAS)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns true if this method looks like it's overriding
- * android.view.View's
- * {@code protected void onLayout(boolean changed, int left, int top,
- * int right, int bottom)}
- */
- private static boolean isOnLayoutMethod(MethodDeclaration node) {
- if (ON_LAYOUT.equals(node.astMethodName().astValue())) {
- StrictListAccessor<VariableDefinition, MethodDeclaration> parameters =
- node.astParameters();
- if (parameters != null && parameters.size() == 5) {
- Iterator<VariableDefinition> iterator = parameters.iterator();
- if (!iterator.hasNext()) {
- return false;
- }
-
- // Ensure that the argument list matches boolean, int, int, int, int
- TypeReferencePart type = iterator.next().astTypeReference().astParts().last();
- if (!type.getTypeName().equals(BOOL) || !iterator.hasNext()) {
- return false;
- }
- for (int i = 0; i < 4; i++) {
- type = iterator.next().astTypeReference().astParts().last();
- if (!type.getTypeName().equals(INT)) {
- return false;
- }
- if (!iterator.hasNext()) {
- return i == 3;
- }
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns true if this method looks like it's overriding android.view.View's
- * {@code protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)}
- */
- private static boolean isOnMeasureMethod(MethodDeclaration node) {
- if (ON_MEASURE.equals(node.astMethodName().astValue())) {
- StrictListAccessor<VariableDefinition, MethodDeclaration> parameters =
- node.astParameters();
- if (parameters != null && parameters.size() == 2) {
- VariableDefinition arg0 = parameters.first();
- VariableDefinition arg1 = parameters.last();
- TypeReferencePart type1 = arg0.astTypeReference().astParts().last();
- TypeReferencePart type2 = arg1.astTypeReference().astParts().last();
- return INT.equals(type1.getTypeName()) && INT.equals(type2.getTypeName());
- }
- }
-
- return false;
- }
-
- /**
- * Returns true if this method looks like it's overriding android.view.View's
- * {@code public void layout(int l, int t, int r, int b)}
- */
- private static boolean isLayoutMethod(MethodDeclaration node) {
- if (LAYOUT.equals(node.astMethodName().astValue())) {
- StrictListAccessor<VariableDefinition, MethodDeclaration> parameters =
- node.astParameters();
- if (parameters != null && parameters.size() == 4) {
- Iterator<VariableDefinition> iterator = parameters.iterator();
- for (int i = 0; i < 4; i++) {
- if (!iterator.hasNext()) {
- return false;
- }
- VariableDefinition next = iterator.next();
- TypeReferencePart type = next.astTypeReference().astParts().last();
- if (!INT.equals(type.getTypeName())) {
- return false;
- }
- }
- return true;
- }
- }
-
- return false;
- }
-
-
- /**
- * Checks whether the given constructor call and type reference refers
- * to a HashMap constructor call that is eligible for replacement by a
- * SparseArray call instead
- */
- private void checkHashMap(ConstructorInvocation node, TypeReference reference) {
- // reference.hasTypeArguments returns false where it should not
- StrictListAccessor<TypeReference, TypeReference> types = reference.getTypeArguments();
- if (types != null && types.size() == 2) {
- TypeReference first = types.first();
- if (first.getTypeName().equals(INTEGER)) {
- String valueType = types.last().getTypeName();
- if (valueType.equals(INTEGER)) {
- mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node),
- "Use new SparseIntArray(...) instead for better performance",
- null);
- } else if (valueType.equals(BOOLEAN)) {
- mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node),
- "Use new SparseBooleanArray(...) instead for better performance",
- null);
- } else if (valueType.equals(LONG) && mContext.getProject().getMinSdk() >= 17) {
- mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node),
- "Use new SparseLongArray(...) instead for better performance",
- null);
- } else {
- mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node),
- String.format(
- "Use new SparseArray<%1$s>(...) instead for better performance",
- valueType),
- null);
- }
- }
- }
- }
-
- private void checkSparseArray(ConstructorInvocation node, TypeReference reference) {
- // reference.hasTypeArguments returns false where it should not
- StrictListAccessor<TypeReference, TypeReference> types = reference.getTypeArguments();
- if (types != null && types.size() == 1) {
- TypeReference first = types.first();
- String valueType = first.getTypeName();
- if (valueType.equals(INTEGER)) {
- mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node),
- "Use new SparseIntArray(...) instead for better performance",
- null);
- } else if (valueType.equals(BOOLEAN)) {
- mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node),
- "Use new SparseBooleanArray(...) instead for better performance",
- null);
- } else if (valueType.equals(LONG) && mContext.getProject().getMinSdk() >= 17) {
- mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node),
- "Use new SparseLongArray(...) instead for better performance",
- null);
- }
- }
- }
- }
-
- /** Visitor which records variable names assigned into */
- private static class AssignmentTracker extends ForwardingAstVisitor {
- private final Collection<String> mVariables;
-
- public AssignmentTracker(Collection<String> variables) {
- mVariables = variables;
- }
-
- @Override
- public boolean visitBinaryExpression(BinaryExpression node) {
- BinaryOperator operator = node.astOperator();
- if (operator == BinaryOperator.ASSIGN || operator == BinaryOperator.OR_ASSIGN) {
- Expression left = node.astLeft();
- String variable;
- if (left instanceof Select && ((Select) left).astOperand() instanceof This) {
- variable = ((Select) left).astIdentifier().astValue();
- } else {
- variable = left.toString();
- }
- mVariables.add(variable);
- }
-
- return super.visitBinaryExpression(node);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java
deleted file mode 100644
index 283e244..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LabelForDetector.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_HINT;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LABEL_FOR;
-import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.EDIT_TEXT;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.tools.lint.detector.api.LintUtils.stripIdPrefix;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Detector which finds unlabeled text fields
- */
-public class LabelForDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "LabelFor", //$NON-NLS-1$
- "Ensures that text fields are marked with a labelFor attribute",
- "Text fields should be labelled with a `labelFor` attribute, " +
- "provided your `minSdkVersion` is at least 17.\n" +
- "\n" +
- "If your view is labeled but by a label in a different layout which " +
- "includes this one, just suppress this warning from lint.",
- Category.A11Y,
- 2,
- Severity.WARNING,
- LabelForDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- private Set<String> mLabels;
- private List<Element> mTextFields;
-
- /** Constructs a new {@link LabelForDetector} */
- public LabelForDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- @Nullable
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_LABEL_FOR);
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- EDIT_TEXT,
- AUTO_COMPLETE_TEXT_VIEW,
- MULTI_AUTO_COMPLETE_TEXT_VIEW
- );
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- if (mTextFields != null) {
- if (mLabels == null) {
- mLabels = Collections.emptySet();
- }
-
- for (Element element : mTextFields) {
- if (element.hasAttributeNS(ANDROID_URI, ATTR_HINT)) {
- continue;
- }
- String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
- boolean missing = true;
- if (mLabels.contains(id)) {
- missing = false;
- } else if (id.startsWith(NEW_ID_PREFIX)) {
- missing = !mLabels.contains(ID_PREFIX + stripIdPrefix(id));
- } else if (id.startsWith(ID_PREFIX)) {
- missing = !mLabels.contains(NEW_ID_PREFIX + stripIdPrefix(id));
- }
-
- if (missing) {
- XmlContext xmlContext = (XmlContext) context;
- Location location = xmlContext.getLocation(element);
- String message;
- if (id == null || id.isEmpty()) {
- message = "No label views point to this text field with a " +
- "labelFor attribute";
- } else {
- message = String.format("No label views point to this text field with " +
- "an android:labelFor=\"@+id/%1$s\" attribute", id);
- }
- xmlContext.report(ISSUE, element, location, message, null);
- }
-
- }
- }
-
- mLabels = null;
- mTextFields = null;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (mLabels == null) {
- mLabels = Sets.newHashSet();
- }
- mLabels.add(attribute.getValue());
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- // NOTE: This should NOT be checking *minSdkVersion*, but *targetSdkVersion*
- // or even buildTarget instead. However, there's a risk that this will flag
- // way too much and make the rule annoying until API 17 support becomes
- // more widespread, so for now limit the check to those projects *really*
- // working with 17. When API 17 reaches a certain amount of adoption, change
- // this to flag all apps supporting 17, including those supporting earlier
- // versions as well.
- if (context.getMainProject().getMinSdk() < 17) {
- return;
- }
-
- if (mTextFields == null) {
- mTextFields = new ArrayList<Element>();
- }
- mTextFields.add(element);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
deleted file mode 100644
index 3bde211..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/LocaleDetector.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.FORMAT_METHOD;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.Frame;
-import org.objectweb.asm.tree.analysis.SourceInterpreter;
-import org.objectweb.asm.tree.analysis.SourceValue;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-
-/**
- * Checks for errors related to locale handling
- */
-public class LocaleDetector extends Detector implements ClassScanner {
- /** Calling risky convenience methods */
- public static final Issue STRING_LOCALE = Issue.create(
- "DefaultLocale", //$NON-NLS-1$
- "Finds calls to locale-ambiguous String manipulation methods",
-
- "Calling `String#toLowerCase()` or `#toUpperCase()` *without specifying an " +
- "explicit locale* is a common source of bugs. The reason for that is that those " +
- "methods will use the current locale on the user's device, and even though the " +
- "code appears to work correctly when you are developing the app, it will fail " +
- "in some locales. For example, in the Turkish locale, the uppercase replacement " +
- "for `i` is *not* `I`.\n" +
- "\n" +
- "If you want the methods to just perform ASCII replacement, for example to convert " +
- "an enum name, call `String#toUpperCase(Locale.US)` instead. If you really want to " +
- "use the current locale, call `String#toUpperCase(Locale.getDefault())` instead.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- LocaleDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.CLASS_FILE)).setMoreInfo(
- "http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$
-
- /** Constructing SimpleDateFormat without an explicit locale */
- public static final Issue DATE_FORMAT = Issue.create(
- "SimpleDateFormat", //$NON-NLS-1$
- "Using SimpleDateFormat directly without an explicit locale",
-
- "Almost all callers should use `getDateInstance()`, `getDateTimeInstance()`, or " +
- "`getTimeInstance()` to get a ready-made instance of SimpleDateFormat suitable " +
- "for the user's locale. The main reason you'd create an instance this class " +
- "directly is because you need to format/parse a specific machine-readable format, " +
- "in which case you almost certainly want to explicitly ask for US to ensure that " +
- "you get ASCII digits (rather than, say, Arabic digits).\n" +
- "\n" +
- "Therefore, you should either use the form of the SimpleDateFormat constructor " +
- "where you pass in an explicit locale, such as Locale.US, or use one of the " +
- "get instance methods, or suppress this error if really know what you are doing.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- LocaleDetector.class,
- Scope.CLASS_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/reference/java/text/SimpleDateFormat.html"); //$NON-NLS-1$
-
- static final String DATE_FORMAT_OWNER = "java/text/SimpleDateFormat"; //$NON-NLS-1$
- private static final String STRING_OWNER = "java/lang/String"; //$NON-NLS-1$
-
- /** Constructs a new {@link LocaleDetector} */
- public LocaleDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(
- "toLowerCase", //$NON-NLS-1$
- "toUpperCase", //$NON-NLS-1$
- FORMAT_METHOD
- );
- }
-
- @Override
- @Nullable
- public List<String> getApplicableCallOwners() {
- return Collections.singletonList(DATE_FORMAT_OWNER);
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String owner = call.owner;
- String desc = call.desc;
- String name = call.name;
- if (owner.equals(DATE_FORMAT_OWNER)) {
- if (!name.equals(CONSTRUCTOR_NAME)) {
- return;
- }
- if (desc.equals("(Ljava/lang/String;Ljava/text/DateFormatSymbols;)V") //$NON-NLS-1$
- || desc.equals("()V") //$NON-NLS-1$
- || desc.equals("(Ljava/lang/String;)V")) { //$NON-NLS-1$
- Location location = context.getLocation(call);
- String message =
- "To get local formatting use getDateInstance(), getDateTimeInstance(), " +
- "or getTimeInstance(), or use new SimpleDateFormat(String template, " +
- "Locale locale) with for example Locale.US for ASCII dates.";
- context.report(DATE_FORMAT, method, call, location, message, null);
- }
- return;
- } else if (!owner.equals(STRING_OWNER)) {
- return;
- }
-
- if (name.equals(FORMAT_METHOD)) {
- // Only check the non-locale version of String.format
- if (!desc.equals("(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;")) { //$NON-NLS-1$
- return;
- }
- // Find the formatting string
- Analyzer analyzer = new Analyzer(new SourceInterpreter() {
- @Override
- public SourceValue newOperation(AbstractInsnNode insn) {
- if (insn.getOpcode() == Opcodes.LDC) {
- Object cst = ((LdcInsnNode) insn).cst;
- if (cst instanceof String) {
- return new StringValue(1, (String) cst);
- }
- }
- return super.newOperation(insn);
- }
- });
- try {
- Frame[] frames = analyzer.analyze(classNode.name, method);
- InsnList instructions = method.instructions;
- Frame frame = frames[instructions.indexOf(call)];
- if (frame.getStackSize() == 0) {
- return;
- }
- SourceValue stackValue = (SourceValue) frame.getStack(0);
- if (stackValue instanceof StringValue) {
- String format = ((StringValue) stackValue).getString();
- if (format != null && StringFormatDetector.isLocaleSpecific(format)) {
- Location location = context.getLocation(call);
- String message =
- "Implicitly using the default locale is a common source of bugs: " +
- "Use String.format(Locale, ...) instead";
- context.report(STRING_LOCALE, method, call, location, message, null);
- }
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- } else {
- if (desc.equals("()Ljava/lang/String;")) { //$NON-NLS-1$
- Location location = context.getLocation(call);
- String message = String.format(
- "Implicitly using the default locale is a common source of bugs: " +
- "Use %1$s(Locale) instead", name);
- context.report(STRING_LOCALE, method, call, location, message, null);
- }
- }
- }
-
- private static class StringValue extends SourceValue {
- private final String mString;
-
- StringValue(int size, String string) {
- super(size);
- mString = string;
- }
-
- String getString() {
- return mString;
- }
-
- @Override
- public int getSize() {
- return 1;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestOrderDetector.java
deleted file mode 100644
index 83fac97..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestOrderDetector.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PACKAGE;
-import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_PERMISSION;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-import static com.android.SdkConstants.TAG_USES_LIBRARY;
-import static com.android.SdkConstants.TAG_USES_PERMISSION;
-import static com.android.SdkConstants.TAG_USES_SDK;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Maps;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks for issues in AndroidManifest files such as declaring elements in the
- * wrong order.
- */
-public class ManifestOrderDetector extends Detector implements Detector.XmlScanner {
-
- /** Wrong order of elements in the manifest */
- public static final Issue ORDER = Issue.create(
- "ManifestOrder", //$NON-NLS-1$
- "Checks for manifest problems like <uses-sdk> after the <application> tag",
- "The <application> tag should appear after the elements which declare " +
- "which version you need, which features you need, which libraries you " +
- "need, and so on. In the past there have been subtle bugs (such as " +
- "themes not getting applied correctly) when the `<application>` tag appears " +
- "before some of these other elements, so it's best to order your " +
- "manifest in the logical dependency order.",
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Missing a {@code <uses-sdk>} element */
- public static final Issue USES_SDK = Issue.create(
- "UsesMinSdkAttributes", //$NON-NLS-1$
- "Checks that the minimum SDK and target SDK attributes are defined",
-
- "The manifest should contain a `<uses-sdk>` element which defines the " +
- "minimum API Level required for the application to run, " +
- "as well as the target version (the highest API level you have tested " +
- "the version for.)",
-
- Category.CORRECTNESS,
- 9,
- Severity.WARNING,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
-
- /** Using a targetSdkVersion that isn't recent */
- public static final Issue TARGET_NEWER = Issue.create(
- "OldTargetApi", //$NON-NLS-1$
- "Checks that the manifest specifies a targetSdkVersion that is recent",
-
- "When your application runs on a version of Android that is more recent than your " +
- "`targetSdkVersion` specifies that it has been tested with, various compatibility " +
- "modes kick in. This ensures that your application continues to work, but it may " +
- "look out of place. For example, if the `targetSdkVersion` is less than 14, your " +
- "app may get an option button in the UI.\n" +
- "\n" +
- "To fix this issue, set the `targetSdkVersion` to the highest available value. Then " +
- "test your app to make sure everything works correctly. You may want to consult " +
- "the compatibility notes to see what changes apply to each version you are adding " +
- "support for: " +
- "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE).setMoreInfo(
- "http://developer.android.com/reference/android/os/Build.VERSION_CODES.html"); //$NON-NLS-1$
-
- /** Using multiple {@code <uses-sdk>} elements */
- public static final Issue MULTIPLE_USES_SDK = Issue.create(
- "MultipleUsesSdk", //$NON-NLS-1$
- "Checks that the <uses-sdk> element appears at most once",
-
- "The `<uses-sdk>` element should appear just once; the tools will *not* merge the " +
- "contents of all the elements so if you split up the attributes across multiple " +
- "elements, only one of them will take effect. To fix this, just merge all the " +
- "attributes from the various elements into a single <uses-sdk> element.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
-
- /** Missing a {@code <uses-sdk>} element */
- public static final Issue WRONG_PARENT = Issue.create(
- "WrongManifestParent", //$NON-NLS-1$
- "Checks that various manifest elements are declared in the right place",
-
- "The `<uses-library>` element should be defined as a direct child of the " +
- "`<application>` tag, not the `<manifest>` tag or an `<activity>` tag. Similarly, " +
- "a `<uses-sdk>` tag much be declared at the root level, and so on. This check " +
- "looks for incorrect declaration locations in the manifest, and complains " +
- "if an element is found in the wrong place.",
-
- Category.CORRECTNESS,
- 6,
- Severity.FATAL,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
-
- /** Missing a {@code <uses-sdk>} element */
- public static final Issue DUPLICATE_ACTIVITY = Issue.create(
- "DuplicateActivity", //$NON-NLS-1$
- "Checks that an activity is registered only once in the manifest",
-
- "An activity should only be registered once in the manifest. If it is " +
- "accidentally registered more than once, then subtle errors can occur, " +
- "since attribute declarations from the two elements are not merged, so " +
- "you may accidentally remove previous declarations.",
-
- Category.CORRECTNESS,
- 5,
- Severity.ERROR,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Not explicitly defining allowBackup */
- public static final Issue ALLOW_BACKUP = Issue.create(
- "AllowBackup", //$NON-NLS-1$
- "Ensure that allowBackup is explicitly set in the application's manifest",
-
- "The allowBackup attribute determines if an application's data can be backed up " +
- "and restored. It is documented at " +
- "http://developer.android.com/reference/android/R.attr.html#allowBackup\n" +
- "\n" +
- "By default, this flag is set to `true`. When this flag is set to `true`, " +
- "application data can be backed up and restored by the user using `adb backup` " +
- "and `adb restore`.\n" +
- "\n" +
- "This may have security consequences for an application. `adb backup` allows " +
- "users who have enabled USB debugging to copy application data off of the " +
- "device. Once backed up, all application data can be read by the user. " +
- "`adb restore` allows creation of application data from a source specified " +
- "by the user. Following a restore, applications should not assume that the " +
- "data, file permissions, and directory permissions were created by the " +
- "application itself.\n" +
- "\n" +
- "Setting `allowBackup=\"false\"` opts an application out of both backup and " +
- "restore.\n" +
- "\n" +
- "To fix this warning, decide whether your application should support backup, " +
- "and explicitly set `android:allowBackup=(true|false)\"`",
-
- Category.SECURITY,
- 3,
- Severity.WARNING,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE).setMoreInfo(
- "http://developer.android.com/reference/android/R.attr.html#allowBackup");
-
- /** Conflicting permission names */
- public static final Issue UNIQUE_PERMISSION = Issue.create(
- "UniquePermission", //$NON-NLS-1$
- "Checks that permission names are unique",
-
- "The unqualified names or your permissions must be unique. The reason for this " +
- "is that at build time, the `aapt` tool will generate a class named `Manifest` " +
- "which contains a field for each of your permissions. These fields are named " +
- "using your permission unqualified names (i.e. the name portion after the last " +
- "dot).\n" +
- "\n" +
- "If more than one permission maps to the same field name, that field will " +
- "arbitrarily name just one of them.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Using a resource for attributes that do not allow it */
- public static final Issue SET_VERSION = Issue.create(
- "MissingVersion", //$NON-NLS-1$
- "Checks that the application name and version are set",
-
- "You should define the version information for your application.\n" +
- "`android:versionCode`: An integer value that represents the version of the " +
- "application code, relative to other versions.\n" +
- "\n" +
- "`android:versionName`: A string value that represents the release version of " +
- "the application code, as it should be shown to users.",
-
- Category.CORRECTNESS,
- 2,
- Severity.WARNING,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE).setMoreInfo(
- "http://developer.android.com/tools/publishing/versioning.html#appversioning");
-
- /** Using a resource for attributes that do not allow it */
- public static final Issue ILLEGAL_REFERENCE = Issue.create(
- "IllegalResourceRef", //$NON-NLS-1$
- "Checks for resource references where only literals are allowed",
-
- "For the `versionCode` attribute, you have to specify an actual integer " +
- "literal; you cannot use an indirection with a `@dimen/name` resource. " +
- "Similarly, the `versionName` attribute should be an actual string, not " +
- "a string resource url.",
-
- Category.CORRECTNESS,
- 8,
- Severity.WARNING,
- ManifestOrderDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Constructs a new {@link ManifestOrderDetector} check */
- public ManifestOrderDetector() {
- }
-
- private boolean mSeenApplication;
-
- /** Number of times we've seen the <uses-sdk> element */
- private int mSeenUsesSdk;
-
- /** Activities we've encountered */
- private final Set<String> mActivities = new HashSet<String>();
-
- /** Permission basenames */
- private Map<String, String> mPermissionNames;
-
- /** Package declared in the manifest */
- private String mPackage;
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- mSeenApplication = false;
- mSeenUsesSdk = 0;
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- XmlContext xmlContext = (XmlContext) context;
- Element element = xmlContext.document.getDocumentElement();
- if (element != null) {
- checkDocumentElement(xmlContext, element);
- }
-
- if (mSeenUsesSdk == 0 && context.isEnabled(USES_SDK)) {
- context.report(USES_SDK, Location.create(context.file),
- "Manifest should specify a minimum API level with " +
- "<uses-sdk android:minSdkVersion=\"?\" />; if it really supports " +
- "all versions of Android set it to 1.", null);
- }
- }
-
- private void checkDocumentElement(XmlContext context, Element element) {
- Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, "versionCode");//$NON-NLS-1$
- if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- context.report(ILLEGAL_REFERENCE, element, context.getLocation(element),
- "The android:versionCode cannot be a resource url, it must be "
- + "a literal integer", null);
- } else if (codeNode == null && context.isEnabled(SET_VERSION)) {
- context.report(SET_VERSION, element, context.getLocation(element),
- "Should set android:versionCode to specify the application version", null);
- }
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, "versionName");//$NON-NLS-1$
- if (nameNode != null && nameNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- context.report(ILLEGAL_REFERENCE, element, context.getLocation(element),
- "The android:versionName cannot be a resource url, it must be "
- + "a literal string", null);
- } else if (nameNode == null && context.isEnabled(SET_VERSION)) {
- context.report(SET_VERSION, element, context.getLocation(element),
- "Should set android:versionName to specify the application version", null);
- }
- }
-
- // ---- Implements Detector.XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_APPLICATION,
- TAG_USES_PERMISSION,
- TAG_PERMISSION,
- "permission-tree", //$NON-NLS-1$
- "permission-group", //$NON-NLS-1$
- TAG_USES_SDK,
- "uses-configuration", //$NON-NLS-1$
- "uses-feature", //$NON-NLS-1$
- "supports-screens", //$NON-NLS-1$
- "compatible-screens", //$NON-NLS-1$
- "supports-gl-texture", //$NON-NLS-1$
- TAG_USES_LIBRARY,
- TAG_ACTIVITY,
- TAG_SERVICE,
- TAG_PROVIDER,
- TAG_RECEIVER
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
- Node parentNode = element.getParentNode();
-
- if (tag.equals(TAG_USES_LIBRARY) || tag.equals(TAG_ACTIVITY) || tag.equals(TAG_SERVICE)
- || tag.equals(TAG_PROVIDER) || tag.equals(TAG_RECEIVER)) {
- if (!TAG_APPLICATION.equals(parentNode.getNodeName())
- && context.isEnabled(WRONG_PARENT)) {
- context.report(WRONG_PARENT, element, context.getLocation(element),
- String.format(
- "The <%1$s> element must be a direct child of the <application> element",
- tag), null);
- }
-
- if (tag.equals(TAG_ACTIVITY)) {
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (nameNode != null) {
- String name = nameNode.getValue();
- if (!name.isEmpty()) {
- if (name.charAt(0) == '.') {
- name = getPackage(element) + name;
- } else if (name.indexOf('.') == -1) {
- name = getPackage(element) + '.' + name;
- }
- if (mActivities.contains(name)) {
- String message = String.format(
- "Duplicate registration for activity %1$s", name);
- context.report(DUPLICATE_ACTIVITY, element,
- context.getLocation(nameNode), message, null);
- } else {
- mActivities.add(name);
- }
- }
- }
- }
-
- return;
- }
-
- if (parentNode != element.getOwnerDocument().getDocumentElement()
- && context.isEnabled(WRONG_PARENT)) {
- context.report(WRONG_PARENT, element, context.getLocation(element),
- String.format(
- "The <%1$s> element must be a direct child of the " +
- "<manifest> root element", tag), null);
- }
-
- if (tag.equals(TAG_USES_SDK)) {
- mSeenUsesSdk++;
-
- if (mSeenUsesSdk == 2) { // Only warn when we encounter the first one
- Location location = context.getLocation(element);
-
- // Link up *all* encountered locations in the document
- NodeList elements = element.getOwnerDocument().getElementsByTagName(TAG_USES_SDK);
- Location secondary = null;
- for (int i = elements.getLength() - 1; i >= 0; i--) {
- Element e = (Element) elements.item(i);
- if (e != element) {
- Location l = context.getLocation(e);
- l.setSecondary(secondary);
- l.setMessage("Also appears here");
- secondary = l;
- }
- }
- location.setSecondary(secondary);
-
- if (context.isEnabled(MULTIPLE_USES_SDK)) {
- context.report(MULTIPLE_USES_SDK, element, location,
- "There should only be a single <uses-sdk> element in the manifest:" +
- " merge these together", null);
- }
- return;
- }
-
- if (!element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
- if (context.isEnabled(USES_SDK)) {
- context.report(USES_SDK, element, context.getLocation(element),
- "<uses-sdk> tag should specify a minimum API level with " +
- "android:minSdkVersion=\"?\"", null);
- }
- } else {
- Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
- if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- context.report(ILLEGAL_REFERENCE, element, context.getLocation(element),
- "The android:minSdkVersion cannot be a resource url, it must be "
- + "a literal integer (or string if a preview codename)", null);
- }
- }
-
- if (!element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
- // Warn if not setting target SDK -- but only if the min SDK is somewhat
- // old so there's some compatibility stuff kicking in (such as the menu
- // button etc)
- if (context.isEnabled(USES_SDK)) {
- context.report(USES_SDK, element, context.getLocation(element),
- "<uses-sdk> tag should specify a target API level (the " +
- "highest verified version; when running on later versions, " +
- "compatibility behaviors may be enabled) with " +
- "android:targetSdkVersion=\"?\"", null);
- }
- } else if (context.isEnabled(TARGET_NEWER)){
- String target = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
- try {
- int api = Integer.parseInt(target);
- if (api < context.getClient().getHighestKnownApiLevel()) {
- context.report(TARGET_NEWER, element, context.getLocation(element),
- "Not targeting the latest versions of Android; compatibility " +
- "modes apply. Consider testing and updating this version. " +
- "Consult the android.os.Build.VERSION_CODES javadoc for details.",
- null);
- }
- } catch (NumberFormatException nufe) {
- // Ignore: AAPT will enforce this.
- }
- }
-
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
- if (nameNode != null && nameNode.getValue().startsWith(PREFIX_RESOURCE_REF)
- && context.isEnabled(ILLEGAL_REFERENCE)) {
- context.report(ILLEGAL_REFERENCE, element, context.getLocation(element),
- "The android:targetSdkVersion cannot be a resource url, it must be "
- + "a literal integer (or string if a preview codename)", null);
- }
- }
- if (tag.equals(TAG_PERMISSION)) {
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (nameNode != null) {
- String name = nameNode.getValue();
- String base = name.substring(name.lastIndexOf('.') + 1);
- if (mPermissionNames == null) {
- mPermissionNames = Maps.newHashMap();
- } else if (mPermissionNames.containsKey(base)) {
- String prevName = mPermissionNames.get(base);
- Location location = context.getLocation(nameNode);
- NodeList siblings = element.getParentNode().getChildNodes();
- for (int i = 0, n = siblings.getLength(); i < n; i++) {
- Node node = siblings.item(i);
- if (node == element) {
- break;
- } else if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element sibling = (Element) node;
- String suffix = '.' + base;
- if (sibling.getTagName().equals(TAG_PERMISSION)) {
- String b = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (b.endsWith(suffix)) {
- Location prevLocation = context.getLocation(node);
- prevLocation.setMessage("Previous permission here");
- location.setSecondary(prevLocation);
- break;
- }
-
- }
- }
- }
-
- String message = String.format("Permission name %1$s is not unique " +
- "(appears in both %2$s and %3$s)", base, prevName, name);
- context.report(UNIQUE_PERMISSION, element, location, message, null);
- }
-
- mPermissionNames.put(base, name);
- }
- }
-
- if (tag.equals(TAG_APPLICATION)) {
- mSeenApplication = true;
- if (!element.hasAttributeNS(ANDROID_URI, SdkConstants.ATTR_ALLOW_BACKUP)
- && context.isEnabled(ALLOW_BACKUP)
- && context.getMainProject().getMinSdk() >= 4) {
- context.report(ALLOW_BACKUP, element, context.getLocation(element),
- "Should explicitly set android:allowBackup to true or " +
- "false (it's true by default, and that can have some security " +
- "implications for the application's data)", null);
- }
- } else if (mSeenApplication) {
- if (context.isEnabled(ORDER)) {
- context.report(ORDER, element, context.getLocation(element),
- String.format("<%1$s> tag appears after <application> tag", tag), null);
- }
-
- // Don't complain for *every* element following the <application> tag
- mSeenApplication = false;
- }
- }
-
- private String getPackage(Element element) {
- if (mPackage == null) {
- mPackage = element.getOwnerDocument().getDocumentElement().getAttribute(ATTR_PACKAGE);
- }
-
- return mPackage;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
deleted file mode 100644
index cc85be6..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ManifestTypoDetector.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.TAG_USES_FEATURE;
-import static com.android.SdkConstants.TAG_USES_LIBRARY;
-import static com.android.SdkConstants.TAG_USES_PERMISSION;
-import static com.android.SdkConstants.TAG_USES_SDK;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Maps;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-/**
- * Checks for typos in AndroidManifest files.
- */
-public class ManifestTypoDetector extends Detector implements Detector.XmlScanner {
-
- private static final String REPORT_FORMAT
- = "<%1$s> looks like a typo; did you mean <%2$s> ?";
-
- /* The match pattern for <uses-sdk> */
- private static final Pattern PATTERN_USES_SDK
- = Pattern.compile("^use.*sdk"); //$NON-NLS-1$
-
- /* The match pattern for <uses-permission> */
- private static final Pattern PATTERN_USES_PERMISSION
- = Pattern.compile("^use.*permission"); //$NON-NLS-1$
-
- /* The match pattern for <uses-feature> */
- private static final Pattern PATTERN_USES_FEATURE
- = Pattern.compile("^use.*feature"); //$NON-NLS-1$
-
- /* The match pattern for <uses-library> */
- private static final Pattern PATTERN_USES_LIBRARY
- = Pattern.compile("^use.*library"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ManifestTypo", //$NON-NLS-1$
- "Checks for manifest typos",
-
- "This check looks through the manifest, and if it finds any tags " +
- "that look like likely misspellings, they are flagged.",
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- ManifestTypoDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Constructs a new {@link ManifestTypoDetector} check */
- public ManifestTypoDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return XmlScanner.ALL;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
-
- if (!tag.startsWith("use")) { //$NON-NLS-1$
- return;
- }
-
- if (PATTERN_USES_SDK.matcher(tag).find() && !TAG_USES_SDK.equals(tag)) {
- context.report(ISSUE, context.getLocation(element),
- String.format(REPORT_FORMAT, tag, TAG_USES_SDK), null);
- }
-
- if (PATTERN_USES_PERMISSION.matcher(tag).find() && !TAG_USES_PERMISSION.equals(tag)) {
- context.report(ISSUE, context.getLocation(element),
- String.format(REPORT_FORMAT, tag, TAG_USES_PERMISSION), null);
- }
-
- if (PATTERN_USES_FEATURE.matcher(tag).find() && !TAG_USES_FEATURE.equals(tag)) {
- context.report(ISSUE, context.getLocation(element),
- String.format(REPORT_FORMAT, tag, TAG_USES_FEATURE), null);
- }
-
- if (PATTERN_USES_LIBRARY.matcher(tag).find() && !TAG_USES_LIBRARY.equals(tag)) {
- context.report(ISSUE, context.getLocation(element),
- String.format(REPORT_FORMAT, tag, TAG_USES_LIBRARY), null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
deleted file mode 100644
index 8709852..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MathDetector.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-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 org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Looks for usages of {@link java.lang.Math} methods which can be replaced with
- * {@code android.util.FloatMath} methods to avoid casting.
- */
-public class MathDetector extends Detector implements Detector.ClassScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "FloatMath", //$NON-NLS-1$
- "Suggests replacing android.util.FloatMath calls with java.lang.Math",
-
- "In older versions of Android, using android.util.FloatMath was recommended " +
- "for performance reasons when operating on floats. However, on modern hardware " +
- "doubles are just as fast as float (though they take more memory), and in " +
- "recent versions of Android, FloatMath is actually slower than using java.lang.Math " +
- "due to the way the JIT optimizes java.lang.Math. Therefore, you should use " +
- "Math instead of FloatMath if you are only targeting Froyo and above.",
-
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- MathDetector.class,
- Scope.CLASS_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
-
- /** Constructs a new {@link MathDetector} check */
- public MathDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(
- "sin", //$NON-NLS-1$
- "cos", //$NON-NLS-1$
- "ceil", //$NON-NLS-1$
- "sqrt", //$NON-NLS-1$
- "floor" //$NON-NLS-1$
- );
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String owner = call.owner;
-
- if (owner.equals("android/util/FloatMath") //$NON-NLS-1$
- && context.getProject().getMinSdk() >= 8) {
- String message = String.format(
- "Use java.lang.Math#%1$s instead of android.util.FloatMath#%1$s() " +
- "since it is faster as of API 8", call.name);
- context.report(ISSUE, method, call, context.getLocation(call), message, null /*data*/);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
deleted file mode 100644
index 1b76c03..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BACKGROUND;
-import static com.android.SdkConstants.ATTR_FOREGROUND;
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.FRAME_LAYOUT;
-import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.R_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-
-/**
- * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
- */
-public class MergeRootFrameLayoutDetector extends LayoutDetector implements Detector.JavaScanner {
- /**
- * Set of layouts that we want to enable the warning for. We only warn for
- * {@code <FrameLayout>}'s that are the root of a layout included from
- * another layout, or directly referenced via a {@code setContentView} call.
- */
- private Set<String> mWhitelistedLayouts;
-
- /**
- * Set of pending [layout, location] pairs where the given layout is a
- * FrameLayout that perhaps should be replaced by a {@code <merge>} tag (if
- * the layout is included or set as the content view. This must be processed
- * after the whole project has been scanned since the set of includes etc
- * can be encountered after the included layout.
- */
- private List<Pair<String, Location.Handle>> mPending;
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "MergeRootFrame", //$NON-NLS-1$
- "Checks whether a root <FrameLayout> can be replaced with a <merge> tag",
- "If a `<FrameLayout>` is the root of a layout and does not provide background " +
- "or padding etc, it can often be replaced with a `<merge>` tag which is slightly " +
- "more efficient. Note that this depends on context, so make sure you understand " +
- "how the `<merge>` tag works before proceeding.",
- Category.PERFORMANCE,
- 4,
- Severity.WARNING,
- MergeRootFrameLayoutDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE)).setMoreInfo(
- "http://android-developers.blogspot.com/2009/03/android-layout-tricks-3-optimize-by.html"); //$NON-NLS-1$
-
- /** Constructs a new {@link MergeRootFrameLayoutDetector} */
- public MergeRootFrameLayoutDetector() {
- }
-
- @Override
- @NonNull
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return LintUtils.isXmlFile(file) || LintUtils.endsWith(file.getName(), DOT_JAVA);
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mPending != null && mWhitelistedLayouts != null) {
- // Process all the root FrameLayouts that are eligible, and generate
- // suggestions for <merge> replacements for any layouts that are included
- // from other layouts
- for (Pair<String, Handle> pair : mPending) {
- String layout = pair.getFirst();
- if (mWhitelistedLayouts.contains(layout)) {
- Handle handle = pair.getSecond();
-
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ISSUE, (Node) clientData)) {
- return;
- }
- }
-
- Location location = handle.resolve();
- context.report(ISSUE, location,
- "This <FrameLayout> can be replaced with a <merge> tag", null);
- }
- }
- }
- }
-
- // Implements XmlScanner
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(VIEW_INCLUDE, FRAME_LAYOUT);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
- if (tag.equals(VIEW_INCLUDE)) {
- String layout = element.getAttribute(ATTR_LAYOUT); // NOTE: Not in android: namespace
- if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { // Ignore @android:layout/ layouts
- layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length());
- whiteListLayout(layout);
- }
- } else {
- assert tag.equals(FRAME_LAYOUT);
- if (LintUtils.isRootElement(element) &&
- ((isWidthFillParent(element) && isHeightFillParent(element)) ||
- !element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY))
- && !element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)
- && !element.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND)
- && !hasPadding(element)) {
- String layout = LintUtils.getLayoutName(context.file);
- Handle handle = context.parser.createLocationHandle(context, element);
- handle.setClientData(element);
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- if (mPending == null) {
- mPending = new ArrayList<Pair<String,Handle>>();
- }
- mPending.add(Pair.of(layout, handle));
- }
- }
- }
-
- private void whiteListLayout(String layout) {
- if (mWhitelistedLayouts == null) {
- mWhitelistedLayouts = new HashSet<String>();
- }
- mWhitelistedLayouts.add(layout);
- }
-
- // Implements JavaScanner
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("setContentView"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- StrictListAccessor<Expression, MethodInvocation> argumentList = node.astArguments();
- if (argumentList != null && argumentList.size() == 1) {
- Expression argument = argumentList.first();
- if (argument instanceof Select) {
- String expression = argument.toString();
- if (expression.startsWith(R_LAYOUT_RESOURCE_PREFIX)) {
- whiteListLayout(expression.substring(R_LAYOUT_RESOURCE_PREFIX.length()));
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
deleted file mode 100644
index 8002c40..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PACKAGE;
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.VIEW_FRAGMENT;
-import static com.android.SdkConstants.VIEW_TAG;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.SdkUtils;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks to ensure that classes referenced in the manifest actually exist and are included
- *
- */
-public class MissingClassDetector extends LayoutDetector implements ClassScanner {
- /** Manifest-referenced classes missing from the project or libraries */
- public static final Issue MISSING = Issue.create(
- "MissingRegistered", //$NON-NLS-1$
- "Ensures that classes referenced in the manifest are present in the project or libraries",
-
- "If a class is referenced in the manifest, it must also exist in the project (or in one " +
- "of the libraries included by the project. This check helps uncover typos in " +
- "registration names, or attempts to rename or move classes without updating the " +
- "manifest file properly.",
-
- Category.CORRECTNESS,
- 8,
- Severity.ERROR,
- MissingClassDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE, Scope.JAVA_LIBRARIES, Scope.RESOURCE_FILE))
- .setMoreInfo("http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
-
- /** Are activity, service, receiver etc subclasses instantiatable? */
- public static final Issue INSTANTIATABLE = Issue.create(
- "Instantiatable", //$NON-NLS-1$
- "Ensures that classes registered in the manifest file are instantiatable",
-
- "Activities, services, broadcast receivers etc. registered in the manifest file " +
- "must be \"instantiatable\" by the system, which means that the class must be " +
- "public, it must have an empty public constructor, and if it's an inner class, " +
- "it must be a static inner class.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- MissingClassDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- /** Is the right character used for inner class separators? */
- public static final Issue INNERCLASS = Issue.create(
- "InnerclassSeparator", //$NON-NLS-1$
- "Ensures that inner classes are referenced using '$' instead of '.' in class names",
-
- "When you reference an inner class in a manifest file, you must use '$' instead of '.' " +
- "as the separator character, i.e. Outer$Inner instead of Outer.Inner.\n" +
- "\n" +
- "(If you get this warning for a class which is not actually an inner class, it's " +
- "because you are using uppercase characters in your package name, which is not " +
- "conventional.)",
-
- Category.CORRECTNESS,
- 3,
- Severity.WARNING,
- MissingClassDetector.class,
- Scope.MANIFEST_SCOPE);
-
- private Map<String, Location.Handle> mReferencedClasses;
- private Set<String> mCustomViews;
- private boolean mHaveClasses;
-
- /** Constructs a new {@link MissingClassDetector} */
- public MissingClassDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return ALL;
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES || folderType == ResourceFolderType.LAYOUT;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String pkg = null;
- Node classNameNode;
- String className;
- String tag = element.getTagName();
- ResourceFolderType folderType = context.getResourceFolderType();
- if (folderType == ResourceFolderType.VALUES) {
- if (!tag.equals(TAG_STRING)) {
- return;
- }
- Attr attr = element.getAttributeNode(ATTR_NAME);
- if (attr == null) {
- return;
- }
- className = attr.getValue();
- classNameNode = attr;
- } else if (folderType == ResourceFolderType.LAYOUT) {
- if (tag.indexOf('.') > 0) {
- className = tag;
- classNameNode = element;
- } else if (tag.equals(VIEW_FRAGMENT) || tag.equals(VIEW_TAG)) {
- Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (attr == null) {
- attr = element.getAttributeNode(ATTR_CLASS);
- }
- if (attr == null) {
- return;
- }
- className = attr.getValue();
- classNameNode = attr;
- } else {
- return;
- }
- } else {
- // Manifest file
- if (TAG_APPLICATION.equals(tag)
- || TAG_ACTIVITY.equals(tag)
- || TAG_SERVICE.equals(tag)
- || TAG_RECEIVER.equals(tag)
- || TAG_PROVIDER.equals(tag)) {
- Element root = element.getOwnerDocument().getDocumentElement();
- pkg = root.getAttribute(ATTR_PACKAGE);
- Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (attr == null) {
- return;
- }
- className = attr.getValue();
- classNameNode = attr;
- } else {
- return;
- }
- }
- if (className.isEmpty()) {
- return;
- }
-
- String fqcn;
- int dotIndex = className.indexOf('.');
- if (dotIndex <= 0) {
- if (pkg == null) {
- return; // value file
- }
- if (dotIndex == 0) {
- fqcn = pkg + className;
- } else {
- // According to the <activity> manifest element documentation, this is not
- // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
- // but it appears in manifest files and appears to be supported by the runtime
- // so handle this in code as well:
- fqcn = pkg + '.' + className;
- }
- } else { // else: the class name is already a fully qualified class name
- fqcn = className;
- // Only look for fully qualified tracker names in analytics files
- if (folderType == ResourceFolderType.VALUES
- && !SdkUtils.endsWith(context.file.getPath(), "analytics.xml")) { //$NON-NLS-1$
- return;
- }
- }
-
- String signature = ClassContext.getInternalName(fqcn);
- if (signature.isEmpty() || signature.startsWith(ANDROID_PKG_PREFIX)) {
- return;
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- Handle handle = null;
- if (!context.getDriver().isSuppressed(MISSING, element)) {
- if (mReferencedClasses == null) {
- mReferencedClasses = Maps.newHashMapWithExpectedSize(16);
- mCustomViews = Sets.newHashSetWithExpectedSize(8);
- }
-
- handle = context.parser.createLocationHandle(context, element);
- mReferencedClasses.put(signature, handle);
- if (folderType == ResourceFolderType.LAYOUT && !tag.equals(VIEW_FRAGMENT)) {
- mCustomViews.add(ClassContext.getInternalName(className));
- }
- }
-
- if (signature.indexOf('$') != -1 && pkg != null) {
- if (className.indexOf('$') == -1 && className.indexOf('.', 1) > 0) {
- boolean haveUpperCase = false;
- for (int i = 0, n = pkg.length(); i < n; i++) {
- if (Character.isUpperCase(pkg.charAt(i))) {
- haveUpperCase = true;
- break;
- }
- }
- if (!haveUpperCase) {
- String message = "Use '$' instead of '.' for inner classes " +
- "(or use only lowercase letters in package names)";
- Location location = context.getLocation(classNameNode);
- context.report(INNERCLASS, element, location, message, null);
- }
- }
-
- // The internal name contains a $ which means it's an inner class.
- // The conversion from fqcn to internal name is a bit ambiguous:
- // "a.b.C.D" usually means "inner class D in class C in package a.b".
- // However, it can (see issue 31592) also mean class D in package "a.b.C".
- // To make sure we don't falsely complain that foo/Bar$Baz doesn't exist,
- // in case the user has actually created a package named foo/Bar and a proper
- // class named Baz, we register *both* into the reference map.
- // When generating errors we'll look for these an rip them back out if
- // it looks like one of the two variations have been seen.
- if (handle != null) {
- signature = signature.replace('$', '/');
- mReferencedClasses.put(signature, handle);
- }
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (!context.getProject().isLibrary() && mHaveClasses
- && mReferencedClasses != null && !mReferencedClasses.isEmpty()
- && context.getDriver().getScope().contains(Scope.CLASS_FILE)) {
- List<String> classes = new ArrayList<String>(mReferencedClasses.keySet());
- Collections.sort(classes);
- for (String owner : classes) {
- Location.Handle handle = mReferencedClasses.get(owner);
- String fqcn = ClassContext.getFqcn(owner);
-
- String signature = ClassContext.getInternalName(fqcn);
- if (!signature.equals(owner)) {
- if (!mReferencedClasses.containsKey(signature)) {
- continue;
- }
- } else {
- signature = signature.replace('$', '/');
- if (!mReferencedClasses.containsKey(signature)) {
- continue;
- }
- }
- mReferencedClasses.remove(owner);
-
- // Ignore usages of platform libraries
- if (owner.startsWith("android/")) { //$NON-NLS-1$
- continue;
- }
-
- String message = String.format(
- "Class referenced in the manifest, %1$s, was not found in the " +
- "project or the libraries", fqcn);
- Location location = handle.resolve();
- File parentFile = location.getFile().getParentFile();
- if (parentFile != null) {
- String parent = parentFile.getName();
- ResourceFolderType type = ResourceFolderType.getFolderType(parent);
- if (type == ResourceFolderType.LAYOUT) {
- message = String.format(
- "Class referenced in the layout file, %1$s, was not found in "
- + "the project or the libraries", fqcn);
- } else if (type == ResourceFolderType.VALUES) {
- message = String.format(
- "Class referenced in the analytics file, %1$s, was not "
- + "found in the project or the libraries", fqcn);
- }
- }
-
- context.report(MISSING, location, message, null);
- }
- }
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if (!mHaveClasses && !context.isFromClassLibrary()
- && context.getProject() == context.getMainProject()) {
- mHaveClasses = true;
- }
- String curr = classNode.name;
- if (mReferencedClasses != null && mReferencedClasses.containsKey(curr)) {
- boolean isCustomView = mCustomViews.contains(curr);
- mReferencedClasses.remove(curr);
-
- // Ensure that the class is public, non static and has a null constructor!
-
- if ((classNode.access & Opcodes.ACC_PUBLIC) == 0) {
- context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
- "This class should be public (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- return;
- }
-
- if (classNode.name.indexOf('$') != -1 && !LintUtils.isStaticInnerClass(classNode)) {
- context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
- "This inner class should be static (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- return;
- }
-
- boolean hasDefaultConstructor = false;
- @SuppressWarnings("rawtypes") // ASM API
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- if (method.name.equals(CONSTRUCTOR_NAME)) {
- if (method.desc.equals("()V")) { //$NON-NLS-1$
- // The constructor must be public
- if ((method.access & Opcodes.ACC_PUBLIC) != 0) {
- hasDefaultConstructor = true;
- } else {
- context.report(INSTANTIATABLE, context.getLocation(method, classNode),
- "The default constructor must be public",
- null);
- // Also mark that we have a constructor so we don't complain again
- // below since we've already emitted a more specific error related
- // to the default constructor
- hasDefaultConstructor = true;
- }
- }
- }
- }
-
- if (!hasDefaultConstructor && !isCustomView && !context.isFromClassLibrary()
- && context.getProject().getReportIssues()) {
- context.report(INSTANTIATABLE, context.getLocation(classNode), String.format(
- "This class should provide a default constructor (a public " +
- "constructor with no arguments) (%1$s)",
- ClassContext.createSignature(classNode.name, null, null)),
- null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java
deleted file mode 100644
index 1b79600..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingIdDetector.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_TAG;
-import static com.android.SdkConstants.VIEW_FRAGMENT;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check which looks for missing id's in views where they are probably needed
- */
-public class MissingIdDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "MissingId", //$NON-NLS-1$
- "Ensures that XML tags like <fragment> specify an id or tag attribute",
-
- "If you do not specify an android:id or an android:tag attribute on a " +
- "<fragment> element, then if the activity is restarted (for example for " +
- "an orientation rotation) you may lose state. From the fragment " +
- "documentation:\n" +
- "\n" +
- "\"Each fragment requires a unique identifier that the system can use " +
- "to restore the fragment if the activity is restarted (and which you can " +
- "use to capture the fragment to perform transactions, such as remove it). " +
- "* Supply the android:id attribute with a unique ID.\n" +
- "* Supply the android:tag attribute with a unique string.\n" +
- "If you provide neither of the previous two, the system uses the ID of the " +
- "container view.",
-
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- MissingIdDetector.class,
- Scope.RESOURCE_FILE_SCOPE)
- .setMoreInfo("http://developer.android.com/guide/components/fragments.html"); //$NON-NLS-1$
-
- /** Constructs a new {@link MissingIdDetector} */
- public MissingIdDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(VIEW_FRAGMENT);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (!element.hasAttributeNS(ANDROID_URI, ATTR_ID) &&
- !element.hasAttributeNS(ANDROID_URI, ATTR_TAG)) {
- context.report(ISSUE, element, context.getLocation(element),
- "This <fragment> tag should specify an id or a tag to preserve state " +
- "across activity restarts", null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
deleted file mode 100644
index 0b6ab02..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.AUTO_URI;
-import static com.android.SdkConstants.URI_PREFIX;
-import static com.android.SdkConstants.XMLNS_PREFIX;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Checks for various issues related to XML namespaces
- */
-public class NamespaceDetector extends LayoutDetector {
- /** Typos in the namespace */
- public static final Issue TYPO = Issue.create(
- "NamespaceTypo", //$NON-NLS-1$
- "Looks for misspellings in namespace declarations",
-
- "Accidental misspellings in namespace declarations can lead to some very " +
- "obscure error messages. This check looks for potential misspellings to " +
- "help track these down.",
- Category.CORRECTNESS,
- 8,
- Severity.WARNING,
- NamespaceDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Unused namespace declarations */
- public static final Issue UNUSED = Issue.create(
- "UnusedNamespace", //$NON-NLS-1$
- "Finds unused namespaces in XML documents",
-
- "Unused namespace declarations take up space and require processing that is not " +
- "necessary",
-
- Category.PERFORMANCE,
- 1,
- Severity.WARNING,
- NamespaceDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Using custom namespace attributes in a library project */
- public static final Issue CUSTOMVIEW = Issue.create(
- "LibraryCustomView", //$NON-NLS-1$
- "Flags custom attributes in libraries, which must use the res-auto-namespace instead",
-
- "When using a custom view with custom attributes in a library project, the layout " +
- "must use the special namespace " + AUTO_URI + " instead of a URI which includes " +
- "the library project's own package. This will be used to automatically adjust the " +
- "namespace of the attributes when the library resources are merged into the " +
- "application project.",
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- NamespaceDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Prefix relevant for custom namespaces */
- private static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$
- private static final String XMLNS_A = "xmlns:a"; //$NON-NLS-1$
-
- private Map<String, Attr> mUnusedNamespaces;
- private boolean mCheckUnused;
-
- /** Constructs a new {@link NamespaceDetector} */
- public NamespaceDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- boolean haveCustomNamespace = false;
- Element root = document.getDocumentElement();
- NamedNodeMap attributes = root.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Node item = attributes.item(i);
- if (item.getNodeName().startsWith(XMLNS_PREFIX)) {
- String value = item.getNodeValue();
-
- if (!value.equals(ANDROID_URI)) {
- Attr attribute = (Attr) item;
-
- if (value.startsWith(URI_PREFIX)) {
- haveCustomNamespace = true;
- if (mUnusedNamespaces == null) {
- mUnusedNamespaces = new HashMap<String, Attr>();
- }
- mUnusedNamespaces.put(item.getNodeName().substring(XMLNS_PREFIX.length()),
- attribute);
- } else if (!value.startsWith("http://")) { //$NON-NLS-1$
- context.report(TYPO, attribute, context.getLocation(attribute),
- "Suspicious namespace: should start with http://", null);
-
- continue;
- }
-
- String name = attribute.getName();
- if (!name.equals(XMLNS_ANDROID) && !name.equals(XMLNS_A)) {
- // See if it looks like a typo
- int resIndex = value.indexOf("/res/"); //$NON-NLS-1$
- if (resIndex != -1 && value.length() + 5 > URI_PREFIX.length()) {
- String urlPrefix = value.substring(0, resIndex + 5);
- if (!urlPrefix.equals(URI_PREFIX) &&
- LintUtils.editDistance(URI_PREFIX, urlPrefix) <= 3) {
- String correctUri = URI_PREFIX + value.substring(resIndex + 5);
- context.report(TYPO, attribute, context.getLocation(attribute),
- String.format(
- "Possible typo in URL: was \"%1$s\", should " +
- "probably be \"%2$s\"",
- value, correctUri),
- null);
- }
- }
- continue;
- }
-
- if (!context.isEnabled(TYPO)) {
- continue;
- }
-
- if (name.equals(XMLNS_A)) {
- // For the "android" prefix we always assume that the namespace prefix
- // should be our expected prefix, but for the "a" prefix we make sure
- // that it's at least "close"; if you're bound it to something completely
- // different, don't complain.
- if (LintUtils.editDistance(ANDROID_URI, value) > 4) {
- continue;
- }
- }
-
- if (value.equalsIgnoreCase(ANDROID_URI)) {
- context.report(TYPO, attribute, context.getLocation(attribute),
- String.format(
- "URI is case sensitive: was \"%1$s\", expected \"%2$s\"",
- value, ANDROID_URI), null);
- } else {
- context.report(TYPO, attribute, context.getLocation(attribute),
- String.format(
- "Unexpected namespace URI bound to the \"android\" " +
- "prefix, was %1$s, expected %2$s", value, ANDROID_URI),
- null);
- }
- }
- }
- }
-
- if (haveCustomNamespace) {
- boolean checkCustomAttrs = context.isEnabled(CUSTOMVIEW) && context.getProject().isLibrary();
- mCheckUnused = context.isEnabled(UNUSED);
-
- if (checkCustomAttrs) {
- checkCustomNamespace(context, root);
- }
- checkElement(context, root);
-
- if (mCheckUnused && !mUnusedNamespaces.isEmpty()) {
- for (Map.Entry<String, Attr> entry : mUnusedNamespaces.entrySet()) {
- String prefix = entry.getKey();
- Attr attribute = entry.getValue();
- context.report(UNUSED, attribute, context.getLocation(attribute),
- String.format("Unused namespace %1$s", prefix), null);
- }
- }
- }
- }
-
- private static void checkCustomNamespace(XmlContext context, Element element) {
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- if (attribute.getName().startsWith(XMLNS_PREFIX)) {
- String uri = attribute.getValue();
- if (uri != null && !uri.isEmpty() && uri.startsWith(URI_PREFIX)
- && !uri.equals(ANDROID_URI)) {
- context.report(CUSTOMVIEW, attribute, context.getLocation(attribute),
- "When using a custom namespace attribute in a library project, " +
- "use the namespace \"" + AUTO_URI + "\" instead.", null);
- }
- }
- }
- }
-
- private void checkElement(XmlContext context, Node node) {
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- if (mCheckUnused) {
- NamedNodeMap attributes = node.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- String prefix = attribute.getPrefix();
- if (prefix != null) {
- mUnusedNamespaces.remove(prefix);
- }
- }
- }
-
- NodeList childNodes = node.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- checkElement(context, childNodes.item(i));
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
deleted file mode 100644
index 4650a8f..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.GALLERY;
-import static com.android.SdkConstants.GRID_VIEW;
-import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
-import static com.android.SdkConstants.LIST_VIEW;
-import static com.android.SdkConstants.SCROLL_VIEW;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
- */
-public class NestedScrollingWidgetDetector extends LayoutDetector {
- private int mVisitingHorizontalScroll;
- private int mVisitingVerticalScroll;
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "NestedScrolling", //$NON-NLS-1$
- "Checks whether a scrolling widget has any nested scrolling widgets within",
- // TODO: Better description!
- "A scrolling widget such as a `ScrollView` should not contain any nested " +
- "scrolling widgets since this has various usability issues",
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- NestedScrollingWidgetDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link NestedScrollingWidgetDetector} */
- public NestedScrollingWidgetDetector() {
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- mVisitingHorizontalScroll = 0;
- mVisitingVerticalScroll = 0;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- SCROLL_VIEW,
- LIST_VIEW,
- GRID_VIEW,
- // Horizontal
- GALLERY,
- HORIZONTAL_SCROLL_VIEW
- );
- }
-
- private Element findOuterScrollingWidget(Node node, boolean vertical) {
- Collection<String> applicableElements = getApplicableElements();
- while (node != null) {
- if (node instanceof Element) {
- Element element = (Element) node;
- String tagName = element.getTagName();
- if (applicableElements.contains(tagName)
- && vertical == isVerticalScroll(element)) {
- return element;
- }
- }
-
- node = node.getParentNode();
- }
-
- return null;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- boolean vertical = isVerticalScroll(element);
- if (vertical) {
- mVisitingVerticalScroll++;
- } else {
- mVisitingHorizontalScroll++;
- }
-
- if (mVisitingHorizontalScroll > 1 || mVisitingVerticalScroll > 1) {
- Element parent = findOuterScrollingWidget(element.getParentNode(), vertical);
- if (parent != null) {
- String format;
- if (mVisitingVerticalScroll > 1) {
- format = "The vertically scrolling %1$s should not contain another " +
- "vertically scrolling widget (%2$s)";
- } else {
- format = "The horizontally scrolling %1$s should not contain another " +
- "horizontally scrolling widget (%2$s)";
- }
- String msg = String.format(format, parent.getTagName(), element.getTagName());
- context.report(ISSUE, element, context.getLocation(element), msg, null);
- }
- }
- }
-
- @Override
- public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) {
- if (isVerticalScroll(element)) {
- mVisitingVerticalScroll--;
- assert mVisitingVerticalScroll >= 0;
- } else {
- mVisitingHorizontalScroll--;
- assert mVisitingHorizontalScroll >= 0;
- }
- }
-
- private static boolean isVerticalScroll(Element element) {
- String view = element.getTagName();
- if (view.equals(GALLERY) || view.equals(HORIZONTAL_SCROLL_VIEW)) {
- return false;
- } else {
- // This method should only be called with one of the 5 widget types
- // listed in getApplicableElements
- assert view.equals(SCROLL_VIEW) || view.equals(LIST_VIEW) || view.equals(GRID_VIEW);
- return true;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java
deleted file mode 100644
index e970572..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-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 java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-
-/** Detector looking for text messages sent to an unlocalized phone number. */
-public class NonInternationalizedSmsDetector extends Detector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "UnlocalizedSms", //$NON-NLS-1$
- "Looks for code sending text messages to unlocalized phone numbers",
-
- "SMS destination numbers must start with a country code or the application code " +
- "must ensure that the SMS is only sent when the user is in the same country as " +
- "the receiver.",
-
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- NonInternationalizedSmsDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
-
- /** Constructs a new {@link NonInternationalizedSmsDetector} check */
- public NonInternationalizedSmsDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- List<String> methodNames = new ArrayList<String>(2);
- methodNames.add("sendTextMessage"); //$NON-NLS-1$
- methodNames.add("sendMultipartTextMessage"); //$NON-NLS-1$
- return methodNames;
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- assert node.astName().astValue().equals("sendTextMessage") || //$NON-NLS-1$
- node.astName().astValue().equals("sendMultipartTextMessage"); //$NON-NLS-1$
- if (node.astOperand() == null) {
- // "sendTextMessage"/"sendMultipartTextMessage" in the code with no operand
- return;
- }
-
- StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
- if (args.size() == 5) {
- Expression destinationAddress = args.first();
- if (destinationAddress instanceof StringLiteral) {
- String number = ((StringLiteral) destinationAddress).astValue();
-
- if (!number.startsWith("+")) { //$NON-NLS-1$
- context.report(ISSUE, node, context.getLocation(destinationAddress),
- "To make sure the SMS can be sent by all users, please start the SMS number " +
- "with a + and a country code or restrict the code invocation to people in the country " +
- "you are targeting.",
- null);
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
deleted file mode 100644
index 559a7ad..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ABSOLUTE_LAYOUT;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING;
-import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_HORIZONTAL;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_IN_PARENT;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_VERTICAL;
-import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
-import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN;
-import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
-import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN;
-import static com.android.SdkConstants.ATTR_LAYOUT_SPAN;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_WEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_LAYOUT_X;
-import static com.android.SdkConstants.ATTR_LAYOUT_Y;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.GRID_LAYOUT;
-import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.TABLE_ROW;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-import static com.android.SdkConstants.VIEW_MERGE;
-import static com.android.SdkConstants.VIEW_TAG;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.IDomParser;
-import com.android.tools.lint.client.api.SdkInfo;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Looks for layout params on views that are "obsolete" - may have made sense
- * when the view was added but there is a different layout parent now which does
- * not use the given layout params.
- */
-public class ObsoleteLayoutParamsDetector extends LayoutDetector {
- /** Usage of deprecated views or attributes */
- public static final Issue ISSUE = Issue.create(
- "ObsoleteLayoutParam", //$NON-NLS-1$
- "Looks for layout params that are not valid for the given parent layout",
- "The given layout_param is not defined for the given layout, meaning it has no " +
- "effect. This usually happens when you change the parent layout or move view " +
- "code around without updating the layout params. This will cause useless " +
- "attribute processing at runtime, and is misleading for others reading the " +
- "layout so the parameter should be removed.",
- Category.PERFORMANCE,
- 6,
- Severity.WARNING,
- ObsoleteLayoutParamsDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /**
- * Set of layout parameter names that are considered valid no matter what so
- * no other checking is necessary - such as layout_width and layout_height.
- */
- private static final Set<String> VALID = new HashSet<String>(10);
-
- /**
- * Mapping from a layout parameter name (local name only) to the defining
- * ViewGroup. Note that it's possible for the same name to be defined by
- * multiple ViewGroups - but it turns out this is extremely rare (the only
- * examples are layout_column defined by both TableRow and GridLayout, and
- * layout_gravity defined by many layouts) so rather than handle this with
- * every single layout attribute pointing to a list, this is just special
- * cased instead.
- */
- private static final Map<String, String> PARAM_TO_VIEW = new HashMap<String, String>(28);
-
- static {
- // Available (mostly) everywhere: No check
- VALID.add(ATTR_LAYOUT_WIDTH);
- VALID.add(ATTR_LAYOUT_HEIGHT);
-
- // The layout_gravity isn't "global" but it's defined on many of the most
- // common layouts (FrameLayout, LinearLayout and GridLayout) so we don't
- // currently check for it. In order to do this we'd need to make the map point
- // to lists rather than individual layouts or we'd need a bunch of special cases
- // like the one done for layout_column below.
- VALID.add(ATTR_LAYOUT_GRAVITY);
-
- // From ViewGroup.MarginLayoutParams
- VALID.add(ATTR_LAYOUT_MARGIN_LEFT);
- VALID.add(ATTR_LAYOUT_MARGIN_RIGHT);
- VALID.add(ATTR_LAYOUT_MARGIN_TOP);
- VALID.add(ATTR_LAYOUT_MARGIN_BOTTOM);
- VALID.add(ATTR_LAYOUT_MARGIN);
-
- // Absolute Layout
- PARAM_TO_VIEW.put(ATTR_LAYOUT_X, ABSOLUTE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_Y, ABSOLUTE_LAYOUT);
-
- // Linear Layout
- PARAM_TO_VIEW.put(ATTR_LAYOUT_WEIGHT, LINEAR_LAYOUT);
-
- // Grid Layout
- PARAM_TO_VIEW.put(ATTR_LAYOUT_COLUMN, GRID_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_COLUMN_SPAN, GRID_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ROW, GRID_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ROW_SPAN, GRID_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ROW_SPAN, GRID_LAYOUT);
-
- // Table Layout
- // ATTR_LAYOUT_COLUMN is defined for both GridLayout and TableLayout,
- // so we don't want to do
- // PARAM_TO_VIEW.put(ATTR_LAYOUT_COLUMN, TABLE_ROW);
- // here since it would wipe out the above GridLayout registration.
- // Since this is the only case where there is a conflict (in addition to layout_gravity
- // which is defined in many places), rather than making the map point to lists
- // this specific case is just special cased below, look for ATTR_LAYOUT_COLUMN.
- PARAM_TO_VIEW.put(ATTR_LAYOUT_SPAN, TABLE_ROW);
-
- // Relative Layout
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_LEFT, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_RIGHT, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_TOP, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_BOTTOM, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_TOP, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_LEFT, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ALIGN_BASELINE, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_CENTER_IN_PARENT, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_CENTER_VERTICAL, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_CENTER_HORIZONTAL, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_TO_RIGHT_OF, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_TO_LEFT_OF, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_BELOW, RELATIVE_LAYOUT);
- PARAM_TO_VIEW.put(ATTR_LAYOUT_ABOVE, RELATIVE_LAYOUT);
- }
-
- /**
- * Map from an included layout to all the including contexts (each including
- * context is a pair of a file containing the include to the parent tag at
- * the included location)
- */
- private Map<String, List<Pair<File, String>>> mIncludes;
-
- /**
- * List of pending include checks. When a layout parameter attribute is
- * found on a root element, or on a child of a {@code merge} root tag, then
- * we want to check across layouts whether the including context (the parent
- * of the include tag) is valid for this attribute. We cannot check this
- * immediately because we are processing the layouts in an arbitrary order
- * so the included layout may be seen before the including layout and so on.
- * Therefore, we stash these attributes to be checked after we're done. Each
- * pair is a pair of an attribute name to be checked, and the file that
- * attribute is referenced in.
- */
- private final List<Pair<String, Location.Handle>> mPending =
- new ArrayList<Pair<String,Location.Handle>>();
-
- /** Constructs a new {@link ObsoleteLayoutParamsDetector} */
- public ObsoleteLayoutParamsDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(VIEW_INCLUDE);
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String name = attribute.getLocalName();
- if (name != null && name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)
- && ANDROID_URI.equals(attribute.getNamespaceURI())) {
- if (VALID.contains(name)) {
- return;
- }
-
- String parent = PARAM_TO_VIEW.get(name);
- if (parent != null) {
- Element viewElement = attribute.getOwnerElement();
- Node layoutNode = viewElement.getParentNode();
- if (layoutNode == null || layoutNode.getNodeType() != Node.ELEMENT_NODE) {
- // This is a layout attribute on a root element; this presumably means
- // that this layout is included so check the included layouts to make
- // sure at least one included context is valid for this layout_param.
- // We can't do that yet since we may be processing the include tag to
- // this layout after the layout itself. Instead, stash a work order...
- if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
- IDomParser parser = context.parser;
- Location.Handle handle = parser.createLocationHandle(context, attribute);
- handle.setClientData(attribute);
- mPending.add(Pair.of(name, handle));
- }
-
- return;
- }
-
- String parentTag = ((Element) layoutNode).getTagName();
- if (parentTag.equals(VIEW_MERGE)) {
- // This is a merge which means we need to check the including contexts,
- // wherever they are. This has to be done after all the files have been
- // scanned since we are not processing the files in any particular order.
- if (context.getScope().contains(Scope.ALL_RESOURCE_FILES)) {
- IDomParser parser = context.parser;
- Location.Handle handle = parser.createLocationHandle(context, attribute);
- handle.setClientData(attribute);
- mPending.add(Pair.of(name, handle));
- }
-
- return;
- }
-
- if (!isValidParamForParent(context, name, parent, parentTag)) {
- if (name.equals(ATTR_LAYOUT_COLUMN)
- && isValidParamForParent(context, name, TABLE_ROW, parentTag)) {
- return;
- }
- context.report(ISSUE, attribute, context.getLocation(attribute),
- String.format("Invalid layout param in a %1$s: %2$s", parentTag, name),
- null);
- }
- } else {
- // We could warn about unknown layout params but this might be brittle if
- // new params are added or if people write custom ones; this is just a log
- // for us to track these and update the check as necessary:
- //context.client.log(null,
- // String.format("Unrecognized layout param '%1$s'", name));
- }
- }
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String layout = element.getAttribute(ATTR_LAYOUT);
- if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { // Ignore @android:layout/ layouts
- layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length());
-
- Node parent = element.getParentNode();
- if (parent.getNodeType() == Node.ELEMENT_NODE) {
- String tag = parent.getNodeName();
- if (tag.indexOf('.') == -1 && !tag.equals(VIEW_MERGE)) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- if (mIncludes == null) {
- mIncludes = new HashMap<String, List<Pair<File, String>>>();
- }
- List<Pair<File, String>> includes = mIncludes.get(layout);
- if (includes == null) {
- includes = new ArrayList<Pair<File, String>>();
- mIncludes.put(layout, includes);
- }
- includes.add(Pair.of(context.file, tag));
- }
- }
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mIncludes == null) {
- return;
- }
-
- for (Pair<String, Location.Handle> pending : mPending) {
- Handle handle = pending.getSecond();
- Location location = handle.resolve();
- File file = location.getFile();
- String layout = file.getName();
- if (layout.endsWith(DOT_XML)) {
- layout = layout.substring(0, layout.length() - DOT_XML.length());
- }
-
- List<Pair<File, String>> includes = mIncludes.get(layout);
- if (includes == null) {
- // Nobody included this file
- continue;
- }
-
- String name = pending.getFirst();
- String parent = PARAM_TO_VIEW.get(name);
- if (parent == null) {
- continue;
- }
-
- boolean isValid = false;
- for (Pair<File, String> include : includes) {
- String parentTag = include.getSecond();
- if (isValidParamForParent(context, name, parent, parentTag)) {
- isValid = true;
- break;
- } else if (!isValid && name.equals(ATTR_LAYOUT_COLUMN)
- && isValidParamForParent(context, name, TABLE_ROW, parentTag)) {
- isValid = true;
- break;
- }
- }
-
- if (!isValid) {
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ISSUE, (Node) clientData)) {
- return;
- }
- }
-
- StringBuilder sb = new StringBuilder(40);
- for (Pair<File, String> include : includes) {
- if (sb.length() > 0) {
- sb.append(", "); //$NON-NLS-1$
- }
- File from = include.getFirst();
- String parentTag = include.getSecond();
- sb.append(String.format("included from within a %1$s in %2$s",
- parentTag,
- from.getParentFile().getName() + File.separator + from.getName()));
- }
- String message = String.format("Invalid layout param '%1$s' (%2$s)",
- name, sb.toString());
- // TODO: Compute applicable scope node
- context.report(ISSUE, location, message, null);
- }
- }
- }
-
- /**
- * Checks whether the given layout parameter name is valid for the given
- * parent tag assuming it has the given current parent tag
- */
- private static boolean isValidParamForParent(Context context, String name, String parent,
- String parentTag) {
- if (parentTag.indexOf('.') != -1 || parentTag.equals(VIEW_TAG)) {
- // Custom tag: We don't know whether it extends one of the builtin
- // types where the layout param is valid, so don't complain
- return true;
- }
-
- SdkInfo sdk = context.getSdkInfo();
-
- if (!parentTag.equals(parent)) {
- String tag = sdk.getParentViewName(parentTag);
- while (tag != null) {
- if (tag.equals(parent)) {
- return true;
- }
- tag = sdk.getParentViewName(tag);
- }
-
- return false;
- }
-
- return true;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
deleted file mode 100644
index 2a07a86..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OnClickDetector.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_ON_CLICK;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.base.Joiner;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Node;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Checks for missing onClick handlers
- */
-public class OnClickDetector extends LayoutDetector implements ClassScanner {
- /** Missing onClick handlers */
- public static final Issue ISSUE = Issue.create(
- "OnClick", //$NON-NLS-1$
- "Ensures that onClick attribute values refer to real methods",
-
- "The `onClick` attribute value should be the name of a method in this View's context " +
- "to invoke when the view is clicked. This name must correspond to a public method " +
- "that takes exactly one parameter of type `View`.\n" +
- "\n" +
- "Must be a string value, using '\\;' to escape characters such as '\\n' or " +
- "'\\uxxxx' for a unicode character.",
- Category.CORRECTNESS,
- 10,
- Severity.ERROR,
- OnClickDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.CLASS_FILE));
-
- private Map<String, Location.Handle> mNames;
- private Map<String, List<String>> mSimilar;
- private boolean mHaveBytecode;
-
- /** Constructs a new {@link OnClickDetector} */
- public OnClickDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mNames != null && !mNames.isEmpty() && mHaveBytecode) {
- List<String> names = new ArrayList<String>(mNames.keySet());
- Collections.sort(names);
- LintDriver driver = context.getDriver();
- for (String name : names) {
- Handle handle = mNames.get(name);
-
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (driver.isSuppressed(ISSUE, (Node) clientData)) {
- continue;
- }
- }
-
- Location location = handle.resolve();
- String message = String.format(
- "Corresponding method handler 'public void %1$s(android.view.View)' not found",
- name);
- List<String> similar = mSimilar != null ? mSimilar.get(name) : null;
- if (similar != null) {
- Collections.sort(similar);
- message += String.format(" (did you mean %1$s ?)", Joiner.on(", ").join(similar));
- }
- context.report(ISSUE, location, message, null);
- }
- }
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_ON_CLICK);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String value = attribute.getValue();
- if (value.isEmpty() || value.trim().isEmpty()) {
- context.report(ISSUE, attribute, context.getLocation(attribute),
- "onClick attribute value cannot be empty", null);
- } else if (!value.equals(value.trim())) {
- context.report(ISSUE, attribute, context.getLocation(attribute),
- "There should be no whitespace around attribute values", null);
- } else if (!value.startsWith(PREFIX_RESOURCE_REF)) { // Not resolved
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- if (mNames == null) {
- mNames = new HashMap<String, Location.Handle>();
- }
- Handle handle = context.parser.createLocationHandle(context, attribute);
- handle.setClientData(attribute);
-
- // Replace unicode characters with the actual value since that's how they
- // appear in the ASM signatures
- if (value.contains("\\u")) { //$NON-NLS-1$
- Pattern pattern = Pattern.compile("\\\\u(\\d\\d\\d\\d)"); //$NON-NLS-1$
- Matcher matcher = pattern.matcher(value);
- StringBuilder sb = new StringBuilder(value.length());
- int remainder = 0;
- while (matcher.find()) {
- sb.append(value.substring(0, matcher.start()));
- String unicode = matcher.group(1);
- int hex = Integer.parseInt(unicode, 16);
- sb.append((char) hex);
- remainder = matcher.end();
- }
- sb.append(value.substring(remainder));
- value = sb.toString();
- }
-
- mNames.put(value, handle);
- }
- }
-
- // ---- Implements ClassScanner ----
-
- @SuppressWarnings("rawtypes")
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if (mNames == null) {
- // No onClick attributes in the XML files
- return;
- }
-
- mHaveBytecode = true;
-
- List methodList = classNode.methods;
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- boolean rightArguments = method.desc.equals("(Landroid/view/View;)V"); //$NON-NLS-1$
- if (!mNames.containsKey(method.name)) {
- if (rightArguments) {
- // See if there's a possible typo instead
- for (String n : mNames.keySet()) {
- if (LintUtils.editDistance(n, method.name) <= 2) {
- recordSimilar(n, classNode, method);
- break;
- }
- }
- }
- continue;
- }
-
- // TODO: Validate class hierarchy: should extend a context method
- // Longer term, also validate that it's in a layout that corresponds to
- // the given activity
-
- if (rightArguments){
- // Found: remove from list to be checked
- mNames.remove(method.name);
-
- // Make sure the method is public
- if ((method.access & Opcodes.ACC_PUBLIC) == 0) {
- Location location = context.getLocation(method, classNode);
- String message = String.format(
- "On click handler %1$s(View) must be public",
- method.name);
- context.report(ISSUE, location, message, null);
- } else if ((method.access & Opcodes.ACC_STATIC) != 0) {
- Location location = context.getLocation(method, classNode);
- String message = String.format(
- "On click handler %1$s(View) should not be static",
- method.name);
- context.report(ISSUE, location, message, null);
- }
-
- if (mNames.isEmpty()) {
- mNames = null;
- return;
- }
- }
- }
- }
-
- private void recordSimilar(String name, ClassNode classNode, MethodNode method) {
- if (mSimilar == null) {
- mSimilar = new HashMap<String, List<String>>();
- }
- List<String> list = mSimilar.get(name);
- if (list == null) {
- list = new ArrayList<String>();
- mSimilar.put(name, list);
- }
-
- String signature = ClassContext.createSignature(classNode.name, method.name, method.desc);
- list.add(signature);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
deleted file mode 100644
index 6627411..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverdrawDetector.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BACKGROUND;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PARENT;
-import static com.android.SdkConstants.ATTR_THEME;
-import static com.android.SdkConstants.ATTR_TILE_MODE;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.DRAWABLE_PREFIX;
-import static com.android.SdkConstants.NULL_RESOURCE;
-import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_BITMAP;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.TRANSPARENT_COLOR;
-import static com.android.SdkConstants.VALUE_DISABLED;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.CompilationUnit;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodInvocation;
-import lombok.ast.PackageDeclaration;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.VariableReference;
-
-/**
- * Check which looks for overdraw problems where view areas are painted and then
- * painted over, meaning that the bottom paint operation is a waste of time.
- */
-public class OverdrawDetector extends LayoutDetector implements Detector.JavaScanner {
- private static final String R_STYLE_PREFIX = "R.style."; //$NON-NLS-1$
- private static final String SET_THEME = "setTheme"; //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "Overdraw", //$NON-NLS-1$
- "Looks for overdraw issues (where a view is painted only to be fully painted over)",
- "If you set a background drawable on a root view, then you should use a " +
- "custom theme where the theme background is null. Otherwise, the theme background " +
- "will be painted first, only to have your custom background completely cover it; " +
- "this is called \"overdraw\".\n" +
- "\n" +
- "NOTE: This detector relies on figuring out which layouts are associated with " +
- "which activities based on scanning the Java code, and it's currently doing that " +
- "using an inexact pattern matching algorithm. Therefore, it can incorrectly " +
- "conclude which activity the layout is associated with and then wrongly complain " +
- "that a background-theme is hidden.\n" +
- "\n" +
- "If you want your custom background on multiple pages, then you should consider " +
- "making a custom theme with your custom background and just using that theme " +
- "instead of a root element background.\n" +
- "\n" +
- "Of course it's possible that your custom drawable is translucent and you want " +
- "it to be mixed with the background. However, you will get better performance " +
- "if you pre-mix the background with your drawable and use that resulting image or " +
- "color as a custom theme background instead.\n",
-
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- OverdrawDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES));
-
- /** Mapping from FQN activity names to theme names registered in the manifest */
- private Map<String, String> mActivityToTheme;
-
- /** The default theme declared in the manifest, or null */
- private String mManifestTheme;
-
- /** Mapping from layout name (not including {@code @layout/} prefix) to activity FQN */
- private Map<String, List<String>> mLayoutToActivity;
-
- /** List of theme names registered in the project which have blank backgrounds */
- private List<String> mBlankThemes;
-
- /** Set of activities registered in the manifest. We will limit the Java analysis to
- * these. */
- private Set<String> mActivities;
-
- /** List of drawable resources that are not flagged for overdraw (XML drawables
- * except for {@code <bitmap>} drawables without tiling) */
- private List<String> mValidDrawables;
-
- /**
- * List of pairs of (location, background drawable) corresponding to root elements
- * in layouts that define a given background drawable. These should be checked to
- * see if they are painting on top of a non-transparent theme.
- */
- private List<Pair<Location, String>> mRootAttributes;
-
- /** Constructs a new {@link OverdrawDetector} */
- public OverdrawDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- // Look in layouts for drawable resources
- return super.appliesTo(folderType)
- // and in resource files for theme definitions
- || folderType == ResourceFolderType.VALUES
- // and in drawable files for bitmap tiling modes
- || folderType == ResourceFolderType.DRAWABLE;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return LintUtils.isXmlFile(file) || LintUtils.endsWith(file.getName(), DOT_JAVA);
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- /** Is the given theme a "blank" theme (one not painting its background) */
- private boolean isBlankTheme(String name) {
- if (name.startsWith("@android:style/Theme_")) { //$NON-NLS-1$
- if (name.contains("NoFrame") //$NON-NLS-1$
- || name.contains("Theme_Wallpaper") //$NON-NLS-1$
- || name.contains("Theme_Holo_Wallpaper") //$NON-NLS-1$
- || name.contains("Theme_Translucent") //$NON-NLS-1$
- || name.contains("Theme_Dialog_NoFrame") //$NON-NLS-1$
- || name.contains("Theme_Holo_Dialog_Alert") //$NON-NLS-1$
- || name.contains("Theme_Holo_Light_Dialog_Alert") //$NON-NLS-1$
- || name.contains("Theme_Dialog_Alert") //$NON-NLS-1$
- || name.contains("Theme_Panel") //$NON-NLS-1$
- || name.contains("Theme_Light_Panel") //$NON-NLS-1$
- || name.contains("Theme_Holo_Panel") //$NON-NLS-1$
- || name.contains("Theme_Holo_Light_Panel")) { //$NON-NLS-1$
- return true;
- }
- }
-
- if (mBlankThemes != null && mBlankThemes.contains(name)) {
- return true;
- }
-
- return false;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mRootAttributes != null) {
- for (Pair<Location, String> pair : mRootAttributes) {
- Location location = pair.getFirst();
-
- Object clientData = location.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ISSUE, (Node) clientData)) {
- return;
- }
- }
-
- String layoutName = location.getFile().getName();
- if (endsWith(layoutName, DOT_XML)) {
- layoutName = layoutName.substring(0, layoutName.length() - DOT_XML.length());
- }
-
- String theme = getTheme(context, layoutName);
- if (theme == null || !isBlankTheme(theme)) {
- String drawable = pair.getSecond();
- String message = String.format(
- "Possible overdraw: Root element paints background %1$s with " +
- "a theme that also paints a background (inferred theme is %2$s)",
- drawable, theme);
- // TODO: Compute applicable scope node
- context.report(ISSUE, location, message, null);
- }
-
- }
- }
- }
-
- /** Return the theme to be used for the given layout */
- private String getTheme(Context context, String layoutName) {
- if (mActivityToTheme != null && mLayoutToActivity != null) {
- List<String> activities = mLayoutToActivity.get(layoutName);
- if (activities != null) {
- for (String activity : activities) {
- String theme = mActivityToTheme.get(activity);
- if (theme != null) {
- return theme;
- }
- }
- }
- }
-
- if (mManifestTheme != null) {
- return mManifestTheme;
- }
-
- Project project = context.getMainProject();
- int apiLevel = project.getTargetSdk();
- if (apiLevel == -1) {
- apiLevel = project.getMinSdk();
- }
-
- if (apiLevel >= 11) {
- return "@android:style/Theme.Holo"; //$NON-NLS-1$
- } else {
- return "@android:style/Theme"; //$NON-NLS-1$
- }
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- // Only consider the root element's background
- if (attribute.getOwnerDocument().getDocumentElement() == attribute.getOwnerElement()) {
- // If the drawable is a non-repeated pattern then the overdraw might be
- // intentional since the image isn't covering the whole screen
- String background = attribute.getValue();
- if (mValidDrawables != null && mValidDrawables.contains(background)) {
- return;
- }
-
- if (background.equals(TRANSPARENT_COLOR)) {
- return;
- }
-
- if (background.startsWith("@android:drawable/")) { //$NON-NLS-1$
- // We haven't had a chance to study the builtin drawables the way we
- // check the project local ones in scanBitmap() and beforeCheckFile(),
- // but many of these are not bitmaps, so ignore these
- return;
- }
-
- String name = context.file.getName();
- if (name.contains("list_") || name.contains("_item")) { //$NON-NLS-1$ //$NON-NLS-2$
- // Canonical list_item layout name: don't warn about these, it's
- // pretty common to want to paint custom list item backgrounds
- return;
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- Location location = context.getLocation(attribute);
- location.setClientData(attribute);
- if (mRootAttributes == null) {
- mRootAttributes = new ArrayList<Pair<Location,String>>();
- }
- mRootAttributes.add(Pair.of(location, attribute.getValue()));
- }
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(
- // Layouts: Look for background attributes on root elements for possible overdraw
- ATTR_BACKGROUND
- );
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- // Manifest: Look at theme registrations
- TAG_ACTIVITY,
- TAG_APPLICATION,
-
- // Resource files: Look at theme definitions
- TAG_STYLE,
-
- // Bitmaps
- TAG_BITMAP
- );
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- if (endsWith(context.file.getName(), DOT_XML)) {
- // Drawable XML files should not be considered for overdraw, except for <bitmap>'s.
- // The bitmap elements are handled in the scanBitmap() method; it will clear
- // out anything added by this method.
- File parent = context.file.getParentFile();
- ResourceFolderType type = ResourceFolderType.getFolderType(parent.getName());
- if (type == ResourceFolderType.DRAWABLE) {
- if (mValidDrawables == null) {
- mValidDrawables = new ArrayList<String>();
- }
- String resource = getDrawableResource(context.file);
- mValidDrawables.add(resource);
- }
- }
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
- if (tag.equals(TAG_STYLE)) {
- scanTheme(element);
- } else if (tag.equals(TAG_ACTIVITY)) {
- scanActivity(context, element);
- } else if (tag.equals(TAG_APPLICATION)) {
- if (element.hasAttributeNS(ANDROID_URI, ATTR_THEME)) {
- mManifestTheme = element.getAttributeNS(ANDROID_URI, ATTR_THEME);
- }
- } else if (tag.equals(TAG_BITMAP)) {
- scanBitmap(context, element);
- }
- }
-
- private static String getDrawableResource(File drawableFile) {
- String resource = drawableFile.getName();
- if (endsWith(resource, DOT_XML)) {
- resource = resource.substring(0, resource.length() - DOT_XML.length());
- }
- return DRAWABLE_PREFIX + resource;
- }
-
- private void scanBitmap(Context context, Element element) {
- String tileMode = element.getAttributeNS(ANDROID_URI, ATTR_TILE_MODE);
- if (!(tileMode.equals(VALUE_DISABLED) || tileMode.isEmpty())) {
- if (mValidDrawables != null) {
- String resource = getDrawableResource(context.file);
- mValidDrawables.remove(resource);
- }
- }
- }
-
- private void scanActivity(Context context, Element element) {
- String name = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (name.indexOf('$') != -1) {
- name = name.replace('$', '.');
- }
- if (name.startsWith(".")) { //$NON-NLS-1$
- String pkg = context.getProject().getPackage();
- if (pkg != null && !pkg.isEmpty()) {
- name = pkg + name;
- }
- }
-
- if (mActivities == null) {
- mActivities = new HashSet<String>();
- }
- mActivities.add(name);
-
- String theme = element.getAttributeNS(ANDROID_URI, ATTR_THEME);
- if (theme != null && !theme.isEmpty()) {
- if (mActivityToTheme == null) {
- mActivityToTheme = new HashMap<String, String>();
- }
- mActivityToTheme.put(name, theme.replace('.', '_'));
- }
- }
-
- private void scanTheme(Element element) {
- // Look for theme definitions, and record themes that provide a null background.
- String styleName = element.getAttribute(ATTR_NAME);
- String parent = element.getAttribute(ATTR_PARENT);
- if (parent == null) {
- // Eclipse DOM workaround
- parent = "";
- }
-
- if (parent.isEmpty()) {
- int index = styleName.lastIndexOf('.');
- if (index != -1) {
- parent = styleName.substring(0, index);
- }
- }
- parent = parent.replace('.', '_');
-
- String resource = STYLE_RESOURCE_PREFIX + styleName.replace('.', '_');
-
- NodeList items = element.getChildNodes();
- for (int i = 0, n = items.getLength(); i < n; i++) {
- if (items.item(i).getNodeType() == Node.ELEMENT_NODE) {
- Element item = (Element) items.item(i);
- String name = item.getAttribute(ATTR_NAME);
- if (name.equals("android:windowBackground")) { //$NON-NLS-1$
- NodeList textNodes = item.getChildNodes();
- for (int j = 0, m = textNodes.getLength(); j < m; j++) {
- Node textNode = textNodes.item(j);
- if (textNode.getNodeType() == Node.TEXT_NODE) {
- String text = textNode.getNodeValue();
- String trim = text.trim();
- if (!trim.isEmpty()) {
- if (trim.equals(NULL_RESOURCE)
- || trim.equals(TRANSPARENT_COLOR)
- || mValidDrawables != null
- && mValidDrawables.contains(trim)) {
- if (mBlankThemes == null) {
- mBlankThemes = new ArrayList<String>();
- }
- mBlankThemes.add(resource);
- }
- }
- }
- }
-
- return;
- }
- }
- }
-
- if (isBlankTheme(parent)) {
- if (mBlankThemes == null) {
- mBlankThemes = new ArrayList<String>();
- }
- mBlankThemes.add(resource);
- }
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
- // This detector does not specify specific node types; this means
- // that the infrastructure will run the full visitor on the compilation
- // unit rather than on individual nodes. This is important since this
- // detector relies on pruning (if it gets to a class declaration that is
- // not an activity, it skips everything inside).
- return null;
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- if (!context.getProject().getReportIssues()) {
- return null;
- }
- return new OverdrawVisitor();
- }
-
- private class OverdrawVisitor extends ForwardingAstVisitor {
- private static final String ACTIVITY = "Activity"; //$NON-NLS-1$
- private String mClassFqn;
-
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- String name = node.getDescription();
-
- if (mActivities != null && mActivities.contains(mClassFqn) || name.endsWith(ACTIVITY)
- || node.astExtending() != null &&
- node.astExtending().getDescription().endsWith(ACTIVITY)) {
- String packageName = "";
- if (node.getParent() instanceof CompilationUnit) {
- CompilationUnit compilationUnit = (CompilationUnit) node.getParent();
- PackageDeclaration packageDeclaration = compilationUnit.astPackageDeclaration();
- if (packageDeclaration == null) {
- // No package declaration: ignore this one
- return true;
- }
- packageName = packageDeclaration.getPackageName();
- }
- mClassFqn = (!packageName.isEmpty() ? (packageName + '.') : "") + name;
-
- return false;
- }
-
- return true; // Done: No need to look inside this class
- }
-
- // Store R.layout references in activity classes in a map mapping back layouts
- // to activities
- @Override
- public boolean visitSelect(Select node) {
- if (node.astIdentifier().astValue().equals("layout") //$NON-NLS-1$
- && node.astOperand() instanceof VariableReference
- && ((VariableReference) node.astOperand()).astIdentifier().astValue()
- .equals("R") //$NON-NLS-1$
- && node.getParent() instanceof Select) {
- String layout = ((Select) node.getParent()).astIdentifier().astValue();
- if (mLayoutToActivity == null) {
- mLayoutToActivity = new HashMap<String, List<String>>();
- }
- List<String> list = mLayoutToActivity.get(layout);
- if (list == null) {
- list = new ArrayList<String>();
- mLayoutToActivity.put(layout, list);
- }
- list.add(mClassFqn);
- }
-
- return false;
- }
-
-
- // Look for setTheme(R.style.whatever) and register as a theme registration
- // for the current activity
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node.astName().astValue().equals(SET_THEME)) {
- // Look at argument
- StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
- if (args.size() == 1) {
- Expression arg = args.first();
- if (arg instanceof Select) {
- String resource = arg.toString();
- if (resource.startsWith(R_STYLE_PREFIX)) {
- if (mActivityToTheme == null) {
- mActivityToTheme = new HashMap<String, String>();
- }
- String name = ((Select) arg).astIdentifier().astValue();
- mActivityToTheme.put(mClassFqn, STYLE_RESOURCE_PREFIX + name);
- }
- }
- }
- }
-
- return false;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java
deleted file mode 100644
index 15e2245..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/OverrideDetector.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
-import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
-import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
-import static org.objectweb.asm.Opcodes.ACC_STATIC;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
-
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * Checks for accidental overrides
- */
-public class OverrideDetector extends Detector implements ClassScanner {
- /** Accidental overrides */
- public static final Issue ISSUE = Issue.create(
- "DalvikOverride", //$NON-NLS-1$
- "Looks for methods treated as overrides by Dalvik",
-
- "The Android virtual machine will treat a package private method in one " +
- "class as overriding a package private method in its super class, even if " +
- "they are in separate packages. This may be surprising, but for compatibility " +
- "reasons the behavior has not been changed (yet).\n" +
- "\n" +
- "If you really did intend for this method to override the other, make the " +
- "method `protected` instead.\n" +
- "\n" +
- "If you did *not* intend the override, consider making the method private, or " +
- "changing its name or signature.",
-
- Category.CORRECTNESS,
- 7,
- Severity.ERROR,
- OverrideDetector.class,
- EnumSet.of(Scope.ALL_CLASS_FILES));
-
- /** map from owner class name to JVM signatures for its package private methods */
- private final Map<String, Set<String>> mPackagePrivateMethods = Maps.newHashMap();
-
- /** Map from owner to signature to super class being overridden */
- private Map<String, Map<String, String>> mErrors;
-
- /**
- * Map from owner to signature to corresponding location. When there are
- * errors a single error can have locations for both the overriding and
- * overridden methods.
- */
- private Map<String, Map<String, Location>> mLocations;
-
- /** Constructs a new {@link OverrideDetector} */
- public OverrideDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- // Process the check in two passes:
- //
- // In the first pass, gather the full set of package private methods for
- // each class.
- // When all classes have been processed at the end of the first pass,
- // find out whether any of the methods are potentially overriding those
- // in its super classes.
- //
- // If so, request a second pass. In the second pass, we gather full locations
- // for both the base and overridden method calls, and store these.
- // If the location is found to be in a suppressed context, remove that error
- // entry.
- //
- // At the end of the second pass, we generate the errors, combining locations
- // from both the overridden and overriding methods.
- if (context.getPhase() == 1) {
- Set<String> classes = mPackagePrivateMethods.keySet();
- LintDriver driver = context.getDriver();
- for (String owner : classes) {
- Set<String> methods = mPackagePrivateMethods.get(owner);
- String superClass = driver.getSuperClass(owner);
- int packageIndex = owner.lastIndexOf('/');
- while (superClass != null) {
- int superPackageIndex = superClass.lastIndexOf('/');
-
- // Only compare methods that differ in packages
- if (packageIndex == -1 || superPackageIndex != packageIndex ||
- !owner.regionMatches(0, superClass, 0, packageIndex)) {
- Set<String> superMethods = mPackagePrivateMethods.get(superClass);
- if (superMethods != null) {
- SetView<String> intersection = Sets.intersection(methods,
- superMethods);
- if (!intersection.isEmpty()) {
- if (mLocations == null) {
- mLocations = Maps.newHashMap();
- }
- // We need a separate data structure to keep track of which
- // signatures are in error,
- if (mErrors == null) {
- mErrors = Maps.newHashMap();
- }
-
- for (String signature : intersection) {
- Map<String, Location> locations = mLocations.get(owner);
- if (locations == null) {
- locations = Maps.newHashMap();
- mLocations.put(owner, locations);
- }
- locations.put(signature, null);
-
- locations = mLocations.get(superClass);
- if (locations == null) {
- locations = Maps.newHashMap();
- mLocations.put(superClass, locations);
- }
- locations.put(signature, null);
-
-
- Map<String, String> errors = mErrors.get(owner);
- if (errors == null) {
- errors = Maps.newHashMap();
- mErrors.put(owner, errors);
- }
- errors.put(signature, superClass);
- }
- }
- }
- }
- superClass = driver.getSuperClass(superClass);
- }
- }
-
- if (mErrors != null) {
- context.requestRepeat(this, ISSUE.getScope());
- }
- } else {
- assert context.getPhase() == 2;
-
- for (Entry<String, Map<String, String>> ownerEntry : mErrors.entrySet()) {
- String owner = ownerEntry.getKey();
- Map<String, String> methodToSuper = ownerEntry.getValue();
- for (Entry<String, String> entry : methodToSuper.entrySet()) {
- String signature = entry.getKey();
- String superClass = entry.getValue();
-
- Map<String, Location> ownerLocations = mLocations.get(owner);
- if (ownerLocations != null) {
- Location location = ownerLocations.get(signature);
- if (location != null) {
- Map<String, Location> superLocations = mLocations.get(superClass);
- if (superLocations != null) {
- Location superLocation = superLocations.get(signature);
- if (superLocation != null) {
- location.setSecondary(superLocation);
- superLocation.setMessage(
- "This method is treated as overridden");
- }
- }
- String methodName = signature;
- int index = methodName.indexOf('(');
- if (index != -1) {
- methodName = methodName.substring(0, index);
- }
- String message = String.format(
- "This package private method may be unintentionally " +
- "overriding %1$s in %2$s", methodName,
- ClassContext.getFqcn(superClass));
- context.report(ISSUE, location, message, null);
- }
- }
- }
- }
- }
- }
-
- @SuppressWarnings("rawtypes") // ASM4 API
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- List methodList = classNode.methods;
- if (context.getPhase() == 1) {
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
- int access = method.access;
- // Only record non-static package private methods
- if ((access & (ACC_STATIC|ACC_PRIVATE|ACC_PROTECTED|ACC_PUBLIC)) != 0) {
- continue;
- }
-
- // Ignore constructors too
- if (CONSTRUCTOR_NAME.equals(method.name)) {
- continue;
- }
-
- String owner = classNode.name;
- Set<String> methods = mPackagePrivateMethods.get(owner);
- if (methods == null) {
- methods = Sets.newHashSetWithExpectedSize(methodList.size());
- mPackagePrivateMethods.put(owner, methods);
- }
- methods.add(method.name + method.desc);
- }
- } else {
- assert context.getPhase() == 2;
- Map<String, Location> methods = mLocations.get(classNode.name);
- if (methods == null) {
- // No locations needed from this class
- return;
- }
-
- for (Object m : methodList) {
- MethodNode method = (MethodNode) m;
-
- String signature = method.name + method.desc;
- if (methods.containsKey(signature)){
- if (context.getDriver().isSuppressed(ISSUE, classNode,
- method, null)) {
- Map<String, String> errors = mErrors.get(classNode.name);
- if (errors != null) {
- errors.remove(signature);
- }
- continue;
- }
-
- Location location = context.getLocation(method, classNode);
- methods.put(signature, location);
- String description = ClassContext.createSignature(classNode.name,
- method.name, method.desc);
- location.setClientData(description);
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
deleted file mode 100644
index e8887a5..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateKeyDetector.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-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 com.google.common.base.Charsets;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.EnumSet;
-
-/**
- * Looks for packaged private key files.
- */
-public class PrivateKeyDetector extends Detector implements Detector.OtherFileScanner {
- /** Packaged private key files */
- public static final Issue ISSUE = Issue.create(
- "PackagedPrivateKey", //$NON-NLS-1$
- "Looks for packaged private key files",
-
- "In general, you should not package private key files inside your app.",
-
- Category.SECURITY,
- 8,
- Severity.WARNING,
- PrivateKeyDetector.class,
- Scope.OTHER_SCOPE);
-
- /** Constructs a new {@link PrivateKeyDetector} check */
- public PrivateKeyDetector() {
- }
-
- private static boolean isPrivateKeyFile(File file) {
- if (!file.isFile() ||
- (!LintUtils.endsWith(file.getPath(), "pem") && //NON-NLS-1$
- !LintUtils.endsWith(file.getPath(), "key"))) { //NON-NLS-1$
- return false;
- }
-
- try {
- String firstLine = Files.readFirstLine(file, Charsets.US_ASCII);
- return firstLine != null &&
- firstLine.startsWith("---") && //NON-NLS-1$
- firstLine.contains("PRIVATE KEY"); //NON-NLS-1$
- } catch (IOException ex) {
- // Don't care
- }
-
- return false;
- }
-
- // ---- Implements OtherFileScanner ----
-
- @NonNull
- @Override
- public EnumSet<Scope> getApplicableFiles() {
- return Scope.OTHER_SCOPE;
- }
-
- @Override
- public void run(@NonNull Context context) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- File file = context.file;
- if (isPrivateKeyFile(file)) {
- String fileName = file.getParentFile().getName() + File.separator
- + file.getName();
- String message = String.format(
- "The %1$s file seems to be a private key file. " +
- "Please make sure not to embed this in your APK file.", fileName);
- context.report(ISSUE, Location.create(file), message, null);
- }
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
deleted file mode 100644
index d8a4ce0..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PrivateResourceDetector.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-
-import java.util.Collection;
-
-/**
- * Check which looks for access of private resources.
- */
-public class PrivateResourceDetector extends ResourceXmlDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "PrivateResource", //$NON-NLS-1$
- "Looks for references to private resources",
- "Private resources should not be referenced; the may not be present everywhere, and " +
- "even where they are they may disappear without notice.\n" +
- "\n" +
- "To fix this, copy the resource into your own project. You can find the platform " +
- "resources under `$ANDROID_SK/platforms/android-$VERSION/data/res/.`",
- Category.CORRECTNESS,
- 3,
- Severity.FATAL,
- PrivateResourceDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new detector */
- public PrivateResourceDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String value = attribute.getNodeValue();
- if (value.startsWith("@*android:")) { //$NON-NLS-1$
- context.report(ISSUE, attribute, context.getLocation(attribute),
- "Illegal resource reference: @*android resources are private and " +
- "not always present", null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java
deleted file mode 100644
index 7762659..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ProguardDetector.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.PROGUARD_CONFIG;
-import static com.android.SdkConstants.PROJECT_PROPERTIES;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 java.io.File;
-import java.util.EnumSet;
-
-/**
- * Check which looks for errors in Proguard files.
- */
-public class ProguardDetector extends Detector {
-
- /** The main issue discovered by this detector */
- public static final Issue WRONGKEEP = Issue.create(
- "Proguard", //$NON-NLS-1$
- "Looks for problems in proguard config files",
- "Using `-keepclasseswithmembernames` in a proguard config file is not " +
- "correct; it can cause some symbols to be renamed which should not be.\n" +
- "Earlier versions of ADT used to create proguard.cfg files with the " +
- "wrong format. Instead of `-keepclasseswithmembernames` use " +
- "`-keepclasseswithmembers`, since the old flags also implies " +
- "\"allow shrinking\" which means symbols only referred to from XML and " +
- "not Java (such as possibly CustomViews) can get deleted.",
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- ProguardDetector.class,
- EnumSet.of(Scope.PROGUARD_FILE)).setMoreInfo(
- "http://http://code.google.com/p/android/issues/detail?id=16384"); //$NON-NLS-1$
-
- /** Finds ProGuard files that contain non-project specific configuration
- * locally and suggests replacing it with an include path */
- public static final Issue SPLITCONFIG = Issue.create(
- "ProguardSplit", //$NON-NLS-1$
- "Checks for old proguard.cfg files that contain generic Android rules",
-
- "Earlier versions of the Android tools bundled a single `proguard.cfg` file " +
- "containing a ProGuard configuration file suitable for Android shrinking and " +
- "obfuscation. However, that version was copied into new projects, which " +
- "means that it does not continue to get updated as we improve the default " +
- "ProGuard rules for Android.\n" +
- "\n" +
- "In the new version of the tools, we have split the ProGuard configuration " +
- "into two halves:\n" +
- "* A simple configuration file containing only project-specific flags, in " +
- "your project\n" +
- "* A generic configuration file containing the recommended set of ProGuard " +
- "options for Android projects. This generic file lives in the SDK install " +
- "directory which means that it gets updated along with the tools.\n" +
- "\n" +
- "In order for this to work, the proguard.config property in the " +
- "`project.properties` file now refers to a path, so you can reference both " +
- "the generic file as well as your own (and any additional files too).\n" +
- "\n" +
- "To migrate your project to the new setup, create a new `proguard-project.txt` file " +
- "in your project containing any project specific ProGuard flags as well as " +
- "any customizations you have made, then update your project.properties file " +
- "to contain:\n" +
- "`proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt`",
-
- Category.CORRECTNESS,
- 3,
- Severity.WARNING,
- ProguardDetector.class,
- EnumSet.of(Scope.PROGUARD_FILE));
-
- @Override
- public void run(@NonNull Context context) {
- String contents = context.getContents();
- if (contents != null) {
- if (context.isEnabled(WRONGKEEP)) {
- int index = contents.indexOf(
- // Old pattern:
- "-keepclasseswithmembernames class * {\n" + //$NON-NLS-1$
- " public <init>(android."); //$NON-NLS-1$
- if (index != -1) {
- context.report(WRONGKEEP,
- Location.create(context.file, contents, index, index),
- "Obsolete ProGuard file; use -keepclasseswithmembers instead of " +
- "-keepclasseswithmembernames", null);
- }
- }
- if (context.isEnabled(SPLITCONFIG)) {
- int index = contents.indexOf("-keep public class * extends android.app.Activity");
- if (index != -1) {
- // Only complain if project.properties actually references this file;
- // no need to bother the users who got a default proguard.cfg file
- // when they created their projects but haven't actually hooked it up
- // to shrinking & obfuscation.
- File propertyFile = new File(context.file.getParentFile(), PROJECT_PROPERTIES);
- if (!propertyFile.exists()) {
- return;
- }
- String properties = context.getClient().readFile(propertyFile);
- int i = properties.indexOf(PROGUARD_CONFIG);
- if (i == -1) {
- return;
- }
- // Make sure the entry isn't just commented out, such as
- // # To enable ProGuard to shrink and obfuscate your code, uncomment this:
- // #proguard.config=proguard.cfg
- for (; i >= 0; i--) {
- char c = properties.charAt(i);
- if (c == '#') {
- return;
- }
- if (c == '\n') {
- break;
- }
- }
- if (properties.contains(PROGUARD_CONFIG)) {
- context.report(SPLITCONFIG,
- Location.create(context.file, contents, index, index),
- String.format(
- "Local ProGuard configuration contains general Android " +
- "configuration: Inherit these settings instead? " +
- "Modify project.properties to define " +
- "proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:%1$s" +
- " and then keep only project-specific configuration here",
- context.file.getName()), null);
- }
- }
- }
- }
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
deleted file mode 100644
index ed9447e..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/PxUsageDetector.java
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TEXT_SIZE;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.UNIT_DIP;
-import static com.android.SdkConstants.UNIT_DP;
-import static com.android.SdkConstants.UNIT_IN;
-import static com.android.SdkConstants.UNIT_MM;
-import static com.android.SdkConstants.UNIT_PX;
-import static com.android.SdkConstants.UNIT_SP;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check for px dimensions instead of dp dimensions.
- * Also look for non-"sp" text sizes.
- */
-public class PxUsageDetector extends LayoutDetector {
- /** Using px instead of dp */
- public static final Issue PX_ISSUE = Issue.create(
- "PxUsage", //$NON-NLS-1$
- "Looks for use of the \"px\" dimension",
- // This description is from the below screen support document
- "For performance reasons and to keep the code simpler, the Android system uses pixels " +
- "as the standard unit for expressing dimension or coordinate values. That means that " +
- "the dimensions of a view are always expressed in the code using pixels, but " +
- "always based on the current screen density. For instance, if `myView.getWidth()` " +
- "returns 10, the view is 10 pixels wide on the current screen, but on a device with " +
- "a higher density screen, the value returned might be 15. If you use pixel values " +
- "in your application code to work with bitmaps that are not pre-scaled for the " +
- "current screen density, you might need to scale the pixel values that you use in " +
- "your code to match the un-scaled bitmap source.",
- Category.CORRECTNESS,
- 2,
- Severity.WARNING,
- PxUsageDetector.class,
- Scope.RESOURCE_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/practices/screens_support.html#screen-independence"); //$NON-NLS-1$
-
- /** Using mm/in instead of dp */
- public static final Issue IN_MM_ISSUE = Issue.create(
- "InOrMmUsage", //$NON-NLS-1$
- "Looks for use of the \"mm\" or \"in\" dimensions",
-
- "Avoid using `mm` (millimeters) or `in` (inches) as the unit for dimensions.\n" +
- "\n" +
- "While it should work in principle, unfortunately many devices do not report " +
- "the correct true physical density, which means that the dimension calculations " +
- "won't work correctly. You are better off using `dp` (and for font sizes, `sp`.)",
-
- Category.CORRECTNESS,
- 4,
- Severity.WARNING,
- PxUsageDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Using sp instead of dp */
- public static final Issue DP_ISSUE = Issue.create(
- "SpUsage", //$NON-NLS-1$
- "Looks for uses of \"dp\" instead of \"sp\" dimensions for text sizes",
-
- "When setting text sizes, you should normally use `sp`, or \"scale-independent " +
- "pixels\". This is like the `dp` unit, but it is also scaled " +
- "by the user's font size preference. It is recommend you use this unit when " +
- "specifying font sizes, so they will be adjusted for both the screen density " +
- "and the user's preference.\n" +
- "\n" +
- "There *are* cases where you might need to use `dp`; typically this happens when " +
- "the text is in a container with a specific dp-size. This will prevent the text " +
- "from spilling outside the container. Note however that this means that the user's " +
- "font size settings are not respected, so consider adjusting the layout itself " +
- "to be more flexible.",
- Category.CORRECTNESS,
- 3,
- Severity.WARNING,
- PxUsageDetector.class,
- Scope.RESOURCE_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/training/multiscreen/screendensities.html"); //$NON-NLS-1$
-
- /** Using text sizes that are too small */
- public static final Issue SMALL_SP_ISSUE = Issue.create(
- "SmallSp", //$NON-NLS-1$
- "Looks for text sizes that are too small",
-
- "Avoid using sizes smaller than 12sp.",
-
- Category.USABILITY,
- 4,
- Severity.WARNING,
- PxUsageDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
-
- /** Constructs a new {@link PxUsageDetector} */
- public PxUsageDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- // Look in both layouts (at attribute values) and in value files (at style definitions)
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- @Nullable
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(TAG_STYLE);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- if (context.getResourceFolderType() != ResourceFolderType.LAYOUT) {
- return;
- }
-
- String value = attribute.getValue();
- if (value.endsWith(UNIT_PX) && value.matches("\\d+px")) { //$NON-NLS-1$
- if (value.charAt(0) == '0') {
- // 0px is fine. 0px is 0dp regardless of density...
- return;
- }
- if (context.isEnabled(PX_ISSUE)) {
- context.report(PX_ISSUE, attribute, context.getLocation(attribute),
- "Avoid using \"px\" as units; use \"dp\" instead", null);
- }
- } else if (value.endsWith(UNIT_MM) && value.matches("\\d+mm") //$NON-NLS-1$
- || value.endsWith(UNIT_IN) && value.matches("\\d+in")) { //$NON-NLS-1$
- if (value.charAt(0) == '0') {
- // 0mm == 0in == 0dp
- return;
- }
- if (context.isEnabled(IN_MM_ISSUE)) {
- String unit = value.substring(value.length() - 2);
- context.report(IN_MM_ISSUE, attribute, context.getLocation(attribute),
- String.format("Avoid using \"%1$s\" as units " +
- "(it does not work accurately on all devices); use \"dp\" instead",
- unit),
- null);
- }
- } else if (value.endsWith(UNIT_SP)
- && (ATTR_TEXT_SIZE.equals(attribute.getLocalName())
- || ATTR_LAYOUT_HEIGHT.equals(attribute.getLocalName()))
- && value.matches("\\d+sp")) { //$NON-NLS-1$
- int size = getSize(value);
- if (size > 0 && size < 12) {
- context.report(SMALL_SP_ISSUE, attribute, context.getLocation(attribute),
- String.format("Avoid using sizes smaller than 12sp: %1$s", value),
- null);
- }
- } else if (ATTR_TEXT_SIZE.equals(attribute.getLocalName())
- && (value.endsWith(UNIT_DP) || value.endsWith(UNIT_DIP))
- && (value.matches("\\d+di?p"))) { //$NON-NLS-1$
- if (context.isEnabled(DP_ISSUE)) {
- context.report(DP_ISSUE, attribute, context.getLocation(attribute),
- "Should use \"sp\" instead of \"dp\" for text sizes", null);
- }
- }
- }
-
- private static int getSize(String text) {
- assert text.matches("\\d+sp") : text; //$NON-NLS-1$
- return Integer.parseInt(text.substring(0, text.length() - 2));
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (context.getResourceFolderType() != ResourceFolderType.VALUES) {
- return;
- }
-
- assert element.getTagName().equals(TAG_STYLE);
- NodeList itemNodes = element.getChildNodes();
- for (int j = 0, nodeCount = itemNodes.getLength(); j < nodeCount; j++) {
- Node item = itemNodes.item(j);
- if (item.getNodeType() == Node.ELEMENT_NODE &&
- TAG_ITEM.equals(item.getNodeName())) {
- Element itemElement = (Element) item;
- NodeList childNodes = item.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() != Node.TEXT_NODE) {
- return;
- }
-
- checkStyleItem(context, itemElement, child);
- }
- }
- }
- }
-
- private static void checkStyleItem(XmlContext context, Element item, Node textNode) {
- String text = textNode.getNodeValue();
- for (int j = text.length() - 1; j > 0; j--) {
- char c = text.charAt(j);
- if (!Character.isWhitespace(c)) {
- if (c == 'x' && text.charAt(j - 1) == 'p') { // ends with px
- text = text.trim();
- if (text.matches("\\d+px") && text.charAt(0) != '0') { //$NON-NLS-1$
- if (context.isEnabled(PX_ISSUE)) {
- context.report(PX_ISSUE, item, context.getLocation(textNode),
- "Avoid using \"px\" as units; use \"dp\" instead", null);
- }
- }
- } else if (c == 'm' && text.charAt(j - 1) == 'm' ||
- c == 'n' && text.charAt(j - 1) == 'i') {
- text = text.trim();
- String unit = text.substring(text.length() - 2);
- if (text.matches("\\d+" + unit) && text.charAt(0) != '0') { //$NON-NLS-1$
- if (context.isEnabled(IN_MM_ISSUE)) {
- context.report(IN_MM_ISSUE, item, context.getLocation(textNode),
- String.format("Avoid using \"%1$s\" as units "
- + "(it does not work accurately on all devices); "
- + "use \"dp\" instead", unit), null);
- }
- }
- } else if (c == 'p' && (text.charAt(j - 1) == 'd'
- || text.charAt(j - 1) == 'i')) { // ends with dp or di
- text = text.trim();
- String name = item.getAttribute(ATTR_NAME);
- if ((name.equals(ATTR_TEXT_SIZE)
- || name.equals("android:textSize")) //$NON-NLS-1$
- && text.matches("\\d+di?p")) { //$NON-NLS-1$
- if (context.isEnabled(DP_ISSUE)) {
- context.report(DP_ISSUE, item, context.getLocation(textNode),
- "Should use \"sp\" instead of \"dp\" for text sizes", null);
- }
- }
- } else if (c == 'p' && text.charAt(j - 1) == 's') {
- String name = item.getAttribute(ATTR_NAME);
- if (ATTR_TEXT_SIZE.equals(name) || ATTR_LAYOUT_HEIGHT.equals(name)) {
- text = text.trim();
- String unit = text.substring(text.length() - 2);
- if (text.matches("\\d+" + unit)) { //$NON-NLS-1$
- if (context.isEnabled(SMALL_SP_ISSUE)) {
- int size = getSize(text);
- if (size > 0 && size < 12) {
- context.report(SMALL_SP_ISSUE, item,
- context.getLocation(textNode), String.format(
- "Avoid using sizes smaller than 12sp: %1$s",
- text), null);
- }
- }
- }
- }
- }
- break;
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
deleted file mode 100644
index c4fd0a7..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
-import static com.android.SdkConstants.ANDROID_APP_SERVICE;
-import static com.android.SdkConstants.ANDROID_CONTENT_BROADCAST_RECEIVER;
-import static com.android.SdkConstants.ANDROID_CONTENT_CONTENT_PROVIDER;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PACKAGE;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.w3c.dom.Element;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.Map.Entry;
-
-/**
- * Checks for missing manifest registrations for activities, services etc
- * and also makes sure that they are registered with the correct tag
- */
-public class RegistrationDetector extends LayoutDetector implements ClassScanner {
- /** Unregistered activities and services */
- public static final Issue ISSUE = Issue.create(
- "Registered", //$NON-NLS-1$
- "Ensures that Activities, Services and Content Providers are registered in the manifest",
-
- "Activities, services and content providers should be registered in the " +
- "`AndroidManifest.xml` file using `<activity>`, `<service>` and `<provider>` tags.\n" +
- "\n" +
- "If your activity is simply a parent class intended to be subclassed by other " +
- "\"real\" activities, make it an abstract class.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- RegistrationDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE)).setMoreInfo(
- "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$
-
- private Multimap<String, String> mManifestRegistrations;
-
- /** Constructs a new {@link RegistrationDetector} */
- public RegistrationDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(sTags);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String fqcn = getFqcn(element);
- String tag = element.getTagName();
- String frameworkClass = tagToClass(tag);
- if (frameworkClass != null) {
- String signature = ClassContext.getInternalName(fqcn);
- if (mManifestRegistrations == null) {
- mManifestRegistrations = ArrayListMultimap.create(4, 8);
- }
- mManifestRegistrations.put(frameworkClass, signature);
- if (signature.indexOf('$') != -1) {
- // The internal name contains a $ which means it's an inner class.
- // The conversion from fqcn to internal name is a bit ambiguous:
- // "a.b.C.D" usually means "inner class D in class C in package a.b".
- // However, it can (see issue 31592) also mean class D in package "a.b.C".
- // Place *both* of these possibilities in the registered map, since this
- // is only used to check that an activity is registered, not the other way
- // (so it's okay to have entries there that do not correspond to real classes).
- signature = signature.replace('$', '/');
- mManifestRegistrations.put(frameworkClass, signature);
- }
- }
- }
-
- /**
- * Returns the fully qualified class name for a manifest entry element that
- * specifies a name attribute
- *
- * @param element the element
- * @return the fully qualified class name
- */
- @NonNull
- private static String getFqcn(@NonNull Element element) {
- Element root = element.getOwnerDocument().getDocumentElement();
- String pkg = root.getAttribute(ATTR_PACKAGE);
- String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
- if (className.startsWith(".")) { //$NON-NLS-1$
- return pkg + className;
- } else if (className.indexOf('.') == -1) {
- // According to the <activity> manifest element documentation, this is not
- // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
- // but it appears in manifest files and appears to be supported by the runtime
- // so handle this in code as well:
- return pkg + '.' + className;
- } // else: the class name is already a fully qualified class name
-
- return className;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- // Abstract classes do not need to be registered
- if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
- return;
- }
- String curr = classNode.name;
-
- int lastIndex = curr.lastIndexOf('$');
- if (lastIndex != -1 && lastIndex < curr.length() - 1) {
- if (Character.isDigit(curr.charAt(lastIndex+1))) {
- // Anonymous inner class, doesn't need to be registered
- return;
- }
- }
-
- while (curr != null) {
- for (String s : sClasses) {
- if (curr.equals(s)) {
- Collection<String> registered = mManifestRegistrations != null ?
- mManifestRegistrations.get(curr) : null;
- if (registered == null || !registered.contains(classNode.name)) {
- report(context, classNode, curr);
- }
-
- }
- }
-
- curr = context.getDriver().getSuperClass(curr);
- }
- }
-
- private void report(ClassContext context, ClassNode classNode, String curr) {
- String tag = classToTag(curr);
- String className = ClassContext.createSignature(classNode.name, null, null);
-
- String wrongClass = null; // The framework class this class actually extends
- if (mManifestRegistrations != null) {
- Collection<Entry<String,String>> entries =
- mManifestRegistrations.entries();
- for (Entry<String,String> entry : entries) {
- if (entry.getValue().equals(classNode.name)) {
- wrongClass = entry.getKey();
- break;
- }
- }
- }
- if (wrongClass != null) {
- Location location = context.getLocation(classNode);
- context.report(
- ISSUE,
- location,
- String.format(
- "%1$s is a <%2$s> but is registered in the manifest as a <%3$s>",
- className, tag, classToTag(wrongClass)),
- null);
- } else if (!tag.equals(TAG_RECEIVER)) { // don't need to be registered
- Location location = context.getLocation(classNode);
- context.report(
- ISSUE,
- location,
- String.format(
- "The <%1$s> %2$s is not registered in the manifest",
- tag, className),
- null);
- }
- }
-
- /** The manifest tags we care about */
- private static final String[] sTags = new String[] {
- TAG_ACTIVITY,
- TAG_SERVICE,
- TAG_RECEIVER,
- TAG_PROVIDER,
- // Keep synchronized with {@link #sClasses}
- };
-
- /** The corresponding framework classes that the tags in {@link #sTags} should extend */
- private static final String[] sClasses = new String[] {
- ANDROID_APP_ACTIVITY,
- ANDROID_APP_SERVICE,
- ANDROID_CONTENT_BROADCAST_RECEIVER,
- ANDROID_CONTENT_CONTENT_PROVIDER,
- // Keep synchronized with {@link #sTags}
- };
-
- /** Looks up the corresponding framework class a given manifest tag's class should extend */
- private static String tagToClass(String tag) {
- for (int i = 0, n = sTags.length; i < n; i++) {
- if (sTags[i].equals(tag)) {
- return sClasses[i];
- }
- }
-
- return null;
- }
-
- /** Looks up the tag a given framework class should be registered with */
- private static String classToTag(String className) {
- for (int i = 0, n = sClasses.length; i < n; i++) {
- if (sClasses[i].equals(className)) {
- return sTags[i];
- }
- }
-
- return null;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
deleted file mode 100644
index 861c1be..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RequiredAttributeDetector.java
+++ /dev/null
@@ -1,618 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
-import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_LAYOUT;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PARENT;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
-import static com.android.SdkConstants.FN_RESOURCE_BASE;
-import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7;
-import static com.android.SdkConstants.GRID_LAYOUT;
-import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.REQUEST_FOCUS;
-import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.TABLE_LAYOUT;
-import static com.android.SdkConstants.TABLE_ROW;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.VIEW_INCLUDE;
-import static com.android.SdkConstants.VIEW_MERGE;
-import static com.android.resources.ResourceFolderType.LAYOUT;
-import static com.android.resources.ResourceFolderType.VALUES;
-import static com.android.tools.lint.detector.api.LintUtils.getLayoutName;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.NullLiteral;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.VariableReference;
-
-/**
- * Ensures that layout width and height attributes are specified
- */
-public class RequiredAttributeDetector extends LayoutDetector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "RequiredSize", //$NON-NLS-1$
- "Ensures that the layout_width and layout_height are specified for all views",
-
- "All views must specify an explicit layout_width and layout_height attribute. " +
- "There is a runtime check for this, so if you fail to specify a size, an exception " +
- "is thrown at runtime.\n" +
- "\n" +
- "It's possible to specify these widths via styles as well. GridLayout, as a special " +
- "case, does not require you to specify a size.",
- Category.CORRECTNESS,
- 4,
- Severity.ERROR,
- RequiredAttributeDetector.class,
- EnumSet.of(Scope.JAVA_FILE, Scope.ALL_RESOURCE_FILES));
-
- /** Map from each style name to parent style */
- @Nullable private Map<String, String> mStyleParents;
-
- /** Set of style names where the style sets the layout width */
- @Nullable private Set<String> mWidthStyles;
-
- /** Set of style names where the style sets the layout height */
- @Nullable private Set<String> mHeightStyles;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the width is set on the include */
- @Nullable private Set<String> mIncludedWidths;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the height is set on the include */
- @Nullable private Set<String> mIncludedHeights;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the width is <b>not</b> set on the include */
- @Nullable private Set<String> mNotIncludedWidths;
-
- /** Set of layout names for layouts that are included by an {@code <include>} tag
- * where the height is <b>not</b> set on the include */
- @Nullable private Set<String> mNotIncludedHeights;
-
- /** Whether the width was set in a theme definition */
- private boolean mSetWidthInTheme;
-
- /** Whether the height was set in a theme definition */
- private boolean mSetHeightInTheme;
-
- /** Constructs a new {@link RequiredAttributeDetector} */
- public RequiredAttributeDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == LAYOUT || folderType == VALUES;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- // Process checks in two phases:
- // Phase 1: Gather styles and includes (styles are encountered after the layouts
- // so we can't do it in a single phase, and includes can be affected by includes from
- // layouts we haven't seen yet)
- // Phase 2: Process layouts, using gathered style and include data, and mark layouts
- // not known.
- //
- if (context.getPhase() == 1) {
- checkSizeSetInTheme();
-
- context.requestRepeat(this, Scope.RESOURCE_FILE_SCOPE);
- }
- }
-
- private boolean isWidthStyle(String style) {
- return isSizeStyle(style, mWidthStyles);
- }
-
- private boolean isHeightStyle(String style) {
- return isSizeStyle(style, mHeightStyles);
- }
-
- private boolean isSizeStyle(String style, Set<String> sizeStyles) {
- if (isFrameworkSizeStyle(style)) {
- return true;
- }
- if (sizeStyles == null) {
- return false;
- }
- return isSizeStyle(stripStylePrefix(style), sizeStyles, 0);
- }
-
- private static boolean isFrameworkSizeStyle(String style) {
- // The styles Widget.TextView.ListSeparator (and several theme variations, such as
- // Widget.Holo.TextView.ListSeparator, Widget.Holo.Light.TextView.ListSeparator, etc)
- // define layout_width and layout_height.
- // These are exposed through the listSeparatorTextViewStyle style.
- if (style.equals("?android:attr/listSeparatorTextViewStyle") //$NON-NLS-1$
- || style.equals("?android/listSeparatorTextViewStyle")) { //$NON-NLS-1$
- return true;
- }
-
- // It's also set on Widget.QuickContactBadge and Widget.QuickContactBadgeSmall
- // These are exposed via a handful of attributes with a common prefix
- if (style.startsWith("?android:attr/quickContactBadgeStyle")) { //$NON-NLS-1$
- return true;
- }
-
- // Finally, the styles are set on MediaButton and Widget.Holo.Tab (and
- // Widget.Holo.Light.Tab) but these are not exposed via attributes.
-
- return false;
- }
-
- private boolean isSizeStyle(
- @NonNull String style,
- @NonNull Set<String> sizeStyles, int depth) {
- if (depth == 30) {
- // Cycle between local and framework attribute style missed
- // by the fact that we're stripping the distinction between framework
- // and local styles here
- return false;
- }
-
- assert !style.startsWith(STYLE_RESOURCE_PREFIX)
- && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
-
- if (sizeStyles.contains(style)) {
- return true;
- }
-
- if (mStyleParents != null) {
- String parentStyle = mStyleParents.get(style);
- if (parentStyle != null) {
- parentStyle = stripStylePrefix(parentStyle);
- if (isSizeStyle(parentStyle, sizeStyles, depth + 1)) {
- return true;
- }
- }
- }
-
- int index = style.lastIndexOf('.');
- if (index > 0) {
- return isSizeStyle(style.substring(0, index), sizeStyles, depth + 1);
- }
-
- return false;
- }
-
- private void checkSizeSetInTheme() {
- // Look through the styles and determine whether each style is a theme
- if (mStyleParents == null) {
- return;
- }
-
- Map<String, Boolean> isTheme = Maps.newHashMap();
- for (String style : mStyleParents.keySet()) {
- if (isTheme(stripStylePrefix(style), isTheme, 0)) {
- mSetWidthInTheme = true;
- mSetHeightInTheme = true;
- break;
- }
- }
- }
-
- private boolean isTheme(String style, Map<String, Boolean> isTheme, int depth) {
- if (depth == 30) {
- // Cycle between local and framework attribute style missed
- // by the fact that we're stripping the distinction between framework
- // and local styles here
- return false;
- }
-
- assert !style.startsWith(STYLE_RESOURCE_PREFIX)
- && !style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX);
-
- Boolean known = isTheme.get(style);
- if (known != null) {
- return known;
- }
-
- if (style.contains("Theme")) { //$NON-NLS-1$
- isTheme.put(style, true);
- return true;
- }
-
- if (mStyleParents != null) {
- String parentStyle = mStyleParents.get(style);
- if (parentStyle != null) {
- parentStyle = stripStylePrefix(parentStyle);
- if (isTheme(parentStyle, isTheme, depth + 1)) {
- isTheme.put(style, true);
- return true;
- }
- }
- }
-
- int index = style.lastIndexOf('.');
- if (index > 0) {
- String parentStyle = style.substring(0, index);
- boolean result = isTheme(parentStyle, isTheme, depth + 1);
- isTheme.put(style, result);
- return result;
- }
-
- return false;
- }
-
- private static boolean hasLayoutVariations(File file) {
- File parent = file.getParentFile();
- if (parent == null) {
- return false;
- }
- File res = file.getParentFile();
- if (res == null) {
- return false;
- }
- String name = file.getName();
- File[] folders = res.listFiles();
- if (folders == null) {
- return false;
- }
- for (File folder : folders) {
- if (!folder.getName().startsWith(FD_RES_LAYOUT)) {
- continue;
- }
- if (folder.equals(parent)) {
- continue;
- }
- File other = new File(folder, name);
- if (other.exists()) {
- return true;
- }
- }
-
- return false;
- }
-
- private static String stripStylePrefix(@NonNull String style) {
- if (style.startsWith(STYLE_RESOURCE_PREFIX)) {
- style = style.substring(STYLE_RESOURCE_PREFIX.length());
- } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
- style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length());
- }
-
- return style;
- }
-
- private static boolean isRootElement(@NonNull Node node) {
- return node == node.getOwnerDocument().getDocumentElement();
- }
-
- // ---- Implements XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return ALL;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- ResourceFolderType folderType = context.getResourceFolderType();
- int phase = context.getPhase();
- if (phase == 1 && folderType == VALUES) {
- String tag = element.getTagName();
- if (TAG_STYLE.equals(tag)) {
- String parent = element.getAttribute(ATTR_PARENT);
- if (parent != null && !parent.isEmpty()) {
- String name = element.getAttribute(ATTR_NAME);
- if (name != null && !name.isEmpty()) {
- if (mStyleParents == null) {
- mStyleParents = Maps.newHashMap();
- }
- mStyleParents.put(name, parent);
- }
- }
- } else if (TAG_ITEM.equals(tag)
- && TAG_STYLE.equals(element.getParentNode().getNodeName())) {
- String name = element.getAttribute(ATTR_NAME);
- if (name.endsWith(ATTR_LAYOUT_WIDTH) &&
- name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_WIDTH)) {
- if (mWidthStyles == null) {
- mWidthStyles = Sets.newHashSet();
- }
- String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
- mWidthStyles.add(styleName);
- }
- if (name.endsWith(ATTR_LAYOUT_HEIGHT) &&
- name.equals(ANDROID_NS_NAME_PREFIX + ATTR_LAYOUT_HEIGHT)) {
- if (mHeightStyles == null) {
- mHeightStyles = Sets.newHashSet();
- }
- String styleName = ((Element) element.getParentNode()).getAttribute(ATTR_NAME);
- mHeightStyles.add(styleName);
- }
- }
- } else if (folderType == LAYOUT) {
- if (phase == 1) {
- // Gather includes
- if (element.getTagName().equals(VIEW_INCLUDE)) {
- String layout = element.getAttribute(ATTR_LAYOUT);
- if (layout != null && !layout.isEmpty()) {
- recordIncludeWidth(layout,
- element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH));
- recordIncludeHeight(layout,
- element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT));
- }
- }
- } else {
- assert phase == 2; // Check everything using style data and include data
- boolean hasWidth = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH);
- boolean hasHeight = element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT);
-
- if (mSetWidthInTheme) {
- hasWidth = true;
- }
-
- if (mSetHeightInTheme) {
- hasHeight = true;
- }
-
- if (hasWidth && hasHeight) {
- return;
- }
-
- String tag = element.getTagName();
- if (VIEW_MERGE.equals(tag)
- || VIEW_INCLUDE.equals(tag)
- || REQUEST_FOCUS.equals(tag)) {
- return;
- }
-
- String parentTag = element.getParentNode() != null
- ? element.getParentNode().getNodeName() : "";
- if (TABLE_LAYOUT.equals(parentTag)
- || TABLE_ROW.equals(parentTag)
- || GRID_LAYOUT.equals(parentTag)
- || FQCN_GRID_LAYOUT_V7.equals(parentTag)) {
- return;
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- boolean certain = true;
- boolean isRoot = isRootElement(element);
- if (isRoot || isRootElement(element.getParentNode())
- && VIEW_MERGE.equals(parentTag)) {
- String name = LAYOUT_RESOURCE_PREFIX + getLayoutName(context.file);
- if (!hasWidth && mIncludedWidths != null) {
- hasWidth = mIncludedWidths.contains(name);
- // If the layout is *also* included in a context where the width
- // was not set, we're not certain; it's possible that
- if (mNotIncludedWidths != null && mNotIncludedWidths.contains(name)) {
- hasWidth = false;
- // If we only have a single layout we know that this layout isn't
- // always included with layout_width or layout_height set, but
- // if there are multiple layouts, it's possible that at runtime
- // we only load the size-less layout by the tag which includes
- // the size
- certain = !hasLayoutVariations(context.file);
- }
- }
- if (!hasHeight && mIncludedHeights != null) {
- hasHeight = mIncludedHeights.contains(name);
- if (mNotIncludedHeights != null && mNotIncludedHeights.contains(name)) {
- hasHeight = false;
- certain = !hasLayoutVariations(context.file);
- }
- }
- if (hasWidth && hasHeight) {
- return;
- }
- }
-
- if (!hasWidth || !hasHeight) {
- String style = element.getAttribute(ATTR_STYLE);
- if (style != null && !style.isEmpty()) {
- if (!hasWidth) {
- hasWidth = isWidthStyle(style);
- }
- if (!hasHeight) {
- hasHeight = isHeightStyle(style);
- }
- }
- if (hasWidth && hasHeight) {
- return;
- }
- }
-
- String message;
- if (!(hasWidth || hasHeight)) {
- if (certain) {
- message = "The required layout_width and layout_height attributes " +
- "are missing";
- } else {
- message = "The required layout_width and layout_height attributes " +
- "*may* be missing";
- }
- } else {
- String attribute = hasWidth ? ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH;
- if (certain) {
- message = String.format("The required %1$s attribute is missing",
- attribute);
- } else {
- message = String.format("The required %1$s attribute *may* be missing",
- attribute);
- }
- }
- context.report(ISSUE, element, context.getLocation(element),
- message, null);
- }
- }
- }
-
- private void recordIncludeWidth(String layout, boolean providesWidth) {
- if (providesWidth) {
- if (mIncludedWidths == null) {
- mIncludedWidths = Sets.newHashSet();
- }
- mIncludedWidths.add(layout);
- } else {
- if (mNotIncludedWidths == null) {
- mNotIncludedWidths = Sets.newHashSet();
- }
- mNotIncludedWidths.add(layout);
- }
- }
-
- private void recordIncludeHeight(String layout, boolean providesHeight) {
- if (providesHeight) {
- if (mIncludedHeights == null) {
- mIncludedHeights = Sets.newHashSet();
- }
- mIncludedHeights.add(layout);
- } else {
- if (mNotIncludedHeights == null) {
- mNotIncludedHeights = Sets.newHashSet();
- }
- mNotIncludedHeights.add(layout);
- }
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("inflate"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(
- @NonNull JavaContext context,
- @Nullable AstVisitor visitor,
- @NonNull MethodInvocation call) {
- // Handle
- // View#inflate(Context context, int resource, ViewGroup root)
- // LayoutInflater#inflate(int resource, ViewGroup root)
- // LayoutInflater#inflate(int resource, ViewGroup root, boolean attachToRoot)
- StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
-
- String layout = null;
- int index = 0;
- for (Iterator<Expression> iterator = args.iterator(); iterator.hasNext(); index++) {
- Expression expression = iterator.next();
- if (expression instanceof Select) {
- Select outer = (Select) expression;
- Expression operand = outer.astOperand();
- if (operand instanceof Select) {
- Select inner = (Select) operand;
- if (inner.astOperand() instanceof VariableReference) {
- VariableReference reference = (VariableReference) inner.astOperand();
- if (FN_RESOURCE_BASE.equals(reference.astIdentifier().astValue())
- // TODO: constant
- && "layout".equals(inner.astIdentifier().astValue())) {
- layout = LAYOUT_RESOURCE_PREFIX + outer.astIdentifier().astValue();
- break;
- }
- }
- }
- }
- }
-
- if (layout == null) {
- lombok.ast.Node method = StringFormatDetector.getParentMethod(call);
- if (method != null) {
- // Must track local types
- index = 0;
- String name = StringFormatDetector.getResourceArg(method, call, index);
- if (name == null) {
- index = 1;
- name = StringFormatDetector.getResourceArg(method, call, index);
- }
- if (name != null) {
- layout = LAYOUT_RESOURCE_PREFIX + name;
- }
- }
- if (layout == null) {
- // Flow analysis didn't succeed
- return;
- }
- }
-
- // In all the applicable signatures, the view root argument is immediately after
- // the layout resource id.
- int viewRootPos = index + 1;
- if (viewRootPos < args.size()) {
- int i = 0;
- Iterator<Expression> iterator = args.iterator();
- while (iterator.hasNext() && i < viewRootPos) {
- iterator.next();
- i++;
- }
- if (iterator.hasNext()) {
- Expression viewRoot = iterator.next();
- if (viewRoot instanceof NullLiteral) {
- // Yep, this one inflates the given view with a null parent:
- // Tag it as such. For now just use the include data structure since
- // it has the same net effect
- recordIncludeWidth(layout, true);
- recordIncludeHeight(layout, true);
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java
deleted file mode 100644
index 81bb522..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ScrollViewChildDetector.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
-import static com.android.SdkConstants.SCROLL_VIEW;
-import static com.android.SdkConstants.VALUE_FILL_PARENT;
-import static com.android.SdkConstants.VALUE_MATCH_PARENT;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Check which looks at the children of ScrollViews and ensures that they fill/match
- * the parent width instead of setting wrap_content.
- */
-public class ScrollViewChildDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ScrollViewSize", //$NON-NLS-1$
- "Checks that ScrollViews use wrap_content in scrolling dimension",
- // TODO add a better explanation here!
- "ScrollView children must set their `layout_width` or `layout_height` attributes " +
- "to `wrap_content` rather than `fill_parent` or `match_parent` in the scrolling " +
- "dimension",
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- ScrollViewChildDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link ScrollViewChildDetector} */
- public ScrollViewChildDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- SCROLL_VIEW,
- HORIZONTAL_SCROLL_VIEW
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- List<Element> children = LintUtils.getChildren(element);
- boolean isHorizontal = HORIZONTAL_SCROLL_VIEW.equals(element.getTagName());
- String attributeName = isHorizontal ? ATTR_LAYOUT_WIDTH : ATTR_LAYOUT_HEIGHT;
- for (Element child : children) {
- Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, attributeName);
- if (sizeNode == null) {
- return;
- }
- String value = sizeNode.getValue();
- if (VALUE_FILL_PARENT.equals(value) || VALUE_MATCH_PARENT.equals(value)) {
- String msg = String.format("This %1$s should use android:%2$s=\"wrap_content\"",
- child.getTagName(), attributeName);
- context.report(ISSUE, sizeNode, context.getLocation(sizeNode), msg,
- null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java
deleted file mode 100644
index c876b96..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SdCardDetector.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-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 java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Node;
-import lombok.ast.StringLiteral;
-
-/**
- * Looks for hardcoded references to /sdcard/.
- */
-public class SdCardDetector extends Detector implements Detector.JavaScanner {
- /** Hardcoded /sdcard/ references */
- public static final Issue ISSUE = Issue.create(
- "SdCardPath", //$NON-NLS-1$
- "Looks for hardcoded references to /sdcard",
-
- "Your code should not reference the `/sdcard` path directly; instead use " +
- "`Environment.getExternalStorageDirectory().getPath()`.\n" +
- "\n" +
- "Similarly, do not reference the `/data/data/` path directly; it can vary " +
- "in multi-user scenarios. Instead, use " +
- "`Context.getFilesDir().getPath()`.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- SdCardDetector.class,
- Scope.JAVA_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/topics/data/data-storage.html#filesExternal"); //$NON-NLS-1$
-
- /** Constructs a new {@link SdCardDetector} check */
- public SdCardDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>>singletonList(StringLiteral.class);
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new StringChecker(context);
- }
-
- private static class StringChecker extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public StringChecker(JavaContext context) {
- mContext = context;
- }
-
- @Override
- public boolean visitStringLiteral(StringLiteral node) {
- String s = node.astValue();
- if (s.isEmpty()) {
- return false;
- }
- char c = s.charAt(0);
- if (c != '/' && c != 'f') {
- return false;
- }
-
- if (s.startsWith("/sdcard") //$NON-NLS-1$
- || s.startsWith("/mnt/sdcard/") //$NON-NLS-1$
- || s.startsWith("/system/media/sdcard") //$NON-NLS-1$
- || s.startsWith("file://sdcard/") //$NON-NLS-1$
- || s.startsWith("file:///sdcard/")) { //$NON-NLS-1$
- String message = "Do not hardcode \"/sdcard/\"; " +
- "use Environment.getExternalStorageDirectory().getPath() instead";
- Location location = mContext.getLocation(node);
- mContext.report(ISSUE, node, location, message, s);
- } else if (s.startsWith("/data/data/") //$NON-NLS-1$
- || s.startsWith("/data/user/")) { //$NON-NLS-1$
- String message = "Do not hardcode \"/data/\"; " +
- "use Context.getFilesDir().getPath() instead";
- Location location = mContext.getLocation(node);
- mContext.report(ISSUE, node, location, message, s);
- }
-
- return false;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
deleted file mode 100644
index f1d349b..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-import org.objectweb.asm.tree.analysis.BasicValue;
-import org.objectweb.asm.tree.analysis.Frame;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Checks for hardcoded seeds with random numbers.
- */
-public class SecureRandomDetector extends Detector implements ClassScanner {
- /** Unregistered activities and services */
- public static final Issue ISSUE = Issue.create(
- "SecureRandom", //$NON-NLS-1$
- "Looks for suspicious usage of the SecureRandom class",
-
- "Specifying a fixed seed will cause the instance to return a predictable sequence " +
- "of numbers. This may be useful for testing but it is not appropriate for secure use.",
-
- Category.PERFORMANCE,
- 9,
- Severity.WARNING,
- SecureRandomDetector.class,
- Scope.CLASS_FILE_SCOPE).
- setMoreInfo("http://developer.android.com/reference/java/security/SecureRandom.html");
-
- private static final String SET_SEED = "setSeed"; //$NON-NLS-1$
- private static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; //$NON-NLS-1$
- private static final String OWNER_RANDOM = "java/util/Random"; //$NON-NLS-1$
- private static final String VM_SECURE_RANDOM = 'L' + OWNER_SECURE_RANDOM + ';';
- /** Method description for a method that takes a long argument (no return type specified */
- private static final String LONG_ARG = "(J)"; //$NON-NLS-1$
-
- /** Constructs a new {@link SecureRandomDetector} */
- public SecureRandomDetector() {
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Collections.singletonList(SET_SEED);
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String owner = call.owner;
- String desc = call.desc;
- if (owner.equals(OWNER_SECURE_RANDOM)) {
- if (desc.startsWith(LONG_ARG)) {
- checkValidSetSeed(context, call);
- } else if (desc.startsWith("([B)")) { //$NON-NLS-1$
- // setSeed(byte[]) ...
- // We could do some flow analysis here to see whether the byte array getting
- // passed in appears to be fixed.
- // However, people calling this constructor rather than the simpler one
- // with a fixed integer are probably less likely to make that mistake... right?
- }
- } else if (owner.equals(OWNER_RANDOM) && desc.startsWith(LONG_ARG)) {
- // Called setSeed(long) on an instanceof a Random object. Flag this if the instance
- // is likely a SecureRandom.
-
- // Track allocations such that we know whether the type of the call
- // is on a SecureRandom rather than a Random
- Analyzer analyzer = new Analyzer(new BasicInterpreter() {
- @Override
- public BasicValue newValue(Type type) {
- if (type != null && type.getDescriptor().equals(VM_SECURE_RANDOM)) {
- return new BasicValue(type);
- }
- return super.newValue(type);
- }
- });
- try {
- Frame[] frames = analyzer.analyze(classNode.name, method);
- InsnList instructions = method.instructions;
- Frame frame = frames[instructions.indexOf(call)];
- int stackSlot = frame.getStackSize();
- for (Type type : Type.getArgumentTypes(desc)) {
- stackSlot -= type.getSize();
- }
- BasicValue stackValue = (BasicValue) frame.getStack(stackSlot);
- Type type = stackValue.getType();
- if (type != null && type.getDescriptor().equals(VM_SECURE_RANDOM)) {
- checkValidSetSeed(context, call);
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- } else if (owner.equals(OWNER_RANDOM) && desc.startsWith(LONG_ARG)) {
- // Called setSeed(long) on an instanceof a Random object. Flag this if the instance
- // is likely a SecureRandom.
- // TODO
- }
- }
-
- private static void checkValidSetSeed(ClassContext context, MethodInsnNode call) {
- assert call.name.equals(SET_SEED);
-
- // Make sure the argument passed is not a literal
- AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
- if (prev == null) {
- return;
- }
- int opcode = prev.getOpcode();
- if (opcode == Opcodes.LCONST_0 || opcode == Opcodes.LCONST_1 || opcode == Opcodes.LDC) {
- context.report(ISSUE, context.getLocation(call),
- "Do not call setSeed() on a SecureRandom with a fixed seed: " +
- "it is not secure. Use getSeed().",
- null);
- } else if (opcode == Opcodes.INVOKESTATIC) {
- String methodName = ((MethodInsnNode) prev).name;
- if (methodName.equals("currentTimeMillis") || methodName.equals("nanoTime")) {
- context.report(ISSUE, context.getLocation(call),
- "It is dangerous to seed SecureRandom with the current time because " +
- "that value is more predictable to an attacker than the default seed.",
- null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
deleted file mode 100644
index b6cc957..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SecurityDetector.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_EXPORTED;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PATH;
-import static com.android.SdkConstants.ATTR_PATH_PATTERN;
-import static com.android.SdkConstants.ATTR_PATH_PREFIX;
-import static com.android.SdkConstants.ATTR_PERMISSION;
-import static com.android.SdkConstants.ATTR_READ_PERMISSION;
-import static com.android.SdkConstants.ATTR_WRITE_PERMISSION;
-import static com.android.SdkConstants.TAG_ACTIVITY;
-import static com.android.SdkConstants.TAG_APPLICATION;
-import static com.android.SdkConstants.TAG_GRANT_PERMISSION;
-import static com.android.SdkConstants.TAG_INTENT_FILTER;
-import static com.android.SdkConstants.TAG_PATH_PERMISSION;
-import static com.android.SdkConstants.TAG_PROVIDER;
-import static com.android.SdkConstants.TAG_RECEIVER;
-import static com.android.SdkConstants.TAG_SERVICE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.Identifier;
-import lombok.ast.MethodInvocation;
-import lombok.ast.StrictListAccessor;
-
-/**
- * Checks that exported services request a permission.
- */
-public class SecurityDetector extends Detector implements Detector.XmlScanner,
- Detector.JavaScanner {
-
- /** Exported services */
- public static final Issue EXPORTED_SERVICE = Issue.create(
- "ExportedService", //$NON-NLS-1$
- "Checks for exported services that do not require permissions",
- "Exported services (services which either set `exported=true` or contain " +
- "an intent-filter and do not specify `exported=false`) should define a " +
- "permission that an entity must have in order to launch the service " +
- "or bind to it. Without this, any application can use this service.",
- Category.SECURITY,
- 5,
- Severity.WARNING,
- SecurityDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Exported content providers */
- public static final Issue EXPORTED_PROVIDER = Issue.create(
- "ExportedContentProvider", //$NON-NLS-1$
- "Checks for exported content providers that do not require permissions",
- "Content providers are exported by default and any application on the " +
- "system can potentially use them to read and write data. If the content " +
- "provider provides access to sensitive data, it should be protected by " +
- "specifying `export=false` in the manifest or by protecting it with a " +
- "permission that can be granted to other applications.",
- Category.SECURITY,
- 5,
- Severity.WARNING,
- SecurityDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Exported receivers */
- public static final Issue EXPORTED_RECEIVER = Issue.create(
- "ExportedReceiver", //$NON-NLS-1$
- "Checks for exported receivers that do not require permissions",
- "Exported receivers (receivers which either set `exported=true` or contain " +
- "an intent-filter and do not specify `exported=false`) should define a " +
- "permission that an entity must have in order to launch the receiver " +
- "or bind to it. Without this, any application can use this receiver.",
- Category.SECURITY,
- 5,
- Severity.WARNING,
- SecurityDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Content provides which grant all URIs access */
- public static final Issue OPEN_PROVIDER = Issue.create(
- "GrantAllUris", //$NON-NLS-1$
- "Checks for <grant-uri-permission> elements where everything is shared",
- "The `<grant-uri-permission>` element allows specific paths to be shared. " +
- "This detector checks for a path URL of just '/' (everything), which is " +
- "probably not what you want; you should limit access to a subset.",
- Category.SECURITY,
- 7,
- Severity.WARNING,
- SecurityDetector.class,
- Scope.MANIFEST_SCOPE);
-
- /** Using the world-writable flag */
- public static final Issue WORLD_WRITEABLE = Issue.create(
- "WorldWriteableFiles", //$NON-NLS-1$
- "Checks for openFileOutput() and getSharedPreferences() calls passing " +
- "MODE_WORLD_WRITEABLE",
- "There are cases where it is appropriate for an application to write " +
- "world writeable files, but these should be reviewed carefully to " +
- "ensure that they contain no private data, and that if the file is " +
- "modified by a malicious application it does not trick or compromise " +
- "your application.",
- Category.SECURITY,
- 4,
- Severity.WARNING,
- SecurityDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
-
- /** Using the world-readable flag */
- public static final Issue WORLD_READABLE = Issue.create(
- "WorldReadableFiles", //$NON-NLS-1$
- "Checks for openFileOutput() and getSharedPreferences() calls passing " +
- "MODE_WORLD_READABLE",
- "There are cases where it is appropriate for an application to write " +
- "world readable files, but these should be reviewed carefully to " +
- "ensure that they contain no private data that is leaked to other " +
- "applications.",
- Category.SECURITY,
- 4,
- Severity.WARNING,
- SecurityDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Constructs a new {@link SecurityDetector} check */
- public SecurityDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- // ---- Implements Detector.XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_SERVICE,
- TAG_GRANT_PERMISSION,
- TAG_PROVIDER,
- TAG_ACTIVITY,
- TAG_RECEIVER
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
- if (tag.equals(TAG_SERVICE)) {
- checkService(context, element);
- } else if (tag.equals(TAG_GRANT_PERMISSION)) {
- checkGrantPermission(context, element);
- } else if (tag.equals(TAG_PROVIDER)) {
- checkProvider(context, element);
- } else if (tag.equals(TAG_RECEIVER)) {
- checkReceiver(context, element);
- }
- }
-
- private static boolean getExported(Element element) {
- // Used to check whether an activity, service or broadcast receiver is exported.
- String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
- if (exportValue != null && !exportValue.isEmpty()) {
- return Boolean.valueOf(exportValue);
- } else {
- for (Element child : LintUtils.getChildren(element)) {
- if (child.getTagName().equals(TAG_INTENT_FILTER)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private static boolean isUnprotectedByPermission(Element element) {
- // Used to check whether an activity, service or broadcast receiver are
- // protected by a permission.
- String permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- Node parent = element.getParentNode();
- if (parent.getNodeType() == Node.ELEMENT_NODE
- && parent.getNodeName().equals(TAG_APPLICATION)) {
- Element application = (Element) parent;
- permission = application.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
- return permission == null || permission.isEmpty();
- }
- }
-
- return false;
- }
-
- private static boolean isLauncher(Element element) {
- // Checks whether an element is a launcher activity.
- for (Element child : LintUtils.getChildren(element)) {
- if (child.getTagName().equals(TAG_INTENT_FILTER)) {
- for (Element innerChild: LintUtils.getChildren(child)) {
- if (innerChild.getTagName().equals("category")) { //$NON-NLS-1$
- String categoryString = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
- return "android.intent.category.LAUNCHER".equals(categoryString); //$NON-NLS-1$
- }
- }
- }
- }
-
- return false;
- }
-
- private static boolean isStandardReceiver(Element element) {
- // Checks whether a broadcast receiver receives a standard Android action
- for (Element child : LintUtils.getChildren(element)) {
- if (child.getTagName().equals(TAG_INTENT_FILTER)) {
- for (Element innerChild : LintUtils.getChildren(child)) {
- if (innerChild.getTagName().equals("action")) { //$NON-NLS-1$
- String categoryString = innerChild.getAttributeNS(ANDROID_URI, ATTR_NAME);
- return categoryString.startsWith("android."); //$NON-NLS-1$
- }
- }
- }
- }
- return false;
- }
-
- private static void checkReceiver(XmlContext context, Element element) {
- if (getExported(element) && isUnprotectedByPermission(element) &&
- !isStandardReceiver(element)) {
- // No declared permission for this exported receiver: complain
- context.report(EXPORTED_RECEIVER, element, context.getLocation(element),
- "Exported receiver does not require permission", null);
- }
- }
-
- private static void checkService(XmlContext context, Element element) {
- if (getExported(element) && isUnprotectedByPermission(element)) {
- // No declared permission for this exported service: complain
- context.report(EXPORTED_SERVICE, element, context.getLocation(element),
- "Exported service does not require permission", null);
- }
- }
-
- private static void checkGrantPermission(XmlContext context, Element element) {
- Attr path = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH);
- Attr prefix = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PREFIX);
- Attr pattern = element.getAttributeNodeNS(ANDROID_URI, ATTR_PATH_PATTERN);
-
- String msg = "Content provider shares everything; this is potentially dangerous.";
- if (path != null && path.getValue().equals("/")) { //$NON-NLS-1$
- context.report(OPEN_PROVIDER, path, context.getLocation(path), msg, null);
- }
- if (prefix != null && prefix.getValue().equals("/")) { //$NON-NLS-1$
- context.report(OPEN_PROVIDER, prefix, context.getLocation(prefix), msg, null);
- }
- if (pattern != null && (pattern.getValue().equals("/") //$NON-NLS-1$
- /* || pattern.getValue().equals(".*")*/)) {
- context.report(OPEN_PROVIDER, pattern, context.getLocation(pattern), msg, null);
- }
- }
-
- private static void checkProvider(XmlContext context, Element element) {
- String exportValue = element.getAttributeNS(ANDROID_URI, ATTR_EXPORTED);
- // Content providers are exported by default
- boolean exported = true;
- if (exportValue != null && !exportValue.isEmpty()) {
- exported = Boolean.valueOf(exportValue);
- }
-
- if (exported) {
- // Just check for some use of permissions. Other Lint checks can check the saneness
- // of the permissions. We'll accept the permission, readPermission, or writePermission
- // attributes on the provider element, or a path-permission element.
- String permission = element.getAttributeNS(ANDROID_URI, ATTR_READ_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- permission = element.getAttributeNS(ANDROID_URI, ATTR_WRITE_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- permission = element.getAttributeNS(ANDROID_URI, ATTR_PERMISSION);
- if (permission == null || permission.isEmpty()) {
- // No permission attributes? Check for path-permission.
-
- // TODO: Add a Lint check to ensure the path-permission is good, similar to
- // the grant-uri-permission check.
- boolean hasPermission = false;
- for (Element child : LintUtils.getChildren(element)) {
- String tag = child.getTagName();
- if (tag.equals(TAG_PATH_PERMISSION)) {
- hasPermission = true;
- break;
- }
- }
-
- if (!hasPermission) {
- context.report(EXPORTED_PROVIDER, element,
- context.getLocation(element),
- "Exported content providers can provide access to " +
- "potentially sensitive data",
- null);
- }
- }
- }
- }
- }
- }
-
- // ---- Implements Detector.JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- // These are the API calls that can accept a MODE_WORLD_READABLE/MODE_WORLD_WRITABLE
- // argument.
- List<String> values = new ArrayList<String>(2);
- values.add("openFileOutput"); //$NON-NLS-1$
- values.add("getSharedPreferences"); //$NON-NLS-1$
- return values;
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- StrictListAccessor<Expression,MethodInvocation> args = node.astArguments();
- Iterator<Expression> iterator = args.iterator();
- while (iterator.hasNext()) {
- iterator.next().accept(visitor);
- }
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new IdentifierVisitor(context);
- }
-
- private static class IdentifierVisitor extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public IdentifierVisitor(JavaContext context) {
- super();
- mContext = context;
- }
-
- @Override
- public boolean visitIdentifier(Identifier node) {
- if ("MODE_WORLD_WRITEABLE".equals(node.getDescription())) { //$NON-NLS-1$
- Location location = mContext.getLocation(node);
- mContext.report(WORLD_WRITEABLE, node, location,
- "Using MODE_WORLD_WRITEABLE when creating files can be " +
- "risky, review carefully",
- null);
- } else if ("MODE_WORLD_READABLE".equals(node.getDescription())) { //$NON-NLS-1$
- Location location = mContext.getLocation(node);
- mContext.report(WORLD_READABLE, node, location,
- "Using MODE_WORLD_READABLE when creating files can be " +
- "risky, review carefully",
- null);
- }
-
- return false;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java
deleted file mode 100644
index 4b4ba01..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SetJavaScriptEnabledDetector.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-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 java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.MethodInvocation;
-
-/**
- * Looks for invocations of android.webkit.WebSettings.setJavaScriptEnabled.
- */
-public class SetJavaScriptEnabledDetector extends Detector implements Detector.JavaScanner {
- /** Invocations of setJavaScriptEnabled */
- public static final Issue ISSUE = Issue.create("SetJavaScriptEnabled", //$NON-NLS-1$
- "Looks for invocations of android.webkit.WebSettings.setJavaScriptEnabled",
-
- "Your code should not invoke `setJavaScriptEnabled` if you are not sure that " +
- "your app really requires JavaScript support.",
-
- Category.SECURITY,
- 6,
- Severity.WARNING,
- SetJavaScriptEnabledDetector.class,
- Scope.JAVA_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/practices/security.html"); //$NON-NLS-1$
-
- /** Constructs a new {@link SetJavaScriptEnabledDetector} check */
- public SetJavaScriptEnabledDetector() {
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- if (node.astArguments().size() == 1
- && !node.astArguments().first().toString().equals("false")) { //$NON-NLS-1$
- context.report(ISSUE, node, context.getLocation(node),
- "Using setJavaScriptEnabled can introduce XSS vulnerabilities " +
- "into you application, review carefully.",
- null);
- }
- }
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("setJavaScriptEnabled");
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
deleted file mode 100644
index 76887a5..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SharedPrefsDetector.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.Return;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableReference;
-
-/**
- * Detector looking for SharedPreferences.edit() calls without a corresponding
- * commit() or apply() call
- */
-public class SharedPrefsDetector extends Detector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "CommitPrefEdits", //$NON-NLS-1$
- "Looks for code editing a SharedPreference but forgetting to call commit() on it",
-
- "After calling `edit()` on a `SharedPreference`, you must call `commit()` " +
- "or `apply()` on the editor to save the results.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- SharedPrefsDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Constructs a new {@link SharedPrefsDetector} check */
- public SharedPrefsDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("edit"); //$NON-NLS-1$
- }
-
- @Nullable
- private static NormalTypeBody findSurroundingTypeBody(Node scope) {
- while (scope != null) {
- Class<? extends Node> type = scope.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == NormalTypeBody.class) {
- return (NormalTypeBody) scope;
- }
-
- scope = scope.getParent();
- }
-
- return null;
- }
-
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- assert node.astName().astValue().equals("edit");
- Expression operand = node.astOperand();
- if (operand == null) {
- return;
- }
-
- // Looking for the specific pattern where you assign the edit() result
- // to a local variable; this means we won't recognize some other usages
- // of the API (e.g. assigning it to a previously declared variable) but
- // is needed until we have type attribution in the AST itself.
- Node parent = node.getParent();
- VariableDefinition definition = getLhs(parent);
- boolean allowCommitBeforeTarget;
- if (definition == null) {
- if (operand instanceof VariableReference) {
- NormalTypeBody body = findSurroundingTypeBody(parent);
- if (body == null) {
- return;
- }
- String variableName = ((VariableReference) operand).astIdentifier().astValue();
- String type = getFieldType(body, variableName);
- if (type == null || !type.equals("SharedPreferences")) { //$NON-NLS-1$
- return;
- }
- allowCommitBeforeTarget = true;
- } else {
- return;
- }
- } else {
- String type = definition.astTypeReference().toString();
- if (!type.endsWith("SharedPreferences.Editor")) { //$NON-NLS-1$
- if (!type.equals("Editor") || //$NON-NLS-1$
- !LintUtils.isImported(context.compilationUnit,
- "android.content.SharedPreferences.Editor")) { //$NON-NLS-1$
- return;
- }
- }
- allowCommitBeforeTarget = false;
- }
-
- Node method = JavaContext.findSurroundingMethod(parent);
- if (method == null) {
- return;
- }
-
- CommitFinder finder = new CommitFinder(node, allowCommitBeforeTarget);
- method.accept(finder);
- if (!finder.isCommitCalled()) {
- context.report(ISSUE, method, context.getLocation(node),
- "SharedPreferences.edit() without a corresponding commit() or apply() call",
- null);
- }
- }
-
- @Nullable
- private static String getFieldType(@NonNull NormalTypeBody cls, @NonNull String name) {
- List<Node> children = cls.getChildren();
- for (Node child : children) {
- if (child.getClass() == VariableDeclaration.class) {
- VariableDeclaration declaration = (VariableDeclaration) child;
- VariableDefinition definition = declaration.astDefinition();
- return definition.astTypeReference().toString();
- }
- }
-
- return null;
- }
-
- @Nullable
- private static VariableDefinition getLhs(@NonNull Node node) {
- while (node != null) {
- Class<? extends Node> type = node.getClass();
- // The Lombok AST uses a flat hierarchy of node type implementation classes
- // so no need to do instanceof stuff here.
- if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) {
- return null;
- }
- if (type == VariableDefinition.class) {
- return (VariableDefinition) node;
- }
-
- node = node.getParent();
- }
-
- return null;
- }
-
- private static class CommitFinder extends ForwardingAstVisitor {
- /** The target edit call */
- private final MethodInvocation mTarget;
- /** whether it allows the commit call to be seen before the target node */
- private final boolean mAllowCommitBeforeTarget;
- /** Whether we've found one of the commit/cancel methods */
- private boolean mFound;
- /** Whether we've seen the target edit node yet */
- private boolean mSeenTarget;
-
- private CommitFinder(MethodInvocation target, boolean allowCommitBeforeTarget) {
- mTarget = target;
- mAllowCommitBeforeTarget = allowCommitBeforeTarget;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node == mTarget) {
- mSeenTarget = true;
- } else if (mAllowCommitBeforeTarget || mSeenTarget || node.astOperand() == mTarget) {
- String name = node.astName().astValue();
- if ("commit".equals(name) || "apply".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
- // TODO: Do more flow analysis to see whether we're really calling commit/apply
- // on the right type of object?
- mFound = true;
- }
- }
-
- return super.visitMethodInvocation(node);
- }
-
- @Override
- public boolean visitReturn(Return node) {
- if (node.astValue() == mTarget) {
- // If you just do "return editor.commit() don't warn
- mFound = true;
- }
- return super.visitReturn(node);
- }
-
- boolean isCommitCalled() {
- return mFound;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java
deleted file mode 100644
index c0dea30..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StateListDetector.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Checks for unreachable states in an Android state list definition
- */
-public class StateListDetector extends ResourceXmlDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "StateListReachable", //$NON-NLS-1$
- "Looks for unreachable states in a <selector>",
- "In a selector, only the last child in the state list should omit a " +
- "state qualifier. If not, all subsequent items in the list will be ignored " +
- "since the given item will match all.",
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- StateListDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- private static final String STATE_PREFIX = "state_"; //$NON-NLS-1$
-
- /** Constructs a new {@link StateListDetector} */
- public StateListDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.DRAWABLE;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- // TODO: Look for views that don't specify
- // Display the error token somewhere so it can be suppressed
- // Emit warning at the end "run with --help to learn how to suppress types of errors/checks";
- // ("...and this message.")
-
- Element root = document.getDocumentElement();
- if (root != null && root.getTagName().equals("selector")) { //$NON-NLS-1$
- List<Element> children = LintUtils.getChildren(root);
- Map<Element, Set<String>> states =
- new HashMap<Element, Set<String>>(children.size());
-
- for (int i = 0; i < children.size(); i++) {
- Element child = children.get(i);
- NamedNodeMap attributes = child.getAttributes();
- Set<String> stateNames = new HashSet<String>(attributes.getLength());
- states.put(child, stateNames);
-
- for (int j = 0; j < attributes.getLength(); j++) {
- Attr attribute = (Attr) attributes.item(j);
- String name = attribute.getLocalName();
- if (name == null) {
- continue;
- }
- if (name.startsWith(STATE_PREFIX)) {
- stateNames.add(name + '=' + attribute.getValue());
- } else {
- String namespaceUri = attribute.getNamespaceURI();
- if (namespaceUri != null && !namespaceUri.isEmpty() &&
- !ANDROID_URI.equals(namespaceUri)) {
- // There is a custom attribute on this item.
- // This could be a state, see
- // http://code.google.com/p/android/issues/detail?id=22339
- // so don't flag this one.
- stateNames.add(attribute.getName() + '=' + attribute.getValue());
- }
- }
- }
- }
-
- // See if for each state, any subsequent state fully contains all the same
- // state requirements
-
- for (int i = 0; i < children.size() - 1; i++) {
- Element prev = children.get(i);
- Set<String> prevStates = states.get(prev);
- assert prevStates != null : prev;
- for (int j = i + 1; j < children.size(); j++) {
- Element current = children.get(j);
- Set<String> currentStates = states.get(current);
- assert currentStates != null : current;
- if (currentStates.containsAll(prevStates)) {
- Location location = context.getLocation(current);
- Location secondary = context.getLocation(prev);
- secondary.setMessage("Earlier item which masks item");
- location.setSecondary(secondary);
- context.report(ISSUE, current, location, String.format(
- "This item is unreachable because a previous item (item #%1$d) is a more general match than this one",
- i + 1), null);
- // Don't keep reporting errors for all the remaining cases in this file
- return;
- }
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
deleted file mode 100644
index 033996e..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java
+++ /dev/null
@@ -1,1299 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.FORMAT_METHOD;
-import static com.android.SdkConstants.GET_STRING_METHOD;
-import static com.android.SdkConstants.TAG_STRING;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.IJavaParser;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-import com.android.tools.lint.detector.api.Position;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.CharLiteral;
-import lombok.ast.ConstructorDeclaration;
-import lombok.ast.ConstructorInvocation;
-import lombok.ast.Expression;
-import lombok.ast.FloatingPointLiteral;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.MethodDeclaration;
-import lombok.ast.MethodInvocation;
-import lombok.ast.NullLiteral;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-import lombok.ast.StringLiteral;
-import lombok.ast.VariableDefinitionEntry;
-import lombok.ast.VariableReference;
-
-/**
- * Check which looks for problems with formatting strings such as inconsistencies between
- * translations or between string declaration and string usage in Java.
- * <p>
- * TODO: Handle Resources.getQuantityString as well
- */
-public class StringFormatDetector extends ResourceXmlDetector implements Detector.JavaScanner {
- /** Whether formatting strings are invalid */
- public static final Issue INVALID = Issue.create(
- "StringFormatInvalid", //$NON-NLS-1$
- "Checks that format strings are valid",
-
- "If a string contains a '%' character, then the string may be a formatting string " +
- "which will be passed to `String.format` from Java code to replace each '%' " +
- "occurrence with specific values.\n" +
- "\n" +
- "This lint warning checks for two related problems:\n" +
- "(1) Formatting strings that are invalid, meaning that `String.format` will throw " +
- "exceptions at runtime when attempting to use the format string.\n" +
- "(2) Strings containing '%' that are not formatting strings getting passed to " +
- "a `String.format` call. In this case the '%' will need to be escaped as '%%'.\n" +
- "\n" +
- "NOTE: Not all Strings which look like formatting strings are intended for " +
- "use by `String.format`; for example, they may contain date formats intended " +
- "for `android.text.format.Time#format()`. Lint cannot always figure out that " +
- "a String is a date format, so you may get false warnings in those scenarios. " +
- "See the suppress help topic for information on how to suppress errors in " +
- "that case.",
-
- Category.MESSAGES,
- 9,
- Severity.ERROR,
- StringFormatDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Whether formatting argument types are consistent across translations */
- public static final Issue ARG_COUNT = Issue.create(
- "StringFormatCount", //$NON-NLS-1$
- "Ensures that all format strings are used and that the same number is defined "
- + "across translations",
-
- "When a formatted string takes arguments, it usually needs to reference the " +
- "same arguments in all translations. There are cases where this is not the case, " +
- "so this issue is a warning rather than an error by default. However, this usually " +
- "happens when a language is not translated or updated correctly.",
- Category.MESSAGES,
- 5,
- Severity.WARNING,
- StringFormatDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Whether the string format supplied in a call to String.format matches the format string */
- public static final Issue ARG_TYPES = Issue.create(
- "StringFormatMatches", //$NON-NLS-1$
- "Ensures that the format used in <string> definitions is compatible with the "
- + "String.format call",
-
- "This lint check ensures the following:\n" +
- "(1) If there are multiple translations of the format string, then all translations " +
- "use the same type for the same numbered arguments\n" +
- "(2) The usage of the format string in Java is consistent with the format string, " +
- "meaning that the parameter types passed to String.format matches those in the " +
- "format string.",
- Category.MESSAGES,
- 9,
- Severity.ERROR,
- StringFormatDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE));
-
- /**
- * Map from a format string name to a list of declaration file and actual
- * formatting string content. We're using a list since a format string can be
- * defined multiple times, usually for different translations.
- */
- private Map<String, List<Pair<Handle, String>>> mFormatStrings;
-
- /**
- * Map of strings that contain percents that aren't formatting strings; these
- * should not be passed to String.format.
- */
- private final Map<String, Handle> mNotFormatStrings = new HashMap<String, Handle>();
-
- /**
- * Set of strings that have an unknown format such as date formatting; we should not
- * flag these as invalid when used from a String#format call
- */
- private Set<String> mIgnoreStrings;
-
- /** Constructs a new {@link StringFormatDetector} check */
- public StringFormatDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- if (LintUtils.endsWith(file.getName(), DOT_JAVA)) {
- return mFormatStrings != null;
- }
-
- return super.appliesTo(context, file);
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(TAG_STRING);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- NodeList childNodes = element.getChildNodes();
- if (childNodes.getLength() > 0) {
- if (childNodes.getLength() == 1) {
- Node child = childNodes.item(0);
- if (child.getNodeType() == Node.TEXT_NODE) {
- checkTextNode(context, element, strip(child.getNodeValue()));
- }
- } else {
- // Concatenate children and build up a plain string.
- // This is needed to handle xliff localization documents,
- // but this needs more work so ignore compound XML documents as
- // string values for now:
- //StringBuilder sb = new StringBuilder();
- //addText(sb, element);
- //if (sb.length() > 0) {
- // checkTextNode(context, element, sb.toString());
- //}
- }
- }
- }
-
- //private static void addText(StringBuilder sb, Node node) {
- // if (node.getNodeType() == Node.TEXT_NODE) {
- // sb.append(strip(node.getNodeValue().trim()));
- // } else {
- // NodeList childNodes = node.getChildNodes();
- // for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- // addText(sb, childNodes.item(i));
- // }
- // }
- //}
-
- private static String strip(String s) {
- if (s.length() < 2) {
- return s;
- }
- char first = s.charAt(0);
- char last = s.charAt(s.length() - 1);
- if (first == last && (first == '\'' || first == '"')) {
- return s.substring(1, s.length() - 1);
- }
-
- return s;
- }
-
- private void checkTextNode(XmlContext context, Element element, String text) {
- String name = null;
- boolean found = false;
-
- // Look at the String and see if it's a format string (contains
- // positional %'s)
- for (int j = 0, m = text.length(); j < m; j++) {
- char c = text.charAt(j);
- if (c == '\\') {
- j++;
- }
- if (c == '%') {
- if (name == null) {
- name = element.getAttribute(ATTR_NAME);
- }
-
- // Also make sure this String isn't an unformatted String
- String formatted = element.getAttribute("formatted"); //$NON-NLS-1$
- if (!formatted.isEmpty() && !Boolean.parseBoolean(formatted)) {
- if (!mNotFormatStrings.containsKey(name)) {
- Handle handle = context.parser.createLocationHandle(context, element);
- handle.setClientData(element);
- mNotFormatStrings.put(name, handle);
- }
- return;
- }
-
- // See if it's not a format string, e.g. "Battery charge is 100%!".
- // If so we want to record this name in a special list such that we can
- // make sure you don't attempt to reference this string from a String.format
- // call.
- Matcher matcher = FORMAT.matcher(text);
- if (!matcher.find(j)) {
- if (!mNotFormatStrings.containsKey(name)) {
- Handle handle = context.parser.createLocationHandle(context, element);
- handle.setClientData(element);
- mNotFormatStrings.put(name, handle);
- }
- return;
- }
-
- String conversion = matcher.group(6);
- int conversionClass = getConversionClass(conversion.charAt(0));
- if (conversionClass == CONVERSION_CLASS_UNKNOWN || matcher.group(5) != null) {
- if (mIgnoreStrings == null) {
- mIgnoreStrings = new HashSet<String>();
- }
- mIgnoreStrings.add(name);
-
- // Don't process any other strings here; some of them could
- // accidentally look like a string, e.g. "%H" is a hash code conversion
- // in String.format (and hour in Time formatting).
- return;
- }
-
- found = true;
- j++; // Ensure that when we process a "%%" we don't separately check the second %
- }
- }
-
- if (found && name != null) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- // Record it for analysis when seen in Java code
- if (mFormatStrings == null) {
- mFormatStrings = new HashMap<String, List<Pair<Handle,String>>>();
- }
-
- List<Pair<Handle, String>> list = mFormatStrings.get(name);
- if (list == null) {
- list = new ArrayList<Pair<Handle, String>>();
- mFormatStrings.put(name, list);
- }
- Handle handle = context.parser.createLocationHandle(context, element);
- handle.setClientData(element);
- list.add(Pair.of(handle, text));
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mFormatStrings != null) {
- boolean checkCount = context.isEnabled(ARG_COUNT);
- boolean checkValid = context.isEnabled(INVALID);
- boolean checkTypes = context.isEnabled(ARG_TYPES);
-
- // Ensure that all the format strings are consistent with respect to each other;
- // e.g. they all have the same number of arguments, they all use all the
- // arguments, and they all use the same types for all the numbered arguments
- for (Map.Entry<String, List<Pair<Handle, String>>> entry : mFormatStrings.entrySet()) {
- String name = entry.getKey();
- List<Pair<Handle, String>> list = entry.getValue();
-
- // Check argument counts
- if (checkCount) {
- checkArity(context, name, list);
- }
-
- // Check argument types (and also make sure that the formatting strings are valid)
- if (checkValid || checkTypes) {
- checkTypes(context, checkValid, checkTypes, name, list);
- }
- }
- }
- }
-
- private static void checkTypes(Context context, boolean checkValid,
- boolean checkTypes, String name, List<Pair<Handle, String>> list) {
- Map<Integer, String> types = new HashMap<Integer, String>();
- Map<Integer, Handle> typeDefinition = new HashMap<Integer, Handle>();
- for (Pair<Handle, String> pair : list) {
- Handle handle = pair.getFirst();
- String formatString = pair.getSecond();
-
- //boolean warned = false;
- Matcher matcher = FORMAT.matcher(formatString);
- int index = 0;
- int prevIndex = 0;
- int nextNumber = 1;
- while (true) {
- if (matcher.find(index)) {
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = formatString.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- index = matcher.end(); // Ensure loop proceeds
- String str = formatString.substring(matchStart, matcher.end());
- if (str.equals("%%")) { //$NON-NLS-1$
- // Just an escaped %
- continue;
- }
-
- if (checkValid) {
- // Make sure it's a valid format string
- if (str.length() > 2 && str.charAt(str.length() - 2) == ' ') {
- char last = str.charAt(str.length() - 1);
- // If you forget to include the conversion character, e.g.
- // "Weight=%1$ g" instead of "Weight=%1$d g", then
- // you're going to end up with a format string interpreted as
- // "%1$ g". This means that the space character is interpreted
- // as a flag character, but it can only be a flag character
- // when used in conjunction with the numeric conversion
- // formats (d, o, x, X). If that's not the case, make a
- // dedicated error message
- if (last != 'd' && last != 'o' && last != 'x' && last != 'X') {
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(INVALID,
- (Node) clientData)) {
- return;
- }
- }
-
- Location location = handle.resolve();
- String message = String.format(
- "Incorrect formatting string %1$s; missing conversion " +
- "character in '%2$s' ?", name, str);
- context.report(INVALID, location, message, null);
- //warned = true;
- continue;
- }
- }
- }
-
- if (!checkTypes) {
- continue;
- }
-
- // Shouldn't throw a number format exception since we've already
- // matched the pattern in the regexp
- int number;
- String numberString = matcher.group(1);
- if (numberString != null) {
- // Strip off trailing $
- numberString = numberString.substring(0, numberString.length() - 1);
- number = Integer.parseInt(numberString);
- nextNumber = number + 1;
- } else {
- number = nextNumber++;
- }
- String format = matcher.group(6);
- String currentFormat = types.get(number);
- if (currentFormat == null) {
- types.put(number, format);
- typeDefinition.put(number, handle);
- } else if (!currentFormat.equals(format)
- && isIncompatible(currentFormat.charAt(0), format.charAt(0))) {
-
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ARG_TYPES, (Node) clientData)) {
- return;
- }
- }
-
- Location location = handle.resolve();
- // Attempt to limit the location range to just the formatting
- // string in question
- location = refineLocation(context, location, formatString,
- matcher.start(), matcher.end());
- Location otherLocation = typeDefinition.get(number).resolve();
- otherLocation.setMessage("Conflicting argument type here");
- location.setSecondary(otherLocation);
- File f = otherLocation.getFile();
- String message = String.format(
- "Inconsistent formatting types for argument #%1$d in " +
- "format string %2$s ('%3$s'): Found both '%4$s' and '%5$s' " +
- "(in %6$s)",
- number, name,
- str,
- currentFormat, format,
- f.getParentFile().getName() + File.separator + f.getName());
- //warned = true;
- context.report(ARG_TYPES, location, message, null);
- break;
- }
- } else {
- break;
- }
- }
-
- // Check that the format string is valid by actually attempting to instantiate
- // it. We only do this if we haven't already complained about this string
- // for other reasons.
- /* Check disabled for now: it had many false reports due to conversion
- * errors (which is expected since we just pass in strings), but once those
- * are eliminated there aren't really any other valid error messages returned
- * (for example, calling the formatter with bogus formatting flags always just
- * returns a "conversion" error. It looks like we'd need to actually pass compatible
- * arguments to trigger other types of formatting errors such as precision errors.
- if (!warned && checkValid) {
- try {
- formatter.format(formatString, "", "", "", "", "", "", "",
- "", "", "", "", "", "", "");
-
- } catch (IllegalFormatException t) { // TODO: UnknownFormatConversionException
- if (!t.getLocalizedMessage().contains(" != ")
- && !t.getLocalizedMessage().contains("Conversion")) {
- Location location = handle.resolve();
- context.report(INVALID, location,
- String.format("Wrong format for %1$s: %2$s",
- name, t.getLocalizedMessage()), null);
- }
- }
- }
- */
- }
- }
-
- /**
- * Returns true if two String.format conversions are "incompatible" (meaning
- * that using these two for the same argument across different translations
- * is more likely an error than intentional. Some conversions are
- * incompatible, e.g. "d" and "s" where one is a number and string, whereas
- * others may work (e.g. float versus integer) but are probably not
- * intentional.
- */
- private static boolean isIncompatible(char conversion1, char conversion2) {
- int class1 = getConversionClass(conversion1);
- int class2 = getConversionClass(conversion2);
- return class1 != class2
- && class1 != CONVERSION_CLASS_UNKNOWN
- && class2 != CONVERSION_CLASS_UNKNOWN;
- }
-
- private static final int CONVERSION_CLASS_UNKNOWN = 0;
- private static final int CONVERSION_CLASS_STRING = 1;
- private static final int CONVERSION_CLASS_CHARACTER = 2;
- private static final int CONVERSION_CLASS_INTEGER = 3;
- private static final int CONVERSION_CLASS_FLOAT = 4;
- private static final int CONVERSION_CLASS_BOOLEAN = 5;
- private static final int CONVERSION_CLASS_HASHCODE = 6;
- private static final int CONVERSION_CLASS_PERCENT = 7;
- private static final int CONVERSION_CLASS_NEWLINE = 8;
- private static final int CONVERSION_CLASS_DATETIME = 9;
-
- private static int getConversionClass(char conversion) {
- // See http://developer.android.com/reference/java/util/Formatter.html
- switch (conversion) {
- case 't': // Time/date conversion
- case 'T':
- return CONVERSION_CLASS_DATETIME;
- case 's': // string
- case 'S': // Uppercase string
- return CONVERSION_CLASS_STRING;
- case 'c': // character
- case 'C': // Uppercase character
- return CONVERSION_CLASS_CHARACTER;
- case 'd': // decimal
- case 'o': // octal
- case 'x': // hex
- case 'X':
- return CONVERSION_CLASS_INTEGER;
- case 'f': // decimal float
- case 'e': // exponential float
- case 'E':
- case 'g': // decimal or exponential depending on size
- case 'G':
- case 'a': // hex float
- case 'A':
- return CONVERSION_CLASS_FLOAT;
- case 'b': // boolean
- case 'B':
- return CONVERSION_CLASS_BOOLEAN;
- case 'h': // boolean
- case 'H':
- return CONVERSION_CLASS_HASHCODE;
- case '%': // literal
- return CONVERSION_CLASS_PERCENT;
- case 'n': // literal
- return CONVERSION_CLASS_NEWLINE;
- }
-
- return CONVERSION_CLASS_UNKNOWN;
- }
-
- private static Location refineLocation(Context context, Location location,
- String formatString, int substringStart, int substringEnd) {
- Position startLocation = location.getStart();
- Position endLocation = location.getStart();
- if (startLocation != null && endLocation != null) {
- int startOffset = startLocation.getOffset();
- int endOffset = endLocation.getOffset();
- if (startOffset >= 0) {
- String contents = context.getClient().readFile(location.getFile());
- if (contents != null
- && endOffset <= contents.length() && startOffset < endOffset) {
- int formatOffset = contents.indexOf(formatString, startOffset);
- if (formatOffset != -1 && formatOffset <= endOffset) {
- return Location.create(context.file, contents,
- formatOffset + substringStart, formatOffset + substringEnd);
- }
- }
- }
- }
-
- return location;
- }
-
- /**
- * Check that the number of arguments in the format string is consistent
- * across translations, and that all arguments are used
- */
- private static void checkArity(Context context, String name, List<Pair<Handle, String>> list) {
- // Check to make sure that the argument counts and types are consistent
- int prevCount = -1;
- for (Pair<Handle, String> pair : list) {
- Set<Integer> indices = new HashSet<Integer>();
- int count = getFormatArgumentCount(pair.getSecond(), indices);
- Handle handle = pair.getFirst();
- if (prevCount != -1 && prevCount != count) {
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ARG_COUNT, (Node) clientData)) {
- return;
- }
- }
- Location location = handle.resolve();
- Location secondary = list.get(0).getFirst().resolve();
- secondary.setMessage("Conflicting number of arguments here");
- location.setSecondary(secondary);
- String message = String.format(
- "Inconsistent number of arguments in formatting string %1$s; " +
- "found both %2$d and %3$d", name, prevCount, count);
- context.report(ARG_COUNT, location, message, null);
- break;
- }
-
- for (int i = 1; i <= count; i++) {
- if (!indices.contains(i)) {
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(ARG_COUNT, (Node) clientData)) {
- return;
- }
- }
-
- Set<Integer> all = new HashSet<Integer>();
- for (int j = 1; j < count; j++) {
- all.add(j);
- }
- all.removeAll(indices);
- List<Integer> sorted = new ArrayList<Integer>(all);
- Collections.sort(sorted);
- Location location = handle.resolve();
- String message = String.format(
- "Formatting string '%1$s' is not referencing numbered arguments %2$s",
- name, sorted);
- context.report(ARG_COUNT, location, message, null);
- break;
- }
- }
-
- prevCount = count;
- }
- }
-
- // See java.util.Formatter docs
- private static final Pattern FORMAT = Pattern.compile(
- // Generic format:
- // %[argument_index$][flags][width][.precision]conversion
- //
- "%" + //$NON-NLS-1$
- // Argument Index
- "(\\d+\\$)?" + //$NON-NLS-1$
- // Flags
- "([-+#, 0(\\<]*)?" + //$NON-NLS-1$
- // Width
- "(\\d+)?" + //$NON-NLS-1$
- // Precision
- "(\\.\\d+)?" + //$NON-NLS-1$
- // Conversion. These are all a single character, except date/time conversions
- // which take a prefix of t/T:
- "([tT])?" + //$NON-NLS-1$
- // The current set of conversion characters are
- // b,h,s,c,d,o,x,e,f,g,a,t (as well as all those as upper-case characters), plus
- // n for newlines and % as a literal %. And then there are all the time/date
- // characters: HIKLm etc. Just match on all characters here since there should
- // be at least one.
- "([a-zA-Z%])"); //$NON-NLS-1$
-
- /** Given a format string returns the format type of the given argument */
- @VisibleForTesting
- static String getFormatArgumentType(String s, int argument) {
- Matcher matcher = FORMAT.matcher(s);
- int index = 0;
- int prevIndex = 0;
- int nextNumber = 1;
- while (true) {
- if (matcher.find(index)) {
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = s.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- // Shouldn't throw a number format exception since we've already
- // matched the pattern in the regexp
- int number;
- String numberString = matcher.group(1);
- if (numberString != null) {
- // Strip off trailing $
- numberString = numberString.substring(0, numberString.length() - 1);
- number = Integer.parseInt(numberString);
- nextNumber = number + 1;
- } else {
- number = nextNumber++;
- }
-
- if (number == argument) {
- return matcher.group(6);
- }
- index = matcher.end();
- } else {
- break;
- }
- }
-
- return null;
- }
-
- /**
- * Given a format string returns the number of required arguments. If the
- * {@code seenArguments} parameter is not null, put the indices of any
- * observed arguments into it.
- */
- @VisibleForTesting
- static int getFormatArgumentCount(String s, Set<Integer> seenArguments) {
- Matcher matcher = FORMAT.matcher(s);
- int index = 0;
- int prevIndex = 0;
- int nextNumber = 1;
- int max = 0;
- while (true) {
- if (matcher.find(index)) {
- if ("%".equals(matcher.group(6))) { //$NON-NLS-1$
- index = matcher.end();
- continue;
- }
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = s.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- // Shouldn't throw a number format exception since we've already
- // matched the pattern in the regexp
- int number;
- String numberString = matcher.group(1);
- if (numberString != null) {
- // Strip off trailing $
- numberString = numberString.substring(0, numberString.length() - 1);
- number = Integer.parseInt(numberString);
- nextNumber = number + 1;
- } else {
- number = nextNumber++;
- }
-
- if (number > max) {
- max = number;
- }
- if (seenArguments != null) {
- seenArguments.add(number);
- }
-
- index = matcher.end();
- } else {
- break;
- }
- }
-
- return max;
- }
-
- /**
- * Determines whether the given {@link String#format(String, Object...)}
- * formatting string is "locale dependent", meaning that its output depends
- * on the locale. This is the case if it for example references decimal
- * numbers of dates and times.
- *
- * @param format the format string
- * @return true if the format is locale sensitive, false otherwise
- */
- public static boolean isLocaleSpecific(@NonNull String format) {
- if (format.indexOf('%') == -1) {
- return false;
- }
-
- String s = format;
- Matcher matcher = FORMAT.matcher(s);
- int index = 0;
- int prevIndex = 0;
- while (true) {
- if (matcher.find(index)) {
- int matchStart = matcher.start();
- // Make sure this is not an escaped '%'
- for (; prevIndex < matchStart; prevIndex++) {
- char c = s.charAt(prevIndex);
- if (c == '\\') {
- prevIndex++;
- }
- }
- if (prevIndex > matchStart) {
- // We're in an escape, ignore this result
- index = prevIndex;
- continue;
- }
-
- String type = matcher.group(6);
- if (!type.isEmpty()) {
- char t = type.charAt(0);
-
- // The following formatting characters are locale sensitive:
- switch (t) {
- case 'd': // decimal integer
- case 'e': // scientific
- case 'E':
- case 'f': // decimal float
- case 'g': // general
- case 'G':
- case 't': // date/time
- case 'T':
- return true;
- }
- }
- index = matcher.end();
- } else {
- break;
- }
- }
-
- return false;
- }
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Arrays.asList(FORMAT_METHOD, GET_STRING_METHOD);
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- if (mFormatStrings == null) {
- return;
- }
-
- String methodName = node.astName().getDescription();
- if (methodName.equals(FORMAT_METHOD)) {
- // String.format(getResources().getString(R.string.foo), arg1, arg2, ...)
- // Check that the arguments in R.string.foo match arg1, arg2, ...
- if (node.astOperand() instanceof VariableReference) {
- VariableReference ref = (VariableReference) node.astOperand();
- if ("String".equals(ref.astIdentifier().astValue())) { //$NON-NLS-1$
- // Found a String.format call
- // Look inside to see if we can find an R string
- // Find surrounding method
- checkFormatCall(context, node);
- }
- }
- } else {
- // getResources().getString(R.string.foo, arg1, arg2, ...)
- // Check that the arguments in R.string.foo match arg1, arg2, ...
- if (node.astArguments().size() > 1 && node.astOperand() != null ) {
- checkFormatCall(context, node);
- }
- }
- }
-
- private void checkFormatCall(JavaContext context, MethodInvocation node) {
- lombok.ast.Node current = getParentMethod(node);
- if (current != null) {
- checkStringFormatCall(context, current, node);
- }
- }
-
- /**
- * Check the given String.format call (with the given arguments) to see if
- * the string format is being used correctly
- *
- * @param context the context to report errors to
- * @param method the method containing the {@link String#format} call
- * @param call the AST node for the {@link String#format}
- */
- private void checkStringFormatCall(
- JavaContext context,
- lombok.ast.Node method,
- MethodInvocation call) {
-
- StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
- if (args.isEmpty()) {
- return;
- }
-
- StringTracker tracker = new StringTracker(method, call, 0);
- method.accept(tracker);
- String name = tracker.getFormatStringName();
- if (name == null) {
- return;
- }
-
- if (mIgnoreStrings != null && mIgnoreStrings.contains(name)) {
- return;
- }
-
- if (mNotFormatStrings.containsKey(name)) {
- Handle handle = mNotFormatStrings.get(name);
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(INVALID, (Node) clientData)) {
- return;
- }
- }
- Location location = handle.resolve();
- String message = String.format(
- "Format string '%1$s' is not a valid format string so it should not be " +
- "passed to String.format",
- name);
- context.report(INVALID, call, location, message, null);
- return;
- }
-
- // TODO: Need type information in the AST
- Iterator<Expression> argIterator = args.iterator();
- Expression first = argIterator.next();
- Expression second = argIterator.hasNext() ? argIterator.next() : null;
- String firstName = first.toString();
- boolean specifiesLocale = firstName.startsWith("Locale.") //$NON-NLS-1$
- || firstName.contains("locale") //$NON-NLS-1$
- || firstName.equals("null") //$NON-NLS-1$
- || second != null && second.toString().contains("getString"); //$NON-NLS-1$
-
- List<Pair<Handle, String>> list = mFormatStrings.get(name);
- if (list != null) {
- for (Pair<Handle, String> pair : list) {
- String s = pair.getSecond();
- int count = getFormatArgumentCount(s, null);
- Handle handle = pair.getFirst();
- if (count != args.size() - 1 - (specifiesLocale ? 1 : 0)) {
- Location location = context.parser.getLocation(context, call);
- Location secondary = handle.resolve();
- secondary.setMessage(String.format("This definition requires %1$d arguments",
- count));
- location.setSecondary(secondary);
- String message = String.format(
- "Wrong argument count, format string %1$s requires %2$d but format " +
- "call supplies %3$d",
- name, count, args.size() - 1);
- context.report(ARG_TYPES, method, location, message, null);
- } else {
- for (int i = 1; i <= count; i++) {
- int argumentIndex = i + (specifiesLocale ? 1 : 0);
- Class<?> type = tracker.getArgumentType(argumentIndex);
- if (type != null) {
- boolean valid = true;
- String formatType = getFormatArgumentType(s, i);
- char last = formatType.charAt(formatType.length() - 1);
- if (formatType.length() >= 2 &&
- Character.toLowerCase(
- formatType.charAt(formatType.length() - 2)) == 't') {
- // Date time conversion.
- // TODO
- continue;
- }
- switch (last) {
- // Booleans. It's okay to pass objects to these;
- // it will print "true" if non-null, but it's
- // unusual and probably not intended.
- case 'b':
- case 'B':
- valid = type == Boolean.TYPE;
- break;
-
- // Numeric: integer and floats in various formats
- case 'x':
- case 'X':
- case 'd':
- case 'o':
- case 'e':
- case 'E':
- case 'f':
- case 'g':
- case 'G':
- case 'a':
- case 'A':
- valid = type == Integer.TYPE
- || type == Float.TYPE;
- break;
- case 'c':
- case 'C':
- // Unicode character
- valid = type == Character.TYPE;
- break;
- case 'h':
- case 'H': // Hex print of hash code of objects
- case 's':
- case 'S':
- // String. Can pass anything, but warn about
- // numbers since you may have meant more
- // specific formatting. Use special issue
- // explanation for this?
- valid = type != Boolean.TYPE &&
- !type.isAssignableFrom(Number.class);
- break;
- }
-
- if (!valid) {
- IJavaParser parser = context.parser;
- Expression argument = tracker.getArgument(argumentIndex);
- Location location = parser.getLocation(context, argument);
- Location secondary = handle.resolve();
- secondary.setMessage("Conflicting argument declaration here");
- location.setSecondary(secondary);
-
- String message = String.format(
- "Wrong argument type for formatting argument '#%1$d' " +
- "in %2$s: conversion is '%3$s', received %4$s",
- i, name, formatType, type.getSimpleName());
- context.report(ARG_TYPES, method, location, message, null);
- }
- }
- }
- }
- }
- }
- }
-
- /** Returns the parent method of the given AST node */
- @Nullable
- static lombok.ast.Node getParentMethod(@NonNull lombok.ast.Node node) {
- lombok.ast.Node current = node.getParent();
- while (current != null
- && !(current instanceof MethodDeclaration)
- && !(current instanceof ConstructorDeclaration)) {
- current = current.getParent();
- }
-
- return current;
- }
-
- /** Returns the resource name corresponding to the first argument in the given call */
- static String getResourceForFirstArg(lombok.ast.Node method, lombok.ast.Node call) {
- assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
- StringTracker tracker = new StringTracker(method, call, 0);
- method.accept(tracker);
- String name = tracker.getFormatStringName();
-
- return name;
- }
-
- /** Returns the resource name corresponding to the given argument in the given call */
- static String getResourceArg(lombok.ast.Node method, lombok.ast.Node call, int argIndex) {
- assert call instanceof MethodInvocation || call instanceof ConstructorInvocation;
- StringTracker tracker = new StringTracker(method, call, argIndex);
- method.accept(tracker);
- String name = tracker.getFormatStringName();
-
- return name;
- }
-
- /**
- * Given a variable reference, finds the original R.string value corresponding to it.
- * For example:
- * <pre>
- * {@code
- * String target = "World";
- * String hello = getResources().getString(R.string.hello);
- * String output = String.format(hello, target);
- * }
- * </pre>
- *
- * Given the {@code String.format} call, we want to find out what R.string resource
- * corresponds to the first argument, in this case {@code R.string.hello}.
- * To do this, we look for R.string references, and track those through assignments
- * until we reach the target node.
- * <p>
- * In addition, it also does some primitive type tracking such that it (in some cases)
- * can answer questions about the types of variables. This allows it to check whether
- * certain argument types are valid. Note however that it does not do full-blown
- * type analysis by checking method call signatures and so on.
- */
- private static class StringTracker extends ForwardingAstVisitor {
- /** Method we're searching within */
- private final lombok.ast.Node mTop;
- /** The argument index in the method we're targeting */
- private final int mArgIndex;
- /** Map from variable name to corresponding string resource name */
- private final Map<String, String> mMap = new HashMap<String, String>();
- /** Map from variable name to corresponding type */
- private final Map<String, Class<?>> mTypes = new HashMap<String, Class<?>>();
- /** The AST node for the String.format we're interested in */
- private final lombok.ast.Node mTargetNode;
- private boolean mDone;
- /**
- * Result: the name of the string resource being passed to the
- * String.format, if any
- */
- private String mName;
-
- public StringTracker(lombok.ast.Node top, lombok.ast.Node targetNode, int argIndex) {
- mTop = top;
- mArgIndex = argIndex;
- mTargetNode = targetNode;
- }
-
- public String getFormatStringName() {
- return mName;
- }
-
- /** Returns the argument type of the given formatting argument of the
- * target node. Note: This is in the formatting string, which is one higher
- * than the String.format parameter number, since the first argument is the
- * formatting string itself.
- *
- * @param argument the argument number
- * @return the class (such as {@link Integer#TYPE} etc) or null if not known
- */
- public Class<?> getArgumentType(int argument) {
- Expression arg = getArgument(argument);
- if (arg != null) {
- Class<?> type = getType(arg);
- if (type != null) {
- return type;
- }
- }
-
- return null;
- }
-
- public Expression getArgument(int argument) {
- if (!(mTargetNode instanceof MethodInvocation)) {
- return null;
- }
- MethodInvocation call = (MethodInvocation) mTargetNode;
- StrictListAccessor<Expression, MethodInvocation> args = call.astArguments();
- if (argument >= args.size()) {
- return null;
- }
-
- Iterator<Expression> iterator = args.iterator();
- int index = 0;
- while (iterator.hasNext()) {
- Expression arg = iterator.next();
- if (index++ == argument) {
- return arg;
- }
- }
-
- return null;
- }
-
- @Override
- public boolean visitNode(lombok.ast.Node node) {
- if (mDone) {
- return true;
- }
-
- return super.visitNode(node);
- }
-
- @Override
- public boolean visitVariableReference(VariableReference node) {
- if (node.astIdentifier().getDescription().equals("R") && //$NON-NLS-1$
- node.getParent() instanceof Select &&
- node.getParent().getParent() instanceof Select) {
-
- // See if we're on the right hand side of an assignment
- lombok.ast.Node current = node.getParent().getParent();
- String reference = ((Select) current).astIdentifier().astValue();
-
- while (current != mTop && !(current instanceof VariableDefinitionEntry)) {
- if (current == mTargetNode) {
- mName = reference;
- mDone = true;
- return false;
- }
- current = current.getParent();
- }
- if (current instanceof VariableDefinitionEntry) {
- VariableDefinitionEntry entry = (VariableDefinitionEntry) current;
- String variable = entry.astName().astValue();
- mMap.put(variable, reference);
- }
- }
-
- return false;
- }
-
- @Nullable
- private Expression getTargetArgument() {
- Iterator<Expression> iterator;
- if (mTargetNode instanceof MethodInvocation) {
- iterator = ((MethodInvocation) mTargetNode).astArguments().iterator();
- } else if (mTargetNode instanceof ConstructorInvocation) {
- iterator = ((ConstructorInvocation) mTargetNode).astArguments().iterator();
- } else {
- return null;
- }
- int i = 0;
- while (i < mArgIndex && iterator.hasNext()) {
- iterator.next();
- i++;
- }
- if (iterator.hasNext()) {
- return iterator.next();
- }
-
- return null;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node == mTargetNode) {
- Expression arg = getTargetArgument();
- if (arg instanceof VariableReference) {
- VariableReference reference = (VariableReference) arg;
- String variable = reference.astIdentifier().astValue();
- mName = mMap.get(variable);
- mDone = true;
- return true;
- }
- }
-
- // Is this a getString() call? On a resource object? If so,
- // promote the resource argument up to the left hand side
- return super.visitMethodInvocation(node);
- }
-
- @Override
- public boolean visitConstructorInvocation(ConstructorInvocation node) {
- if (node == mTargetNode) {
- Expression arg = getTargetArgument();
- if (arg instanceof VariableReference) {
- VariableReference reference = (VariableReference) arg;
- String variable = reference.astIdentifier().astValue();
- mName = mMap.get(variable);
- mDone = true;
- return true;
- }
- }
-
- // Is this a getString() call? On a resource object? If so,
- // promote the resource argument up to the left hand side
- return super.visitConstructorInvocation(node);
- }
-
- @Override
- public boolean visitVariableDefinitionEntry(VariableDefinitionEntry node) {
- String name = node.astName().astValue();
- Expression rhs = node.astInitializer();
- Class<?> type = getType(rhs);
- if (type != null) {
- mTypes.put(name, type);
- } else {
- // Make sure we're not visiting the String.format node itself. If you have
- // msg = String.format("%1$s", msg)
- // then we'd be wiping out the type of "msg" before visiting the
- // String.format call!
- if (rhs != mTargetNode) {
- mTypes.remove(name);
- }
- }
-
- return super.visitVariableDefinitionEntry(node);
- }
-
- private Class<?> getType(Expression expression) {
- if (expression instanceof VariableReference) {
- VariableReference reference = (VariableReference) expression;
- String variable = reference.astIdentifier().astValue();
- return mTypes.get(variable);
- } else if (expression instanceof MethodInvocation) {
- MethodInvocation method = (MethodInvocation) expression;
- String methodName = method.astName().astValue();
- if (methodName.equals(GET_STRING_METHOD)) {
- return String.class;
- }
- } else if (expression instanceof StringLiteral) {
- return String.class;
- } else if (expression instanceof IntegralLiteral) {
- return Integer.TYPE;
- } else if (expression instanceof FloatingPointLiteral) {
- return Float.TYPE;
- } else if (expression instanceof CharLiteral) {
- return Character.TYPE;
- } else if (expression instanceof NullLiteral) {
- return Object.class;
- }
-
- return null;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StyleCycleDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StyleCycleDetector.java
deleted file mode 100644
index 8ce103b..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StyleCycleDetector.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PARENT;
-import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
-import static com.android.SdkConstants.TAG_STYLE;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Checks for cycles in style definitions
- */
-public class StyleCycleDetector extends ResourceXmlDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "StyleCycle", //$NON-NLS-1$
- "Looks for cycles in style definitions",
- "There should be no cycles in style definitions as this can lead to runtime " +
- "exceptions.",
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- StyleCycleDetector.class,
- Scope.RESOURCE_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/topics/ui/themes.html#Inheritance"); //$NON-NLS-1$
-
- /** Constructs a new {@link StyleCycleDetector} */
- public StyleCycleDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singleton(TAG_STYLE);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- Attr parentNode = element.getAttributeNode(ATTR_PARENT);
- if (parentNode != null) {
- String parent = parentNode.getValue();
- String name = element.getAttribute(ATTR_NAME);
- if (parent.endsWith(name) &&
- parent.equals(STYLE_RESOURCE_PREFIX + name)) {
- context.report(ISSUE, parentNode, context.getLocation(parentNode),
- String.format("Style %1$s should not extend itself", name), null);
- } else if (parent.startsWith(STYLE_RESOURCE_PREFIX)
- && parent.startsWith(name, STYLE_RESOURCE_PREFIX.length())
- && parent.startsWith(".", STYLE_RESOURCE_PREFIX.length() + name.length())) {
- context.report(ISSUE, parentNode, context.getLocation(parentNode),
- String.format("Potential cycle: %1$s is the implied parent of %2$s and " +
- "this defines the opposite", name,
- parent.substring(STYLE_RESOURCE_PREFIX.length())), null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java
deleted file mode 100644
index 21fb6c0..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/SystemPermissionsDetector.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.TAG_USES_PERMISSION;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-
-/**
- * Checks if an application wants to use permissions that can only be used by
- * system applications.
- */
-public class SystemPermissionsDetector extends Detector implements Detector.XmlScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ProtectedPermissions", //$NON-NLS-1$
- "Looks for permissions that are only granted to system apps",
-
- "Permissions with the protection level signature or signatureOrSystem are only " +
- "granted to system apps. If an app is a regular non-system app, it will never be " +
- "able to use these permissions.",
-
- Category.CORRECTNESS,
- 5,
- Severity.ERROR,
- SystemPermissionsDetector.class,
- EnumSet.of(Scope.MANIFEST));
-
- // List of permissions have the protection levels signature or systemOrSignature.
- // This list must be sorted alphabetically.
- private static final String[] SYSTEM_PERMISSIONS = new String[] {
- "android.intent.category.MASTER_CLEAR.permission.C2D_MESSAGE",
- "android.permission.ACCESS_CACHE_FILESYSTEM",
- "android.permission.ACCESS_CHECKIN_PROPERTIES",
- "android.permission.ACCESS_MTP",
- "android.permission.ACCESS_SURFACE_FLINGER",
- "android.permission.ACCOUNT_MANAGER",
- "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK",
- "android.permission.ASEC_ACCESS",
- "android.permission.ASEC_CREATE",
- "android.permission.ASEC_DESTROY",
- "android.permission.ASEC_MOUNT_UNMOUNT",
- "android.permission.ASEC_RENAME",
- "android.permission.BACKUP",
- "android.permission.BIND_APPWIDGET",
- "android.permission.BIND_DEVICE_ADMIN",
- "android.permission.BIND_INPUT_METHOD",
- "android.permission.BIND_PACKAGE_VERIFIER",
- "android.permission.BIND_REMOTEVIEWS",
- "android.permission.BIND_TEXT_SERVICE",
- "android.permission.BIND_VPN_SERVICE",
- "android.permission.BIND_WALLPAPER",
- "android.permission.BRICK",
- "android.permission.BROADCAST_PACKAGE_REMOVED",
- "android.permission.BROADCAST_SMS",
- "android.permission.BROADCAST_WAP_PUSH",
- "android.permission.CALL_PRIVILEGED",
- "android.permission.CHANGE_BACKGROUND_DATA_SETTING",
- "android.permission.CHANGE_COMPONENT_ENABLED_STATE",
- "android.permission.CLEAR_APP_USER_DATA",
- "android.permission.CONFIRM_FULL_BACKUP",
- "android.permission.CONNECTIVITY_INTERNAL",
- "android.permission.CONTROL_LOCATION_UPDATES",
- "android.permission.COPY_PROTECTED_DATA",
- "android.permission.CRYPT_KEEPER",
- "android.permission.DELETE_CACHE_FILES",
- "android.permission.DELETE_PACKAGES",
- "android.permission.DEVICE_POWER",
- "android.permission.DIAGNOSTIC",
- "android.permission.DUMP",
- "android.permission.FACTORY_TEST",
- "android.permission.FORCE_BACK",
- "android.permission.FORCE_STOP_PACKAGES",
- "android.permission.GLOBAL_SEARCH",
- "android.permission.GLOBAL_SEARCH_CONTROL",
- "android.permission.HARDWARE_TEST",
- "android.permission.INJECT_EVENTS",
- "android.permission.INSTALL_LOCATION_PROVIDER",
- "android.permission.INSTALL_PACKAGES",
- "android.permission.INTERNAL_SYSTEM_WINDOW",
- "android.permission.MANAGE_APP_TOKENS",
- "android.permission.MANAGE_NETWORK_POLICY",
- "android.permission.MANAGE_USB",
- "android.permission.MASTER_CLEAR",
- "android.permission.MODIFY_NETWORK_ACCOUNTING",
- "android.permission.MODIFY_PHONE_STATE",
- "android.permission.MOVE_PACKAGE",
- "android.permission.NET_ADMIN",
- "android.permission.MODIFY_PHONE_STATE",
- "android.permission.PACKAGE_USAGE_STATS",
- "android.permission.PACKAGE_VERIFICATION_AGENT",
- "android.permission.PERFORM_CDMA_PROVISIONING",
- "android.permission.READ_FRAME_BUFFER",
- "android.permission.READ_INPUT_STATE",
- "android.permission.READ_NETWORK_USAGE_HISTORY",
- "android.permission.READ_PRIVILEGED_PHONE_STATE",
- "android.permission.REBOOT",
- "android.permission.RECEIVE_EMERGENCY_BROADCAST",
- "android.permission.REMOVE_TASKS",
- "android.permission.RETRIEVE_WINDOW_CONTENT",
- "android.permission.SEND_SMS_NO_CONFIRMATION",
- "android.permission.SET_ACTIVITY_WATCHER",
- "android.permission.SET_ORIENTATION",
- "android.permission.SET_POINTER_SPEED",
- "android.permission.SET_PREFERRED_APPLICATIONS",
- "android.permission.SET_SCREEN_COMPATIBILITY",
- "android.permission.SET_TIME",
- "android.permission.SET_WALLPAPER_COMPONENT",
- "android.permission.SHUTDOWN",
- "android.permission.STATUS_BAR",
- "android.permission.STATUS_BAR_SERVICE",
- "android.permission.STOP_APP_SWITCHES",
- "android.permission.UPDATE_DEVICE_STATS",
- "android.permission.WRITE_APN_SETTINGS",
- "android.permission.WRITE_GSERVICES",
- "android.permission.WRITE_MEDIA_STORAGE",
- "android.permission.WRITE_SECURE_SETTINGS"
- };
-
- /** Constructs a new {@link SystemPermissionsDetector} check */
- public SystemPermissionsDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return file.getName().equals(ANDROID_MANIFEST_XML);
- }
-
- // ---- Implements Detector.XmlScanner ----
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(TAG_USES_PERMISSION);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- Attr nameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
- if (nameNode != null) {
- String permissionName = nameNode.getValue();
- if (Arrays.binarySearch(SYSTEM_PERMISSIONS, permissionName) >= 0) {
- context.report(ISSUE, element, context.getLocation(nameNode),
- "Permission is only granted to system apps", null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
deleted file mode 100644
index a059f5c..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextFieldDetector.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_HINT;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_INPUT_METHOD;
-import static com.android.SdkConstants.ATTR_INPUT_TYPE;
-import static com.android.SdkConstants.ATTR_PASSWORD;
-import static com.android.SdkConstants.ATTR_PHONE_NUMBER;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.EDIT_TEXT;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Checks for usability problems in text fields: omitting inputType, or omitting a hint.
- */
-public class TextFieldDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "TextFields", //$NON-NLS-1$
- "Looks for text fields missing inputType or hint settings",
-
- "Providing an `inputType` attribute on a text field improves usability " +
- "because depending on the data to be input, optimized keyboards can be shown " +
- "to the user (such as just digits and parentheses for a phone number). Similarly," +
- "a hint attribute displays a hint to the user for what is expected in the " +
- "text field.\n" +
- "\n" +
- "The lint detector also looks at the `id` of the view, and if the id offers a " +
- "hint of the purpose of the field (for example, the `id` contains the phrase " +
- "`phone` or `email`), then lint will also ensure that the `inputType` contains " +
- "the corresponding type attributes.\n" +
- "\n" +
- "If you really want to keep the text field generic, you can suppress this warning " +
- "by setting `inputType=\"text\"`.",
-
- Category.USABILITY,
- 5,
- Severity.WARNING,
- TextFieldDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link TextFieldDetector} */
- public TextFieldDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(EDIT_TEXT);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String style = element.getAttribute(ATTR_STYLE);
- if (style != null && !style.isEmpty()) {
- // The input type might be specified via a style. This will require
- // us to track these (similar to what is done for the
- // RequiredAttributeDetector to track layout_width and layout_height
- // in style declarations). For now, simply ignore these elements
- // to avoid producing false positives.
- return;
- }
-
- Attr inputTypeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_INPUT_TYPE);
- if (inputTypeNode == null &&
- !element.hasAttributeNS(ANDROID_URI, ATTR_HINT)) {
- // Also make sure the EditText does not set an inputMethod in which case
- // an inputType might be provided from the input.
- if (element.hasAttributeNS(ANDROID_URI, ATTR_INPUT_METHOD)) {
- return;
- }
-
- context.report(ISSUE, element, context.getLocation(element),
- "This text field does not specify an inputType or a hint", null);
- }
-
- Attr idNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_ID);
- if (idNode == null) {
- return;
- }
- String id = idNode.getValue();
- if (id.isEmpty()) {
- return;
- }
- if (id.startsWith("editText")) { //$NON-NLS-1$
- // Just the default label
- return;
- }
-
- String inputType = "";
- if (inputTypeNode != null) {
- inputType = inputTypeNode.getValue();
- }
-
- // TODO: See if the name is just the default names (button1, editText1 etc)
- // and if so, do nothing
- // TODO: Unit test this
-
- if (containsWord(id, "phone", true, true)) { //$NON-NLS-1$
- if (!inputType.contains("phone") //$NON-NLS-1$
- && element.getAttributeNodeNS(ANDROID_URI, ATTR_PHONE_NUMBER) == null) {
- String message = String.format("The view name (%1$s) suggests this is a phone "
- + "number, but it does not include 'phone' in the inputType", id);
- reportMismatch(context, idNode, inputTypeNode, message);
- }
- return;
- }
-
- if (containsWord(id, "width", false, true)
- || containsWord(id, "height", false, true)
- || containsWord(id, "size", false, true)
- || containsWord(id, "length", false, true)
- || containsWord(id, "weight", false, true)
- || containsWord(id, "number", false, true)) {
- if (!inputType.contains("number") && !inputType.contains("phone")) { //$NON-NLS-1$
- String message = String.format("The view name (%1$s) suggests this is a number, "
- + "but it does not include a numeric inputType (such as 'numberSigned')",
- id);
- reportMismatch(context, idNode, inputTypeNode, message);
- }
- return;
- }
-
- if (containsWord(id, "password", true, true)) { //$NON-NLS-1$
- if (!(inputType.contains("Password")) //$NON-NLS-1$
- && element.getAttributeNodeNS(ANDROID_URI, ATTR_PASSWORD) == null) {
- String message = String.format("The view name (%1$s) suggests this is a password, "
- + "but it does not include 'textPassword' in the inputType", id);
- reportMismatch(context, idNode, inputTypeNode, message);
- }
- return;
- }
-
- if (containsWord(id, "email", true, true)) { //$NON-NLS-1$
- if (!inputType.contains("Email")) { //$NON-NLS-1$
- String message = String.format("The view name (%1$s) suggests this is an e-mail "
- + "address, but it does not include 'textEmail' in the inputType", id);
- reportMismatch(context, idNode, inputTypeNode, message);
- }
- return;
- }
-
- if (endsWith(id, "pin", false, true)) { //$NON-NLS-1$
- if (!(inputType.contains("numberPassword")) //$NON-NLS-1$
- && element.getAttributeNodeNS(ANDROID_URI, ATTR_PASSWORD) == null) {
- String message = String.format("The view name (%1$s) suggests this is a password, "
- + "but it does not include 'numberPassword' in the inputType", id);
- reportMismatch(context, idNode, inputTypeNode, message);
- }
- return;
- }
-
- if ((containsWord(id, "uri") || containsWord(id, "url"))
- && !inputType.contains("textUri")) {
- String message = String.format("The view name (%1$s) suggests this is a URI, "
- + "but it does not include 'textUri' in the inputType", id);
- reportMismatch(context, idNode, inputTypeNode, message);
- }
-
- if ((containsWord(id, "date")) //$NON-NLS-1$
- && !inputType.contains("date")) { //$NON-NLS-1$
- String message = String.format("The view name (%1$s) suggests this is a date, "
- + "but it does not include 'date' or 'datetime' in the inputType", id);
- reportMismatch(context, idNode, inputTypeNode, message);
- }
- }
-
- private static void reportMismatch(XmlContext context, Attr idNode, Attr inputTypeNode,
- String message) {
- Location location;
- if (inputTypeNode != null) {
- location = context.getLocation(inputTypeNode);
- Location secondary = context.getLocation(idNode);
- secondary.setMessage("id defined here");
- location.setSecondary(secondary);
- } else {
- location = context.getLocation(idNode);
- }
- context.report(ISSUE, idNode.getOwnerElement(), location, message, null);
- }
-
- /** Returns true if the given sentence contains a given word */
- @VisibleForTesting
- static boolean containsWord(String sentence, String word) {
- return containsWord(sentence, word, false, false);
- }
-
- /**
- * Returns true if the given sentence contains a given word
- * @param sentence the full sentence to search within
- * @param word the word to look for
- * @param allowPrefix if true, allow a prefix match even if the next character
- * is in the same word (same case or not an underscore)
- * @param allowSuffix if true, allow a suffix match even if the preceding character
- * is in the same word (same case or not an underscore)
- * @return true if the word is contained in the sentence
- */
- @VisibleForTesting
- static boolean containsWord(String sentence, String word, boolean allowPrefix,
- boolean allowSuffix) {
- return indexOfWord(sentence, word, allowPrefix, allowSuffix) != -1;
- }
-
- /** Returns true if the given sentence <b>ends</b> with a given word */
- private static boolean endsWith(String sentence, String word, boolean allowPrefix,
- boolean allowSuffix) {
- int index = indexOfWord(sentence, word, allowPrefix, allowSuffix);
-
- if (index != -1) {
- return index == sentence.length() - word.length();
- }
-
- return false;
- }
-
- /**
- * Returns the index of the given word in the given sentence, if any. It will match
- * across cases, and ignore words that seem to be just a substring in the middle
- * of another word.
- *
- * @param sentence the full sentence to search within
- * @param word the word to look for
- * @param allowPrefix if true, allow a prefix match even if the next character
- * is in the same word (same case or not an underscore)
- * @param allowSuffix if true, allow a suffix match even if the preceding character
- * is in the same word (same case or not an underscore)
- * @return true if the word is contained in the sentence
- */
- private static int indexOfWord(String sentence, String word, boolean allowPrefix,
- boolean allowSuffix) {
- if (sentence.isEmpty()) {
- return -1;
- }
- int wordLength = word.length();
- if (wordLength > sentence.length()) {
- return -1;
- }
-
- char firstUpper = Character.toUpperCase(word.charAt(0));
- char firstLower = Character.toLowerCase(firstUpper);
-
- int start = 0;
- if (sentence.startsWith(NEW_ID_PREFIX)) {
- start += NEW_ID_PREFIX.length();
- } else if (sentence.startsWith(ID_PREFIX)) {
- start += ID_PREFIX.length();
- }
-
- for (int i = start, n = sentence.length(), m = n - (wordLength - 1); i < m; i++) {
- char c = sentence.charAt(i);
- if (c == firstUpper || c == firstLower) {
- if (sentence.regionMatches(true, i, word, 0, wordLength)) {
- if (i <= start && allowPrefix) {
- return i;
- }
- if (i == m - 1 && allowSuffix) {
- return i;
- }
- if (i <= start || (sentence.charAt(i - 1) == '_')
- || Character.isUpperCase(c)) {
- if (i == m - 1) {
- return i;
- }
- char after = sentence.charAt(i + wordLength);
- if (after == '_' || Character.isUpperCase(after)) {
- return i;
- }
- }
- }
- }
- }
-
- return -1;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
deleted file mode 100644
index 3e519cf..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_AUTO_TEXT;
-import static com.android.SdkConstants.ATTR_BUFFER_TYPE;
-import static com.android.SdkConstants.ATTR_CAPITALIZE;
-import static com.android.SdkConstants.ATTR_CURSOR_VISIBLE;
-import static com.android.SdkConstants.ATTR_DIGITS;
-import static com.android.SdkConstants.ATTR_EDITABLE;
-import static com.android.SdkConstants.ATTR_EDITOR_EXTRAS;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_IME_ACTION_ID;
-import static com.android.SdkConstants.ATTR_IME_ACTION_LABEL;
-import static com.android.SdkConstants.ATTR_IME_OPTIONS;
-import static com.android.SdkConstants.ATTR_INPUT_METHOD;
-import static com.android.SdkConstants.ATTR_INPUT_TYPE;
-import static com.android.SdkConstants.ATTR_NUMERIC;
-import static com.android.SdkConstants.ATTR_PASSWORD;
-import static com.android.SdkConstants.ATTR_PHONE_NUMBER;
-import static com.android.SdkConstants.ATTR_PRIVATE_IME_OPTIONS;
-import static com.android.SdkConstants.ATTR_TEXT;
-import static com.android.SdkConstants.ATTR_TEXT_IS_SELECTABLE;
-import static com.android.SdkConstants.ATTR_VISIBILITY;
-import static com.android.SdkConstants.BUTTON;
-import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
-import static com.android.SdkConstants.CHECK_BOX;
-import static com.android.SdkConstants.RADIO_BUTTON;
-import static com.android.SdkConstants.SWITCH;
-import static com.android.SdkConstants.TEXT_VIEW;
-import static com.android.SdkConstants.TOGGLE_BUTTON;
-import static com.android.SdkConstants.VALUE_EDITABLE;
-import static com.android.SdkConstants.VALUE_NONE;
-import static com.android.SdkConstants.VALUE_TRUE;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Checks for cases where a TextView should probably be an EditText instead
- */
-public class TextViewDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "TextViewEdits", //$NON-NLS-1$
- "Looks for TextViews being used for input",
-
- "Using a `<TextView>` to input text is generally an error, you should be " +
- "using `<EditText>` instead. `EditText` is a subclass of `TextView`, and some " +
- "of the editing support is provided by `TextView`, so it's possible to set " +
- "some input-related properties on a `TextView`. However, using a `TextView` " +
- "along with input attributes is usually a cut & paste error. To input " +
- "text you should be using `<EditText>`." +
- "\n" +
- "This check also checks subclasses of `TextView`, such as `Button` and `CheckBox`, " +
- "since these have the same issue: they should not be used with editable " +
- "attributes.",
-
- Category.CORRECTNESS,
- 7,
- Severity.WARNING,
- TextViewDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Text could be selectable */
- public static final Issue SELECTABLE = Issue.create(
- "SelectableText", //$NON-NLS-1$
- "Looks for TextViews which should probably allow their text to be selected",
-
- "If a `<TextView>` is used to display data, the user might want to copy that " +
- "data and paste it elsewhere. To allow this, the `<TextView>` should specify " +
- "`android:textIsSelectable=\"true\"`.\n" +
- "\n" +
- "This lint check looks for TextViews which are likely to be displaying data: " +
- "views whose text is set dynamically. This value will be ignored on platforms " +
- "older than API 11, so it is okay to set it regardless of your `minSdkVersion`.",
-
- Category.USABILITY,
- 7,
- Severity.WARNING,
- TextViewDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link TextViewDetector} */
- public TextViewDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TEXT_VIEW,
- BUTTON,
- TOGGLE_BUTTON,
- CHECK_BOX,
- RADIO_BUTTON,
- CHECKED_TEXT_VIEW,
- SWITCH
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (element.getTagName().equals(TEXT_VIEW)) {
- if (!element.hasAttributeNS(ANDROID_URI, ATTR_TEXT)
- && element.hasAttributeNS(ANDROID_URI, ATTR_ID)
- && !element.hasAttributeNS(ANDROID_URI, ATTR_TEXT_IS_SELECTABLE)
- && !element.hasAttributeNS(ANDROID_URI, ATTR_VISIBILITY)
- && context.getMainProject().getTargetSdk() >= 11) {
- context.report(SELECTABLE, element, context.getLocation(element),
- "Consider making the text value selectable by specifying " +
- "android:textIsSelectable=\"true\"", null);
- }
- }
-
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attribute = (Attr) attributes.item(i);
- String name = attribute.getLocalName();
- if (name == null) {
- // Attribute not in a namespace; we only care about the android: ones
- continue;
- }
-
- boolean isEditAttribute = false;
- switch (name.charAt(0)) {
- case 'a': {
- isEditAttribute = name.equals(ATTR_AUTO_TEXT);
- break;
- }
- case 'b': {
- isEditAttribute = name.equals(ATTR_BUFFER_TYPE) &&
- attribute.getValue().equals(VALUE_EDITABLE);
- break;
- }
- case 'p': {
- isEditAttribute = name.equals(ATTR_PASSWORD)
- || name.equals(ATTR_PHONE_NUMBER)
- || name.equals(ATTR_PRIVATE_IME_OPTIONS);
- break;
- }
- case 'c': {
- isEditAttribute = name.equals(ATTR_CAPITALIZE)
- || name.equals(ATTR_CURSOR_VISIBLE);
- break;
- }
- case 'd': {
- isEditAttribute = name.equals(ATTR_DIGITS);
- break;
- }
- case 'e': {
- if (name.equals(ATTR_EDITABLE)) {
- isEditAttribute = attribute.getValue().equals(VALUE_TRUE);
- } else {
- isEditAttribute = name.equals(ATTR_EDITOR_EXTRAS);
- }
- break;
- }
- case 'i': {
- if (name.equals(ATTR_INPUT_TYPE)) {
- String value = attribute.getValue();
- isEditAttribute = !value.isEmpty() && !value.equals(VALUE_NONE);
- } else {
- isEditAttribute = name.equals(ATTR_INPUT_TYPE)
- || name.equals(ATTR_IME_OPTIONS)
- || name.equals(ATTR_IME_ACTION_LABEL)
- || name.equals(ATTR_IME_ACTION_ID)
- || name.equals(ATTR_INPUT_METHOD);
- }
- break;
- }
- case 'n': {
- isEditAttribute = name.equals(ATTR_NUMERIC);
- break;
- }
- }
-
- if (isEditAttribute && ANDROID_URI.equals(attribute.getNamespaceURI())) {
- Location location = context.getLocation(attribute);
- String message;
- String view = element.getTagName();
- if (view.equals(TEXT_VIEW)) {
- message = String.format(
- "Attribute %1$s should not be used with <TextView>: " +
- "Change element type to <EditText> ?", attribute.getName());
- } else {
- message = String.format(
- "Attribute %1$s should not be used with <%2$s>: " +
- "intended for editable text widgets",
- attribute.getName(), view);
- }
- context.report(ISSUE, attribute, location, message, null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
deleted file mode 100644
index 5fc5340..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TitleDetector.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_TITLE;
-import static com.android.SdkConstants.ATTR_VISIBLE;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.VALUE_FALSE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector.JavaScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * Check which makes sure menu items specify a title
- */
-public class TitleDetector extends ResourceXmlDetector implements JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "MenuTitle", //$NON-NLS-1$
- "Ensures that all menu items supply a title",
-
- "From the action bar documentation:\n" +
- // u2014: em dash
- "\"It's important that you always define android:title for each menu item \u2014 " +
- "even if you don't declare that the title appear with the action item \u2014 for " +
- "three reasons:\n" +
- "\n" +
- "* If there's not enough room in the action bar for the action item, the menu " +
- "item appears in the overflow menu and only the title appears.\n" +
- "* Screen readers for sight-impaired users read the menu item's title.\n" +
- "* If the action item appears with only the icon, a user can long-press the item " +
- "to reveal a tool-tip that displays the action item's title.\n" +
- "The android:icon is always optional, but recommended.",
-
- Category.USABILITY,
- 5,
- Severity.WARNING,
- TitleDetector.class,
- Scope.RESOURCE_FILE_SCOPE).setMoreInfo(
- "http://developer.android.com/guide/topics/ui/actionbar.html"); //$NON-NLS-1$
-
- /** Constructs a new {@link TitleDetector} */
- public TitleDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.MENU;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- @Nullable
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(TAG_ITEM);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (element.hasAttributeNS(ANDROID_URI, ATTR_TITLE)) {
- return;
- }
-
- // TODO: Find out if this is necessary on older versions too.
- // I swear I saw it mentioned.
- if (context.getMainProject().getTargetSdk() < 11) {
- return;
- }
-
- if (VALUE_FALSE.equals(element.getAttributeNS(ANDROID_URI, ATTR_VISIBLE))) {
- return;
- }
-
- String message = "Menu items should specify a title";
- context.report(ISSUE, element, context.getLocation(element), message, null);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java
deleted file mode 100644
index e245fe8..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-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 java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.IntegralLiteral;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.Return;
-import lombok.ast.StrictListAccessor;
-
-/** Detector looking for Toast.makeText() without a corresponding show() call */
-public class ToastDetector extends Detector implements Detector.JavaScanner {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ShowToast", //$NON-NLS-1$
- "Looks for code creating a Toast but forgetting to call show() on it",
-
- "`Toast.makeText()` creates a `Toast` but does *not* show it. You must call " +
- "`show()` on the resulting object to actually make the `Toast` appear.",
-
- Category.CORRECTNESS,
- 6,
- Severity.WARNING,
- ToastDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
-
- /** Constructs a new {@link ToastDetector} check */
- public ToastDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
-
- // ---- Implements JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("makeText"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- assert node.astName().astValue().equals("makeText");
- if (node.astOperand() == null) {
- // "makeText()" in the code with no operand
- return;
- }
-
- String operand = node.astOperand().toString();
- if (!(operand.equals("Toast") || operand.endsWith(".Toast"))) {
- return;
- }
-
- // Make sure you pass the right kind of duration: it's not a delay, it's
- // LENGTH_SHORT or LENGTH_LONG
- // (see http://code.google.com/p/android/issues/detail?id=3655)
- StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
- if (args.size() == 3) {
- Expression duration = args.last();
- if (duration instanceof IntegralLiteral) {
- context.report(ISSUE, duration, context.getLocation(duration),
- "Expected duration Toast.LENGTH_SHORT or Toast.LENGTH_LONG, a custom " +
- "duration value is not supported",
- null);
- }
- }
-
- Node method = JavaContext.findSurroundingMethod(node.getParent());
- if (method == null) {
- return;
- }
-
- ShowFinder finder = new ShowFinder(node);
- method.accept(finder);
- if (!finder.isShowCalled()) {
- context.report(ISSUE, method, context.getLocation(node),
- "Toast created but not shown: did you forget to call show() ?", null);
- }
- }
-
- private static class ShowFinder extends ForwardingAstVisitor {
- /** The target makeText call */
- private final MethodInvocation mTarget;
- /** Whether we've found the show method */
- private boolean mFound;
- /** Whether we've seen the target makeText node yet */
- private boolean mSeenTarget;
-
- private ShowFinder(MethodInvocation target) {
- mTarget = target;
- }
-
- @Override
- public boolean visitMethodInvocation(MethodInvocation node) {
- if (node == mTarget) {
- mSeenTarget = true;
- } else if ((mSeenTarget || node.astOperand() == mTarget)
- && "show".equals(node.astName().astValue())) { //$NON-NLS-1$
- // TODO: Do more flow analysis to see whether we're really calling show
- // on the right type of object?
- mFound = true;
- }
-
- return true;
- }
-
- @Override
- public boolean visitReturn(Return node) {
- if (node.astValue() == mTarget) {
- // If you just do "return Toast.makeText(...) don't warn
- mFound = true;
- }
- return super.visitReturn(node);
- }
-
- boolean isShowCalled() {
- return mFound;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java
deleted file mode 100644
index 94a9611..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TooManyViewsDetector.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-
-/**
- * Checks whether a root FrameLayout can be replaced with a {@code <merge>} tag.
- */
-public class TooManyViewsDetector extends LayoutDetector {
- /** Issue of having too many views in a single layout */
- public static final Issue TOO_MANY = Issue.create(
- "TooManyViews", //$NON-NLS-1$
- "Checks whether a layout has too many views",
- "Using too many views in a single layout is bad for " +
- "performance. Consider using compound drawables or other tricks for " +
- "reducing the number of views in this layout.\n\n" +
- "The maximum view count defaults to 80 but can be configured with the " +
- "environment variable `ANDROID_LINT_MAX_VIEW_COUNT`.",
- Category.PERFORMANCE,
- 1,
- Severity.WARNING,
- TooManyViewsDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Issue of having too deep hierarchies in layouts */
- public static final Issue TOO_DEEP = Issue.create(
- "TooDeepLayout", //$NON-NLS-1$
- "Checks whether a layout hierarchy is too deep",
- "Layouts with too much nesting is bad for performance. " +
- "Consider using a flatter layout (such as `RelativeLayout` or `GridLayout`)." +
- "The default maximum depth is 10 but can be configured with the environment " +
- "variable `ANDROID_LINT_MAX_DEPTH`.",
- Category.PERFORMANCE,
- 1,
- Severity.WARNING,
- TooManyViewsDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- private static final int MAX_VIEW_COUNT;
- private static final int MAX_DEPTH;
- static {
- int maxViewCount = 0;
- int maxDepth = 0;
-
- String countValue = System.getenv("ANDROID_LINT_MAX_VIEW_COUNT"); //$NON-NLS-1$
- if (countValue != null) {
- try {
- maxViewCount = Integer.parseInt(countValue);
- } catch (NumberFormatException e) {
- // pass: set to default below
- }
- }
- String depthValue = System.getenv("ANDROID_LINT_MAX_DEPTH"); //$NON-NLS-1$
- if (depthValue != null) {
- try {
- maxDepth = Integer.parseInt(depthValue);
- } catch (NumberFormatException e) {
- // pass: set to default below
- }
- }
- if (maxViewCount == 0) {
- maxViewCount = 80;
- }
- if (maxDepth == 0) {
- maxDepth = 10;
- }
-
- MAX_VIEW_COUNT = maxViewCount;
- MAX_DEPTH = maxDepth;
- }
-
- private int mViewCount;
- private int mDepth;
- private boolean mWarnedAboutDepth;
-
- /** Constructs a new {@link TooManyViewsDetector} */
- public TooManyViewsDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- mViewCount = mDepth = 0;
- mWarnedAboutDepth = false;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return ALL;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- mViewCount++;
- mDepth++;
-
- if (mDepth == MAX_DEPTH && !mWarnedAboutDepth) {
- // Have to record whether or not we've warned since we could have many siblings
- // at the max level and we'd warn for each one. No need to do the same thing
- // for the view count error since we'll only have view count exactly equal the
- // max just once.
- mWarnedAboutDepth = true;
- String msg = String.format("%1$s has more than %2$d levels, bad for performance",
- context.file.getName(), MAX_DEPTH);
- context.report(TOO_DEEP, element, context.getLocation(element), msg, null);
- }
- if (mViewCount == MAX_VIEW_COUNT) {
- String msg = String.format("%1$s has more than %2$d views, bad for performance",
- context.file.getName(), MAX_VIEW_COUNT);
- context.report(TOO_MANY, element, context.getLocation(element), msg, null);
- }
- }
-
- @Override
- public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) {
- mDepth--;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
deleted file mode 100644
index 0f892b1..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_PREFIX;
-import static com.android.SdkConstants.ATTR_LOCALE;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TRANSLATABLE;
-import static com.android.SdkConstants.STRING_PREFIX;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-import static com.android.SdkConstants.TOOLS_URI;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-import com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-/**
- * Checks for incomplete translations - e.g. keys that are only present in some
- * locales but not all.
- */
-public class TranslationDetector extends ResourceXmlDetector {
- @VisibleForTesting
- static boolean sCompleteRegions =
- System.getenv("ANDROID_LINT_COMPLETE_REGIONS") != null; //$NON-NLS-1$
-
- private static final Pattern LANGUAGE_PATTERN = Pattern.compile("^[a-z]{2}$"); //$NON-NLS-1$
- private static final Pattern REGION_PATTERN = Pattern.compile("^r([A-Z]{2})$"); //$NON-NLS-1$
-
- /** Are all translations complete? */
- public static final Issue MISSING = Issue.create(
- "MissingTranslation", //$NON-NLS-1$
- "Checks for incomplete translations where not all strings are translated",
- "If an application has more than one locale, then all the strings declared in " +
- "one language should also be translated in all other languages.\n" +
- "\n" +
- "If the string should *not* be translated, you can add the attribute " +
- "`translatable=\"false\"` on the `<string>` element, or you can define all " +
- "your non-translatable strings in a resource file called `donottranslate.xml`. " +
- "Or, you can ignore the issue with a `tools:ignore=\"MissingTranslation\"` " +
- "attribute.\n" +
- "\n" +
- "By default this detector allows regions of a language to just provide a " +
- "subset of the strings and fall back to the standard language strings. " +
- "You can require all regions to provide a full translation by setting the " +
- "environment variable `ANDROID_LINT_COMPLETE_REGIONS`.\n" +
- "\n" +
- "You can tell lint (and other tools) which language is the default language " +
- "in your `res/values/` folder by specifying `tools:locale=\"languageCode\"` for " +
- "the root `<resources>` element in your resource file. (The `tools` prefix refers " +
- "to the namespace declaration `http://schemas.android.com/tools`.)",
- Category.MESSAGES,
- 8,
- Severity.FATAL,
- TranslationDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Are there extra translations that are "unused" (appear only in specific languages) ? */
- public static final Issue EXTRA = Issue.create(
- "ExtraTranslation", //$NON-NLS-1$
- "Checks for translations that appear to be unused (no default language string)",
- "If a string appears in a specific language translation file, but there is " +
- "no corresponding string in the default locale, then this string is probably " +
- "unused. (It's technically possible that your application is only intended to " +
- "run in a specific locale, but it's still a good idea to provide a fallback.).\n" +
- "\n" +
- "Note that these strings can lead to crashes if the string is looked up on any " +
- "locale not providing a translation, so it's important to clean them up.",
- Category.MESSAGES,
- 6,
- Severity.FATAL,
- TranslationDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- private Set<String> mNames;
- private Set<String> mTranslatedArrays;
- private Set<String> mNonTranslatable;
- private boolean mIgnoreFile;
- private Map<File, Set<String>> mFileToNames;
- private Map<File, String> mFileToLocale;
-
- /** Locations for each untranslated string name. Populated during phase 2, if necessary */
- private Map<String, Location> mMissingLocations;
-
- /** Locations for each extra translated string name. Populated during phase 2, if necessary */
- private Map<String, Location> mExtraLocations;
-
- /** Error messages for each untranslated string name. Populated during phase 2, if necessary */
- private Map<String, String> mDescriptions;
-
- /** Constructs a new {@link TranslationDetector} */
- public TranslationDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STRING,
- TAG_STRING_ARRAY
- );
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- if (context.getDriver().getPhase() == 1) {
- mFileToNames = new HashMap<File, Set<String>>();
- }
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mNames = new HashSet<String>();
- }
-
- // Convention seen in various projects
- mIgnoreFile = context.file.getName().startsWith("donottranslate") //$NON-NLS-1$
- || UnusedResourceDetector.isAnalyticsFile(context);
-
- if (!context.getProject().getReportIssues()) {
- mIgnoreFile = true;
- }
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- if (context.getPhase() == 1) {
- // Store this layout's set of ids for full project analysis in afterCheckProject
- if (context.getProject().getReportIssues() && mNames != null) {
- mFileToNames.put(context.file, mNames);
-
- Element root = ((XmlContext) context).document.getDocumentElement();
- if (root != null) {
- String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
- if (locale != null && !locale.isEmpty()) {
- if (mFileToLocale == null) {
- mFileToLocale = Maps.newHashMap();
- }
- mFileToLocale.put(context.file, locale);
- }
- }
- }
-
- mNames = null;
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- // NOTE - this will look for the presence of translation strings.
- // If you create a resource folder but don't actually place a file in it
- // we won't detect that, but it seems like a smaller problem.
-
- checkTranslations(context);
-
- mFileToNames = null;
-
- if (mMissingLocations != null || mExtraLocations != null) {
- context.getDriver().requestRepeat(this, Scope.ALL_RESOURCES_SCOPE);
- }
- } else {
- assert context.getPhase() == 2;
-
- reportMap(context, MISSING, mMissingLocations);
- reportMap(context, EXTRA, mExtraLocations);
- mMissingLocations = null;
- mExtraLocations = null;
- mDescriptions = null;
- }
- }
-
- private void reportMap(Context context, Issue issue, Map<String, Location> map) {
- if (map != null) {
- for (Map.Entry<String, Location> entry : map.entrySet()) {
- Location location = entry.getValue();
- String name = entry.getKey();
- String message = mDescriptions.get(name);
-
- // We were prepending locations, but we want to prefer the base folders
- location = Location.reverse(location);
-
- context.report(issue, location, message, null);
- }
- }
- }
-
- private void checkTranslations(Context context) {
- // Only one file defining strings? If so, no problems.
- Set<File> files = mFileToNames.keySet();
- if (files.size() == 1) {
- return;
- }
-
- Set<File> parentFolders = new HashSet<File>();
- for (File file : files) {
- parentFolders.add(file.getParentFile());
- }
- if (parentFolders.size() == 1) {
- // Only one language - no problems.
- return;
- }
-
- boolean reportMissing = context.isEnabled(MISSING);
- boolean reportExtra = context.isEnabled(EXTRA);
-
- // res/strings.xml etc
- String defaultLanguage = "Default";
-
- Map<File, String> parentFolderToLanguage = new HashMap<File, String>();
- for (File parent : parentFolders) {
- String name = parent.getName();
-
- // Look up the language for this folder.
- String language = getLanguage(name);
- if (language == null) {
- language = defaultLanguage;
- }
-
- parentFolderToLanguage.put(parent, language);
- }
-
- int languageCount = parentFolderToLanguage.values().size();
- if (languageCount <= 1) {
- // At most one language -- no problems.
- return;
- }
-
- // Merge together the various files building up the translations for each language
- Map<String, Set<String>> languageToStrings =
- new HashMap<String, Set<String>>(languageCount);
- Set<String> allStrings = new HashSet<String>(200);
- for (File file : files) {
- String language = null;
- if (mFileToLocale != null) {
- String locale = mFileToLocale.get(file);
- if (locale != null) {
- int index = locale.indexOf('-');
- if (index != -1) {
- locale = locale.substring(0, index);
- }
- language = locale;
- }
- }
- if (language == null) {
- language = parentFolderToLanguage.get(file.getParentFile());
- }
- assert language != null : file.getParent();
- Set<String> fileStrings = mFileToNames.get(file);
-
- Set<String> languageStrings = languageToStrings.get(language);
- if (languageStrings == null) {
- // We don't need a copy; we're done with the string tables now so we
- // can modify them
- languageToStrings.put(language, fileStrings);
- } else {
- languageStrings.addAll(fileStrings);
- }
- allStrings.addAll(fileStrings);
- }
-
- Set<String> defaultStrings = languageToStrings.get(defaultLanguage);
- if (defaultStrings == null) {
- defaultStrings = new HashSet<String>();
- }
-
- // Fast check to see if there's no problem: if the default locale set is the
- // same as the all set (meaning there are no extra strings in the other languages)
- // then we can quickly determine if everything is okay by just making sure that
- // each language defines everything. If that's the case they will all have the same
- // string count.
- int stringCount = allStrings.size();
- if (stringCount == defaultStrings.size()) {
- boolean haveError = false;
- for (Map.Entry<String, Set<String>> entry : languageToStrings.entrySet()) {
- Set<String> strings = entry.getValue();
- if (stringCount != strings.size()) {
- haveError = true;
- break;
- }
- }
- if (!haveError) {
- return;
- }
- }
-
- // Do we need to resolve fallback strings for regions that only define a subset
- // of the strings in the language and fall back on the main language for the rest?
- if (!sCompleteRegions) {
- for (String l : languageToStrings.keySet()) {
- if (l.indexOf('-') != -1) {
- // Yes, we have regions. Merge all base language string names into each region.
- for (Map.Entry<String, Set<String>> entry : languageToStrings.entrySet()) {
- Set<String> strings = entry.getValue();
- if (stringCount != strings.size()) {
- String languageRegion = entry.getKey();
- int regionIndex = languageRegion.indexOf('-');
- if (regionIndex != -1) {
- String language = languageRegion.substring(0, regionIndex);
- Set<String> fallback = languageToStrings.get(language);
- if (fallback != null) {
- strings.addAll(fallback);
- }
- }
- }
- }
- // We only need to do this once; when we see the first region we know
- // we need to do it; once merged we can bail
- break;
- }
- }
- }
-
- List<String> languages = new ArrayList<String>(languageToStrings.keySet());
- Collections.sort(languages);
- for (String language : languages) {
- Set<String> strings = languageToStrings.get(language);
- if (defaultLanguage.equals(language)) {
- continue;
- }
-
- // if strings.size() == stringCount, then this language is defining everything,
- // both all the default language strings and the union of all extra strings
- // defined in other languages, so there's no problem.
- if (stringCount != strings.size()) {
- if (reportMissing) {
- Set<String> difference = Sets.difference(defaultStrings, strings);
- if (!difference.isEmpty()) {
- if (mMissingLocations == null) {
- mMissingLocations = new HashMap<String, Location>();
- }
- if (mDescriptions == null) {
- mDescriptions = new HashMap<String, String>();
- }
-
- for (String s : difference) {
- mMissingLocations.put(s, null);
- String message = mDescriptions.get(s);
- if (message == null) {
- message = String.format("\"%1$s\" is not translated in %2$s",
- s, language);
- } else {
- message = message + ", " + language;
- }
- mDescriptions.put(s, message);
- }
- }
- }
-
- if (reportExtra) {
- Set<String> difference = Sets.difference(strings, defaultStrings);
- if (!difference.isEmpty()) {
- if (mExtraLocations == null) {
- mExtraLocations = new HashMap<String, Location>();
- }
- if (mDescriptions == null) {
- mDescriptions = new HashMap<String, String>();
- }
-
- for (String s : difference) {
- if (mTranslatedArrays != null && mTranslatedArrays.contains(s)) {
- continue;
- }
- mExtraLocations.put(s, null);
- String message = String.format(
- "\"%1$s\" is translated here but not found in default locale", s);
- mDescriptions.put(s, message);
- }
- }
- }
- }
- }
- }
-
- /** Look up the language for the given folder name */
- private static String getLanguage(String name) {
- String[] segments = name.split("-"); //$NON-NLS-1$
-
- // TODO: To get an accurate answer, this should later do a
- // FolderConfiguration.getConfig(String[] folderSegments)
- // to obtain a FolderConfiguration, then call
- // getLanguageQualifier() on it, and if not null, call getValue() to get the
- // actual language value.
- // However, we don't have sdk_common on the build path for lint, so for now
- // use a simple guess about what constitutes a language qualifier here:
-
- String language = null;
- for (String segment : segments) {
- // Language
- if (language == null && segment.length() == 2
- && LANGUAGE_PATTERN.matcher(segment).matches()) {
- language = segment;
- }
-
- // Add in region
- if (language != null && segment.length() == 3
- && REGION_PATTERN.matcher(segment).matches()) {
- language = language + '-' + segment;
- break;
- }
- }
-
- return language;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (mIgnoreFile) {
- return;
- }
-
- Attr attribute = element.getAttributeNode(ATTR_NAME);
-
- if (context.getPhase() == 2) {
- // Just locating names requested in the {@link #mLocations} map
- if (attribute == null) {
- return;
- }
- String name = attribute.getValue();
- if (mMissingLocations != null && mMissingLocations.containsKey(name)) {
- String language = getLanguage(context.file.getParentFile().getName());
- if (language == null) {
- if (context.getDriver().isSuppressed(MISSING, element)) {
- mMissingLocations.remove(name);
- return;
- }
-
- Location location = context.getLocation(attribute);
- location.setClientData(element);
- location.setSecondary(mMissingLocations.get(name));
- mMissingLocations.put(name, location);
- }
- }
- if (mExtraLocations != null && mExtraLocations.containsKey(name)) {
- if (context.getDriver().isSuppressed(EXTRA, element)) {
- mExtraLocations.remove(name);
- return;
- }
- Location location = context.getLocation(attribute);
- location.setClientData(element);
- location.setMessage("Also translated here");
- location.setSecondary(mExtraLocations.get(name));
- mExtraLocations.put(name, location);
- }
- return;
- }
-
- assert context.getPhase() == 1;
- if (attribute == null || attribute.getValue().isEmpty()) {
- context.report(MISSING, element, context.getLocation(element),
- "Missing name attribute in <string> declaration", null);
- } else {
- String name = attribute.getValue();
-
- Attr translatable = element.getAttributeNode(ATTR_TRANSLATABLE);
- if (translatable != null && !Boolean.valueOf(translatable.getValue())) {
- String l = LintUtils.getLocaleAndRegion(context.file.getParentFile().getName());
- if (l != null) {
- context.report(EXTRA, translatable, context.getLocation(translatable),
- "Non-translatable resources should only be defined in the base " +
- "values/ folder", null);
- } else {
- if (mNonTranslatable == null) {
- mNonTranslatable = new HashSet<String>();
- }
- mNonTranslatable.add(name);
- }
- return;
- }
-
- if (element.getTagName().equals(TAG_STRING_ARRAY) &&
- allItemsAreReferences(element)) {
- // No need to provide translations for string arrays where all
- // the children items are defined as translated string resources,
- // e.g.
- // <string-array name="foo">
- // <item>@string/item1</item>
- // <item>@string/item2</item>
- // </string-array>
- // However, we need to remember these names such that we don't consider
- // these arrays "extra" if one of the *translated* versions of the array
- // perform an inline translation of an array item
- if (mTranslatedArrays == null) {
- mTranslatedArrays = new HashSet<String>();
- }
- mTranslatedArrays.add(name);
- return;
- }
-
- // Check for duplicate name definitions? No, because there can be
- // additional customizations like product=
- //if (mNames.contains(name)) {
- // context.mClient.report(ISSUE, context.getLocation(attribute),
- // String.format("Duplicate name %1$s, already defined earlier in this file",
- // name));
- //}
-
- mNames.add(name);
-
- if (mNonTranslatable != null && mNonTranslatable.contains(name)) {
- String message = String.format("The resource string \"%1$s\" has been marked as " +
- "translatable=\"false\"", name);
- context.report(EXTRA, attribute, context.getLocation(attribute), message, null);
- }
-
- // TBD: Also make sure that the strings are not empty or placeholders?
- }
- }
-
- private static boolean allItemsAreReferences(Element element) {
- assert element.getTagName().equals(TAG_STRING_ARRAY);
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node item = childNodes.item(i);
- if (item.getNodeType() == Node.ELEMENT_NODE &&
- TAG_ITEM.equals(item.getNodeName())) {
- NodeList itemChildren = item.getChildNodes();
- for (int j = 0, m = itemChildren.getLength(); j < m; j++) {
- Node valueNode = itemChildren.item(j);
- if (valueNode.getNodeType() == Node.TEXT_NODE) {
- String value = valueNode.getNodeValue().trim();
- if (!value.startsWith(ANDROID_PREFIX)
- && !value.startsWith(STRING_PREFIX)) {
- return false;
- }
- }
- }
- }
- }
-
- return true;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
deleted file mode 100644
index b9b889f..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_LOCALE;
-import static com.android.SdkConstants.FD_RES_VALUES;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.TOOLS_URI;
-import static com.android.tools.lint.checks.TypoLookup.isLetter;
-import static com.google.common.base.Objects.equal;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.base.Charsets;
-import com.google.common.base.Splitter;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Check which looks for likely typos in Strings.
- * <p>
- * TODO:
- * <ul>
- * <li> Add check of Java String literals too!
- * <li> Add support for <b>additional</b> languages. The typo detector is now
- * multilingual and looks for typos-*locale*.txt files to use. However,
- * we need to seed it with additional typo databases. I did some searching
- * and came up with some alternatives. Here's the strategy I used:
- * Used Google Translate to translate "Wikipedia Common Misspellings", and
- * then I went to google.no, google.fr etc searching with that translation, and
- * came up with what looks like wikipedia language local lists of typos.
- * This is how I found the Norwegian one for example:
- * <br>
- * http://no.wikipedia.org/wiki/Wikipedia:Liste_over_alminnelige_stavefeil/Maskinform
- * <br>
- * Here are some additional possibilities not yet processed:
- * <ul>
- * <li> French: http://fr.wikipedia.org/wiki/Wikip%C3%A9dia:Liste_de_fautes_d'orthographe_courantes
- * (couldn't find a machine-readable version there?)
- * <li> Swedish:
- * http://sv.wikipedia.org/wiki/Wikipedia:Lista_%C3%B6ver_vanliga_spr%C3%A5kfel
- * (couldn't find a machine-readable version there?)
- * <li> German
- * http://de.wikipedia.org/wiki/Wikipedia:Liste_von_Tippfehlern/F%C3%BCr_Maschinen
- * </ul>
- * <li> Consider also digesting files like
- * http://sv.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/Typos
- * See http://en.wikipedia.org/wiki/Wikipedia:AutoWikiBrowser/User_manual.
- * </ul>
- */
-public class TypoDetector extends ResourceXmlDetector {
- @Nullable private TypoLookup mLookup;
- @Nullable private String mLastLanguage;
- @Nullable private String mLastRegion;
- @Nullable private String mLanguage;
- @Nullable private String mRegion;
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "Typos", //$NON-NLS-1$
- "Looks for typos in messages",
-
- "This check looks through the string definitions, and if it finds any words " +
- "that look like likely misspellings, they are flagged.",
- Category.MESSAGES,
- 7,
- Severity.WARNING,
- TypoDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new detector */
- public TypoDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- /** Look up the locale and region from the given parent folder name and store it
- * in {@link #mLanguage} and {@link #mRegion} */
- private void initLocale(@NonNull String parent) {
- mLanguage = null;
- mRegion = null;
-
- if (parent.equals(FD_RES_VALUES)) {
- return;
- }
-
- for (String qualifier : Splitter.on('-').split(parent)) {
- int qualifierLength = qualifier.length();
- if (qualifierLength == 2) {
- char first = qualifier.charAt(0);
- char second = qualifier.charAt(1);
- if (first >= 'a' && first <= 'z' && second >= 'a' && second <= 'z') {
- mLanguage = qualifier;
- }
- } else if (qualifierLength == 3 && qualifier.charAt(0) == 'r') {
- char first = qualifier.charAt(1);
- char second = qualifier.charAt(2);
- if (first >= 'A' && first <= 'Z' && second >= 'A' && second <= 'Z') {
- mRegion = new String(new char[] { first, second }); // Don't include the "r"
- }
- break;
- }
- }
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- initLocale(context.file.getParentFile().getName());
- if (mLanguage == null) {
- // Check to see if the user has specified the language for this folder
- // using a tools:locale attribute
- if (context instanceof XmlContext) {
- Element root = ((XmlContext) context).document.getDocumentElement();
- if (root != null) {
- String locale = root.getAttributeNS(TOOLS_URI, ATTR_LOCALE);
- if (locale != null && !locale.isEmpty()) {
- initLocale(FD_RES_VALUES + '-' + locale);
- }
- }
- }
-
- if (mLanguage == null) {
- mLanguage = "en"; //$NON-NLS-1$
- }
- }
-
- if (!equal(mLastLanguage, mLanguage) || !equal(mLastRegion, mRegion)) {
- mLookup = TypoLookup.get(context.getClient(), mLanguage, mRegion);
- mLastLanguage = mLanguage;
- mLastRegion = mRegion;
- }
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.NORMAL;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(TAG_STRING);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (mLookup == null) {
- return;
- }
-
- visit(context, element);
- }
-
- private void visit(XmlContext context, Node node) {
- if (node.getNodeType() == Node.TEXT_NODE) {
- // TODO: Figure out how to deal with entities
- check(context, node, node.getNodeValue());
- } else {
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- visit(context, children.item(i));
- }
- }
- }
-
- private void check(XmlContext context, Node node, String text) {
- int max = text.length();
- int index = 0;
- boolean checkedTypos = false;
- while (index < max) {
- for (; index < max; index++) {
- char c = text.charAt(index);
- if (c == '\\') {
- index++;
- continue;
- } else if (Character.isLetter(c)) {
- break;
- }
- }
- if (index >= max) {
- return;
- }
- int begin = index;
- for (; index < max; index++) {
- char c = text.charAt(index);
- if (c == '\\') {
- index++;
- break;
- } else if (!Character.isLetter(c)) {
- break;
- } else if (text.charAt(index) >= 0x80) {
- // Switch to UTF-8 handling for this string
- if (checkedTypos) {
- // If we've already checked words we may have reported typos
- // so create a substring from the current word and on.
- byte[] utf8Text = text.substring(begin).getBytes(Charsets.UTF_8);
- check(context, node, utf8Text, 0, utf8Text.length, text, begin);
- } else {
- // If all we've done so far is skip whitespace (common scenario)
- // then no need to substring the text, just re-search with the
- // UTF-8 routines
- byte[] utf8Text = text.getBytes(Charsets.UTF_8);
- check(context, node, utf8Text, 0, utf8Text.length, text, 0);
- }
- return;
- }
- }
-
- int end = index;
- checkedTypos = true;
- List<String> replacements = mLookup.getTypos(text, begin, end);
- if (replacements != null) {
- reportTypo(context, node, text, begin, replacements);
- }
-
- index = end + 1;
- }
- }
-
- private void check(XmlContext context, Node node, byte[] utf8Text,
- int byteStart, int byteEnd, String text, int charStart) {
- int index = byteStart;
- while (index < byteEnd) {
- // Find beginning of word
- while (index < byteEnd) {
- byte b = utf8Text[index];
- if (b == '\\') {
- index++;
- charStart++;
- if (index < byteEnd) {
- b = utf8Text[index];
- }
- } else if (isLetter(b)) {
- break;
- }
- index++;
- if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
- // First characters in UTF-8 are always ASCII (0 high bit) or 11XXXXXX
- charStart++;
- }
- }
-
- if (index >= byteEnd) {
- return;
- }
- int charEnd = charStart;
- int begin = index;
-
- // Find end of word. Unicode has the nice property that even 2nd, 3rd and 4th
- // bytes won't match these ASCII characters (because the high bit must be set there)
- while (index < byteEnd) {
- byte b = utf8Text[index];
- if (b == '\\') {
- index++;
- charEnd++;
- if (index < byteEnd) {
- b = utf8Text[index++];
- if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
- charEnd++;
- }
- }
- break;
- } else if (!isLetter(b)) {
- break;
- }
- index++;
- if ((b & 0x80) == 0 || (b & 0xC0) == 0xC0) {
- // First characters in UTF-8 are always ASCII (0 high bit) or 11XXXXXX
- charEnd++;
- }
- }
-
- int end = index;
- List<String> replacements = mLookup.getTypos(utf8Text, begin, end);
- if (replacements != null) {
- reportTypo(context, node, text, charStart, replacements);
- }
-
- charStart = charEnd;
- }
- }
-
- /** Report the typo found at the given offset and suggest the given replacements */
- private static void reportTypo(XmlContext context, Node node, String text, int begin,
- List<String> replacements) {
- if (replacements.size() < 2) {
- return;
- }
-
- String typo = replacements.get(0);
- String word = text.substring(begin, begin + typo.length());
-
- String first = null;
- String message;
-
- boolean isCapitalized = Character.isUpperCase(word.charAt(0));
- StringBuilder sb = new StringBuilder(40);
- for (int i = 1, n = replacements.size(); i < n; i++) {
- String replacement = replacements.get(i);
- if (first == null) {
- first = replacement;
- }
- if (sb.length() > 0) {
- sb.append(" or ");
- }
- sb.append('"');
- if (isCapitalized) {
- sb.append(Character.toUpperCase(replacement.charAt(0)));
- sb.append(replacement.substring(1));
- } else {
- sb.append(replacement);
- }
- sb.append('"');
- }
-
- if (first != null && first.equalsIgnoreCase(word)) {
- if (first.equals(word)) {
- return;
- }
- message = String.format(
- "\"%1$s\" is usually capitalized as \"%2$s\"",
- word, first);
- } else {
- message = String.format(
- "\"%1$s\" is a common misspelling; did you mean %2$s ?",
- word, sb.toString());
- }
-
- int end = begin + word.length();
- context.report(ISSUE, node, context.getLocation(node, begin, end), message, null);
- }
-
- /** Returns the suggested replacements, if any, for the given typo. The error
- * message <b>must</b> be one supplied by lint.
- *
- * @param errorMessage the error message
- * @return a list of replacement words suggested by the error message
- */
- @Nullable
- public static List<String> getSuggestions(@NonNull String errorMessage) {
- // The words are all in quotes; the first word is the misspelling,
- // the other words are the suggested replacements
- List<String> words = new ArrayList<String>();
- // Skip the typo
- int index = errorMessage.indexOf('"');
- index = errorMessage.indexOf('"', index + 1);
- index++;
-
- while (true) {
- index = errorMessage.indexOf('"', index);
- if (index == -1) {
- break;
- }
- index++;
- int start = index;
- index = errorMessage.indexOf('"', index);
- if (index == -1) {
- index = errorMessage.length();
- }
- words.add(errorMessage.substring(start, index));
- index++;
- }
-
- return words;
- }
-
- /**
- * Returns the typo word in the error message from this detector
- *
- * @param errorMessage the error message produced earlier by this detector
- * @return the typo
- */
- @Nullable
- public static String getTypo(@NonNull String errorMessage) {
- // The words are all in quotes
- int index = errorMessage.indexOf('"');
- int start = index + 1;
- index = errorMessage.indexOf('"', start);
- if (index != -1) {
- return errorMessage.substring(start, index);
- }
-
- return null;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
deleted file mode 100644
index 7d9d8f2..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypoLookup.java
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
-import com.android.tools.lint.client.api.LintClient;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.google.common.base.Charsets;
-import com.google.common.base.Splitter;
-import com.google.common.io.Files;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel.MapMode;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.WeakHashMap;
-
-/**
- * Database of common typos / misspellings.
- */
-public class TypoLookup {
- private static final TypoLookup NONE = new TypoLookup();
-
- /** String separating misspellings and suggested replacements in the text file */
- private static final String WORD_SEPARATOR = "->"; //$NON-NLS-1$
-
- /** Relative path to the typos database file within the Lint installation */
- private static final String XML_FILE_PATH = "tools/support/typos-%1$s.txt"; //$NON-NLS-1$
- private static final String FILE_HEADER = "Typo database used by Android lint\000";
- private static final int BINARY_FORMAT_VERSION = 2;
- private static final boolean DEBUG_FORCE_REGENERATE_BINARY = false;
- private static final boolean DEBUG_SEARCH = false;
- private static final boolean WRITE_STATS = false;
- /** Default size to reserve for each API entry when creating byte buffer to build up data */
- private static final int BYTES_PER_ENTRY = 28;
-
- private final LintClient mClient;
- private final File mXmlFile;
- private final File mBinaryFile;
- private byte[] mData;
- private int[] mIndices;
- private int mWordCount;
-
- private static final WeakHashMap<String, TypoLookup> sInstanceMap =
- new WeakHashMap<String, TypoLookup>();
-
- /**
- * Returns an instance of the Typo database for the given locale
- *
- * @param client the client to associate with this database - used only for
- * logging. The database object may be shared among repeated
- * invocations, and in that case client used will be the one
- * originally passed in. In other words, this parameter may be
- * ignored if the client created is not new.
- * @param locale the locale to look up a typo database for (should be a
- * language code (ISO 639-1, two lowercase character names)
- * @param region the region to look up a typo database for (should be a two
- * letter ISO 3166-1 alpha-2 country code in upper case) language
- * code
- * @return a (possibly shared) instance of the typo database, or null if its
- * data can't be found
- */
- @Nullable
- public static TypoLookup get(@NonNull LintClient client, @NonNull String locale,
- @Nullable String region) {
- synchronized (TypoLookup.class) {
- String key = locale;
-
- if (region != null) {
- // Allow for region-specific dictionaries. See for example
- // http://en.wikipedia.org/wiki/American_and_British_English_spelling_differences
- assert region.length() == 2
- && Character.isUpperCase(region.charAt(0))
- && Character.isUpperCase(region.charAt(1)) : region;
- // Look for typos-en-rUS.txt etc
- key = locale + 'r' + region;
- }
-
- TypoLookup db = sInstanceMap.get(key);
- if (db == null) {
- String path = String.format(XML_FILE_PATH, key);
- File file = client.findResource(path);
- if (file == null) {
- // AOSP build environment?
- String build = System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$
- if (build != null) {
- file = new File(build, ("sdk/files/" //$NON-NLS-1$
- + path.substring(path.lastIndexOf('/') + 1))
- .replace('/', File.separatorChar));
- }
- }
-
- if (file == null || !file.exists()) {
- if (region != null) {
- // Fall back to the generic locale (non-region-specific) database
- return get(client, locale, null);
- }
- db = NONE;
- } else {
- db = get(client, file);
- assert db != null : file;
- }
- sInstanceMap.put(key, db);
- }
-
- if (db == NONE) {
- return null;
- } else {
- return db;
- }
- }
- }
-
- /**
- * Returns an instance of the typo database
- *
- * @param client the client to associate with this database - used only for
- * logging
- * @param xmlFile the XML file containing configuration data to use for this
- * database
- * @return a (possibly shared) instance of the typo database, or null
- * if its data can't be found
- */
- @Nullable
- private static TypoLookup get(LintClient client, File xmlFile) {
- if (!xmlFile.exists()) {
- client.log(null, "The typo database file %1$s does not exist", xmlFile);
- return null;
- }
-
- String name = xmlFile.getName();
- if (LintUtils.endsWith(name, DOT_XML)) {
- name = name.substring(0, name.length() - DOT_XML.length());
- }
- File cacheDir = client.getCacheDir(true/*create*/);
- if (cacheDir == null) {
- cacheDir = xmlFile.getParentFile();
- }
-
- File binaryData = new File(cacheDir, name
- // Incorporate version number in the filename to avoid upgrade filename
- // conflicts on Windows (such as issue #26663)
- + '-' + BINARY_FORMAT_VERSION + ".bin"); //$NON-NLS-1$
-
- if (DEBUG_FORCE_REGENERATE_BINARY) {
- System.err.println("\nTemporarily regenerating binary data unconditionally \nfrom "
- + xmlFile + "\nto " + binaryData);
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- } else if (!binaryData.exists() || binaryData.lastModified() < xmlFile.lastModified()) {
- if (!createCache(client, xmlFile, binaryData)) {
- return null;
- }
- }
-
- if (!binaryData.exists()) {
- client.log(null, "The typo database file %1$s does not exist", binaryData);
- return null;
- }
-
- return new TypoLookup(client, xmlFile, binaryData);
- }
-
- private static boolean createCache(LintClient client, File xmlFile, File binaryData) {
- long begin = 0;
- if (WRITE_STATS) {
- begin = System.currentTimeMillis();
- }
-
- // Read in data
- List<String> lines;
- try {
- lines = Files.readLines(xmlFile, Charsets.UTF_8);
- } catch (IOException e) {
- client.log(e, "Can't read typo database file");
- return false;
- }
-
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("Reading data structures took " + (end - begin) + " ms)");
- }
-
- try {
- writeDatabase(binaryData, lines);
- return true;
- } catch (IOException ioe) {
- client.log(ioe, "Can't write typo cache file");
- }
-
- return false;
- }
-
- /** Use one of the {@link #get} factory methods instead */
- private TypoLookup(
- @NonNull LintClient client,
- @NonNull File xmlFile,
- @Nullable File binaryFile) {
- mClient = client;
- mXmlFile = xmlFile;
- mBinaryFile = binaryFile;
-
- if (binaryFile != null) {
- readData();
- }
- }
-
- private TypoLookup() {
- mClient = null;
- mXmlFile = null;
- mBinaryFile = null;
- }
-
- private void readData() {
- if (!mBinaryFile.exists()) {
- mClient.log(null, "%1$s does not exist", mBinaryFile);
- return;
- }
- long start = System.currentTimeMillis();
- try {
- MappedByteBuffer buffer = Files.map(mBinaryFile, MapMode.READ_ONLY);
- assert buffer.order() == ByteOrder.BIG_ENDIAN;
-
- // First skip the header
- byte[] expectedHeader = FILE_HEADER.getBytes(Charsets.US_ASCII);
- buffer.rewind();
- for (int offset = 0; offset < expectedHeader.length; offset++) {
- if (expectedHeader[offset] != buffer.get()) {
- mClient.log(null, "Incorrect file header: not an typo database cache " +
- "file, or a corrupt cache file");
- return;
- }
- }
-
- // Read in the format number
- if (buffer.get() != BINARY_FORMAT_VERSION) {
- // Force regeneration of new binary data with up to date format
- if (createCache(mClient, mXmlFile, mBinaryFile)) {
- readData(); // Recurse
- }
-
- return;
- }
-
- mWordCount = buffer.getInt();
-
- // Read in the word table indices;
- int count = mWordCount;
- int[] offsets = new int[count];
-
- // Another idea: I can just store the DELTAS in the file (and add them up
- // when reading back in) such that it takes just ONE byte instead of four!
-
- for (int i = 0; i < count; i++) {
- offsets[i] = buffer.getInt();
- }
-
- // No need to read in the rest -- we'll just keep the whole byte array in memory
- // TODO: Make this code smarter/more efficient.
- int size = buffer.limit();
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- mData = b;
- mIndices = offsets;
-
- // TODO: We only need to keep the data portion here since we've initialized
- // the offset array separately.
- // TODO: Investigate (profile) accessing the byte buffer directly instead of
- // accessing a byte array.
- } catch (IOException e) {
- mClient.log(e, null);
- }
- if (WRITE_STATS) {
- long end = System.currentTimeMillis();
- System.out.println("\nRead typo database in " + (end - start)
- + " milliseconds.");
- System.out.println("Size of data table: " + mData.length + " bytes ("
- + Integer.toString(mData.length/1024) + "k)\n");
- }
- }
-
- /** See the {@link #readData()} for documentation on the data format. */
- private static void writeDatabase(File file, List<String> lines) throws IOException {
- /*
- * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- * as ASCII characters. The purpose of the header is to identify what the file
- * is for, for anyone attempting to open the file.
- * 2. A file version number. If the binary file does not match the reader's expected
- * version, it can ignore it (and regenerate the cache from XML).
- */
-
- // Drop comments etc
- List<String> words = new ArrayList<String>(lines.size());
- for (String line : lines) {
- if (!line.isEmpty() && Character.isLetter(line.charAt(0))) {
- int end = line.indexOf(WORD_SEPARATOR);
- if (end == -1) {
- end = line.trim().length();
- }
- String typo = line.substring(0, end).trim();
- String replacements = line.substring(end + WORD_SEPARATOR.length()).trim();
- if (replacements.isEmpty()) {
- // We don't support empty replacements
- continue;
- }
- String combined = typo + (char) 0 + replacements;
-
- words.add(combined);
- }
- }
-
- byte[][] wordArrays = new byte[words.size()][];
- for (int i = 0, n = words.size(); i < n; i++) {
- String word = words.get(i);
- wordArrays[i] = word.getBytes(Charsets.UTF_8);
- }
- // Sort words, using our own comparator to ensure that it matches the
- // binary search in getTypos()
- Comparator<byte[]> comparator = new Comparator<byte[]>() {
- @Override
- public int compare(byte[] o1, byte[] o2) {
- return TypoLookup.compare(o1, 0, (byte) 0, o2, 0, o2.length);
- }
- };
- Arrays.sort(wordArrays, comparator);
-
- int entryCount = wordArrays.length;
- int capacity = entryCount * BYTES_PER_ENTRY;
- ByteBuffer buffer = ByteBuffer.allocate(capacity);
- buffer.order(ByteOrder.BIG_ENDIAN);
- // 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
- // as ASCII characters. The purpose of the header is to identify what the file
- // is for, for anyone attempting to open the file.
- buffer.put(FILE_HEADER.getBytes(Charsets.US_ASCII));
-
- // 2. A file version number. If the binary file does not match the reader's expected
- // version, it can ignore it (and regenerate the cache from XML).
- buffer.put((byte) BINARY_FORMAT_VERSION);
-
- // 3. The number of words [1 int]
- buffer.putInt(entryCount);
-
- // 4. Word offset table (one integer per word, pointing to the byte offset in the
- // file (relative to the beginning of the file) where each word begins.
- // The words are always sorted alphabetically.
- int wordOffsetTable = buffer.position();
-
- // Reserve enough room for the offset table here: we will backfill it with pointers
- // as we're writing out the data structures below
- for (int i = 0, n = entryCount; i < n; i++) {
- buffer.putInt(0);
- }
-
- int nextEntry = buffer.position();
- int nextOffset = wordOffsetTable;
-
- // 7. Word entry table. Each word entry consists of the word, followed by the byte 0
- // as a terminator, followed by a comma separated list of suggestions (which
- // may be empty), or a final 0.
- for (int i = 0; i < entryCount; i++) {
- byte[] word = wordArrays[i];
- buffer.position(nextOffset);
- buffer.putInt(nextEntry);
- nextOffset = buffer.position();
- buffer.position(nextEntry);
-
- buffer.put(word); // already embeds 0 to separate typo from words
- buffer.put((byte) 0);
-
- nextEntry = buffer.position();
- }
-
- int size = buffer.position();
- assert size <= buffer.limit();
- buffer.mark();
-
- if (WRITE_STATS) {
- System.out.println("Wrote " + words.size() + " word entries");
- System.out.print("Actual binary size: " + size + " bytes");
- System.out.println(String.format(" (%.1fM)", size/(1024*1024.f)));
-
- System.out.println("Allocated size: " + (entryCount * BYTES_PER_ENTRY) + " bytes");
- System.out.println("Required bytes per entry: " + (size/ entryCount) + " bytes");
- }
-
- // Now dump this out as a file
- // There's probably an API to do this more efficiently; TODO: Look into this.
- byte[] b = new byte[size];
- buffer.rewind();
- buffer.get(b);
- FileOutputStream output = Files.newOutputStreamSupplier(file).getOutput();
- output.write(b);
- output.close();
- }
-
- // For debugging only
- private String dumpEntry(int offset) {
- if (DEBUG_SEARCH) {
- int end = offset;
- while (mData[end] != 0) {
- end++;
- }
- return new String(mData, offset, end - offset, Charsets.UTF_8);
- } else {
- return "<disabled>"; //$NON-NLS-1$
- }
- }
-
- /** Comparison function: *only* used for ASCII strings */
- @VisibleForTesting
- static int compare(byte[] data, int offset, byte terminator, CharSequence s,
- int begin, int end) {
- int i = offset;
- int j = begin;
- for (; ; i++, j++) {
- byte b = data[i];
- if (b == ' ') {
- // We've matched up to the space in a split-word typo, such as
- // in German all zu=>allzu; here we've matched just past "all".
- // Rather than terminating, attempt to continue in the buffer.
- if (j == end) {
- int max = s.length();
- if (end < max && s.charAt(end) == ' ') {
- // Find next word
- for (; end < max; end++) {
- char c = s.charAt(end);
- if (!Character.isLetter(c)) {
- if (c == ' ' && end == j) {
- continue;
- }
- break;
- }
- }
- }
- }
- }
-
- if (j == end) {
- break;
- }
-
- if (b == '*') {
- // Glob match (only supported at the end)
- return 0;
- }
- char c = s.charAt(j);
- byte cb = (byte) c;
- int delta = b - cb;
- if (delta != 0) {
- cb = (byte) Character.toLowerCase(c);
- if (b != cb) {
- // Ensure that it has the right sign
- b = (byte) Character.toLowerCase(b);
- delta = b - cb;
- if (delta != 0) {
- return delta;
- }
- }
- }
- }
-
- return data[i] - terminator;
- }
-
- /** Comparison function used for general UTF-8 encoded strings */
- @VisibleForTesting
- static int compare(byte[] data, int offset, byte terminator, byte[] s,
- int begin, int end) {
- int i = offset;
- int j = begin;
- for (; ; i++, j++) {
- byte b = data[i];
- if (b == ' ') {
- // We've matched up to the space in a split-word typo, such as
- // in German all zu=>allzu; here we've matched just past "all".
- // Rather than terminating, attempt to continue in the buffer.
- // We've matched up to the space in a split-word typo, such as
- // in German all zu=>allzu; here we've matched just past "all".
- // Rather than terminating, attempt to continue in the buffer.
- if (j == end) {
- int max = s.length;
- if (end < max && s[end] == ' ') {
- // Find next word
- for (; end < max; end++) {
- byte cb = s[end];
- if (!isLetter(cb)) {
- if (cb == ' ' && end == j) {
- continue;
- }
- break;
- }
- }
- }
- }
- }
-
- if (j == end) {
- break;
- }
- if (b == '*') {
- // Glob match (only supported at the end)
- return 0;
- }
- byte cb = s[j];
- int delta = b - cb;
- if (delta != 0) {
- cb = toLowerCase(cb);
- b = toLowerCase(b);
- delta = b - cb;
- if (delta != 0) {
- return delta;
- }
- }
-
- if (b == terminator || cb == terminator) {
- return delta;
- }
- }
-
- return data[i] - terminator;
- }
-
- /**
- * Look up whether this word is a typo, and if so, return the typo itself
- * and one or more likely meanings
- *
- * @param text the string containing the word
- * @param begin the index of the first character in the word
- * @param end the index of the first character after the word. Note that the
- * search may extend <b>beyond</b> this index, if for example the
- * word matches a multi-word typo in the dictionary
- * @return a list of the typo itself followed by the replacement strings if
- * the word represents a typo, and null otherwise
- */
- @Nullable
- public List<String> getTypos(@NonNull CharSequence text, int begin, int end) {
- assert end <= text.length();
-
- if (assertionsEnabled()) {
- for (int i = begin; i < end; i++) {
- char c = text.charAt(i);
- if (c >= 128) {
- assert false : "Call the UTF-8 version of this method instead";
- return null;
- }
- }
- }
-
- int low = 0;
- int high = mWordCount - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- System.out.println("Comparing string " + text +" with entry at " + offset
- + ": " + dumpEntry(offset));
- }
-
- // Compare the word at the given index.
- int compare = compare(mData, offset, (byte) 0, text, begin, end);
-
- if (compare == 0) {
- offset = mIndices[middle];
-
- // Don't allow matching uncapitalized words, such as "enlish", when
- // the dictionary word is capitalized, "Enlish".
- if (mData[offset] != text.charAt(begin)
- && Character.isLowerCase(text.charAt(begin))) {
- return null;
- }
-
- // Make sure there is a case match; we only want to allow
- // matching capitalized words to capitalized typos or uncapitalized typos
- // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized
- // typos (e.g. "enlish" to "Enlish").
- String glob = null;
- for (int i = begin; ; i++) {
- byte b = mData[offset++];
- if (b == 0) {
- offset--;
- break;
- } else if (b == '*') {
- int globEnd = i;
- while (globEnd < text.length()
- && Character.isLetter(text.charAt(globEnd))) {
- globEnd++;
- }
- glob = text.subSequence(i, globEnd).toString();
- break;
- }
- char c = text.charAt(i);
- byte cb = (byte) c;
- if (b != cb && i > begin) {
- return null;
- }
- }
-
- return computeSuggestions(mIndices[middle], offset, glob);
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return null;
- }
- }
-
- return null;
- }
-
- /**
- * Look up whether this word is a typo, and if so, return the typo itself
- * and one or more likely meanings
- *
- * @param utf8Text the string containing the word, encoded as UTF-8
- * @param begin the index of the first character in the word
- * @param end the index of the first character after the word. Note that the
- * search may extend <b>beyond</b> this index, if for example the
- * word matches a multi-word typo in the dictionary
- * @return a list of the typo itself followed by the replacement strings if
- * the word represents a typo, and null otherwise
- */
- @Nullable
- public List<String> getTypos(@NonNull byte[] utf8Text, int begin, int end) {
- assert end <= utf8Text.length;
-
- int low = 0;
- int high = mWordCount - 1;
- while (low <= high) {
- int middle = (low + high) >>> 1;
- int offset = mIndices[middle];
-
- if (DEBUG_SEARCH) {
- String s = new String(Arrays.copyOfRange(utf8Text, begin, end), Charsets.UTF_8);
- System.out.println("Comparing string " + s +" with entry at " + offset
- + ": " + dumpEntry(offset));
- System.out.println(" middle=" + middle + ", low=" + low + ", high=" + high);
- }
-
- // Compare the word at the given index.
- int compare = compare(mData, offset, (byte) 0, utf8Text, begin, end);
-
- if (DEBUG_SEARCH) {
- System.out.println(" signum=" + (int)Math.signum(compare) + ", delta=" + compare);
- }
-
- if (compare == 0) {
- offset = mIndices[middle];
-
- // Don't allow matching uncapitalized words, such as "enlish", when
- // the dictionary word is capitalized, "Enlish".
- if (mData[offset] != utf8Text[begin] && isUpperCase(mData[offset])) {
- return null;
- }
-
- // Make sure there is a case match; we only want to allow
- // matching capitalized words to capitalized typos or uncapitalized typos
- // (e.g. "Teh" and "teh" to "the"), but not uncapitalized words to capitalized
- // typos (e.g. "enlish" to "Enlish").
- String glob = null;
- for (int i = begin; ; i++) {
- byte b = mData[offset++];
- if (b == 0) {
- offset--;
- break;
- } else if (b == '*') {
- int globEnd = i;
- while (globEnd < utf8Text.length && isLetter(utf8Text[globEnd])) {
- globEnd++;
- }
- glob = new String(utf8Text, i, globEnd - i, Charsets.UTF_8);
- break;
- }
- byte cb = utf8Text[i];
- if (b != cb && i > begin) {
- return null;
- }
- }
-
- return computeSuggestions(mIndices[middle], offset, glob);
- }
-
- if (compare < 0) {
- low = middle + 1;
- } else if (compare > 0) {
- high = middle - 1;
- } else {
- assert false; // compare == 0 already handled above
- return null;
- }
- }
-
- return null;
- }
-
- private List<String> computeSuggestions(int begin, int offset, String glob) {
- String typo = new String(mData, begin, offset - begin, Charsets.UTF_8);
-
- if (glob != null) {
- typo = typo.replaceAll("\\*", glob); //$NON-NLS-1$
- }
-
- assert mData[offset] == 0;
- offset++;
- int replacementEnd = offset;
- while (mData[replacementEnd] != 0) {
- replacementEnd++;
- }
- String replacements = new String(mData, offset, replacementEnd - offset, Charsets.UTF_8);
- List<String> words = new ArrayList<String>();
- words.add(typo);
-
- // The first entry should be the typo itself. We need to pass this back since due
- // to multi-match words and globbing it could extend beyond the initial word range
-
- for (String s : Splitter.on(',').omitEmptyStrings().trimResults().split(replacements)) {
- if (glob != null) {
- // Need to append the glob string to each result
- words.add(s.replaceAll("\\*", glob)); //$NON-NLS-1$
- } else {
- words.add(s);
- }
- }
-
- return words;
- }
-
- // "Character" handling for bytes. This assumes that the bytes correspond to Unicode
- // characters in the ISO 8859-1 range, which is are encoded the same way in UTF-8.
- // This obviously won't work to for example uppercase to lowercase conversions for
- // multi byte characters, which means we simply won't catch typos if the dictionaries
- // contain these. None of the currently included dictionaries do. However, it does
- // help us properly deal with punctuation and spacing characters.
-
- static boolean isUpperCase(byte b) {
- return Character.isUpperCase((char) b);
- }
-
- static byte toLowerCase(byte b) {
- return (byte) Character.toLowerCase((char) b);
- }
-
- static boolean isSpace(byte b) {
- return Character.isWhitespace((char) b);
- }
-
- static boolean isLetter(byte b) {
- // Assume that multi byte characters represent letters in other languages.
- // Obviously, it could be unusual punctuation etc but letters are more likely
- // in this context.
- return Character.isLetter((char) b) || (b & 0x80) != 0;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java
deleted file mode 100644
index ae5f24c..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.VisibleForTesting;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Checks for various typographical issues in string definitions.
- */
-public class TypographyDetector extends ResourceXmlDetector {
- /** Replace hyphens with dashes? */
- public static final Issue DASHES = Issue.create(
- "TypographyDashes", //$NON-NLS-1$
- "Looks for usages of hyphens which can be replaced by n dash and m dash characters",
- "The \"n dash\" (\u2013, &#8211;) and the \"m dash\" (\u2014, &#8212;) " +
- "characters are used for ranges (n dash) and breaks (m dash). Using these " +
- "instead of plain hyphens can make text easier to read and your application " +
- "will look more polished.",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Dash"); //$NON-NLS-1$
-
- /** Replace dumb quotes with smart quotes? */
- public static final Issue QUOTES = Issue.create(
- "TypographyQuotes", //$NON-NLS-1$
- "Looks for straight quotes which can be replaced by curvy quotes",
- "Straight single quotes and double quotes, when used as a pair, can be replaced " +
- "by \"curvy quotes\" (or directional quotes). This can make the text more " +
- "readable.\n" +
- "\n" +
- "Note that you should never use grave accents and apostrophes to quote, " +
- "`like this'.\n" +
- "\n" +
- "(Also note that you should not use curvy quotes for code fragments.)",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Quotation_mark"). //$NON-NLS-1$
- // This feature is apparently controversial: recent apps have started using
- // straight quotes to avoid inconsistencies. Disabled by default for now.
- setEnabledByDefault(false);
-
- /** Replace fraction strings with fraction characters? */
- public static final Issue FRACTIONS = Issue.create(
- "TypographyFractions", //$NON-NLS-1$
- "Looks for fraction strings which can be replaced with a fraction character",
- "You can replace certain strings, such as 1/2, and 1/4, with dedicated " +
- "characters for these, such as \u00BD (&#189;) and \00BC (&#188;). " +
- "This can help make the text more readable.",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Number_Forms"); //$NON-NLS-1$
-
- /** Replace ... with the ellipsis character? */
- public static final Issue ELLIPSIS = Issue.create(
- "TypographyEllipsis", //$NON-NLS-1$
- "Looks for ellipsis strings (...) which can be replaced with an ellipsis character",
- "You can replace the string \"...\" with a dedicated ellipsis character, " +
- "ellipsis character (\u2026, &#8230;). This can help make the text more readable.",
- Category.TYPOGRAPHY,
- 5,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE).
- setMoreInfo("http://en.wikipedia.org/wiki/Ellipsis"); //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue OTHER = Issue.create(
- "TypographyOther", //$NON-NLS-1$
- "Looks for miscellaneous typographical problems like replacing (c) with \u00A9",
- "This check looks for miscellaneous typographical problems and offers replacement " +
- "sequences that will make the text easier to read and your application more " +
- "polished.",
- Category.TYPOGRAPHY,
- 3,
- Severity.WARNING,
- TypographyDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- private static final String GRAVE_QUOTE_MESSAGE =
- "Avoid quoting with grave accents; use apostrophes or better yet directional quotes instead";
- private static final String ELLIPSIS_MESSAGE =
- "Replace \"...\" with ellipsis character (\u2026, &#8230;) ?";
- private static final String EN_DASH_MESSAGE =
- "Replace \"-\" with an \"en dash\" character (\u2013, &#8211;) ?";
- private static final String EM_DASH_MESSAGE =
- "Replace \"--\" with an \"em dash\" character (\u2014, &#8212;) ?";
- private static final String TYPOGRAPHIC_APOSTROPHE_MESSAGE =
- "Replace apostrophe (') with typographic apostrophe (\u2019, &#8217;) ?";
- private static final String SINGLE_QUOTE_MESSAGE =
- "Replace straight quotes ('') with directional quotes (\u2018\u2019, &#8216; and &#8217;) ?";
- private static final String DBL_QUOTES_MESSAGE =
- "Replace straight quotes (\") with directional quotes (\u201C\u201D, &#8220; and &#8221;) ?";
- private static final String COPYRIGHT_MESSAGE =
- "Replace (c) with copyright symbol \u00A9 (&#169;) ?";
-
- /**
- * Pattern used to detect scenarios which can be replaced with n dashes: a
- * numeric range with a hyphen in the middle (and possibly spaces)
- */
- @VisibleForTesting
- static final Pattern HYPHEN_RANGE_PATTERN =
- Pattern.compile(".*(\\d+\\s*)-(\\s*\\d+).*"); //$NON-NLS-1$
-
- /**
- * Pattern used to detect scenarios where a grave accent mark is used
- * to do ASCII quotations of the form `this'' or ``this'', which is frowned upon.
- * This pattern tries to avoid falsely complaining about strings like
- * "Type Option-` then 'Escape'."
- */
- @VisibleForTesting
- static final Pattern GRAVE_QUOTATION =
- Pattern.compile("(^[^`]*`[^'`]+'[^']*$)|(^[^`]*``[^'`]+''[^']*$)"); //$NON-NLS-1$
-
- /**
- * Pattern used to detect common fractions, e.g. 1/2, 1/3, 2/3, 1/4, 3/4 and
- * variations like 2 / 3, but not 11/22 and so on.
- */
- @VisibleForTesting
- static final Pattern FRACTION_PATTERN =
- Pattern.compile(".*\\b([13])\\s*/\\s*([234])\\b.*"); //$NON-NLS-1$
-
- /**
- * Pattern used to detect single quote strings, such as 'hello', but
- * not just quoted strings like 'Double quote: "', and not sentences
- * where there are multiple apostrophes but not in a quoting context such
- * as "Mind Your P's and Q's".
- */
- @VisibleForTesting
- static final Pattern SINGLE_QUOTE =
- Pattern.compile(".*\\W*'[^']+'(\\W.*)?"); //$NON-NLS-1$
-
- private static final String FRACTION_MESSAGE =
- "Use fraction character %1$c (%2$s) instead of %3$s ?";
-
- private static final String FRACTION_MESSAGE_PATTERN =
- "Use fraction character (.+) \\((.+)\\) instead of (.+) \\?";
-
- private boolean mCheckDashes;
- private boolean mCheckQuotes;
- private boolean mCheckFractions;
- private boolean mCheckEllipsis;
- private boolean mCheckMisc;
-
- /** Constructs a new {@link TypographyDetector} */
- public TypographyDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.VALUES;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STRING,
- TAG_STRING_ARRAY
- );
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- mCheckDashes = context.isEnabled(DASHES);
- mCheckQuotes = context.isEnabled(QUOTES);
- mCheckFractions = context.isEnabled(FRACTIONS);
- mCheckEllipsis = context.isEnabled(ELLIPSIS);
- mCheckMisc = context.isEnabled(OTHER);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- NodeList childNodes = element.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = child.getNodeValue();
- checkText(context, element, child, text);
- } else if (child.getNodeType() == Node.ELEMENT_NODE &&
- child.getParentNode().getNodeName().equals(TAG_STRING_ARRAY)) {
- // String array item children
- NodeList items = child.getChildNodes();
- for (int j = 0, m = items.getLength(); j < m; j++) {
- Node item = items.item(j);
- if (item.getNodeType() == Node.TEXT_NODE) {
- String text = item.getNodeValue();
- checkText(context, child, item, text);
- }
- }
- }
- }
- }
-
- private void checkText(XmlContext context, Node element, Node textNode, String text) {
- if (mCheckEllipsis) {
- // Replace ... with ellipsis character?
- int ellipsis = text.indexOf("..."); //$NON-NLS-1$
- if (ellipsis != -1 && !text.startsWith(".", ellipsis + 3)) { //$NON-NLS-1$
- context.report(ELLIPSIS, element, context.getLocation(textNode),
- ELLIPSIS_MESSAGE, null);
- }
- }
-
- // Dashes
- if (mCheckDashes) {
- int hyphen = text.indexOf('-');
- if (hyphen != -1) {
- // n dash
- Matcher matcher = HYPHEN_RANGE_PATTERN.matcher(text);
- if (matcher.matches()) {
- // Make sure that if there is no space before digit there isn't
- // one on the left either -- since we don't want to consider
- // "1 2 -3" as a range from 2 to 3
- boolean isNegativeNumber =
- !Character.isWhitespace(matcher.group(2).charAt(0)) &&
- Character.isWhitespace(matcher.group(1).charAt(
- matcher.group(1).length() - 1));
- if (!isNegativeNumber && !isAnalyticsTrackingId((Element) element)) {
- context.report(DASHES, element, context.getLocation(textNode),
- EN_DASH_MESSAGE,
- null);
- }
- }
-
- // m dash
- int emdash = text.indexOf("--"); //$NON-NLS-1$
- // Don't suggest replacing -- or "--" with an m dash since these are sometimes
- // used as digit marker strings
- if (emdash > 1 && !text.startsWith("-", emdash + 2)) { //$NON-NLS-1$
- context.report(DASHES, element, context.getLocation(textNode),
- EM_DASH_MESSAGE, null);
- }
- }
- }
-
- if (mCheckQuotes) {
- // Check for single quotes that can be replaced with directional quotes
- int quoteStart = text.indexOf('\'');
- if (quoteStart != -1) {
- int quoteEnd = text.indexOf('\'', quoteStart + 1);
- if (quoteEnd != -1 && quoteEnd > quoteStart + 1
- && (quoteEnd < text.length() -1 || quoteStart > 0)
- && SINGLE_QUOTE.matcher(text).matches()) {
- context.report(QUOTES, element, context.getLocation(textNode),
- SINGLE_QUOTE_MESSAGE, null);
- return;
- }
-
- // Check for apostrophes that can be replaced by typographic apostrophes
- if (quoteEnd == -1 && quoteStart > 0
- && Character.isLetterOrDigit(text.charAt(quoteStart - 1))) {
- context.report(QUOTES, element, context.getLocation(textNode),
- TYPOGRAPHIC_APOSTROPHE_MESSAGE, null);
- return;
- }
- }
-
- // Check for double quotes that can be replaced by directional double quotes
- quoteStart = text.indexOf('"');
- if (quoteStart != -1) {
- int quoteEnd = text.indexOf('"', quoteStart + 1);
- if (quoteEnd != -1 && quoteEnd > quoteStart + 1) {
- if (quoteEnd < text.length() -1 || quoteStart > 0) {
- context.report(QUOTES, element, context.getLocation(textNode),
- DBL_QUOTES_MESSAGE, null);
- return;
- }
- }
- }
-
- // Check for grave accent quotations
- if (text.indexOf('`') != -1 && GRAVE_QUOTATION.matcher(text).matches()) {
- // Are we indenting ``like this'' or `this' ? If so, complain
- context.report(QUOTES, element, context.getLocation(textNode),
- GRAVE_QUOTE_MESSAGE, null);
- return;
- }
-
- // Consider suggesting other types of directional quotes, such as guillemets, in
- // other languages?
- // There are a lot of exceptions and special cases to be considered so
- // this will need careful implementation and testing.
- // See http://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks
- }
-
- // Fraction symbols?
- if (mCheckFractions && text.indexOf('/') != -1) {
- Matcher matcher = FRACTION_PATTERN.matcher(text);
- if (matcher.matches()) {
- String top = matcher.group(1); // Numerator
- String bottom = matcher.group(2); // Denominator
- if (top.equals("1") && bottom.equals("2")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u00BD', "&#189;", "1/2"), null);
- } else if (top.equals("1") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u00BC', "&#188;", "1/4"), null);
- } else if (top.equals("3") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u00BE', "&#190;", "3/4"), null);
- } else if (top.equals("1") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u2153', "&#8531;", "1/3"), null);
- } else if (top.equals("2") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$
- context.report(FRACTIONS, element, context.getLocation(textNode),
- String.format(FRACTION_MESSAGE, '\u2154', "&#8532;", "2/3"), null);
- }
- }
- }
-
- if (mCheckMisc) {
- // Fix copyright symbol?
- if (text.indexOf('(') != -1
- && (text.contains("(c)") || text.contains("(C)"))) { //$NON-NLS-1$ //$NON-NLS-2$
- // Suggest replacing with copyright symbol?
- context.report(OTHER, element, context.getLocation(textNode),
- COPYRIGHT_MESSAGE, null);
- // Replace (R) and TM as well? There are unicode characters for these but they
- // are probably not very common within Android app strings.
- }
- }
- }
-
- private static boolean isAnalyticsTrackingId(Element element) {
- String name = element.getAttribute(ATTR_NAME);
- return "ga_trackingId".equals(name); //$NON-NLS-1$
- }
-
- /**
- * An object describing a single edit to be made. The offset points to a
- * location to start editing; the length is the number of characters to
- * delete, and the replaceWith string points to a string to insert at the
- * offset. Note that this can model not just replacement edits but deletions
- * (empty replaceWith) and insertions (replace length = 0) too.
- */
- public static class ReplaceEdit {
- /** The offset of the edit */
- public final int offset;
- /** The number of characters to delete at the offset */
- public final int length;
- /** The characters to insert at the offset */
- public final String replaceWith;
-
- /**
- * Creates a new replace edit
- *
- * @param offset the offset of the edit
- * @param length the number of characters to delete at the offset
- * @param replaceWith the characters to insert at the offset
- */
- public ReplaceEdit(int offset, int length, String replaceWith) {
- super();
- this.offset = offset;
- this.length = length;
- this.replaceWith = replaceWith;
- }
- }
-
- /**
- * Returns a list of edits to be applied to fix the suggestion made by the
- * given warning. The specific issue id and message should be the message
- * provided by this detector in an earlier run.
- * <p>
- * This is intended to help tools implement automatic fixes of these
- * warnings. The reason only the message and issue id can be provided
- * instead of actual state passed in the data field to a reporter is that
- * fix operation can be run much later than the lint is processed (for
- * example, in a subsequent run of the IDE when only the warnings have been
- * persisted),
- *
- * @param issueId the issue id, which should be the id for one of the
- * typography issues
- * @param message the actual error message, which should be a message
- * provided by this detector
- * @param textNode a text node which corresponds to the text node the
- * warning operated on
- * @return a list of edits, which is never null but could be empty. The
- * offsets in the edit objects are relative to the text node.
- */
- public static List<ReplaceEdit> getEdits(String issueId, String message, Node textNode) {
- return getEdits(issueId, message, textNode.getNodeValue());
- }
-
- /**
- * Returns a list of edits to be applied to fix the suggestion made by the
- * given warning. The specific issue id and message should be the message
- * provided by this detector in an earlier run.
- * <p>
- * This is intended to help tools implement automatic fixes of these
- * warnings. The reason only the message and issue id can be provided
- * instead of actual state passed in the data field to a reporter is that
- * fix operation can be run much later than the lint is processed (for
- * example, in a subsequent run of the IDE when only the warnings have been
- * persisted),
- *
- * @param issueId the issue id, which should be the id for one of the
- * typography issues
- * @param message the actual error message, which should be a message
- * provided by this detector
- * @param text the text of the XML node where the warning appeared
- * @return a list of edits, which is never null but could be empty. The
- * offsets in the edit objects are relative to the text node.
- */
- public static List<ReplaceEdit> getEdits(String issueId, String message, String text) {
- List<ReplaceEdit> edits = new ArrayList<ReplaceEdit>();
- if (message.equals(ELLIPSIS_MESSAGE)) {
- int offset = text.indexOf("..."); //$NON-NLS-1$
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 3, "\u2026")); //$NON-NLS-1$
- }
- } else if (message.equals(EN_DASH_MESSAGE)) {
- int offset = text.indexOf('-');
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2013")); //$NON-NLS-1$
- }
- } else if (message.equals(EM_DASH_MESSAGE)) {
- int offset = text.indexOf("--"); //$NON-NLS-1$
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 2, "\u2014")); //$NON-NLS-1$
- }
- } else if (message.equals(TYPOGRAPHIC_APOSTROPHE_MESSAGE)) {
- int offset = text.indexOf('\'');
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2019")); //$NON-NLS-1$
- }
- } else if (message.equals(COPYRIGHT_MESSAGE)) {
- int offset = text.indexOf("(c)"); //$NON-NLS-1$
- if (offset == -1) {
- offset = text.indexOf("(C)"); //$NON-NLS-1$
- }
- if (offset != -1) {
- edits.add(new ReplaceEdit(offset, 3, "\u00A9")); //$NON-NLS-1$
- }
- } else if (message.equals(SINGLE_QUOTE_MESSAGE)) {
- int offset = text.indexOf('\'');
- if (offset != -1) {
- int endOffset = text.indexOf('\'', offset + 1); //$NON-NLS-1$
- if (endOffset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2018")); //$NON-NLS-1$
- edits.add(new ReplaceEdit(endOffset, 1, "\u2019")); //$NON-NLS-1$
- }
- }
- } else if (message.equals(DBL_QUOTES_MESSAGE)) {
- int offset = text.indexOf('"');
- if (offset != -1) {
- int endOffset = text.indexOf('"', offset + 1);
- if (endOffset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u201C")); //$NON-NLS-1$
- edits.add(new ReplaceEdit(endOffset, 1, "\u201D")); //$NON-NLS-1$
- }
- }
- } else if (message.equals(GRAVE_QUOTE_MESSAGE)) {
- int offset = text.indexOf('`');
- if (offset != -1) {
- int endOffset = text.indexOf('\'', offset + 1);
- if (endOffset != -1) {
- edits.add(new ReplaceEdit(offset, 1, "\u2018")); //$NON-NLS-1$
- edits.add(new ReplaceEdit(endOffset, 1, "\u2019")); //$NON-NLS-1$
- }
- }
- } else {
- Matcher matcher = Pattern.compile(FRACTION_MESSAGE_PATTERN).matcher(message);
- if (matcher.find()) {
- // "Use fraction character %1$c (%2$s) instead of %3$s ?";
- String replace = matcher.group(3);
- int offset = text.indexOf(replace);
- if (offset != -1) {
- String replaceWith = matcher.group(2);
- edits.add(new ReplaceEdit(offset, replace.length(), replaceWith));
- }
- }
- }
-
- return edits;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
deleted file mode 100644
index caa112a..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_REF_PREFIX;
-import static com.android.SdkConstants.DOT_GIF;
-import static com.android.SdkConstants.DOT_JPG;
-import static com.android.SdkConstants.DOT_PNG;
-import static com.android.SdkConstants.DOT_XML;
-import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE;
-import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY;
-import static com.android.SdkConstants.RESOURCE_CLZ_ID;
-import static com.android.SdkConstants.R_ATTR_PREFIX;
-import static com.android.SdkConstants.R_CLASS;
-import static com.android.SdkConstants.R_ID_PREFIX;
-import static com.android.SdkConstants.R_PREFIX;
-import static com.android.SdkConstants.TAG_ARRAY;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_PLURALS;
-import static com.android.SdkConstants.TAG_RESOURCES;
-import static com.android.SdkConstants.TAG_STRING_ARRAY;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Project;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.collect.Lists;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ClassDeclaration;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-
-/**
- * Finds unused resources.
- * <p>
- * Note: This detector currently performs *string* analysis to check Java files.
- * The Lint API needs an official Java AST API (or map to an existing one like
- * BCEL for bytecode analysis etc) and once it does this should be updated to
- * use it.
- */
-public class UnusedResourceDetector extends ResourceXmlDetector implements Detector.JavaScanner {
-
- /** Unused resources (other than ids). */
- public static final Issue ISSUE = Issue.create("UnusedResources", //$NON-NLS-1$
- "Looks for unused resources",
- "Unused resources make applications larger and slow down builds.",
- Category.PERFORMANCE,
- 3,
- Severity.WARNING,
- UnusedResourceDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES));
-
- /** Unused id's */
- public static final Issue ISSUE_IDS = Issue.create("UnusedIds", //$NON-NLS-1$
- "Looks for unused id's",
- "This resource id definition appears not to be needed since it is not referenced " +
- "from anywhere. Having id definitions, even if unused, is not necessarily a bad " +
- "idea since they make working on layouts and menus easier, so there is not a " +
- "strong reason to delete these.",
- Category.PERFORMANCE,
- 1,
- Severity.WARNING,
- UnusedResourceDetector.class,
- EnumSet.of(Scope.MANIFEST, Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES))
- .setEnabledByDefault(false);
-
- private Set<String> mDeclarations;
- private Set<String> mReferences;
- private Map<String, Location> mUnused;
-
- /**
- * Constructs a new {@link UnusedResourceDetector}
- */
- public UnusedResourceDetector() {
- }
-
- @Override
- public void run(@NonNull Context context) {
- assert false;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- return true;
- }
-
- @Override
- public void beforeCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mDeclarations = new HashSet<String>(300);
- mReferences = new HashSet<String>(300);
- }
- }
-
- // ---- Implements JavaScanner ----
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- File file = context.file;
-
- String fileName = file.getName();
- boolean isXmlFile = endsWith(fileName, DOT_XML);
- if (isXmlFile
- || endsWith(fileName, DOT_PNG)
- || endsWith(fileName, DOT_JPG)
- || endsWith(fileName, DOT_GIF)) {
- String parentName = file.getParentFile().getName();
- int dash = parentName.indexOf('-');
- String typeName = parentName.substring(0, dash == -1 ? parentName.length() : dash);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type != null && LintUtils.isFileBasedResourceType(type)) {
- String baseName = fileName.substring(0, fileName.length() - DOT_XML.length());
- String resource = R_PREFIX + typeName + '.' + baseName;
- if (context.getPhase() == 1) {
- mDeclarations.add(resource);
- } else {
- assert context.getPhase() == 2;
- if (mUnused.containsKey(resource)) {
- // Check whether this is an XML document that has a tools:ignore attribute
- // on the document element: if so don't record it as a declaration.
- if (isXmlFile && context instanceof XmlContext) {
- XmlContext xmlContext = (XmlContext) context;
- if (xmlContext.document != null
- && xmlContext.document.getDocumentElement() != null) {
- Element root = xmlContext.document.getDocumentElement();
- if (xmlContext.getDriver().isSuppressed(ISSUE, root)) {
- // Also remove it from consideration such that even the
- // presence of this field in the R file is ignored.
- mUnused.remove(resource);
- return;
- }
- }
- }
-
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- mUnused.remove(resource);
- return;
- }
-
- recordLocation(resource, Location.create(file));
- }
- }
- }
- }
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (context.getPhase() == 1) {
- mDeclarations.removeAll(mReferences);
- Set<String> unused = mDeclarations;
- mReferences = null;
- mDeclarations = null;
-
- // Remove styles and attributes: they may be used, analysis isn't complete for these
- List<String> styles = new ArrayList<String>();
- for (String resource : unused) {
- // R.style.x, R.styleable.x, R.attr
- if (resource.startsWith("R.style") //$NON-NLS-1$
- || resource.startsWith("R.attr")) { //$NON-NLS-1$
- styles.add(resource);
- }
- }
- unused.removeAll(styles);
-
- // Remove id's if the user has disabled reporting issue ids
- if (!unused.isEmpty() && !context.isEnabled(ISSUE_IDS)) {
- // Remove all R.id references
- List<String> ids = new ArrayList<String>();
- for (String resource : unused) {
- if (resource.startsWith(R_ID_PREFIX)) {
- ids.add(resource);
- }
- }
- unused.removeAll(ids);
- }
-
- if (!unused.isEmpty() && !context.getDriver().hasParserErrors()) {
- mUnused = new HashMap<String, Location>(unused.size());
- for (String resource : unused) {
- mUnused.put(resource, null);
- }
-
- // Request another pass, and in the second pass we'll gather location
- // information for all declaration locations we've found
- context.requestRepeat(this, Scope.ALL_RESOURCES_SCOPE);
- }
- } else {
- assert context.getPhase() == 2;
-
- // Report any resources that we (for some reason) could not find a declaration
- // location for
- if (!mUnused.isEmpty()) {
- // Fill in locations for files that we didn't encounter in other ways
- for (Map.Entry<String, Location> entry : mUnused.entrySet()) {
- String resource = entry.getKey();
- Location location = entry.getValue();
- if (location != null) {
- continue;
- }
-
- // Try to figure out the file if it's a file based resource (such as R.layout) --
- // in that case we can figure out the filename since it has a simple mapping
- // from the resource name (though the presence of qualifiers like -land etc
- // makes it a little tricky if there's no base file provided)
- int secondDot = resource.indexOf('.', 2);
- String typeName = resource.substring(2, secondDot); // 2: Skip R.
- ResourceType type = ResourceType.getEnum(typeName);
- if (type != null && LintUtils.isFileBasedResourceType(type)) {
- String name = resource.substring(secondDot + 1);
-
- List<File> folders = Lists.newArrayList();
- List<File> resourceFolders = context.getProject().getResourceFolders();
- for (File res : resourceFolders) {
- File[] f = res.listFiles();
- if (f != null) {
- folders.addAll(Arrays.asList(f));
- }
- }
- if (folders != null) {
- // Process folders in alphabetical order such that we process
- // based folders first: we want the locations in base folder
- // order
- Collections.sort(folders, new Comparator<File>() {
- @Override
- public int compare(File file1, File file2) {
- return file1.getName().compareTo(file2.getName());
- }
- });
- for (File folder : folders) {
- if (folder.getName().startsWith(typeName)) {
- File[] files = folder.listFiles();
- if (files != null) {
- for (File file : files) {
- String fileName = file.getName();
- if (fileName.startsWith(name)
- && fileName.startsWith(".", //$NON-NLS-1$
- name.length())) {
- recordLocation(resource, Location.create(file));
- }
- }
- }
- }
- }
- }
- }
- }
-
- List<String> sorted = new ArrayList<String>(mUnused.keySet());
- Collections.sort(sorted);
-
- Boolean skippedLibraries = null;
-
- for (String resource : sorted) {
- Location location = mUnused.get(resource);
- if (location != null) {
- // We were prepending locations, but we want to prefer the base folders
- location = Location.reverse(location);
- }
-
- if (location == null) {
- if (skippedLibraries == null) {
- skippedLibraries = false;
- for (Project project : context.getDriver().getProjects()) {
- if (!project.getReportIssues()) {
- skippedLibraries = true;
- break;
- }
- }
- }
- if (skippedLibraries) {
- // Skip this resource if we don't have a location, and one or
- // more library projects were skipped; the resource was very
- // probably defined in that library project and only encountered
- // in the main project's java R file
- continue;
- }
- }
-
- String message = String.format("The resource %1$s appears to be unused",
- resource);
- Issue issue = getIssue(resource);
- // TODO: Compute applicable node scope
- context.report(issue, location, message, resource);
- }
- }
- }
- }
-
- private static Issue getIssue(String resource) {
- return resource.startsWith(R_ID_PREFIX) ? ISSUE_IDS : ISSUE;
- }
-
- private void recordLocation(String resource, Location location) {
- Location oldLocation = mUnused.get(resource);
- if (oldLocation != null) {
- location.setSecondary(oldLocation);
- }
- mUnused.put(resource, location);
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return ALL;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- TAG_STYLE,
- TAG_RESOURCES,
- TAG_ARRAY,
- TAG_STRING_ARRAY,
- TAG_PLURALS
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (TAG_RESOURCES.equals(element.getTagName())) {
- for (Element item : LintUtils.getChildren(element)) {
- Attr nameAttribute = item.getAttributeNode(ATTR_NAME);
- if (nameAttribute != null) {
- String name = nameAttribute.getValue();
- if (name.indexOf('.') != -1) {
- name = name.replace('.', '_');
- }
- String type = item.getTagName();
- if (type.equals(TAG_ITEM)) {
- type = RESOURCE_CLZ_ID;
- } else if (type.equals("declare-styleable")) { //$NON-NLS-1$
- type = RESOURCE_CLR_STYLEABLE;
- } else if (type.contains("array")) { //$NON-NLS-1$
- // <string-array> etc
- type = RESOURCE_CLZ_ARRAY;
- }
- String resource = R_PREFIX + type + '.' + name;
-
- if (context.getPhase() == 1) {
- mDeclarations.add(resource);
- checkChildRefs(item);
- } else {
- assert context.getPhase() == 2;
- if (mUnused.containsKey(resource)) {
- if (context.getDriver().isSuppressed(getIssue(resource), item)) {
- mUnused.remove(resource);
- continue;
- }
- if (!context.getProject().getReportIssues()) {
- mUnused.remove(resource);
- continue;
- }
- if (isAnalyticsFile(context)) {
- mUnused.remove(resource);
- continue;
- }
-
- recordLocation(resource, context.getLocation(nameAttribute));
- }
- }
- }
- }
- } else if (mReferences != null) {
- assert TAG_STYLE.equals(element.getTagName())
- || TAG_ARRAY.equals(element.getTagName())
- || TAG_PLURALS.equals(element.getTagName())
- || TAG_STRING_ARRAY.equals(element.getTagName());
- for (Element item : LintUtils.getChildren(element)) {
- checkChildRefs(item);
- }
- }
- }
-
- private static final String ANALYTICS_FILE = "analytics.xml"; //$NON-NLS-1$
-
- /**
- * Returns true if this XML file corresponds to an Analytics configuration file;
- * these contain some attributes read by the library which won't be flagged as
- * used by the application
- *
- * @param context the context used for scanning
- * @return true if the file represents an analytics file
- */
- public static boolean isAnalyticsFile(Context context) {
- File file = context.file;
- return file.getPath().endsWith(ANALYTICS_FILE) && file.getName().equals(ANALYTICS_FILE);
- }
-
- private void checkChildRefs(Element item) {
- // Look for ?attr/ and @dimen/foo etc references in the item children
- NodeList childNodes = item.getChildNodes();
- for (int i = 0, n = childNodes.getLength(); i < n; i++) {
- Node child = childNodes.item(i);
- if (child.getNodeType() == Node.TEXT_NODE) {
- String text = child.getNodeValue();
-
- int index = text.indexOf(ATTR_REF_PREFIX);
- if (index != -1) {
- String name = text.substring(index + ATTR_REF_PREFIX.length()).trim();
- mReferences.add(R_ATTR_PREFIX + name);
- } else {
- index = text.indexOf('@');
- if (index != -1 && text.indexOf('/', index) != -1
- && !text.startsWith("@android:", index)) { //$NON-NLS-1$
- // Compute R-string, e.g. @string/foo => R.string.foo
- String token = text.substring(index + 1).trim().replace('/', '.');
- String r = R_PREFIX + token;
- mReferences.add(r);
- }
- }
- }
- }
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String value = attribute.getValue();
-
- if (value.startsWith("@+") && !value.startsWith("@+android")) { //$NON-NLS-1$ //$NON-NLS-2$
- String resource = R_PREFIX + value.substring(2).replace('/', '.');
- // We already have the declarations when we scan the R file, but we're tracking
- // these here to get attributes for position info
-
- if (context.getPhase() == 1) {
- mDeclarations.add(resource);
- } else if (mUnused.containsKey(resource)) {
- if (context.getDriver().isSuppressed(getIssue(resource), attribute)) {
- mUnused.remove(resource);
- return;
- }
- if (!context.getProject().getReportIssues()) {
- mUnused.remove(resource);
- return;
- }
- recordLocation(resource, context.getLocation(attribute));
- return;
- }
- } else if (mReferences != null) {
- if (value.startsWith("@") //$NON-NLS-1$
- && !value.startsWith("@android:")) { //$NON-NLS-1$
- // Compute R-string, e.g. @string/foo => R.string.foo
- String r = R_PREFIX + value.substring(1).replace('/', '.');
- mReferences.add(r);
- } else if (value.startsWith(ATTR_REF_PREFIX)) {
- mReferences.add(R_ATTR_PREFIX + value.substring(ATTR_REF_PREFIX.length()));
- }
- }
-
- if (attribute.getNamespaceURI() != null
- && !ANDROID_URI.equals(attribute.getNamespaceURI()) && mReferences != null) {
- mReferences.add(R_ATTR_PREFIX + attribute.getLocalName());
- }
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends lombok.ast.Node>>singletonList(ClassDeclaration.class);
- }
-
- @Override
- public boolean appliesToResourceRefs() {
- return true;
- }
-
- @Override
- public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull lombok.ast.Node node, @NonNull String type, @NonNull String name,
- boolean isFramework) {
- if (mReferences != null && !isFramework) {
- String reference = R_PREFIX + type + '.' + name;
- mReferences.add(reference);
- }
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- if (mReferences != null) {
- return new UnusedResourceVisitor();
- } else {
- // Second pass, computing resource declaration locations: No need to look at Java
- return null;
- }
- }
-
- // Look for references and declarations
- private class UnusedResourceVisitor extends ForwardingAstVisitor {
- @Override
- public boolean visitClassDeclaration(ClassDeclaration node) {
- // Look for declarations of R class fields and store them in
- // mDeclarations
- String description = node.getDescription();
- if (description.equals(R_CLASS)) {
- // This is an R class. We can process this class very deliberately.
- // The R class has a very specific AST format:
- // ClassDeclaration ("R")
- // NormalTypeBody
- // ClassDeclaration (e.g. "drawable")
- // NormalTypeBody
- // VariableDeclaration
- // VariableDefinition (e.g. "ic_launcher")
- for (lombok.ast.Node body : node.getChildren()) {
- if (body instanceof NormalTypeBody) {
- for (lombok.ast.Node subclass : body.getChildren()) {
- if (subclass instanceof ClassDeclaration) {
- String className = ((ClassDeclaration) subclass).getDescription();
- for (lombok.ast.Node innerBody : subclass.getChildren()) {
- if (innerBody instanceof NormalTypeBody) {
- for (lombok.ast.Node field : innerBody.getChildren()) {
- if (field instanceof VariableDeclaration) {
- for (lombok.ast.Node child : field.getChildren()) {
- if (child instanceof VariableDefinition) {
- VariableDefinition def =
- (VariableDefinition) child;
- String name = def.astVariables().first()
- .astName().astValue();
- String resource = R_PREFIX + className
- + '.' + name;
- mDeclarations.add(resource);
- } // Else: It could be a comment node
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
deleted file mode 100644
index db5de4d..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UseCompoundDrawableDetector.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BACKGROUND;
-import static com.android.SdkConstants.ATTR_LAYOUT_WEIGHT;
-import static com.android.SdkConstants.ATTR_SCALE_TYPE;
-import static com.android.SdkConstants.IMAGE_VIEW;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.TEXT_VIEW;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Checks whether the current node can be replaced by a TextView using compound
- * drawables.
- */
-public class UseCompoundDrawableDetector extends LayoutDetector {
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "UseCompoundDrawables", //$NON-NLS-1$
- "Checks whether the current node can be replaced by a TextView using compound drawables.",
- "A `LinearLayout` which contains an `ImageView` and a `TextView` can be more " +
- "efficiently handled as a compound drawable (a single TextView, using the " +
- "`drawableTop`, `drawableLeft`, `drawableRight` and/or `drawableBottom` attributes " +
- "to draw one or more images adjacent to the text).\n" +
- "\n" +
- "If the two widgets are offset from each other with " +
- "margins, this can be replaced with a `drawablePadding` attribute.\n" +
- "\n" +
- "There's a lint quickfix to perform this conversion in the Eclipse plugin.",
- Category.PERFORMANCE,
- 6,
- Severity.WARNING,
- UseCompoundDrawableDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link UseCompoundDrawableDetector} */
- public UseCompoundDrawableDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Collections.singletonList(
- LINEAR_LAYOUT
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- int childCount = LintUtils.getChildCount(element);
- if (childCount == 2) {
- List<Element> children = LintUtils.getChildren(element);
- Element first = children.get(0);
- Element second = children.get(1);
- if ((first.getTagName().equals(IMAGE_VIEW) &&
- second.getTagName().equals(TEXT_VIEW) &&
- !first.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) ||
- ((second.getTagName().equals(IMAGE_VIEW) &&
- first.getTagName().equals(TEXT_VIEW) &&
- !second.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)))) {
- // If the layout has a background, ignore since it would disappear from
- // the TextView
- if (element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)) {
- return;
- }
-
- // Certain scale types cannot be done with compound drawables
- String scaleType = first.getTagName().equals(IMAGE_VIEW)
- ? first.getAttributeNS(ANDROID_URI, ATTR_SCALE_TYPE)
- : second.getAttributeNS(ANDROID_URI, ATTR_SCALE_TYPE);
- if (scaleType != null && !scaleType.isEmpty()) {
- // For now, ignore if any scale type is set
- return;
- }
-
- context.report(ISSUE, element, context.getLocation(element),
- "This tag and its children can be replaced by one <TextView/> and " +
- "a compound drawable", null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
deleted file mode 100644
index 867c3c4..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UselessViewDetector.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ABSOLUTE_LAYOUT;
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_BACKGROUND;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.SdkConstants.FRAME_LAYOUT;
-import static com.android.SdkConstants.GRID_LAYOUT;
-import static com.android.SdkConstants.GRID_VIEW;
-import static com.android.SdkConstants.HORIZONTAL_SCROLL_VIEW;
-import static com.android.SdkConstants.LINEAR_LAYOUT;
-import static com.android.SdkConstants.VIEW_MERGE;
-import static com.android.SdkConstants.RADIO_GROUP;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.SCROLL_VIEW;
-import static com.android.SdkConstants.TABLE_LAYOUT;
-import static com.android.SdkConstants.TABLE_ROW;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Checks whether the current node can be removed without affecting the layout.
- */
-public class UselessViewDetector extends LayoutDetector {
- /** Issue of including a parent that has no value on its own */
- public static final Issue USELESS_PARENT = Issue.create(
- "UselessParent", //$NON-NLS-1$
- "Checks whether a parent layout can be removed.",
- "A layout with children that has no siblings, is not a scrollview or " +
- "a root layout, and does not have a background, can be removed and have " +
- "its children moved directly into the parent for a flatter and more " +
- "efficient layout hierarchy.",
- Category.PERFORMANCE,
- 2,
- Severity.WARNING,
- UselessViewDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Issue of including a leaf that isn't shown */
- public static final Issue USELESS_LEAF = Issue.create(
- "UselessLeaf", //$NON-NLS-1$
- "Checks whether a leaf layout can be removed.",
- "A layout that has no children or no background can often be removed (since it " +
- "is invisible) for a flatter and more efficient layout hierarchy.",
- Category.PERFORMANCE,
- 2,
- Severity.WARNING,
- UselessViewDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link UselessViewDetector} */
- public UselessViewDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- private static final List<String> CONTAINERS = new ArrayList<String>(18);
- static {
- CONTAINERS.add(ABSOLUTE_LAYOUT);
- CONTAINERS.add(FRAME_LAYOUT);
- CONTAINERS.add(GRID_LAYOUT);
- CONTAINERS.add(GRID_VIEW);
- CONTAINERS.add(HORIZONTAL_SCROLL_VIEW);
- CONTAINERS.add("ImageSwitcher"); //$NON-NLS-1$
- CONTAINERS.add(LINEAR_LAYOUT);
- CONTAINERS.add(RADIO_GROUP);
- CONTAINERS.add(RELATIVE_LAYOUT);
- CONTAINERS.add(SCROLL_VIEW);
- CONTAINERS.add("SlidingDrawer"); //$NON-NLS-1$
- CONTAINERS.add("StackView"); //$NON-NLS-1$
- CONTAINERS.add(TABLE_LAYOUT);
- CONTAINERS.add(TABLE_ROW);
- CONTAINERS.add("TextSwitcher"); //$NON-NLS-1$
- CONTAINERS.add("ViewAnimator"); //$NON-NLS-1$
- CONTAINERS.add("ViewFlipper"); //$NON-NLS-1$
- CONTAINERS.add("ViewSwitcher"); //$NON-NLS-1$
- // Available ViewGroups that are not included by this check:
- // CONTAINERS.add("android.gesture.GestureOverlayView");
- // CONTAINERS.add("AdapterViewFlipper");
- // CONTAINERS.add("DialerFilter");
- // CONTAINERS.add("ExpandableListView");
- // CONTAINERS.add("ListView");
- // CONTAINERS.add("MediaController");
- // CONTAINERS.add("merge");
- // CONTAINERS.add("SearchView");
- // CONTAINERS.add("TabWidget");
- // CONTAINERS.add("TabHost");
- }
- @Override
- public Collection<String> getApplicableElements() {
- return CONTAINERS;
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- int childCount = LintUtils.getChildCount(element);
- if (childCount == 0) {
- // Check to see if this is a leaf layout that can be removed
- checkUselessLeaf(context, element);
- } else {
- // Check to see if this is a middle-man layout which can be removed
- checkUselessMiddleLayout(context, element);
- }
- }
-
- // This is the old UselessLayoutCheck from layoutopt
- private static void checkUselessMiddleLayout(XmlContext context, Element element) {
- // Conditions:
- // - The node has children
- // - The node does not have siblings
- // - The node's parent is not a scroll view (horizontal or vertical)
- // - The node does not have a background or its parent does not have a
- // background or neither the node and its parent have a background
- // - The parent is not a <merge/>
-
- Node parentNode = element.getParentNode();
- if (parentNode.getNodeType() != Node.ELEMENT_NODE) {
- // Can't remove root
- return;
- }
-
- Element parent = (Element) parentNode;
- String parentTag = parent.getTagName();
- if (parentTag.equals(SCROLL_VIEW) || parentTag.equals(HORIZONTAL_SCROLL_VIEW) ||
- parentTag.equals(VIEW_MERGE)) {
- // Can't remove if the parent is a scroll view or a merge
- return;
- }
-
- // This method is only called when we've already ensured that it has children
- assert LintUtils.getChildCount(element) > 0;
-
- int parentChildCount = LintUtils.getChildCount(parent);
- if (parentChildCount != 1) {
- // Don't remove if the node has siblings
- return;
- }
-
- // - A parent can be removed if it doesn't have a background
- // - A parent can be removed if has a background *and* the child does not have a
- // background (in which case, just move the background over to the child, remove
- // the parent)
- // - If both child and parent have a background, the parent cannot be removed (a
- // background can be translucent, have transparent padding, etc.)
- boolean nodeHasBackground = element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
- boolean parentHasBackground = parent.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND);
- if (nodeHasBackground && parentHasBackground) {
- // Can't remove because both define a background, and they might both be
- // visible (e.g. through transparency or padding).
- return;
- }
-
- // Certain parents are special - such as the TabHost and the GestureOverlayView -
- // where we want to leave things alone.
- if (!CONTAINERS.contains(parentTag)) {
- return;
- }
-
- boolean hasId = element.hasAttributeNS(ANDROID_URI, ATTR_ID);
- Location location = context.getLocation(element);
- String tag = element.getTagName();
- String format;
- if (hasId) {
- format = "This %1$s layout or its %2$s parent is possibly useless";
- } else {
- format = "This %1$s layout or its %2$s parent is useless";
- }
- if (nodeHasBackground || parentHasBackground) {
- format += "; transfer the background attribute to the other view";
- }
- String message = String.format(format, tag, parentTag);
- context.report(USELESS_PARENT, element, location, message, null);
- }
-
- // This is the old UselessView check from layoutopt
- private static void checkUselessLeaf(XmlContext context, Element element) {
- assert LintUtils.getChildCount(element) == 0;
-
- // Conditions:
- // - The node is a container view (LinearLayout, etc.)
- // - The node has no id
- // - The node has no background
- // - The node has no children
- // - The node has no style
- // - The node is not a root
-
- if (element.hasAttributeNS(ANDROID_URI, ATTR_ID)) {
- return;
- }
-
- if (element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)) {
- return;
- }
-
- if (element.hasAttribute(ATTR_STYLE)) {
- return;
- }
-
- if (element == context.document.getDocumentElement()) {
- return;
- }
-
- Location location = context.getLocation(element);
- String tag = element.getTagName();
- String message = String.format(
- "This %1$s view is useless (no children, no background, no id, no style)", tag);
- context.report(USELESS_LEAF, element, location, message, null);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
deleted file mode 100644
index 2e23483..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/Utf8Detector.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.Location;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Document;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Checks that the encoding used in resource files is always UTF-8.
- */
-public class Utf8Detector extends LayoutDetector {
- /** Detects non-utf8 encodings */
- public static final Issue ISSUE = Issue.create(
- "EnforceUTF8", //$NON-NLS-1$
- "Checks that all XML resource files are using UTF-8 as the file encoding",
- "XML supports encoding in a wide variety of character sets. However, not all " +
- "tools handle the XML encoding attribute correctly, and nearly all Android " +
- "apps use UTF-8, so by using UTF-8 you can protect yourself against subtle " +
- "bugs when using non-ASCII characters.",
- Category.I18N,
- 2,
- Severity.WARNING,
- Utf8Detector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** See http://www.w3.org/TR/REC-xml/#NT-EncodingDecl */
- private static final Pattern ENCODING_PATTERN =
- Pattern.compile("encoding=['\"](\\S*)['\"]");//$NON-NLS-1$
-
- /** Constructs a new {@link Utf8Detector} */
- public Utf8Detector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- String xml = context.getContents();
- if (xml == null) {
- return;
- }
-
- // AAPT: The prologue must be in the first line
- int lineEnd = 0;
- int max = xml.length();
- for (; lineEnd < max; lineEnd++) {
- char c = xml.charAt(lineEnd);
- if (c == '\n' || c == '\r') {
- break;
- }
- }
-
- for (int i = 16; i < lineEnd - 5; i++) { // +4: Skip at least <?xml encoding="
- if ((xml.charAt(i) == 'u' || xml.charAt(i) == 'U')
- && (xml.charAt(i + 1) == 't' || xml.charAt(i + 1) == 'T')
- && (xml.charAt(i + 2) == 'f' || xml.charAt(i + 2) == 'F')
- && (xml.charAt(i + 3) == '-' || xml.charAt(i + 3) == '_')
- && (xml.charAt(i + 4) == '8')) {
- return;
- }
- }
-
- int encodingIndex = xml.lastIndexOf("encoding", lineEnd); //$NON-NLS-1$
- if (encodingIndex != -1) {
- Matcher matcher = ENCODING_PATTERN.matcher(xml);
- if (matcher.find(encodingIndex)) {
- String encoding = matcher.group(1);
- Location location = Location.create(context.file, xml,
- matcher.start(1), matcher.end(1));
- context.report(ISSUE, null, location, String.format(
- "%1$s: Not using UTF-8 as the file encoding. This can lead to subtle " +
- "bugs with non-ascii characters", encoding), null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
deleted file mode 100644
index 4bffdd7..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.CONSTRUCTOR_NAME;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Looks for custom views that do not define the view constructors needed by UI builders
- */
-public class ViewConstructorDetector extends Detector implements Detector.ClassScanner {
- private static final String SIG1 =
- "(Landroid/content/Context;)V"; //$NON-NLS-1$
- private static final String SIG2 =
- "(Landroid/content/Context;Landroid/util/AttributeSet;)V"; //$NON-NLS-1$
- private static final String SIG3 =
- "(Landroid/content/Context;Landroid/util/AttributeSet;I)V"; //$NON-NLS-1$
-
- /** The main issue discovered by this detector */
- public static final Issue ISSUE = Issue.create(
- "ViewConstructor", //$NON-NLS-1$
- "Checks that custom views define the expected constructors",
-
- "Some layout tools (such as the Android layout editor for Eclipse) needs to " +
- "find a constructor with one of the following signatures:\n" +
- "* `View(Context context)`\n" +
- "* `View(Context context, AttributeSet attrs)`\n" +
- "* `View(Context context, AttributeSet attrs, int defStyle)`\n" +
- "\n" +
- "If your custom view needs to perform initialization which does not apply when " +
- "used in a layout editor, you can surround the given code with a check to " +
- "see if `View#isInEditMode()` is false, since that method will return `false` " +
- "at runtime but true within a user interface editor.",
-
- Category.USABILITY,
- 3,
- Severity.WARNING,
- ViewConstructorDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- /** Constructs a new {@link ViewConstructorDetector} check */
- public ViewConstructorDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
- if (classNode.name.indexOf('$') != -1
- && (classNode.access & Opcodes.ACC_STATIC) == 0) {
- // Ignore inner classes that aren't static: we can't create these
- // anyway since we'd need the outer instance
- return;
- }
-
- // Ignore abstract classes
- if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) {
- return;
- }
-
- if (isViewClass(context, classNode)) {
- checkConstructors(context, classNode);
- }
- }
-
- private static boolean isViewClass(ClassContext context, ClassNode node) {
- String superName = node.superName;
- while (superName != null) {
- if (superName.equals("android/view/View") //$NON-NLS-1$
- || superName.equals("android/view/ViewGroup") //$NON-NLS-1$
- || superName.startsWith("android/widget/") //$NON-NLS-1$
- && !((superName.endsWith("Adapter") //$NON-NLS-1$
- || superName.endsWith("Controller") //$NON-NLS-1$
- || superName.endsWith("Service") //$NON-NLS-1$
- || superName.endsWith("Provider") //$NON-NLS-1$
- || superName.endsWith("Filter")))) { //$NON-NLS-1$
- return true;
- }
-
- superName = context.getDriver().getSuperClass(superName);
- }
-
- return false;
- }
-
- private static void checkConstructors(ClassContext context, ClassNode classNode) {
- // Look through constructors
- @SuppressWarnings("rawtypes")
- List methods = classNode.methods;
- for (Object methodObject : methods) {
- MethodNode method = (MethodNode) methodObject;
- if (method.name.equals(CONSTRUCTOR_NAME)) {
- String desc = method.desc;
- if (desc.equals(SIG1) || desc.equals(SIG2) || desc.equals(SIG3)) {
- return;
- }
- }
- }
-
- // If we get this far, none of the expected constructors were found.
-
- // Use location of one of the constructors?
- String message = String.format(
- "Custom view %1$s is missing constructor used by tools: " +
- "(Context) or (Context,AttributeSet) or (Context,AttributeSet,int)",
- classNode.name);
- File sourceFile = context.getSourceFile();
- Location location = Location.create(sourceFile != null
- ? sourceFile : context.file);
- context.report(ISSUE, location, message, null /*data*/);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
deleted file mode 100644
index 46e24cc..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTagDetector.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.SdkInfo;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Location;
-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 org.objectweb.asm.Type;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.Analyzer;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-import org.objectweb.asm.tree.analysis.BasicInterpreter;
-import org.objectweb.asm.tree.analysis.BasicValue;
-import org.objectweb.asm.tree.analysis.Frame;
-
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-
-/**
- * Checks for missing view tag detectors
- */
-public class ViewTagDetector extends Detector implements ClassScanner {
- /** Using setTag and leaking memory */
- public static final Issue ISSUE = Issue.create(
- "ViewTag", //$NON-NLS-1$
- "Finds potential leaks when using View.setTag",
-
- "Prior to Android 4.0, the implementation of View.setTag(int, Object) would " +
- "store the objects in a static map, where the values were strongly referenced. " +
- "This means that if the object contains any references pointing back to the " +
- "context, the context (which points to pretty much everything else) will leak. " +
- "If you pass a view, the view provides a reference to the context " +
- "that created it. Similarly, view holders typically contain a view, and cursors " +
- "are sometimes also associated with views.",
-
- Category.PERFORMANCE,
- 6,
- Severity.WARNING,
- ViewTagDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.CLASS_FILE));
-
- /** Constructs a new {@link ViewTagDetector} */
- public ViewTagDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Collections.singletonList("setTag"); //$NON-NLS-1$
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- // The leak behavior is fixed in ICS:
- // http://code.google.com/p/android/issues/detail?id=18273
- if (context.getMainProject().getMinSdk() >= 14) {
- return;
- }
-
- String owner = call.owner;
- String desc = call.desc;
- if (owner.equals("android/view/View") //$NON-NLS-1$
- && desc.equals("(ILjava/lang/Object;)V")) { //$NON-NLS-1$
- Analyzer analyzer = new Analyzer(new BasicInterpreter() {
- @Override
- public BasicValue newValue(Type type) {
- if (type == null) {
- return BasicValue.UNINITIALIZED_VALUE;
- } else if (type.getSort() == Type.VOID) {
- return null;
- } else {
- return new BasicValue(type);
- }
- }
- });
- try {
- Frame[] frames = analyzer.analyze(classNode.name, method);
- InsnList instructions = method.instructions;
- Frame frame = frames[instructions.indexOf(call)];
- if (frame.getStackSize() < 3) {
- return;
- }
- BasicValue stackValue = (BasicValue) frame.getStack(2);
- Type type = stackValue.getType();
- if (type == null) {
- return;
- }
-
- String internalName = type.getInternalName();
- String className = type.getClassName();
- LintDriver driver = context.getDriver();
-
- SdkInfo sdkInfo = context.getClient().getSdkInfo(context.getMainProject());
- String objectType = null;
- while (className != null) {
- if (className.equals("android.view.View")) { //$NON-NLS-1$
- objectType = "views";
- break;
- } else if (className.endsWith("ViewHolder")) { //$NON-NLS-1$
- objectType = "view holders";
- break;
- } else if (className.endsWith("Cursor") //$NON-NLS-1$
- && className.startsWith("android.")) { //$NON-NLS-1$
- objectType = "cursors";
- break;
- }
-
- // TBD: Bitmaps, drawables? That's tricky, because as explained in
- // http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
- // apparently these are used along with nulling out the callbacks,
- // and that's harder to detect here
-
- String parent = sdkInfo.getParentViewClass(className);
- if (parent == null) {
- if (internalName == null) {
- internalName = className.replace('.', '/');
- }
- assert internalName != null;
- parent = driver.getSuperClass(internalName);
- }
- className = parent;
- internalName = null;
- }
-
- if (objectType != null) {
- Location location = context.getLocation(call);
- String message = String.format("Avoid setting %1$s as values for setTag: " +
- "Can lead to memory leaks in versions older than Android 4.0",
- objectType);
- context.report(ISSUE, method, call, location, message, null);
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
deleted file mode 100644
index bad5d50..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewTypeDetector.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ATTR_CLASS;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.DOT_JAVA;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.SdkConstants.VIEW_TAG;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.ResourceXmlDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.google.common.base.Joiner;
-
-import org.w3c.dom.Attr;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.Cast;
-import lombok.ast.Expression;
-import lombok.ast.MethodInvocation;
-import lombok.ast.Select;
-import lombok.ast.StrictListAccessor;
-
-/** Detector for finding inconsistent usage of views and casts */
-public class ViewTypeDetector extends ResourceXmlDetector implements Detector.JavaScanner {
- /** Mismatched view types */
- public static final Issue ISSUE = Issue.create("WrongViewCast", //$NON-NLS-1$
- "Looks for incorrect casts to views that according to the XML are of a different type",
- "Keeps track of the view types associated with ids and if it finds a usage of " +
- "the id in the Java code it ensures that it is treated as the same type.",
- Category.CORRECTNESS,
- 9,
- Severity.ERROR,
- ViewTypeDetector.class,
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.ALL_JAVA_FILES));
-
- private final Map<String, Object> mIdToViewTag = new HashMap<String, Object>(50);
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.SLOW;
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT;
- }
-
- @Override
- public boolean appliesTo(@NonNull Context context, @NonNull File file) {
- if (LintUtils.endsWith(file.getName(), DOT_JAVA)) {
- return true;
- }
-
- return super.appliesTo(context, file);
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_ID);
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- String view = attribute.getOwnerElement().getTagName();
- String value = attribute.getValue();
- String id = null;
- if (value.startsWith(ID_PREFIX)) {
- id = value.substring(ID_PREFIX.length());
- } else if (value.startsWith(NEW_ID_PREFIX)) {
- id = value.substring(NEW_ID_PREFIX.length());
- } // else: could be @android id
-
- if (id != null) {
- if (view.equals(VIEW_TAG)) {
- view = attribute.getOwnerElement().getAttribute(ATTR_CLASS);
- }
-
- Object existing = mIdToViewTag.get(id);
- if (existing == null) {
- mIdToViewTag.put(id, view);
- } else if (existing instanceof String) {
- String existingString = (String) existing;
- if (!existingString.equals(view)) {
- // Convert to list
- List<String> list = new ArrayList<String>(2);
- list.add((String) existing);
- list.add(view);
- mIdToViewTag.put(id, list);
- }
- } else if (existing instanceof List<?>) {
- @SuppressWarnings("unchecked")
- List<String> list = (List<String>) existing;
- if (!list.contains(view)) {
- list.add(view);
- }
- }
- }
- }
-
- // ---- Implements Detector.JavaScanner ----
-
- @Override
- public List<String> getApplicableMethodNames() {
- return Collections.singletonList("findViewById"); //$NON-NLS-1$
- }
-
- @Override
- public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
- @NonNull MethodInvocation node) {
- assert node.astName().getDescription().equals("findViewById");
- if (node.getParent() instanceof Cast) {
- Cast cast = (Cast) node.getParent();
- String castType = cast.astTypeReference().getTypeName();
- StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
- if (args.size() == 1) {
- Expression first = args.first();
- // TODO: Do flow analysis as in the StringFormatDetector in order
- // to handle variable references too
- if (first instanceof Select) {
- String resource = first.toString();
- if (resource.startsWith("R.id.")) { //$NON-NLS-1$
- String id = ((Select) first).astIdentifier().astValue();
- Object types = mIdToViewTag.get(id);
- if (types instanceof String) {
- String layoutType = (String) types;
- checkCompatible(context, castType, layoutType, null, cast);
- } else if (types instanceof List<?>) {
- @SuppressWarnings("unchecked")
- List<String> layoutTypes = (List<String>) types;
- checkCompatible(context, castType, null, layoutTypes, cast);
- }
- }
- }
- }
- }
- }
-
- /** Check if the view and cast type are compatible */
- private static void checkCompatible(JavaContext context, String castType, String layoutType,
- List<String> layoutTypes, Cast node) {
- assert layoutType == null || layoutTypes == null; // Should only specify one or the other
- boolean compatible = true;
- if (layoutType != null) {
- if (!layoutType.equals(castType)
- && !context.getSdkInfo().isSubViewOf(castType, layoutType)) {
- compatible = false;
- }
- } else {
- compatible = false;
- assert layoutTypes != null;
- for (String type : layoutTypes) {
- if (type.equals(castType)
- || context.getSdkInfo().isSubViewOf(castType, type)) {
- compatible = true;
- break;
- }
- }
- }
-
- if (!compatible) {
- if (layoutType == null) {
- layoutType = Joiner.on("|").join(layoutTypes);
- }
- String message = String.format(
- "Unexpected cast to %1$s: layout tag was %2$s",
- castType, layoutType);
- context.report(ISSUE, node, context.parser.getLocation(context, node), message,
- null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
deleted file mode 100644
index 47410eb..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_APP_ACTIVITY;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.checks.ControlFlowGraph.Node;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Scope;
-import com.android.tools.lint.detector.api.Severity;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.JumpInsnNode;
-import org.objectweb.asm.tree.LdcInsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.analysis.AnalyzerException;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Checks for problems with wakelocks (such as failing to release them)
- * which can lead to unnecessary battery usage.
- */
-public class WakelockDetector extends Detector implements ClassScanner {
-
- /** Problems using wakelocks */
- public static final Issue ISSUE = Issue.create(
- "Wakelock", //$NON-NLS-1$
- "Looks for problems with wakelock usage",
-
- "Failing to release a wakelock properly can keep the Android device in " +
- "a high power mode, which reduces battery life. There are several causes " +
- "of this, such as releasing the wake lock in `onDestroy()` instead of in " +
- "`onPause()`, failing to call `release()` in all possible code paths after " +
- "an `acquire()`, and so on.\n" +
- "\n" +
- "NOTE: If you are using the lock just to keep the screen on, you should " +
- "strongly consider using `FLAG_KEEP_SCREEN_ON` instead. This window flag " +
- "will be correctly managed by the platform as the user moves between " +
- "applications and doesn't require a special permission. See " +
- "http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON.",
-
- Category.PERFORMANCE,
- 9,
- Severity.WARNING,
- WakelockDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- private static final String WAKELOCK_OWNER = "android/os/PowerManager$WakeLock"; //$NON-NLS-1$
- private static final String RELEASE_METHOD = "release"; //$NON-NLS-1$
- private static final String ACQUIRE_METHOD = "acquire"; //$NON-NLS-1$
- private static final String IS_HELD_METHOD = "isHeld"; //$NON-NLS-1$
- private static final String POWER_MANAGER = "android/os/PowerManager"; //$NON-NLS-1$
- private static final String NEW_WAKE_LOCK_METHOD = "newWakeLock"; //$NON-NLS-1$
-
- /** Print diagnostics during analysis (display flow control graph etc).
- * Make sure you add the asm-debug or asm-util jars to the runtime classpath
- * as well since the opcode integer to string mapping display routine looks for
- * it via reflection. */
- private static final boolean DEBUG = false;
-
- /** Constructs a new {@link WakelockDetector} */
- public WakelockDetector() {
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mHasAcquire && !mHasRelease && context.getDriver().getPhase() == 1) {
- // Gather positions of the acquire calls
- context.getDriver().requestRepeat(this, Scope.CLASS_FILE_SCOPE);
- }
- }
-
- // ---- Implements ClassScanner ----
-
- /** Whether any {@code acquire()} calls have been encountered */
- private boolean mHasAcquire;
-
- /** Whether any {@code release()} calls have been encountered */
- private boolean mHasRelease;
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(ACQUIRE_METHOD, RELEASE_METHOD, NEW_WAKE_LOCK_METHOD);
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- if (call.owner.equals(WAKELOCK_OWNER)) {
- String name = call.name;
- if (name.equals(ACQUIRE_METHOD)) {
- mHasAcquire = true;
-
- if (context.getDriver().getPhase() == 2) {
- assert !mHasRelease;
- context.report(ISSUE, method, call, context.getLocation(call),
- "Found a wakelock acquire() but no release() calls anywhere",
- null);
- } else {
- assert context.getDriver().getPhase() == 1;
- // Perform flow analysis in this method to see if we're
- // performing an acquire/release block, where there are code paths
- // between the acquire and release which can result in the
- // release call not getting reached.
- checkFlow(context, classNode, method, call);
- }
- } else if (name.equals(RELEASE_METHOD)) {
- mHasRelease = true;
-
- // See if the release is happening in an onDestroy method, in an
- // activity.
- if ("onDestroy".equals(method.name) //$NON-NLS-1$
- && context.getDriver().isSubclassOf(
- classNode, ANDROID_APP_ACTIVITY)) {
- context.report(ISSUE, method, call, context.getLocation(call),
- "Wakelocks should be released in onPause, not onDestroy",
- null);
- }
- }
- } else if (call.owner.equals(POWER_MANAGER)) {
- if (call.name.equals(NEW_WAKE_LOCK_METHOD)) {
- AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
- if (prev == null) {
- return;
- }
- prev = LintUtils.getPrevInstruction(prev);
- if (prev == null || prev.getOpcode() != Opcodes.LDC) {
- return;
- }
- LdcInsnNode ldc = (LdcInsnNode) prev;
- Object constant = ldc.cst;
- if (constant instanceof Integer) {
- int flag = ((Integer) constant).intValue();
- // Constant values are copied into the bytecode so we have to compare
- // values; however, that means the values are part of the API
- final int PARTIAL_WAKE_LOCK = 0x00000001;
- final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
- final int both = PARTIAL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP;
- if ((flag & both) == both) {
- context.report(ISSUE, method, call, context.getLocation(call),
- "Should not set both PARTIAL_WAKE_LOCK and ACQUIRE_CAUSES_WAKEUP. "
- + "If you do not want the screen to turn on, get rid of "
- + "ACQUIRE_CAUSES_WAKEUP",
- null);
- }
- }
-
- }
- }
- }
-
- private void checkFlow(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode acquire) {
- // Track allocations such that we know whether the type of the call
- // is on a SecureRandom rather than a Random
- final InsnList instructions = method.instructions;
- MethodInsnNode release = null;
-
- // Find release call
- for (int i = 0, n = instructions.size(); i < n; i++) {
- AbstractInsnNode instruction = instructions.get(i);
- int type = instruction.getType();
- if (type == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode call = (MethodInsnNode) instruction;
- if (call.name.equals(RELEASE_METHOD) &&
- call.owner.equals(WAKELOCK_OWNER)) {
- release = call;
- break;
- }
- }
- }
-
- if (release == null) {
- // Didn't find both acquire and release in this method; no point in doing
- // local flow analysis
- return;
- }
-
- try {
- MyGraph graph = new MyGraph();
- ControlFlowGraph.create(graph, classNode, method);
-
- if (DEBUG) {
- // Requires util package
- //ClassNode clazz = classNode;
- //clazz.accept(new TraceClassVisitor(new PrintWriter(System.out)));
- System.out.println(graph.toString(graph.getNode(acquire)));
- }
-
- int status = dfs(graph.getNode(acquire));
- if ((status & SEEN_RETURN) != 0) {
- String message;
- if ((status & SEEN_EXCEPTION) != 0) {
- message = "The release() call is not always reached (via exceptional flow)";
- } else {
- message = "The release() call is not always reached";
- }
-
- context.report(ISSUE, method, acquire,
- context.getLocation(release), message, null);
- }
- } catch (AnalyzerException e) {
- context.log(e, null);
- }
- }
-
- private static final int SEEN_TARGET = 1;
- private static final int SEEN_BRANCH = 2;
- private static final int SEEN_EXCEPTION = 4;
- private static final int SEEN_RETURN = 8;
-
- /** TODO RENAME */
- private static class MyGraph extends ControlFlowGraph {
- @Override
- protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
- if (from.getOpcode() == Opcodes.IFNULL) {
- JumpInsnNode jump = (JumpInsnNode) from;
- if (jump.label == to) {
- // Skip jump targets on null if it's surrounding the release call
- //
- // if (lock != null) {
- // lock.release();
- // }
- //
- // The above shouldn't be considered a scenario where release() may not
- // be called.
- AbstractInsnNode next = LintUtils.getNextInstruction(from);
- if (next != null && next.getType() == AbstractInsnNode.VAR_INSN) {
- next = LintUtils.getNextInstruction(next);
- if (next != null && next.getType() == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode method = (MethodInsnNode) next;
- if (method.name.equals(RELEASE_METHOD) &&
- method.owner.equals(WAKELOCK_OWNER)) {
- // This isn't entirely correct; this will also trigger
- // for "if (lock == null) { lock.release(); }" but that's
- // not likely (and caught by other null checking in tools)
- return;
- }
- }
- }
- }
- } else if (from.getOpcode() == Opcodes.IFEQ) {
- JumpInsnNode jump = (JumpInsnNode) from;
- if (jump.label == to) {
- AbstractInsnNode prev = LintUtils.getPrevInstruction(from);
- if (prev != null && prev.getType() == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode method = (MethodInsnNode) prev;
- if (method.name.equals(IS_HELD_METHOD) &&
- method.owner.equals(WAKELOCK_OWNER)) {
- AbstractInsnNode next = LintUtils.getNextInstruction(from);
- if (next != null) {
- super.add(from, next);
- return;
- }
- }
- }
- }
- }
-
- super.add(from, to);
- }
- }
-
- /** Search from the given node towards the target; return false if we reach
- * an exit point such as a return or a call on the way there that is not within
- * a try/catch clause.
- *
- * @param node the current node
- * @return true if the target was reached
- * XXX RETURN VALUES ARE WRONG AS OF RIGHT NOW
- */
- protected int dfs(ControlFlowGraph.Node node) {
- AbstractInsnNode instruction = node.instruction;
- if (instruction.getType() == AbstractInsnNode.JUMP_INSN) {
- int opcode = instruction.getOpcode();
- if (opcode == Opcodes.RETURN || opcode == Opcodes.ARETURN
- || opcode == Opcodes.LRETURN || opcode == Opcodes.IRETURN
- || opcode == Opcodes.DRETURN || opcode == Opcodes.FRETURN
- || opcode == Opcodes.ATHROW) {
- if (DEBUG) {
- System.out.println("Found exit via explicit return: " //$NON-NLS-1$
- + node.toString(false));
- }
- return SEEN_RETURN;
- }
- }
-
- if (!DEBUG) {
- // There are no cycles, so no *NEED* for this, though it does avoid
- // researching shared labels. However, it makes debugging harder (no re-entry)
- // so this is only done when debugging is off
- if (node.visit != 0) {
- return 0;
- }
- node.visit = 1;
- }
-
- // Look for the target. This is any method call node which is a release on the
- // lock (later also check it's the same instance, though that's harder).
- // This is because finally blocks tend to be inlined so from a single try/catch/finally
- // with a release() in the finally, the bytecode can contain multiple repeated
- // (inlined) release() calls.
- if (instruction.getType() == AbstractInsnNode.METHOD_INSN) {
- MethodInsnNode method = (MethodInsnNode) instruction;
- if (method.name.equals(RELEASE_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
- return SEEN_TARGET;
- } else if (method.name.equals(ACQUIRE_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
- // OK
- } else if (method.name.equals(IS_HELD_METHOD) && method.owner.equals(WAKELOCK_OWNER)) {
- // OK
- } else {
- // Some non acquire/release method call: if this is not associated with a
- // try-catch block, it would mean the exception would exit the method,
- // which would be an error
- if (node.exceptions == null || node.exceptions.isEmpty()) {
- // Look up the corresponding frame, if any
- AbstractInsnNode curr = method.getPrevious();
- boolean foundFrame = false;
- while (curr != null) {
- if (curr.getType() == AbstractInsnNode.FRAME) {
- foundFrame = true;
- break;
- }
- curr = curr.getPrevious();
- }
-
- if (!foundFrame) {
- if (DEBUG) {
- System.out.println("Found exit via unguarded method call: " //$NON-NLS-1$
- + node.toString(false));
- }
- return SEEN_RETURN;
- }
- }
- }
- }
-
- // if (node.instruction is a call, and the call is not caught by
- // a try/catch block (provided the release is not inside the try/catch block)
- // then return false
- int status = 0;
-
- boolean implicitReturn = true;
- List<Node> successors = node.successors;
- List<Node> exceptions = node.exceptions;
- if (exceptions != null) {
- if (!exceptions.isEmpty()) {
- implicitReturn = false;
- }
- for (Node successor : exceptions) {
- status = dfs(successor) | status;
- if ((status & SEEN_RETURN) != 0) {
- if (DEBUG) {
- System.out.println("Found exit via exception: " //$NON-NLS-1$
- + node.toString(false));
- }
- return status;
- }
- }
-
- if (status != 0) {
- status |= SEEN_EXCEPTION;
- }
- }
-
- if (successors != null) {
- if (!successors.isEmpty()) {
- implicitReturn = false;
- if (successors.size() > 1) {
- status |= SEEN_BRANCH;
- }
- }
- for (Node successor : successors) {
- status = dfs(successor) | status;
- if ((status & SEEN_RETURN) != 0) {
- if (DEBUG) {
- System.out.println("Found exit via branches: " //$NON-NLS-1$
- + node.toString(false));
- }
- return status;
- }
- }
- }
-
- if (implicitReturn) {
- status |= SEEN_RETURN;
- if (DEBUG) {
- System.out.println("Found exit: via implicit return: " //$NON-NLS-1$
- + node.toString(false));
- }
- }
-
- return status;
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
deleted file mode 100644
index 174f915..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCallDetector.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_DRAW;
-import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_LAYOUT;
-import static com.android.tools.lint.checks.JavaPerformanceDetector.ON_MEASURE;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.ClassContext;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Detector.ClassScanner;
-import com.android.tools.lint.detector.api.Issue;
-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 org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Checks for cases where the wrong call is being made
- */
-public class WrongCallDetector extends Detector implements ClassScanner {
- /** Calling the wrong method */
- public static final Issue ISSUE = Issue.create(
- "WrongCall", //$NON-NLS-1$
- "Finds cases where the wrong call is made, such as calling onMeasure instead of measure",
-
- "Custom views typically need to call `measure()` on their children, not `onMeasure`. " +
- "Ditto for onDraw, onLayout, etc.",
-
- Category.CORRECTNESS,
- 6,
- Severity.ERROR,
- WrongCallDetector.class,
- Scope.CLASS_FILE_SCOPE);
-
- /** Constructs a new {@link WrongCallDetector} */
- public WrongCallDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements ClassScanner ----
-
- @Override
- @Nullable
- public List<String> getApplicableCallNames() {
- return Arrays.asList(
- ON_DRAW,
- ON_MEASURE,
- ON_LAYOUT
- );
- }
-
- @Override
- public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
- @NonNull MethodNode method, @NonNull MethodInsnNode call) {
- String name = call.name;
- // Call is only allowed if it is both only called on the super class (invoke special)
- // as well as within the same overriding method (e.g. you can't call super.onLayout
- // from the onMeasure method)
- if (call.getOpcode() != Opcodes.INVOKESPECIAL || !name.equals(method.name)) {
- String suggestion = Character.toLowerCase(name.charAt(2)) + name.substring(3);
- String message = String.format(
- "Suspicious method call; should probably call \"%1$s\" rather than \"%2$s\"",
- suggestion, name);
- context.report(ISSUE, method, call, context.getLocation(call), message, null);
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
deleted file mode 100644
index d15af9a..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Element;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Check which looks for missing wrong case usage for certain layout tags.
- *
- * @todo Generalize this to handling spelling errors in general.
- */
-public class WrongCaseDetector extends LayoutDetector {
- /** Using the wrong case for layout tags */
- public static final Issue WRONGCASE = Issue.create(
- "WrongCase", //$NON-NLS-1$
- "Ensures that the correct case is used for special layout tags such as <fragment>",
-
- ""
- + "Most layout tags, such as <Button>, refer to actual view classes and are therefore "
- + "capitalized. However, there are exceptions such as <fragment> and <include>. This "
- + "lint check looks for incorrect capitalizations.",
-
- Category.CORRECTNESS,
- 8,
- Severity.WARNING,
- WrongCaseDetector.class,
- Scope.RESOURCE_FILE_SCOPE)
- .setMoreInfo("http://developer.android.com/guide/components/fragments.html"); //$NON-NLS-1$
-
- /** Constructs a new {@link WrongCaseDetector} */
- public WrongCaseDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(
- "Fragment", //$NON-NLS-1$
- "RequestFocus", //$NON-NLS-1$
- "Include", //$NON-NLS-1$
- "Merge"
- );
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String tag = element.getTagName();
- String correct = Character.toLowerCase(tag.charAt(0)) + tag.substring(1);
- context.report(WRONGCASE, element, context.getLocation(element),
- String.format("Invalid tag <%1$s>; should be <%2$s>", tag, correct), null);
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
deleted file mode 100644
index e0e2d0a..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongIdDetector.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_TYPE;
-import static com.android.SdkConstants.ID_PREFIX;
-import static com.android.SdkConstants.NEW_ID_PREFIX;
-import static com.android.SdkConstants.RELATIVE_LAYOUT;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.VALUE_ID;
-import static com.android.tools.lint.detector.api.LintUtils.stripIdPrefix;
-
-import com.android.annotations.NonNull;
-import com.android.resources.ResourceFolderType;
-import com.android.tools.lint.client.api.IDomParser;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Context;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-import com.android.tools.lint.detector.api.LintUtils;
-import com.android.tools.lint.detector.api.Location;
-import com.android.tools.lint.detector.api.Location.Handle;
-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 com.android.tools.lint.detector.api.XmlContext;
-import com.android.utils.Pair;
-import com.google.common.base.Joiner;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Checks for duplicate ids within a layout and within an included layout
- */
-public class WrongIdDetector extends LayoutDetector {
-
- /** Ids bound to widgets in any of the layout files */
- private final Set<String> mGlobalIds = new HashSet<String>(100);
-
- /** Ids bound to widgets in the current layout file */
- private Set<String> mFileIds;
-
- /** Ids declared in a value's file, e.g. {@code <item type="id" name="foo"/>} */
- private Set<String> mDeclaredIds;
-
- /**
- * Location handles for the various id references that were not found as
- * defined in the same layout, to be checked after the whole project has
- * been scanned
- */
- private List<Pair<String, Location.Handle>> mHandles;
-
- /** List of RelativeLayout elements in the current layout */
- private List<Element> mRelativeLayouts;
-
- /** Reference to an unknown id */
- public static final Issue UNKNOWN_ID = Issue.create(
- "UnknownId", //$NON-NLS-1$
- "Checks for id references in RelativeLayouts that are not defined elsewhere",
- "The `@+id/` syntax refers to an existing id, or creates a new one if it has " +
- "not already been defined elsewhere. However, this means that if you have a " +
- "typo in your reference, or if the referred view no longer exists, you do not " +
- "get a warning since the id will be created on demand. This check catches " +
- "errors where you have renamed an id without updating all of the references to " +
- "it.",
- Category.CORRECTNESS,
- 8,
- Severity.FATAL,
- WrongIdDetector.class,
- Scope.ALL_RESOURCES_SCOPE);
-
- /** Reference to an id that is not in the current layout */
- public static final Issue UNKNOWN_ID_LAYOUT = Issue.create(
- "UnknownIdInLayout", //$NON-NLS-1$
- "Makes sure that @+id references refer to views in the same layout",
-
- "The `@+id/` syntax refers to an existing id, or creates a new one if it has " +
- "not already been defined elsewhere. However, this means that if you have a " +
- "typo in your reference, or if the referred view no longer exists, you do not " +
- "get a warning since the id will be created on demand.\n" +
- "\n" +
- "This is sometimes intentional, for example where you are referring to a view " +
- "which is provided in a different layout via an include. However, it is usually " +
- "an accident where you have a typo or you have renamed a view without updating " +
- "all the references to it.",
-
- Category.CORRECTNESS,
- 5,
- Severity.WARNING,
- WrongIdDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a duplicate id check */
- public WrongIdDetector() {
- }
-
- @Override
- public boolean appliesTo(@NonNull ResourceFolderType folderType) {
- return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES;
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public Collection<String> getApplicableAttributes() {
- return Collections.singletonList(ATTR_ID);
- }
-
- @Override
- public Collection<String> getApplicableElements() {
- return Arrays.asList(RELATIVE_LAYOUT, TAG_ITEM);
- }
-
- @Override
- public void beforeCheckFile(@NonNull Context context) {
- mFileIds = new HashSet<String>();
- mRelativeLayouts = null;
- }
-
- @Override
- public void afterCheckFile(@NonNull Context context) {
- if (mRelativeLayouts != null) {
- if (!context.getProject().getReportIssues()) {
- // If this is a library project not being analyzed, ignore it
- return;
- }
-
- for (Element layout : mRelativeLayouts) {
- NodeList children = layout.getChildNodes();
- for (int j = 0, childCount = children.getLength(); j < childCount; j++) {
- Node child = children.item(j);
- if (child.getNodeType() != Node.ELEMENT_NODE) {
- continue;
- }
- Element element = (Element) child;
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0, n = attributes.getLength(); i < n; i++) {
- Attr attr = (Attr) attributes.item(i);
- String value = attr.getValue();
- if ((value.startsWith(NEW_ID_PREFIX) ||
- value.startsWith(ID_PREFIX))
- && ANDROID_URI.equals(attr.getNamespaceURI())
- && attr.getLocalName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
- if (!idDefined(mFileIds, value)) {
- // Stash a reference to this id and location such that
- // we can check after the *whole* layout has been processed,
- // since it's too early to conclude here that the id does
- // not exist (you are allowed to have forward references)
- XmlContext xmlContext = (XmlContext) context;
- IDomParser parser = xmlContext.parser;
- Handle handle = parser.createLocationHandle(xmlContext, attr);
- handle.setClientData(attr);
-
- if (mHandles == null) {
- mHandles = new ArrayList<Pair<String,Handle>>();
- }
- mHandles.add(Pair.of(value, handle));
- }
- }
- }
- }
- }
- }
-
- mFileIds = null;
- }
-
- @Override
- public void afterCheckProject(@NonNull Context context) {
- if (mHandles != null) {
- boolean checkSameLayout = context.isEnabled(UNKNOWN_ID_LAYOUT);
- boolean checkExists = context.isEnabled(UNKNOWN_ID);
- boolean projectScope = context.getScope().contains(Scope.ALL_RESOURCE_FILES);
- for (Pair<String, Handle> pair : mHandles) {
- String id = pair.getFirst();
- boolean isBound = idDefined(mGlobalIds, id);
- if (!isBound && checkExists && projectScope) {
- Handle handle = pair.getSecond();
- boolean isDeclared = idDefined(mDeclaredIds, id);
- id = stripIdPrefix(id);
- String suggestionMessage;
- List<String> suggestions = getSpellingSuggestions(id, mGlobalIds);
- if (suggestions.size() > 1) {
- suggestionMessage = String.format(" Did you mean one of {%2$s} ?",
- id, Joiner.on(", ").join(suggestions));
- } else if (!suggestions.isEmpty()) {
- suggestionMessage = String.format(" Did you mean %2$s ?",
- id, suggestions.get(0));
- } else {
- suggestionMessage = "";
- }
- String message;
- if (isDeclared) {
- message = String.format(
- "The id \"%1$s\" is defined but not assigned to any views.%2$s",
- id, suggestionMessage);
- } else {
- message = String.format(
- "The id \"%1$s\" is not defined anywhere.%2$s",
- id, suggestionMessage);
- }
- report(context, UNKNOWN_ID, handle, message);
- } else if (checkSameLayout && (!projectScope || isBound)
- && id.startsWith(NEW_ID_PREFIX)) {
- // The id was defined, but in a different layout. Usually not intentional
- // (might be referring to a random other view that happens to have the same
- // name.)
- Handle handle = pair.getSecond();
- report(context, UNKNOWN_ID_LAYOUT, handle,
- String.format(
- "The id \"%1$s\" is not referring to any views in this layout",
- stripIdPrefix(id)));
- }
- }
- }
- }
-
- private static void report(Context context, Issue issue, Handle handle, String message) {
- Location location = handle.resolve();
- Object clientData = handle.getClientData();
- if (clientData instanceof Node) {
- if (context.getDriver().isSuppressed(issue, (Node) clientData)) {
- return;
- }
- }
-
- context.report(issue, location, message, null);
- }
-
- @Override
- public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- if (element.getTagName().equals(RELATIVE_LAYOUT)) {
- if (mRelativeLayouts == null) {
- mRelativeLayouts = new ArrayList<Element>();
- }
- mRelativeLayouts.add(element);
- } else {
- assert element.getTagName().equals(TAG_ITEM);
- String type = element.getAttribute(ATTR_TYPE);
- if (VALUE_ID.equals(type)) {
- String name = element.getAttribute(ATTR_NAME);
- if (!name.isEmpty()) {
- if (mDeclaredIds == null) {
- mDeclaredIds = Sets.newHashSet();
- }
- mDeclaredIds.add(ID_PREFIX + name);
- }
- }
- }
- }
-
- @Override
- public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) {
- assert attribute.getName().equals(ATTR_ID) || attribute.getLocalName().equals(ATTR_ID);
- String id = attribute.getValue();
- mFileIds.add(id);
- mGlobalIds.add(id);
- }
-
- private static boolean idDefined(Set<String> ids, String id) {
- if (ids == null) {
- return false;
- }
- boolean definedLocally = ids.contains(id);
- if (!definedLocally) {
- if (id.startsWith(NEW_ID_PREFIX)) {
- definedLocally = ids.contains(ID_PREFIX +
- id.substring(NEW_ID_PREFIX.length()));
- } else if (id.startsWith(ID_PREFIX)) {
- definedLocally = ids.contains(NEW_ID_PREFIX +
- id.substring(ID_PREFIX.length()));
- }
- }
-
- return definedLocally;
- }
-
- private static List<String> getSpellingSuggestions(String id, Collection<String> ids) {
- int maxDistance = id.length() >= 4 ? 2 : 1;
-
- // Look for typos and try to match with custom views and android views
- Multimap<Integer, String> matches = ArrayListMultimap.create(2, 10);
- int count = 0;
- if (!ids.isEmpty()) {
- for (String matchWith : ids) {
- matchWith = stripIdPrefix(matchWith);
- if (Math.abs(id.length() - matchWith.length()) > maxDistance) {
- // The string lengths differ more than the allowed edit distance;
- // no point in even attempting to compute the edit distance (requires
- // O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
- continue;
- }
- int distance = LintUtils.editDistance(id, matchWith);
- if (distance <= maxDistance) {
- matches.put(distance, matchWith);
- }
-
- if (count++ > 100) {
- // Make sure that for huge projects we don't completely grind to a halt
- break;
- }
- }
- }
-
- for (int i = 0; i < maxDistance; i++) {
- Collection<String> strings = matches.get(i);
- if (strings != null && !strings.isEmpty()) {
- List<String> suggestions = new ArrayList<String>(strings);
- Collections.sort(suggestions);
- return suggestions;
- }
- }
-
- return Collections.emptyList();
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java
deleted file mode 100644
index 64f3ce1..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongImportDetector.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Detector;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.Location;
-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 java.util.Collections;
-import java.util.List;
-
-import lombok.ast.AstVisitor;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.ImportDeclaration;
-import lombok.ast.Node;
-
-/**
- * Checks for "import android.R", which seems to be a common source of confusion
- * (see for example http://stackoverflow.com/questions/885009/r-cannot-be-resolved-android-error
- * and many other forums).
- * <p>
- * The root cause is probably this (from http://source.android.com/source/using-eclipse.html) :
- * <blockquote> Note: Eclipse sometimes likes to add an import android.R
- * statement at the top of your files that use resources, especially when you
- * ask eclipse to sort or otherwise manage imports. This will cause your make to
- * break. Look out for these erroneous import statements and delete them.
- * </blockquote>
- */
-public class WrongImportDetector extends Detector implements Detector.JavaScanner {
- /** Is android.R being imported? */
- public static final Issue ISSUE = Issue.create("SuspiciousImport", //$NON-NLS-1$
- "Checks for 'import android.R' statements, which are usually accidental",
- "Importing `android.R` is usually not intentional; it sometimes happens when " +
- "you use an IDE and ask it to automatically add imports at a time when your " +
- "project's R class it not present.\n" +
- "\n" +
- "Once the import is there you might get a lot of \"confusing\" error messages " +
- "because of course the fields available on `android.R` are not the ones you'd " +
- "expect from just looking at your own `R` class.",
- Category.CORRECTNESS,
- 9,
- Severity.WARNING,
- WrongImportDetector.class,
- Scope.JAVA_FILE_SCOPE);
-
- /** Constructs a new {@link WrongImportDetector} check */
- public WrongImportDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- // ---- Implements Detector.JavaScanner ----
-
- @Override
- public List<Class<? extends Node>> getApplicableNodeTypes() {
- return Collections.<Class<? extends Node>> singletonList(
- ImportDeclaration.class);
- }
-
- @Override
- public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
- return new ImportVisitor(context);
- }
-
- private static class ImportVisitor extends ForwardingAstVisitor {
- private final JavaContext mContext;
-
- public ImportVisitor(JavaContext context) {
- super();
- mContext = context;
- }
-
- @Override
- public boolean visitImportDeclaration(ImportDeclaration node) {
- String fqn = node.asFullyQualifiedName();
- if (fqn.equals("android.R")) { //$NON-NLS-1$
- Location location = mContext.getLocation(node);
- mContext.report(ISSUE, node, location,
- "Don't include android.R here; use a fully qualified name for "
- + "each usage instead", null);
- }
- return false;
- }
- }
-}
diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
deleted file mode 100644
index 37ffbeb..0000000
--- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/WrongLocationDetector.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * 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.android.tools.lint.checks;
-
-import static com.android.SdkConstants.TAG_RESOURCES;
-
-import com.android.annotations.NonNull;
-import com.android.tools.lint.detector.api.Category;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.LayoutDetector;
-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 com.android.tools.lint.detector.api.XmlContext;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-/** Looks for problems with XML files being placed in the wrong folder */
-public class WrongLocationDetector extends LayoutDetector {
- /** Main issue investigated by this detector */
- public static final Issue ISSUE = Issue.create(
- "WrongFolder", //$NON-NLS-1$
-
- "Finds resource files that are placed in the wrong folders",
-
- "Resource files are sometimes placed in the wrong folder, and it can lead to " +
- "subtle bugs that are hard to understand. This check looks for problems in this " +
- "area, such as attempting to place a layout \"alias\" file in a `layout/` folder " +
- "rather than the `values/` folder where it belongs.",
- Category.CORRECTNESS,
- 8,
- Severity.ERROR,
- WrongLocationDetector.class,
- Scope.RESOURCE_FILE_SCOPE);
-
- /** Constructs a new {@link WrongLocationDetector} check */
- public WrongLocationDetector() {
- }
-
- @NonNull
- @Override
- public Speed getSpeed() {
- return Speed.FAST;
- }
-
- @Override
- public void visitDocument(@NonNull XmlContext context, @NonNull Document document) {
- Element root = document.getDocumentElement();
- if (root != null && root.getTagName().equals(TAG_RESOURCES)) {
- context.report(ISSUE, root, context.getLocation(root),
- "This file should be placed in a values/ folder, not a layout/ folder", null);
- }
- }
-}