/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.sarl.eclipse.util; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.internal.core.ClasspathEntry; import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.util.Util; import org.eclipse.jdt.internal.launching.RuntimeClasspathEntry; import org.eclipse.jdt.launching.IRuntimeClasspathEntry; import org.osgi.framework.Bundle; import org.osgi.framework.Version; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; import io.sarl.eclipse.SARLEclipsePlugin; import io.sarl.eclipse.util.Utilities.BundleURLMappings; import io.sarl.lang.SARLConfig; /** Utilities around bundles. It should be replaced * by the OSGi, Eclipse and Xtext API. * * @author $Author: sgalland$ * @author $Author: ngaud$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public final class BundleUtil { /** OS-independent paths of the bin folders. */ public static final String[] BIN_FOLDERS = { SARLConfig.FOLDER_BIN, "bin", //$NON-NLS-1$ }; /** OS-independent paths of the source folders. */ public static final String[] SRC_FOLDERS = { SARLConfig.FOLDER_SOURCE_JAVA, SARLConfig.FOLDER_SOURCE_SARL, "src", //$NON-NLS-1$ }; private static final String SOURCE_SUFIX = ".source"; //$NON-NLS-1$ private static final String JAVADOC_SUFIX = ".javadoc"; //$NON-NLS-1$ private static final String JAR_EXTENSION = "jar"; //$NON-NLS-1$ private static final String DOT_JAR_EXTENSION = "." + JAR_EXTENSION; //$NON-NLS-1$ private static final String ROOT_NAME = "/"; //$NON-NLS-1$ private static final String DEFAULT_PATH_TO_CLASSES_IN_MAVEN_PROJECT = "target/classes"; //$NON-NLS-1$ private BundleUtil() { // } /** Replies the source location for the given bundle. * * <p>The source location is usually the root folder where the source code of the bundle is located. * * <p>We can't use P2Utils and we can't use SimpleConfiguratorManipulator because of * API breakage between 3.5 and 4.2. * So we do a bit EDV (Computer data processing) ;-) * * <p>FIXME: Use P2Utils or SimpleConfiguratorManipulator. * * @param bundle - the bundle for which the source location must be computed. * @param bundleLocation - the location of the bundle, as replied by {@link #getBundlePath(Bundle)}. * @return the path to the source folder of the bundle, or <code>null</code> if undefined. * @see #getBundlePath(Bundle) */ public static IPath getSourceBundlePath(Bundle bundle, IPath bundleLocation) { IPath sourcesPath = null; // Not an essential functionality, make it robust try { final IPath srcFolderPath = getSourceRootProjectFolderPath(bundle); if (srcFolderPath == null) { //common case, jar file. final IPath bundlesParentFolder = bundleLocation.removeLastSegments(1); final String binaryJarName = bundleLocation.lastSegment(); final String symbolicName = bundle.getSymbolicName(); final String sourceJarName = binaryJarName.replace(symbolicName, symbolicName.concat(SOURCE_SUFIX)); final IPath potentialSourceJar = bundlesParentFolder.append(sourceJarName); if (potentialSourceJar.toFile().exists()) { sourcesPath = potentialSourceJar; } } else { sourcesPath = srcFolderPath; } } catch (Throwable t) { throw new RuntimeException(t); } return sourcesPath; } private static IPath getBinFolderPath(Bundle bundle) { for (final String binFolder : BIN_FOLDERS) { final URL binFolderURL = FileLocator.find(bundle, Path.fromPortableString(binFolder), null); if (binFolderURL != null) { try { final URL binFolderFileURL = FileLocator.toFileURL(binFolderURL); return new Path(binFolderFileURL.getPath()).makeAbsolute(); } catch (IOException e) { throw new RuntimeException(e); } } } return null; } private static IPath getSourceRootProjectFolderPath(Bundle bundle) { for (final String srcFolder : SRC_FOLDERS) { final IPath relPath = Path.fromPortableString(srcFolder); final URL srcFolderURL = FileLocator.find(bundle, relPath, null); if (srcFolderURL != null) { try { final URL srcFolderFileURL = FileLocator.toFileURL(srcFolderURL); IPath absPath = new Path(srcFolderFileURL.getPath()).makeAbsolute(); absPath = absPath.removeLastSegments(relPath.segmentCount()); return absPath; } catch (IOException e) { // } } } return null; } /** Replies the path of the binary files of the given bundle. * * @param bundle - the bundle for which the path must be retreived. * @return the path to the binaries of the bundle. * @see #getSourceBundlePath(Bundle, IPath) */ public static IPath getBundlePath(Bundle bundle) { IPath path = getBinFolderPath(bundle); if (path == null) { // common jar file case, no bin folder try { path = new Path(FileLocator.getBundleFile(bundle).getAbsolutePath()); } catch (IOException e) { throw new RuntimeException(e); } } return path; } /** Replies the javadoc location for the given bundle. * * <p>We can't use P2Utils and we can't use SimpleConfiguratorManipulator because of * API breakage between 3.5 and 4.2. * So we do a bit EDV (Computer data processing) ;-) * * <p>FIXME: Use P2Utils or SimpleConfiguratorManipulator. * * @param bundle - the bundle for which the javadoc location must be computed. * @param bundleLocation - the location of the bundle, as replied by {@link #getBundlePath(Bundle)}. * @return the path to the javadoc folder of the bundle, or <code>null</code> if undefined. * @see #getBundlePath(Bundle) */ public static IPath getJavadocBundlePath(Bundle bundle, IPath bundleLocation) { IPath sourcesPath = null; // Not an essential functionality, make it robust try { final IPath srcFolderPath = getSourceRootProjectFolderPath(bundle); if (srcFolderPath == null) { //common case, jar file. final IPath bundlesParentFolder = bundleLocation.removeLastSegments(1); final String binaryJarName = bundleLocation.lastSegment(); final String symbolicName = bundle.getSymbolicName(); final String sourceJarName = binaryJarName.replace(symbolicName, symbolicName.concat(JAVADOC_SUFIX)); final IPath potentialSourceJar = bundlesParentFolder.append(sourceJarName); if (potentialSourceJar.toFile().exists()) { sourcesPath = potentialSourceJar; } } else { sourcesPath = srcFolderPath; } } catch (Throwable t) { throw new RuntimeException(t); } return sourcesPath; } /** Replies the dependencies for the given bundle. * * @param bundle the bundle. * @param directDependencies the list of the bundle symbolic names that are the direct dependencies of the bundle to * be considered. If the given bundle has other dependencies in its Manifest, they will be ignored if they * are not in this parameter. * @return the bundle dependencies. */ public static IBundleDependencies resolveBundleDependencies(Bundle bundle, String... directDependencies) { return resolveBundleDependencies(bundle, (BundleURLMappings) null, directDependencies); } /** Replies the dependencies for the given bundle. * * @param bundle the bundle. * @param javadocURLs the mapping from bundles to the corresponding Javadoc URLs. * @param directDependencies the list of the bundle symbolic names that are the direct dependencies of the bundle to * be considered. If the given bundle has other dependencies in its Manifest, they will be ignored if they * are not in this parameter. * @return the bundle dependencies. */ public static IBundleDependencies resolveBundleDependencies(Bundle bundle, BundleURLMappings javadocURLs, String... directDependencies) { final BundleURLMappings docMapping = javadocURLs == null ? new Utilities.SARLBundleJavadocURLMappings() : javadocURLs; final Collection<String> deps = directDependencies == null || directDependencies.length == 0 ? null : Arrays.asList(directDependencies); return new BundleDependencies(bundle, deps, docMapping); } /** Container of bundle dependencies. This class is an iterable on the symbolic names of the dependency bundles. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public static class BundleDependency { private final Bundle bundle; private final IClasspathEntry classpathEntry; private final boolean isFragment; private IRuntimeClasspathEntry runtimeClasspathEntry; /** Constructor. * * @param bundle the bundle. * @param classPathEntry the classpath entry for the bundle. * @param isFragment indicates if the dependency is a fragment or not. */ BundleDependency(Bundle bundle, IClasspathEntry classPathEntry, boolean isFragment) { assert bundle != null; assert classPathEntry != null; this.bundle = bundle; this.classpathEntry = classPathEntry; this.isFragment = isFragment; } /** Replies the bundle. * * @return the bundle. */ public Bundle getBundle() { return this.bundle; } /** The class path entry for the bundle. * * @return the classpath entry. */ public IClasspathEntry getClassPathEntry() { return this.classpathEntry; } /** The runtime class path entry for the bundle. * * @return the runtime classpath entry. */ public IRuntimeClasspathEntry getRuntimeClassPathEntry() { if (this.classpathEntry != null && this.runtimeClasspathEntry == null) { this.runtimeClasspathEntry = new RuntimeClasspathEntry(this.classpathEntry); } return this.runtimeClasspathEntry; } /** Replies if the dependency is a fragment bundle. * * @return <code>true</code> if the bundle is a fragment. */ public boolean isFragment() { return this.isFragment; } @Override public String toString() { return this.bundle.getSymbolicName(); } } /** Container of bundle dependencies. This class is an iterable on the symbolic names of the dependency bundles. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public interface IBundleDependencies { /** Replies the name of the bundle that depends on the current dependencies. * * @return the bundle name. */ String getBundleSymbolicName(); /** Replies the detected binary path for the bundle. * * @return the output folder. */ IPath getBundleBinaryPath(); /** Replies the version of the bundle that is considered for computing the dependencies. * * @return the bundle version. */ Version getBundleVersion(); /** Replies the symbolic names of the direct dependencies of the bundle (no transitivity). * The bundle itself is included in the replied list if it is not a directory. * * @return the symbolic names of the bundle dependencies. */ Iterable<String> getDirectSymbolicNames(); /** Replies the classpath entries of the bundle dependencies (no transitivity). * The bundle itself is included in the replied list if it is not a directory. * * @return the classpath entries of the bundle dependencies. */ Iterable<IClasspathEntry> getDirectClasspathEntries(); /** Replies the runtime classpath entries of the bundle dependencies (no transitivity). * The bundle itself is included in the replied list if it is not a directory. * * @return the runtime classpath entries of the bundle dependencies. */ Iterable<IRuntimeClasspathEntry> getDirectRuntimeClasspathEntries(); /** Replies the dependencies of the bundle. * The bundle itself is included in the replied list if it is not a directory (no transitivity). * * @return the bundle dependencies, or <code>null</code> if the dependencies cannot be computed. */ List<BundleDependency> getDirectDependencies(); /** Replies the symbolic names of the bundle dependencies (transitivity). * The bundle itself is included in the replied list if it is not a directory. * * @param includeFragments indicates if bundle fragments should be replied also. * @return the symbolic names of the bundle dependencies. */ Iterable<String> getTransitiveSymbolicNames(boolean includeFragments); /** Replies the classpath entries of the bundle dependencies (transitivity). * The bundle itself is included in the replied list if it is not a directory. * * @param includeFragments indicates if bundle fragments should be replied also. * @return the classpath entries of the bundle dependencies. */ Iterable<IClasspathEntry> getTransitiveClasspathEntries(boolean includeFragments); /** Replies the runtime classpath entries of the bundle dependencies (transitivity). * The bundle itself is included in the replied list if it is not a directory. * * @param includeFragments indicates if bundle fragments should be replied also. * @return the runtime classpath entries of the bundle dependencies. */ Iterable<IRuntimeClasspathEntry> getTransitiveRuntimeClasspathEntries(boolean includeFragments); /** Replies the dependencies of the bundle (transitivity). * The bundle itself is included in the replied list if it is not a directory. * * @param includeFragments indicates if bundle fragments should be replied also. * @return the bundle dependencies, or <code>null</code> if the dependencies cannot be computed. */ Iterable<BundleDependency> getTransitiveDependencies(boolean includeFragments); } /** Definition of a set of dependencies. * * <p>The set entries are sorted in the insertion order. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class DependencyDefinition { private Version version; private final List<BundleDependency> dependencies = new ArrayList<>(); private final Set<String> dependencyIds = new TreeSet<>(); DependencyDefinition(Version version, List<BundleDependency> dependencies) { this.version = version; addDependencies(dependencies); } /** Change the version of the dependency. * * @param version the new version. if <code>null</code>, the version does not change. */ public void setVersion(Version version) { if (version != null) { this.version = version; } } /** Add the given dependencies. * * @param dependencies the dependencies. */ public void addDependencies(List<BundleDependency> dependencies) { for (final BundleDependency dependency : dependencies) { if (this.dependencyIds.add(dependency.getBundle().getSymbolicName())) { this.dependencies.add(dependency); } } } /** Replies the version of the bundle. * * @return the version. */ public Version getVersion() { return this.version; } /** Replies the dependencies. * * @return the dependencies. */ public List<BundleDependency> getDependencies() { return Collections.unmodifiableList(this.dependencies); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("version="); //$NON-NLS-1$ buf.append(this.version); buf.append("; dependencies="); //$NON-NLS-1$ buf.append(this.dependencies.toString()); return buf.toString(); } } /** Container of bundle dependencies. This class is an iterable on the symbolic names of the dependency bundles. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class BundleDependencies implements IBundleDependencies { private final Bundle bundle; private final Collection<String> directDependencies; private final BundleURLMappings javadocURLs; private IPath binaryBundlePath; private Map<Bundle, DependencyDefinition> bundleDependencies; /** Constructor. * * @param bundle the bundle. * @param directDependencies the list of the bundle symbolic names that are the direct dependencies of the bundle to * be considered. If the given bundle has other dependencies in its Manifest, they will be ignored if they * are not in this parameter. * @param javadocURLs the mapping from bundles to the corresponding Javadoc URLs. */ BundleDependencies(Bundle bundle, Collection<String> directDependencies, BundleURLMappings javadocURLs) { assert bundle != null; this.bundle = bundle; this.directDependencies = directDependencies; this.javadocURLs = javadocURLs; } private Map<Bundle, DependencyDefinition> getBundleDependencies() { if (this.bundleDependencies == null) { this.bundleDependencies = new TreeMap<>(new Comparator<Bundle>() { @Override public int compare(Bundle o1, Bundle o2) { return o1.getSymbolicName().compareTo(o2.getSymbolicName()); } }); } return this.bundleDependencies; } private DependencyDefinition getBundleDependencies(Bundle bundle) { final Map<Bundle, DependencyDefinition> repository = getBundleDependencies(); synchronized (repository) { return repository.get(bundle); } } private void setBundleDependencies(Bundle bundle, List<BundleDependency> cpEntries, boolean overwrite) { final Map<Bundle, DependencyDefinition> repository = getBundleDependencies(); synchronized (repository) { if (overwrite || !repository.containsKey(bundle)) { repository.put(bundle, new DependencyDefinition(bundle.getVersion(), cpEntries)); } else { final DependencyDefinition dependencySet = repository.get(bundle); assert dependencySet != null; dependencySet.setVersion(bundle.getVersion()); dependencySet.addDependencies(cpEntries); } } } private void addToBundleDependencies(Bundle bundle, BundleDependency dependency) { final Map<Bundle, DependencyDefinition> repository = getBundleDependencies(); synchronized (repository) { final DependencyDefinition dependencySet = repository.get(bundle); if (dependencySet == null) { repository.put(bundle, new DependencyDefinition(bundle.getVersion(), Collections.singletonList(dependency))); } else { dependencySet.addDependencies(Collections.singletonList(dependency)); } } } @Override public String toString() { final StringBuilder buf = new StringBuilder(); final DependencyDefinition dependencies = getDependencyDefinition(); if (dependencies != null) { toDependencyTree( buf, new String(), new String(), this.bundle, false, dependencies.getDependencies()); } return buf.toString(); } private void toDependencyTree(StringBuilder builder, String indent1, String indent2, Bundle current, boolean isFragment, List<BundleDependency> dependencies) { builder.append(indent1); builder.append(current.getSymbolicName()); if (isFragment) { builder.append(" (fragment)"); //$NON-NLS-1$ } builder.append("\n"); //$NON-NLS-1$ for (final BundleDependency dependency : dependencies) { if (!Objects.equals(current.getSymbolicName(), dependency.getBundle().getSymbolicName())) { final DependencyDefinition subdependencies = getBundleDependencies(dependency.getBundle()); if (subdependencies != null) { toDependencyTree( builder, indent2 + "|- ", //$NON-NLS-1$ indent2 + " ", //$NON-NLS-1$ dependency.getBundle(), dependency.isFragment(), subdependencies.getDependencies()); } } } } @Override public IPath getBundleBinaryPath() { if (this.binaryBundlePath == null) { getDependencyDefinition(); } return this.binaryBundlePath; } @Override public Iterable<String> getDirectSymbolicNames() { return () -> new SymbolicNameIterator(getDirectDependencies()); } @Override public Iterable<IClasspathEntry> getDirectClasspathEntries() { return () -> new ClasspathEntryIterator(getDirectDependencies()); } @Override public Iterable<IRuntimeClasspathEntry> getDirectRuntimeClasspathEntries() { return () -> new RuntimeClasspathEntryIterator(getDirectDependencies()); } @Override public Iterable<String> getTransitiveSymbolicNames(boolean includeFragments) { return () -> new SymbolicNameIterator(getTransitiveDependencies(includeFragments)); } @Override public Iterable<IClasspathEntry> getTransitiveClasspathEntries(boolean includeFragments) { return () -> new ClasspathEntryIterator(getTransitiveDependencies(includeFragments)); } @Override public Iterable<IRuntimeClasspathEntry> getTransitiveRuntimeClasspathEntries(boolean includeFragments) { return () -> new RuntimeClasspathEntryIterator(getTransitiveDependencies(includeFragments)); } private DependencyDefinition getDependencyDefinition() { DependencyDefinition dependencies = getBundleDependencies(this.bundle); if (dependencies == null) { final IPath bundlePath = BundleUtil.getBundlePath(this.bundle); if (bundlePath.toFile().isDirectory()) { // we have a directory, we assume we are in debug mode of the // product final IPath bundleSourcePath = BundleUtil.getSourceBundlePath(this.bundle, bundlePath); // Default value of the output folder for our project but will be // overload later in we find a .classpath precising the output // folder IPath outputFolder = Path.fromPortableString(bundleSourcePath.toPortableString().concat( DEFAULT_PATH_TO_CLASSES_IN_MAVEN_PROJECT)); URL janusBundleURL = null; try { janusBundleURL = new URL("file://" + bundleSourcePath.toPortableString()); //$NON-NLS-1$ } catch (MalformedURLException e) { return null; } final IPath classpathOutputFolder = readDotClasspathAndReferencestoClasspath(null, this.bundle, janusBundleURL); if (classpathOutputFolder != null) { outputFolder = classpathOutputFolder; } this.binaryBundlePath = outputFolder; } else { this.binaryBundlePath = bundlePath; final IClasspathEntry cpEntry = Utilities.newLibraryEntry(this.bundle, bundlePath, null); final List<BundleDependency> cpEntries = new ArrayList<>(); updateBundleClassPath(this.bundle, cpEntry, cpEntries); setBundleDependencies(this.bundle, cpEntries, true); } extractAllBundleDependencies(this.bundle, true); dependencies = getBundleDependencies(this.bundle); } return dependencies; } @Override public Version getBundleVersion() { final DependencyDefinition dependencies = getDependencyDefinition(); if (dependencies == null) { return null; } return dependencies.getVersion(); } @Override public String getBundleSymbolicName() { return this.bundle.getSymbolicName(); } @Override public List<BundleDependency> getDirectDependencies() { final DependencyDefinition dependencies = getDependencyDefinition(); if (dependencies == null) { return null; } return Collections.unmodifiableList(dependencies.getDependencies()); } @Override public Iterable<BundleDependency> getTransitiveDependencies(boolean includeFragments) { final DependencyDefinition dependencies = getDependencyDefinition(); if (dependencies == null) { return Collections.emptyList(); } return () -> new TransitiveDependencyIterator(dependencies.getDependencies(), includeFragments); } /** Add the given bundle to the entries. * * <p>This function add the classpath entry for the bundle, and the related fragments. * * @param bundle the bundle to point to. Never <code>null</code>. * @param entry the classpath entry to add to. Never <code>null</code>. * @param entries the list of entries to add to. * @return the main added dependency. */ private static BundleDependency updateBundleClassPath(Bundle bundle, IClasspathEntry entry, Collection<BundleDependency> entries) { assert bundle != null; assert entry != null; final BundleDependency rootDep = new BundleDependency(bundle, entry, false); entries.add(rootDep); final Bundle[] fragments = Platform.getFragments(bundle); if (fragments != null && fragments.length > 0) { for (final Bundle fragment : fragments) { final IClasspathEntry fragmentEntry = Utilities.newLibraryEntry(fragment, null, null); entries.add(new BundleDependency(fragment, fragmentEntry, true)); } } return rootDep; } /** * Recursive function to get all the required dependencies of the given bundle and adding the corresponding elements to the * dependency collection. * * @param bundle * the bundle used as root to start the dynamic search of dependencies. * @param firstCall * boolean specifying if we are at the first recursive call, in this case we use the {@link #directDependencies} * collections to filter the dependencies that are really useful. */ @SuppressWarnings({"checkstyle:nestedifdepth"}) private void extractAllBundleDependencies(Bundle bundle, boolean firstCall) { final BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); final List<BundleWire> bundleWires = bundleWiring.getRequiredWires(null); if (bundleWires != null) { for (final BundleWire wire : bundleWires) { final Bundle dependency = wire.getProviderWiring().getBundle(); assert dependency != null; final String dependencyInstallationPath = dependency.getLocation(); final DependencyDefinition existingDependencyCPE = getBundleDependencies(dependency); final boolean validDependency = (existingDependencyCPE == null || dependency.getVersion().compareTo(existingDependencyCPE.getVersion()) > 0) && (!firstCall || this.directDependencies == null || this.directDependencies.contains(dependency.getSymbolicName())); if (validDependency) { URL u = null; try { u = FileLocator.resolve(dependency.getEntry(ROOT_NAME)); } catch (IOException e) { SARLEclipsePlugin.getDefault().log(e); return; } if (dependencyInstallationPath.contains(JAR_EXTENSION) || u.getProtocol().equals(JAR_EXTENSION)) { final IClasspathEntry cpEntry = Utilities.newLibraryEntry(dependency, null, this.javadocURLs); final List<BundleDependency> cpEntries = new ArrayList<>(); final BundleDependency dep = updateBundleClassPath(dependency, cpEntry, cpEntries); setBundleDependencies(dependency, cpEntries, false); addToBundleDependencies(bundle, dep); } else { // Management of a project having a .classpath to get the classapth readDotClasspathAndReferencestoClasspath(bundle, dependency, u); } extractAllBundleDependencies(dependency, false); } } } } /** * Explore the various entries of a bundle to find its .classpath file, parse it and update accordingly the {@code classpathEntries} * collection of this bundle. * * @param parent if not <code>null</code> it is the bundle that depends on the current bundle. * @param bundle the bundle to explore * @param bundleInstallURL the URL where the specified bundle is stored * @return the Path to the output folder used to store .class file if any (if we are in an eclipse project (debug mode)) */ @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"}) private IPath readDotClasspathAndReferencestoClasspath(Bundle parent, Bundle bundle, URL bundleInstallURL) { IPath outputLocation = null; BundleDependency mainDependency = null; final List<BundleDependency> cpEntries = new ArrayList<>(); final Enumeration<String> entries = bundle.getEntryPaths(ROOT_NAME); String entry = null; while (entries.hasMoreElements()) { entry = entries.nextElement(); if (entry.contains(JavaProject.CLASSPATH_FILENAME)) { try { // copied from See {@link JavaProject#decodeClasspath} final IClasspathEntry[][] classpath = JavaClasspathParser.readFileEntriesWithException(bundle.getSymbolicName(), bundleInstallURL); if (classpath[0].length > 0) { // extract the output location int outputLocationEntryIndex = -1; for (int i = 0; outputLocationEntryIndex < 0 && i < classpath[0].length; ++i) { IClasspathEntry outputLocationEntry = classpath[0][i]; if (outputLocationEntry.getContentKind() == ClasspathEntry.K_OUTPUT) { outputLocation = outputLocationEntry.getPath(); // Ensure that the classpath entry has a source attachment path final IPath sourcePath = outputLocationEntry.getSourceAttachmentPath(); if (sourcePath == null) { final IPath entryPath = outputLocationEntry.getPath(); outputLocationEntry = Utilities.newOutputClasspathEntry(bundle, entryPath, null); } mainDependency = new BundleDependency(bundle, outputLocationEntry, false); cpEntries.add(mainDependency); outputLocationEntryIndex = i; } } // discard the output location and add others real entries // to the classpath final IClasspathEntry[] copy; if (outputLocationEntryIndex >= 0) { copy = new IClasspathEntry[classpath[0].length - 1]; if (outputLocationEntryIndex > 0) { System.arraycopy(classpath[0], 0, copy, 0, outputLocationEntryIndex); } if (outputLocationEntryIndex < (classpath[0].length - 1)) { System.arraycopy(classpath[0], outputLocationEntryIndex + 1, copy, outputLocationEntryIndex, classpath[0].length - outputLocationEntryIndex - 1); } } else { copy = classpath[0]; } if (copy != null && copy.length > 0) { for (final IClasspathEntry cpentry : copy) { if (cpentry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { // FIXME do something if we have a // container, usually this is the JRE // container // already managed by the launch // configuration, that's why we do nothing } else if (cpentry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { // FIXME do something with package fragments } else { // Common entry type: // CPE_PROJECT|CPE_LIBRARY|CPE_VARIABLE, // directly managed by RuntimeClasspathEntry cpEntries.add(new BundleDependency(bundle, cpentry, false)); } } } } } catch (IOException | CoreException | URISyntaxException e) { SARLEclipsePlugin.getDefault().log(e); return null; } } else if (entry.contains(DOT_JAR_EXTENSION)) { // A jar inside the bundle // FIXME we have an error at runtime to these referenced jars try { final URL bundleJARfileFullURL = new URL(bundleInstallURL.toExternalForm().concat(File.separator).concat(entry)); final File jarFile = Util.toLocalFile(bundleJARfileFullURL.toURI(), null); final IPath jarFilePath = new Path(jarFile.getAbsolutePath()); final IClasspathEntry cpEntry = Utilities.newLibraryEntry(bundle, jarFilePath, this.javadocURLs); updateBundleClassPath(bundle, cpEntry, cpEntries); } catch (CoreException | URISyntaxException | MalformedURLException e) { SARLEclipsePlugin.getDefault().log(e); return null; } } } if (cpEntries.size() > 0) { setBundleDependencies(bundle, cpEntries, true); if (parent != null && mainDependency != null) { addToBundleDependencies(parent, mainDependency); } } return outputLocation; } /** Iterator on symbolic names. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class SymbolicNameIterator implements Iterator<String> { private final Iterator<BundleDependency> iterator; /** Constructor. * @param dependencies the dependencies or <code>null</code> */ SymbolicNameIterator(Iterable<BundleDependency> dependencies) { if (dependencies == null) { this.iterator = Collections.<BundleDependency>emptyList().iterator(); } else { this.iterator = dependencies.iterator(); } } @Override public boolean hasNext() { return this.iterator.hasNext(); } @Override public String next() { final BundleDependency dependency = this.iterator.next(); return dependency.getBundle().getSymbolicName(); } } /** Iterator on classpath entries. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class ClasspathEntryIterator implements Iterator<IClasspathEntry> { private final Iterator<BundleDependency> iterator; /** Constructor. * @param dependencies the dependencies or <code>null</code> */ ClasspathEntryIterator(Iterable<BundleDependency> dependencies) { if (dependencies == null) { this.iterator = Collections.<BundleDependency>emptyList().iterator(); } else { this.iterator = dependencies.iterator(); } } @Override public boolean hasNext() { return this.iterator.hasNext(); } @Override public IClasspathEntry next() { final BundleDependency dependency = this.iterator.next(); return dependency.getClassPathEntry(); } } /** Iterator on runtime classpath entries. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class RuntimeClasspathEntryIterator implements Iterator<IRuntimeClasspathEntry> { private final Iterator<BundleDependency> iterator; /** Constructor. * @param dependencies the dependencies or <code>null</code> */ RuntimeClasspathEntryIterator(Iterable<BundleDependency> dependencies) { if (dependencies == null) { this.iterator = Collections.<BundleDependency>emptyList().iterator(); } else { this.iterator = dependencies.iterator(); } } @Override public boolean hasNext() { return this.iterator.hasNext(); } @Override public IRuntimeClasspathEntry next() { final BundleDependency dependency = this.iterator.next(); return dependency.getRuntimeClassPathEntry(); } } /** Iterator on transitive dependencies. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class TransitiveDependencyIterator implements Iterator<BundleDependency> { private final boolean includeFragments; private final LinkedList<Iterator<BundleDependency>> iterators = new LinkedList<>(); private final Set<String> repliedBundles = new TreeSet<>(); private Iterator<BundleDependency> currentIterator; private BundleDependency current; /** Constructor. * @param dependencies the dependencies or <code>null</code> * @param includeFragments indicates if bundle fragments must be included. */ TransitiveDependencyIterator(Iterable<BundleDependency> dependencies, boolean includeFragments) { this.includeFragments = includeFragments; if (dependencies != null) { this.iterators.add(dependencies.iterator()); } searchForNextElement(); } private void searchForNextElement() { this.current = null; while (this.current == null && !this.iterators.isEmpty()) { if (this.currentIterator == null || !this.currentIterator.hasNext()) { this.currentIterator = null; while (this.currentIterator == null & !this.iterators.isEmpty()) { final Iterator<BundleDependency> iterator = this.iterators.removeFirst(); if (iterator.hasNext()) { this.currentIterator = iterator; } } } while (this.current == null && this.currentIterator != null && this.currentIterator.hasNext()) { final BundleDependency dep = this.currentIterator.next(); if (!this.repliedBundles.contains(dep.getBundle().getSymbolicName()) && (this.includeFragments || !dep.isFragment())) { this.current = dep; } } } } @Override public boolean hasNext() { return this.current != null; } @SuppressWarnings("synthetic-access") @Override public BundleDependency next() { if (this.current == null) { throw new NoSuchElementException(); } final BundleDependency cur = this.current; final DependencyDefinition deps = getBundleDependencies(cur.getBundle()); if (deps != null && deps.getDependencies() != null) { this.iterators.add(deps.getDependencies().iterator()); } this.repliedBundles.add(cur.getBundle().getSymbolicName()); searchForNextElement(); return cur; } } } }