| /******************************************************************************* |
| * 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 == '_'; |
| } |
| } |