| 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; |
| } |
| } |