/*
* StandardUtilities.java - Various miscallaneous utility functions
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 1999, 2006 Matthieu Casanova, Slava Pestov
* Portions copyright (C) 2000 Richard S. Hall
* Portions copyright (C) 2001 Dirk Moebius
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.util;
//{{{ Imports
import javax.annotation.Nullable;
import javax.swing.text.Segment;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.util.Comparator;
import java.util.Stack;
//}}}
/**
* Several tools that depends on JDK only.
*
* @author Matthieu Casanova
* @version $Id$
* @since 4.3pre5
*/
public class StandardUtilities
{
//{{{ charsToEscapes() methods
/**
* Escapes newlines, tabs, backslashes, and quotes in the specified
* string.
* @param str The string
* @since jEdit 4.3pre15
*/
public static String charsToEscapes(String str)
{
return charsToEscapes(str,"\n\t\\\"'");
}
/**
* Escapes the specified characters in the specified string.
* @param str The string
* @param toEscape Any characters that require escaping
* @since jEdit 4.3pre15
*/
public static String charsToEscapes(String str, String toEscape)
{
StringBuilder buf = new StringBuilder();
for(int i = 0; i < str.length(); i++)
{
char c = str.charAt(i);
if(toEscape.indexOf(c) != -1)
{
if(c == '\n')
buf.append("\\n");
else if(c == '\t')
buf.append("\\t");
else
{
buf.append('\\');
buf.append(c);
}
}
else
buf.append(c);
}
return buf.toString();
} //}}}
//{{{ getPrevIndentStyle() method
/**
* @param str A java string
* @return the leading whitespace of that string, for indenting subsequent lines.
* @since jEdit 4.3pre10
*/
public static String getIndentString(String str)
{
StringBuilder indentString = new StringBuilder();
for (int i = 0; i < str.length(); i++)
{
char ch = str.charAt(i);
if (! Character.isWhitespace(ch))
break;
indentString.append(ch);
}
return indentString.toString();
} //}}}
//{{{ getLeadingWhiteSpace() methods
/**
* Returns the number of leading white space characters in the
* specified string.
*
* @param str The string
*/
public static int getLeadingWhiteSpace(String str)
{
return getLeadingWhiteSpace((CharSequence)str);
}
/**
* Returns the number of leading white space characters in the
* specified string.
*
* @param str The string
* @since jEdit 4.3pre15
*/
public static int getLeadingWhiteSpace(CharSequence str)
{
int whitespace = 0;
loop: for(;whitespace < str.length();)
{
switch(str.charAt(whitespace))
{
case ' ':
case '\t':
whitespace++;
break;
default:
break loop;
}
}
return whitespace;
} //}}}
//{{{ getTrailingWhiteSpace() method
/**
* Returns the number of trailing whitespace characters in the
* specified string.
* @param str The string
*/
public static int getTrailingWhiteSpace(String str)
{
int whitespace = 0;
loop: for(int i = str.length() - 1; i >= 0; i--)
{
switch(str.charAt(i))
{
case ' ':
case '\t':
whitespace++;
break;
default:
break loop;
}
}
return whitespace;
} //}}}
//{{{ getLeadingWhiteSpaceWidth() methods
/**
* Returns the width of the leading white space in the specified
* string.
* @param str The string
* @param tabSize The tab size
*/
public static int getLeadingWhiteSpaceWidth(String str, int tabSize)
{
return getLeadingWhiteSpaceWidth((CharSequence)str, tabSize);
}
/**
* Returns the width of the leading white space in the specified
* string.
* @param str The string
* @param tabSize The tab size
* @since jEdit 4.3pre15
*/
public static int getLeadingWhiteSpaceWidth(CharSequence str, int tabSize)
{
int whitespace = 0;
loop: for(int i = 0; i < str.length(); i++)
{
switch(str.charAt(i))
{
case ' ':
whitespace++;
break;
case '\t':
whitespace += tabSize -
whitespace % tabSize;
break;
default:
break loop;
}
}
return whitespace;
} //}}}
//{{{ createWhiteSpace() method
/**
* Creates a string of white space with the specified length.<p>
*
* To get a whitespace string tuned to the current buffer's
* settings, call this method as follows:
*
* <pre>myWhitespace = MiscUtilities.createWhiteSpace(myLength,
* (buffer.getBooleanProperty("noTabs") ? 0
* : buffer.getTabSize()));</pre>
*
* @param len The length
* @param tabSize The tab size, or 0 if tabs are not to be used
*/
public static String createWhiteSpace(int len, int tabSize)
{
return createWhiteSpace(len,tabSize,0);
} //}}}
//{{{ truncateWhiteSpace() method
public static String truncateWhiteSpace(int len, int tabSize,
String indentStr)
{
StringBuilder buf = new StringBuilder();
int indent = 0;
for (int i = 0; indent < len && i < indentStr.length(); i++)
{
char c = indentStr.charAt(i);
if (c == ' ')
{
indent++;
buf.append(c);
}
else if (c == '\t')
{
int withTab = indent + tabSize - (indent % tabSize);
if (withTab > len)
{
for (; indent < len; indent++)
buf.append(' ');
}
else
{
indent = withTab;
buf.append(c);
}
}
}
return buf.toString();
} //}}}
//{{{ createWhiteSpace() method
/**
* Creates a string of white space with the specified length.<p>
*
* To get a whitespace string tuned to the current buffer's
* settings, call this method as follows:
*
* <pre>myWhitespace = MiscUtilities.createWhiteSpace(myLength,
* (buffer.getBooleanProperty("noTabs") ? 0
* : buffer.getTabSize()));</pre>
*
* @param len The length
* @param tabSize The tab size, or 0 if tabs are not to be used
* @param start The start offset, for tab alignment
*/
public static String createWhiteSpace(int len, int tabSize, int start)
{
StringBuilder buf = new StringBuilder();
if(tabSize == 0)
{
while(len-- > 0)
buf.append(' ');
}
else if(len == 1)
buf.append(' ');
else
{
int count = (len + start % tabSize) / tabSize;
if(count != 0)
len += start;
while(count-- > 0)
buf.append('\t');
count = len % tabSize;
while(count-- > 0)
buf.append(' ');
}
return buf.toString();
} //}}}
//{{{ getVirtualWidth() method
/**
* Returns the virtual column number (taking tabs into account) of the
* specified offset in the segment.
*
* @param seg The segment
* @param tabSize The tab size
*/
public static int getVirtualWidth(Segment seg, int tabSize)
{
int virtualPosition = 0;
for (int i = 0; i < seg.count; i++)
{
char ch = seg.array[seg.offset + i];
if (ch == '\t')
{
virtualPosition += tabSize
- virtualPosition % tabSize;
}
else
{
++virtualPosition;
}
}
return virtualPosition;
} //}}}
//{{{ getOffsetOfVirtualColumn() method
/**
* Returns the array offset of a virtual column number (taking tabs
* into account) in the segment.
*
* @param seg The segment
* @param tabSize The tab size
* @param column The virtual column number
* @param totalVirtualWidth If this array is non-null, the total
* virtual width will be stored in its first location if this method
* returns -1.
*
* @return -1 if the column is out of bounds
*/
public static int getOffsetOfVirtualColumn(Segment seg, int tabSize,
int column, int[] totalVirtualWidth)
{
int virtualPosition = 0;
for (int i = 0; i < seg.count; i++)
{
char ch = seg.array[seg.offset + i];
if (ch == '\t')
{
int tabWidth = tabSize
- virtualPosition % tabSize;
if(virtualPosition >= column)
return i;
else
virtualPosition += tabWidth;
}
else
{
if(virtualPosition >= column)
return i;
else
++virtualPosition;
}
}
if(totalVirtualWidth != null)
totalVirtualWidth[0] = virtualPosition;
return -1;
} //}}}
//{{{ compareStrings() method
/**
* Compares two strings.<p>
*
* Unlike <code>String.compareTo()</code>,
* this method correctly recognizes and handles embedded numbers.
* For example, it places "My file 2" before "My file 10".<p>
*
* @param str1 The first string (maybe null)
* @param str2 The second string (maybe null)
* @param ignoreCase If true, case will be ignored
* @return negative If str1 < str2, 0 if both are the same,
* positive if str1 > str2 (null < any non-null string, null = null)
* @since jEdit 4.3pre5
*/
public static int compareStrings(String str1, String str2, boolean ignoreCase)
{
if (str1 == str2)
{
return 0;
}
else if (str1 == null)
{
return -1;
}
else if(str2 == null)
{
return 1;
}
char[] char1 = str1.toCharArray();
char[] char2 = str2.toCharArray();
int len = Math.min(char1.length,char2.length);
for(int i = 0, j = 0; i < len && j < len; i++, j++)
{
char ch1 = char1[i];
char ch2 = char2[j];
if(Character.isDigit(ch1) && Character.isDigit(ch2)
&& ch1 != '0' && ch2 != '0')
{
int _i = i + 1;
int _j = j + 1;
for(; _i < char1.length; _i++)
{
if(!Character.isDigit(char1[_i]))
{
//_i--;
break;
}
}
for(; _j < char2.length; _j++)
{
if(!Character.isDigit(char2[_j]))
{
//_j--;
break;
}
}
int len1 = _i - i;
int len2 = _j - j;
if(len1 > len2)
return 1;
else if(len1 < len2)
return -1;
else
{
for(int k = 0; k < len1; k++)
{
ch1 = char1[i + k];
ch2 = char2[j + k];
if(ch1 != ch2)
return ch1 - ch2;
}
}
i = _i - 1;
j = _j - 1;
}
else
{
if(ignoreCase)
{
ch1 = Character.toLowerCase(ch1);
ch2 = Character.toLowerCase(ch2);
}
if(ch1 != ch2)
return ch1 - ch2;
}
}
return char1.length - char2.length;
} //}}}
//{{{ StringCompare class
/**
* Compares objects as strings.
*/
public static class StringCompare<E> implements Comparator<E>
{
private boolean icase;
public StringCompare(boolean icase)
{
this.icase = icase;
}
public StringCompare()
{
}
@Override
public int compare(E obj1, E obj2)
{
return compareStrings(obj1.toString(),
obj2.toString(),icase);
}
} //}}}
//{{{ objectsEqual() method
/**
* Returns if two strings are equal. This correctly handles null pointers,
* as opposed to calling <code>o1.equals(o2)</code>.
* @since jEdit 4.3pre6
* @deprecated use {java.util.Objects#equals(Object, Object}
*/
@Deprecated
public static boolean objectsEqual(@Nullable Object o1, @Nullable Object o2)
{
if(o1 == null)
{
if(o2 == null)
return true;
else
return false;
}
else if(o2 == null)
return false;
else
return o1.equals(o2);
} //}}}
//{{{ globToRE() method
/**
* Converts a Unix-style glob to a regular expression.<p>
*
* ? becomes ., * becomes .*, {aa,bb} becomes (aa|bb).
* @param glob The glob pattern
* @since jEdit 4.3pre7
*/
public static String globToRE(String glob)
{
if (glob.startsWith("(re)"))
{
return glob.substring(4);
}
final Object NEG = new Object();
final Object GROUP = new Object();
Stack<Object> state = new Stack<Object>();
StringBuilder buf = new StringBuilder();
boolean backslash = false;
for(int i = 0; i < glob.length(); i++)
{
char c = glob.charAt(i);
if(backslash)
{
buf.append('\\');
buf.append(c);
backslash = false;
continue;
}
switch(c)
{
case '\\':
backslash = true;
break;
case '?':
buf.append('.');
break;
case '.':
case '+':
case '(':
case ')':
buf.append('\\');
buf.append(c);
break;
case '*':
buf.append(".*");
break;
case '|':
if(backslash)
buf.append("\\|");
else
buf.append('|');
break;
case '{':
buf.append('(');
if(i + 1 != glob.length() && glob.charAt(i + 1) == '!')
{
buf.append('?');
state.push(NEG);
}
else
state.push(GROUP);
break;
case ',':
if(!state.isEmpty() && state.peek() == GROUP)
buf.append('|');
else
buf.append(',');
break;
case '}':
if(!state.isEmpty())
{
buf.append(')');
if(state.pop() == NEG)
buf.append(".*");
}
else
buf.append('}');
break;
default:
buf.append(c);
}
}
return buf.toString();
} //}}}
//{{{ regionMatches() method
/**
* Implementation of String.regionMatches() for CharSequence.
*
* @param seq The test CharSequence.
* @param toff Offset for the test sequence.
* @param other The sequence to compare to.
* @param ooff Offset of the comparison sequence.
* @param len How many characters to compare.
* @return Whether the two subsequences are equal.
* @see String#regionMatches(int,String,int,int)
*
* @since jEdit 4.3pre15
*/
public static boolean regionMatches(CharSequence seq,
int toff,
CharSequence other,
int ooff,
int len)
{
if (toff < 0 || ooff < 0 || len < 0)
return false;
boolean ret = true;
for (int i = 0; i < len; i++)
{
char c1;
if (i + toff < seq.length())
c1 = seq.charAt(i + toff);
else
{
ret = false;
break;
}
char c2;
if (i + ooff < other.length())
c2 = other.charAt(i + ooff);
else
{
ret = false;
break;
}
if (c1 != c2)
{
ret = false;
break;
}
}
return ret;
} //}}}
//{{{ startsWith() method
/**
* Implementation of String.startsWith() for CharSequence.
*
* @param seq The CharSequence.
* @param str String to test.
* @return Whether the sequence starts with the test string.
*
* @since jEdit 4.3pre15
*/
public static boolean startsWith(CharSequence seq, String str)
{
boolean ret = true;
for (int i = 0; i < str.length(); i++)
{
if (i >= seq.length() ||
seq.charAt(i) != str.charAt(i))
{
ret = false;
break;
}
}
return ret;
} //}}}
//{{{ getBoolean() method
/**
* Returns a boolean from a given object.
* @param obj the object
* @param def The default value
* @return the boolean value if obj is a Boolean,
* true if the value is "true", "yes", "on",
* false if the value is "false", "no", "off"
* def if the value is null or anything else
* @since jEdit 4.3pre17
*/
public static boolean getBoolean(Object obj, boolean def)
{
if(obj == null)
return def;
else if(obj instanceof Boolean)
return ((Boolean)obj).booleanValue();
else if("true".equals(obj) || "yes".equals(obj)
|| "on".equals(obj))
return true;
else if("false".equals(obj) || "no".equals(obj)
|| "off".equals(obj))
return false;
return def;
} //}}}
//{{{ formatFileSize() method
public static final DecimalFormat KB_FORMAT = new DecimalFormat("#.# kB");
public static final DecimalFormat MB_FORMAT = new DecimalFormat("#.# MB");
/**
* Formats the given file size into a nice string (123 Bytes, 10.6 kB,
* 1.2 MB).
* @param length The size
* @since jEdit 4.4pre1
*/
public static String formatFileSize(long length)
{
if(length < 1024)
{
return length + " Bytes";
}
else if(length < 1024 << 10)
{
return KB_FORMAT.format((double)length / 1024);
}
else
{
return MB_FORMAT.format((double)length / 1024 / 1024);
}
} //}}}
private StandardUtilities(){}
// {{{ MD5 sum method
/**
* Returns the md5sum for given string. Or dummy byte array on error
* Suppress NoSuchAlgorithmException because MD5 algorithm always present in JRE
* @param charSequence Given string
* @return md5 sum of given string
*/
public static byte[] md5(CharSequence charSequence)
{
try
{
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] ba = new byte[2];
for(int i = 0, n = charSequence.length(); i < n; i++)
{
char cp = charSequence.charAt(i);
ba[0] = (byte)(cp & 0xff);
ba[1] = (byte)(cp >> 8 & 0xff);
digest.update(ba);
}
return digest.digest();
}
catch (NoSuchAlgorithmException e)
{
Log.log(Log.ERROR, StandardUtilities.class, "Can't Calculate MD5 hash!", e);
return new byte[1];
}
}
// }}}
}