/********************************************************************** * Copyright (c) 2005-2009 ant4eclipse project team. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich **********************************************************************/ package org.ant4eclipse.lib.pde.internal.tools; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.ant4eclipse.lib.core.Assure; import org.ant4eclipse.lib.core.exception.Ant4EclipseException; import org.ant4eclipse.lib.core.logging.A4ELogging; import org.ant4eclipse.lib.core.osgi.BundleLayoutResolver; import org.ant4eclipse.lib.core.osgi.ExplodedBundleLayoutResolver; import org.ant4eclipse.lib.core.osgi.JaredBundleLayoutResolver; import org.ant4eclipse.lib.jdt.tools.ResolvedClasspathEntry; import org.ant4eclipse.lib.jdt.tools.ResolvedClasspathEntry.AccessRestrictions; import org.ant4eclipse.lib.pde.PdeExceptionCode; import org.ant4eclipse.lib.pde.model.pluginproject.BundleSource; import org.ant4eclipse.lib.pde.tools.PluginProjectLayoutResolver; import org.ant4eclipse.lib.pde.tools.TargetPlatform; import org.ant4eclipse.lib.platform.model.resource.EclipseProject; import org.eclipse.osgi.internal.resolver.StateHelperImpl; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.service.resolver.BundleSpecification; import org.eclipse.osgi.service.resolver.ExportPackageDescription; /** * <p> * The {@link BundleDependenciesResolver} is a helper class that can be used to resolve the dependencies of a given * bundle. * </p> * * @author Gerd Wütherich (gerd@gerd-wuetherich.de) * @author Nils Hartmann (nils@nilshartmann.net) */ public class BundleDependenciesResolver { public List<BundleDependency> resolveBundleClasspath(BundleDescription description) throws UnresolvedBundleException { return resolveBundleClasspath(description, null, null); } /** * <p> * </p> * * @param description * the bundle description * @param includeOptionalDependencies * @return * @throws UnresolvedBundleException */ public List<BundleDependency> resolveBundleClasspath(final BundleDescription description, TargetPlatform targetPlatform, String[] additionalBundles) throws UnresolvedBundleException { Assure.notNull("description", description); // step 1: throw exception if bundle description is not resolved if (!description.isResolved()) { throw new UnresolvedBundleException(description); } // step 2: if the bundle is a fragment - get the host BundleDescription rootDescription = isFragment(description) ? getHost(description) : description; // step 3: get visible packages that are exported by other bundles List<ExportPackageDescription> allPackageDescriptions = new LinkedList<ExportPackageDescription>(); // step 4: add the host visible packages allPackageDescriptions.addAll(Arrays.asList(StateHelperImpl.getInstance().getVisiblePackages(rootDescription))); // step 5: add the fragment visible packages for (BundleDescription fragmentDescription : rootDescription.getFragments()) { allPackageDescriptions.addAll(Arrays .asList(StateHelperImpl.getInstance().getVisiblePackages(fragmentDescription))); } // step 6: Get exported packages from 'additional bundles' if (additionalBundles != null) { allPackageDescriptions.addAll(addAdditionalPackages(targetPlatform, additionalBundles)); } // step 7: create the result Map<BundleDescription, BundleDependency> map = new HashMap<BundleDescription, BundleDependency>(); List<BundleDependency> result = new ArrayList<BundleDependency>(); for (ExportPackageDescription exportPackageDescription : allPackageDescriptions) { // BundleDescription bundleDescription = exportPackageDescription.getSupplier(); if (isFragment(bundleDescription)) { bundleDescription = getHost(bundleDescription); } BundleDependency bundleDependency; if (map.containsKey(bundleDescription)) { bundleDependency = map.get(bundleDescription); } else { bundleDependency = new BundleDependency(bundleDescription); map.put(bundleDescription, bundleDependency); result.add(bundleDependency); } // bundleDependency.addExportedPackage(exportPackageDescription.getName()); } // return the result return result; } /** * <p> * </p> * * @param packageDescriptions * @param targetPlatform * @param additionalBundles * @return */ private List<ExportPackageDescription> addAdditionalPackages(TargetPlatform targetPlatform, String[] additionalBundles) { List<ExportPackageDescription> result = new LinkedList<ExportPackageDescription>(); for (String additionalBundle : additionalBundles) { A4ELogging.debug("Adding additional bundle '%s'", additionalBundle); BundleDescription resolvedBundle = targetPlatform.getResolvedBundle(additionalBundle, null); if (resolvedBundle == null) { throw new Ant4EclipseException(PdeExceptionCode.SPECIFIED_BUNDLE_NOT_FOUND, additionalBundle, "(any)"); } addAdditionalPackages(result, targetPlatform, resolvedBundle); } return result; } /** * <p> * </p> * * @param exportedPackages * @param targetPlatform * @param resolvedBundle */ private void addAdditionalPackages(List<ExportPackageDescription> exportedPackages, TargetPlatform targetPlatform, BundleDescription resolvedBundle) { // Add exported package from resolvedBundle A4ELogging.debug("Adding packages from '%s' to classpath", resolvedBundle); ExportPackageDescription[] exportPackages = resolvedBundle.getExportPackages(); for (ExportPackageDescription exportPackageDescription : exportPackages) { if (!exportedPackages.contains(exportPackageDescription)) { A4ELogging.debug("Add additional exported package %s", exportPackageDescription); exportedPackages.add(exportPackageDescription); } } // Add packages from re-exported required bundle BundleSpecification[] requiredBundles = resolvedBundle.getRequiredBundles(); for (BundleSpecification bundleSpecification : requiredBundles) { if (bundleSpecification.isExported()) { A4ELogging.debug("Add re-exported bundle %s", bundleSpecification); addAdditionalPackages(exportedPackages, targetPlatform, bundleSpecification.getSupplier().getSupplier()); } } } /** * <p> * Returns <code>true</code> if the given <code>bundleDescription</code> is a fragment. * </p> * * @param bundleDescription * the {@link BundleDescription} * @return <code>true</code> if the given <code>bundleDescription</code> is a fragment. */ public static boolean isFragment(BundleDescription bundleDescription) { // bundle description describes a fragment if bundleDescription.getHost() != null return bundleDescription.getHost() != null; } /** * <p> * Returns the host for the given {@link BundleDescription}. * </p> * * @param bundleDescription * the {@link BundleDescription} * @return the host for the given {@link BundleDescription}. * @throws UnresolvedBundleException */ public static BundleDescription getHost(BundleDescription bundleDescription) throws UnresolvedBundleException { // if (!bundleDescription.isResolved()) { throw new UnresolvedBundleException(bundleDescription); } // bundle description describes a fragment -> // choose host as main bundle description if (isFragment(bundleDescription)) { return bundleDescription.getHost().getHosts()[0]; } // bundle description describes a host -> // return bundle description else { return bundleDescription; } } /** * <p> * Returns a {@link BundleLayoutResolver} for the given {@link BundleDescription}. * </p> * * @param bundleDescription * the given {@link BundleDescription}. * @return a {@link BundleLayoutResolver} for the given {@link BundleDescription}. */ public static BundleLayoutResolver getBundleLayoutResolver(BundleDescription bundleDescription) { // get the bundle source BundleSource bundleSource = (BundleSource) bundleDescription.getUserObject(); // get the location File location = getLocation(bundleDescription); // eclipse project -> PluginProjectLayoutResolver if (bundleSource.isEclipseProject()) { return new PluginProjectLayoutResolver(bundleSource.getAsEclipseProject()); } // directory -> ExplodedBundleLayoutResolver else if (location.isDirectory()) { return new ExplodedBundleLayoutResolver(location); } // jar -> JaredBundleLayoutResolver else { return new JaredBundleLayoutResolver(location, ExpansionDirectory.getExpansionDir()); } } /** * <p> * Returns the location for the given {@link BundleDescription}. * </p> * * @param bundleDescription * the {@link BundleDescription} * @return the location for the given {@link BundleDescription}. */ public static File getLocation(BundleDescription bundleDescription) { // get the bundle source BundleSource bundleSource = (BundleSource) bundleDescription.getUserObject(); // get the location File result = bundleSource.isEclipseProject() ? bundleSource.getAsEclipseProject().getFolder() : bundleSource .getAsFile(); // return result return result; } /** * <p> * The class {@link BundleDependency} represents a dependency to another bundle. It also contains a set of all * packages that are imported by this dependency. * </p> * * @author Gerd Wütherich (gerd@gerd-wuetherich.de) */ public static class BundleDependency { /** the host */ private BundleDescription _bundleDescription; /** list of exported packages for this bundle dependency */ private Set<String> _exportedPackages; /** * <p> * Creates a new instance of type {@link BundleDependency}. * </p> * * @param bundleDescription */ public BundleDependency(BundleDescription bundleDescription) { Assure.notNull("bundleDescription", bundleDescription); this._bundleDescription = bundleDescription; this._exportedPackages = new LinkedHashSet<String>(); } /** * <p> * Returns the {@link ResolvedClasspathEntry}. * </p> * * @return the {@link ResolvedClasspathEntry}. */ public ResolvedClasspathEntry getResolvedClasspathEntry() { // create the list of all contained files List<File> classfiles = new LinkedList<File>(); List<File> sourcefiles = new LinkedList<File>(); // resolve the host BundleLayoutResolver layoutResolver = getBundleLayoutResolver(this._bundleDescription); classfiles.addAll(Arrays.asList(layoutResolver.resolveBundleClasspathEntries())); if (layoutResolver instanceof PluginProjectLayoutResolver) { sourcefiles .addAll(Arrays.asList(((PluginProjectLayoutResolver) layoutResolver).getPluginProjectSourceFolders())); } // resolve the fragments BundleDescription[] fragments = this._bundleDescription.getFragments(); for (BundleDescription fragmentDescription : fragments) { layoutResolver = getBundleLayoutResolver(fragmentDescription); classfiles.addAll(Arrays.asList(layoutResolver.resolveBundleClasspathEntries())); if (layoutResolver instanceof PluginProjectLayoutResolver) { sourcefiles.addAll(Arrays.asList(((PluginProjectLayoutResolver) layoutResolver) .getPluginProjectSourceFolders())); } } // create the access restrictions AccessRestrictions accessRestrictions = new AccessRestrictions(); for (String exportedPackage : this._exportedPackages) { accessRestrictions.addPublicPackage(exportedPackage); } // return the result return new ResolvedClasspathEntry(classfiles.toArray(new File[0]), accessRestrictions, sourcefiles.toArray(new File[0])); } /** * <p> * Returns all referenced eclipse plug-in projects. This information is required to compute the build order between * eclipse plug-in projects. * </p> * * @return a list with referenced eclipse plug-in projects */ public List<EclipseProject> getReferencedPluginProjects() { // create the result list List<EclipseProject> result = new LinkedList<EclipseProject>(); // add the host if it is an eclipse project BundleSource bundleSource = (BundleSource) this._bundleDescription.getUserObject(); if (bundleSource.isEclipseProject()) { result.add(bundleSource.getAsEclipseProject()); } // // add the fragment if it is an eclipse project // for (BundleDescription fragment : getFragments()) { // BundleSource fragmentSource = (BundleSource) fragment.getUserObject(); // // if (fragmentSource.isEclipseProject()) { // result.add(fragmentSource.getAsEclipseProject()); // } // } // return the result return result; } /** * <p> * </p> * * @return */ public BundleDescription getHost() { return this._bundleDescription; } /** * <p> * </p> * * @return */ public BundleDescription[] getFragments() { return this._bundleDescription.getFragments(); } /** * <p> * Adds the package with the specified name as an imported packages. * </p> * * @param packageName * the package name */ private void addExportedPackage(String packageName) { this._exportedPackages.add(packageName); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this._bundleDescription == null) ? 0 : this._bundleDescription.hashCode()); result = prime * result + ((this._exportedPackages == null) ? 0 : this._exportedPackages.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } BundleDependency other = (BundleDependency) obj; if (this._bundleDescription == null) { if (other._bundleDescription != null) { return false; } } else if (!this._bundleDescription.equals(other._bundleDescription)) { return false; } if (this._exportedPackages == null) { if (other._exportedPackages != null) { return false; } } else if (!this._exportedPackages.equals(other._exportedPackages)) { return false; } return true; } } }