1d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye/* 2d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Copyright (C) 2012 The Android Open Source Project 3d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * 4d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * you may not use this file except in compliance with the License. 6d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * You may obtain a copy of the License at 7d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * 8d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * http://www.eclipse.org/org/documents/epl-v10.php 9d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * 10d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Unless required by applicable law or agreed to in writing, software 11d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * distributed under the License is distributed on an "AS IS" BASIS, 12d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * See the License for the specific language governing permissions and 14d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * limitations under the License. 15d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye */ 16d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyepackage com.android.ide.eclipse.adt.internal.lint; 17d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 1812d4581faa6438941e65a9dc83213be34c6ca970Tor Norbyeimport static com.android.SdkConstants.DOT_CLASS; 1912d4581faa6438941e65a9dc83213be34c6ca970Tor Norbyeimport static com.android.SdkConstants.DOT_JAVA; 2012d4581faa6438941e65a9dc83213be34c6ca970Tor Norbyeimport static com.android.SdkConstants.EXT_JAVA; 21d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 22d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport com.android.annotations.NonNull; 238dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.annotations.Nullable; 24d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport com.android.ide.eclipse.adt.AdtPlugin; 25d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport com.android.ide.eclipse.adt.AdtUtils; 268dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 278dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; 288dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; 298dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 30d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 31d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.core.resources.IFile; 328dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport org.eclipse.core.resources.IMarkerDelta; 33d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.core.resources.IResource; 34d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.core.resources.IResourceDelta; 35d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.swt.widgets.Display; 36d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 37d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport java.util.ArrayList; 388dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport java.util.Collections; 39d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport java.util.List; 40d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 41d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye/** 42d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Delta processor for Java files, which runs single-file lints if it finds that 43d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * the currently active file has been updated. 44d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye */ 45d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyepublic class LintDeltaProcessor implements Runnable { 46d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye private List<IResource> mFiles; 47d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye private IFile mActiveFile; 48d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 49d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye private LintDeltaProcessor() { 508dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // Get the active editor file, if any 518dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye Display display = AdtPlugin.getDisplay(); 528dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye if (display == null || display.isDisposed()) { 538dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye return; 548dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 558dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye if (display.getThread() != Thread.currentThread()) { 568dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye display.syncExec(this); 578dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } else { 588dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye run(); 598dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 60d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 61d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 62d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye /** 63d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Creates a new {@link LintDeltaProcessor} 64d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * 65d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * @return a visitor 66d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye */ 67d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye @NonNull 68d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye public static LintDeltaProcessor create() { 69d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye return new LintDeltaProcessor(); 70d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 71d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 72d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye /** 73d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Process the given delta: update lint on any Java source and class files found. 74d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * 75d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * @param delta the delta describing recently changed files 76d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye */ 77d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye public void process(@NonNull IResourceDelta delta) { 78d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) { 79d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye return; 80d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 81d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 82d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye mFiles = new ArrayList<IResource>(); 83d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye gatherFiles(delta); 84d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 85d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye if (!mFiles.isEmpty()) { 86d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye EclipseLintRunner.startLint(mFiles, mActiveFile, null, 87d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye false /*fatalOnly*/, false /*show*/); 88d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 89d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 90d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 91d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye /** 928dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * Process edits in the given file: update lint on the Java source provided 938dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * it's the active file. 948dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * 958dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * @param file the file that was changed 968dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye */ 978dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye public void process(@NonNull IFile file) { 988dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) { 998dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye return; 1008dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1018dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye 1028dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye if (file.equals(mActiveFile)) { 1038dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye mFiles = Collections.<IResource>singletonList(file); 1048dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye EclipseLintRunner.startLint(mFiles, mActiveFile, null, 1058dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye false /*fatalOnly*/, false /*show*/); 1068dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1078dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1088dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye 1098dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye /** 110d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Collect .java and .class files to be run in lint. Only collects files 111d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * that match the active editor. 112d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye */ 113d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye private void gatherFiles(@NonNull IResourceDelta delta) { 114d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye IResource resource = delta.getResource(); 115d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye String name = resource.getName(); 116d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye if (name.endsWith(DOT_JAVA)) { 117d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye if (resource.equals(mActiveFile)) { 118d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye mFiles.add(resource); 119d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 120d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } else if (name.endsWith(DOT_CLASS)) { 121d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // Make sure this class corresponds to the .java file, meaning it has 122d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // the same basename, or that it is an inner class of a class that 123d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // matches the same basename. (We could potentially make sure the package 124d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // names match too, but it's unlikely that the class names match without a 125d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // package match, and there's no harm in including some extra classes here, 126d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // since lint will resolve full paths and the resource markers won't go 127d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // to the wrong place, we simply end up analyzing some extra files.) 128d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye String className = mActiveFile.getName(); 129d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye if (name.regionMatches(0, className, 0, className.length() - DOT_JAVA.length())) { 130d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye if (name.length() == className.length() - DOT_JAVA.length() + DOT_CLASS.length() 131d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye || name.charAt(className.length() - DOT_JAVA.length()) == '$') { 132d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye mFiles.add(resource); 133d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 134d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 135d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } else { 136d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye IResourceDelta[] children = delta.getAffectedChildren(); 137d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye if (children != null && children.length > 0) { 138d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye for (IResourceDelta d : children) { 139d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye gatherFiles(d); 140d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 141d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 142d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 143d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 144d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye 145d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye @Override 146d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye public void run() { 147d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye // Get the active file: this must be run on the GUI thread 148d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye mActiveFile = AdtUtils.getActiveFile(); 149d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye } 1508dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye 1518dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye /** 1528dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * Start listening to the resource monitor 1538dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * 1548dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * @param resourceMonitor the resource monitor 1558dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye */ 1568dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye public static void startListening(@NonNull GlobalProjectMonitor resourceMonitor) { 1578dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // Add a file listener which finds out when files have changed. This is listening 1588dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // specifically for saves of Java files, in order to run incremental lint on them. 1598dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // Note that the {@link PostCompilerBuilder} already handles incremental lint files 1608dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // on Java files - and runs it for both the .java and .class files. 1618dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // 1628dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // However, if Project > Build Automatically is turned off, then the PostCompilerBuilder 1638dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // isn't run after a save. THAT's what the below is for: it will run and *only* 1648dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // run lint incrementally if build automatically is off. 1658dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye assert sListener == null; // Should only be called once on plugin activation 1668dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye sListener = new IFileListener() { 1678dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye @Override 1688dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye public void fileChanged(@NonNull IFile file, 1698dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye @NonNull IMarkerDelta[] markerDeltas, 1708dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye int kind, @Nullable String extension, int flags) { 1718dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye if (flags == IResourceDelta.MARKERS) { 1728dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // ONLY the markers changed. Ignore these since they happen 1738dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // when we add markers for lint errors found in the current file, 1748dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // which would cause us to repeatedly enter this method over and over 1758dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye // again. 1768dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye return; 1778dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1788dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye if (EXT_JAVA.equals(extension) 1798dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye && !ResourceManager.isAutoBuilding() 1808dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye && AdtPrefs.getPrefs().isLintOnSave()) { 1818dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye LintDeltaProcessor.create().process(file); 1828dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1838dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1848dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye }; 1858dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye resourceMonitor.addFileListener(sListener, IResourceDelta.ADDED | IResourceDelta.CHANGED); 1868dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1878dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye 1888dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye /** 1898dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * Stop listening to the resource monitor 1908dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * 1918dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye * @param resourceMonitor the resource monitor 1928dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye */ 1938dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye public static void stopListening(@NonNull GlobalProjectMonitor resourceMonitor) { 1948dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye assert sListener != null; 1958dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye resourceMonitor.removeFileListener(sListener); 1968dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye sListener = null; 1978dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye } 1988dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye 1998dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye private static IFileListener sListener; 200d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye} 201