blob: e56891bbe3fd31aa7cad066002f787185a6dbd55 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 SSI Schaefer IT Solutions GmbH 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:
* SSI Schaefer IT Solutions GmbH
*******************************************************************************/
package org.eclipse.tea.library.build.model;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.tea.library.build.internal.Activator;
import org.osgi.framework.Version;
/**
* Stores useful information about an OSGi bundle.
*/
public abstract class BundleData {
/**
* the name of the bundle
*/
private final String bundleName;
/**
* the bundle directory; {@code null} for a JAR distribution
*/
protected final File bundleDir;
/**
* {@code true} for a source distribution; {@code false} for a binary
* distribution
*/
protected final boolean hasSource;
/**
* the JAR file; {@code null} for a source distribution or if the JAR is
* extracted
*/
protected final File jarFile;
/**
* content of the manifest file; optional
*/
protected final ManifestHolder manifest;
static final String[] EMPTY_STRINGS = {};
/**
* Creates the bundle data.
*
* @param projectName
* external project name; only in use if we couldn't read the
* manifest
* @param bundleDir
* the bundle directory; {@code null} for a JAR distribution
* @param hasSource
* {@code true} for a source distribution; {@code false} for a
* binary distribution
* @param jarFile
* the JAR file; {@code null} for a source distribution or if the
* JAR is extracted
*/
protected BundleData(String projectName, File bundleDir, boolean hasSource, File jarFile) {
this.bundleDir = bundleDir;
this.hasSource = hasSource;
this.jarFile = jarFile;
// read manifest
if (jarFile != null) {
manifest = readManifestFromJar(jarFile);
} else {
if (bundleDir.isDirectory()) {
manifest = readManifestFromDirectory(bundleDir);
} else {
// maybe the project is not installed
manifest = null;
}
}
// get the bundle name
if (manifest != null) {
ParameterValue symName = manifest.getSymbolicName();
if (symName != null) {
bundleName = symName.getValue();
if (projectName != null && !bundleName.equals(projectName)) {
Activator.log(IStatus.WARNING,
"Missmatch of names: projectName=" + projectName + " bundleName=" + bundleName, null);
}
} else {
bundleName = projectName;
}
} else {
bundleName = projectName;
}
}
/**
* Returns {@code true} if full access to the meta-data is possible.
*/
public boolean isMetadataOK() {
return manifest != null;
}
/**
* Returns the name of the bundle.
*/
public String getBundleName() {
return bundleName;
}
/**
* Returns the bundle directory; {@code null} for a JAR distribution.
*/
public final File getBundleDir() {
return bundleDir;
}
/**
* Returns {@code true} for a source distribution; {@code false} for a
* binary distribution.
*/
public final boolean hasSource() {
return hasSource;
}
/**
* Returns the JAR file; {@code null} for a source distribution or if the
* JAR is extracted.
*/
public final File getJarFile() {
return jarFile;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(\"" + getBundleName() + "\")";
}
/**
* Re-writes the manifest file; only possible for source distributions.
*
* @return {@code true} if we could write the manifest file
*/
public final boolean writeManifest() {
final File manifestFile = getManifestFile();
if (manifestFile == null) {
return false;
}
try {
manifest.write(manifestFile);
return true;
} catch (Exception ex) {
throw new IllegalStateException("cannot write " + manifestFile, ex);
}
}
/**
* Returns the path to the manifest file.
*
* @return path to the manifest file; {@code null} if not supported
*/
public final File getManifestFile() {
if (manifest == null || !hasSource || bundleDir == null) {
return null;
}
return new File(bundleDir, "META-INF/MANIFEST.MF");
}
/**
* Returns the version string of this bundle.
*/
public abstract String getBundleVersion();
/**
* Returns the version of the bundle as {@link Version}
*/
public final Version getVersion() {
return Version.parseVersion(getBundleVersion());
}
/**
* Sets the version string of this bundle.
*/
public abstract void setBundleVersion(String value);
protected static ManifestHolder readManifestFromDirectory(File bundleDir) {
File manifestFile = new File(bundleDir, "META-INF/MANIFEST.MF");
if (!manifestFile.isFile()) {
return null;
}
try {
FileInputStream fis = new FileInputStream(manifestFile);
try {
Manifest mf = new Manifest(fis);
return ManifestHolder.fromManifest(mf, manifestFile);
} finally {
fis.close();
}
} catch (Exception ex) {
throw new IllegalStateException("cannot read " + manifestFile, ex);
}
}
protected static ManifestHolder readManifestFromJar(File jarFile) {
try {
JarFile jar = new JarFile(jarFile);
try {
Manifest mf = jar.getManifest();
return ManifestHolder.fromManifest(mf, null);
} finally {
jar.close();
}
} catch (Exception ex) {
throw new IllegalStateException("cannot read " + jarFile, ex);
}
}
protected static Properties readBuildPropertiesFromDirectory(File bundleDir) {
File propFile = new File(bundleDir, "build.properties");
if (!propFile.isFile()) {
return null;
}
try {
FileInputStream fis = new FileInputStream(propFile);
try {
Properties result = new Properties();
result.load(fis);
return result;
} finally {
fis.close();
}
} catch (Exception ex) {
throw new IllegalStateException("cannot read " + propFile, ex);
}
}
protected static Properties readBuildPropertiesFromJar(File jarFile) {
try {
JarFile jar = new JarFile(jarFile);
try {
ZipEntry ze = jar.getEntry("build.properties");
if (ze == null) {
return null;
}
InputStream is = jar.getInputStream(ze);
try {
Properties result = new Properties();
result.load(is);
is.close();
return result;
} finally {
is.close();
}
} finally {
jar.close();
}
} catch (Exception ex) {
throw new IllegalStateException("cannot read " + jarFile, ex);
}
}
protected static String[] splitList(Attributes attributes, String name) {
String value = attributes.getValue(name);
return splitList(value);
}
protected static String[] splitList(Properties props, String name) {
String value = props.getProperty(name);
return splitList(value);
}
private static String[] splitList(String value) {
if (value == null) {
return EMPTY_STRINGS;
}
// we cannot call value.split(",") because of substrings like "[5.1.0,
// 5.2.0)"
List<String> parts = new ArrayList<>();
final int len = value.length();
char[] chars = value.toCharArray();
int i1 = 0, i2 = 1;
while (i2 < len) {
char c = chars[i2];
if (c == ',') {
addPart(parts, value.substring(i1, i2).trim());
i1 = i2 + 1;
i2 = i1 + 1;
} else if (c == '"') {
++i2;
while (i2 < len) {
c = chars[i2];
++i2;
if (c == '"') {
break;
}
}
} else {
++i2;
}
}
if (i1 < len) {
addPart(parts, value.substring(i1).trim());
}
return parts.toArray(new String[parts.size()]);
}
private static void addPart(List<String> parts, String v) {
if (v.isEmpty() || v.equals(",")) {
return;
}
parts.add(v);
}
protected static String[] mergeLists(String[] a, String[] b) {
if (a.length < 1) {
return b;
}
if (b.length < 1) {
return a;
}
String[] result = new String[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
protected static Map<String, List<String>> splitMap(Properties props, String prefix) {
Map<String, List<String>> result = new TreeMap<>();
for (Entry<Object, Object> entry : props.entrySet()) {
String fullKey = entry.getKey().toString();
if (fullKey.startsWith(prefix)) {
String itemName = fullKey.substring(prefix.length());
result.put(itemName, Arrays.asList(splitList(entry.getValue().toString())));
}
}
return result;
}
public abstract IProject getProject();
public final void refreshProject() {
IProject project = getProject();
if (project != null && project.exists() && project.isOpen()) {
try {
project.refreshLocal(IResource.DEPTH_INFINITE, null);
} catch (CoreException ex) {
throw new IllegalStateException(ex);
}
}
}
public void addDependency(String pluginName, boolean optional) {
manifest.addDependency(pluginName, optional);
}
}