blob: ebb9a591cb4cceeab55d3fde2ef2f9c208414ae3 [file] [log] [blame]
/*
* 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;
}