/********************************************************************** * 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.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; 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.util.Pair; import org.ant4eclipse.lib.core.util.Utilities; import org.ant4eclipse.lib.pde.PdeExceptionCode; import org.ant4eclipse.lib.pde.model.featureproject.FeatureManifest; import org.ant4eclipse.lib.pde.model.featureproject.FeatureManifest.Includes; import org.ant4eclipse.lib.pde.model.featureproject.FeatureManifest.Plugin; import org.ant4eclipse.lib.pde.model.pluginproject.BundleSource; import org.ant4eclipse.lib.pde.tools.PlatformConfiguration; import org.ant4eclipse.lib.pde.tools.ResolvedFeature; import org.ant4eclipse.lib.pde.tools.TargetPlatform; import org.eclipse.osgi.framework.internal.core.FrameworkProperties; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.service.resolver.ResolverError; import org.eclipse.osgi.service.resolver.State; import org.eclipse.osgi.service.resolver.StateHelper; import org.eclipse.osgi.service.resolver.StateObjectFactory; import org.eclipse.osgi.service.resolver.VersionConstraint; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.Version; /** * <p> * A target platform contains different plug-in sets. It defines the target against which plug-ins will be compiled and * tested. * </p> * * @author Gerd Wütherich (gerd@gerd-wuetherich.de) * @author Nils Hartmann (nils@nilshartmann.net) */ public final class TargetPlatformImpl implements TargetPlatform { /** the bundle set that contains the plug-in projects */ private BundleAndFeatureSet _pluginProjectSet; /** contains a list of all the binary bundle sets that belong to this target location */ private List<BundleAndFeatureSet> _binaryBundleSets; /** the target platform configuration */ private PlatformConfiguration _configuration; /** the state object */ private State _state; /** - */ private File[] _targetplatformLocations; /** * <p> * Creates a new instance of type {@link TargetPlatformImpl}. * </p> * * @param pluginProjectSet * the set of all plug-in projects contained in the workspace, may <code>null</code>. * @param binaryBundleSets * an array of bundle sets that contain the binary bundles, may <code>null</code>. * @param configuration * the {@link PlatformConfiguration} of this target platform * @param targetlocations * a list of all targetplatform locations providing the bundles. Neither <code>null</code> nor empty. */ public TargetPlatformImpl(BundleAndFeatureSet pluginProjectSet, BundleAndFeatureSet[] binaryBundleSets, PlatformConfiguration configuration, File[] targetlocations) { Assure.notNull("configuration", configuration); // set the plug-in project set this._pluginProjectSet = pluginProjectSet; // set the binary bundle sets if (binaryBundleSets != null) { this._binaryBundleSets = Arrays.asList(binaryBundleSets); } else { this._binaryBundleSets = new LinkedList<BundleAndFeatureSet>(); } // set the configuration this._configuration = configuration; this._targetplatformLocations = targetlocations; // initialize initialize(); } /** * {@inheritDoc} */ public File[] getLocations() { return this._targetplatformLocations; } /** * {@inheritDoc} */ public PlatformConfiguration getTargetPlatformConfiguration() { return this._configuration; } /** * {@inheritDoc} */ public BundleDescription getResolvedBundle(String symbolicName, Version version) { return this._state.getBundle(symbolicName, version); } /** * {@inheritDoc} */ public BundleDescription[] getBundlesWithResolverErrors() { // create result List<BundleDescription> bundleDescriptions = new LinkedList<BundleDescription>(); // iterate over all descriptions for (BundleDescription description : this._state.getBundles()) { ResolverError[] errors = this._state.getResolverErrors(description); if (errors != null && errors.length > 0) { bundleDescriptions.add(description); } } return bundleDescriptions.toArray(new BundleDescription[0]); } /** * {@inheritDoc} */ private void initialize() { if (this._state == null) { if (this._pluginProjectSet != null) { this._pluginProjectSet.initialize(); } for (BundleAndFeatureSet bundleSet : this._binaryBundleSets) { bundleSet.initialize(); } this._state = resolve(); } } /** * {@inheritDoc} */ public void refresh() { if (this._pluginProjectSet != null) { this._pluginProjectSet.refresh(); } for (BundleAndFeatureSet bundleSet : this._binaryBundleSets) { bundleSet.refresh(); } this._state = resolve(); } public List<File> getAllBundleFiles() { List<BundleDescription> allBundleDescriptions = getAllBundleDescriptions(true); List<File> allBundles = new LinkedList<File>(); for (BundleDescription bundleDescription : allBundleDescriptions) { String location = bundleDescription.getLocation(); if (location == null) { A4ELogging.warn("Bundle '%s_%s' has no location", bundleDescription.getSymbolicName(), bundleDescription.getVersion()); } else { allBundles.add(new File(location)); } } return allBundles; } /** * <p> * Returns a list with a {@link BundleDescription BundleDescriptions} of each bundle that is contained in the plug-in * project set or the binary bundle sets. * </p> * * @param preferProjects * indicates of plug-in projects should be preferred over binary bundles or not. * @return a list with a {@link BundleDescription BundleDescriptions} of each bundle that is contained in the plug-in * project set or the binary bundle sets. */ private List<BundleDescription> getAllBundleDescriptions(boolean preferProjects) { // step 1: create the result list List<BundleDescription> result = new LinkedList<BundleDescription>(); // step 2: add plug-in projects from the plug-in projects list to the result if (this._pluginProjectSet != null) { result.addAll(this._pluginProjectSet.getAllBundleDescriptions()); } // step 3: add bundles from binary bundle sets to the result for (BundleAndFeatureSet binaryBundleSet : this._binaryBundleSets) { for (BundleDescription bundleDescription : binaryBundleSet.getAllBundleDescriptions()) { if ((this._pluginProjectSet != null) && preferProjects && this._pluginProjectSet.containsBundle(bundleDescription.getSymbolicName())) { // TODO: WARNING AUSGEBEN? } else { result.add(bundleDescription); } } } // step 4: return the result return result; } /** * {@inheritDoc} */ public FeatureDescription getFeatureDescription(String id, Version version) { Assure.nonEmpty("id", id); if (version == null || version.equals(Version.emptyVersion)) { return getFeatureDescription(id); } // FeatureDescription featureDescription = this._pluginProjectSet.getFeatureDescription(id, version); // if (featureDescription != null) { A4ELogging.debug("Feature '%s' with version '%s' found in plugin project set", id, version); return featureDescription; } for (BundleAndFeatureSet bundleSet : this._binaryBundleSets) { featureDescription = bundleSet.getFeatureDescription(id, version); if (featureDescription != null) { A4ELogging.debug("Feature '%s' with version '%s' found in binary bundle set", id, version); return featureDescription; } } throw new IllegalArgumentException("Feature with id '" + id + "' and version '" + version + "' not found in target or project set"); } /** * {@inheritDoc} */ public boolean hasFeatureDescription(String id, Version version) { try { getFeatureDescription(id, version); return true; } catch (RuntimeException e) { A4ELogging.debug("Could not find feature '%s' in version '%s' Problem %s.", id, version, e); return false; } } /** * {@inheritDoc} */ public FeatureDescription getFeatureDescription(String id) { Assure.nonEmpty("id", id); FeatureDescription featureDescription = this._pluginProjectSet.getFeatureDescription(id); if (featureDescription != null) { A4ELogging.debug("Feature '%s' found in plugin project set", id); return featureDescription; } // result FeatureDescription result = null; // iterate over feature descriptions for (BundleAndFeatureSet bundleSet : this._binaryBundleSets) { // get the feature manifest featureDescription = bundleSet.getFeatureDescription(id); // if match -> set as result if (featureDescription != null && featureDescription.getFeatureManifest().getId().equals(id)) { if (result == null) { result = featureDescription; A4ELogging.debug("Feature '%s' found in binary bundle set", id); } else { // the current feature description has a higher version, so use this one if (result.getFeatureManifest().getVersion().compareTo(featureDescription.getFeatureManifest().getVersion()) < 0) { A4ELogging.debug("Higher version for feature '%s' found in binary bundle set", id); result = featureDescription; } } } } if (result == null) { throw new IllegalArgumentException("Feature with id '" + id + "' not found in target or project set"); } // return result return result; } /** * {@inheritDoc} */ public boolean hasFeatureDescription(String id) { try { getFeatureDescription(id); return true; } catch (RuntimeException e) { A4ELogging.debug("Could not find feature '%s' Problem %s.", id, e); return false; } } public boolean matchesPlatformFilter(String id) { try { // BundleDescription bundleDescription = getBundleDescription(id); if (bundleDescription == null) { return true; } // String platformFilter = bundleDescription.getPlatformFilter(); if (platformFilter == null) { return true; } String arch = getTargetPlatformConfiguration().getArchitecture(); String os = getTargetPlatformConfiguration().getOperatingSystem(); String ws = getTargetPlatformConfiguration().getWindowingSystem(); Properties dictionary = new Properties(); dictionary.put("osgi.ws", ws); dictionary.put("osgi.os", os); dictionary.put("osgi.arch", arch); Filter filter = FrameworkUtil.createFilter(platformFilter); return filter.match(dictionary); } catch (InvalidSyntaxException e) { e.printStackTrace(); return false; } } /** * {@inheritDoc} */ public BundleDescription getBundleDescription(String id) { Assure.nonEmpty("id", id); // BundleDescription bundleDescription = this._pluginProjectSet.getBundleDescription(id); // if (bundleDescription != null) { return bundleDescription; } // result BundleDescription result = null; // iterate over feature descriptions for (BundleAndFeatureSet bundleSet : this._binaryBundleSets) { // get the feature manifest bundleDescription = bundleSet.getBundleDescription(id); // if match -> set as result if (bundleDescription != null) { if (result == null) { result = bundleDescription; } else { // the current bundle description has a higher version, so use this one if (result.getVersion().compareTo(bundleDescription.getVersion()) < 0) { result = bundleDescription; } } } } return result; } /** * {@inheritDoc} */ public BundleDescription getBundleDescriptionFromWorkspace(String symbolicName) { Assure.nonEmpty("symbolicName", symbolicName); // return this._pluginProjectSet.getBundleDescription(symbolicName); } /** * {@inheritDoc} */ public BundleDescription getBundleDescriptionFromBinaryBundles(String symbolicName) { Assure.nonEmpty("symbolicName", symbolicName); // BundleDescription bundleDescription = null; BundleDescription result = null; // iterate over feature descriptions for (BundleAndFeatureSet bundleSet : this._binaryBundleSets) { // get the feature manifest bundleDescription = bundleSet.getBundleDescription(symbolicName); // if match -> set as result if (bundleDescription != null) { if (result == null) { result = bundleDescription; } else { // the current bundle description has a higher version, so use this one if (result.getVersion().compareTo(bundleDescription.getVersion()) < 0) { result = bundleDescription; } } } } return result; } /** * {@inheritDoc} */ public boolean hasBundleDescription(String id) { return getBundleDescription(id) != null; } /** * {@inheritDoc} */ public ResolvedFeature resolveFeature(Object source, FeatureManifest manifest) { Assure.notNull("source", source); Assure.notNull("manifest", manifest); ResolvedFeature resolvedFeature = new ResolvedFeature(source, manifest); resolvePlugins(manifest, resolvedFeature); resolveIncludes(manifest, resolvedFeature); // 6.3 return result return resolvedFeature; } /** * <p> * </p> * * @param manifest * @param resolvedFeature */ private void resolveIncludes(FeatureManifest manifest, ResolvedFeature resolvedFeature) { // TODO: DependencyGraph!! List<Pair<Includes, FeatureDescription>> result = new LinkedList<Pair<Includes, FeatureDescription>>(); for (Includes includes : manifest.getIncludes()) { if (matches(includes.getOperatingSystem(), includes.getMachineArchitecture(), includes.getWindowingSystem(), includes.getLocale())) { FeatureDescription featureDescription; try { if (includes.getVersion().equals(Version.emptyVersion)) { featureDescription = getFeatureDescription(includes.getId()); } else { featureDescription = getFeatureDescription(includes.getId(), includes.getVersion()); } } catch (IllegalStateException e) { throw new RuntimeException("Can't resolve included feature '" + includes.getId() + "_" + includes.getVersion() + "'."); } catch (IllegalArgumentException e) { throw new RuntimeException("No Feature found for included feature '" + includes.getId() + "_" + includes.getVersion() + "'."); } result.add(new Pair<Includes, FeatureDescription>(includes, featureDescription)); } } resolvedFeature.setIncludesToFeatureDescriptionList(result); } /** * <p> * </p> * * @param manifest * @param resolvedFeature * @throws BuildException */ private void resolvePlugins(FeatureManifest manifest, ResolvedFeature resolvedFeature) { // 4. Retrieve BundlesDescriptions for feature plug-ins Map<BundleDescription, Plugin> map = new HashMap<BundleDescription, Plugin>(); List<BundleDescription> bundleDescriptions = new LinkedList<BundleDescription>(); for (Plugin plugin : manifest.getPlugins()) { if (matches(plugin.getOperatingSystem(), plugin.getMachineArchitecture(), plugin.getWindowingSystem(), plugin.getLocale())) { // if a plug-in reference uses a version, the exact version must be found in the workspace // if a plug-in reference specifies "0.0.0" as version, the newest plug-in found will be used BundleDescription bundleDescription = this._state.getBundle(plugin.getId(), plugin.getVersion().equals(Version.emptyVersion) ? null : plugin.getVersion()); // TODO: NLS if (bundleDescription == null) { throw new Ant4EclipseException(PdeExceptionCode.SPECIFIED_BUNDLE_NOT_FOUND, plugin.getId(), plugin.getVersion()); } // TODO: NLS if (!bundleDescription.isResolved()) { if (!TargetPlatformImpl.platformFilterDoesNotMatch(bundleDescription)) { String resolverErrors = TargetPlatformImpl.dumpResolverErrors(bundleDescription, true); String bundleInfo = TargetPlatformImpl.getBundleInfo(bundleDescription); throw new RuntimeException(String.format("Bundle '%s' is not resolved. Reason:\n%s", bundleInfo, resolverErrors)); } } else { bundleDescriptions.add(bundleDescription); map.put(bundleDescription, plugin); } } } // 5. Sort the bundles BundleDescription[] sortedbundleDescriptions = bundleDescriptions.toArray(new BundleDescription[0]); Object[][] cycles = this._state.getStateHelper().sortBundles(sortedbundleDescriptions); // warn on circular dependencies if ((cycles != null) && (cycles.length > 0)) { // TODO: better error messages A4ELogging.warn("Detected circular dependencies:"); for (Object[] cycle : cycles) { A4ELogging.warn(Arrays.asList(cycle).toString()); } } // 6.1 create result List<Pair<Plugin, BundleDescription>> result = new LinkedList<Pair<Plugin, BundleDescription>>(); for (BundleDescription bundleDescription : sortedbundleDescriptions) { Pair<Plugin, BundleDescription> pair = new Pair<Plugin, BundleDescription>(map.get(bundleDescription), bundleDescription); result.add(pair); } resolvedFeature.setPluginToBundleDescptionList(result); } /** * <p> * </p> * * @return */ private State resolve() { // TODO FrameworkProperties.setProperty("osgi.resolver.usesMode", "ignore"); // step 1: create new state State state = StateObjectFactory.defaultFactory.createState(true); for (BundleDescription bundleDescription : getAllBundleDescriptions(this._configuration.isPreferProjects())) { BundleDescription copy = StateObjectFactory.defaultFactory.createBundleDescription(bundleDescription); copy.setUserObject(bundleDescription.getUserObject()); if (!state.addBundle(copy)) { // TODO: NLS throw new RuntimeException("Could not add bundle '" + bundleDescription + "' to state!"); } if (A4ELogging.isTraceingEnabled()) { A4ELogging.trace("Copied bundle to state: '%s'", getBundleInfo(bundleDescription)); } } // set the platform properties Properties platformProperties = this._configuration.getConfigurationProperties(); if (A4ELogging.isDebuggingEnabled()) { A4ELogging.debug(Utilities.toString("Initializing TargetPlatform with properties: ", platformProperties)); } state.setPlatformProperties(platformProperties); // resolve the state state.resolve(); // log errors if any BundleDescription[] bundleDescriptions = state.getBundles(); // boolean allStatesResolved = true; if (A4ELogging.isDebuggingEnabled()) { String resolverErrors = dumpResolverErrors(bundleDescriptions, true); if (resolverErrors != null && !resolverErrors.trim().equals("")) { A4ELogging.debug(resolverErrors); } } // return the state return state; } /** * Returns <code>true</code> if the current target configuration matches a given system specification. * * @param os * The os to be matched. * @param arch * The architecture to be matched. * @param ws * The windowing system to be matched. * @param nl * The language to be matched. * * @return <code>true</code> <=> The configuration matches the given system specification. */ private boolean matches(String os, String arch, String ws, String nl) { return contains(this._configuration.getOperatingSystem(), os) && contains(this._configuration.getArchitecture(), arch) && contains(this._configuration.getWindowingSystem(), ws) && contains(this._configuration.getLanguageSetting(), nl); } /** * <p> * </p> * * @param element * @param commaSeparatedList * @return */ private static boolean contains(String element, String commaSeparatedList) { // if (element == null || element.trim().equals("")) { return true; } // if (commaSeparatedList == null || commaSeparatedList.trim().equals("")) { return true; } // split the elements String[] elements = commaSeparatedList.split(","); // iterate over all the list elements for (String listElement : elements) { // if (element.trim().equalsIgnoreCase(listElement)) { return true; } } // finally return false return false; } public static boolean platformFilterDoesNotMatch(BundleDescription description) { Assure.notNull("description", description); State state = description.getContainingState(); ResolverError[] errors = state.getResolverErrors(description); return errors != null && errors.length == 1 && errors[0].getType() == ResolverError.PLATFORM_FILTER; } /** * <p> * Returns the resolver errors as a string. * </p> * * @param description * the bundle description * @param dumpHeader * indicates if the header should be dumped or not * @return the resolver errors as a string. */ public static String dumpResolverErrors(BundleDescription description, boolean dumpHeader) { Assure.notNull("description", description); StringBuilder stringBuffer = new StringBuilder(); State state = description.getContainingState(); ResolverError[] errors = state.getResolverErrors(description); if (!description.isResolved() || ((errors != null) && (errors.length != 0))) { if ((errors != null) && (errors.length == 1) && (errors[0].getType() == ResolverError.SINGLETON_SELECTION)) { stringBuffer.append("Not using '"); stringBuffer.append(getBundleInfo(description)); stringBuffer.append("' -- another version resolved\n"); } else { if (dumpHeader) { stringBuffer.append("Could not resolve '"); // stringBuffer.append(getBundleInfo(description)); stringBuffer.append(description.getSymbolicName()); stringBuffer.append("_"); stringBuffer.append(description.getVersion()); stringBuffer.append("' (Location: "); stringBuffer.append(description.getLocation()); stringBuffer.append("):\n"); } if (errors != null) { if (errors.length > 0) { for (int i = 0; i < errors.length; i++) { stringBuffer.append(" "); stringBuffer.append(errors[i]); if (i + 1 < errors.length) { stringBuffer.append("\n"); } } } } } } return stringBuffer.toString(); } public static String dumpResolverErrors(BundleDescription[] descriptions, boolean dumpHeader) { if (descriptions.length < 1) { return ""; } final StringBuilder result = new StringBuilder(); for (BundleDescription bundleDescription : descriptions) { result.append(dumpResolverErrors(bundleDescription, dumpHeader)); } final State state = descriptions[0].getContainingState(); if (Boolean.getBoolean("a4e.dumpResolverErrors")) { result.append(getUnresolvedLeaves(state, descriptions)); } return result.toString(); } public static String getUnresolvedLeaves(final State state, final BundleDescription[] allDescriptions) { final StringBuilder stringBuffer = new StringBuilder(); final StateHelper helper = state.getStateHelper(); final List<BundleDescription> descriptionsWithError = new LinkedList<BundleDescription>(); for (BundleDescription bundleDescription : allDescriptions) { if (!bundleDescription.isResolved()) { descriptionsWithError.add(bundleDescription); } } final BundleDescription[] descriptions = descriptionsWithError.toArray(new BundleDescription[0]); if (helper != null) { VersionConstraint[] unsatisfiedLeaves = helper.getUnsatisfiedLeaves(descriptions); stringBuffer.append("\n -------- UNSATISFIED LEAVES OF " + Arrays.asList(descriptions) + ": -----------\n"); stringBuffer.append(" Unsatisfied Leaves Count: " + unsatisfiedLeaves.length + "\n"); final SortedMap<String, SortedSet<String>> allUnresolvedBundles = new TreeMap<String, SortedSet<String>>(); for (VersionConstraint versionConstraint : unsatisfiedLeaves) { BundleDescription unresolvedBundle = versionConstraint.getBundle(); final String unresolvedBundleName = unresolvedBundle.getName(); String missingConstraint = versionConstraint.getName(); SortedSet<String> missingConstraints = allUnresolvedBundles.get(unresolvedBundleName); if (missingConstraints == null) { missingConstraints = new TreeSet<String>(); BundleDescription[] fragments = unresolvedBundle.getFragments(); if (fragments != null && fragments.length > 0) { for (BundleDescription bundleDescription : fragments) { missingConstraints.add(" (attached fragment: " + bundleDescription.getName() + ")"); } } allUnresolvedBundles.put(unresolvedBundleName, missingConstraints); } BundleDescription missingOrUnresolvedBundle = state.getBundle(versionConstraint.getName(), null); if (missingOrUnresolvedBundle == null) { missingConstraint = missingConstraint + " (Package or Bundle not found in Target Platform)"; } else { BundleDescription[] fragments = missingOrUnresolvedBundle.getFragments(); if (fragments != null && fragments.length > 0) { missingConstraint = missingConstraint + " (has attached fragments: "; for (BundleDescription bundleDescription : fragments) { missingConstraint = missingConstraint + " " + bundleDescription.getName(); } missingConstraint = missingConstraint + ")"; } } missingConstraints.add(missingConstraint); } Iterator<Entry<String, SortedSet<String>>> it = allUnresolvedBundles.entrySet().iterator(); while (it.hasNext()) { Entry<String, SortedSet<String>> next = it.next(); stringBuffer.append(" - " + next.getKey() + " misses:\n"); Iterator<String> iterator = next.getValue().iterator(); while (iterator.hasNext()) { stringBuffer.append(" -> " + iterator.next() + "\n"); } } stringBuffer.append(" -----------------------------------------------------------------------\n"); } else { stringBuffer.append("Could not get StateHelper\n"); } return stringBuffer.toString(); } /** * <p> * Returns the bundle info of the given bundle description. * </p> * * @param description * the bundle description. * @return the bundle info of the given bundle description. */ static String getBundleInfo(BundleDescription description) { Assure.notNull("description", description); BundleSource bundleSource = BundleSource.getBundleSource(description); StringBuffer buffer = new StringBuffer(); buffer.append(description.getSymbolicName()).append("_").append(description.getVersion().toString()).append("@"); if (bundleSource.isEclipseProject()) { buffer.append("<P>").append(bundleSource.getAsEclipseProject().getFolder()); } else { buffer.append(bundleSource.getAsFile().getAbsolutePath()); } return buffer.toString(); } }