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