/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.core.internal.util;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.model.DartModelException;
import com.google.dart.tools.core.model.DartModelStatusConstants;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Comparator;
/**
* Instances of the class <code>Util</code>.
*
* @coverage dart.tools.core
*/
public class Util {
public static final String EMPTY_STRING = "";
public static final char[] NO_CHAR = new char[0];
/**
* A cached copy of the Dart-like file extensions computed by DartCore.
*/
private static char[][] DART_LIKE_EXTENSIONS;
private static final int DEFAULT_READING_SIZE = 0x2000;
private static final String UTF_8 = "UTF-8";
/**
* Combine two hash codes to make a new one.
*
* @param hashCode1 the first hash code to be combined
* @param hashCode2 the second hash code to be combined
* @return the result of combining the hash codes
*/
public static int combineHashCodes(int hashCode1, int hashCode2) {
return hashCode1 * 17 + hashCode2;
}
/**
* Find and return the first line separator used by the given text.
*
* @return </code>"\n"</code> or </code>"\r"</code> or </code>"\r\n"</code>, or <code>null</code>
* if the text does not contain a line separator
*/
public static String findLineSeparator(char[] text) {
// find the first line separator
int length = text.length;
if (length > 0) {
char nextChar = text[0];
for (int i = 0; i < length; i++) {
char currentChar = nextChar;
nextChar = i < length - 1 ? text[i + 1] : ' ';
switch (currentChar) {
case '\n':
return "\n"; //$NON-NLS-1$
case '\r':
return nextChar == '\n' ? "\r\n" : "\r"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
// not found
return null;
}
/**
* Return the registered Dart-like extensions.
*
* @return the registered Dart-like extensions
* @see DartCore#getDartLikeExtensions()
*/
public static char[][] getDartLikeExtensions() {
if (DART_LIKE_EXTENSIONS == null) {
String[] coreExtensions = DartCore.getDartLikeExtensions();
int length = coreExtensions.length;
char[][] extensions = new char[length][];
for (int i = 0; i < length; i++) {
extensions[i] = coreExtensions[i].toCharArray();
}
DART_LIKE_EXTENSIONS = extensions;
}
return DART_LIKE_EXTENSIONS;
}
public static char[] getInputStreamAsCharArray(InputStream stream, int length, String encoding)
throws IOException {
BufferedReader reader = null;
try {
reader = encoding == null ? new BufferedReader(new InputStreamReader(stream))
: new BufferedReader(new InputStreamReader(stream, encoding));
} catch (UnsupportedEncodingException e) {
// encoding is not supported
reader = new BufferedReader(new InputStreamReader(stream));
}
char[] contents;
int totalRead = 0;
if (length == -1) {
contents = NO_CHAR;
} else {
// length is a good guess when the encoding produces less or the same
// amount of characters than the file length
contents = new char[length]; // best guess
}
while (true) {
int amountRequested;
if (totalRead < length) {
// until known length is met, reuse same array sized eagerly
amountRequested = length - totalRead;
} else {
// reading beyond known length
int current = reader.read();
if (current < 0) {
break;
}
// read at least 8K
amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE);
// resize contents if needed
if (totalRead + 1 + amountRequested > contents.length) {
System.arraycopy(
contents,
0,
contents = new char[totalRead + 1 + amountRequested],
0,
totalRead);
}
// add current character
contents[totalRead++] = (char) current; // coming from totalRead==length
}
// read as many chars as possible
int amountRead = reader.read(contents, totalRead, amountRequested);
if (amountRead < 0) {
break;
}
totalRead += amountRead;
}
// Do not keep first character for UTF-8 BOM encoding
int start = 0;
if (totalRead > 0 && UTF_8.equals(encoding)) {
if (contents[0] == 0xFEFF) { // if BOM char then skip
totalRead--;
start = 1;
}
}
// resize contents if necessary
if (totalRead < contents.length) {
System.arraycopy(contents, start, contents = new char[totalRead], 0, totalRead);
}
return contents;
}
/**
* Return the line number corresponding to a source position, given an array of line end
* positions.
*
* @param position the source position for which a line number is needed
* @param lineEnds an array of line end positions
* @param g min index in lineEnds (>= 0)
* @param d max index in lineEnds (< lineEnds.length)
* @return line number of source position
*/
public static int getLineNumber(int position, int[] lineEnds, int g, int d) {
if (lineEnds == null) {
return 1;
}
if (d == -1) {
return 1;
}
int m = g, start;
while (g <= d) {
m = g + (d - g) / 2;
if (position < (start = lineEnds[m])) {
d = m - 1;
} else if (position > start) {
g = m + 1;
} else {
return m + 1;
}
}
if (position < lineEnds[m]) {
return m + 1;
}
return m + 2;
}
/**
* Return the substring of the given file name, ending at the start of a Dart-like extension. The
* entire file name is returned if it doesn't end with a Dart-like extension.
*
* @return the given file name with any Dart-like extension removed
*/
public static String getNameWithoutDartLikeExtension(String fileName) {
int index = indexOfDartLikeExtension(fileName);
if (index == -1) {
return fileName;
}
return fileName.substring(0, index);
}
/**
* Returns the given file's contents as a character array.
*/
public static char[] getResourceContentsAsCharArray(IFile file) throws DartModelException {
// Get encoding from file
String encoding;
try {
encoding = file.getCharset();
} catch (CoreException ce) {
// do not use any encoding
encoding = null;
}
return getResourceContentsAsCharArray(file, encoding);
}
public static char[] getResourceContentsAsCharArray(IFile file, String encoding)
throws DartModelException {
// Get file length
// workaround https://bugs.eclipse.org/bugs/show_bug.cgi?id=130736 by using
// java.io.File if possible
IPath location = file.getLocation();
long length;
if (location == null) {
// non local file
try {
URI locationURI = file.getLocationURI();
if (locationURI == null) {
// throw new CoreException(new Status(IStatus.ERROR,
// DartCore.PLUGIN_ID, Messages.bind(Messages.file_notFound,
// file.getFullPath().toString())));
throw new CoreException(new Status(IStatus.ERROR, DartCore.PLUGIN_ID, "File not found: "
+ file.getFullPath().toString()));
}
length = EFS.getStore(locationURI).fetchInfo().getLength();
} catch (CoreException e) {
throw new DartModelException(e, DartModelStatusConstants.ELEMENT_DOES_NOT_EXIST);
}
} else {
// local file
length = location.toFile().length();
}
// Get resource contents
InputStream stream = null;
try {
stream = file.getContents(true);
} catch (CoreException e) {
throw new DartModelException(e, DartModelStatusConstants.ELEMENT_DOES_NOT_EXIST);
}
try {
return getInputStreamAsCharArray(stream, (int) length, encoding);
} catch (IOException e) {
throw new DartModelException(e, DartModelStatusConstants.IO_EXCEPTION);
} finally {
try {
stream.close();
} catch (IOException e) {
// ignore
}
}
}
/**
* Return the index of the Dart-like extension of the given file name or -1 if it doesn't end with
* a known Dart-like extension. Note this is the index of the '.' even if it is not considered
* part of the extension.
*
* @return the index of the Dart-like extension of the given file name
*/
public static int indexOfDartLikeExtension(String fileName) {
int fileNameLength = fileName.length();
char[][] javaLikeExtensions = getDartLikeExtensions();
extensions : for (int i = 0, length = javaLikeExtensions.length; i < length; i++) {
char[] extension = javaLikeExtensions[i];
int extensionLength = extension.length;
int extensionStart = fileNameLength - extensionLength;
int dotIndex = extensionStart - 1;
if (dotIndex < 0) {
continue;
}
if (fileName.charAt(dotIndex) != '.') {
continue;
}
for (int j = 0; j < extensionLength; j++) {
if (fileName.charAt(extensionStart + j) != extension[j]) {
continue extensions;
}
}
return dotIndex;
}
return -1;
}
/**
* Sorts an array of objects in place. The given comparer compares pairs of items.
*/
public static <T> void sort(T[] objects, Comparator<T> comparer) {
if (objects.length > 1) {
quickSort(objects, 0, objects.length - 1, comparer);
}
}
/**
* Sort the objects in the given collection using the given comparer.
*/
private static <T> void quickSort(T[] sortedCollection, int left, int right,
Comparator<T> comparer) {
int original_left = left;
int original_right = right;
T mid = sortedCollection[left + (right - left) / 2];
do {
while (comparer.compare(sortedCollection[left], mid) < 0) {
left++;
}
while (comparer.compare(mid, sortedCollection[right]) < 0) {
right--;
}
if (left <= right) {
T tmp = sortedCollection[left];
sortedCollection[left] = sortedCollection[right];
sortedCollection[right] = tmp;
left++;
right--;
}
} while (left <= right);
if (original_left < right) {
quickSort(sortedCollection, original_left, right, comparer);
}
if (left < original_right) {
quickSort(sortedCollection, left, original_right, comparer);
}
}
}