blob: 5fc14f458e52d1f7c1e97f394ffb446d9ad0ef0e [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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import com.ibm.icu.text.Collator;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Region;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.core.formatter.IndentManipulation;
public class NLSUtil {
//no instances
private NLSUtil() {
}
/**
* Reads a stream into a String and closes the stream.
* @param is the input stream
* @param encoding the encoding
* @return the contents, or <code>null</code> if an error occurred
*/
public static String readString(InputStream is, String encoding) {
if (is == null)
return null;
BufferedReader reader= null;
try {
StringBuffer buffer= new StringBuffer();
char[] part= new char[2048];
int read= 0;
reader= new BufferedReader(new InputStreamReader(is, encoding));
while ((read= reader.read(part)) != -1)
buffer.append(part, 0, read);
return buffer.toString();
} catch (IOException ex) {
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
}
}
}
return null;
}
/**
* Creates and returns an NLS tag edit for a string that is at the specified position in a
* compilation unit.
*
* @param cu the compilation unit
* @param position position of the string
* @return the edit, or <code>null</code> if the string is already NLSed or the edit could not
* be created for some other reason.
* @throws CoreException if scanning fails
*/
public static TextEdit createNLSEdit(ICompilationUnit cu, int position) throws CoreException {
NLSLine nlsLine= scanCurrentLine(cu, position);
if (nlsLine == null)
return null;
NLSElement element= findElement(nlsLine, position);
if (element.hasTag())
return null;
NLSElement[] elements= nlsLine.getElements();
int indexInElementList= Arrays.asList(elements).indexOf(element);
int editOffset= computeInsertOffset(elements, indexInElementList, cu);
String editText= ' ' + NLSElement.createTagText(indexInElementList + 1); //tags are 1-based
return new InsertEdit(editOffset, editText);
}
/**
* Creates and returns NLS tag edits for strings that are at the specified positions in a
* compilation unit.
*
* @param cu the compilation unit
* @param positions positions of the strings
* @return the edit, or <code>null</code> if all strings are already NLSed or the edits could
* not be created for some other reason.
* @throws CoreException if scanning fails
*/
public static TextEdit[] createNLSEdits(ICompilationUnit cu, int[] positions) throws CoreException {
List<InsertEdit> result= new ArrayList<InsertEdit>();
try {
NLSLine[] allLines= NLSScanner.scan(cu);
for (int i= 0; i < allLines.length; i++) {
NLSLine line= allLines[i];
NLSElement[] elements= line.getElements();
for (int j= 0; j < elements.length; j++) {
NLSElement element= elements[j];
if (!element.hasTag()) {
for (int k= 0; k < positions.length; k++) {
if (isPositionInElement(element, positions[k])) {
int editOffset;
if (j==0) {
if (elements.length > j+1) {
editOffset= elements[j+1].getTagPosition().getOffset();
} else {
editOffset= findLineEnd(cu, element.getPosition().getOffset());
}
} else {
Region previousPosition= elements[j-1].getTagPosition();
editOffset= previousPosition.getOffset() + previousPosition.getLength();
}
String editText= ' ' + NLSElement.createTagText(j + 1); //tags are 1-based
result.add(new InsertEdit(editOffset, editText));
}
}
}
}
}
} catch (InvalidInputException e) {
return null;
} catch (BadLocationException e) {
return null;
}
if (result.isEmpty())
return null;
return result.toArray(new TextEdit[result.size()]);
}
private static NLSLine scanCurrentLine(ICompilationUnit cu, int position) throws JavaModelException {
try {
Assert.isTrue(position >= 0 && position <= cu.getBuffer().getLength());
NLSLine[] allLines= NLSScanner.scan(cu);
for (int i= 0; i < allLines.length; i++) {
NLSLine line= allLines[i];
if (findElement(line, position) != null)
return line;
}
return null;
} catch (InvalidInputException e) {
return null;
} catch (BadLocationException e) {
return null;
}
}
private static boolean isPositionInElement(NLSElement element, int position) {
Region elementPosition= element.getPosition();
return (elementPosition.getOffset() <= position && position <= elementPosition.getOffset() + elementPosition.getLength());
}
private static NLSElement findElement(NLSLine line, int position) {
NLSElement[] elements= line.getElements();
for (int i= 0; i < elements.length; i++) {
NLSElement element= elements[i];
if (isPositionInElement(element, position))
return element;
}
return null;
}
//we try to find a good place to put the nls tag
//first, try to find the previous nlsed-string and try putting after its tag
//if no such string exists, try finding the next nlsed-string try putting before its tag
//otherwise, find the line end and put the tag there
private static int computeInsertOffset(NLSElement[] elements, int index, ICompilationUnit cu) throws CoreException {
NLSElement previousTagged= findPreviousTagged(index, elements);
if (previousTagged != null)
return previousTagged.getTagPosition().getOffset() + previousTagged.getTagPosition().getLength();
NLSElement nextTagged= findNextTagged(index, elements);
if (nextTagged != null)
return nextTagged.getTagPosition().getOffset();
return findLineEnd(cu, elements[index].getPosition().getOffset());
}
private static NLSElement findPreviousTagged(int startIndex, NLSElement[] elements) {
int i= startIndex - 1;
while (i >= 0) {
if (elements[i].hasTag())
return elements[i];
i--;
}
return null;
}
private static NLSElement findNextTagged(int startIndex, NLSElement[] elements) {
int i= startIndex + 1;
while (i < elements.length) {
if (elements[i].hasTag())
return elements[i];
i++;
}
return null;
}
private static int findLineEnd(ICompilationUnit cu, int position) throws JavaModelException {
IBuffer buffer= cu.getBuffer();
int length= buffer.getLength();
for (int i= position; i < length; i++) {
if (IndentManipulation.isLineDelimiterChar(buffer.getChar(i))) {
return i;
}
}
return length;
}
/**
* Determine a good insertion position for <code>key</code> into the list of given
* <code>keys</code>.
*
* @param key the key to insert
* @param keys a list of {@link String}s
* @return the position in <code>keys</code> after which key must be inserted, returns -1 for before
* @since 3.4
*/
public static int getInsertionPosition(String key, List<String> keys) {
int result= 0;
int invertDistance= Integer.MIN_VALUE;
int i= 0;
for (Iterator<String> iterator= keys.iterator(); iterator.hasNext();) {
String string= iterator.next();
int currentInvertDistance= invertDistance(key, string);
if (currentInvertDistance > invertDistance) {
invertDistance= currentInvertDistance;
if (Collator.getInstance().compare(key, string) >= 0) {
result= i;
} else {
result= i - 1;
}
} else if (currentInvertDistance == invertDistance) {
if (Collator.getInstance().compare(key, string) >= 0) {
result= i;
}
}
i++;
}
return result;
}
/**
* @param insertKey the key to insert
* @param existingKey the existing key
* @return the invert distance between <code>insertkey</code> and <code>existingKey</code>,
* the higher the closer
* @since 3.4
*/
public static int invertDistance(String insertKey, String existingKey) {
int existingKeyLength= existingKey.length();
int insertKeyLength= insertKey.length();
int minLen= Math.min(insertKeyLength, existingKeyLength);
int prefixMatchCount= 0;
for (int i= 0; i < minLen; i++) {
if (insertKey.charAt(i) == existingKey.charAt(i)) {
prefixMatchCount++;
} else {
return prefixMatchCount << 16;
}
}
if (insertKeyLength > existingKeyLength && isSeparator(insertKey.charAt(existingKeyLength))) {
//existing: prefix
//new: prefix_xyz
//insert it after existing key -> prefix match plus one
return (prefixMatchCount + 1) << 16;
}
int existingLonger= existingKeyLength - insertKeyLength;
// Sort by prefix match length first (<< 16). Existing keys that are longer
// than the insertion key are not preferred insertion positions.
return (prefixMatchCount << 16) - Math.max(0, existingLonger);
}
private static boolean isSeparator(char ch) {
return ch == '.' || ch == '-' || ch == '_';
}
}