/*
* Contributions to FindBugs
* Copyright (C) 2008, Andrei Loskutov
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package de.tobject.findbugs.builder;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.team.internal.core.subscribers.ChangeSet;
import org.eclipse.ui.IAggregateWorkingSet;
import org.eclipse.ui.IWorkingSet;
import de.tobject.findbugs.util.ProjectUtilities;
import de.tobject.findbugs.util.Util;
import edu.umd.cs.findbugs.Project;
/**
* @author Andrei
*/
public class ResourceUtils {
/**
* Convenience empty array of resources.
*/
private static final List<WorkItem> EMPTY = Collections.emptyList();
private ResourceUtils() {
// forbidden
}
public static IPath getOutputLocation(IClasspathEntry classpathEntry, IPath defaultOutputLocation) {
IPath outputLocation = classpathEntry.getOutputLocation();
if (outputLocation != null) {
// this location is workspace relative and starts with project dir
outputLocation = relativeToAbsolute(outputLocation);
} else {
outputLocation = defaultOutputLocation;
}
return outputLocation;
}
public static final class FileCollector implements FileFilter {
private final Pattern pat;
private final Project findBugsProject;
private FileCollector(Pattern pat, Project findBugsProject) {
this.pat = pat;
this.findBugsProject = findBugsProject;
}
public boolean accept(File file) {
if (!file.isDirectory()) {
// add the clzs to the list of files to be analyzed
if (pat.matcher(file.getName()).matches()) {
findBugsProject.addFile(file.getAbsolutePath());
}
}
return false;
}
}
/**
* recurse add all the files matching given name pattern inside the given
* directory and all subdirectories
*/
public static void addFiles(final Project findBugsProject, File clzDir, final Pattern pat) {
if (clzDir.isDirectory()) {
clzDir.listFiles(new FileCollector(pat, findBugsProject));
}
}
/**
* @param relativePath
* workspace relative path
* @return given path if path is not known in workspace
*/
public static IPath relativeToAbsolute(IPath relativePath) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IResource resource = root.findMember(relativePath);
if (resource != null) {
return resource.getLocation();
}
return relativePath;
}
/**
* Returns a list of all <b>Java source related</b> files in a resource
* delta. This is of help when performing an incremental build.
*
* @return Collection A list of all <b>Java source related</b> files to be
* built.
*/
public static List<WorkItem> collectIncremental(IResourceDelta delta) {
// XXX deleted packages should be considered to remove markers
List<WorkItem> result = new ArrayList<WorkItem>();
List<IResourceDelta> foldersDelta = new ArrayList<IResourceDelta>();
IResourceDelta affectedChildren[] = delta.getAffectedChildren();
for (int i = 0; i < affectedChildren.length; i++) {
IResourceDelta childDelta = affectedChildren[i];
IResource child = childDelta.getResource();
if (child.isDerived()) {
continue;
}
int childType = child.getType();
int deltaKind = childDelta.getKind();
if (childType == IResource.FILE) {
if ((deltaKind == IResourceDelta.ADDED || deltaKind == IResourceDelta.CHANGED) && Util.isJavaFile(child)) {
result.add(new WorkItem(child));
}
} else if (childType == IResource.FOLDER) {
if (deltaKind == IResourceDelta.ADDED) {
result.add(new WorkItem(child));
} else if (deltaKind == IResourceDelta.REMOVED) {
// TODO should just remove markers....
IContainer parent = child.getParent();
if (parent instanceof IProject) {
// have to recompute entire project if one of root
// folders is removed
result.clear();
result.add(new WorkItem(parent));
return result;
}
result.add(new WorkItem(parent));
} else if (deltaKind != IResourceDelta.REMOVED) {
foldersDelta.add(childDelta);
}
}
}
for (IResourceDelta childDelta : foldersDelta) {
result.addAll(collectIncremental(childDelta));
}
return result;
}
/**
* Collects and combines the selection which may contain sources from
* different projects and / or multiple sources from same project.
* <p>
* If selection contains hierarchical data (like file and it's parent
* directory), the only topmost element is returned (same for directories
* from projects).
* <p>
* The children from selected parents are not resolved, so that the return
* value contains the 'highest' possible hierarchical elements without
* children.
*
* @param structuredSelection
* @return a map with the project as a key and selected resources as value.
* If project itself was selected, then key is the same as value.
*/
public static Map<IProject, List<WorkItem>> getResourcesPerProject(IStructuredSelection structuredSelection) {
Map<IProject, List<WorkItem>> projectsMap = new HashMap<IProject, List<WorkItem>>();
for (Iterator<?> iter = structuredSelection.iterator(); iter.hasNext();) {
Object element = iter.next();
WorkItem workItem = getWorkItem(element);
if (workItem == null) {
IWorkingSet wset = Util.getAdapter(IWorkingSet.class, element);
if (wset != null) {
mapResources(wset, projectsMap);
continue;
}
// Support for active changesets
ChangeSet set = Util.getAdapter(ChangeSet.class, element);
for (WorkItem change : getResources(set)) {
mapResource(change, projectsMap, true);
}
continue;
}
mapResource(workItem, projectsMap, false);
}
return projectsMap;
}
private static void mapResources(IWorkingSet wset, Map<IProject, List<WorkItem>> projectsMap) {
Set<WorkItem> set = getResources(wset);
for (WorkItem item : set) {
mapResource(item, projectsMap, true);
}
}
/**
* @param wset
* non null working set
* @return non null set with work items, which may be empty
*/
public static Set<WorkItem> getResources(IWorkingSet wset) {
Set<WorkItem> set = new HashSet<WorkItem>();
boolean aggregateWorkingSet = wset.isAggregateWorkingSet();
// IAggregateWorkingSet was introduced in Eclipse 3.5
if (aggregateWorkingSet && wset instanceof IAggregateWorkingSet) {
IAggregateWorkingSet aggr = (IAggregateWorkingSet) wset;
IWorkingSet[] sets = aggr.getComponents();
for (IWorkingSet iWorkingSet : sets) {
set.addAll(getResources(iWorkingSet));
}
} else {
IAdaptable[] elements = wset.getElements();
for (IAdaptable iAdaptable : elements) {
WorkItem item = getWorkItem(iAdaptable);
if (item != null) {
set.add(item);
}
}
}
return set;
}
/**
* Maps the resource into its project
*
* @param resource
* @param projectsMap
*/
private static void mapResource(WorkItem resource, Map<IProject, List<WorkItem>> projectsMap, boolean checkJavaProject) {
IProject project = resource.getProject();
if (checkJavaProject && !ProjectUtilities.isJavaProject(project)) {
// non java projects: can happen only for changesets
return;
}
List<WorkItem> resources = projectsMap.get(project);
if (resources == null) {
resources = new ArrayList<WorkItem>();
projectsMap.put(project, resources);
}
// do not need to check for duplicates, cause user cannot select
// the same element twice
if (!containsParents(resources, resource)) {
resources.add(resource);
}
}
/**
* Extracts only files from a change set
*
* @param set
* @return
*/
@SuppressWarnings("restriction")
public static List<WorkItem> getResources(ChangeSet set) {
if (set != null && !set.isEmpty()) {
IResource[] resources = set.getResources();
List<WorkItem> filtered = new ArrayList<WorkItem>();
for (IResource resource : resources) {
if (resource.getType() == IResource.FILE && !Util.isJavaArtifact(resource)) {
// Ignore non java files
continue;
}
if (resource.exists()) {
// add only resources which are NOT deleted
filtered.add(new WorkItem(resource));
}
}
return filtered;
}
return EMPTY;
}
/**
* @param resources
* @param candidate
* @return true if the given list contains at least one parent of the given
* candidate
*/
private static boolean containsParents(List<WorkItem> resources, WorkItem candidate) {
IPath location = candidate.getPath();
if (location == null) {
// TODO java elements?
return false;
}
for (WorkItem resource : resources) {
if (!resource.isDirectory()) {
continue;
}
IPath parentLoc = resource.getPath();
if (parentLoc != null && parentLoc.isPrefixOf(location)) {
return true;
}
}
return false;
}
/**
* Convenient method to get work items (java related stuff) from adaptables
*
* @param element
* an IAdaptable object which may provide an adapter for
* IResource
* @return resource object or null
*/
@CheckForNull
public static WorkItem getWorkItem(Object element) {
if (element instanceof IResource) {
IResource resource = (IResource) element;
if (resource.getType() == IResource.FILE && !Util.isJavaArtifact(resource) || !resource.isAccessible()) {
// Ignore non java files or deleted/closed files/projects
return null;
}
return new WorkItem((IResource) element);
}
if (element instanceof IJavaElement) {
return new WorkItem((IJavaElement) element);
}
if (element instanceof IAdaptable) {
Object adapter = ((IAdaptable) element).getAdapter(IResource.class);
if (adapter instanceof IResource) {
IResource resource = (IResource) element;
if (resource.getType() == IResource.FILE && !Util.isJavaArtifact(resource) || !resource.isAccessible()) {
// Ignore non java files or deleted/closed files/projects
return null;
}
return new WorkItem(resource);
}
adapter = ((IAdaptable) element).getAdapter(IPackageFragment.class);
if (adapter instanceof IPackageFragment) {
return new WorkItem((IPackageFragment) element);
}
}
return null;
}
/**
* Convenient method to get resources from adaptables
*
* @param element
* an IAdaptable object which may provide an adapter for
* IResource
* @return resource object or null
*/
@javax.annotation.CheckForNull
public static IResource getResource(Object element) {
if (element instanceof IJavaElement) {
return ((IJavaElement) element).getResource();
}
return Util.getAdapter(IResource.class, element);
}
}