/** * Copyright (c) 2010, 2013 Darmstadt University of Technology. * 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: * Olav Lenz - initial API and implementation */ package org.eclipse.recommenders.coordinates.osgi; import static com.google.common.base.Optional.*; import static org.apache.commons.lang3.ArrayUtils.reverse; import static org.apache.commons.lang3.ArrayUtils.subarray; import static org.apache.commons.lang3.StringUtils.*; import static org.eclipse.recommenders.coordinates.Coordinates.tryNewProjectCoordinate; import static org.eclipse.recommenders.coordinates.DependencyType.*; import static org.eclipse.recommenders.utils.Versions.canonicalizeVersion; import static org.eclipse.recommenders.utils.Zips.closeQuietly; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.jar.Attributes; import java.util.jar.Attributes.Name; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.commons.io.IOUtils; import org.eclipse.recommenders.coordinates.AbstractProjectCoordinateAdvisor; import org.eclipse.recommenders.coordinates.DependencyInfo; import org.eclipse.recommenders.coordinates.DependencyType; import org.eclipse.recommenders.coordinates.ProjectCoordinate; import org.eclipse.recommenders.utils.Zips.DefaultJarFileConverter; import org.eclipse.recommenders.utils.Zips.IFileToJarFileConverter; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.net.InternetDomainName; public class OsgiManifestAdvisor extends AbstractProjectCoordinateAdvisor { public static final Name BUNDLE_NAME = new Attributes.Name("Bundle-SymbolicName"); public static final Name BUNDLE_VERSION = new Attributes.Name("Bundle-Version"); private IFileToJarFileConverter jarFileConverter; public OsgiManifestAdvisor() { jarFileConverter = new DefaultJarFileConverter(); } @VisibleForTesting public OsgiManifestAdvisor(IFileToJarFileConverter fileToJarFileConverter) { jarFileConverter = fileToJarFileConverter; } @Override protected Optional<ProjectCoordinate> doSuggest(DependencyInfo dependencyInfo) { Optional<Manifest> optionalManifest = absent(); if (dependencyInfo.getType() == DependencyType.JAR) { optionalManifest = extractManifestFromJar(dependencyInfo); } else if (dependencyInfo.getType() == DependencyType.PROJECT) { optionalManifest = extractManifestFromProject(dependencyInfo); } if (optionalManifest.isPresent()) { return extractProjectCoordinateFromManifest(optionalManifest.get()); } return absent(); } private Optional<Manifest> extractManifestFromProject(DependencyInfo dependencyInfo) { File projectFolder = dependencyInfo.getFile(); File metaInfFolder = new File(projectFolder, "META-INF"); File manifestFile = new File(metaInfFolder, "MANIFEST.MF"); if (manifestFile.exists()) { InputStream in = null; try { in = new FileInputStream(manifestFile); Manifest manifest = new Manifest(in); return of(manifest); } catch (IOException e) { return absent(); } finally { IOUtils.closeQuietly(in); } } return absent(); } private Optional<Manifest> extractManifestFromJar(DependencyInfo dependencyInfo) { Optional<JarFile> optionalJarFile = jarFileConverter.createJarFile(dependencyInfo.getFile()); if (!optionalJarFile.isPresent()) { return absent(); } JarFile jarFile = optionalJarFile.get(); try { final Manifest manifest = jarFile.getManifest(); return fromNullable(manifest); } catch (IOException e) { return absent(); } finally { closeQuietly(jarFile); } } private Optional<ProjectCoordinate> extractProjectCoordinateFromManifest(Manifest manifest) { Attributes attributes = manifest.getMainAttributes(); String bundleName = attributes.getValue(BUNDLE_NAME); String bundleVersion = attributes.getValue(BUNDLE_VERSION); if (bundleName == null || bundleVersion == null) { return absent(); } int indexOf = bundleName.indexOf(';'); String artifactId = bundleName.substring(0, indexOf == -1 ? bundleName.length() : indexOf); Optional<String> groupId = guessGroupId(artifactId); Optional<String> version = OsgiVersionParser.parse(bundleVersion); if (groupId.isPresent() && version.isPresent()) { return tryNewProjectCoordinate(groupId.get(), artifactId, canonicalizeVersion(version.get())); } return absent(); } @Override public boolean isApplicable(DependencyType type) { return JAR == type || PROJECT == type; } private static Optional<String> guessGroupId(String reverseDomainName) { String[] segments = split(reverseDomainName, "."); removeSlashes(segments); String[] reverse = copyAndReverse(segments); try { InternetDomainName name = InternetDomainName.from(join(reverse, ".")); if (!name.isUnderPublicSuffix()) { return Optional.of(segments[0]); } else { InternetDomainName topPrivateDomain = name.topPrivateDomain(); int size = topPrivateDomain.parts().size(); int end = Math.min(segments.length, size + 1); return Optional.of(join(subarray(segments, 0, end), ".")); } } catch (IllegalArgumentException e) { return Optional.absent(); } } private static String[] copyAndReverse(String[] segments) { String[] reverse = segments.clone(); reverse(reverse); return reverse; } private static void removeSlashes(String[] segments) { for (int i = segments.length; i-- > 0;) { segments[i] = replace(segments[i], "/", ""); } } }