| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.ide.eclipse.adt.internal.resources.manager; |
| |
| import static com.android.SdkConstants.ANDROID_URI; |
| import static com.android.ide.eclipse.adt.AdtConstants.MARKER_AAPT_COMPILE; |
| import static org.eclipse.core.resources.IResource.DEPTH_ONE; |
| import static org.eclipse.core.resources.IResource.DEPTH_ZERO; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.common.resources.ResourceRepository; |
| import com.android.ide.common.resources.ScanningContext; |
| import com.android.ide.common.resources.platform.AttributeInfo; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.build.AaptParser; |
| import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; |
| import com.android.ide.eclipse.adt.internal.sdk.Sdk; |
| import com.android.utils.Pair; |
| |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * An {@link IdeScanningContext} is a specialized {@link ScanningContext} which |
| * carries extra information about the scanning state, such as which file is |
| * currently being scanned, and which files have been scanned in the past, such |
| * that at the end of a scan we can mark and clear errors, etc. |
| */ |
| public class IdeScanningContext extends ScanningContext { |
| private final IProject mProject; |
| private final List<IResource> mScannedResources = new ArrayList<IResource>(); |
| private IResource mCurrentFile; |
| private List<Pair<IResource, String>> mErrors; |
| private Set<IProject> mFullAaptProjects; |
| private boolean mValidate; |
| private Map<String, AttributeInfo> mAttributeMap; |
| private ResourceRepository mFrameworkResources; |
| |
| /** |
| * Constructs a new {@link IdeScanningContext} |
| * |
| * @param repository the associated {@link ResourceRepository} |
| * @param project the associated project |
| * @param validate if true, check that the attributes and resources are |
| * valid and if not request a full AAPT check |
| */ |
| public IdeScanningContext(@NonNull ResourceRepository repository, @NonNull IProject project, |
| boolean validate) { |
| super(repository); |
| mProject = project; |
| mValidate = validate; |
| |
| Sdk sdk = Sdk.getCurrent(); |
| if (sdk != null) { |
| AndroidTargetData targetData = sdk.getTargetData(project); |
| if (targetData != null) { |
| mAttributeMap = targetData.getAttributeMap(); |
| mFrameworkResources = targetData.getFrameworkResources(); |
| } |
| } |
| } |
| |
| @Override |
| public void addError(@NonNull String error) { |
| super.addError(error); |
| |
| if (mErrors == null) { |
| mErrors = new ArrayList<Pair<IResource,String>>(); |
| } |
| mErrors.add(Pair.of(mCurrentFile, error)); |
| } |
| |
| /** |
| * Notifies the context that the given resource is about to be scanned. |
| * |
| * @param resource the resource about to be scanned |
| */ |
| public void startScanning(@NonNull IResource resource) { |
| assert mCurrentFile == null : mCurrentFile; |
| mCurrentFile = resource; |
| mScannedResources.add(resource); |
| } |
| |
| /** |
| * Notifies the context that the given resource has been scanned. |
| * |
| * @param resource the resource that was scanned |
| */ |
| public void finishScanning(@NonNull IResource resource) { |
| assert mCurrentFile != null; |
| mCurrentFile = null; |
| } |
| |
| /** |
| * Process any errors found to add error markers in the affected files (and |
| * also clear up any aapt errors in files that are no longer applicable) |
| * |
| * @param async if true, delay updating markers until the next display |
| * thread event loop update |
| */ |
| public void updateMarkers(boolean async) { |
| // Run asynchronously? This is necessary for example when adding markers |
| // as the result of a resource change notification, since at that point the |
| // resource tree is locked for modifications and attempting to create a |
| // marker will throw a org.eclipse.core.internal.resources.ResourceException. |
| if (async) { |
| AdtPlugin.getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| updateMarkers(false); |
| } |
| }); |
| return; |
| } |
| |
| // First clear out old/previous markers |
| for (IResource resource : mScannedResources) { |
| try { |
| if (resource.exists()) { |
| int depth = resource instanceof IFolder ? DEPTH_ONE : DEPTH_ZERO; |
| resource.deleteMarkers(MARKER_AAPT_COMPILE, true, depth); |
| } |
| } catch (CoreException ce) { |
| // Pass |
| } |
| } |
| |
| // Add new errors |
| if (mErrors != null && mErrors.size() > 0) { |
| List<String> errors = new ArrayList<String>(); |
| for (Pair<IResource, String> pair : mErrors) { |
| errors.add(pair.getSecond()); |
| } |
| AaptParser.parseOutput(errors, mProject); |
| } |
| } |
| |
| @Override |
| public boolean needsFullAapt() { |
| // returns true if it was explicitly requested or if a file that has errors was modified. |
| // This handles the case where an edit doesn't add any new id but fix a compile error. |
| return super.needsFullAapt() || hasModifiedFilesWithErrors(); |
| } |
| |
| /** |
| * Returns true if any of the scanned resources has an error marker on it. |
| */ |
| private boolean hasModifiedFilesWithErrors() { |
| for (IResource resource : mScannedResources) { |
| try { |
| int depth = resource instanceof IFolder ? DEPTH_ONE : DEPTH_ZERO; |
| if (resource.exists()) { |
| IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, |
| true /*includeSubtypes*/, depth); |
| for (IMarker marker : markers) { |
| if (marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO) == |
| IMarker.SEVERITY_ERROR) { |
| return true; |
| } |
| } |
| } |
| } catch (CoreException ce) { |
| // Pass |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| protected void requestFullAapt() { |
| super.requestFullAapt(); |
| |
| if (mCurrentFile != null) { |
| if (mFullAaptProjects == null) { |
| mFullAaptProjects = new HashSet<IProject>(); |
| } |
| mFullAaptProjects.add(mCurrentFile.getProject()); |
| } else { |
| assert false : "No current context to apply IdeScanningContext to"; |
| } |
| } |
| |
| /** |
| * Returns the collection of projects that scanned resources have requested |
| * a full aapt for. |
| * |
| * @return a collection of projects that scanned resources requested full |
| * aapt runs for, or null |
| */ |
| public Collection<IProject> getAaptRequestedProjects() { |
| return mFullAaptProjects; |
| } |
| |
| @Override |
| public boolean checkValue(@Nullable String uri, @NonNull String name, @NonNull String value) { |
| if (!mValidate) { |
| return true; |
| } |
| |
| if (!needsFullAapt() && mAttributeMap != null && ANDROID_URI.equals(uri)) { |
| AttributeInfo info = mAttributeMap.get(name); |
| if (info != null && !info.isValid(value, mRepository, mFrameworkResources)) { |
| return false; |
| } |
| } |
| |
| return super.checkValue(uri, name, value); |
| } |
| } |