blob: 041c4be273275d15115699c597f5f4012b55b69b [file] [log] [blame]
/**
* 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;
}
}