blob: 04c9a3cacfe6914bd1310f7b5301d938e2d19f99 [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 static org.eclipse.tea.library.build.model.BundleData.splitList;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.tea.library.build.internal.Activator;
/**
* Abstraction of the famous {@code MANIFEST.MF} file.
* <p>
* Supports manipulation of the manifest whilst keeping track of the order of
* headers.
*/
final class ManifestHolder {
private static final ParameterValue[] EMPTY_VALUES = {};
private final Map<String, ManifestAttribute> attributes = new TreeMap<>();
private final File referenceFile;
private ManifestHolder(final Manifest manifest, final File referenceFile) {
final Attributes attr = manifest.getMainAttributes();
for (Object key : attr.keySet()) {
final String name = key.toString();
final String[] values = splitList(attr, name);
attributes.put(name, new ManifestAttribute(name, values));
}
this.referenceFile = referenceFile;
}
static ManifestHolder fromManifest(Manifest manifest, File manifestFile) {
if (manifest == null) {
return null;
}
return new ManifestHolder(manifest, manifestFile);
}
/**
* Returns the value list to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key.
*/
private ParameterValue[] getValues(String key) {
ManifestAttribute attr = attributes.get(key);
if (attr == null) {
return null;
}
return attr.values;
}
private ParameterValue[] safeList(String name) {
ParameterValue[] result = getValues(name);
if (result == null) {
return EMPTY_VALUES;
}
return result;
}
ParameterValue[] getListAttribute(String name) {
return safeList(name);
}
ParameterValue getSingleAttribute(String name) {
return firstValue(name);
}
/**
* Returns the first parameter value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key or there are
* no values.
*/
private ParameterValue firstValue(String key) {
ParameterValue[] result = getValues(key);
if (result == null || result.length < 1) {
return null;
}
return result[0];
}
private String[] safeValueList(String name) {
ParameterValue[] result = getValues(name);
if (result == null) {
return BundleData.EMPTY_STRINGS;
}
return ParameterValue.valuesFromList(result);
}
private String getSimple(String name) {
ParameterValue pv = firstValue(name);
if (pv == null) {
return null;
}
return pv.getValue();
}
private boolean getBoolean(String name) {
return Boolean.parseBoolean(getSimple(name));
}
private void putSimple(String name, String value) {
attributes.put(name, new ManifestAttribute(name, value));
}
private void putList(String name, String[] values) {
attributes.put(name, new ManifestAttribute(name, values));
}
private void putList(String name, ParameterValue[] values) {
attributes.put(name, new ManifestAttribute(name, values));
}
ParameterValue getSymbolicName() {
return firstValue("Bundle-SymbolicName");
}
String getDescription() {
return getSimple("Bundle-Name");
}
String getActivator() {
return getSimple("Bundle-Activator");
}
void setActivator(String activator) {
if (activator == null) {
attributes.remove("Bundle-Activator");
} else {
putSimple("Bundle-Activator", activator);
}
}
void addDependency(String pluginName, boolean optional) {
ManifestAttribute reqBundleAttr = attributes.get("Require-Bundle");
ParameterValue[] dependencies = reqBundleAttr.values;
ParameterValue[] newDependecies = new ParameterValue[dependencies.length + 1];
System.arraycopy(dependencies, 0, newDependecies, 0, dependencies.length);
String param = optional ? ";resolution:=optional" : "";
newDependecies[newDependecies.length - 1] = new ParameterValue(pluginName + param);
ManifestAttribute manifestAttr = new ManifestAttribute("Require-Bundle", newDependecies);
attributes.put("Require-Bundle", manifestAttr);
}
ParameterValue[] getDependencies() {
return safeList("Require-Bundle");
}
ParameterValue[] getMavenDependencies() {
if (attributes.containsKey("WAMAS-Build-Maven")) {
// DEPRECATED
return safeList("WAMAS-Build-Maven");
}
return safeList("Build-Maven");
}
ParameterValue[] getImportPackages() {
return safeList("Import-Package");
}
ParameterValue[] getExportPackages() {
return safeList("Export-Package");
}
void setExportPackages(ParameterValue[] value) {
if (value == null || value.length == 0) {
attributes.remove("Export-Package");
} else {
putList("Export-Package", value);
}
}
String getPlatformFilter() {
return getSimple("Eclipse-PlatformFilter");
}
void setPlatformFilter(String value) {
putSimple("Eclipse-PlatformFilter", value);
}
String[] getClassPath() {
return safeValueList("Bundle-ClassPath");
}
void setClassPath(String[] values) {
putList("Bundle-ClassPath", values);
}
ParameterValue[] getNativeCode() {
return safeList("Bundle-NativeCode");
}
String[] getRequiredExecutionEnvironment() {
return safeValueList("Bundle-RequiredExecutionEnvironment");
}
void setRequiredExecutionEnvironment(String[] values) {
putList("Bundle-RequiredExecutionEnvironment", values);
}
void setAutomaticModuleName(String value) {
putSimple("Automatic-Module-Name", value);
}
String getAutomaticModuleName() {
return getSimple("Automatic-Module-Name");
}
String getBundleVersion() {
return getSimple("Bundle-Version");
}
void setBundleVersion(String value) {
putSimple("Bundle-Version", value);
}
String getBundleVendor() {
return getSimple("Bundle-Vendor");
}
void setBundleVendor(String value) {
putSimple("Bundle-Vendor", value);
}
String getActivationPolicy() {
return getSimple("Bundle-ActivationPolicy");
}
void setLazyActivationPolicy() {
putSimple("Bundle-ActivationPolicy", "lazy");
}
/**
* Returns the fragment host, or {@code null} if no fragment host is
* defined.
*/
ParameterValue getFragmentHost() {
return firstValue("Fragment-Host");
}
/**
* Returns {@code true} if the 'unpack' flag must be set (used by features).
*/
boolean getNeedUnpack() {
ParameterValue value = firstValue("Eclipse-BundleShape");
if (value != null && value.getValue() != null && value.getValue().equals("dir")) {
return true;
}
return false;
}
String[] getBinaryInclude() {
return safeValueList("Binary-Include");
}
private static final String MANIFEST_VERSION = "Manifest-Version";
void write(File mfFile) throws IOException {
// calculate the order of the manifest entries
if (referenceFile != null) {
calculateOrder();
}
// always provide a manifest version
ManifestAttribute mfVer = attributes.get(MANIFEST_VERSION);
if (mfVer == null) {
mfVer = new ManifestAttribute(MANIFEST_VERSION, "1.0");
attributes.put(MANIFEST_VERSION, mfVer);
}
mfVer.order = Integer.MIN_VALUE;
// sort the manifest entries
List<ManifestAttribute> sorted = new ArrayList<>(attributes.values());
Collections.sort(sorted, new Comparator<ManifestAttribute>() {
@Override
public int compare(ManifestAttribute a1, ManifestAttribute a2) {
if (a1.order < a2.order) {
return -1;
}
if (a1.order > a2.order) {
return 1;
}
return a1.name.compareTo(a2.name);
}
});
// write the manifest
Writer ps = new FileWriter(mfFile);
try {
for (ManifestAttribute mf : sorted) {
writeAttribute(mf.name, mf.values, ps);
}
} finally {
ps.close();
}
}
private static void writeAttribute(String name, ParameterValue[] value, Writer ps) throws IOException {
ps.write(name);
ps.write(": ");
boolean firstLine = true;
for (ParameterValue pv : value) {
if (firstLine) {
firstLine = false;
} else {
ps.write(",\n ");
}
pv.write(ps);
}
ps.write('\n');
}
private void calculateOrder() {
List<String> lines = new ArrayList<>();
try {
LineNumberReader r = new LineNumberReader(new FileReader(referenceFile));
try {
String line;
while ((line = r.readLine()) != null) {
lines.add(line);
}
} finally {
r.close();
}
} catch (Exception e) {
Activator.log(IStatus.ERROR, "cannot read " + referenceFile, e);
return;
}
int index = 0;
for (String line : lines) {
for (ManifestAttribute mf : attributes.values()) {
if (line.startsWith(mf.name + ':')) {
mf.order = index;
break;
}
}
++index;
}
}
public boolean migrateUnpackInformation() {
if (getBoolean("Unpack")) {
putSimple("Eclipse-BundleShape", "dir");
attributes.remove("Unpack");
return true;
}
return false;
}
public ParameterValue getBuddyPolicy() {
return firstValue("Eclipse-BuddyPolicy");
}
public ParameterValue[] getBuddyRegistrations() {
return safeList("Eclipse-RegisterBuddy");
}
}