blob: 7efb4d9dd67df62371a0f9f74ab0bfb873ced5b4 [file] [log] [blame]
package org.eclipse.epp.installer.internal.core.operations;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.epp.installer.core.InstallOptions;
import org.eclipse.epp.installer.core.eclipse.EclipseInstallation;
import org.eclipse.epp.installer.core.model.Context;
import org.eclipse.epp.installer.core.model.SequentialOperation;
import org.eclipse.epp.installer.core.product.IProductVersion;
/**
* Modify the platform.xml file in specified Eclipse installation.
*/
public class ModifyPlatformXMLOperation extends SequentialOperation
{
private EclipseInstallation eclipseInstall;
private File extensionDir;
private String extensionId;
private InstallOptions options;
/**
* Constructor for ModifyPlatformXMLOperation.
*/
public ModifyPlatformXMLOperation(InstallOptions options, EclipseInstallation eclipseInstall, File extensionDir,
String extensionId) {
if (options == null || eclipseInstall == null || extensionDir == null || extensionId == null)
throw new IllegalArgumentException();
this.options = options;
this.eclipseInstall = eclipseInstall;
this.extensionDir = extensionDir;
this.extensionId = extensionId;
}
/**
* Modify the platform.xml file
*/
protected IStatus run(Context installer) {
File platformXmlFile = null;
// For Eclipse 3.2 and beyond, modify the
// configuration/org.eclipse.update/platform.xml file if it exists
// See CleanEclipseConfigurationOperation#prepareForEclipse3()
IProductVersion version = eclipseInstall.getEclipseVersion();
File rootDir = eclipseInstall.getEclipseDir();
if (version.getMajor() > 3 || (version.getMajor() == 3 && version.getMinor() >= 2))
platformXmlFile = new File(new File(new File(rootDir, "configuration"),
"org.eclipse.update"), "platform.xml");
// Skip this operation if the file does not exist
if (platformXmlFile == null)
return Status.OK_STATUS;
if (!platformXmlFile.exists()) {
if (options.isVerbose())
System.out.println("Failed to locate platform.xml at " + platformXmlFile.getPath());
return Status.OK_STATUS;
}
// Backup current config.ini and platform.xml file
File backupFile = new File(rootDir, "configuration-backup.zip");
if (!backupFile.exists()) {
File configIniFile = new File(new File(rootDir, "configuration"), "config.ini");
if (options.isVerbose()) {
System.out.println("Backup configuration files before modification");
System.out.println(" config.ini: " + configIniFile.getPath());
System.out.println(" platform.xml: " + platformXmlFile.getPath());
System.out.println(" backup file: " + backupFile.getPath());
}
try {
zipFiles(backupFile, rootDir, new File[] {configIniFile, platformXmlFile});
}
catch (IOException e) {
System.err.println("Failed to backup configuration files before modification");
System.err.println(" config.ini: " + configIniFile.getPath());
System.err.println(" platform.xml: " + platformXmlFile.getPath());
System.err.println(" backup file: " + backupFile.getPath());
e.printStackTrace();
}
}
// Extract the feature information from the extension directory
// This ASSUMES that the extension directory has already been created and
// populated
Feature[] data = readFeatures(extensionDir);
if (data == null || data.length == 0) {
System.out.println("Failed to read features from " + extensionDir.getPath());
return Status.OK_STATUS;
}
// TODO [author=Dan] if there are multiple linked sites being created at once,
// then this operation could be optimized by caching the content and writing once
// after all of the modifications have been made
// Replace existing content with new content
String content = readContent(platformXmlFile);
if (content == null)
return Status.OK_STATUS;
content = removeLinkFileSite(content, extensionId);
content = appendLinkFileSite(content, rootDir, extensionId, extensionDir, data);
writeContent(platformXmlFile, content);
return Status.OK_STATUS;
}
/**
* Method hashCode.
*
* @return int
*/
public int hashCode() {
return extensionDir.hashCode() + eclipseInstall.hashCode() + extensionId.hashCode();
}
/**
* Method equals.
*
* @param o Object
* @return boolean
*/
public boolean equals(Object o) {
if (o instanceof ModifyPlatformXMLOperation) {
ModifyPlatformXMLOperation mpo = (ModifyPlatformXMLOperation) o;
return (extensionDir.equals(mpo.extensionDir) && eclipseInstall.equals(mpo.eclipseInstall) && extensionId
.equals(mpo.extensionId));
}
return false;
}
// //////////////////////////////////////////////////////////////////////////
//
// Utilities
//
// //////////////////////////////////////////////////////////////////////////
/**
* Append a new link file site to the specified platform.xml content.
*
* @param content the original content of the platform.xml file
* @param eclipseDir The eclipse directory (not <code>null</code>)
* @param linkId the name of the link to be appended. This is the same as the name of
* the link file minus the ".link" file extension.
* @param extensionDir The directory containing the Eclipse extension (e.g. C:/Program
* Files/Instantiations/CodeProAnt_v4.5.1/E-3.2)
* @param featureId The identifier for the feature to be linked
* @param featureVersion The version for the feature to be linked
* @param pluginId The identifier of the primary plugin for that feature or
* <code>null</code> if the feature does not have a primary plugin
* @return the content with the link site appended (not <code>null</code>)
*/
static String appendLinkFileSite(String content, File eclipseDir, String linkId, File extensionDir, Feature[] data)
{
// Find the insertion point and the newline character(s)
int index = content.lastIndexOf("</config>");
int previousElementEnd = content.lastIndexOf(">", index) + 1;
String newline = content.substring(previousElementEnd, index);
// Insert the link file site
StringBuffer buf = new StringBuffer(content.length() + 500);
buf.append(content.substring(0, index));
String eclipsePath = eclipseDir.getAbsolutePath().replace('\\', '/');
String extensionPath = extensionDir.getAbsolutePath().replace('\\', '/') + "/eclipse";
// Site element
buf.append("<site enabled=\"true\" linkfile=\"");
buf.append(eclipsePath + "/links/");
buf.append(linkId);
buf.append(".link\" policy=\"USER-EXCLUDE\" updateable=\"true\" url=\"file:/");
buf.append(extensionPath);
buf.append("/\">");
buf.append(newline);
// Embedded feature element(s)
for (int i = 0; i < data.length; i++) {
Feature feature = data[i];
buf.append("<feature id=\"");
buf.append(feature.featureId);
if (feature.primaryPluginId != null && feature.primaryPluginId.length() > 0) {
buf.append("\" plugin-identifier=\"");
buf.append(feature.primaryPluginId);
}
buf.append("\" url=\"features/");
buf.append(feature.featureId);
buf.append("_");
buf.append(feature.featureVersion);
buf.append("/\" version=\"");
buf.append(feature.featureVersion);
buf.append("\">");
buf.append(newline);
buf.append("</feature>");
buf.append(newline);
}
buf.append("</site>");
buf.append(newline);
buf.append(content.substring(index));
return buf.toString();
}
/**
* Search the content for the site containing the specified feature, and answer new
* content with that site removed. If the site cannot be located, then return the
* content unaltered.
*
* @param content the original content of the platform.xml file
* @param extensionFeature the name of the link to be matched. This is the same as the
* name of the link file minus the ".link" file extension.
* @return the content with the site containing that feature removed or the original
* content if it did not contain that feature (not <code>null</code>)
*/
static String removeLinkFileSite(String content, String extensionFeature) {
// Locate the link
int index = content.indexOf("/links/" + extensionFeature + ".link\"");
if (index == -1)
return content;
// Locate the site containing the link attribute
int start = 0;
int end = 0;
while (true) {
end = content.indexOf("<site", start + 5);
if (end == -1) {
end = content.indexOf("</config");
break;
}
if (end > index) {
break;
}
start = end;
}
// Remove the site from the content
return content.substring(0, start) + content.substring(end);
}
/**
* Read the feature information in the specificed extension location and return a data
* structure containing just enough information to append a new link site to the
* platform.xml file.
*
* @param extensionDir The directory containing the Eclipse extension (e.g. C:/Program
* Files/Instantiations/CodeProAnt_v4.5.1/E-3.2)
* @return an array of feature information or <code>null</code> if it could not be
* read
*/
static Feature[] readFeatures(File extensionDir) {
// Obtains a list of feature directories for this extension
if (extensionDir == null || !extensionDir.isDirectory())
return null;
File featuresDir = new File(new File(extensionDir, "eclipse"), "features");
if (!featuresDir.isDirectory())
return null;
File[] allFeatureDirs = featuresDir.listFiles();
// Extract information from each feature
List result = new ArrayList();
for (int i = 0; i < allFeatureDirs.length; i++) {
// Read the first 1000 characters from the feature.xml
File file = new File(allFeatureDirs[i], "feature.xml");
String content = readContent(file);
if (content == null)
continue;
// Extract the required data
Feature data = readOneFeature(content);
// Add the feature if appropriate
if (data != null && data.featureId != null && data.featureVersion != null)
result.add(data);
}
return (Feature[]) result.toArray(new Feature[result.size()]);
}
/**
* Read the feature information in the specificed string and return a data structure
* containing just enough information to append a new link site to the platform.xml
* file.
*
* @param content the content of the feature.xml file
* @return the feature information or <code>null</code> if it could not be
* determined
*/
static Feature readOneFeature(String content) {
// Narrow the content to just the attributes of the feature element
if (content == null)
return null;
int start = content.indexOf("<feature");
if (start == -1)
return null;
int end = content.indexOf('>', start);
if (end == -1)
return null;
String featureElement = content.substring(start + 8, end);
// Extract the attributes
Feature data = new Feature();
start = 0;
while (true) {
int index = featureElement.indexOf('=', start);
if (index == -1)
break;
String key = featureElement.substring(start, index).trim();
start = featureElement.indexOf('"', index) + 1;
if (start == -1)
break;
end = featureElement.indexOf('"', start);
if (end == -1)
break;
String value = featureElement.substring(start, end);
start = end + 1;
if (key.equals("id"))
data.featureId = value;
else if (key.equals("version"))
data.featureVersion = value;
else if (key.equals("plugin"))
data.primaryPluginId = value;
}
return data;
}
/**
* Read from the specified file
*
* @param file the file to be read
* @return the file content or <code>null</code> if the file does not exist or there
* was a problem reading the file
*/
static String readContent(File file) {
FileReader reader;
try {
reader = new FileReader(file);
}
catch (FileNotFoundException e) {
System.out.println("Failed to open " + file.getPath());
return null;
}
StringBuffer result;
try {
result = new StringBuffer(1024);
char[] buf = new char[1024];
int count;
try {
while (true) {
count = reader.read(buf);
if (count == -1)
break;
result.append(buf, 0, count);
}
}
catch (IOException e) {
System.out.println("Failed to read " + file.getPath());
e.printStackTrace();
return null;
}
}
finally {
try {
reader.close();
}
catch (IOException e) {
System.out.println("Failed to close " + file.getPath());
e.printStackTrace();
return null;
}
}
return result.toString();
}
/**
* Write content to the specified file
*
* @param file the file to be written
* @param content the new file content
*/
static void writeContent(File file, String content) {
FileWriter writer;
try {
writer = new FileWriter(file);
}
catch (IOException e) {
System.out.println("Failed to open " + file.getPath());
e.printStackTrace();
return;
}
try {
writer.write(content);
}
catch (IOException e) {
System.out.println("Failed to write " + file.getPath());
e.printStackTrace();
}
finally {
try {
writer.close();
}
catch (IOException e) {
System.out.println("Failed to close " + file.getPath());
e.printStackTrace();
return;
}
}
}
/**
* Zip the specified files. This is intended to backup the platform.xml and config.ini
* files before the platform.xml file is modified.
*
* @param zipFile the zip file to be created
* @param rootDir the root directory used to determine relative paths for the
* specified files when creating the zip file entries
* @param files the files to be zipped
*/
static void zipFiles(File zipFile, File rootDir, File[] files) throws IOException {
final String prefix = rootDir.getAbsolutePath() + File.separator;
ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(zipFile));
try {
final byte[] buf = new byte[2048];
for (int i = 0; i < files.length; i++) {
File eachFile = files[i];
String relPath = eachFile.getAbsolutePath();
if (relPath.startsWith(prefix))
relPath = relPath.substring(prefix.length()).replace(File.separatorChar, '/');
ZipEntry entry = new ZipEntry(relPath);
zipStream.putNextEntry(entry);
FileInputStream in = new FileInputStream(eachFile);
try {
while (true) {
int count = in.read(buf);
if (count == -1)
break;
zipStream.write(buf, 0, count);
}
}
finally {
in.close();
}
zipStream.closeEntry();
}
}
finally {
zipStream.close();
}
}
// //////////////////////////////////////////////////////////////////////////
//
// Feature information data structure
//
// //////////////////////////////////////////////////////////////////////////
/**
* A data structure containing just enough information to append a new link site to
* the platform.xml file.
*/
static class Feature
{
String featureId;
String featureVersion;
String primaryPluginId;
}
}