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      */
IdeScanningContext(@onNull ResourceRepository repository, @NonNull IProject project, boolean validate)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
addError(@onNull String error)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      */
startScanning(@onNull IResource resource)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      */
finishScanning(@onNull IResource resource)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      */
updateMarkers(boolean async)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
needsFullAapt()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      */
hasModifiedFilesWithErrors()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
requestFullAapt()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      */
getAaptRequestedProjects()215     public Collection<IProject> getAaptRequestedProjects() {
216         return mFullAaptProjects;
217     }
218 
219     @Override
checkValue(@ullable String uri, @NonNull String name, @NonNull String value)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