1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.eclipse.adt.internal.lint;
17 
18 import static com.android.SdkConstants.DOT_CLASS;
19 import static com.android.SdkConstants.DOT_JAVA;
20 import static com.android.SdkConstants.DOT_XML;
21 
22 import com.android.SdkConstants;
23 import com.android.annotations.NonNull;
24 import com.android.annotations.Nullable;
25 import com.android.ide.eclipse.adt.AdtPlugin;
26 import com.android.ide.eclipse.adt.AdtUtils;
27 import com.android.tools.lint.client.api.IssueRegistry;
28 import com.android.tools.lint.client.api.LintDriver;
29 import com.android.tools.lint.client.api.LintRequest;
30 import com.android.tools.lint.detector.api.Issue;
31 import com.android.tools.lint.detector.api.Scope;
32 import com.android.utils.SdkUtils;
33 
34 import org.eclipse.core.resources.IFile;
35 import org.eclipse.core.resources.IMarker;
36 import org.eclipse.core.resources.IProject;
37 import org.eclipse.core.resources.IResource;
38 import org.eclipse.core.runtime.IProgressMonitor;
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.Status;
41 import org.eclipse.core.runtime.jobs.IJobManager;
42 import org.eclipse.core.runtime.jobs.Job;
43 
44 import java.io.File;
45 import java.util.ArrayList;
46 import java.util.EnumSet;
47 import java.util.List;
48 
49 /** Job to check lint on a set of resources */
50 public final class LintJob extends Job {
51     /** Job family */
52     private static final Object FAMILY_RUN_LINT = new Object();
53     private final EclipseLintClient mClient;
54     private final List<? extends IResource> mResources;
55     private final IResource mSource;
56     private final IssueRegistry mRegistry;
57     private LintDriver mLint;
58     private boolean mFatal;
59 
LintJob( @onNull EclipseLintClient client, @NonNull List<? extends IResource> resources, @Nullable IResource source, @NonNull IssueRegistry registry)60     public LintJob(
61             @NonNull EclipseLintClient client,
62             @NonNull List<? extends IResource> resources,
63             @Nullable IResource source,
64             @NonNull IssueRegistry registry) {
65         super("Running Android Lint");
66         mClient = client;
67         mResources = resources;
68         mSource = source;
69         mRegistry = registry;
70     }
71 
LintJob( @onNull EclipseLintClient client, @NonNull List<? extends IResource> resources, @Nullable IResource source)72     public LintJob(
73             @NonNull EclipseLintClient client,
74             @NonNull List<? extends IResource> resources,
75             @Nullable IResource source) {
76         this(client, resources, source, EclipseLintClient.getRegistry());
77     }
78 
79     @Override
belongsTo(Object family)80     public boolean belongsTo(Object family) {
81         return family == FAMILY_RUN_LINT;
82     }
83 
84     @Override
canceling()85     protected void canceling() {
86         super.canceling();
87         if (mLint != null) {
88             mLint.cancel();
89         }
90     }
91 
92     @Override
93     @NonNull
run(IProgressMonitor monitor)94     protected IStatus run(IProgressMonitor monitor) {
95         try {
96             monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
97             EnumSet<Scope> scope = null;
98             List<File> files = new ArrayList<File>(mResources.size());
99             for (IResource resource : mResources) {
100                 File file = AdtUtils.getAbsolutePath(resource).toFile();
101                 files.add(file);
102 
103                 if (resource instanceof IProject && mSource == null) {
104                     scope = Scope.ALL;
105                 } else {
106                     String name = resource.getName();
107                     if (SdkUtils.endsWithIgnoreCase(name, DOT_XML)) {
108                         if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
109                             scope = EnumSet.of(Scope.MANIFEST);
110                         } else {
111                             scope = Scope.RESOURCE_FILE_SCOPE;
112                         }
113                     } else if (name.endsWith(DOT_JAVA) && resource instanceof IFile) {
114                         if (scope != null) {
115                             if (!scope.contains(Scope.JAVA_FILE)) {
116                                 scope = EnumSet.copyOf(scope);
117                                 scope.add(Scope.JAVA_FILE);
118                             }
119                         } else {
120                             scope = Scope.JAVA_FILE_SCOPE;
121                         }
122                     } else if (name.endsWith(DOT_CLASS) && resource instanceof IFile) {
123                         if (scope != null) {
124                             if (!scope.contains(Scope.CLASS_FILE)) {
125                                 scope = EnumSet.copyOf(scope);
126                                 scope.add(Scope.CLASS_FILE);
127                             }
128                         } else {
129                             scope = Scope.CLASS_FILE_SCOPE;
130                         }
131                     } else {
132                         return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
133                             "Only XML & Java files are supported for single file lint", null); //$NON-NLS-1$
134                     }
135                 }
136             }
137             if (scope == null) {
138                 scope = Scope.ALL;
139             }
140             if (mSource == null) {
141                 assert !Scope.checkSingleFile(scope) : scope + " with " + mResources;
142             }
143             // Check single file?
144             if (mSource != null) {
145                 // Delete specific markers
146                 IMarker[] markers = EclipseLintClient.getMarkers(mSource);
147                 for (IMarker marker : markers) {
148                     String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY, "");
149                     Issue issue = mRegistry.getIssue(id);
150                     if (issue == null) {
151                         continue;
152                     }
153                     if (issue.getImplementation().isAdequate(scope)) {
154                         marker.delete();
155                     }
156                 }
157                 mClient.setSearchForSuperClasses(true);
158             } else {
159                 EclipseLintClient.clearMarkers(mResources);
160             }
161 
162             mLint = new LintDriver(mRegistry, mClient);
163             mLint.analyze(new LintRequest(mClient, files).setScope(scope));
164             mFatal = mClient.hasFatalErrors();
165             return Status.OK_STATUS;
166         } catch (Exception e) {
167             return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
168                               "Failed", e); //$NON-NLS-1$
169         } finally {
170             if (monitor != null) {
171                 monitor.done();
172             }
173         }
174     }
175 
176     /**
177      * Returns true if a fatal error was encountered
178      *
179      * @return true if a fatal error was encountered
180      */
isFatal()181     public boolean isFatal() {
182         return mFatal;
183     }
184 
185     /**
186      * Returns the associated lint client
187      *
188      * @return the associated lint client
189      */
190     @NonNull
getLintClient()191     public EclipseLintClient getLintClient() {
192         return mClient;
193     }
194 
195     /** Returns the current lint jobs, if any (never returns null but array may be empty) */
196     @NonNull
getCurrentJobs()197     static Job[] getCurrentJobs() {
198         IJobManager jobManager = Job.getJobManager();
199         return jobManager.find(LintJob.FAMILY_RUN_LINT);
200     }
201 }
202