Home | History | Annotate | Download | only in manager
      1 /*
      2  * Copyright (C) 2011 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.resources.manager;
     17 
     18 import static com.android.SdkConstants.ANDROID_URI;
     19 import static com.android.ide.eclipse.adt.AdtConstants.MARKER_AAPT_COMPILE;
     20 import static org.eclipse.core.resources.IResource.DEPTH_ONE;
     21 import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
     22 
     23 import com.android.annotations.NonNull;
     24 import com.android.annotations.Nullable;
     25 import com.android.ide.common.resources.ResourceRepository;
     26 import com.android.ide.common.resources.ScanningContext;
     27 import com.android.ide.common.resources.platform.AttributeInfo;
     28 import com.android.ide.eclipse.adt.AdtPlugin;
     29 import com.android.ide.eclipse.adt.internal.build.AaptParser;
     30 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     31 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     32 import com.android.utils.Pair;
     33 
     34 import org.eclipse.core.resources.IFolder;
     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.CoreException;
     39 
     40 import java.util.ArrayList;
     41 import java.util.Collection;
     42 import java.util.HashSet;
     43 import java.util.List;
     44 import java.util.Map;
     45 import java.util.Set;
     46 
     47 /**
     48  * An {@link IdeScanningContext} is a specialized {@link ScanningContext} which
     49  * carries extra information about the scanning state, such as which file is
     50  * currently being scanned, and which files have been scanned in the past, such
     51  * that at the end of a scan we can mark and clear errors, etc.
     52  */
     53 public class IdeScanningContext extends ScanningContext {
     54     private final IProject mProject;
     55     private final List<IResource> mScannedResources = new ArrayList<IResource>();
     56     private IResource mCurrentFile;
     57     private List<Pair<IResource, String>> mErrors;
     58     private Set<IProject> mFullAaptProjects;
     59     private boolean mValidate;
     60     private Map<String, AttributeInfo> mAttributeMap;
     61     private ResourceRepository mFrameworkResources;
     62 
     63     /**
     64      * Constructs a new {@link IdeScanningContext}
     65      *
     66      * @param repository the associated {@link ResourceRepository}
     67      * @param project the associated project
     68      * @param validate if true, check that the attributes and resources are
     69      *            valid and if not request a full AAPT check
     70      */
     71     public IdeScanningContext(@NonNull ResourceRepository repository, @NonNull IProject project,
     72             boolean validate) {
     73         super(repository);
     74         mProject = project;
     75         mValidate = validate;
     76 
     77         Sdk sdk = Sdk.getCurrent();
     78         if (sdk != null) {
     79             AndroidTargetData targetData = sdk.getTargetData(project);
     80             if (targetData != null) {
     81                 mAttributeMap = targetData.getAttributeMap();
     82                 mFrameworkResources = targetData.getFrameworkResources();
     83             }
     84         }
     85     }
     86 
     87     @Override
     88     public void addError(@NonNull String error) {
     89         super.addError(error);
     90 
     91         if (mErrors == null) {
     92             mErrors = new ArrayList<Pair<IResource,String>>();
     93         }
     94         mErrors.add(Pair.of(mCurrentFile, error));
     95     }
     96 
     97     /**
     98      * Notifies the context that the given resource is about to be scanned.
     99      *
    100      * @param resource the resource about to be scanned
    101      */
    102     public void startScanning(@NonNull IResource resource) {
    103         assert mCurrentFile == null : mCurrentFile;
    104         mCurrentFile = resource;
    105         mScannedResources.add(resource);
    106     }
    107 
    108     /**
    109      * Notifies the context that the given resource has been scanned.
    110      *
    111      * @param resource the resource that was scanned
    112      */
    113     public void finishScanning(@NonNull IResource resource) {
    114         assert mCurrentFile != null;
    115         mCurrentFile = null;
    116     }
    117 
    118     /**
    119      * Process any errors found to add error markers in the affected files (and
    120      * also clear up any aapt errors in files that are no longer applicable)
    121      *
    122      * @param async if true, delay updating markers until the next display
    123      *            thread event loop update
    124      */
    125     public void updateMarkers(boolean async) {
    126         // Run asynchronously? This is necessary for example when adding markers
    127         // as the result of a resource change notification, since at that point the
    128         // resource tree is locked for modifications and attempting to create a
    129         // marker will throw a org.eclipse.core.internal.resources.ResourceException.
    130         if (async) {
    131             AdtPlugin.getDisplay().asyncExec(new Runnable() {
    132                 @Override
    133                 public void run() {
    134                     updateMarkers(false);
    135                 }
    136             });
    137             return;
    138         }
    139 
    140         // First clear out old/previous markers
    141         for (IResource resource : mScannedResources) {
    142             try {
    143                 if (resource.exists()) {
    144                     int depth = resource instanceof IFolder ? DEPTH_ONE : DEPTH_ZERO;
    145                     resource.deleteMarkers(MARKER_AAPT_COMPILE, true, depth);
    146                 }
    147             } catch (CoreException ce) {
    148                 // Pass
    149             }
    150         }
    151 
    152         // Add new errors
    153         if (mErrors != null && mErrors.size() > 0) {
    154             List<String> errors = new ArrayList<String>();
    155             for (Pair<IResource, String> pair : mErrors) {
    156                 errors.add(pair.getSecond());
    157             }
    158             AaptParser.parseOutput(errors, mProject);
    159         }
    160     }
    161 
    162     @Override
    163     public boolean needsFullAapt() {
    164         // returns true if it was explicitly requested or if a file that has errors was modified.
    165         // This handles the case where an edit doesn't add any new id but fix a compile error.
    166         return super.needsFullAapt() || hasModifiedFilesWithErrors();
    167     }
    168 
    169     /**
    170      * Returns true if any of the scanned resources has an error marker on it.
    171      */
    172     private boolean hasModifiedFilesWithErrors() {
    173         for (IResource resource : mScannedResources) {
    174             try {
    175                 int depth = resource instanceof IFolder ? DEPTH_ONE : DEPTH_ZERO;
    176                 if (resource.exists()) {
    177                     IMarker[] markers = resource.findMarkers(IMarker.PROBLEM,
    178                             true /*includeSubtypes*/, depth);
    179                     for (IMarker marker : markers) {
    180                         if (marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO) ==
    181                                 IMarker.SEVERITY_ERROR) {
    182                             return true;
    183                         }
    184                     }
    185                 }
    186             } catch (CoreException ce) {
    187                 // Pass
    188             }
    189         }
    190 
    191         return false;
    192     }
    193 
    194     @Override
    195     protected void requestFullAapt() {
    196         super.requestFullAapt();
    197 
    198         if (mCurrentFile != null) {
    199             if (mFullAaptProjects == null) {
    200                 mFullAaptProjects = new HashSet<IProject>();
    201             }
    202             mFullAaptProjects.add(mCurrentFile.getProject());
    203         } else {
    204             assert false : "No current context to apply IdeScanningContext to";
    205         }
    206     }
    207 
    208     /**
    209      * Returns the collection of projects that scanned resources have requested
    210      * a full aapt for.
    211      *
    212      * @return a collection of projects that scanned resources requested full
    213      *         aapt runs for, or null
    214      */
    215     public Collection<IProject> getAaptRequestedProjects() {
    216         return mFullAaptProjects;
    217     }
    218 
    219     @Override
    220     public boolean checkValue(@Nullable String uri, @NonNull String name, @NonNull String value) {
    221         if (!mValidate) {
    222             return true;
    223         }
    224 
    225         if (!needsFullAapt() && mAttributeMap != null && ANDROID_URI.equals(uri)) {
    226             AttributeInfo info = mAttributeMap.get(name);
    227             if (info != null && !info.isValid(value, mRepository, mFrameworkResources)) {
    228                 return false;
    229             }
    230         }
    231 
    232         return super.checkValue(uri, name, value);
    233     }
    234 }
    235