| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php |
| * |
| * 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.ide.eclipse.adt.internal.lint; |
| |
| import static com.android.SdkConstants.DOT_CLASS; |
| import static com.android.SdkConstants.DOT_JAVA; |
| import static com.android.SdkConstants.EXT_JAVA; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.AdtUtils; |
| import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; |
| import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; |
| import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; |
| import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IMarkerDelta; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.swt.widgets.Display; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Delta processor for Java files, which runs single-file lints if it finds that |
| * the currently active file has been updated. |
| */ |
| public class LintDeltaProcessor implements Runnable { |
| private List<IResource> mFiles; |
| private IFile mActiveFile; |
| |
| private LintDeltaProcessor() { |
| // Get the active editor file, if any |
| Display display = AdtPlugin.getDisplay(); |
| if (display == null || display.isDisposed()) { |
| return; |
| } |
| if (display.getThread() != Thread.currentThread()) { |
| display.syncExec(this); |
| } else { |
| run(); |
| } |
| } |
| |
| /** |
| * Creates a new {@link LintDeltaProcessor} |
| * |
| * @return a visitor |
| */ |
| @NonNull |
| public static LintDeltaProcessor create() { |
| return new LintDeltaProcessor(); |
| } |
| |
| /** |
| * Process the given delta: update lint on any Java source and class files found. |
| * |
| * @param delta the delta describing recently changed files |
| */ |
| public void process(@NonNull IResourceDelta delta) { |
| if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) { |
| return; |
| } |
| |
| mFiles = new ArrayList<IResource>(); |
| gatherFiles(delta); |
| |
| if (!mFiles.isEmpty()) { |
| EclipseLintRunner.startLint(mFiles, mActiveFile, null, |
| false /*fatalOnly*/, false /*show*/); |
| } |
| } |
| |
| /** |
| * Process edits in the given file: update lint on the Java source provided |
| * it's the active file. |
| * |
| * @param file the file that was changed |
| */ |
| public void process(@NonNull IFile file) { |
| if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) { |
| return; |
| } |
| |
| if (file.equals(mActiveFile)) { |
| mFiles = Collections.<IResource>singletonList(file); |
| EclipseLintRunner.startLint(mFiles, mActiveFile, null, |
| false /*fatalOnly*/, false /*show*/); |
| } |
| } |
| |
| /** |
| * Collect .java and .class files to be run in lint. Only collects files |
| * that match the active editor. |
| */ |
| private void gatherFiles(@NonNull IResourceDelta delta) { |
| IResource resource = delta.getResource(); |
| String name = resource.getName(); |
| if (name.endsWith(DOT_JAVA)) { |
| if (resource.equals(mActiveFile)) { |
| mFiles.add(resource); |
| } |
| } else if (name.endsWith(DOT_CLASS)) { |
| // Make sure this class corresponds to the .java file, meaning it has |
| // the same basename, or that it is an inner class of a class that |
| // matches the same basename. (We could potentially make sure the package |
| // names match too, but it's unlikely that the class names match without a |
| // package match, and there's no harm in including some extra classes here, |
| // since lint will resolve full paths and the resource markers won't go |
| // to the wrong place, we simply end up analyzing some extra files.) |
| String className = mActiveFile.getName(); |
| if (name.regionMatches(0, className, 0, className.length() - DOT_JAVA.length())) { |
| if (name.length() == className.length() - DOT_JAVA.length() + DOT_CLASS.length() |
| || name.charAt(className.length() - DOT_JAVA.length()) == '$') { |
| mFiles.add(resource); |
| } |
| } |
| } else { |
| IResourceDelta[] children = delta.getAffectedChildren(); |
| if (children != null && children.length > 0) { |
| for (IResourceDelta d : children) { |
| gatherFiles(d); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void run() { |
| // Get the active file: this must be run on the GUI thread |
| mActiveFile = AdtUtils.getActiveFile(); |
| } |
| |
| /** |
| * Start listening to the resource monitor |
| * |
| * @param resourceMonitor the resource monitor |
| */ |
| public static void startListening(@NonNull GlobalProjectMonitor resourceMonitor) { |
| // Add a file listener which finds out when files have changed. This is listening |
| // specifically for saves of Java files, in order to run incremental lint on them. |
| // Note that the {@link PostCompilerBuilder} already handles incremental lint files |
| // on Java files - and runs it for both the .java and .class files. |
| // |
| // However, if Project > Build Automatically is turned off, then the PostCompilerBuilder |
| // isn't run after a save. THAT's what the below is for: it will run and *only* |
| // run lint incrementally if build automatically is off. |
| assert sListener == null; // Should only be called once on plugin activation |
| sListener = new IFileListener() { |
| @Override |
| public void fileChanged(@NonNull IFile file, |
| @NonNull IMarkerDelta[] markerDeltas, |
| int kind, @Nullable String extension, int flags, boolean isAndroidProject) { |
| if (!isAndroidProject || flags == IResourceDelta.MARKERS) { |
| // If not an Android project or ONLY the markers changed. |
| // Ignore these since they happen |
| // when we add markers for lint errors found in the current file, |
| // which would cause us to repeatedly enter this method over and over |
| // again. |
| return; |
| } |
| if (EXT_JAVA.equals(extension) |
| && !ResourceManager.isAutoBuilding() |
| && AdtPrefs.getPrefs().isLintOnSave()) { |
| LintDeltaProcessor.create().process(file); |
| } |
| } |
| }; |
| resourceMonitor.addFileListener(sListener, IResourceDelta.ADDED | IResourceDelta.CHANGED); |
| } |
| |
| /** |
| * Stop listening to the resource monitor |
| * |
| * @param resourceMonitor the resource monitor |
| */ |
| public static void stopListening(@NonNull GlobalProjectMonitor resourceMonitor) { |
| assert sListener != null; |
| resourceMonitor.removeFileListener(sListener); |
| sListener = null; |
| } |
| |
| private static IFileListener sListener; |
| } |