blob: 1fbf35a915b1541388a57616f100354b68f68d05 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2009 - 2012 SpringSource, a division of VMware, Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.virgo.ide.facet.core;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.PackageNotFoundException;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.virgo.ide.facet.internal.core.Plan;
import org.eclipse.virgo.ide.facet.internal.core.PlanReader;
import org.eclipse.virgo.ide.facet.internal.core.PlanReference;
import org.eclipse.virgo.ide.par.Bundle;
import org.eclipse.virgo.ide.par.Par;
import org.eclipse.virgo.ide.par.ParPackage;
import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework;
/**
* Utility to check if the given {@link IResource} belongs to a project that has the par or bundle facet.
*
* @author Christian Dupuis
* @author Leo Dos Santos
* @author GianMaria Romanato
* @since 1.0.0
*/
public class FacetUtils {
/**
* Checks if a given {@link IResource} has the bundle facet.
*/
public static boolean isBundleProject(IResource resource) {
return hasNature(resource, JavaCore.NATURE_ID) && hasProjectFacet(resource, FacetCorePlugin.BUNDLE_FACET_ID);
}
/**
* Checks if a given {@link IResource} has the par facet.
*/
public static boolean isParProject(IResource resource) {
return hasProjectFacet(resource, FacetCorePlugin.PAR_FACET_ID);
}
/**
* Checks if a given {@link IResource} has the par facet.
*/
public static boolean isPlanProject(IResource resource) {
return hasProjectFacet(resource, FacetCorePlugin.PLAN_FACET_ID);
}
/**
* Checks if a {@link IResource} has a given project facet.
*/
public static boolean hasProjectFacet(IResource resource, String facetId) {
if (resource != null && resource.isAccessible()) {
try {
return FacetedProjectFramework.hasProjectFacet(resource.getProject(), facetId);
} catch (CoreException e) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, FacetCorePlugin.PLUGIN_ID, "An error occurred inspecting project facet", e));
}
}
return false;
}
/**
* Checks if a {@link IResource} has a given project nature.
*/
public static boolean hasNature(IResource resource, String natureId) {
if (resource != null && resource.isAccessible()) {
IProject project = resource.getProject();
if (project != null) {
try {
return project.hasNature(natureId);
} catch (CoreException e) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, FacetCorePlugin.PLUGIN_ID, "An error occurred inspecting project nature", e));
}
}
}
return false;
}
/**
* Returns all bundle project in the current workspace regardless weather they are open or closed.
*/
public static IProject[] getBundleProjects() {
List<IProject> bundles = new ArrayList<IProject>();
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (IProject candidate : projects) {
if (FacetUtils.isBundleProject(candidate)) {
bundles.add(candidate);
}
}
return bundles.toArray(new IProject[bundles.size()]);
}
public static IProject[] getParProjects(IProject project) {
Set<IProject> bundles = new HashSet<IProject>();
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (IProject candidate : projects) {
if (FacetUtils.isParProject(candidate)) {
if (Arrays.asList(getBundleProjects(candidate)).contains(project)) {
bundles.add(candidate);
}
}
}
return bundles.toArray(new IProject[bundles.size()]);
}
public static IProject[] getBundleProjects(IProject parProject) {
Set<IProject> bundles = new HashSet<IProject>();
if (isParProject(parProject)) {
Par par = getParDefinition(parProject);
if (par != null && par.getBundle() != null) {
for (Bundle bundle : par.getBundle()) {
IProject bundleProject = ResourcesPlugin.getWorkspace().getRoot().getProject(bundle.getSymbolicName());
if (FacetUtils.isBundleProject(bundleProject)) {
bundles.add(bundleProject);
}
}
}
}
return bundles.toArray(new IProject[bundles.size()]);
}
public static Par getParDefinition(IProject project) {
// Create a resource set to hold the resources.
ResourceSet resourceSet = new ResourceSetImpl();
// Register the package to ensure it is available during loading.
resourceSet.getPackageRegistry().put(ParPackage.eNS_URI, ParPackage.eINSTANCE);
File parFile = new File(new File(project.getLocation().toString() + File.separatorChar + ".settings"),
"org.eclipse.virgo.ide.runtime.core.par.xml");
if (parFile.exists()) {
URI fileUri = URI.createFileURI(parFile.toString());
Resource resource = null;
try {
resource = resourceSet.getResource(fileUri, true);
} catch (WrappedException e) {
if (e.getCause() instanceof PackageNotFoundException) {
// Handle case where we need to update old par file format.
try {
BufferedReader br = new BufferedReader(new FileReader(parFile));
StringBuilder sb = new StringBuilder();
String next = br.readLine();
do {
next = next.replaceAll("http:///com/springsource/server/ide/par.ecore", "http://eclipse.org/virgo/par.ecore");
next = next.replaceAll("com\\.springsource\\.server", "org.eclipse.virgo");
sb.append(next + "\n");
next = br.readLine();
} while (next != null);
br.close();
BufferedWriter bw = new BufferedWriter(new FileWriter(parFile));
bw.write(sb.toString());
bw.close();
project.refreshLocal(IResource.DEPTH_INFINITE, null);
resource = resourceSet.getResource(fileUri, true);
} catch (IOException e1) {
throw new RuntimeException(e1);
} catch (CoreException e2) {
throw new RuntimeException(e2);
}
}
}
return (Par) resource.getContents().iterator().next();
}
return null;
}
public static IFile getParFile(IProject project) {
IResource resource = project.findMember(new Path(".settings").append("org.eclipse.virgo.ide.runtime.core.par.xml"));
if (resource instanceof IFile) {
return (IFile) resource;
}
return null;
}
/**
* Gets all the plan files found in the given project.
*
* @param project
* @return
*/
public static Collection<IFile> getPlansInPlanProject(IProject project) {
if (!isPlanProject(project)) {
return Collections.emptyList();
}
final List<IFile> planFiles = new ArrayList<IFile>();
// Collect output locations if java project
final Set<IPath> outputLocations = new HashSet<IPath>();
try {
if (FacetUtils.hasNature(project, JavaCore.NATURE_ID)) {
IJavaProject je = JavaCore.create(project);
try {
outputLocations.add(je.getOutputLocation());
for (IClasspathEntry entry : je.getRawClasspath()) {
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
if (entry.getOutputLocation() != null) {
outputLocations.add(entry.getOutputLocation());
}
}
}
} catch (JavaModelException e) {
// safe to ignore
}
}
project.accept(new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
if (resource.isTeamPrivateMember() || resource.isDerived()) {
return false;
}
if (resource instanceof IFile && "plan".equals(resource.getFileExtension())) {
planFiles.add((IFile) resource);
} else if (resource instanceof IContainer) {
IPath path = ((IContainer) resource).getFullPath();
for (IPath outputLocation : outputLocations) {
if (outputLocation.isPrefixOf(path)) {
return false;
}
}
return true;
}
return true;
}
});
} catch (CoreException e) {
// TODO CD log exception
}
return planFiles;
}
/**
* Returns all the plans in the workspace as a map project to list of plan files
*
* @return
*/
private static Map<IProject, Collection<IFile>> getPlansInWorkspace() {
Map<IProject, Collection<IFile>> plans = new HashMap<IProject, Collection<IFile>>();
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (IProject iProject : projects) {
if (iProject.isOpen() && isPlanProject(iProject)) {
Collection<IFile> ps = getPlansInPlanProject(iProject);
plans.put(iProject, ps);
}
}
return plans;
}
/**
* Returns the list of nested plans for a given plan file, including the given plan file as the first one. The look
* only in the planFile project, unless the project is also a Java project. In such case, the project classpath is
* used to look for plans in required projects.
*
* @param planFile the plan file
* @param recurse whether the method should return only direct children of the file (false) or recurse and collect
* all descendants (true)
* @return
*/
public static List<IFile> getNestedPlanFiles(IFile planFile, boolean recurse) {
if (!isPlanProject(planFile)) {
return Collections.emptyList();
}
// parse the top level plan file
PlanReader reader = new PlanReader();
Plan topLevelPlan;
try {
topLevelPlan = reader.read(planFile);
} catch (Exception e1) {
return Collections.emptyList();
}
List<PlanReference> nestedReferences = topLevelPlan.getNestedPlans();
if (nestedReferences.isEmpty()) {
return Collections.emptyList();
}
List<IFile> nestedPlanFiles = new ArrayList<IFile>();
/*
* examine the containing Java project class path (if a Java projecT) and find all the required Java projects.
* Nested plans will be searched not only in the current project but also in its dependencies
*/
List<IProject> orderedProjects = getOrderedProjectDependencies(planFile.getProject());
orderedProjects.add(0, planFile.getProject());
Map<IProject, Collection<IFile>> allPlans = FacetUtils.getPlansInWorkspace();
// used for searching a nested plan that is referred only by name (no version)
Map<String, Plan> name2PlanLookup = new HashMap<String, Plan>();
// used for searching a nested plan that is referred by name and version
Map<PlanReference, Plan> ref2Plan = new HashMap<PlanReference, Plan>();
// plan to related file
Map<PlanReference, IFile> ref2File = new HashMap<PlanReference, IFile>();
// loop over the list of ordered projects and search for plans
for (IProject iProject : orderedProjects) {
Collection<IFile> candidates = allPlans.get(iProject);
if (candidates != null) {
for (IFile iFile : candidates) {
// ignore self
if (!planFile.equals(iFile)) {
try {
Plan p = reader.read(iFile);
PlanReference r = p.asRefence();
/*
* in case of duplicate plans (same name and version) first found in classpath wins and
* found is assumed to be the right one
*/
if (!ref2Plan.containsKey(r)) {
// add for name+version lookup
ref2Plan.put(r, p);
/*
* in case of duplicates plans with the same name and different version if an outer plan
* is referring to a nested plan via name only, the first found in classpath wins
*/
if (!name2PlanLookup.containsKey(r.getName())) {
name2PlanLookup.put(r.getName(), p);
}
ref2File.put(r, iFile);
}
} catch (Exception e) {
// ignore
}
}
}
}
}
// finally compute the list of nested plans. Use a queue instead of recursion.
Queue<PlanReference> toBeProcessed = new ArrayBlockingQueue<PlanReference>(ref2Plan.size() + 1);
Set<PlanReference> alreadyProcessed = new HashSet<PlanReference>();
toBeProcessed.addAll(nestedReferences);
while (toBeProcessed.peek() != null) {
PlanReference planReference = toBeProcessed.poll();
alreadyProcessed.add(planReference);
// search for exact match name + version
Plan nestedPlan = ref2Plan.get(planReference); // search for exact match name + version
if (nestedPlan == null && planReference.getVersion() == null) {
nestedPlan = name2PlanLookup.get(planReference.getName());
}
if (nestedPlan != null) {
IFile nestedFile = ref2File.get(nestedPlan.asRefence());
nestedPlanFiles.add(nestedFile);
for (PlanReference aRef : nestedPlan.getNestedPlans()) {
if (recurse && !alreadyProcessed.contains(aRef)) {
toBeProcessed.add(aRef);
}
}
}
}
return nestedPlanFiles;
}
/**
* Returns the ordered list of project dependencies for the given Java project or an empty list if the project is
* not a Java project.
*
* @param project the Java project
* @return the list of required projects
*/
private static List<IProject> getOrderedProjectDependencies(IProject project) {
LinkedHashSet<IProject> projects = new LinkedHashSet<IProject>();
if (FacetUtils.hasNature(project, JavaCore.NATURE_ID)) {
IJavaProject je = JavaCore.create(project);
String[] names;
try {
names = je.getRequiredProjectNames();
for (String prjName : names) {
IProject prj = ResourcesPlugin.getWorkspace().getRoot().getProject(prjName);
if (prj.exists() && prj.isOpen() && isPlanProject(prj)) {
projects.add(prj);
projects.addAll(getOrderedProjectDependencies(prj));
}
}
} catch (JavaModelException e) {
}
}
return new ArrayList<IProject>(projects);
}
}