| /******************************************************************************* |
| * Copyright (c) 2000, 2008 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| * Sonatype Inc. - adopted to work outside of eclipse workspace |
| *******************************************************************************/ |
| package org.eclipse.tycho.core.osgitools; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| import org.codehaus.plexus.component.annotations.Component; |
| import org.codehaus.plexus.component.annotations.Requirement; |
| import org.eclipse.osgi.internal.resolver.ExportPackageDescriptionImpl; |
| import org.eclipse.osgi.service.resolver.BaseDescription; |
| import org.eclipse.osgi.service.resolver.BundleDescription; |
| import org.eclipse.osgi.service.resolver.BundleSpecification; |
| import org.eclipse.osgi.service.resolver.ExportPackageDescription; |
| import org.eclipse.osgi.service.resolver.HostSpecification; |
| import org.eclipse.osgi.service.resolver.ImportPackageSpecification; |
| import org.eclipse.osgi.service.resolver.StateHelper; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.eclipse.tycho.classpath.ClasspathEntry.AccessRule; |
| import org.eclipse.tycho.core.osgitools.DefaultClasspathEntry.DefaultAccessRule; |
| import org.osgi.framework.Constants; |
| |
| /** |
| * Helper class that computes compile dependencies of a bundle project. |
| * |
| * Code below is copy&paste of org.eclipse.pde.internal.core.RequiredPluginsClasspathContainer |
| * adopted to work outside of Eclipse runtime. |
| * |
| * Note that some functionality, namely SecondaryDependencies, ExtraClasspathEntries and |
| * isPatchFragment, has been removed due to time constraints. |
| */ |
| @Component(role = DependencyComputer.class) |
| public class DependencyComputer { |
| |
| @Requirement |
| private BundleReader manifestReader; |
| |
| public static class DependencyEntry { |
| public final BundleDescription desc; |
| public final List<AccessRule> rules; |
| |
| public DependencyEntry(BundleDescription desc, List<AccessRule> rules) { |
| this.desc = desc; |
| this.rules = rules; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((desc == null) ? 0 : desc.hashCode()); |
| result = prime * result + ((rules == null) ? 0 : rules.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; |
| DependencyEntry other = (DependencyEntry) obj; |
| if (desc == null) { |
| if (other.desc != null) |
| return false; |
| } else if (!desc.equals(other.desc)) |
| return false; |
| if (rules == null) { |
| if (other.rules != null) |
| return false; |
| } else if (!rules.equals(other.rules)) |
| return false; |
| return true; |
| } |
| |
| } |
| |
| public List<DependencyEntry> computeDependencies(StateHelper helper, BundleDescription desc) { |
| ArrayList<DependencyEntry> entries = new ArrayList<>(); |
| |
| if (desc == null) |
| return entries; |
| |
| Map<BundleDescription, ArrayList<AccessRule>> map = retrieveVisiblePackagesFromState(helper, desc); |
| |
| HashSet<BundleDescription> added = new HashSet<>(); |
| |
| // to avoid cycles, e.g. when a bundle imports a package it exports |
| added.add(desc); |
| |
| HostSpecification host = desc.getHost(); |
| if (host != null) { |
| addHostPlugin(host, added, map, entries); |
| } |
| |
| // add dependencies |
| BundleSpecification[] required = desc.getRequiredBundles(); |
| for (BundleSpecification required1 : required) { |
| addDependency((BundleDescription) required1.getSupplier(), added, map, entries); |
| } |
| |
| // addSecondaryDependencies(desc, added, entries); |
| |
| // add Import-Package |
| // sort by symbolicName_version to get a consistent order |
| Map<String, BundleDescription> sortedMap = new TreeMap<>(); |
| for (BundleDescription bundle : map.keySet()) { |
| sortedMap.put(bundle.toString(), bundle); |
| } |
| for (BundleDescription bundle : sortedMap.values()) { |
| addDependencyViaImportPackage(bundle, added, map, entries); |
| } |
| |
| // addExtraClasspathEntries(added, entries); |
| |
| // for (int i = 0; i < entries.size(); i++) { |
| // System.err.println(i + "\t" + entries.get(i).desc); |
| // } |
| |
| return entries; |
| } |
| |
| private Map<BundleDescription, ArrayList<AccessRule>> retrieveVisiblePackagesFromState(StateHelper helper, |
| BundleDescription desc) { |
| Map<BundleDescription, ArrayList<AccessRule>> visiblePackages = new HashMap<>(); |
| addVisiblePackagesFromState(helper, desc, visiblePackages); |
| if (desc.getHost() != null) |
| addVisiblePackagesFromState(helper, (BundleDescription) desc.getHost().getSupplier(), visiblePackages); |
| return visiblePackages; |
| } |
| |
| private void addVisiblePackagesFromState(StateHelper helper, BundleDescription desc, |
| Map<BundleDescription, ArrayList<AccessRule>> visiblePackages) { |
| if (desc == null) |
| return; |
| ExportPackageDescription[] exports = helper.getVisiblePackages(desc, StateHelper.VISIBLE_INCLUDE_EE_PACKAGES); |
| for (ExportPackageDescription export : exports) { |
| BundleDescription exporter = export.getExporter(); |
| if (exporter == null) |
| continue; |
| ArrayList<AccessRule> list = visiblePackages.get(exporter); |
| if (list == null) |
| list = new ArrayList<>(); |
| AccessRule rule = getRule(helper, desc, export); |
| if (!list.contains(rule)) |
| list.add(rule); |
| visiblePackages.put(exporter, list); |
| } |
| } |
| |
| private AccessRule getRule(StateHelper helper, BundleDescription desc, ExportPackageDescription export) { |
| boolean discouraged = helper.getAccessCode(desc, export) == StateHelper.ACCESS_DISCOURAGED; |
| String name = export.getName(); |
| String path = (name.equals(".")) ? "*" : name.replaceAll("\\.", "/") + "/*"; |
| return new DefaultAccessRule(path, discouraged); |
| } |
| |
| protected void addDependencyViaImportPackage(BundleDescription desc, HashSet<BundleDescription> added, |
| Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) { |
| if (desc == null || !added.add(desc)) |
| return; |
| |
| addPlugin(desc, true, map, entries); |
| |
| if (hasExtensibleAPI(desc) && desc.getContainingState() != null) { |
| BundleDescription[] fragments = desc.getFragments(); |
| for (BundleDescription fragment : fragments) { |
| if (fragment.isResolved()) { |
| addDependencyViaImportPackage(fragment, added, map, entries); |
| } |
| } |
| } |
| } |
| |
| private void addDependency(BundleDescription desc, HashSet<BundleDescription> added, |
| Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) { |
| addDependency(desc, added, map, entries, true); |
| } |
| |
| private void addDependency(BundleDescription desc, HashSet<BundleDescription> added, |
| Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries, boolean useInclusion) { |
| if (desc == null || !added.add(desc)) |
| return; |
| |
| BundleDescription[] fragments = hasExtensibleAPI(desc) ? desc.getFragments() : new BundleDescription[0]; |
| |
| // add fragment patches before host |
| for (BundleDescription fragment : fragments) { |
| if (fragment.isResolved() && isPatchFragment(fragment)) { |
| addDependency(fragment, added, map, entries, useInclusion); |
| } |
| } |
| |
| addPlugin(desc, useInclusion, map, entries); |
| |
| // add fragments that are not patches after the host |
| for (BundleDescription fragment : fragments) { |
| if (fragment.isResolved() && !isPatchFragment(fragment)) { |
| addDependency(fragment, added, map, entries, useInclusion); |
| } |
| } |
| |
| BundleSpecification[] required = desc.getRequiredBundles(); |
| for (BundleSpecification required1 : required) { |
| addDependency((BundleDescription) required1.getSupplier(), added, map, entries, useInclusion); |
| } |
| } |
| |
| private boolean isPatchFragment(BundleDescription bundleDescription) { |
| return false; // TODO |
| } |
| |
| private boolean addPlugin(BundleDescription desc, boolean useInclusions, |
| Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) { |
| List<AccessRule> rules = useInclusions ? getInclusions(map, desc) : null; |
| DependencyEntry entry = new DependencyEntry(desc, rules); |
| if (!entries.contains(entry)) |
| entries.add(entry); |
| return true; |
| } |
| |
| private List<AccessRule> getInclusions(Map<BundleDescription, ArrayList<AccessRule>> map, BundleDescription desc) { |
| ArrayList<AccessRule> rules; |
| |
| if (desc.getHost() != null) |
| rules = map.get((BundleDescription) desc.getHost().getSupplier()); |
| else |
| rules = map.get(desc); |
| |
| return rules != null ? rules : new ArrayList<AccessRule>(); |
| } |
| |
| private void addHostPlugin(HostSpecification hostSpec, HashSet<BundleDescription> added, |
| Map<BundleDescription, ArrayList<AccessRule>> map, ArrayList<DependencyEntry> entries) { |
| BaseDescription desc = hostSpec.getSupplier(); |
| |
| if (desc instanceof BundleDescription) { |
| BundleDescription host = (BundleDescription) desc; |
| |
| // add host plug-in |
| if (added.add(host) && addPlugin(host, false, map, entries)) { |
| BundleSpecification[] required = host.getRequiredBundles(); |
| for (BundleSpecification required1 : required) { |
| addDependency((BundleDescription) required1.getSupplier(), added, map, entries); |
| } |
| |
| // add Import-Package |
| ImportPackageSpecification[] imports = host.getImportPackages(); |
| for (ImportPackageSpecification import1 : imports) { |
| BaseDescription supplier = import1.getSupplier(); |
| if (supplier instanceof ExportPackageDescription) { |
| addDependencyViaImportPackage(((ExportPackageDescription) supplier).getExporter(), added, map, |
| entries); |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean hasExtensibleAPI(BundleDescription desc) { |
| // TODO re-enable Eclipse-ExtensibleAPI |
| return true; //"true".equals(state.getManifestAttribute(desc, "Eclipse-ExtensibleAPI")); |
| } |
| |
| /** |
| * Although totally not obvious from the specification text, section 3.15 "Extension Bundles" of |
| * OSGi Core Spec apparently says that framework extension bundles can export additional |
| * packaged of the underlying JRE. More specific explanation is provided in [1] and I verified |
| * that at least Equinox 3.7.1 does indeed behave like described. |
| * <p/> |
| * There does not seem to be a way to tell which packages exported by a framework extension |
| * bundle are supposed to come from JRE and which from the bundle itself, so returned classpath |
| * access rules include all packages exported by the framework extension bundles. |
| * |
| * [1] http://blog.meschberger.ch/2008/10/osgi-bundles-require-classes-from.html |
| */ |
| public List<AccessRule> computeBootClasspathExtraAccessRules(StateHelper helper, BundleDescription desc) { |
| List<AccessRule> result = new ArrayList<>(); |
| ExportPackageDescription[] exports = helper.getVisiblePackages(desc); |
| for (ExportPackageDescription export : exports) { |
| BundleDescription host = export.getExporter(); |
| BaseDescription fragment = ((ExportPackageDescriptionImpl) export).getFragmentDeclaration(); |
| if (host.getBundleId() == 0 && fragment != null && isFrameworkExtension(fragment.getSupplier())) { |
| result.add(getRule(helper, host, export)); |
| } |
| } |
| return result; |
| } |
| |
| private boolean isFrameworkExtension(BundleDescription bundle) { |
| OsgiManifest mf = manifestReader.loadManifest(new File(bundle.getLocation())); |
| ManifestElement[] elements = mf.getManifestElements(Constants.FRAGMENT_HOST); |
| if (elements.length == 1) { |
| if (Constants.EXTENSION_FRAMEWORK.equals(elements[0].getDirective(Constants.EXTENSION_DIRECTIVE))) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |