/** * Copyright (c) 2009 Borland Software Corp. * * 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: * Alexander Shatalin (Borland) - initial API and implementation */ package org.eclipse.gmf.internal.xpand.migration.ui; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.gmf.internal.xpand.migration.Activator; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextUtilities; import org.eclipse.pde.core.build.IBuildEntry; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MalformedTreeException; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; public class BuildPropertiesManager { private static final String BUILD_PROPERTIES = "build.properties"; private static final String PROPERTY_JAR_ORDER = "jars.compile.order"; private static final String DEFAULT_JAR_NAME = "."; private static final String DEFAULT_LIBRARY_ENTRY = IBuildEntry.JAR_PREFIX + DEFAULT_JAR_NAME; /** Copied from {@link org.eclipse.pde.internal.core.util.PropertiesUtil} */ private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; private IProject project; private List<IFolder> sourceFolders = new ArrayList<IFolder>(); private List<IResource> binInclude = new ArrayList<IResource>(); private IFile buildPropertiesFile; /** Copied from {@link org.eclipse.pde.internal.core.util.PropertiesUtil} */ private static String createWritableName(String source) { if (source.indexOf(' ') >= 0) { // has blanks StringBuffer writableName = new StringBuffer(); for (int i = 0; i < source.length(); i++) { char c = source.charAt(i); if (c == ' ') { writableName.append("\\ "); //$NON-NLS-1$ } else writableName.append(c); } source = writableName.toString(); } return createEscapedValue(source); } /** Copied from {@link org.eclipse.pde.internal.core.util.PropertiesUtil} */ public static String createEscapedValue(String value) { // if required, escape property values as \\uXXXX StringBuffer buf = new StringBuffer(value.length() * 2); // assume expansion by less than factor of 2 for (int i = 0; i < value.length(); i++) { char character = value.charAt(i); if (character == '\\' || character == '\t' || character == '\r' || character == '\n' || character == '\f') { // handle characters requiring leading \ buf.append('\\'); buf.append(character); } else if ((character < 0x0020) || (character > 0x007e)) { // handle characters outside base range (encoded) buf.append('\\'); buf.append('u'); buf.append(HEX[(character >> 12) & 0xF]); // first nibble buf.append(HEX[(character >> 8) & 0xF]); // second nibble buf.append(HEX[(character >> 4) & 0xF]); // third nibble buf.append(HEX[character & 0xF]); // fourth nibble } else { // handle base characters buf.append(character); } } return buf.toString(); } /** Copied from {@link org.eclipse.pde.internal.core.util.PropertiesUtil} */ public static int getInsertOffset(IDocument doc) { int offset = doc.getLength(); for (int i = doc.getNumberOfLines() - 1; i >= 0; i--) { try { if (doc.get(doc.getLineOffset(i), doc.getLineLength(i)).trim().length() > 0) { break; } offset = doc.getLineOffset(i); } catch (BadLocationException e) { } } return offset; } // TODO: move to the shared static MigrationUtils class private static CoreException createCoreException(Throwable th) { return new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Exception during migration", th)); } private static CoreException createCoreException(String message) { return new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, message)); } // TODO: move to the shared static MigrationUtils class private static IProgressMonitor createSubProgressMonitor(IProgressMonitor monitor, String taskName, int numberOfTicks) throws InterruptedException { if (monitor.isCanceled()) { throw new InterruptedException("Process was canceled"); } SubProgressMonitor spm = new SubProgressMonitor(monitor, numberOfTicks); if (taskName != null) { spm.setTaskName(taskName); } return spm; } public BuildPropertiesManager(IProject project) { this.project = project; } public void addSourceFolder(IFolder sourceFolder) { sourceFolders.add(sourceFolder); } public void addBinInclude(IResource resource) { binInclude.add(resource); } public void save(IProgressMonitor monitor) throws CoreException, InterruptedException { if (sourceFolders.isEmpty() && binInclude.isEmpty()) { monitor.done(); return; } monitor.beginTask("Saving modified " + BUILD_PROPERTIES + " file", 6); Properties buildProperties = loadBuildProperties(); monitor.worked(1); IDocument document = loadDocument(getBuildPropertiesFile()); String lf = TextUtilities.getDefaultLineDelimiter(document); monitor.worked(1); MultiTextEdit edit = new MultiTextEdit(); addSourceFolders(edit, document, buildProperties, lf); monitor.worked(1); addBinIncludes(edit, document, buildProperties, lf); monitor.worked(1); try { edit.apply(document); } catch (MalformedTreeException e) { createCoreException(e); } catch (BadLocationException e) { createCoreException(e); } monitor.worked(1); saveDocument(document, getBuildPropertiesFile(), createSubProgressMonitor(monitor, "Saving modified " + BUILD_PROPERTIES + " file", 1)); } private void addBinIncludes(MultiTextEdit edit, IDocument document, Properties buildProperties, String lf) throws CoreException { List<String> binIncludes = getValues(buildProperties, IBuildEntry.BIN_INCLUDES); for (IResource resource : binInclude) { IPath projectRelativePath = resource.getProjectRelativePath(); if (resource instanceof IFolder) { projectRelativePath = projectRelativePath.addTrailingSeparator(); } binIncludes.add(projectRelativePath.toString()); } modifyKey(IBuildEntry.BIN_INCLUDES, binIncludes, edit, document, buildProperties, lf); } private void addSourceFolders(MultiTextEdit edit, IDocument document, Properties buildProperties, String lf) throws CoreException { List<String> jarOrderValues = getValues(buildProperties, PROPERTY_JAR_ORDER); if (!jarOrderValues.contains(DEFAULT_JAR_NAME)) { jarOrderValues.add(DEFAULT_JAR_NAME); modifyKey(PROPERTY_JAR_ORDER, jarOrderValues, edit, document, buildProperties, lf); } List<String> defaultLibraryFolders = getValues(buildProperties, DEFAULT_LIBRARY_ENTRY); for (IFolder nextFolder : sourceFolders) { defaultLibraryFolders.add(nextFolder.getProjectRelativePath().addTrailingSeparator().toString()); } modifyKey(DEFAULT_LIBRARY_ENTRY, defaultLibraryFolders, edit, document, buildProperties, lf); } private void modifyKey(String key, List<String> values, MultiTextEdit edit, IDocument document, Properties buildProperties, String lf) throws CoreException { String text = serialize(values, key, lf); if (buildProperties.containsKey(key)) { int offset = getKeyOffset(document, key); int length = getKeyLength(document, key, offset); edit.addChild(new ReplaceEdit(offset, length, text)); } else { edit.addChild(new InsertEdit(getInsertOffset(document), text)); } } /** * Similar to * {@link org.eclipse.pde.internal.core.text.build.Build#adjustOffsets()} */ private int getKeyLength(IDocument document, String key, int keyOffset) { int lines = document.getNumberOfLines(); try { for (int i = 0; i < lines; i++) { int offset = document.getLineOffset(i); if (offset < keyOffset) { continue; } int length = document.getLineLength(i); String line = document.get(offset, length); if (line.startsWith("#") | line.startsWith("!")) { return offset - 1 - keyOffset; } line = line.trim(); if (line.length() == 0) { return offset - 1 - keyOffset; } if (!line.endsWith("\\")) { return offset + document.getLineLength(i) - keyOffset; } } } catch (BadLocationException e) { // should never be here } return document.getLength() - keyOffset; } /** * Similar to * {@link org.eclipse.pde.internal.core.text.build.Build#adjustOffsets()} */ private int getKeyOffset(IDocument document, String key) throws CoreException { try { int lines = document.getNumberOfLines(); for (int i = 0; i < lines; i++) { int offset = document.getLineOffset(i); int length = document.getLineLength(i); String line = document.get(offset, length); if (line.startsWith("#") | line.startsWith("!")) { continue; } line = line.trim(); int index = line.indexOf('='); if (index == -1) { continue; } String name = line.substring(0, index).trim(); if (createEscapedValue(key).equals(name)) { while (Character.isSpaceChar(document.getChar(offset))) { offset += 1; } return offset; } } } catch (BadLocationException e) { // should never be here } assert false; throw createCoreException("Property: " + key + " was not found in " + BUILD_PROPERTIES + " file"); } /** * Copied from * {@link org.eclipse.pde.internal.core.text.build.BuildEntry#write()} */ private String serialize(List<String> values, String propertyName, String lf) { StringBuffer buffer = new StringBuffer(); buffer.append(createWritableName(propertyName)); buffer.append(" = "); //$NON-NLS-1$ int indentLength = propertyName.length() + 3; for (int i = 0; i < values.size(); i++) { buffer.append(createEscapedValue(values.get(i))); if (i < values.size() - 1) { buffer.append(",\\"); //$NON-NLS-1$ buffer.append(lf); for (int j = 0; j < indentLength; j++) { buffer.append(" "); //$NON-NLS-1$ } } } buffer.append(lf); return buffer.toString(); } private List<String> getValues(Properties buildProperties, String key) { List<String> values = new ArrayList<String>(); if (!buildProperties.containsKey(key)) { return values; } StringTokenizer stok = new StringTokenizer(buildProperties.getProperty(key).toString(), ","); //$NON-NLS-1$ while (stok.hasMoreTokens()) { values.add(stok.nextToken().trim()); } return values; } private IDocument loadDocument(IFile file) throws CoreException { if (!file.exists()) { return new Document(); } try { Reader reader = new InputStreamReader(buildPropertiesFile.getContents(), file.getCharset()); StringBuilder currentLine = new StringBuilder(); for (char nextChar = (char) reader.read(); nextChar != (char) -1; nextChar = (char) reader.read()) { currentLine.append(nextChar); } return new Document(currentLine.toString()); } catch (IOException e) { throw createCoreException(e); } } private void saveDocument(IDocument document, IFile file, IProgressMonitor monitor) throws CoreException { // TODO: check if it works correctly String charset = file.exists() ? file.getCharset() : "ISO-8859-1"; try { InputStream inputStream = new ByteArrayInputStream(document.get().getBytes(charset)); if (buildPropertiesFile.exists()) { buildPropertiesFile.setContents(inputStream, IFile.FORCE | IFile.KEEP_HISTORY, monitor); } else { buildPropertiesFile.create(inputStream, true, monitor); } } catch (UnsupportedEncodingException e) { throw createCoreException(e); } } private Properties loadBuildProperties() throws CoreException { Properties result = new Properties(); if (getBuildPropertiesFile().exists()) { try { result.load(getBuildPropertiesFile().getContents()); } catch (IOException e) { throw createCoreException(e); } } return result; } private IFile getBuildPropertiesFile() { if (buildPropertiesFile == null) { buildPropertiesFile = project.getFile(BUILD_PROPERTIES); } return buildPropertiesFile; } }