/*******************************************************************************
* Copyright (c) 2005, 2009 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
*******************************************************************************/
package org.eclipse.buckminster.pde.tasks;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Locale;
import org.eclipse.buckminster.pde.PDEPlugin;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.equinox.internal.p2.publisher.eclipse.ExecutablesDescriptor;
import org.eclipse.pde.internal.build.IXMLConstants;
import org.eclipse.pde.internal.build.Utils;
import org.eclipse.pde.internal.swt.tools.IconExe;
/**
* This class is based on {@link org.eclipse.pde.internal.build.BrandingIron}.
* It was originally created solely to provide a workaround for bug 278909 but
* it has diverged substantially from the original since then.
*/
@SuppressWarnings("restriction")
public class BrandingIron implements IXMLConstants {
private static final String MARKER_NAME = "%EXECUTABLE_NAME%"; //$NON-NLS-1$
private static final String BUNDLE_NAME = "%BUNDLE_NAME%"; //$NON-NLS-1$
private static final String ICON_NAME = "%ICON_NAME%"; //$NON-NLS-1$
private static final String MARKER_KEY = "<key>CFBundleExecutable</key>"; //$NON-NLS-1$
private static final String BUNDLE_KEY = "<key>CFBundleName</key>"; //$NON-NLS-1$
private static final String ICON_KEY = "<key>CFBundleIconFile</key>"; //$NON-NLS-1$
private static final String STRING_START = "<string>"; //$NON-NLS-1$
private static final String STRING_END = "</string>"; //$NON-NLS-1$
private static final String XDOC_ICON = "-Xdock:icon=../Resources/Eclipse.icns"; //$NON-NLS-1$
private String[] icons = null;
private String name;
private String os = "win32"; //$NON-NLS-1$
private boolean brandIcons = true;
public void brand(ExecutablesDescriptor descriptor) throws Exception {
// if the name property is not set it will be ${launcher.name} so just
// bail.
if (name.startsWith("${")) //$NON-NLS-1$
return;
if (icons == null || icons[0].startsWith("${")) //$NON-NLS-1$
brandIcons = false;
File root = descriptor.getLocation();
// if the root does not exists (happens in some packaging cases) or
// there is already a file with target name and we don't need to update
// its icons, don't do anything
String testName = os.equals("win32") ? name + ".exe" : name; //$NON-NLS-1$ //$NON-NLS-2$
if (!root.exists() || (!brandIcons && new File(root, testName).exists()))
return;
// make sure the descriptor's location is a canonical path otherwise
// removing files from it may fail (this happens notably on Windows)
try {
root = root.getCanonicalFile();
} catch (IOException e) {
// try to live with an absolute path
root = root.getAbsoluteFile();
}
descriptor.setLocation(root);
if ("win32".equals(os)) //$NON-NLS-1$
brandWindows(descriptor);
else if ("linux".equals(os)) //$NON-NLS-1$
brandLinux(descriptor);
else if ("macosx".equals(os)) //$NON-NLS-1$
brandMac(descriptor);
else if ("solaris".equals(os)) //$NON-NLS-1$
brandSolaris(descriptor);
else if ("aix".equals(os)) //$NON-NLS-1$
brandAIX(descriptor);
else if ("hpux".equals(os)) //$NON-NLS-1$
brandHPUX(descriptor);
}
public void setIcons(String value) {
icons = value.split(",\\s*"); //$NON-NLS-1$
if (icons[0].startsWith("${")) { //$NON-NLS-1$
if (icons.length > 1) {
String[] temp = new String[icons.length - 1];
System.arraycopy(icons, 1, temp, 0, temp.length);
icons = temp;
} else
icons = null;
}
}
public void setName(String value) {
name = value;
}
public void setOS(String value) {
os = value;
}
private void brandAIX(ExecutablesDescriptor descriptor) {
renameLauncher(descriptor);
}
private void brandHPUX(ExecutablesDescriptor descriptor) {
renameLauncher(descriptor);
}
private void brandLinux(ExecutablesDescriptor descriptor) throws Exception {
renameLauncher(descriptor);
if (brandIcons) {
File icon = null;
if (icons.length > 0)
for (int i = 0; i < icons.length; i++) {
if (icons[i].toLowerCase(Locale.ENGLISH).endsWith(".xpm")) { //$NON-NLS-1$
icon = new File(icons[i]);
break;
}
}
else
icon = new File(icons[0]);
File targetIcon = new File(descriptor.getLocation(), "icon.xpm"); //$NON-NLS-1$
Utils.copy(icon, targetIcon);
descriptor.addFile(targetIcon);
}
}
private void brandMac(ExecutablesDescriptor descriptor) throws Exception {
// Initially the files are in: <root>/Eclipse.app/
// and they must appear in <root>/MyAppName.app/
// Because java does not support the rename of a folder, files are
// copied.
// Initialize the target folders
File root = descriptor.getLocation();
File target = new File(root, name + ".app/Contents"); //$NON-NLS-1$
target.mkdirs();
new File(target, "MacOS").mkdirs(); //$NON-NLS-1$
new File(target, "Resources").mkdirs(); //$NON-NLS-1$
File initialRoot = new File(root, "Eclipse.app/Contents"); //$NON-NLS-1$
copyMacLauncher(descriptor, initialRoot, target);
String iconName = ""; //$NON-NLS-1$
if (brandIcons) {
File icon = null;
if (icons.length > 1)
for (int i = 0; i < icons.length; i++) {
if (icons[i].toLowerCase(Locale.ENGLISH).endsWith(".icns")) { //$NON-NLS-1$
icon = new File(icons[i]);
break;
}
}
else
icon = new File(icons[0]);
if (icon.exists()) {
iconName = name + ".icns"; //$NON-NLS-1$
File initialIcon = new File(initialRoot, "Resources/Eclipse.icns"); //$NON-NLS-1$
File targetIcon = new File(target, "Resources/" + iconName); //$NON-NLS-1$
initialIcon.delete();
descriptor.removeFile(initialIcon);
Utils.copy(icon, targetIcon);
descriptor.addFile(targetIcon);
}
}
copyMacIni(descriptor, initialRoot, target, iconName);
modifyInfoPListFile(descriptor, initialRoot, target, iconName);
File splashApp = new File(initialRoot, "Resources/Splash.app"); //$NON-NLS-1$
if (splashApp.exists())
brandMacSplash(descriptor, initialRoot, target, iconName);
// move over any files remaining in the original folder
moveContents(descriptor, initialRoot, target);
new File(root, "Eclipse.app").delete(); //$NON-NLS-1$
}
/**
* Brand the splash.app Info.plist and link or copy the mac launcher. It is
* assumed that the mac launcher has been branded already.
*
* @param descriptor
*
* @param initialRoot
* @param target
* @param iconName
*/
private void brandMacSplash(ExecutablesDescriptor descriptor, File initialRoot, File target, String iconName) {
Logger logger = PDEPlugin.getLogger();
String splashContents = "Resources/Splash.app/Contents"; //$NON-NLS-1$
modifyInfoPListFile(descriptor, new File(initialRoot, splashContents), new File(target, splashContents), iconName);
String splashMacOS = splashContents + "/MacOS"; //$NON-NLS-1$
File macOSDir = new File(target, "MacOS"); //$NON-NLS-1$
File splashMacOSDir = new File(target, splashMacOS);
splashMacOSDir.mkdirs();
File targetLauncher = new File(splashMacOSDir, name);
INSTALL_SPLASH_LAUNCHER: {
String osName = System.getProperty("os.name"); //$NON-NLS-1$
// link the MacOS launcher for the splash app
if (osName != null && !osName.startsWith("Windows")) { //$NON-NLS-1$
try {
String[] command = new String[] { "ln", "-sf", "../../../MacOS/" + name, name }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Process proc = Runtime.getRuntime().exec(command, null, splashMacOSDir);
if (proc.waitFor() == 0)
break INSTALL_SPLASH_LAUNCHER;
} catch (IOException e) {
// ignore
} catch (InterruptedException e) {
// ignore
}
}
try {
Utils.copy(new File(macOSDir, name), targetLauncher);
} catch (IOException e) {
logger.error(e, "Could not copy macosx splash launcher"); //$NON-NLS-1$
}
}
File initialLauncher = findLauncher(new File(initialRoot, splashMacOS));
if (initialLauncher != null) {
try {
if (!initialLauncher.getCanonicalFile().equals(targetLauncher.getCanonicalFile()))
initialLauncher.delete();
} catch (IOException e) {
// ignore
}
descriptor.removeFile(initialLauncher);
}
descriptor.addFile(targetLauncher);
}
private void brandSolaris(ExecutablesDescriptor descriptor) throws Exception {
renameLauncher(descriptor);
if (brandIcons == false)
return;
File root = descriptor.getLocation();
for (int i = 0; i < icons.length; i++) {
String icon = icons[i];
int iconNameLength = icon.length();
if (iconNameLength < 5)
continue;
String extension = icon.substring(iconNameLength - 5);
// check if the extension is one of: .l.pm, .m.pm, .s.pm, .t.pm
if (extension.charAt(0) == '.' && extension.endsWith(".pm") && "lmst".indexOf(extension.charAt(1)) >= 0) { //$NON-NLS-1$ //$NON-NLS-2$
File targetIcon = new File(root, name + extension);
Utils.copy(new File(icon), targetIcon);
descriptor.addFile(targetIcon);
}
}
}
private void brandWindows(ExecutablesDescriptor descriptor) throws Exception {
Logger logger = PDEPlugin.getLogger();
File root = descriptor.getLocation();
String targetLauncherName = name + ".exe"; //$NON-NLS-1$
File templateLauncher = null;
for (String launcherName : new String[] { targetLauncherName, "launcher.exe", "eclipse.exe" }) { //$NON-NLS-1$ //$NON-NLS-2$
File launcher = new File(root, launcherName);
if (launcher.exists()) {
templateLauncher = launcher;
break;
}
}
if (brandIcons) {
if (templateLauncher != null) {
String[] args = new String[icons.length + 1];
args[0] = templateLauncher.getAbsolutePath();
System.arraycopy(icons, 0, args, 1, icons.length);
IconExe.main(args);
} else
logger.warning("Could not find executable to brand."); //$NON-NLS-1$
}
File targetLauncher = new File(root, targetLauncherName);
if (templateLauncher != null && !templateLauncher.getName().equals(targetLauncherName)) {
templateLauncher.renameTo(targetLauncher);
descriptor.replace(templateLauncher, targetLauncher);
}
descriptor.setExecutableName(name, true);
}
private void copyMacIni(ExecutablesDescriptor descriptor, File initialRoot, File target, String iconName) {
Logger logger = PDEPlugin.getLogger();
String brandedIniName = "MacOS/" + name + ".ini"; //$NON-NLS-1$//$NON-NLS-2$
// 4 possibilities, in order of preference:
// rcp.app/Contents/MacOS/rcp.ini (targetFile)
// Eclipse.app/Contents/MacOS/rcp.ini (brandedIni)
// Eclipse.app/Contents/MacOS/eclipse.ini (ini)
// Eclipse.app/Contents/MacOS/Eclipse.ini (ini2)
File targetFile = getCanonicalFile(new File(target, brandedIniName));
File brandedIni = getCanonicalFile(new File(initialRoot, brandedIniName));
File ini = getCanonicalFile(new File(initialRoot, "MacOS/eclipse.ini")); //$NON-NLS-1$
File ini2 = getCanonicalFile(new File(initialRoot, "MacOS/Eclipse.ini")); //$NON-NLS-1$
if (targetFile.exists()) {
// an ini already exists at the target, use that
if (brandedIni.exists() && !brandedIni.equals(targetFile)) {
brandedIni.delete();
descriptor.removeFile(brandedIni);
}
if (ini.exists() && !ini.equals(targetFile)) {
ini.delete();
descriptor.removeFile(ini);
}
if (ini2.exists() && !ini2.equals(targetFile)) {
ini2.delete();
descriptor.removeFile(ini2);
}
ini = targetFile;
} else if (brandedIni.exists()) {
// take the one that is already branded
if (ini.exists() && !ini.equals(brandedIni)) {
ini.delete();
descriptor.removeFile(ini);
}
if (ini2.exists() && !ini2.equals(brandedIni)) {
ini2.delete();
descriptor.removeFile(ini2);
}
ini = brandedIni;
} else {
if (ini.exists()) {
if (ini2.exists() && !ini2.equals(ini)) {
// this should not happen really
logger.warning("Found both %s and %s - discarding the latter", ini.getAbsolutePath(), ini2.getAbsolutePath()); //$NON-NLS-1$
ini2.delete();
descriptor.removeFile(ini2);
}
} else if (ini2.exists()) {
ini = ini2;
} else
return;
}
StringBuffer buffer;
try {
buffer = readFile(ini);
} catch (IOException e) {
logger.error(e, "Impossible to brand ini file"); //$NON-NLS-1$
return;
}
if (iconName.length() > 0) {
int xdoc = scan(buffer, 0, XDOC_ICON);
if (xdoc != -1) {
String icns = XDOC_ICON.replaceFirst("Eclipse.icns", iconName); //$NON-NLS-1$
buffer.replace(xdoc, xdoc + XDOC_ICON.length(), icns);
}
}
try {
transferStreams(new ByteArrayInputStream(buffer.toString().getBytes()), new FileOutputStream(targetFile));
if (!ini.equals(targetFile)) {
ini.delete();
descriptor.replace(ini, targetFile);
}
} catch (FileNotFoundException e) {
logger.error(e, "Impossible to brand ini file"); //$NON-NLS-1$
return;
} catch (IOException e) {
logger.error(e, "Impossible to brand ini file"); //$NON-NLS-1$
return;
}
}
private void copyMacLauncher(ExecutablesDescriptor descriptor, File initialRoot, File target) {
Logger logger = PDEPlugin.getLogger();
File launcher = getCanonicalFile(new File(initialRoot, "MacOS/launcher")); //$NON-NLS-1$
File eclipseLauncher = getCanonicalFile(new File(initialRoot, "MacOS/eclipse")); //$NON-NLS-1$
File targetFile = getCanonicalFile(new File(target, "MacOS/" + name)); //$NON-NLS-1$
if (!launcher.exists()) {
launcher = eclipseLauncher;
} else if (eclipseLauncher.exists() && !targetFile.equals(eclipseLauncher)) {
// we may actually have both if exporting from the mac
eclipseLauncher.delete();
descriptor.removeFile(eclipseLauncher);
}
if (!targetFile.equals(launcher)) {
try {
Utils.copy(launcher, targetFile);
} catch (IOException e) {
logger.error(e, "Could not copy macosx launcher"); //$NON-NLS-1$
return;
}
launcher.delete();
descriptor.replace(launcher, targetFile);
}
descriptor.setExecutableName(name, false);
}
private File findLauncher(File root) {
for (String launcherName : new String[] { "launcher", "eclipse" }) { //$NON-NLS-1$ //$NON-NLS-2$
File launcher = new File(root, launcherName);
if (launcher.exists())
return launcher;
}
return null;
}
private File getCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
return file;
}
}
private void modifyInfoPListFile(ExecutablesDescriptor descriptor, File initialRoot, File target2, String iconName) {
Logger logger = PDEPlugin.getLogger();
File infoPList = new File(initialRoot, "Info.plist"); //$NON-NLS-1$
StringBuffer buffer;
try {
buffer = readFile(infoPList);
} catch (IOException e) {
logger.error(e, "Impossible to brand info.plist file"); //$NON-NLS-1$
return;
}
int exePos = scan(buffer, 0, MARKER_NAME);
if (exePos != -1)
buffer.replace(exePos, exePos + MARKER_NAME.length(), name);
else {
exePos = scan(buffer, 0, MARKER_KEY);
if (exePos != -1) {
int start = scan(buffer, exePos + MARKER_KEY.length(), STRING_START);
int end = scan(buffer, start + STRING_START.length(), STRING_END);
if (start > -1 && end > start) {
buffer.replace(start + STRING_START.length(), end, name);
}
}
}
int bundlePos = scan(buffer, 0, BUNDLE_NAME);
if (bundlePos != -1)
buffer.replace(bundlePos, bundlePos + BUNDLE_NAME.length(), name);
else {
exePos = scan(buffer, 0, BUNDLE_KEY);
if (exePos != -1) {
int start = scan(buffer, exePos + BUNDLE_KEY.length(), STRING_START);
int end = scan(buffer, start + STRING_START.length(), STRING_END);
if (start > -1 && end > start) {
buffer.replace(start + STRING_START.length(), end, name);
}
}
}
int iconPos = scan(buffer, 0, ICON_NAME);
if (iconPos != -1)
buffer.replace(iconPos, iconPos + ICON_NAME.length(), iconName);
else {
exePos = scan(buffer, 0, ICON_KEY);
if (exePos != -1) {
int start = scan(buffer, exePos + ICON_KEY.length(), STRING_START);
int end = scan(buffer, start + STRING_START.length(), STRING_END);
if (start > -1 && end > start) {
buffer.replace(start + STRING_START.length(), end, iconName);
}
}
}
File target = new File(target2, "Info.plist"); //$NON-NLS-1$
try {
target.getParentFile().mkdirs();
transferStreams(new ByteArrayInputStream(buffer.toString().getBytes()), new FileOutputStream(target));
} catch (FileNotFoundException e) {
logger.error(e, "Impossible to brand info.plist file"); //$NON-NLS-1$
return;
} catch (IOException e) {
logger.error(e, "Impossible to brand info.plist file"); //$NON-NLS-1$
return;
}
try {
if (!infoPList.getCanonicalFile().equals(target.getCanonicalFile()))
infoPList.delete();
} catch (IOException e) {
// ignore
}
descriptor.replace(infoPList, target);
}
private void moveContents(ExecutablesDescriptor descriptor, File source, File target) {
Logger logger = PDEPlugin.getLogger();
if (!source.exists())
return;
try {
source = source.getCanonicalFile();
target = target.getCanonicalFile();
} catch (IOException e) {
logger.error(e, "Could not copy macosx resources."); //$NON-NLS-1$
return;
}
if (source.equals(target))
return;
target.getParentFile().mkdirs();
if (source.isDirectory()) {
moveDirectoryContents(descriptor, source, target);
} else {
source.renameTo(target);
descriptor.replace(source, target);
}
}
private void moveDirectoryContents(ExecutablesDescriptor descriptor, File sourceDirectory, File targetDirectory) {
targetDirectory.mkdir();
File[] contents = sourceDirectory.listFiles();
for (int i = 0; i < contents.length; i++) {
File dest = new File(targetDirectory, contents[i].getName());
if (contents[i].isDirectory()) {
moveDirectoryContents(descriptor, contents[i], dest);
} else {
contents[i].renameTo(dest);
descriptor.replace(contents[i], dest);
}
}
sourceDirectory.delete();
}
private StringBuffer readFile(File targetName) throws IOException {
InputStreamReader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(targetName)));
StringBuffer result = new StringBuffer();
char[] buf = new char[4096];
int count;
try {
count = reader.read(buf, 0, buf.length);
while (count != -1) {
result.append(buf, 0, count);
count = reader.read(buf, 0, buf.length);
}
} finally {
try {
reader.close();
} catch (IOException e) {
// ignore exceptions here
}
}
return result;
}
private void renameLauncher(ExecutablesDescriptor descriptor) {
File root = descriptor.getLocation();
File launcher = findLauncher(root);
if (launcher == null)
return;
File targetLauncher = new File(root, name);
launcher.renameTo(targetLauncher);
descriptor.replace(launcher, targetLauncher);
descriptor.setExecutableName(name, true);
}
private int scan(StringBuffer buf, int start, String targetName) {
return scan(buf, start, new String[] { targetName });
}
private int scan(StringBuffer buf, int start, String[] targets) {
for (int i = start; i < buf.length(); i++) {
for (int j = 0; j < targets.length; j++) {
if (i < buf.length() - targets[j].length()) {
String match = buf.substring(i, i + targets[j].length());
if (targets[j].equalsIgnoreCase(match))
return i;
}
}
}
return -1;
}
private void transferStreams(InputStream source, OutputStream destination) throws IOException {
source = new BufferedInputStream(source);
destination = new BufferedOutputStream(destination);
try {
byte[] buffer = new byte[8192];
while (true) {
int bytesRead = -1;
if ((bytesRead = source.read(buffer)) == -1)
break;
destination.write(buffer, 0, bytesRead);
}
} finally {
try {
source.close();
} catch (IOException e) {
// ignore
}
try {
destination.close();
} catch (IOException e) {
// ignore
}
}
}
}