/******************************************************************************* * Copyright (c) 2006-2015 * Software Technology Group, Dresden University of Technology * DevBoost GmbH, Dresden, Amtsgericht Dresden, HRB 34001 * * 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: * Software Technology Group - TU Dresden, Germany; * DevBoost GmbH - Dresden, Germany * - initial API and implementation ******************************************************************************/ package de.devboost.buildboost.artifacts; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Properties; import java.util.Set; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import de.devboost.buildboost.discovery.reader.DotClasspathReader; import de.devboost.buildboost.discovery.reader.ManifestReader; import de.devboost.buildboost.model.IArtifact; import de.devboost.buildboost.model.IDependable; import de.devboost.buildboost.model.IFileArtifact; import de.devboost.buildboost.model.UnresolvedDependency; import de.devboost.buildboost.util.StreamUtil; /** * A Plug-in represents an OSGi bundle (i.e., an Eclipse plug-in) that is either found in a target platform (e.g., an * Eclipse distribution) or that is part of the workspace that is subject to the build process. */ public class Plugin extends AbstractArtifact implements IFileArtifact, Serializable { private static final String ALTERNATIVE_DEPENDENCIES_PROPERTIES = "alternative_dependencies.properties"; private static final long serialVersionUID = 7995849293974638695L; /** * The set of plug-in fragments that complement this plug-in. */ private Set<Plugin> fragments = new LinkedHashSet<Plugin>(); // TODO Add documentation private Plugin fragmentHost = null; /** * The plug-in that is complemented by this plug-in (if this plug-in is a fragment). */ private UnresolvedDependency unresolvedFragmentHost; /** * The libraries that are required by this plug-in. */ private Set<String> libs = new LinkedHashSet<String>(); /** * The location of this plug-in. This can be either a directory or a JAR file. */ protected File location; /** * The absolute path to the location of the plug-in. Stored for performance reasons only. */ private String absolutePath; private Set<String> allLibs; private Set<String> sourceFolders = new LinkedHashSet<String>(); private Set<Plugin> allDependencies; private Set<Package> exportedPackages; private String name; private String version; /** * Create a descriptor for the plug-in at the given location. Reads the manifest and class path information if * available. * * @param location * a folder or a JAR file that contains the plug-in * * @throws IOException * if reading plug-ins files fails * @throws InvalidMetadataException * if the plug-in is missing required meta data */ public Plugin(File location) throws IOException, InvalidMetadataException { super(); this.location = location; // Read meta-data analyzeManifest(); analyzeClassPath(); } private void analyzeManifest() throws IOException, InvalidMetadataException { InputStream manifestInputStream = getManifestInputStream(); if (manifestInputStream == null) { setIdentifier(location.getName()); exportedPackages = Collections.emptySet(); } else { ManifestReader reader = new ManifestReader(manifestInputStream); manifestInputStream.close(); Set<UnresolvedDependency> unresolvedDependencies = reader.getDependencies(); addAlternativeIdentifiers(unresolvedDependencies); getUnresolvedDependencies().addAll(unresolvedDependencies); unresolvedFragmentHost = reader.getFragmentHost(); libs.addAll(reader.getBundleClassPath()); addWebLibraries(); String symbolicName = reader.getSymbolicName(); if (ManifestReader.UNKNOWN_SYMBOLIC_NAME.equals(symbolicName)) { throw new InvalidMetadataException(); } setIdentifier(symbolicName); setName(reader.getName()); setVersion(reader.getVersion()); Set<String> exportedPackageName = reader.getExportedPackages(); exportedPackages = new LinkedHashSet<Package>(); for (String packageName : exportedPackageName) { exportedPackages.add(new Package(packageName, this)); } } } private void addAlternativeIdentifiers(Set<UnresolvedDependency> unresolvedDependencies) { byte[] content = getAlternativeDependenciesContent(); InputStream inputStream = new ByteArrayInputStream(content); Properties properties = loadProperties(inputStream); Set<UnresolvedDependency> removableDependencies = new LinkedHashSet<UnresolvedDependency>(); for (UnresolvedDependency unresolvedDependency : unresolvedDependencies) { String identifier = unresolvedDependency.getIdentifier(); String alternativesValue = properties.getProperty(identifier); if (alternativesValue == null) { continue; } if (alternativesValue.trim().isEmpty()) { removableDependencies.add(unresolvedDependency); continue; } String[] alternatives = alternativesValue.split(","); for (String alternative : alternatives) { unresolvedDependency.addAlternativeIdentifier(alternative); } } unresolvedDependencies.removeAll(removableDependencies); } private Properties loadProperties(InputStream inputStream) { Properties properties = new Properties(); if (inputStream == null) { return properties; } try { properties.load(inputStream); } catch (IOException ioe) { // TODO Auto-generated catch block ioe.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // Ignore } } } return properties; } private byte[] getAlternativeDependenciesContent() { File location = getLocation(); if (location.isDirectory()) { File alternativesFile = new File(location, ALTERNATIVE_DEPENDENCIES_PROPERTIES); if (!alternativesFile.exists()) { return new byte[0]; } InputStream inputStream = null; try { inputStream = new FileInputStream(alternativesFile); return new StreamUtil().getContent(inputStream); } catch (IOException ioe) { // TODO Auto-generated catch block ioe.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // Ignore } } } } else if (location.isFile() && location.getName().endsWith(".jar")) { JarFile jarFile = null; try { jarFile = new JarFile(location); ZipEntry entry = jarFile.getEntry(ALTERNATIVE_DEPENDENCIES_PROPERTIES); if (entry == null) { return new byte[0]; } InputStream inputStream = jarFile.getInputStream(entry); return new StreamUtil().getContent(inputStream); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (jarFile != null) { try { jarFile.close(); } catch (IOException e) { // Ignore } } } } return new byte[0]; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getName() { return name; } private void setName(String name) { this.name = name; } private void addWebLibraries() { // TODO this belongs somewhere else if (location.isDirectory()) { File webLibsDir = new File(new File(new File(location, "WebContent"), "WEB-INF"), "lib"); if (webLibsDir.exists()) { File[] webLibs = webLibsDir.listFiles(new FileFilter() { public boolean accept(File file) { return file.isFile() && file.getName().endsWith(".jar"); } }); if (webLibs == null) { return; } for (File webLib : webLibs) { try { String relativePath = webLib.getCanonicalFile().getAbsolutePath() .substring(location.getCanonicalFile().getAbsolutePath().length() + 1); libs.add(relativePath); } catch (IOException e) { // TODO handle exception System.out.println("IOException: " + e.getMessage()); } } } } } /** * Resolves all unresolved dependencies (denoted by symbolic names) by replacing them with actual references to * plug-in objects in the given set of available plug-ins. * * @param allArtifacts * all artifacts that were discovered */ @Override public void resolveDependencies(Collection<? extends IArtifact> allArtifacts) { for (IArtifact artifact : allArtifacts) { Collection<UnresolvedDependency> unresolvedDependencies = new ArrayList<UnresolvedDependency>( getUnresolvedDependencies()); for (UnresolvedDependency dependency : unresolvedDependencies) { if (dependency.isFulfilledBy(artifact)) { addResolvedDependency(dependency, artifact); } } // --- inserted in superclass code ---- if (artifact instanceof Plugin) { Plugin plugin = (Plugin) artifact; if (unresolvedFragmentHost != null && unresolvedFragmentHost.isFulfilledBy(plugin)) { plugin.addFragment(this); fragmentHost = plugin; addDependency(plugin); } } } } private void addFragment(Plugin pluginFragment) { fragments.add(pluginFragment); } private Collection<Plugin> getPluginDependencies() { Collection<Plugin> result = new LinkedHashSet<Plugin>(); for (IDependable artifact : getDependencies()) { if (artifact instanceof Plugin) { Plugin plugin = (Plugin) artifact; result.add(plugin); if (getFragmentHost() != null) { result.add(getFragmentHost()); } } if (artifact instanceof Package) { Package p = (Package) artifact; result.add(p.getExportingPlugin()); } } return Collections.unmodifiableCollection(result); } /** * Returns all plug-ins that this plug-in depends on, except the ones that are part of the target platform. */ public Set<Plugin> getDependenciesExcludingTargetPlatform() { Set<Plugin> result = new LinkedHashSet<Plugin>(); for (IDependable dependency : getDependencies()) { if (dependency instanceof Plugin) { Plugin plugin = (Plugin) dependency; if (!plugin.isProject()) { continue; } result.add(plugin); } } return Collections.unmodifiableSet(result); } public Set<Package> getExportedPackages() { return exportedPackages; } public Set<String> getLibs() { return libs; } /** * Returns all dependencies transitively. */ public Set<Plugin> getAllDependencies() { if (allDependencies == null) { Collection<Plugin> pluginDependencies = getPluginDependencies(); allDependencies = new LinkedHashSet<Plugin>(); allDependencies.addAll(pluginDependencies); allDependencies.addAll(fragments); for (Plugin dependency : pluginDependencies) { allDependencies.addAll(dependency.getAllDependencies()); } for (Plugin fragment : fragments) { allDependencies.addAll(fragment.getAllDependencies()); } } return allDependencies; } /** * Returns the absolute paths of all libraries (transitively). */ public Set<String> getAllLibPaths() { if (allLibs == null) { allLibs = new LinkedHashSet<String>(); for (String lib : getLibs()) { allLibs.add(getAbsoluteLibPath(lib)); } for (Plugin dependency : getPluginDependencies()) { allLibs.addAll(dependency.getAllLibPaths()); } } return allLibs; } /** * Returns the absolute path of the given library. */ public String getAbsoluteLibPath(String lib) { String prefix = getAbsolutePath() + "/"; if (lib.startsWith("/")) { // absolute paths to libraries must be handled differently return prefix + ".." + lib; } else { return prefix + lib; } } public File getLocation() { return location; } public String getAbsolutePath() { if (absolutePath == null) { absolutePath = location.getAbsolutePath(); } return absolutePath; } /** * Returns the absolute locations of the source folders in this plug-in, if there are any. */ public File[] getSourceFolders() { if (location.isFile()) { return new File[0]; } if (sourceFolders == null) { return new File[0]; } File[] sourceFolderFiles = new File[sourceFolders.size()]; int i = 0; for (String sourceFolder : sourceFolders) { sourceFolderFiles[i] = new File(location, sourceFolder); i++; } return sourceFolderFiles; } /** * Returns the relative paths of the source folders in this plug-in, if there are any. */ public Set<String> getRelativeSourceFolders() { if (location.isFile()) { return Collections.emptySet(); } if (sourceFolders == null) { return Collections.emptySet(); } return sourceFolders; } /** * Checks whether this plug-in depends on the given other plug-in. */ public boolean dependsOn(Plugin otherPlugin) { // check direct dependency if (getDependencies().contains(otherPlugin)) { return true; } for (Plugin dependency : getPluginDependencies()) { if (dependency.dependsOn(otherPlugin)) { return true; } } return false; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getIdentifier() == null) ? 0 : getIdentifier().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; Plugin other = (Plugin) obj; if (getIdentifier() == null) { if (other.getIdentifier() != null) return false; } else if (!getIdentifier().equals(other.getIdentifier())) return false; return true; } public boolean hasManifest() { return getManifestFile().exists(); } /** * Returns an input stream to access the manifest of this plug-in. * * This method is protected to allow test to override it. */ protected InputStream getManifestInputStream() throws IOException { File pluginLocation = getLocation(); if (pluginLocation.isFile()) { ZipFile zipFile = new ZipFile(pluginLocation); ZipEntry manifestEntry = zipFile.getEntry("META-INF/MANIFEST.MF"); if (manifestEntry == null) { return null; } InputStream inputStream = zipFile.getInputStream(manifestEntry); return inputStream; } else { File manifest = getManifestFile(); if (!manifest.exists()) { System.out.println("INFO: Project without MANIFEST: " + getLocation().getName()); return null; } return new FileInputStream(manifest); } } private File getManifestFile() { File manifest = new File(new File(getLocation(), "META-INF"), "MANIFEST.MF"); return manifest; } private void analyzeClassPath() throws IOException { InputStream dotClassPathInputStream = getDotClasspathInputStream(); if (dotClassPathInputStream != null) { DotClasspathReader classpathReader = new DotClasspathReader(dotClassPathInputStream); this.libs.addAll(classpathReader.getLibraries()); this.sourceFolders.addAll(classpathReader.getSourceFolders()); dotClassPathInputStream.close(); } } /** * This method is protected to allow test to override it. */ protected InputStream getDotClasspathInputStream() throws FileNotFoundException { File pluginLocation = getLocation(); if (pluginLocation.isDirectory()) { File dotClassPathFile = new File(pluginLocation, ".classpath"); if (!dotClassPathFile.exists()) { return null; } return new FileInputStream(dotClassPathFile); } else { // JARs do usually not contain .classpath files since they contain // compiled plug-ins. return null; } } public boolean isExperimental() { return new File(getAbsolutePath(), "EXPERIMENTAL").exists(); } /** * Returns true if this plug-in is a project that is built. If the plug-in was obtained from another location (e.g., * as a JAR from an Eclipse distribution), this method will return false. */ public boolean isProject() { return true; } public boolean isJarFile() { return location.isFile() && location.getName().endsWith(".jar"); } public Plugin getFragmentHost() { return fragmentHost; } public File getFile() { return getLocation(); } }