blob: 500fe23e96dad0b3d623aa3c0833731ad7e01c9c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.nls;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.ibm.icu.text.Collator;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.Strings;
import org.eclipse.jdt.internal.ui.propertiesfileeditor.PropertiesFileEscapes;
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
public class PropertyFileDocumentModel {
private List<KeyValuePairModell> fKeyValuePairs;
private String fLineDelimiter;
public PropertyFileDocumentModel(IDocument document) {
parsePropertyDocument(document);
fLineDelimiter= TextUtilities.getDefaultLineDelimiter(document);
}
/**
* @return the line delimiter used by the document described by this model
*/
public String getLineDelimiter() {
return fLineDelimiter;
}
/**
* Return the key value pair in this model with the key <code>key</code>
*
* @param key the key of the pair
* @return the pair with the key or <b>null</b> if no such pair.
*/
public KeyValuePair getKeyValuePair(String key) {
for (int i= 0; i < fKeyValuePairs.size(); i++) {
KeyValuePairModell keyValuePair = fKeyValuePairs.get(i);
if (keyValuePair.getKey().equals(key)) {
return keyValuePair;
}
}
return null;
}
private InsertEdit insert(KeyValuePair keyValuePair) {
KeyValuePairModell keyValuePairModell = new KeyValuePairModell(keyValuePair);
int index = findInsertPosition(keyValuePairModell);
KeyValuePairModell insertHere = fKeyValuePairs.get(index);
int offset = insertHere.fOffset;
String extra= ""; //$NON-NLS-1$
if (insertHere instanceof LastKeyValuePair && ((LastKeyValuePair)insertHere).needsNewLine()) {
extra= fLineDelimiter;
((LastKeyValuePair)insertHere).resetNeedsNewLine();
offset-= insertHere.fLeadingWhiteSpaces;
} else if (index > 0) {
String beforeKey= fKeyValuePairs.get(index - 1).fKey;
String afterKey= insertHere.fKey;
String key= keyValuePair.fKey;
int distBefore= NLSUtil.invertDistance(key, beforeKey);
int distAfter= NLSUtil.invertDistance(key, afterKey);
if (distBefore > distAfter) {
offset-= insertHere.fLeadingWhiteSpaces;
} else if (distBefore == distAfter && Collator.getInstance().compare(beforeKey, afterKey) < 0) {
offset-= insertHere.fLeadingWhiteSpaces;
} else {
//insert it before afterKey -> move the leading white spaces to the inserted pair
keyValuePairModell.fLeadingWhiteSpaces= insertHere.fLeadingWhiteSpaces;
insertHere.fLeadingWhiteSpaces= 0;
}
}
keyValuePairModell.fOffset= offset;
fKeyValuePairs.add(index, keyValuePairModell);
return new InsertEdit(offset, extra + keyValuePairModell.getKeyValueText());
}
/**
* Inserts the given key value pairs into this model at appropriate
* positions. Records all required text changes in the given change
*
* @param keyValuePairs the key value pairs to insert
* @param change the change to use to record text changes
*/
public void insert(KeyValuePair[] keyValuePairs, TextChange change) {
ArrayList<KeyValuePair> sorted= new ArrayList<KeyValuePair>(Arrays.asList(keyValuePairs));
Collections.sort(sorted, new Comparator<KeyValuePair>() {
public int compare(KeyValuePair p1, KeyValuePair p2) {
return Collator.getInstance().compare(p1.fKey, p2.fKey);
}
});
for (int i = 0; i < sorted.size(); i++) {
KeyValuePair curr= sorted.get(i);
InsertEdit insertEdit= insert(curr);
String message= Messages.format(NLSMessages.NLSPropertyFileModifier_add_entry, BasicElementLabels.getJavaElementName(curr.getKey()));
TextChangeCompatibility.addTextEdit(change, message, insertEdit);
}
}
public DeleteEdit remove(String key) {
for (Iterator<KeyValuePairModell> iter = fKeyValuePairs.iterator(); iter.hasNext();) {
KeyValuePairModell keyValuePair = iter.next();
if (keyValuePair.fKey.equals(key)) {
return new DeleteEdit(keyValuePair.fOffset, keyValuePair.getLength());
}
}
return null;
}
public ReplaceEdit replace(KeyValuePair toReplace, KeyValuePair replaceWith) {
for (Iterator<KeyValuePairModell> iter = fKeyValuePairs.iterator(); iter.hasNext();) {
KeyValuePairModell keyValuePair = iter.next();
if (keyValuePair.fKey.equals(toReplace.getKey())) {
String newText= new KeyValuePairModell(replaceWith).getKeyValueText();
return new ReplaceEdit(keyValuePair.fOffset, keyValuePair.getLength(), newText);
}
}
return null;
}
private int findInsertPosition(KeyValuePairModell keyValuePair) {
ArrayList<String> keys= new ArrayList<String>();
for (int i= 0; i < fKeyValuePairs.size(); i++) {
KeyValuePairModell element = fKeyValuePairs.get(i);
if (! (element instanceof LastKeyValuePair))
keys.add(element.getKey());
}
int insertIndex= NLSUtil.getInsertionPosition(keyValuePair.getKey(), keys);
if (insertIndex < fKeyValuePairs.size() - 1) {
insertIndex++;
}
return insertIndex;
}
private void parsePropertyDocument(IDocument document) {
fKeyValuePairs = new ArrayList<KeyValuePairModell>();
SimpleLineReader reader = new SimpleLineReader(document);
int offset = 0;
String line = reader.readLine();
int leadingWhiteSpaces = 0;
while (line != null) {
if (!SimpleLineReader.isCommentOrWhiteSpace(line)) {
int idx = getIndexOfSeparationCharacter(line);
if (idx != -1) {
String key= line.substring(0, idx).trim();
String value= Strings.trimLeadingTabsAndSpaces(line.substring(idx + 1));
fKeyValuePairs.add(new KeyValuePairModell(key, value, offset, leadingWhiteSpaces));
leadingWhiteSpaces = 0;
}
} else {
leadingWhiteSpaces += line.length();
}
offset += line.length();
line = reader.readLine();
}
int lastLine= document.getNumberOfLines() - 1;
boolean needsNewLine= false;
try {
needsNewLine= !(document.getLineLength(lastLine) == 0);
} catch (BadLocationException ignore) {
// treat last line having no new line
}
LastKeyValuePair lastKeyValuePair = new LastKeyValuePair(offset, needsNewLine);
fKeyValuePairs.add(lastKeyValuePair);
}
private int getIndexOfSeparationCharacter(String line) {
int minIndex = -1;
int indexOfEven = line.indexOf('=');
int indexOfColumn = line.indexOf(':');
int indexOfBlank = line.indexOf(' ');
if ((indexOfEven != -1) && (indexOfColumn != -1)) {
minIndex = Math.min(indexOfEven, indexOfColumn);
} else {
minIndex = Math.max(indexOfEven, indexOfColumn);
}
if ((minIndex == -1) && (indexOfBlank != -1)) {
minIndex= indexOfBlank;
}
return minIndex;
}
public static String escape(String s, boolean escapeCommentCharsAndLeadingWhitespaces) {
StringBuffer sb= new StringBuffer(s.length());
int length= s.length();
for (int i= 0; i < length; i++){
char c= s.charAt(i);
sb.append(PropertiesFileEscapes.escape(c));
}
if(!escapeCommentCharsAndLeadingWhitespaces)
return sb.toString();
return escapeLeadingWhiteSpaces(escapeCommentChars(sb.toString()));
}
private static String escapeCommentChars(String string) {
StringBuffer sb = new StringBuffer(string.length() + 5);
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
switch (c) {
case '!':
sb.append("\\!"); //$NON-NLS-1$
break;
case '#':
sb.append("\\#"); //$NON-NLS-1$
break;
default:
sb.append(c);
}
}
return sb.toString();
}
private static String escapeLeadingWhiteSpaces(String str) {
int firstNonWhiteSpace= findFirstNonWhiteSpace(str);
StringBuffer buf= new StringBuffer(firstNonWhiteSpace);
for (int i = 0; i < firstNonWhiteSpace; i++) {
buf.append('\\');
buf.append(str.charAt(i));
}
buf.append(str.substring(firstNonWhiteSpace));
return buf.toString();
}
/**
* @param s the string to inspect
* @return the first non whitespace character, the length if only whitespace characters
*/
private static int findFirstNonWhiteSpace(String s) {
for (int i = 0; i < s.length(); i++) {
if (!Character.isWhitespace(s.charAt(i)))
return i;
}
return s.length();
}
private static class KeyValuePairModell extends KeyValuePair {
int fOffset;
int fLeadingWhiteSpaces;
public KeyValuePairModell(String key, String value, int offset, int leadingWhiteSpaces) {
super(key, value);
fOffset = offset;
fLeadingWhiteSpaces = leadingWhiteSpaces;
}
public KeyValuePairModell(KeyValuePair keyValuePair) {
super(keyValuePair.fKey, keyValuePair.fValue);
}
public int getLength() {
return fKey.length() + 1 + fValue.length();
}
private String getKeyValueText() {
return fKey + '=' + fValue;
}
}
/**
* anchor element for a list of KeyValuePairs. (it is greater than every
* other KeyValuePair)
*/
private static class LastKeyValuePair extends KeyValuePairModell {
private boolean fNeedsNewLine;
public LastKeyValuePair(int offset, boolean needsNewLine) {
super("zzzzzzz", "key", offset, 0); //$NON-NLS-1$ //$NON-NLS-2$
fNeedsNewLine= needsNewLine;
}
public boolean needsNewLine() {
return fNeedsNewLine;
}
public void resetNeedsNewLine() {
fNeedsNewLine= false;
}
}
}