/******************************************************************************* * Copyright (c) 2005, 2008 IBM Corporation 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: * 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 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 = (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 = (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 validEntries = new HashMap(); for (Enumeration enumeration = featureArchive.entries(); enumeration.hasMoreElements();) { ZipEntry entry = (ZipEntry) 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 = (ZipEntry) validEntries.get(potentials[i]); if (entry != null) { try { stream = featureArchive.getInputStream(entry); break; } catch (IOException e) { // do nothing } } } if (stream == null) { ZipEntry entry = (ZipEntry) 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; } 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))return true; //$NON-NLS-1$ if ("".equals(candidateValues))return true; //$NON-NLS-1$ 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 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 = (Config[]) configInfos.toArray(new Config[configInfos.size()]); } }