blob: d613249375e22ecae8b541f8a93ee6ae4e3162e3 [file] [log] [blame]
/*
* 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);
}
}