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