/*******************************************************************************
* Copyright © 2011, 2013 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
*
*******************************************************************************/
package org.eclipse.edt.ide.core;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.zip.ZipFile;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.equinox.frameworkadmin.BundleInfo;
import org.eclipse.equinox.simpleconfigurator.manipulator.SimpleConfiguratorManipulator;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.osgi.service.resolver.VersionRange;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
/**
* An entry in a container can be a single jar or directory. It may specify a source attachment. The source
* attachment may either be a file or folder within the main bundle, or it may be a separate source bundle.
*/
public class EDTRuntimeContainerEntry {
/**
* The symbolic name for the bundle providing the runtime.
*/
private final String bundleId;
/**
* An optional path within the resolved bundle. This is only used when the
* resolved bundle is a directory.
*/
private final String bundleRoot;
/**
* An optional symbolic name for a source bundle. This is only used if
* sourceBundleRoot wasn't specified or it didn't resolve to a valid path
* within bundleId.
*/
private final String sourceBundleId;
/**
* An optional path within bundleId specifying the source.
*/
private final String sourceBundleRoot;
/**
* An optional version range. If specified we make sure bundleId is within this range, otherwise
* we'll use the highest version found.
*/
private final VersionRange bundleVersionRange;
/**
* Can be a URL or a location on the file system. For locations within a bundle, you can
* use {@link #findBestBundle(String, VersionRange, boolean) to find where it's located.
*/
private final String javadocLocation;
/**
* The resolved classpath entry, or null if it couldn't be resolved.
*/
private IClasspathEntry entry;
/**
* The version of the resolved bundle.
*/
private String bundleVersion;
public EDTRuntimeContainerEntry(String bundleId, String bundleRoot, VersionRange versionRange, String sourceBundleId,
String sourceBundleRoot, String javadocLocation) {
this.bundleId = bundleId;
this.bundleVersionRange = versionRange;
this.bundleRoot = bundleRoot;
this.sourceBundleId = sourceBundleId;
this.sourceBundleRoot = sourceBundleRoot;
this.javadocLocation = javadocLocation;
create();
}
/**
* Uses the provided information to resolve the classpath entry. Clients may subclass this if they have other resolution needs.
*/
protected void create() {
try {
BundleContext context = EDTCoreIDEPlugin.getPlugin().getBundleContext();
if (context == null) {
return;
}
ServiceReference ref = context.getServiceReference(SimpleConfiguratorManipulator.class.getName());
if (ref == null) {
return;
}
SimpleConfiguratorManipulator manipulator = (SimpleConfiguratorManipulator)context.getService(ref);
if (manipulator == null) {
return;
}
// Main bundle (required).
BundleInfo mainBundle = findBestBundle(bundleId, manipulator.loadConfiguration(context, null), bundleVersionRange);
if (mainBundle == null) {
return;
}
// Store the version in case any clients need to know which version was chosen.
bundleVersion = mainBundle.getVersion();
URL bundleURL = FileLocator.toFileURL(URIUtil.toURL(mainBundle.getLocation()));
String path = bundleURL.getPath();
path = URLDecoder.decode(path, "UTF-8"); //$NON-NLS-1$
IPath bundlePath = new Path(path);
if (bundleRoot != null && bundleRoot.length() != 0 && bundlePath.toFile().isDirectory()) {
bundlePath = bundlePath.append(bundleRoot);
}
// Source bundle (optional).
IPath srcBundlePath = null;
IPath srcRootPath = null;
// First check for the source root inside the original bundle, which may or may not be a jar.
if (sourceBundleRoot != null && sourceBundleRoot.length() != 0) {
File bundleFile = new File(bundleURL.getFile());
if (bundleFile.isDirectory()) {
File srcFile = new File(bundleFile, sourceBundleRoot);
if (srcFile.exists()) {
srcBundlePath = new Path(srcFile.getPath());
if (srcFile.isDirectory()) {
srcBundlePath = srcBundlePath.addTrailingSeparator();
}
}
}
else if (bundleFile.exists()) {
try {
ZipFile zip = new ZipFile(bundleFile);
if (zip.getEntry(sourceBundleRoot) != null) {
srcBundlePath = bundlePath;
srcRootPath = new Path(sourceBundleRoot);
}
}
catch (Exception e) {
}
}
}
// Not found, so try resolving the source bundle.
if (srcBundlePath == null && (sourceBundleId != null && sourceBundleId.length() != 0)) {
BundleInfo srcBundle = findBestBundle(sourceBundleId, manipulator.loadConfiguration(context, SimpleConfiguratorManipulator.SOURCE_INFO), bundleVersionRange);
if (srcBundle != null) {
path = FileLocator.toFileURL(URIUtil.toURL(srcBundle.getLocation())).getPath();
path = URLDecoder.decode(path, "UTF-8"); //$NON-NLS-1$
srcBundlePath = new Path(path);
}
}
IClasspathAttribute[] attrs;
if (javadocLocation != null && javadocLocation.length() != 0) {
attrs = new IClasspathAttribute[]{JavaCore.newClasspathAttribute(IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, javadocLocation)};
}
else {
attrs = new IClasspathAttribute[0];
}
entry = JavaCore.newLibraryEntry(bundlePath, srcBundlePath, srcRootPath, new IAccessRule[0], attrs, false);
}
catch (IOException e) {
EDTCoreIDEPlugin.log(e);
}
}
/**
* Finds the best-matching bundle. If no range is specified, the highest version is returned.
*
* @param id The bundle's symbolic name.
* @param range An optional version range; this may be null.
* @param isSource Flag indicating if we should look for a source bundle.
* @return the best-matching bundle, or null if there are no matches.
* @throws IOException
*/
public static BundleInfo findBestBundle(String id, VersionRange range, boolean isSource) throws IOException {
BundleContext context = EDTCoreIDEPlugin.getPlugin().getBundleContext();
if (context == null) {
return null;
}
ServiceReference ref = context.getServiceReference(SimpleConfiguratorManipulator.class.getName());
if (ref == null) {
return null;
}
SimpleConfiguratorManipulator manipulator = (SimpleConfiguratorManipulator)context.getService(ref);
if (manipulator == null) {
return null;
}
return findBestBundle(id, manipulator.loadConfiguration(context, isSource ? SimpleConfiguratorManipulator.SOURCE_INFO : null), range);
}
/**
* Finds the best-matching bundle. If no range is specified, the highest version is returned.
*
* @param id The bundle's symbolic name.
* @param infos The available BundleInfos.
* @param range An optional version range; this may be null.
* @return the best-matching bundle, or null if there are no matches.
*/
public static BundleInfo findBestBundle(String id, BundleInfo[] infos, VersionRange range) {
if (infos == null) {
return null;
}
BundleInfo bestMatch = null;
Version bestVersion = null;
for (int i = 0; i < infos.length; i++) {
BundleInfo info = infos[i];
if (id.equals(info.getSymbolicName())) {
URI location = info.getLocation();
if (location != null) {
Version version = new Version(info.getVersion());
if (range == null || range.isIncluded(version)) {
try {
String path = FileLocator.toFileURL(URIUtil.toURL(location)).getPath();
path = URLDecoder.decode(path, "UTF-8"); //$NON-NLS-1$
IPath bundlePath = new Path(path);
if (bundlePath.toFile().exists()) {
if (bestMatch == null || bestVersion.compareTo(version) < 0) {
bestMatch = info;
bestVersion = version;
}
}
}
catch (IOException e) {
}
}
}
}
}
return bestMatch;
}
/**
* @return the classpath entry, or null if the bundle could not be resolved.
*/
public IClasspathEntry getClasspathEntry() {
return entry;
}
public String getBundleId() {
return bundleId;
}
public String getBundleRoot() {
return bundleRoot;
}
public VersionRange getVersionRange() {
return bundleVersionRange;
}
public String getSourceBundleId() {
return sourceBundleId;
}
public String getSourceBundleRoot() {
return sourceBundleRoot;
}
public String getJavadocLocation() {
return javadocLocation;
}
public String getBundleVersion() {
return bundleVersion;
}
}