/*
* Created on Jan 29, 2005
*
*/
package org.rubypeople.rdt.internal.core.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyModelStatusConstants;
import org.rubypeople.rdt.core.IRubyProject;
import org.rubypeople.rdt.core.IType;
import org.rubypeople.rdt.core.RubyConventions;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.internal.core.RubyElement;
/**
* @author Chris
*/
public class Util
{
private static boolean ENABLE_RUBY_LIKE_EXTENSIONS = true;
private static char[][] RUBY_LIKE_EXTENSIONS;
private static char[][] RUBY_LIKE_FILENAMES;
private static final String NAMESPACE_DELIMETER = "::";
private Util()
{
// cannot be instantiated
}
public interface Comparer
{
/**
* Returns 0 if a and b are equal, >0 if a is greater than b, or <0 if a is less than b.
*/
int compare(Object a, Object b);
}
/**
* Sorts an array of objects in place. The given comparer compares pairs of items.
*/
public static void sort(Object[] objects, Comparer 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 void quickSort(Object[] sortedCollection, int left, int right, Comparer comparer)
{
int original_left = left;
int original_right = right;
Object mid = sortedCollection[(left + right) / 2];
do
{
while (comparer.compare(sortedCollection[left], mid) < 0)
{
left++;
}
while (comparer.compare(mid, sortedCollection[right]) < 0)
{
right--;
}
if (left <= right)
{
Object 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);
}
}
/*
* Returns whether the given resource path matches one of the inclusion/exclusion patterns. NOTE: should not be
* asked directly using pkg root pathes
* @see IClasspathEntry#getInclusionPatterns
* @see IClasspathEntry#getExclusionPatterns
*/
public final static boolean isExcluded(IPath resourcePath, char[][] inclusionPatterns, char[][] exclusionPatterns,
boolean isFolderPath)
{
if (inclusionPatterns == null && exclusionPatterns == null)
return false;
return isExcluded(resourcePath.toString().toCharArray(), inclusionPatterns, exclusionPatterns, isFolderPath);
}
/*
* Returns whether the given resource matches one of the exclusion patterns. NOTE: should not be asked directly
* using pkg root pathes
* @see ILoadpathEntry#getExclusionPatterns
*/
public final static boolean isExcluded(IResource resource, char[][] inclusionPatterns, char[][] exclusionPatterns)
{
IPath path = resource.getFullPath();
// ensure that folders are only excluded if all of their children are
// excluded
return isExcluded(path, inclusionPatterns, exclusionPatterns, resource.getType() == IResource.FOLDER);
}
/*
* TODO (philippe) should consider promoting it to CharOperation Returns whether the given resource path matches one
* of the inclusion/exclusion patterns. NOTE: should not be asked directly using pkg root pathes
* @see IClasspathEntry#getInclusionPatterns
* @see IClasspathEntry#getExclusionPatterns
*/
public final static boolean isExcluded(char[] path, char[][] inclusionPatterns, char[][] exclusionPatterns,
boolean isFolderPath)
{
if (inclusionPatterns == null && exclusionPatterns == null)
return false;
inclusionCheck: if (inclusionPatterns != null)
{
for (int i = 0, length = inclusionPatterns.length; i < length; i++)
{
char[] pattern = inclusionPatterns[i];
char[] folderPattern = pattern;
if (isFolderPath)
{
int lastSlash = CharOperation.lastIndexOf('/', pattern);
if (lastSlash != -1 && lastSlash != pattern.length - 1)
{ // trailing
// slash
// ->
// adds
// '**'
// for
// free
// (see
// http://ant.apache.org/manual/dirtasks.html)
int star = CharOperation.indexOf('*', pattern, lastSlash);
if ((star == -1 || star >= pattern.length - 1 || pattern[star + 1] != '*'))
{
folderPattern = CharOperation.subarray(pattern, 0, lastSlash);
}
}
}
if (CharOperation.pathMatch(folderPattern, path, true, '/'))
{
break inclusionCheck;
}
}
return true; // never included
}
if (isFolderPath)
{
path = CharOperation.concat(path, new char[] { '*' }, '/');
}
exclusionCheck: if (exclusionPatterns != null)
{
for (int i = 0, length = exclusionPatterns.length; i < length; i++)
{
if (CharOperation.pathMatch(exclusionPatterns[i], path, true, '/'))
{
return true;
}
}
}
return false;
}
public static void verbose(String log)
{
verbose(log, System.out);
}
public static synchronized void verbose(String log, PrintStream printStream)
{
int start = 0;
do
{
int end = log.indexOf('\n', start);
printStream.print(Thread.currentThread());
printStream.print(" "); //$NON-NLS-1$
printStream.print(log.substring(start, end == -1 ? log.length() : end + 1));
start = end + 1;
}
while (start != 0);
printStream.println();
}
/**
* Returns true if the given name ends with one of the known ruby like extension. (implementation is not creating
* extra strings)
*/
public final static boolean isRubyLikeFileName(String name)
{
if (name == null)
return false;
char[][] rubyFileNames = getRubyLikeFilenames();
for (int i = 0; i < rubyFileNames.length; i++)
{
char[] filename = rubyFileNames[i];
if (name.equals(new String(filename)))
return true;
}
return indexOfRubyLikeExtension(name) != -1;
}
public final static boolean isRubyOrERBLikeFileName(String name)
{
return isRubyLikeFileName(name) || isERBLikeFileName(name);
}
/**
* Validate the given compilation unit name. A compilation unit name must obey the following rules:
* <ul>
* <li>it must not be null
* <li>it must include the <code>".rb"</code> or <code>".rbw"</code> suffix
* <li>its prefix must be a valid identifier
* </ul>
* </p>
*
* @param name
* the name of a compilation unit
* @return a status object with code <code>IStatus.OK</code> if the given name is valid as a compilation unit name,
* otherwise a status object indicating what is wrong with the name
*/
public static boolean isValidRubyScriptName(String name)
{
return RubyConventions.validateRubyScriptName(name).getSeverity() != IStatus.ERROR;
}
/**
* Compares two arrays using equals() on the elements. Either or both arrays may be null. Returns true if both are
* null. Returns false if only one is null. If both are arrays, returns true iff they have the same length and all
* elements compare true with equals.
*/
public static boolean equalArraysOrNull(Object[] a, Object[] b)
{
if (a == b)
return true;
if (a == null || b == null)
return false;
int len = a.length;
if (len != b.length)
return false;
for (int i = 0; i < len; ++i)
{
if (a[i] == null)
{
if (b[i] != null)
return false;
}
else
{
if (!a[i].equals(b[i]))
return false;
}
}
return true;
}
/*
* Add a log entry
*/
public static void log(Throwable e, String message)
{
Throwable nestedException;
if (e instanceof RubyModelException && (nestedException = ((RubyModelException) e).getException()) != null)
{
e = nestedException;
}
IStatus status = new Status(IStatus.ERROR, RubyCore.PLUGIN_ID, IStatus.ERROR, message, e);
RubyCore.getPlugin().getLog().log(status);
}
/**
* Combines two hash codes to make a new one.
*/
public static int combineHashCodes(int hashCode1, int hashCode2)
{
return hashCode1 * 17 + hashCode2;
}
/*
* Returns whether the given ruby element is exluded from its root's classpath. It doesn't check whether the root
* itself is on the classpath or not
*/
public static final boolean isExcluded(IRubyElement element)
{
int elementType = element.getElementType();
switch (elementType)
{
case IRubyElement.RUBY_MODEL:
case IRubyElement.RUBY_PROJECT:
return false;
case IRubyElement.SCRIPT:
IResource resource = element.getResource();
if (resource == null)
return false;
// if (isExcluded(resource, root.fullInclusionPatternChars(),
// root.fullExclusionPatternChars()))
// return true;
return isExcluded(element.getParent());
default:
IRubyElement cu = element.getAncestor(IRubyElement.SCRIPT);
return cu != null && isExcluded(cu);
}
}
/**
* Returns the substring of the given file name, ending at the start of a Ruby like extension. The entire file name
* is returned if it doesn't end with a Ruby like extension.
*/
public static String getNameWithoutRubyLikeExtension(String fileName)
{
int index = indexOfRubyLikeExtension(fileName);
if (index == -1)
return fileName;
return fileName.substring(0, index);
}
/*
* Returns the index of the Ruby like extension of the given file name or -1 if it doesn't end with a known Ruby
* like extension. Note this is the index of the '.' even if it is not considered part of the extension.
*/
public static int indexOfRubyLikeExtension(String fileName)
{
int fileNameLength = fileName.length();
char[][] rubyLikeExtensions = getRubyLikeExtensions();
extensions: for (int i = 0, length = rubyLikeExtensions.length; i < length; i++)
{
char[] extension = rubyLikeExtensions[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;
}
/**
* Returns the registered Ruby like extensions.
*/
public static char[][] getRubyLikeExtensions()
{
if (RUBY_LIKE_EXTENSIONS == null)
{
// TODO (jerome) reenable once RDT UI supports other file extensions
// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=71460)
if (!ENABLE_RUBY_LIKE_EXTENSIONS)
RUBY_LIKE_EXTENSIONS = new char[][] { "rb".toCharArray(), "rbw".toCharArray(), "rjs".toCharArray(),
"rxml".toCharArray(), "rake".toCharArray() };
else
{
IContentType rubyContentType = Platform.getContentTypeManager().getContentType(
RubyCore.RUBY_SOURCE_CONTENT_TYPE);
String[] fileExtensions = rubyContentType == null ? null : rubyContentType
.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
// note that file extensions contains "ruby" as it is defined in
// RDT Core's plugin.xml
int length = fileExtensions == null ? 0 : fileExtensions.length;
char[][] extensions = new char[length][];
SimpleWordSet knownExtensions = new SimpleWordSet(length); // used
// to
// ensure
// no
// duplicate
// extensions
extensions[0] = "rb".toCharArray(); // ensure that "rb" is first
knownExtensions.add(extensions[0]);
int index = 1;
for (int i = 0; i < length; i++)
{
String fileExtension = fileExtensions[i];
char[] extension = fileExtension.toCharArray();
if (!knownExtensions.includes(extension))
{
extensions[index++] = extension;
knownExtensions.add(extension);
}
}
if (index != length)
System.arraycopy(extensions, 0, extensions = new char[index][], 0, index);
RUBY_LIKE_EXTENSIONS = extensions;
}
}
return RUBY_LIKE_EXTENSIONS;
}
/**
* Returns the registered Ruby like filenames.
*/
public static char[][] getRubyLikeFilenames()
{
if (RUBY_LIKE_FILENAMES == null)
{
IContentType rubyContentType = Platform.getContentTypeManager().getContentType(
RubyCore.RUBY_SOURCE_CONTENT_TYPE);
String[] filenames = rubyContentType == null ? null : rubyContentType
.getFileSpecs(IContentType.FILE_NAME_SPEC);
int length = filenames == null ? 0 : filenames.length;
names = new char[length][];
SimpleWordSet knownExtensions = new SimpleWordSet(length); // used
// to
// ensure
// no
// duplicate
// names
names[0] = "Rakefile".toCharArray(); // ensure that "Rakefile" is
// first
knownExtensions.add(names[0]);
int index = 1;
for (int i = 0; i < length; i++)
{
String fileExtension = filenames[i];
char[] extension = fileExtension.toCharArray();
if (!knownExtensions.includes(extension))
{
names[index++] = extension;
knownExtensions.add(extension);
}
}
if (index != length)
System.arraycopy(names, 0, names = new char[index][], 0, index);
RUBY_LIKE_FILENAMES = names;
}
return RUBY_LIKE_FILENAMES;
}
private static final int DEFAULT_READING_SIZE = 8192;
private static char[][] names;
private static final String ARGUMENTS_DELIMITER = "#"; //$NON-NLS-1$
private static final String EMPTY_ARGUMENT = " "; //$NON-NLS-1$
/**
* @param file
* @return
* @throws IOException
* @throws CoreException
*/
public static char[] getResourceContentsAsCharArray(IFile file) throws RubyModelException
{
// Get encoding from file
String encoding = null;
try
{
encoding = file.getCharset();
}
catch (CoreException ce)
{
// do not use any encoding
}
return getResourceContentsAsCharArray(file, encoding);
}
public static char[] getResourceContentsAsCharArray(IFile file, String encoding) throws RubyModelException
{
// Get resource contents
InputStream stream = null;
try
{
stream = new BufferedInputStream(file.getContents(true));
}
catch (CoreException e)
{
throw new RubyModelException(e, IRubyModelStatusConstants.ELEMENT_DOES_NOT_EXIST);
}
try
{
return Util.getInputStreamAsCharArray(stream, -1, encoding);
}
catch (IOException e)
{
throw new RubyModelException(e, IRubyModelStatusConstants.IO_EXCEPTION);
}
finally
{
try
{
stream.close();
}
catch (IOException e)
{
// ignore
}
}
}
/**
* Returns the given input stream's contents as a character array. If a length is specified (ie. if length != -1),
* only length chars are returned. Otherwise all chars in the stream are returned. Note this doesn't close the
* stream.
*
* @throws IOException
* if a problem occured reading the stream.
*/
public static char[] getInputStreamAsCharArray(InputStream stream, int length, String encoding) throws IOException
{
InputStreamReader reader = null;
reader = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding);
char[] contents;
if (length == -1)
{
contents = new char[0];
int contentsLength = 0;
int amountRead = -1;
do
{
int amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE); // read
// at
// least
// 8K
// resize contents if needed
if (contentsLength + amountRequested > contents.length)
{
System.arraycopy(contents, 0, contents = new char[contentsLength + amountRequested], 0,
contentsLength);
}
// read as many chars as possible
amountRead = reader.read(contents, contentsLength, amountRequested);
if (amountRead > 0)
{
// remember length of contents
contentsLength += amountRead;
}
}
while (amountRead != -1);
// Do not keep first character for UTF-8 BOM encoding
int start = 0;
if (contentsLength > 0 && "UTF-8".equals(encoding)) { //$NON-NLS-1$
if (contents[0] == 0xFEFF)
{ // if BOM char then skip
contentsLength--;
start = 1;
}
}
// resize contents if necessary
if (contentsLength < contents.length)
{
System.arraycopy(contents, start, contents = new char[contentsLength], 0, contentsLength);
}
}
else
{
contents = new char[length];
int len = 0;
int readSize = 0;
while ((readSize != -1) && (len != length))
{
// See PR 1FMS89U
// We record first the read size. In this case len is the actual
// read size.
len += readSize;
readSize = reader.read(contents, len, length - len);
}
// Do not keep first character for UTF-8 BOM encoding
int start = 0;
if (length > 0 && "UTF-8".equals(encoding)) { //$NON-NLS-1$
if (contents[0] == 0xFEFF)
{ // if BOM char then skip
len--;
start = 1;
}
}
// See PR 1FMS89U
// Now we need to resize in case the default encoding used more than
// one byte for each
// character
if (len != length)
System.arraycopy(contents, start, (contents = new char[len]), 0, len);
}
return contents;
}
public static void resetRubyLikeExtensions()
{
RUBY_LIKE_EXTENSIONS = null;
RUBY_LIKE_FILENAMES = null;
}
/**
* Returns a new array adding the second array at the end of first array. It answers null if the first and second
* are null. If the first array is null or if it is empty, then a new array is created with second. If the second
* array is null, then the first array is returned. <br>
* <br>
* For example:
* <ol>
* <li>
*
* <pre>
* first = null
* second = "a"
* => result = {"a"}
* </pre>
* <li>
*
* <pre>
* first = {"a"}
* second = null
* => result = {"a"}
* </pre>
*
* </li>
* <li>
*
* <pre>
* first = {"a"}
* second = {"b"}
* => result = {"a", "b"}
* </pre>
*
* </li>
* </ol>
*
* @param first
* the first array to concatenate
* @param second
* the array to add at the end of the first array
* @return a new array adding the second array at the end of first array, or null if the two arrays are null.
*/
public static final String[] arrayConcat(String[] first, String second)
{
if (second == null)
return first;
if (first == null)
return new String[] { second };
int length = first.length;
if (first.length == 0)
{
return new String[] { second };
}
String[] result = new String[length + 1];
System.arraycopy(first, 0, result, 0, length);
result[length] = second;
return result;
}
public static String[] getTrimmedSimpleNames(String packageName)
{
if (packageName.length() == 0)
return new String[0];
return packageName.split("\\" + File.separator);
}
public static String concatWith(String[] array, char separator)
{
StringBuffer buffer = new StringBuffer();
for (int i = 0, length = array.length; i < length; i++)
{
buffer.append(array[i]);
if (i < length - 1)
buffer.append(separator);
}
return buffer.toString();
}
public static boolean isValidSourceFolderName(String name)
{
// TODO Actually make sure there's no special characters.
return true;
}
/**
* Returns the given file's contents as a byte array.
*/
public static byte[] getResourceContentsAsByteArray(IFile file) throws RubyModelException
{
InputStream stream = null;
try
{
stream = file.getContents(true);
}
catch (CoreException e)
{
throw new RubyModelException(e);
}
try
{
return org.rubypeople.rdt.core.util.Util.getInputStreamAsByteArray(stream, -1);
}
catch (IOException e)
{
throw new RubyModelException(e, IRubyModelStatusConstants.IO_EXCEPTION);
}
finally
{
try
{
stream.close();
}
catch (IOException e)
{
// ignore
}
}
}
/*
* Converts the given URI to a local file. Use the existing file if the uri is on the local file system. Otherwise
* fetch it. Returns null if unable to fetch it.
*/
public static File toLocalFile(URI uri, IProgressMonitor monitor) throws CoreException
{
IFileStore fileStore = EFS.getStore(uri);
File localFile = fileStore.toLocalFile(EFS.NONE, monitor);
if (localFile == null)
// non local file system
localFile = fileStore.toLocalFile(EFS.CACHE, monitor);
return localFile;
}
/**
* Put all the arguments in one String.
*/
public static String getProblemArgumentsForMarker(String[] arguments)
{
StringBuffer args = new StringBuffer(10);
args.append(arguments.length);
args.append(':');
for (int j = 0; j < arguments.length; j++)
{
if (j != 0)
args.append(ARGUMENTS_DELIMITER);
if (arguments[j].length() == 0)
{
args.append(EMPTY_ARGUMENT);
}
else
{
args.append(arguments[j]);
}
}
return args.toString();
}
/**
* Returns the line separator found in the given text. If it is null, or not found return the line delimitor for the
* given project. If the project is null, returns the line separator for the workspace. If still null, return the
* system line separator.
*/
public static String getLineSeparator(String text, IRubyProject project)
{
String lineSeparator = null;
// line delimiter in given text
if (text != null && text.length() != 0)
{
lineSeparator = findLineSeparator(text.toCharArray());
if (lineSeparator != null)
return lineSeparator;
}
// line delimiter in project preference
IScopeContext[] scopeContext;
if (project != null)
{
scopeContext = new IScopeContext[] { new ProjectScope(project.getProject()) };
lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME,
Platform.PREF_LINE_SEPARATOR, null, scopeContext);
if (lineSeparator != null)
return lineSeparator;
}
// line delimiter in workspace preference
scopeContext = new IScopeContext[] { new InstanceScope() };
lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR,
null, scopeContext);
if (lineSeparator != null)
return lineSeparator;
// system line delimiter
return org.rubypeople.rdt.core.util.Util.LINE_SEPARATOR;
}
/**
* Finds 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 none found
*/
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;
}
/**
* Sorts an array of strings in place using quicksort.
*/
public static void sort(String[] strings)
{
if (strings.length > 1)
quickSort(strings, 0, strings.length - 1);
}
/**
* Sort the strings in the given collection.
*/
private static void quickSort(String[] sortedCollection, int left, int right)
{
int original_left = left;
int original_right = right;
String mid = sortedCollection[(left + right) / 2];
do
{
while (sortedCollection[left].compareTo(mid) < 0)
{
left++;
}
while (mid.compareTo(sortedCollection[right]) < 0)
{
right--;
}
if (left <= right)
{
String tmp = sortedCollection[left];
sortedCollection[left] = sortedCollection[right];
sortedCollection[right] = tmp;
left++;
right--;
}
}
while (left <= right);
if (original_left < right)
{
quickSort(sortedCollection, original_left, right);
}
if (left < original_right)
{
quickSort(sortedCollection, left, original_right);
}
}
/**
* Compares two arrays using equals() on the elements. Neither can be null. Only the first len elements are
* compared. Return false if either array is shorter than len.
*/
public static boolean equalArrays(Object[] a, Object[] b, int len)
{
if (a == b)
return true;
if (a.length < len || b.length < len)
return false;
for (int i = 0; i < len; ++i)
{
if (a[i] == null)
{
if (b[i] != null)
return false;
}
else
{
if (!a[i].equals(b[i]))
return false;
}
}
return true;
}
public static String getSimpleName(String fullyQualifiedName)
{
if (fullyQualifiedName == null)
return null;
String[] names = getTypeNameParts(fullyQualifiedName);
if (names.length == 0)
return null;
return names[names.length - 1];
}
public static boolean parentsMatch(IType type, String fullyQualifiedName)
{
String[] names = getTypeNameParts(fullyQualifiedName);
for (int i = names.length - 2; i >= 0; i--)
{ // Start at second last
// name piece, go all
// the way to first
IType parent = type.getDeclaringType();
if (parent == null || !names[i].equals(parent.getElementName()))
{
return false;
}
type = parent;
}
return true;
}
public static String[] getTypeNameParts(String fullyQualifiedName)
{
if (fullyQualifiedName == null)
return new String[0];
return fullyQualifiedName.split(NAMESPACE_DELIMETER);
}
public static void sort(int[] list)
{
if (list.length > 1)
quickSort(list, 0, list.length - 1);
}
private static void quickSort(int[] list, int left, int right)
{
int original_left = left;
int original_right = right;
int mid = list[(left + right) / 2];
do
{
while (list[left] < mid)
{
left++;
}
while (mid < list[right])
{
right--;
}
if (left <= right)
{
int tmp = list[left];
list[left] = list[right];
list[right] = tmp;
left++;
right--;
}
}
while (left <= right);
if (original_left < right)
{
quickSort(list, original_left, right);
}
if (left < original_right)
{
quickSort(list, left, original_right);
}
}
/**
* Returns the toString() of the given full path minus the first given number of segments. The returned string is
* always a relative path (it has no leading slash)
*/
public static String relativePath(IPath fullPath, int skipSegmentCount)
{
boolean hasTrailingSeparator = fullPath.hasTrailingSeparator();
String[] segments = fullPath.segments();
// compute length
int length = 0;
int max = segments.length;
if (max > skipSegmentCount)
{
for (int i1 = skipSegmentCount; i1 < max; i1++)
{
length += segments[i1].length();
}
// add the separator lengths
length += max - skipSegmentCount - 1;
}
if (hasTrailingSeparator)
length++;
char[] result = new char[length];
int offset = 0;
int len = segments.length - 1;
if (len >= skipSegmentCount)
{
// append all but the last segment, with separators
for (int i = skipSegmentCount; i < len; i++)
{
int size = segments[i].length();
segments[i].getChars(0, size, result, offset);
offset += size;
result[offset++] = '/';
}
// append the last segment
int size = segments[len].length();
segments[len].getChars(0, size, result, offset);
offset += size;
}
if (hasTrailingSeparator)
result[offset++] = '/';
return new String(result);
}
/**
* Sorts an array of Ruby elements based on their toStringWithAncestors(), returning a new array with the sorted
* items. The original array is left untouched.
*/
public static IRubyElement[] sortCopy(IRubyElement[] elements)
{
int len = elements.length;
IRubyElement[] copy = new IRubyElement[len];
System.arraycopy(elements, 0, copy, 0, len);
sort(copy, new Comparer()
{
public int compare(Object a, Object b)
{
return ((RubyElement) a).toStringWithAncestors().compareTo(((RubyElement) b).toStringWithAncestors());
}
});
return copy;
}
public static String identifierToConstant(String typeName)
{
StringBuffer buffer = new StringBuffer();
boolean doNextAsUpper = true;
for (int i = 0; i < typeName.length(); i++)
{
char c = typeName.charAt(i);
if (doNextAsUpper)
{
buffer.append(Character.toUpperCase(c));
doNextAsUpper = false;
}
else if (c == '_')
{
doNextAsUpper = true;
}
else
{
buffer.append(c);
}
}
return buffer.toString();
}
/**
* Sorts an array of strings in place using quicksort in reverse alphabetical order.
*/
public static void sortReverseOrder(String[] strings)
{
if (strings.length > 1)
quickSortReverse(strings, 0, strings.length - 1);
}
/**
* Sort the strings in the given collection in reverse alphabetical order.
*/
private static void quickSortReverse(String[] sortedCollection, int left, int right)
{
int original_left = left;
int original_right = right;
String mid = sortedCollection[(left + right) / 2];
do
{
while (sortedCollection[left].compareTo(mid) > 0)
{
left++;
}
while (mid.compareTo(sortedCollection[right]) > 0)
{
right--;
}
if (left <= right)
{
String tmp = sortedCollection[left];
sortedCollection[left] = sortedCollection[right];
sortedCollection[right] = tmp;
left++;
right--;
}
}
while (left <= right);
if (original_left < right)
{
quickSortReverse(sortedCollection, original_left, right);
}
if (left < original_right)
{
quickSortReverse(sortedCollection, left, original_right);
}
}
/**
* Returns the concatenation of the given array parts using the given separator between each part and appending the
* given name at the end. <br>
* <br>
* For example:<br>
* <ol>
* <li>
*
* <pre>
* name = "c"
* array = { "a", "b" }
* separator = '.'
* => result = "a.b.c"
* </pre>
*
* </li>
* <li>
*
* <pre>
* name = null
* array = { "a", "b" }
* separator = '.'
* => result = "a.b"
* </pre>
*
* </li>
* <li>
*
* <pre>
* name = " c"
* array = null
* separator = '.'
* => result = "c"
* </pre>
*
* </li>
* </ol>
*
* @param array
* the given array
* @param name
* the given name
* @param separator
* the given separator
* @return the concatenation of the given array parts using the given separator between each part and appending the
* given name at the end
*/
public static final String concatWith(String[] array, String name, char separator)
{
if (array == null || array.length == 0)
return name;
if (name == null || name.length() == 0)
return concatWith(array, separator);
StringBuffer buffer = new StringBuffer();
for (int i = 0, length = array.length; i < length; i++)
{
buffer.append(array[i]);
buffer.append(separator);
}
buffer.append(name);
return buffer.toString();
}
/**
* Whether or not to ignore the warning from JRuby
*
* @param message
* @return
*/
public static boolean ignore(String message)
{
return message.startsWith("Useless") || message.startsWith("Found '='");
}
public static boolean isERBLikeFileName(String name)
{
return name.endsWith(".erb") || name.endsWith(".rhtml");
}
public static char[] replaceNonRubyCodeWithWhitespace(String source)
{
List<String> code = getRubyCodeChunks(source);
if (code == null || code.size() == 0)
{
return fillWithWhitespace(source).toCharArray();
}
StringBuilder buffer = new StringBuilder();
int endOfLastFragment = 0;
boolean dontIncludeSemicolon = false;
for (String codeFragment : code)
{
int beginningOfCurrentFragment = source.indexOf(codeFragment, endOfLastFragment); // find index of current
// piece of code,
// start looking after last piece of
// code
// replace from end of last code piece to beginning of next with
// spaces for any non-whitespace characters in between
if (codeFragment.startsWith("#"))
{
codeFragment = fillWithWhitespace(codeFragment);
dontIncludeSemicolon = true;
}
String portion = source.substring(endOfLastFragment, beginningOfCurrentFragment);
for (int j = 0; j < portion.length(); j++)
{
char chr = portion.charAt(j);
if (Character.isWhitespace(chr))
{
buffer.append(chr);
}
else
{
if (j != 0 && chr == '>' && portion.charAt(j - 1) == '%')
{
if (dontIncludeSemicolon)
{
buffer.append(' ');
dontIncludeSemicolon = false;
}
else
buffer.append(';');
}
else
{
buffer.append(' ');
}
}
}
buffer.append(codeFragment); // now add in code piece
endOfLastFragment = beginningOfCurrentFragment + codeFragment.length(); // now search from end of
// current fragment
}
return buffer.toString().toCharArray();
}
/**
* Takes a string and replaces all non-whistespace content with space characters (retains any existing whitespace in
* place).
*
* @param source
* @return
*/
private static String fillWithWhitespace(String source)
{
StringBuilder buffer = new StringBuilder();
for (int j = 0; j < source.length(); j++)
{
char chr = source.charAt(j);
if (Character.isWhitespace(chr))
{
buffer.append(chr);
}
else
{
buffer.append(' ');
}
}
return buffer.toString();
}
private static List<String> getRubyCodeChunks(String stringContents)
{
List<String> code = new ArrayList<String>();
String[] pieces = stringContents.split("(<%%)|(%%>)|(<%=)|(<%)|(\\-?%>)");
for (int i = 0; i < pieces.length; i++)
{
if ((i % 2) == 1)
{
code.add(pieces[i]);
}
}
return code;
}
public static boolean isValidRubyOrERBScriptName(String name)
{
if (RubyConventions.validateRubyScriptName(name).getSeverity() != IStatus.ERROR)
return true;
return isERBLikeFileName(name);
}
}