blob: a46287bb697a923d149a16b67ea4703401fb2036 [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.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.core.resources.IProject;
import org.eclipse.tea.library.build.services.TeaBuildVersionService;
import org.eclipse.tea.library.build.util.FileUtils;
import org.eclipse.tea.library.build.util.StreamHelper;
import org.eclipse.tea.library.build.util.StringHelper;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* Stores useful information about a RCP feature.
*/
public class FeatureData extends BundleData {
/**
* content of the feature XML file
*/
private Document document;
/**
* A version override that is valid as long as there is no document set.
* This is to be able to set the version of a feature that is to be
* generated in the future but does not yet exist.
*/
private String version;
private final IProject project;
private final TeaBuildVersionService bvService;
/**
* Creates the feature data for a source distribution.
*
* @param project
* Eclipse project
* @param bvService
*/
public FeatureData(IProject project, TeaBuildVersionService bvService) {
super(project.getName(), project.getLocation().toFile(), true, null);
this.project = project;
this.bvService = bvService;
this.document = readFeatureXml();
}
@Override
public IProject getProject() {
return project;
}
private static String trim(String text) {
if (text == null || text.isEmpty()) {
return null;
}
text = text.trim();
if (text.isEmpty()) {
return null;
}
return text;
}
@Override
public boolean isMetadataOK() {
return document != null;
}
@Override
public final String getBundleVersion() {
if (document != null) {
final Element rootElement = document.getDocumentElement();
return trim(rootElement.getAttribute("version"));
} else if (version != null) {
return version;
}
// can happen when building FeatureData from a workspace project without
// manifest.
return bvService.getBuildVersion();
}
@Override
public String getBundleName() {
if (document != null) {
final Element rootElement = document.getDocumentElement();
return trim(rootElement.getAttribute("id"));
}
// can happen when building FeatureData from a workspace project without
// manifest.
return super.getBundleName();
}
public String getBundleLabel() {
if (document != null) {
final Element rootElement = document.getDocumentElement();
String lbl = trim(rootElement.getAttribute("label"));
if (!StringHelper.isNullOrEmpty(lbl)) {
return lbl;
}
}
return getBundleName();
}
@Override
public final void setBundleVersion(String value) {
if (document != null) {
final Element rootElement = document.getDocumentElement();
rootElement.setAttribute("version", value);
} else {
version = value;
}
}
public final String getBundleVendor() {
if (document != null) {
final Element rootElement = document.getDocumentElement();
return trim(rootElement.getAttribute("provider-name"));
}
// we're just creating it, no feature.xml there yet. so it's ours. i'm
// sure. period.
return bvService.getDefaultVendor();
}
public final void setBundleVendor(String value) {
final Element rootElement = document.getDocumentElement();
rootElement.setAttribute("provider-name", value);
}
/**
* Returns the list of plugin IDs, which defines the content of this
* feature.
*/
public final List<PluginInfo> getPluginInfos() {
List<PluginInfo> pluginInfos = new ArrayList<>();
final Element rootElement = document.getDocumentElement();
final NodeList rootNodes = rootElement.getChildNodes();
for (int i = 0; i < rootNodes.getLength(); i++) {
Node node = rootNodes.item(i);
if (node instanceof Element) {
Element child = (Element) node;
if ("plugin".equals(child.getTagName())) {
PluginInfo info = new PluginInfo();
info.id = child.getAttribute("id");
info.version = child.getAttribute("version");
pluginInfos.add(info);
}
}
}
return pluginInfos;
}
/**
* reloads feature XML file
*/
final void reloadFeatureXml() {
document = readFeatureXml();
}
File getFeatureXmlFile() {
return new File(bundleDir, "feature.xml");
}
/**
* Reads the feature XML file.
*/
Document readFeatureXml() {
try {
return FileUtils.readXml(getFeatureXmlFile());
} catch (Exception ex) {
throw new IllegalStateException("cannot read " + jarFile, ex);
}
}
/**
* Re-writes the manifest file; only possible for source distributions.
*
* @return {@code true} if we could write the manifest file
*/
public final boolean writeFeatureXml() {
if (document == null) {
return false;
}
final File xmlFile = new File(bundleDir, "feature.xml");
try {
writeXml(document, xmlFile);
return true;
} catch (Exception ex) {
throw new IllegalStateException("cannot write " + xmlFile, ex);
}
}
/**
* Writes a DOM into a XML file; the formatting looks like an Eclipse
* feature XML.
*
* @param document
* DOM
* @param xmlFile
* output file
* @throws IOException
* if an IO error occurred writing the xmlFile
*/
public static void writeXml(Document document, File xmlFile) throws IOException {
Writer w = null;
try {
w = new OutputStreamWriter(new FileOutputStream(xmlFile), "UTF-8");
w.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
Element rootElement = document.getDocumentElement();
writeXml(rootElement, w, "");
} finally {
StreamHelper.closeQuietly(w);
}
}
private static void writeXml(Element elem, Writer w, final String rootIndent) throws IOException {
final String tagName = elem.getTagName();
w.write(rootIndent);
w.write('<');
w.write(tagName);
final String childIndent = rootIndent + " ";
final String attrIndent = childIndent + " ";
final boolean isMain = tagName.equals("feature") || tagName.equals("plugin") || tagName.equals("includes");
NamedNodeMap attrs = elem.getAttributes();
int len = attrs.getLength();
Map<String, String> sortedAttributes = new TreeMap<>(FeatureAttributeComparator.fromTag(tagName));
for (int i = 0; i < len; ++i) {
Attr attr = (Attr) attrs.item(i);
sortedAttributes.put(attr.getName(), attr.getValue());
}
for (Map.Entry<String, String> entry : sortedAttributes.entrySet()) {
if (isMain) {
w.write('\n');
w.write(attrIndent);
} else {
w.write(' ');
}
w.write(entry.getKey());
w.write("=\"");
w.write(entry.getValue());
w.write('"');
}
NodeList children = elem.getChildNodes();
len = children.getLength();
if (len == 0) {
w.write("/>\n");
} else {
w.write(">\n");
for (int i = 0; i < len; ++i) {
Node child = children.item(i);
if (child instanceof Element) {
if (isMain) {
w.write('\n');
}
writeXml((Element) child, w, childIndent);
continue;
}
if (child instanceof Text) {
String text = ((Text) child).getWholeText().trim();
if (!text.isEmpty()) {
w.write(childIndent);
w.write(text);
w.write('\n');
}
continue;
}
w.write("<!-- " + child.getClass().getSimpleName() + " -->\n");
}
if (rootIndent.isEmpty()) {
w.write('\n');
} else {
w.write(rootIndent);
}
w.write("</");
w.write(tagName);
w.write(">\n");
}
}
public Document generateFeatureHeader() throws ParserConfigurationException {
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
Document doc = docBuilder.newDocument();
// create the root element and add it to the document
Element root = doc.createElement("feature");
root.setAttribute("id", getBundleName());
root.setAttribute("label", getBundleLabel());
root.setAttribute("version", getBundleVersion());
root.setAttribute("provider-name", getBundleVendor());
doc.appendChild(root);
return doc;
}
}