| /******************************************************************************* |
| * Copyright (c) 2000, 2005 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.Iterator; |
| import java.util.List; |
| |
| 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; |
| |
| public class PropertyFileDocumentModel { |
| |
| private static final char[] HEX_DIGITS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; |
| private List fKeyValuePairs; |
| private String fLineDelimiter; |
| |
| public PropertyFileDocumentModel(IDocument document) { |
| parsePropertyDocument(document); |
| fLineDelimiter= TextUtilities.getDefaultLineDelimiter(document); |
| } |
| |
| public int getIndex(String key) { |
| for (int i= 0; i < fKeyValuePairs.size(); i++) { |
| KeyValuePairModell keyValuePair = (KeyValuePairModell) fKeyValuePairs.get(i); |
| if (keyValuePair.getKey().equals(key)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| |
| public InsertEdit insert(String key, String value) { |
| return insert(new KeyValuePair(key, value)); |
| } |
| |
| public InsertEdit insert(KeyValuePair keyValuePair) { |
| KeyValuePairModell keyValuePairModell = new KeyValuePairModell(keyValuePair); |
| int index = findInsertPosition(keyValuePairModell); |
| KeyValuePairModell insertHere = (KeyValuePairModell) fKeyValuePairs.get(index); |
| int offset = insertHere.fOffset - insertHere.fLeadingWhiteSpaces; |
| |
| String extra= ""; //$NON-NLS-1$ |
| if (insertHere instanceof LastKeyValuePair && ((LastKeyValuePair)insertHere).needsNewLine()) { |
| extra= fLineDelimiter; |
| ((LastKeyValuePair)insertHere).resetNeedsNewLine(); |
| } |
| return new InsertEdit(offset, extra + keyValuePairModell.getEncodedText(fLineDelimiter)); |
| } |
| |
| public InsertEdit[] insert(KeyValuePair[] keyValuePairs) { |
| InsertEdit[] inserts = new InsertEdit[keyValuePairs.length]; |
| for (int i = 0; i < keyValuePairs.length; i++) { |
| inserts[i] = insert(keyValuePairs[i]); |
| } |
| return inserts; |
| } |
| |
| public DeleteEdit remove(String key) { |
| for (Iterator iter = fKeyValuePairs.iterator(); iter.hasNext();) { |
| KeyValuePairModell keyValuePair = (KeyValuePairModell) iter.next(); |
| if (keyValuePair.fKey.equals(key)) { |
| KeyValuePairModell next = (KeyValuePairModell) iter.next(); |
| return new DeleteEdit(keyValuePair.fOffset, next.fOffset - keyValuePair.fOffset); |
| } |
| } |
| return null; |
| } |
| |
| public ReplaceEdit replace(KeyValuePair toReplace, KeyValuePair replaceWith) { |
| for (Iterator iter = fKeyValuePairs.iterator(); iter.hasNext();) { |
| KeyValuePairModell keyValuePair = (KeyValuePairModell) iter.next(); |
| if (keyValuePair.fKey.equals(toReplace.getKey())) { |
| String newText = new KeyValuePairModell(replaceWith).getEncodedText(fLineDelimiter); |
| KeyValuePairModell next = (KeyValuePairModell) iter.next(); |
| int range = next.fOffset - keyValuePair.fOffset; |
| return new ReplaceEdit(keyValuePair.fOffset, range, newText); |
| } |
| } |
| return null; |
| } |
| |
| private int findInsertPosition(KeyValuePairModell keyValuePair) { |
| int insertIndex = 0; |
| int maxMatch = Integer.MIN_VALUE; |
| for (int i=0; i<fKeyValuePairs.size(); i++) { |
| KeyValuePairModell element = (KeyValuePairModell) fKeyValuePairs.get(i); |
| int match = element.compareTo(keyValuePair); |
| if (match >= maxMatch) { |
| insertIndex = i; |
| maxMatch = match; |
| } |
| } |
| |
| if (insertIndex < fKeyValuePairs.size() - 1) { |
| insertIndex++; |
| } |
| |
| return insertIndex; |
| } |
| |
| private void parsePropertyDocument(IDocument document) { |
| fKeyValuePairs = new ArrayList(); |
| |
| 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); |
| String value= 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 = Math.min(minIndex, indexOfBlank); |
| } else { |
| minIndex = Math.max(minIndex, indexOfBlank); |
| } |
| |
| return minIndex; |
| } |
| |
| public static String unwindEscapeChars(String s){ |
| StringBuffer sb= new StringBuffer(s.length()); |
| int length= s.length(); |
| for (int i= 0; i < length; i++){ |
| char c= s.charAt(i); |
| sb.append(getUnwoundString(c)); |
| } |
| return sb.toString(); |
| } |
| |
| public static String unwindValue(String value) { |
| return escapeLeadingWhiteSpaces(escapeCommentChars(unwindEscapeChars(value))); |
| } |
| |
| private static String getUnwoundString(char c){ |
| switch(c){ |
| case '\b' : |
| return "\\b";//$NON-NLS-1$ |
| case '\t' : |
| return "\\t";//$NON-NLS-1$ |
| case '\n' : |
| return "\\n";//$NON-NLS-1$ |
| case '\f' : |
| return "\\f";//$NON-NLS-1$ |
| case '\r' : |
| return "\\r";//$NON-NLS-1$ |
| |
| // These can be used unescaped in properties file: |
| // case '\"' : |
| // return "\\\"";//$NON-NLS-1$ |
| // case '\'' : |
| // return "\\\'";//$NON-NLS-1$ |
| |
| case '\\' : |
| return "\\\\";//$NON-NLS-1$ |
| |
| // This is only done when writing to the .properties file in #unwindValue(String) |
| // case '!': |
| // return "\\!";//$NON-NLS-1$ |
| // case '#': |
| // return "\\#";//$NON-NLS-1$ |
| |
| default: |
| if (((c < 0x0020) || (c > 0x007e))){ |
| return new StringBuffer() |
| .append('\\') |
| .append('u') |
| .append(toHex((c >> 12) & 0xF)) |
| .append(toHex((c >> 8) & 0xF)) |
| .append(toHex((c >> 4) & 0xF)) |
| .append(toHex( c & 0xF)).toString(); |
| |
| } else |
| return String.valueOf(c); |
| } |
| } |
| |
| private static char toHex(int halfByte) { |
| return HEX_DIGITS[(halfByte & 0xF)]; |
| } |
| |
| 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(); |
| } |
| |
| /** |
| * returns the length if only whitespaces |
| */ |
| 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 implements Comparable { |
| |
| 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 String getEncodedText(String lineDelimiter) { |
| return PropertyFileDocumentModel.unwindEscapeChars(fKey) + '=' + PropertyFileDocumentModel.unwindValue(fValue) + lineDelimiter; |
| } |
| |
| public int compareTo(Object o) { |
| int counter = 0; |
| String key = ((KeyValuePair) o).fKey; |
| int minLen = Math.min(key.length(), fKey.length()); |
| int diffLen = Math.abs(key.length() - fKey.length()); |
| for (int i=0; i<minLen; i++) { |
| if (key.charAt(i) == fKey.charAt(i)) { |
| counter++; |
| } else { |
| break; |
| } |
| } |
| return counter - diffLen; |
| } |
| } |
| |
| /** |
| * 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("last", "key", offset, 0); //$NON-NLS-1$ //$NON-NLS-2$ |
| fNeedsNewLine= needsNewLine; |
| } |
| public int compareTo(Object o) { |
| return 1; |
| } |
| public boolean needsNewLine() { |
| return fNeedsNewLine; |
| } |
| public void resetNeedsNewLine() { |
| fNeedsNewLine= false; |
| } |
| } |
| } |