Home | History | Annotate | Download | only in lint
      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.EXT_JAVA;
     21 
     22 import com.android.annotations.NonNull;
     23 import com.android.annotations.Nullable;
     24 import com.android.ide.eclipse.adt.AdtPlugin;
     25 import com.android.ide.eclipse.adt.AdtUtils;
     26 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     27 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
     28 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
     29 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     30 
     31 import org.eclipse.core.resources.IFile;
     32 import org.eclipse.core.resources.IMarkerDelta;
     33 import org.eclipse.core.resources.IResource;
     34 import org.eclipse.core.resources.IResourceDelta;
     35 import org.eclipse.swt.widgets.Display;
     36 
     37 import java.util.ArrayList;
     38 import java.util.Collections;
     39 import java.util.List;
     40 
     41 /**
     42  * Delta processor for Java files, which runs single-file lints if it finds that
     43  * the currently active file has been updated.
     44  */
     45 public class LintDeltaProcessor implements Runnable {
     46     private List<IResource> mFiles;
     47     private IFile mActiveFile;
     48 
     49     private LintDeltaProcessor() {
     50         // Get the active editor file, if any
     51         Display display = AdtPlugin.getDisplay();
     52         if (display == null || display.isDisposed()) {
     53             return;
     54         }
     55         if (display.getThread() != Thread.currentThread()) {
     56             display.syncExec(this);
     57         } else {
     58             run();
     59         }
     60     }
     61 
     62     /**
     63      * Creates a new {@link LintDeltaProcessor}
     64      *
     65      * @return a visitor
     66      */
     67     @NonNull
     68     public static LintDeltaProcessor create() {
     69         return new LintDeltaProcessor();
     70     }
     71 
     72     /**
     73      * Process the given delta: update lint on any Java source and class files found.
     74      *
     75      * @param delta the delta describing recently changed files
     76      */
     77     public void process(@NonNull IResourceDelta delta)  {
     78         if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) {
     79             return;
     80         }
     81 
     82         mFiles = new ArrayList<IResource>();
     83         gatherFiles(delta);
     84 
     85         if (!mFiles.isEmpty()) {
     86             EclipseLintRunner.startLint(mFiles, mActiveFile, null,
     87                     false /*fatalOnly*/, false /*show*/);
     88         }
     89     }
     90 
     91     /**
     92      * Process edits in the given file: update lint on the Java source provided
     93      * it's the active file.
     94      *
     95      * @param file the file that was changed
     96      */
     97     public void process(@NonNull IFile file)  {
     98         if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) {
     99             return;
    100         }
    101 
    102         if (file.equals(mActiveFile)) {
    103             mFiles = Collections.<IResource>singletonList(file);
    104             EclipseLintRunner.startLint(mFiles, mActiveFile, null,
    105                     false /*fatalOnly*/, false /*show*/);
    106         }
    107     }
    108 
    109     /**
    110      * Collect .java and .class files to be run in lint. Only collects files
    111      * that match the active editor.
    112      */
    113     private void gatherFiles(@NonNull IResourceDelta delta) {
    114         IResource resource = delta.getResource();
    115         String name = resource.getName();
    116         if (name.endsWith(DOT_JAVA)) {
    117             if (resource.equals(mActiveFile)) {
    118                 mFiles.add(resource);
    119             }
    120         } else if (name.endsWith(DOT_CLASS)) {
    121             // Make sure this class corresponds to the .java file, meaning it has
    122             // the same basename, or that it is an inner class of a class that
    123             // matches the same basename. (We could potentially make sure the package
    124             // names match too, but it's unlikely that the class names match without a
    125             // package match, and there's no harm in including some extra classes here,
    126             // since lint will resolve full paths and the resource markers won't go
    127             // to the wrong place, we simply end up analyzing some extra files.)
    128             String className = mActiveFile.getName();
    129             if (name.regionMatches(0, className, 0, className.length() - DOT_JAVA.length())) {
    130                 if (name.length() == className.length() - DOT_JAVA.length() + DOT_CLASS.length()
    131                         || name.charAt(className.length() - DOT_JAVA.length()) == '$') {
    132                     mFiles.add(resource);
    133                 }
    134             }
    135         } else {
    136             IResourceDelta[] children = delta.getAffectedChildren();
    137             if (children != null && children.length > 0) {
    138                 for (IResourceDelta d : children) {
    139                     gatherFiles(d);
    140                 }
    141             }
    142         }
    143     }
    144 
    145     @Override
    146     public void run() {
    147         // Get the active file: this must be run on the GUI thread
    148         mActiveFile = AdtUtils.getActiveFile();
    149     }
    150 
    151     /**
    152      * Start listening to the resource monitor
    153      *
    154      * @param resourceMonitor the resource monitor
    155      */
    156     public static void startListening(@NonNull GlobalProjectMonitor resourceMonitor) {
    157         // Add a file listener which finds out when files have changed. This is listening
    158         // specifically for saves of Java files, in order to run incremental lint on them.
    159         // Note that the {@link PostCompilerBuilder} already handles incremental lint files
    160         // on Java files - and runs it for both the .java and .class files.
    161         //
    162         // However, if Project > Build Automatically is turned off, then the PostCompilerBuilder
    163         // isn't run after a save. THAT's what the below is for: it will run and *only*
    164         // run lint incrementally if build automatically is off.
    165         assert sListener == null; // Should only be called once on plugin activation
    166         sListener = new IFileListener() {
    167             @Override
    168             public void fileChanged(@NonNull IFile file,
    169                     @NonNull IMarkerDelta[] markerDeltas,
    170                     int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
    171                 if (!isAndroidProject || flags == IResourceDelta.MARKERS) {
    172                     // If not an Android project or ONLY the markers changed.
    173                     // Ignore these since they happen
    174                     // when we add markers for lint errors found in the current file,
    175                     // which would cause us to repeatedly enter this method over and over
    176                     // again.
    177                     return;
    178                 }
    179                 if (EXT_JAVA.equals(extension)
    180                         && !ResourceManager.isAutoBuilding()
    181                         && AdtPrefs.getPrefs().isLintOnSave()) {
    182                     LintDeltaProcessor.create().process(file);
    183                 }
    184             }
    185         };
    186         resourceMonitor.addFileListener(sListener, IResourceDelta.ADDED | IResourceDelta.CHANGED);
    187     }
    188 
    189     /**
    190      * Stop listening to the resource monitor
    191      *
    192      * @param resourceMonitor the resource monitor
    193      */
    194     public static void stopListening(@NonNull GlobalProjectMonitor resourceMonitor) {
    195         assert sListener != null;
    196         resourceMonitor.removeFileListener(sListener);
    197         sListener = null;
    198     }
    199 
    200     private static IFileListener sListener;
    201 }
    202