blob: 93daf5539cf0b03cb33f86904c019600189ec2ef [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* G&H Softwareentwicklung GmbH - internationalization implementation (bug 150933)
* Michael Seele - remove offline-allowed (bug 153403)
*******************************************************************************/
package org.eclipse.pde.internal.build.tasks;
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
/**
*
* @since 3.1
*/
public class JNLPGenerator extends DefaultHandler {
private SAXParser parser;
private final File featureRoot;
private final String codebase;
private final String j2se;
/**
* id = ???
* version = jnlp.version
* label = information.title
* provider-name = information.vendor
* image = information.icon
* feature.description = information.description
* feature.includes = extension
* feature.plugin = jar
*/
private final static SAXParserFactory parserFactory = SAXParserFactory.newInstance();
private PrintWriter out;
private String destination;
private String provider;
private String label;
private String version;
private String id;
private String description;
private boolean resourceWritten = false;
private String currentOS = null;
private String currentArch = null;
private Locale locale = null;
private PropertyResourceBundle nlsBundle = null;
private final boolean generateOfflineAllowed;
private Config[] configs;
/**
* For testing purposes only.
*/
public static void main(String[] args) {
JNLPGenerator generator = new JNLPGenerator(args[0], args[1], args[2], args[3]);
generator.process();
}
/**
* Constructs a feature parser.
*/
public JNLPGenerator(String feature, String destination, String codebase, String j2se) {
this(feature, destination, codebase, j2se, Locale.getDefault(), true, null);
}
/**
* Constructs a feature parser.
*/
public JNLPGenerator(String feature, String destination, String codebase, String j2se, Locale locale, boolean generateOfflineAllowed, String configs) {
super();
this.featureRoot = new File(feature);
this.destination = destination;
this.codebase = codebase;
this.j2se = j2se;
this.locale = locale;
this.generateOfflineAllowed = generateOfflineAllowed;
try {
parserFactory.setNamespaceAware(true);
parser = parserFactory.newSAXParser();
} catch (ParserConfigurationException e) {
System.out.println(e);
} catch (SAXException e) {
System.out.println(e);
}
setConfigInfo(configs);
}
/**
* Parses the specified url and constructs a feature
*/
public void process() {
InputStream in = null;
final String FEATURE_XML = "feature.xml"; //$NON-NLS-1$
try {
ZipFile featureArchive = null;
InputStream nlsStream = null;
if (featureRoot.isFile()) {
featureArchive = new ZipFile(featureRoot);
nlsStream = getNLSStream(featureArchive);
ZipEntry featureXML = featureArchive.getEntry(FEATURE_XML);
in = featureArchive.getInputStream(featureXML);
} else {
nlsStream = getNLSStream(this.featureRoot);
in = new BufferedInputStream(new FileInputStream(new File(featureRoot, FEATURE_XML)));
}
try {
if (nlsStream != null) {
nlsBundle = new PropertyResourceBundle(nlsStream);
nlsStream.close();
}
} catch (IOException e) {
// do nothing
}
try {
parser.parse(new InputSource(in), this);
writeResourceEpilogue();
writeEpilogue();
} catch (SAXException e) {
//Ignore the exception
} finally {
in.close();
if (out != null)
out.close();
if (featureArchive != null)
featureArchive.close();
}
} catch (IOException e) {
//Ignore the exception
}
}
/**
* Search for nls properties files and return the stream if files are found.
* First try to load the default properties file, then one with the default
* locale settings and if nothing matches, return the stream of the first
* properties file found.
*/
private InputStream getNLSStream(File root) {
String appendix = ".properties"; //$NON-NLS-1$
String[] potentials = createNLSPotentials();
Map<String, File> validEntries = new HashMap<>();
File[] files = root.listFiles();
for (int i = 0; i < files.length; i++) {
String filename = files[i].getName();
if (filename.endsWith(appendix)) {
validEntries.put(filename, files[i]);
}
}
InputStream stream = null;
if (validEntries.size() > 0) {
for (int i = 0; i < potentials.length; i++) {
File file = validEntries.get(potentials[i]);
if (file != null) {
try {
stream = new BufferedInputStream(new FileInputStream(file));
break;
} catch (IOException e) {
// do nothing
}
}
}
if (stream == null) {
File file = validEntries.values().iterator().next();
try {
stream = new BufferedInputStream(new FileInputStream(file));
} catch (IOException e) {
// do nothing
}
}
}
return stream;
}
/**
* Search for nls properties files and return the stream if files are found.
* First try to load the default properties file, then one with the default
* locale settings and if nothing matches, return the stream of the first
* founded properties file.
*/
private InputStream getNLSStream(ZipFile featureArchive) {
String appendix = ".properties"; //$NON-NLS-1$
String[] potentials = createNLSPotentials();
Map<String, ZipEntry> validEntries = new HashMap<>();
for (Enumeration<? extends ZipEntry> enumeration = featureArchive.entries(); enumeration.hasMoreElements();) {
ZipEntry entry = enumeration.nextElement();
String entryName = entry.getName();
if (entryName.endsWith(appendix)) {
validEntries.put(entryName, entry);
}
}
InputStream stream = null;
if (validEntries.size() > 0) {
for (int i = 0; i < potentials.length; i++) {
ZipEntry entry = validEntries.get(potentials[i]);
if (entry != null) {
try {
stream = featureArchive.getInputStream(entry);
break;
} catch (IOException e) {
// do nothing
}
}
}
if (stream == null) {
ZipEntry entry = validEntries.values().iterator().next();
try {
stream = featureArchive.getInputStream(entry);
} catch (IOException e) {
// do nothing
}
}
}
return stream;
}
private String[] createNLSPotentials() {
String suffix = "feature"; //$NON-NLS-1$
String appendix = ".properties"; //$NON-NLS-1$
String language = locale.getLanguage();
String country = locale.getCountry();
String variant = locale.getVariant();
String potential1 = '_' + language + '_' + country + '_' + variant;
String potential2 = '_' + language + '_' + country;
String potential3 = '_' + language;
String potential4 = ""; //$NON-NLS-1$
String[] potentials = new String[] {potential1, potential2, potential3, potential4};
for (int i = 0; i < potentials.length; i++) {
potentials[i] = suffix + potentials[i] + appendix;
}
return potentials;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
try {
if ("feature".equals(localName)) { //$NON-NLS-1$
processFeature(attributes);
} else if ("includes".equals(localName)) { //$NON-NLS-1$
processIncludes(attributes);
} else if ("description".equals(localName)) { //$NON-NLS-1$
processDescription(attributes);
} else if ("plugin".equals(localName)) { //$NON-NLS-1$
processPlugin(attributes);
}
} catch (IOException e) {
throw new SAXException(e);
}
}
private void processPlugin(Attributes attributes) throws IOException {
writePrologue();
String pluginId = attributes.getValue("id"); //$NON-NLS-1$
String pluginVersion = attributes.getValue("version"); //$NON-NLS-1$
String os = attributes.getValue("os"); //$NON-NLS-1$
String ws = attributes.getValue("ws"); //$NON-NLS-1$
String arch = attributes.getValue("arch"); //$NON-NLS-1$
if (isValidEnvironment(os, ws, arch)) {
writeResourcePrologue(os, ws, arch);
out.println("\t\t<jar href=\"plugins/" + pluginId + "_" + pluginVersion + ".jar\"/>"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
}
}
private void writeResourceEpilogue() {
if (!resourceWritten)
return;
out.println("\t</resources>"); //$NON-NLS-1$
resourceWritten = false;
currentOS = null;
}
private void writeResourcePrologue(String os, String ws, String arch) {
if (os == null)
os = ws;
os = convertOS(os);
arch = convertArch(arch);
if (resourceWritten && osMatch(os) && archMatch(arch))
return;
if (resourceWritten)
writeResourceEpilogue();
out.println("\t<resources" + (os == null ? "" : " os=\"" + os + "\"") + (arch == null ? "" : " arch=\"" + arch + "\"") + ">"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$//$NON-NLS-7$ //$NON-NLS-8$
resourceWritten = true;
currentOS = os;
currentArch = arch;
}
private String convertOS(String os) {
if (os == null)
return null;
if ("win32".equalsIgnoreCase(os)) //$NON-NLS-1$
return "Windows"; //$NON-NLS-1$
if ("macosx".equalsIgnoreCase(os)) //$NON-NLS-1$
return "Mac"; //$NON-NLS-1$
if ("linux".equalsIgnoreCase(os)) //$NON-NLS-1$
return "Linux"; //$NON-NLS-1$
if ("solaris".equalsIgnoreCase(os)) //$NON-NLS-1$
return "Solaris"; //$NON-NLS-1$
if ("hpux".equalsIgnoreCase(os)) //$NON-NLS-1$
return "HP-UX"; //$NON-NLS-1$
if ("aix".equalsIgnoreCase(os)) //$NON-NLS-1$
return "AIX"; //$NON-NLS-1$
return os;
}
private boolean osMatch(String os) {
if (os == currentOS)
return true;
if (os == null)
return false;
return os.equals(currentOS);
}
private String convertArch(String arch) {
if (arch == null)
return null;
if ("x86".equals(arch)) //$NON-NLS-1$
return "x86"; //$NON-NLS-1$
if ("PA_RISC".equals(arch)) //$NON-NLS-1$
return "PA_RISC"; //$NON-NLS-1$
if ("ppc".equals(arch)) //$NON-NLS-1$
return "ppc"; //$NON-NLS-1$
if ("sparc".equals(arch)) //$NON-NLS-1$
return "sparc"; //$NON-NLS-1$
if ("x86_64".equals(arch))//$NON-NLS-1$
return "x86_64"; //$NON-NLS-1$
if ("ia64".equals(arch)) //$NON-NLS-1$
return "ia64"; //$NON-NLS-1$
if ("ia64_32".equals(arch)) //$NON-NLS-1$
return "ia64_32"; //$NON-NLS-1$
return arch;
}
private boolean archMatch(String arch) {
if (arch == currentOS)
return true;
if (arch == null)
return false;
return arch.equals(currentArch);
}
private void processDescription(Attributes attributes) {
// ignoring for now
}
private void processIncludes(Attributes attributes) throws IOException {
writePrologue();
String inclusionId = attributes.getValue("id"); //$NON-NLS-1$
String inclusionVersion = attributes.getValue("version"); //$NON-NLS-1$
String name = attributes.getValue("name"); //$NON-NLS-1$
String os = attributes.getValue("os"); //$NON-NLS-1$
String ws = attributes.getValue("ws"); //$NON-NLS-1$
String arch = attributes.getValue("arch"); //$NON-NLS-1$
if (isValidEnvironment(os, ws, arch)) {
writeResourcePrologue(os, ws, arch);
out.print("\t\t<extension ");//$NON-NLS-1$
if (name != null)
out.print("name=\"" + name + "\" "); //$NON-NLS-1$ //$NON-NLS-2$
if (inclusionId != null) {
out.print("href=\"features/" + inclusionId); //$NON-NLS-1$
if (inclusionVersion != null)
out.print('_' + inclusionVersion);
out.print(".jnlp\" "); //$NON-NLS-1$
}
out.println("/>"); //$NON-NLS-1$
}
}
private void processFeature(Attributes attributes) {
id = attributes.getValue("id"); //$NON-NLS-1$
version = attributes.getValue("version"); //$NON-NLS-1$
label = processNLS(attributes.getValue("label")); //$NON-NLS-1$
provider = processNLS(attributes.getValue("provider-name")); //$NON-NLS-1$
}
/**
* Search for a human readable string in the feature.properties file(s) if
* the given string is a translateable key.
*
* @param string a translateable key or a normal string(nothing is done)
*
* @return a translateabled string or the given string if it is not a
* translateable key
*/
private String processNLS(String string) {
if (string == null)
return null;
string = string.trim();
if (!string.startsWith("%")) { //$NON-NLS-1$
return string;
}
if (string.startsWith("%%")) { //$NON-NLS-1$
return string.substring(1);
}
int index = string.indexOf(" "); //$NON-NLS-1$
String key = index == -1 ? string : string.substring(0, index);
String dflt = index == -1 ? string : string.substring(index + 1);
if (nlsBundle == null) {
return dflt;
}
try {
return nlsBundle.getString(key.substring(1));
} catch (MissingResourceException e) {
return dflt;
}
}
private void writePrologue() throws IOException {
if (out != null)
return;
if (destination == null) {
featureRoot.getParentFile();
destination = featureRoot.getParent() + '/';
}
if (destination.endsWith("/") || destination.endsWith("\\")) //$NON-NLS-1$ //$NON-NLS-2$
destination = new File(featureRoot.getParentFile(), id + "_" + version + ".jnlp").getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$
out = new PrintWriter(new BufferedOutputStream(new FileOutputStream(destination)));
writePrologue();
out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$
out.print("<jnlp spec=\"1.0+\" "); //$NON-NLS-1$
if (codebase != null)
out.print("codebase=\"" + codebase); //$NON-NLS-1$
out.println("\">"); //$NON-NLS-1$
out.println("\t<information>"); //$NON-NLS-1$
if (label != null)
out.println("\t\t<title>" + label + "</title>"); //$NON-NLS-1$ //$NON-NLS-2$
if (provider != null)
out.println("\t\t<vendor>" + provider + "</vendor>"); //$NON-NLS-1$ //$NON-NLS-2$
if (description != null)
out.println("\t\t<description>" + description + "</description>"); //$NON-NLS-1$ //$NON-NLS-2$
if (generateOfflineAllowed)
out.println("\t\t<offline-allowed/>"); //$NON-NLS-1$
out.println("\t</information>"); //$NON-NLS-1$
out.println("\t<security>"); //$NON-NLS-1$
out.println("\t\t<all-permissions/>"); //$NON-NLS-1$
out.println("\t</security>"); //$NON-NLS-1$
out.println("\t<component-desc/>"); //$NON-NLS-1$
out.println("\t<resources>"); //$NON-NLS-1$
out.println("\t\t<j2se version=\"" + j2se + "\" />"); //$NON-NLS-1$ //$NON-NLS-2$
out.println("\t</resources>"); //$NON-NLS-1$
}
private void writeEpilogue() {
out.println("</jnlp>"); //$NON-NLS-1$
}
private boolean isMatching(String candidateValues, String siteValues) {
if (candidateValues == null)
return true;
if (siteValues == null)
return false;
if ("*".equals(candidateValues)) //$NON-NLS-1$
return true;
if ("".equals(candidateValues)) //$NON-NLS-1$
return true;
StringTokenizer siteTokens = new StringTokenizer(siteValues, ","); //$NON-NLS-1$
//$NON-NLS-1$
while (siteTokens.hasMoreTokens()) {
StringTokenizer candidateTokens = new StringTokenizer(candidateValues, ","); //$NON-NLS-1$
String siteValue = siteTokens.nextToken();
while (candidateTokens.hasMoreTokens()) {
if (siteValue.equalsIgnoreCase(candidateTokens.nextToken()))
return true;
}
}
return false;
}
private boolean isValidEnvironment(String os, String ws, String arch) {
if (configs.length == 0)
return true;
for (int i = 0; i < configs.length; i++) {
if (isMatching(os, configs[i].getOs()) && isMatching(ws, configs[i].getWs()) && isMatching(arch, configs[i].getArch()))
return true;
}
return false;
}
private void setConfigInfo(String spec) {
if (spec != null && spec.startsWith("$")) { //$NON-NLS-1$
configs = new Config[0];
return;
}
if (spec == null) {
configs = new Config[] {Config.genericConfig()};
return;
}
StringTokenizer tokens = new StringTokenizer(spec, "&"); //$NON-NLS-1$
int configNbr = tokens.countTokens();
ArrayList<Config> configInfos = new ArrayList<>(configNbr);
while (tokens.hasMoreElements()) {
String aConfig = tokens.nextToken();
StringTokenizer configTokens = new StringTokenizer(aConfig, ","); //$NON-NLS-1$
if (configTokens.countTokens() == 3) {
Config toAdd = new Config(configTokens.nextToken().trim(), configTokens.nextToken().trim(), configTokens.nextToken().trim());
if (toAdd.equals(Config.genericConfig()))
toAdd = Config.genericConfig();
configInfos.add(toAdd);
}
}
if (configInfos.size() == 0)
configInfos.add(Config.genericConfig());
configs = configInfos.toArray(new Config[configInfos.size()]);
}
}