/*
* (C) Copyright IBM Corp. 2008
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.nio.channels.FileChannel;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import com.ibm.db2.jcc.DB2Diagnosable;
import com.ibm.db2.jcc.DB2Sqlca;
import com.ibm.db2j.GaianTable;
import com.ibm.gaiandb.DataSourcesManager.RDBProvider;
import com.ibm.gaiandb.diags.GDBMessages;
/**
* General utilities class
*
* @author DavidVyvyan
*
*/
public class Util {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2008";
private static final Logger logger = new Logger( "Util", 20 );
static final List<String> EMPTY_LIST = new ArrayList<String>();
static final Set<String> EMPTY_SET = new HashSet<String>();
// Standard VARCHAR lengths
public static final String TSTR = "VARCHAR(20)"; // tiny
public static final String SSTR = "VARCHAR(80)"; // small
public static final String MSTR = "VARCHAR(128)"; // medium
public static final String LSTR = "VARCHAR(2000)"; // large
public static final String VSTR = "VARCHAR(8000)"; // v. large
public static final String XSTR = "VARCHAR(32672)"; // xtra large
public static String[] splitByCommas( String list ) {
return splitByTrimmedDelimiter( list, ',' );
}
public static String[] splitByWhitespace( String list ) {
if ( null == list || 0 == list.length() ) return new String[0];
return list.trim().split("[\\s]+");
}
public static String[] splitByTrimmedDelimiter( String list, char delimiter ) {
if ( null == list || 0 == list.length() ) return new String[0];
// Note: a trailing empty value after the last comma will not appear in the resulting array
return list.trim().split("[\\s]*" + delimiter + "[\\s]*");
}
/**
* Split a string around instances of the delimiter (trimming white space too), but ignore any delmiters nested in quotes or within
* bracketed argument lists, e.g: "a, b + 'g,k', myfunction('bla''h,h''ello',c,d)" -> "a", "b + 'g,k'", "myfunction('bla''h,h''ello',c,d)"
* Also handle any depth of nesting...
*
* @param s String to be split
* @param delimiter delimiter to split it by
* @return
*/
public static String[] splitByTrimmedDelimiterNonNestedInCurvedBracketsOrSingleQuotes( String s, char delimiter ) {
return splitByTrimmedDelimiterNonNestedInBracketsOrQuotesOrComments(s, delimiter, '\'', '(', true, -1, null, null);
}
public static String[] splitByTrimmedDelimiterNonNestedInCurvedBracketsOrDoubleQuotes( String s, char delimiter ) {
return splitByTrimmedDelimiterNonNestedInBracketsOrQuotesOrComments(s, delimiter, '"', '(', true, -1, null, null);
}
public static String[] splitByTrimmedDelimiterNonNestedInCurvedBracketsOrQuotes( String s, char delimiter ) {
return splitByTrimmedDelimiterNonNestedInBracketsOrQuotesOrComments(s, delimiter, '\0', '(', true, -1, null, null);
}
public static String[] splitByTrimmedDelimiterNonNestedInSquareBracketsOrDoubleQuotes( String s, char delimiter ) {
return splitByTrimmedDelimiterNonNestedInBracketsOrQuotesOrComments(s, delimiter, '"', '[', true, -1, null, null);
}
// Splits by the delimiters that are not nested in '()' or both single or double quotes. (Implementation uses '\0' to test *both* quote chars)
public static String[] splitByTrimmedDelimiterNonNestedInCurvedBracketsOrQuotes(
String s, char delimiter, boolean includeEmptyElmts, int maxElmts, String cmtStart, String cmtEnd ) {
return splitByTrimmedDelimiterNonNestedInBracketsOrQuotesOrComments(s, delimiter, '\0', '(', includeEmptyElmts, maxElmts, cmtStart, cmtEnd);
}
private static String[] splitByTrimmedDelimiterNonNestedInBracketsOrQuotesOrComments(
final String s, final char delimiter, final char quoteChar, char bracketChar,
final boolean includeIntermediaryEmptyElmts, final int maxElmts, String cmtStart, String cmtEnd ) {
if ( null == s || 0 == s.length() || 0 == maxElmts ) return new String[0];
char closingBracketChar;
switch ( bracketChar ) {
default: bracketChar='(';
case '(': closingBracketChar = ')'; break; case '[': closingBracketChar = ']'; break;
case '{': closingBracketChar = '}'; break; case '<': closingBracketChar = '>'; break;
}
ArrayList<String> results = new ArrayList<String>();
int slen = s.length();
int bracketNestingDepth = 0;
int start = 0, elmtCount = 0;
boolean isInQuotedExpression = false;
int consecutiveQuoteCount = 0;
char currentQuoteChar = quoteChar;
if ( null == cmtStart || null == cmtEnd ) cmtStart = cmtEnd = null;
final int cslen = null == cmtStart ? -1 : cmtStart.length(), celen = null == cmtEnd ? -1 :cmtEnd.length();
boolean isInComment = false;
for ( int i=0; i<slen; i++ ) {
char c = s.charAt(i);
if ( isInComment ) {
// Check for end of a comment
if ( i >= celen-1 ) {
isInComment = false;
for ( int k=0; k<celen; k++ )
if ( cmtEnd.charAt(k) != s.charAt(i-celen+k+1) ) { isInComment = true; break; }
if ( isInComment ) continue;
}
}
// Not in a comment - look for nesting inside quotes..
if ( '\0' == quoteChar && '\0' == currentQuoteChar && ('\''==c||'"'==c) ) currentQuoteChar = c;
// if ( c == currentQuoteChar ) System.out.println("Hit quote at end of: " + (1<i?s.charAt(i-2):"") + (0<i?s.charAt(i-1):"") + c);
if ( currentQuoteChar == c ) {
// Only start counting consecutive quotes once we are IN the quoted expression
if ( isInQuotedExpression ) consecutiveQuoteCount++;
else isInQuotedExpression = true;
continue;
}
// Test if we have been in a quoted expression
if ( isInQuotedExpression ) {
if ( 0 == consecutiveQuoteCount ) continue;
// An uneven number of quotes marks the end of a nested quoted expression
isInQuotedExpression = 0 == consecutiveQuoteCount%2;
consecutiveQuoteCount = 0;
if ( isInQuotedExpression ) continue;
}
if ( '\0' == quoteChar ) currentQuoteChar = '\0';
if ( bracketChar == c ) bracketNestingDepth++;
else if ( closingBracketChar == c && 0 < bracketNestingDepth ) bracketNestingDepth--;
if ( 0 < bracketNestingDepth ) continue;
// Check if this is the start of a comment
if ( null != cmtStart && i >= cslen-1 ) {
isInComment = true;
for ( int k=0; k<cslen; k++ )
if ( cmtStart.charAt(k) != s.charAt(i-cslen+k+1) ) { isInComment = false; break; }
if ( isInComment ) continue;
}
// Check if we hit a delimiter char
// if ( c == delimiter ) System.out.println("Reached delimiter, with bnd: " + bracketNestingDepth + ", entry: " + s.substring(start, i).trim());
if ( c == delimiter ) {
final String entry = s.substring(start, i).trim();
if ( includeIntermediaryEmptyElmts || 0 < entry.length() ) {
results.add( entry );
if ( 0 < maxElmts && ++elmtCount == maxElmts ) { slen = 0; break; }
}
start = i+1;
continue;
}
}
// Always add the last string - even if empty - (as the above code only deals with the strings having a delimiter at the end)
if ( 0 < slen ) results.add( s.substring(start).trim() );
return (String[]) results.toArray(new String[0]);
}
public static boolean isWhiteSpaceChar( char c ) {
// In Pattern.java, whitespace is defined as one of: [ \t\n\x0B\f\r]
// Note '\013' is the Octal value for unicode '\u000B' (i.e. \x0B), which is the vertical tab.
switch (c) { case ' ': case '\t': case '\n': case '\013': case '\f': case '\r': return true; default: return false; }
}
public static String intArrayAsString(int[] a) {
if ( null==a ) return null; int len = a.length;
StringBuffer pcs = new StringBuffer( 0<len ? "[" + a[0] : "[" );
for (int i=1; i<len; i++) pcs.append( ", " + a[i] ); pcs.append(']');
return pcs.toString();
}
public static String longArrayAsString(long[] a) {
if ( null==a ) return null; int len = a.length;
StringBuffer pcs = new StringBuffer( 0<len ? "[" + a[0] : "[" );
for (int i=1; i<len; i++) pcs.append( ", " + a[i] ); pcs.append(']');
return pcs.toString();
}
public static String boolArrayAsString(boolean[] a) {
if ( null==a ) return null; int len = a.length;
StringBuffer pcs = new StringBuffer( 0<len ? "[" + a[0] : "[" );
for (int i=1; i<len; i++) pcs.append( ", " + a[i] ); pcs.append(']');
return pcs.toString();
}
public static String stringArrayAsCSV(String[] a) {
return stringArrayAsCSV(a, null);
}
public static String stringArrayAsCSV(String[] a, RDBProvider rdbmsProvider) {
if ( null==a ) return null; int len = a.length;
StringBuffer pcs = new StringBuffer( 1>len ? "" :
null == rdbmsProvider ? a[0] : GaianResultSetMetaData.wrapColumnNameForQueryingIfNotAnOrdinaryIdentifier(a[0], rdbmsProvider) );
for (int i=1; i<len; i++) pcs.append( ", " +
( null == rdbmsProvider ? a[i] : GaianResultSetMetaData.wrapColumnNameForQueryingIfNotAnOrdinaryIdentifier(a[i], rdbmsProvider) ) );
return pcs.toString();
}
public static final void copyBinaryData( InputStream is, OutputStream os ) throws IOException {
BufferedInputStream bis = new BufferedInputStream( is );
BufferedOutputStream bos = new BufferedOutputStream( os );
final int BUFFER_SIZE = 1<<10<<3; // 8K buffer
byte[] buffer = new byte[BUFFER_SIZE];
int numBytes = -1;
while ((numBytes = bis.read(buffer)) > -1)
bos.write(buffer, 0, numBytes);
bis.close(); is.close();
bos.close(); os.close();
}
public static byte[] getFileBytes( File f ) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Util.copyBinaryData(new FileInputStream(f), baos);
return baos.toByteArray();
}
public static String byteArray2HexString(byte[] b, boolean format) {
if ( null == b ) return null;
if ( 0 == b.length ) return "";
String hex = Integer.toString( ( b[0] & 0xff ) + 0x100, 16).substring( 1 );
for (int i=1; i < b.length; i++)
hex += (format?"-":"") + Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
return hex;
}
public static String getFileMD5( String path ) {
return getFileMD5(new File(path));
}
public static String getFileMD5( File file ) {
try { return byteArray2HexString( SecurityManager.getChecksumMD5( Util.getFileBytes( file ) ), false ).toUpperCase(); }
catch ( Exception e ) { return "Unknown"; }
}
public static int intArrayContains(int[] a, int v) {
for ( int i=0; i<a.length; i++ )
if ( a[i] == v ) return i;
return -1;
}
public static boolean stringsContainCommonChars( String a, String b ) {
if ( null == a || null == b ) return false;
for ( char c1 : a.toCharArray() )
for ( char c2 : b.toCharArray() )
if ( c1 == c2 ) return true;
return false;
}
public static <T> Set<T> setDisjunction( Collection<T> a, Collection<T> b ) {
Set<T> c = new HashSet<T>(a);
c.addAll(b);
for( Iterator<T> i = c.iterator(); i.hasNext(); ) {
T o = i.next();
if ( a.contains(o) && b.contains(o) ) i.remove();
}
return c;
}
public static String stripSingleQuotesDownOneNestingLevel( String s ) {
return stripEscapeCharacterDownOneNestingLevel(s, '\'');
}
public static String stripBackSlashesDownOneNestingLevel( String s ) {
return stripEscapeCharacterDownOneNestingLevel(s, '\\');
}
public static String stripEscapeCharacterDownOneNestingLevel( String s, char escapeChar ) {
if ( null == s ) return null;
boolean isEscaped = false;
StringBuffer sb = new StringBuffer();
for ( int i=0; i<s.length(); i++ ) {
char c = s.charAt(i);
if ( escapeChar == c ) {
isEscaped = !isEscaped;
if ( isEscaped ) continue;
} else
isEscaped = false;
sb.append(c);
}
return sb.toString();
}
public static String escapeSingleQuotes( String s ) { return escapeCharactersByDoublingThem(s, '\''); }
public static String escapeDoubleQuotes( String s ) { return escapeCharactersByDoublingThem(s, '"'); }
public static String escapeCharactersByDoublingThem( String s, char escapeChar ) {
String escapeCharDoubled = escapeChar + "" + escapeChar;
if ( null == s ) return null;
String[] strs = s.split(escapeChar+"", -1); // use -1 to split even off a trailing quote
StringBuffer sb = new StringBuffer( strs[0] );
for (int i=1; i<strs.length; i++)
sb.append( escapeCharDoubled + strs[i] );
return sb.toString();
}
// public static String escapeBackslashes( String s ) {
//
// if ( null == s ) return null;
// // 4 backslashes needed, 2 to escape the regex backslash, and 2 more to escape the java preprocessing.
// String[] strs = s.split("\\\\", -1); // use -1 to split even off a trailing quote
// StringBuffer sb = new StringBuffer( strs[0] );
// for (int i=1; i<strs.length; i++)
// sb.append( "\\\\" + strs[i] ); // 4 backslashes to insert 2
//
// return sb.toString();
// }
public static String escapeBackslashes( final String s ) {
if ( null == s ) return null;
StringBuilder sb = new StringBuilder(s);
for ( int i = 0; i < sb.length(); i++ )
if ( '\\' == sb.charAt(i) ) sb.insert(i++, '\\');
return sb.toString();
}
public static String getExceptionAsString( Throwable e ) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.close();
return "\n\t"+sw.toString();
}
public static String getAllExceptionCauses( Throwable e ) {
Set<String> errors = new HashSet<String>();
StringBuffer sb = new StringBuffer();
Throwable cause = e;
while (true) {
cause = cause.getCause();
if (null != cause) {
if (!errors.contains(cause.getMessage())) {
errors.add(cause.getMessage());
sb.append(cause.getMessage() + "\n");
}
}
else {
break;
}
}
return sb.toString();
}
public static String getStringWithLongestMatchingPrefix( String a, Set<String> candidates ) {
String match = null;
int currentml = 0;
for ( String b : candidates ) {
int ml = getMatchingStringLength(a, b);
if ( currentml < ml ) {
currentml = ml;
match = b;
}
}
return match;
}
public static int getMatchingStringLength( String a, String b ) {
if ( null == a || null == b ) return 0;
int alen = a.length(), blen = b.length();
for ( int i=0; i<alen; i++ ) {
if ( blen == i ) return blen;
if ( a.charAt(i) != b.charAt(i) ) return i;
}
return alen;
}
public static Set<InetAddress> getAllMyHostAdresses() throws Exception {
Set<InetAddress> addresses = new HashSet<InetAddress>();
Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
while ( en.hasMoreElements() ) {
Enumeration<InetAddress> ias = en.nextElement().getInetAddresses();
while( ias.hasMoreElements() )
addresses.add( ias.nextElement() );
}
return addresses;
}
public static Set<InetAddress> getAllMyHostIPV4Adresses() throws Exception {
Set<InetAddress> addresses = new HashSet<InetAddress>();
Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
while ( en.hasMoreElements() ) {
Enumeration<InetAddress> ias = en.nextElement().getInetAddresses();
while( ias.hasMoreElements() ) {
InetAddress address = ias.nextElement();
if ( isIPv4( stripToSlash( address.toString() ) ) ) addresses.add( address );
}
}
return addresses;
}
public static boolean isIPv4(String ip) { return ip.matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"); }
public static String stripToSlash( String s ) { int idx = s.indexOf('/'); return -1 == idx ? s : s.substring( idx+1 ); }
public static class NetInfo {
private final String hostname;
private final List<String> interfaceIDs = new ArrayList<String>(), interfaceInfos = new ArrayList<String>(),
ipv4s = new ArrayList<String>(), broadcasts = new ArrayList<String>();
private final List<Integer> netPrefixLengths = new ArrayList<Integer>();
public NetInfo() throws Exception {
hostname = InetAddress.getLocalHost().getHostName();
Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
while ( en.hasMoreElements() ) {
NetworkInterface ni = en.nextElement();
if (GaianNode.isJavaVersion6OrMore) {
for ( InterfaceAddress ifa : ni.getInterfaceAddresses() ) {
String ip = stripToSlash( ifa.getAddress().toString() );
if ( isIPv4(ip) ) {
Integer npl = new Integer( ifa.getNetworkPrefixLength() );
InetAddress bcastAddress = ifa.getBroadcast();
String bcast = null == bcastAddress ? null : stripToSlash( bcastAddress.toString() );
// // Do some checking -> corrected: don't remove broadcast IPs whose prefix doesn't match the IP prefix - these can still be valid.
// if ( null != bcast && 8 <= npl && false == ip.substring(0, ip.indexOf('.')).equals( bcast.substring(0, bcast.indexOf('.')) ) ) {
// logger.logInfo("Detected invalid broadcast entry in Java net info (doesn't match ip prefix - setting broadcast to null). IP: " + ip
// + ", Broadcast: " + bcast + ", netPrefixLength: " + npl);
// bcast = null;
// }
interfaceIDs.add( ni.getName() ); // repeat interface props for different IPs on it.
interfaceInfos.add( ni.getDisplayName() ); // repeat interface props for different IPs on it.
ipv4s.add(ip);
broadcasts.add( bcast );
netPrefixLengths.add( npl );
// System.out.println("New ni ip: " + ni.getName() + " -> " + ip + " -> " + bcast + "->" + ifa.getNetworkPrefixLength());
}
}
} else {
Enumeration<InetAddress> ias = ni.getInetAddresses();
while( ias.hasMoreElements() ) {
String ip = stripToSlash( ias.nextElement().toString() );
if ( isIPv4(ip) ) {
interfaceIDs.add( ni.getName() ); // repeat interface props for different IPs on it.
interfaceInfos.add( ni.getDisplayName() ); // repeat interface props for different IPs on it.
ipv4s.add(ip);
broadcasts.add( null );
netPrefixLengths.add( null );
}
}
}
}
// System.out.println("Built NetInfo() with numelmts: " + ipv4s.size());
}
List<String> getAllBroadcastIPs() {
if ( GaianNode.isJavaVersion6OrMore ) return broadcasts;
ArrayList<String> bcastOptions = new ArrayList<String>();
for ( String ip : ipv4s ) bcastOptions.addAll( Util.deduceAllPossibleBroadcastIPs(ip) );
return bcastOptions;
}
public Object[] getInfoForClosestMatchingIP( String ip ) throws Exception {
int i = 0;
String address = Util.getStringWithLongestMatchingPrefix(
null==ip || 0==ip.length() ? GaianNodeSeeker.getDefaultLocalIP() : ip,
new HashSet<String>( ipv4s ) );
for ( int k=0; k<ipv4s.size(); k++ )
if ( ipv4s.get(k).equals(address) )
{ i = k; break; }
return new Object[] { hostname, interfaceIDs.get(i), interfaceInfos.get(i),
ipv4s.get(i), broadcasts.get(i), netPrefixLengths.get(i) };
}
// public String getAllInfoAsValuesString() {
// if ( 1 > ipv4s.size() ) return null;
// StringBuffer sb = new StringBuffer();
// for ( int i=0; i<ipv4s.size(); i++ )
// sb.append("('"+hostname+"','"+interfaceIDs.get(i)+"','"+interfaceInfos.get(i)+"','"+ipv4s.get(i)+"','"+broadcasts.get(i)+"',"+netPrefixLengths.get(i)+"),");
// sb.deleteCharAt(sb.length()-1);
// return sb.toString();
// }
public List<String> getAllInterfaceInfoAsListOfRowsWithAliasedColumnsForIPsPrefixed( String ipPrefix ) {
if ( 1 > ipv4s.size() ) return null;
List<String> rows = new ArrayList<String>();
for ( int i=0; i<ipv4s.size(); i++ )
if ( null == ipPrefix || ipv4s.get(i).startsWith(ipPrefix) ) {
String bcastXpr = broadcasts.get(i);
bcastXpr = null == bcastXpr ? "CAST(NULL AS " + TSTR + ")" : "'" + bcastXpr + "'";
rows.add("'"+hostname+"' hostname,'"+interfaceIDs.get(i)+"' interface,'"+interfaceInfos.get(i)+"' description,'"
+ipv4s.get(i)+"' ipv4,"+bcastXpr+" broadcast,CAST("+netPrefixLengths.get(i)+" as INT) NetPrefixLength");
}
return rows;
}
}
public static List<String> getAllCurrentBroadcastIPs() throws Exception {
return new NetInfo().getAllBroadcastIPs();
}
public static ArrayList<String> deduceAllPossibleBroadcastIPs( String ip ) {
ArrayList<String> bips = new ArrayList<String>();
String[] ipBytes = ip.split("\\.");
if ( 4 > ipBytes.length ) return null;
for ( int i=3; i>0; i-- ) {
for ( String s : getAllRightMaskingCombinations(padString(dec2bin(Integer.parseInt(ipBytes[i])), '0', 8)) ) {
ipBytes[i] = bin2dec(s) + "";
bips.add(ipBytes[0] + "." + ipBytes[1] + "." + ipBytes[2] + "." + ipBytes[3]);
}
ipBytes[i] = "255";
}
return bips;
}
private static List<String> getAllRightMaskingCombinations( String bin ) {
ArrayList<String> cbs = new ArrayList<String>();
StringBuffer sb = new StringBuffer(bin);
while( true ) {
int idx = sb.lastIndexOf("0");
if ( -1 == idx ) break;
sb.replace(idx, idx+1, "1");
cbs.add(sb.toString());
}
return cbs;
}
private static String padString( String s, char c, int toSize ) {
int padCount = toSize - s.length();
if ( 1 > padCount ) return s;
StringBuffer sb = new StringBuffer();
while ( padCount-- > 0 ) sb.append('0');
return sb.toString() + s;
}
private static int bin2dec( String bin ) {
int dec=0, n = 1;
for ( int i=bin.length()-1; i>=0; i-- ) {
dec += n * ( '1' == bin.charAt(i) ? 1 : 0 );
n <<= 1;
}
return dec;
}
private static String dec2bin( int dec ) {
if ( 0 == dec ) return "0";
StringBuffer sb = new StringBuffer();
while ( dec != 0 ) {
sb.append( 1==dec%2 ? 1 : 0 );
dec >>= 1;
}
return sb.reverse().toString();
}
/**
* This method tells us if a JDBC connection appears to be open and therefore capable of servicing queries.
* However it does not detect a connection to be invalid if a query against it hangs - unlike the DatabaseConnectionChecker - because it doesn't have the
* capability to determine that it is not a legitimate long-running query.
*
* A hang can occur for example when a Host OS of a connected node dies.
* Subsequent TCP socket receive() methods can then fail to detect the error condition - hence why it waits indefinitely thereafter.
*
* For this scenario, the faulty hanging Connection is only rooted out (and its connection pool cleared) if a data-source query runs against it.
* This is done is via GaianResult: When the data-source query takes too long to respond, it invokes DatabaseConnectionsChecker.rootOutHangingDataSources().
* This runs a very simple SQL "values 1" statement, and waits for a response within a configurable time defined using
* property GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS, which should be tailored to the physical network latency.
*
* At the moment the checker is tailored/limited to check connections held by data-source wrappers, and then abort data-source wrapper queries as a result.
* However in future one may consider re-factoring the checker so it can be called from here just for checking simple connections. This would allow us to clear
* connection pools sooner and remove some purging calls for connection pools in DataSourcesManager.
*
* @param c The Connection to be checked.
* @return true is the Connection appears to be valid. false if connection.isClosed() is true after attempting to run a simple query.
*/
public static final boolean isJDBCConnectionValid( final Connection c ) {
final AtomicInteger isValid = new AtomicInteger( -1 ); // -1 = not checked yet; 0 = closed; 1 = valid (i.e. working)
// Check connection in a separate thread in case the query hangs
new Thread("Connection validator for JDBC Connection object: " + c) {
public void run() {
// Test-mode simulated hang behaviour - ignore unless running tests.
if ( GaianNode.isInTestMode() && GaianDBConfigProcedures.internalDiags.containsKey("hang_on_maintenance_poll") ) {
GaianDBConfigProcedures.internalDiags.remove("hang_on_maintenance_poll"); // disable this straight away
logger.logInfo("Simulating 10s hang on maintenance poll against JDBC connection using Thread.sleep(10000)");
try { Thread.sleep(10000); } catch (InterruptedException e) {}
logger.logInfo("Simulated hang on maintenance poll completed");
}
Statement stmt = null;
try { stmt = c.createStatement(); stmt.execute("values 'dummy sql to check jdbc connection'"); }
catch (Exception e) {} finally { try { if ( null != stmt ) stmt.close(); } catch ( Exception e1 ) {} }
// Note there is no standard dummy statement hence the one above, but we'll still know better if the connection is closed after running it...
try { isValid.set( c.isClosed() ? 0 : 1 ); } catch (Exception e) {}
synchronized( isValid ) { isValid.notify(); }
}
}.start();
synchronized ( isValid ) {
try { if ( -1 == isValid.get() ) isValid.wait( 200 ); }
catch( InterruptedException e ) {}
return 0 != isValid.get(); // Assume the connection is still valid if the statement does not return in 200ms (e.g. high latency networks).
}
}
public static String getDerbyDatabaseProperty( Statement derbyStatementForQuerying, String propertyKey ) throws SQLException {
ResultSet rs = derbyStatementForQuerying.executeQuery("VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY('" + propertyKey + "')");
try { return rs.next() ? rs.getString(1) : null; }
finally { rs.close(); }
}
public static void executeCreateIfDerbyTableDoesNotExist( Statement stmt, String schema, String table, String createTableSQL ) throws SQLException {
if ( false == Util.isExistsDerbyTable( stmt.getConnection(), schema, table ) )
stmt.execute( createTableSQL );
}
public static boolean isExistsDerbyTable( Connection derbyConnection, String schema, String table ) throws SQLException {
ResultSet rs = derbyConnection.getMetaData().getTables(null, schema, table, null);
boolean isTableExists = rs.next();
// Must close ResultSet - vital step - otherwise we can hit db locks
// seen with: vti.TestICARESTSelfSameQueryJoinsCached.testDistributedSearchSelfSameJoinRestrictedFetchSizeAgainstOtherNode
rs.close();
return isTableExists;
}
public static String getStackTraceElementDigest( StackTraceElement se ) {
// String className = stackTraceTranslate.getOldClassName( se.getClassName() );
String className = se.getClassName();
String shortClassName = className.substring( className.lastIndexOf('.')+1 );
// return shortClassName + "." + stackTraceTranslate.getOldMethodSignatures( className, se.getMethodName() ) +
return shortClassName + "." + se.getMethodName() + ":" + se.getLineNumber();
}
// Methods to get Call Stack Trace information ...
public static String getStackTraceDigest() { return getStackTraceDigest( -1, -1 ); }
/**
* Get stack info, starting at a given depth (so depth=0 would return nothing) and extracting a given count of elements upwards from there.
* e.g: "getStackTraceDigest(4, 3)" would extract 3 stack trace lines from depth 4, missing out just 1 line from the top of the trace.
*
* @param depth
* @param count
* @return
*/
public static String getStackTraceDigest( int depth, int count ) {
// Don't use Thread.currentThread().getStackTrace() as it extracts unhelpful deeper "getStackTrace() -> getStackTraceImpl()" method calls
return getStackTraceDigest(new Exception(), depth, count);
}
public static String getStackTraceDigest( Throwable e ) { return getStackTraceDigest( e, -1, -1, true ); }
public static String getStackTraceDigest( Throwable e, int depth, int count, boolean printExMessage ) {
return 0 == depth ? "" : (printExMessage ? e+"\n\t=> TRACE: " : "") + getStackTraceDigest(e, depth, count);
}
// depth to start working back from (i.e. 0 would return nothing), count is the number of elements to extract upwards from that position.
public static String getStackTraceDigest( Throwable e, final int depthIn, final int countIn ) {
int depth = depthIn, count = countIn;
StackTraceElement[] ses = e.getStackTrace();
if ( 0 == depth ) return "";
StringBuffer chain = new StringBuffer();
if ( depth < 1 || depth > ses.length ) depth = ses.length;
for (int i=depth-1; i>0 && 0!=count; i--, count--)
chain.append( (i<depth-1?" -> ":"") + getStackTraceElementDigest( ses[i] ) );
if ( 0 != count )
chain.append( (1<depth?" -> ":"") + getStackTraceElementDigest( ses[0] ) );
Throwable eCause = e.getCause();
if ( null != eCause && -1 == depthIn && -1 == countIn )
chain.append( "\n\t=> CAUSED BY: " + getStackTraceDigest( eCause, depthIn, countIn ) );
return chain.toString();
}
public static String getGaiandbInvocationTargetException( Throwable e ) {
Throwable cause = e;
while ( null != ( cause = cause.getCause() ) ) {
String msg = cause.getMessage();
if ( null != msg && -1 < msg.indexOf( GaianTable.IEX_PREFIX ) )
return msg;
}
return null;
}
// isWindowsOS if the os.name sys property is not null and starts with "WINDOWS" in any case
public static final boolean isWindowsOS = (System.getProperty("os.name")+"").toUpperCase().startsWith("WINDOWS");
private static final String UNRESOLVED_PATH = "<UNRESOLVED_PATH>".intern();
private static String BYTECODE_PATH = UNRESOLVED_PATH;
private static String INSTALL_PATH = UNRESOLVED_PATH;
static String getBytecodeLocation() {
// Alternative methods to get install path...
// String[] eps = new String[10];
// try { eps[0] = System.getProperty("user.dir"); } catch (Exception e) { eps[0] = e.toString(); } // This is the execution dir - may not always be the install dir!
// try { eps[1] = System.getenv("GDBH"); } catch (Exception e) { eps[0] = e.toString(); } // This may not be the install dir if it set in a customer script (which runs launchGaianServer independently)
// try { eps[2] = Util.class.getProtectionDomain().getCodeSource().getLocation().toString(); } catch (Exception e) { eps[0] = e.toString(); } // gets URL rather than URI path
// try { eps[3] = ClassLoader.getSystemClassLoader().getResource(GaianNode.class.getName().replace('.', '/')).getPath(); } catch (Exception e) { eps[0] = e.toString(); } // sometimes throws exception...
// try { eps[4] = Util.class.getResource("GAIANDB.jar").getPath(); } catch (Exception e) { eps[0] = e.toString(); } // sometimes throws exception...
// try { eps[5] = Util.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); } catch (Exception e) { eps[0] = e.toString(); } // this is the one we use
// int i=0; for ( String ep : eps ) logger.logAlways("eps[" + (i++) + "] = " + ep);
try {
return !(UNRESOLVED_PATH.equals(BYTECODE_PATH)) ? BYTECODE_PATH :
( BYTECODE_PATH = Util.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath() );
} catch (Throwable e) { return UNRESOLVED_PATH; } // should never happen
}
// static String getBytecodeLocation() {
// try {
// if ( UNRESOLVED_PATH == BYTECODE_PATH ) {
// String bytecodePath = Util.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
// BYTECODE_PATH = bytecodePath.endsWith(".jar") ? bytecodePath.substring( 0, bytecodePath.lastIndexOf( '/' ) ) : bytecodePath;
// }
//
// } catch (URISyntaxException e) { return BYTECODE_PATH = null; } // should never happen
//
// return BYTECODE_PATH;
// }
public static String getInstallPath() {
if ( UNRESOLVED_PATH.equals(INSTALL_PATH) ) {
try {
String bytecodePath = getBytecodeLocation();
// System.out.println("installPath: " + installPath);
String libDir = -1 == bytecodePath.lastIndexOf("/lib/") ? "/build/" : "/lib/";
// Strip the jar file name and a preceding "/lib/" substring, or everything past the "/bin" substring (when running from eclipse)
int idx = bytecodePath.lastIndexOf( bytecodePath.endsWith(".jar") ? libDir : "/bin" );
if ( -1 != idx ) {
INSTALL_PATH = bytecodePath.substring(0, idx);
// Class path comes with special characters, but they ruin file search
// path = path.replaceAll("%20"," ");
if (isWindowsOS) while ('/' == INSTALL_PATH.charAt(0)) INSTALL_PATH = INSTALL_PATH.substring(1);
}
else throw new Exception("Cannot find '/lib/' or '/build/' or '/bin' in bytecode path: " + bytecodePath);
}
catch (Exception e) {
logger.logWarning(GDBMessages.NODE_UNRESOLVED_INSTALL_PATH, "GaianDB was unable to resolve install path: " + e);
INSTALL_PATH = null;
}
}
return INSTALL_PATH;
}
public static void moveFileAndLogOutcome(File fromFile, File toFile) throws Exception {
final String fromFilePath = fromFile.getPath(), toFileName = toFile.getName();
toFile.setWritable(true);
// First, try to just rename the file
if ( false == fromFile.renameTo(toFile) ) {
logger.logInfo("Unable to rename file: " + fromFilePath + " to " + toFileName + "; attempting to delete target first...");
String errMessage = null;
// Try deleting the file and renaming after
if ( false == toFile.delete() )
errMessage = "Unable to delete file: " + toFileName + "; attempting to copy to it directly...";
else if ( false == fromFile.renameTo(toFile) ) // fails (on windows) if 'fromFile' still has open file descriptors
errMessage = "Unable to rename file (after target deleted) from " + fromFilePath + " to " + toFileName + "; attempting to copy it directly...";
else
logger.logInfo("Successfully renamed file (after target deleted) from " + fromFilePath + " to " + toFileName);
if ( null != errMessage ) {
logger.logInfo(errMessage);
try { Util.copyFile(fromFile, toFile); }
catch (IOException ioe) { throw new Exception("Unable to copy file " + fromFilePath + " to " + toFileName); }
logger.logInfo("Successfully copied file " + fromFilePath + " to " + toFileName);
// Only delete tmp config file after a successful copy (no need after a rename!)
if ( false == fromFile.delete() )
logger.logInfo("Unable to delete lingering temp file (after successful copy): " + fromFilePath);
}
} else
logger.logInfo("Successfully renamed file (without target deletion) from " + fromFilePath + " to " + toFileName);
}
public static void moveFile(File fromFile, File toFile) throws Exception {
// final String fromFilePath = fromFile.getPath(), toFilePath = toFile.getPath(), toFileName = toFile.getName();
//
// toFile.setWritable(true);
//
// // Try to rename the file directly
// if ( fromFile.renameTo(toFile) ) return;
//
// // Try to delete 'toFile' first and try a rename again after. This may fail if 'fromFile' has open file descriptors...
// // First back-up 'toFile'
// File toFileBackup = new File(toFilePath + ".backup");
// try { Util.copyFile(toFile, toFileBackup); }
// catch (IOException ioe) { throw new Exception("Unable to backup/copy file " + toFilePath + " to " + toFilePath+".bak"); }
//
// if ( true == toFile.delete() && fromFile.renameTo(toFile) ) return;
//
final String fromFilePath = fromFile.getPath(), toFileName = toFile.getName();
toFile.setWritable(true);
// Try to rename the file directly; OR try to delete 'toFile' first and try a rename again after. This may fail if 'fromFile' has open file descriptors...
if ( fromFile.renameTo(toFile) || ( true == toFile.delete() && fromFile.renameTo(toFile) ) ) return;
// Last resort: Do a hard copy of the file.
try { Util.copyFile(fromFile, toFile); }
catch (IOException ioe) { throw new Exception("Unable to copy file " + fromFilePath + " to " + toFileName); }
// Only delete tmp config file after a successful copy (no need after a rename!)
fromFile.delete(); // may not succeed...
}
public static boolean copyFile(String fromFileName, String toFileName) throws IOException {
return copyFile(new File(fromFileName), new File(toFileName));
}
public static boolean copyFile(File fromFile, File toFile) throws IOException {
// System.out.println("Testing if files exist. "
// + "fromFile: " + fromFile.getCanonicalPath() + ": " + fromFile.exists()
// + "; toFile: " + toFile.getCanonicalPath() + ": " + toFile.exists()
// );
if ( !fromFile.exists() ) return false;
if ( !toFile.exists() ) toFile.createNewFile();
FileChannel fromChannel = null, toChannel = null;
try {
fromChannel = new FileInputStream(fromFile).getChannel();
toChannel = new FileOutputStream(toFile).getChannel();
toChannel.transferFrom(fromChannel, 0, fromChannel.size());
return true;
} finally {
if ( null != fromChannel ) fromChannel.close();
if ( null != toChannel ) toChannel.close();
}
}
public static String getDB2Msg(SQLException sqle, boolean complete )
{
try { Util.class.getClassLoader().loadClass(DB2Sqlca.class.getName()); }
catch ( Throwable e ) { return null; } //DB2 class DB2Sqlca cannot be loaded"; }
StringBuilder msg = new StringBuilder();
while(sqle != null) { // Check whether there are more
// SQLExceptions to process
//=====> Optional DB2-only error processing
if (sqle instanceof DB2Diagnosable) { // Check if DB2-only information exists
com.ibm.db2.jcc.DB2Diagnosable diagnosable =(com.ibm.db2.jcc.DB2Diagnosable)sqle;
//diagnosable.printTrace (printWriter, "");
java.lang.Throwable throwable = diagnosable.getThrowable();
if (throwable != null) {
// Extract java.lang.Throwable information
// such as message or stack trace.
//TODO
}
DB2Sqlca sqlca = diagnosable.getSqlca();
if (sqlca != null) { // Check that DB2Sqlca is not null
int sqlCode = sqlca.getSqlCode(); // Get the SQL error code
String sqlErrmc = sqlca.getSqlErrmc(); // Get the entire SQLERRMC
String[] sqlErrmcTokens = sqlca.getSqlErrmcTokens();
// You can also retrieve the individual SQLERRMC tokens
String sqlErrp = sqlca.getSqlErrp(); // Get the SQLERRP
int[] sqlErrd = sqlca.getSqlErrd(); // Get SQLERRD fields
char[] sqlWarn = sqlca.getSqlWarn(); // Get SQLWARN fields
String sqlState = sqlca.getSqlState(); // Get SQLSTATE
String errMessage=null;
try{
errMessage = sqlca.getMessage(); // Get error message
}catch(Exception ee){ } ;
if (complete)
{
msg.append ("Server error message: " + errMessage);
msg.append ("--------------- SQLCA ---------------");
msg.append ("Error code: " + sqlCode);
msg.append ("SQLERRMC: " + sqlErrmc);
for (int i=0; i< sqlErrmcTokens.length; i++) {
msg.append (" token " + i + ": " + sqlErrmcTokens[i]);
}
msg.append ( "SQLERRP: " + sqlErrp );
msg.append ( "SQLERRD(1): " + sqlErrd[0] + "\n" +
"SQLERRD(2): " + sqlErrd[1] + "\n" +
"SQLERRD(3): " + sqlErrd[2] + "\n" +
"SQLERRD(4): " + sqlErrd[3] + "\n" +
"SQLERRD(5): " + sqlErrd[4] + "\n" +
"SQLERRD(6): " + sqlErrd[5] );
msg.append ( "SQLWARN1: " + sqlWarn[0] + "\n" +
"SQLWARN2: " + sqlWarn[1] + "\n" +
"SQLWARN3: " + sqlWarn[2] + "\n" +
"SQLWARN4: " + sqlWarn[3] + "\n" +
"SQLWARN5: " + sqlWarn[4] + "\n" +
"SQLWARN6: " + sqlWarn[5] + "\n" +
"SQLWARN7: " + sqlWarn[6] + "\n" +
"SQLWARN8: " + sqlWarn[7] + "\n" +
"SQLWARN9: " + sqlWarn[8] + "\n" +
"SQLWARNA: " + sqlWarn[9] );
msg.append ("SQLSTATE: " + sqlState); // portion of SQLException
}
else
{
String SsqlCode=("0000"+(-sqlCode));
msg.append("SQL"+ (SsqlCode.substring(SsqlCode.length()-4 ))+" "+errMessage );
}
}
else { //not diagnosable, but still an exception
if (sqle.getSQLState()!= null )
msg.append("SQLSTATE "+ (sqle.getSQLState())+" "+sqle.getMessage() );
}
}
sqle=sqle.getNextException(); // Retrieve next SQLException
}
return msg.toString();
}
public static int getStringEditDifference(String s1, String s2) {
if ( null == s1 ) return null == s2 ? 0 : s2.length();
else if ( null == s2 ) return null == s1 ? 0 : s1.length();
if ( s1.length() > s2.length() ) { String t = s1; s1 = s2; s2 = t; } // we will create 2 arrays of size equal to the shortest string length
int[] diff = new int[s1.length()+1], next = new int[s1.length()+1]; // create 2 flat arrays
for ( int i=0; i<diff.length; i++ ) diff[i] = i; // initialise char differences (assumes chars will be different at each position)
for ( int i=1; i<s2.length()+1; i++, next[0] = i-1 ) {
for ( int j=1; j<diff.length; j++ )
next[j] = Math.min( Math.min( next[j-1] + 1, diff[j] + 1 ),
diff[j-1] + (s2.charAt(i-1) == s1.charAt(j-1) ? 0 : 1) );
int[] temp = diff; diff = next; next = temp;
}
return diff[diff.length-1];
}
public static int getStringSimilarityPercentage(String s1, String s2) {
int l = getStringEditDifference(s1, s2);
int len = Math.max( null == s1 ? 0 : s1.length(), null == s2 ? 0 : s2.length() );
return l*100 / len;
}
public static boolean stringMatchesWilcardExpression( String text, String expr ) {
if ( null == text || null == expr ) return false;
String[] frags = expr.split("\\*");
if ( 0 < expr.length() && '*' != expr.charAt(0) && false == text.startsWith(frags[0]) ) return false;
if ( 0 < expr.length() && '*' != expr.charAt(expr.length()-1) && false == text.endsWith(frags[frags.length-1]) ) return false;
for ( String frag : frags ) {
// System.out.println("Looking for fragment: '" + frag + "' in suffix string: " + text);
int idx = text.indexOf(frag);
if ( -1 == idx ) return false;
text = text.substring(idx + frag.length());
}
return true;
}
public static String trimConsecutivePathSeparatorsToSingleOnes( String s ) {
final StringBuilder sb = new StringBuilder(s);
char c;
for ( int start = 1, idx = 0; idx < sb.length(); start = idx + 1 ) {
// logger.logThreadDetail( "trimConsecutivePathSeparatorsToSingleOnes() remaining string: " + sb.substring(idx) );
do c = sb.charAt(idx++); while ( isSeparatorChar(c) && idx < sb.length() );
int delta = idx - 1 - start;
if ( delta > 0 ) {
sb.delete(start, start+delta);
idx -= delta; // shift the index back by the number of deleted characters
}
}
return sb.toString();
}
public static boolean isSeparatorChar( char c ) { return '/' == c || isWindowsOS && '\\' == c; } // Note '/' is considered a file separator on all platforms.
public static boolean isAbsolutePath( final String path ) {
int pathLen = path.length();
// NOTE: Do not assume that a Windows path will have a '\' path separator - '/' can also be valid.
return Util.isWindowsOS ? 2 < pathLen && ':' == path.charAt(1) && isSeparatorChar(path.charAt(2))
: 0 < pathLen && '/' == path.charAt(0);
}
/**
* Resolves all file paths matching a given maskedPath.
* The full path is split using the patform separator char ('/' on unix, '\' on windows), and each path element expression is resolved into existing folder or file names.
*
* If 'isMatchByRegex' is false, then only the '*' character will have special meaning as a wildcard matching any character sequence for a given path element.
*
* If 'isMatchByRegex' is true, then each element of the maskedPath (split by the separator char) is treated as a regular expression.
* Therefore when 'isMatchByRegex' is true, the '.' and '..' expressions will no longer interpreted as 'current folder' and 'parent folder',
* but rather 'any character' and 'any 2 characters in sequence'.
*
* @param maskedPath
* @param isMatchByRegex
* @return The full set of file paths matching the maskedPath, either by simple wildcard matching or by regular expression.
*/
public static String[] findFilesTreeMatchingMask( String maskedPath, final boolean isMatchByRegex ) {
// StringBuilder sb = new StringBuilder( regexPath );
//
// for ( int i = 0; i < sb.length() ; i++ ) {
// switch ( sb.charAt(i) ) {
// case '.': sb.insert(i++, '\\'); continue;
// case '*': sb.insert(i++, '.'); continue;
// }
// }
//
// regexPath = sb.toString();
maskedPath = trimConsecutivePathSeparatorsToSingleOnes( maskedPath );
logger.logThreadInfo("Getting matching files for maskedPath: " + maskedPath);
boolean isAbsolutePath = isAbsolutePath(maskedPath);
String parentPrefix;
if ( isAbsolutePath ) {
parentPrefix = maskedPath.substring(0, Util.isWindowsOS ? 3 : 1);
maskedPath = maskedPath.substring(Util.isWindowsOS ? 3 : 1);
} else {
parentPrefix = System.getProperty("user.dir");
if ( false == isSeparatorChar( parentPrefix.charAt( parentPrefix.length()-1 ) ) ) parentPrefix += File.separatorChar;
}
logger.logThreadInfo("Resolved isAbsolutePath? " + isAbsolutePath + ", parentPrefix: " + parentPrefix + ", maskedPath: " + maskedPath);
List<String> matches = findFilesTreeMatchingMask( parentPrefix, maskedPath, new HashSet<String>(), isMatchByRegex );
if ( isAbsolutePath )
return matches.toArray( new String[0] );
// Remove absolute folder locations as they were not in the original mask.
String[] relativeMatches = new String[ matches.size() ];
int prefixLen = parentPrefix.length();
for ( int i=0; i<relativeMatches.length; i++ )
relativeMatches[i] = matches.get(i).substring(prefixLen);
return relativeMatches;
}
public static int indexOfFileSeparator( String s ) {
// Note '/' is also valid on windows
int idx1 = s.indexOf( '/' );
if ( false == isWindowsOS ) return idx1;
int idx2 = s.indexOf( '\\' );
if ( -1 == idx1 ) return idx2;
if ( -1 == idx2 ) return idx1;
return Math.min(idx1, idx2);
}
private static List<String> findFilesTreeMatchingMask( final String parentPrefix, String maskedPath, final Set<String> visitedFolders, final boolean isMatchByRegex ) {
final File parent = new File( parentPrefix ); // includes trailing separator char
if ( false == parent.isDirectory() ) return EMPTY_LIST;
final int separatorIndex = indexOfFileSeparator( maskedPath );
final boolean isLeafElmt = -1 == separatorIndex;
if ( isLeafElmt )
try { if ( false == visitedFolders.add( parent.getCanonicalPath() ) ) return EMPTY_LIST; } // we've been here before
catch (IOException e) { return EMPTY_LIST; } // ignore this folder
// get the next path element, escaping the $ ^ ( ) characters that are valid in filenames but also
// special characters in RegExes. If you don't mask these characters then the pattern matcher below
// treats then as special characters and they probably won't match. e.g. a $ in a filename will be
// treated as a end of line match.
final String nxtPathElmtMask = maskedPath.substring( 0, -1 == separatorIndex ? maskedPath.length() : separatorIndex ).replaceAll("([\\$\\^\\(\\)])", "\\\\$1");
logger.logThreadDetail("Getting matching files for parent: " + parentPrefix + ", nxtPathElmtMask: " + nxtPathElmtMask);
String[] flist;
if ( false == isMatchByRegex && -1 == nxtPathElmtMask.indexOf('*') )
flist = new String[] { nxtPathElmtMask }; // this is key to handling the '.' and '..' expressions for navigating to "current" and "parent" folders
else {
// Apply the corresponding regex as filter to all files at the folder location
final Pattern fileNamePattern = isMatchByRegex ? Pattern.compile( nxtPathElmtMask ) : null;
flist = parent.list( new FilenameFilter() {
@Override public boolean accept(File dir, String name) {
return isMatchByRegex ? fileNamePattern.matcher( name ).matches() : stringMatchesWilcardExpression(name, nxtPathElmtMask); }
});
}
if ( null == flist ) return EMPTY_LIST; // not a folder or IOException - shouldn't happen
logger.logThreadDetail("Derived subtree elements: " + Arrays.asList(flist));
List<String> matchingFilePaths = new ArrayList<String>();
if ( isLeafElmt )
for ( String pathElmt : flist )
matchingFilePaths.add( parentPrefix + pathElmt );
else {
maskedPath = maskedPath.substring( separatorIndex+1 ); // get ready to search one level deeper
for ( String pathElmt : flist )
matchingFilePaths.addAll( findFilesTreeMatchingMask( parentPrefix + pathElmt + File.separatorChar, maskedPath, visitedFolders, isMatchByRegex ) );
}
return matchingFilePaths;
}
public static int runSystemCommand( final String[] sysCommand, boolean isPrintLog ) throws IOException {
if ( isPrintLog ) System.out.println("Running system command: " + Arrays.asList(sysCommand));
Process process = (isPrintLog ? new ProcessBuilder().inheritIO() : new ProcessBuilder() ).command( sysCommand ).start();
try { return process.waitFor(); } catch (InterruptedException e) { e.printStackTrace(); }
return -1;
}
public static int runSystemCommand( final String[] sysCommand, final Object synchObject, boolean isPrintLog ) throws IOException {
if ( null == synchObject ) return runSystemCommand( sysCommand, isPrintLog );
else synchronized ( synchObject ) { return runSystemCommand( sysCommand, isPrintLog ); }
}
}