/******************************************************************************* * Copyright (c) 2015 QNX Software Systems 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: * QNX Software Systems - Initial API and implementation *******************************************************************************/ package org.eclipse.cdt.internal.qt.ui.pro.parser; import org.eclipse.cdt.internal.qt.core.Activator; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; /** * Allows for the manipulation of information stored in a Qt Project File. At the moment the only modifiable information is that * which is contained within variables such as the following: * * <pre> * <code>SOURCES += file.cpp \ # This is the first line with value "file.cpp" * file2.cpp # This is the second line with value "file2.cpp"</code> * </pre> * * This class supports the following modifications to variables: * <ul> * <li><b>Add Value</b>: If the specified String does not exist in the given variable then it is added as a new line at the end of * the variable declaration. A line escape (\) is also inserted into the preceding line.</li> * <li><b>Remove Value</b>: If the specified String exists in the given variable then it is removed. The line escape character (\) * is also removed from the preceding line if necessary.</li> * <li><b>Replace Value</b>: If the specified String exists as a line in the given variable, then it is replaced with another * String. All spacing is preserved as only the value itself is modified.</li> * </ul> * <p> * Comments may appear after the line escape character (\) in a variable Declaration. For this case, replace and addition operations * will preserve these comments. However, a comment will not be preserved if its line is deleted during a remove operation. * </p> */ public class QtProjectFileModifier { private QtProjectFileParser parser; private IDocument document; public QtProjectFileModifier(IDocument doc) { if (doc == null) { throw new IllegalArgumentException("document cannot be null"); //$NON-NLS-1$ } this.document = doc; this.parser = new QtProjectFileParser(doc); } public QtProjectFileModifier(QtProjectFileParser parser) { if (parser == null) { throw new IllegalArgumentException("parser cannot be null"); //$NON-NLS-1$ } this.document = parser.getDocument(); this.parser = parser; } /** * Attempts to replace the given value with a new value if it is found within the given variable name. This is a convenience * method equivalent to <code>replaceVariableValue(variable,oldValue,newValue,true)</code> and will only match values that * occupy an entire line within the variable declaration. * <p> * This method does <b>not</b> create a new value if the specified <code>oldValue</code> was not found. If this behavior is * desired, then check for a return of <code>false</code> from this method and then call the <code>addVariableValue</code> * method. * </p> * <p> * <b>Note:</b> The "entire line" refers to only the value as it appears in the variable declaration. That is, any whitespace * before or after will not be included when matching a value to the "entire line". * </p> * * @param variable * the name of the variable * @param oldValue * the value that will be replaced * @param newValue * the value to replace with * @return whether or not the value was able to be replaced */ public boolean replaceVariableValue(String variable, String oldValue, String newValue) { return replaceVariableValue(variable, oldValue, newValue, true); } /** * Attempts to replace the first instance of <code>oldValue</code> with <code>newValue</code> if it is found within the given * variable name. If <code>matchWholeLine</code> is false, this method will try to match sections of each line with the value of * <code>oldValue</code>. If a match is found, only that portion of the line will be replaced. If <code>matchWholeLine</code> is * true, this method will try to match the entire line with the value of <code>oldValue</code> and will replace that. All other * line spacing and comments are preserved as only the value itself is replaced. * <p> * This method does <b>not</b> create a new value if <code>oldValue</code> was not found. If this behavior is desired, then * check for a return of <code>false</code> from this method and then call the <code>addVariableValue</code> method. * </p> * <p> * <b>Note:</b> The "entire line" refers to only the value as it appears in the variable declaration. That is, any whitespace * before or after will not be included when matching a value to the "entire line". * </p> * * @param variable * the name of the variable * @param oldValue * the value that will be replaced * @param newValue * the value to replace with * @param matchWholeLine * whether or not the value should match the entire line * @return whether or not the value was able to be replaced */ public boolean replaceVariableValue(String variable, String oldValue, String newValue, boolean matchWholeLine) { QtProjectVariable var = parser.getVariable(variable); if (var != null) { if (matchWholeLine) { int line = var.getValueIndex(oldValue); if (line >= 0) { return replaceVariableValue(var, line, newValue); } } else { int line = 0; for (String value : var.getValues()) { int offset = value.indexOf(oldValue); if (offset >= 0) { return replaceVariableValue(var, line, var.getValueOffsetForLine(line) + offset, oldValue.length(), newValue); } line++; } } } return false; } private boolean replaceVariableValue(QtProjectVariable var, int lineNo, String newValue) { int offset = var.getValueOffsetForLine(lineNo); String value = var.getValueForLine(lineNo); int length = value.length(); return replaceVariableValue(var, lineNo, offset, length, newValue); } private boolean replaceVariableValue(QtProjectVariable var, int lineNo, int offset, int length, String newValue) { try { document.replace(offset, length, newValue); return true; } catch (BadLocationException e) { Activator.log(e); } return false; } /** * Adds <code>value</code> to the specified variable as a new line and escapes the previous line with a backslash. The escaping * is done in such a way that comments and spacing are preserved on the previous line. If this variable does not exist, a new * one is created at the bottom-most position of the document with the initial value specified by <code>value</code>. * * @param variable * the name of the variable to add to * @param value * the value to add to the variable */ public void addVariableValue(String variable, String value) { QtProjectVariable var = parser.getVariable(variable); if (var != null) { if (var.getValueIndex(value) < 0) { int line = var.getNumberOfLines() - 1; String indent = var.getIndentString(line); int offset = var.getEndOffset(); if (var.getLine(line).endsWith("\n")) { //$NON-NLS-1$ offset--; } try { document.replace(offset, 0, "\n" + indent + value); //$NON-NLS-1$ } catch (BadLocationException e) { Activator.log(e); } try { offset = var.getLineEscapeReplacementOffset(line); String lineEscape = var.getLineEscapeReplacementString(line); document.replace(offset, 0, lineEscape); } catch (BadLocationException e) { Activator.log(e); } } } else { // Variable does not exist, create it String baseVariable = variable + " += " + value + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ // Check the contents of the document and re-format accordingly if (document.get().trim().isEmpty()) { try { document.replace(0, document.getLength(), baseVariable); } catch (BadLocationException e) { Activator.log(e); } } else if (document.get().endsWith("\n")) { //$NON-NLS-1$ try { document.replace(document.getLength(), 0, "\n" + baseVariable); //$NON-NLS-1$ } catch (BadLocationException e) { Activator.log(e); } } else { try { document.replace(document.getLength(), 0, "\n\n" + baseVariable); //$NON-NLS-1$ } catch (BadLocationException e) { Activator.log(e); } } } } /** * Removes <code>value</code> from the specified variable and removes the previous line escape if necessary. The entire line is * removed including any comments. If the value is not found, nothing happens. * * @param variable * the name of the variable to remove from * @param value * the value to remove from the variable */ public void removeVariableValue(String variable, String value) { QtProjectVariable var = parser.getVariable(variable); if (var != null) { int line = var.getValueIndex(value); if (line == 0 && var.getNumberOfLines() > 1) { // Entering this block means we're removing the first line where more lines exist. int offset = var.getValueOffsetForLine(line); int end = var.getValueOffsetForLine(line + 1); try { document.replace(offset, end - offset, ""); //$NON-NLS-1$ } catch (BadLocationException e) { Activator.log(e); } } else if (line >= 0) { int offset = var.getLineOffset(line); int length = var.getLine(line).length(); if (line > 0) { // Remove the previous line feed character offset--; length++; } try { document.replace(offset, length, ""); //$NON-NLS-1$ } catch (BadLocationException e) { Activator.log(e); } // Remove the previous line's line escape character if necessary if (line > 0 && line == var.getNumberOfLines() - 1) { try { offset = var.getLineEscapeOffset(line - 1); length = var.getLineEscapeEnd(line - 1) - offset; document.replace(offset, length, ""); //$NON-NLS-1$ } catch (BadLocationException e) { Activator.log(e); } } } } } /** * Get the <code>IDocument</code> currently being modified by this class. * * @return the document being modified */ public IDocument getDocument() { return document; } }