Coverage Report - us.daveread.basicquery.util.Utility
 
Classes in this File Line Coverage Branch Coverage Complexity
Utility
89%
113/127
100%
30/30
0
 
 1  
 package us.daveread.basicquery.util;
 2  
 
 3  
 import java.awt.Component;
 4  
 import java.text.SimpleDateFormat;
 5  
 import java.text.DecimalFormat;
 6  
 import java.util.ArrayList;
 7  
 import java.util.HashMap;
 8  
 import java.util.List;
 9  
 import java.util.Map;
 10  
 
 11  
 import javax.swing.JTable;
 12  
 import javax.swing.table.TableColumn;
 13  
 import javax.swing.table.TableModel;
 14  
 
 15  
 import org.apache.log4j.Logger;
 16  
 
 17  
 /**
 18  
  * Title: Utilities
 19  
  * Description: Utilities for string manipulation and other convenience methods
 20  
  * Copyright: Copyright (c) 2003-2014
 21  
  * 
 22  
  * <p>
 23  
  * This program is free software; you can redistribute it and/or modify it under
 24  
  * the terms of the GNU General Public License as published by the Free Software
 25  
  * Foundation; either version 2 of the License, or (at your option) any later
 26  
  * version.
 27  
  * </p>
 28  
  * <p>
 29  
  * This program is distributed in the hope that it will be useful, but WITHOUT
 30  
  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 31  
  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 32  
  * details.
 33  
  * </p>
 34  
  * <p>
 35  
  * You should have received a copy of the GNU General Public License along with
 36  
  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 37  
  * Place, Suite 330, Boston, MA 02111-1307 USA
 38  
  * </p>
 39  
  * 
 40  
  * @author David Read
 41  
  */
 42  
 public class Utility {
 43  
   /**
 44  
    * Logger
 45  
    */
 46  1
   private static final Logger LOGGER = Logger.getLogger(Utility.class);
 47  
 
 48  
   /**
 49  
    * Map used for storing date formats for formattedDate() methods
 50  
    */
 51  
   private static Map<String, SimpleDateFormat> formats;
 52  
 
 53  
   // Establish static configuration
 54  
   static {
 55  1
     formats = new HashMap<String, SimpleDateFormat>();
 56  1
   }
 57  
 
 58  
   /**
 59  
    * Utility class, no instances should be created
 60  
    */
 61  0
   private Utility() {
 62  0
   }
 63  
 
 64  
   /**
 65  
    * Replaces the specified substring with another in the first (whole) string.
 66  
    * 
 67  
    * @param aString
 68  
    *          The whole string.
 69  
    * @param aOldSubString
 70  
    *          The substrinng to be replaced.
 71  
    * @param aNewSubString
 72  
    *          The new substring to replace the old one.
 73  
    * 
 74  
    * @return The whole string with the substrings replaced.
 75  
    */
 76  
   public static String replace(String aString, String aOldSubString,
 77  
       String aNewSubString) {
 78  1
     return replace(aString, aOldSubString, aNewSubString, 0, true);
 79  
   }
 80  
 
 81  
   /**
 82  
    * Replaces the specified substring with another in the first (whole)
 83  
    * string starting at iaStartIndex.
 84  
    * 
 85  
    * @param saString
 86  
    *          The whole string.
 87  
    * @param saOldSubString
 88  
    *          The substring to be replaced.
 89  
    * @param saNewSubString
 90  
    *          The new substring to replace the old one.
 91  
    * @param iaStartIndex
 92  
    *          The index at which to start the process of 'search
 93  
    *          and replace'.
 94  
    * @param replaceAll
 95  
    *          Whether to replace all matches or only the first
 96  
    * 
 97  
    * @return The whole string with the substring(s) replaced.
 98  
    */
 99  
   public static String replace(String saString, String saOldSubString,
 100  
       String saNewSubString, int iaStartIndex,
 101  
       boolean replaceAll) {
 102  
     StringBuffer sblResult;
 103  
     boolean blDone, blFirstReplacement;
 104  
     int ilIndex;
 105  10
     if (saString == null || saOldSubString == null || saNewSubString == null) {
 106  3
       throw new IllegalArgumentException("arguments may not be null");
 107  7
     } else if (saString.equals("") || saOldSubString.equals("")) {
 108  2
       return saString; // return the empty String as nothing can be replaced
 109  5
     } else if (iaStartIndex < 0) {
 110  1
       throw new IllegalArgumentException(
 111  
           "iaStartIndex must be greater than or equal to 0");
 112  4
     } else if (iaStartIndex >= saString.length()) {
 113  1
       throw new IllegalArgumentException("iaStartIndex cannot equal or exceed"
 114  
           + "length of input String saString");
 115  
     }
 116  
 
 117  3
     sblResult = new StringBuffer();
 118  3
     blDone = false;
 119  3
     blFirstReplacement = true;
 120  3
     String workingVersion = saString;
 121  3
     int workingIndex = iaStartIndex;
 122  12
     while (!blDone) {
 123  9
       ilIndex = workingVersion.indexOf(saOldSubString, workingIndex);
 124  9
       if ((!blFirstReplacement && !replaceAll) || ilIndex == -1) {
 125  1
         sblResult.append(workingVersion);
 126  1
         blDone = true;
 127  
       } else {
 128  8
         sblResult.append(workingVersion.substring(0, ilIndex));
 129  8
         sblResult.append(saNewSubString);
 130  8
         if (workingVersion.length() > ilIndex + saOldSubString.length()) {
 131  6
           workingVersion = workingVersion.substring(
 132  
               ilIndex + saOldSubString.length(),
 133  
               workingVersion.length());
 134  
         } else {
 135  2
           blDone = true;
 136  
         }
 137  
 
 138  8
         blFirstReplacement = false;
 139  
 
 140  
         /* We have skipped the beginning, now always assess what's left */
 141  8
         workingIndex = 0;
 142  
       }
 143  
     }
 144  3
     return sblResult.toString();
 145  
   }
 146  
 
 147  
   /**
 148  
    * This method picks good column sizes.
 149  
    * If all column heads are wider than the column's cells'
 150  
    * contents, then you can just use column.sizeWidthToFit().
 151  
    * 
 152  
    * @param table
 153  
    *          The table contains the columns for comparision
 154  
    * @param model
 155  
    *          This is the Default Table Model of the Jtable
 156  
    */
 157  
   public static void initColumnSizes(JTable table, TableModel model) {
 158  1
     TableColumn column = null;
 159  1
     Component comp = null;
 160  1
     int headerWidth = 0;
 161  1
     int cellWidth = 0;
 162  1
     final Object[] longValues = getLongestValues(model);
 163  
 
 164  4
     for (int i = 0; i < longValues.length; i++) {
 165  3
       column = table.getColumnModel().getColumn(i);
 166  
 
 167  
       try {
 168  
         // comp = column.getHeaderRenderer().
 169  
         // getTableCellRendererComponent(
 170  
         // null, column.getHeaderValue(),
 171  
         // false, false, 0, 0);
 172  3
         comp = table.getTableHeader().getDefaultRenderer().
 173  
             getTableCellRendererComponent(
 174  
                 null, column.getHeaderValue() + "W",
 175  
                 false, false, 0, 0);
 176  3
         headerWidth = comp.getPreferredSize().width;
 177  
 
 178  
         /**
 179  
          * Periodically the return value from getPreferredSize() is huge. I have
 180  
          * no idea what is going on - this recheck seems to always come back
 181  
          * with a sane value
 182  
          */
 183  3
         if (headerWidth > 10000) {
 184  0
           LOGGER.debug("Header unusually wide (" + headerWidth
 185  
               + "): calc again");
 186  
 
 187  0
           headerWidth = comp.getPreferredSize().width;
 188  
 
 189  0
           LOGGER.debug("Result of header recalc (" + headerWidth + ")");
 190  
         }
 191  0
       } catch (NullPointerException npe) {
 192  0
         LOGGER
 193  
             .error(
 194  
                 "getHeaderRenderer returns null in 1.3. The replacement is getDefaultRenderer.",
 195  
                 npe);
 196  3
       }
 197  
 
 198  3
       comp = table.getDefaultRenderer(model.getColumnClass(i)).
 199  
           getTableCellRendererComponent(
 200  
               table, longValues[i] + "W",
 201  
               false, false, 0, i);
 202  3
       cellWidth = comp.getPreferredSize().width;
 203  
 
 204  
       /**
 205  
        * Periodically the return value from getPreferredSize() is huge. I have
 206  
        * no idea what is going on - this recheck seems to always come back
 207  
        * with a sane value
 208  
        */
 209  3
       if (cellWidth > 10000) {
 210  0
         LOGGER.debug("Column unusually wide (" + cellWidth + "): calc again");
 211  
 
 212  0
         cellWidth = comp.getPreferredSize().width;
 213  
 
 214  0
         LOGGER.debug("Result of recalc (" + cellWidth + ")");
 215  
       }
 216  
 
 217  3
       LOGGER.debug("Initializing width of column " + i + ". "
 218  
           + "headerWidth = " + headerWidth + "; cellWidth = " + cellWidth
 219  
           + "; longValue = [" + longValues[i] + "]");
 220  
 
 221  
       // NOTE: Before Swing 1.1 Beta 2, use setMinWidth instead.
 222  3
       column.setPreferredWidth(Math.max(headerWidth, cellWidth));
 223  
     }
 224  1
   }
 225  
 
 226  
   /**
 227  
    * Gets longest values, one for each column
 228  
    * 
 229  
    * @param model
 230  
    *          The DefaultTable model
 231  
    * 
 232  
    * @return obj1longest Object that has the longest value for each column
 233  
    */
 234  
   public static Object[] getLongestValues(TableModel model) {
 235  
     Object[] objlLongest;
 236  
     Object objlValue;
 237  
     int[] ilLen;
 238  
     int ilThisLen;
 239  
     int ilNumRows;
 240  
     int ilRow;
 241  
     int ilNumCols;
 242  
     int ilCol;
 243  
 
 244  2
     ilNumCols = model.getColumnCount();
 245  2
     ilNumRows = model.getRowCount();
 246  
 
 247  2
     objlLongest = new Object[ilNumCols];
 248  2
     ilLen = new int[ilNumCols];
 249  
 
 250  8
     for (ilCol = 0; ilCol < ilNumCols; ++ilCol) {
 251  6
       objlLongest[ilCol] = "";
 252  6
       ilLen[ilCol] = 0;
 253  
     }
 254  
 
 255  13
     for (ilRow = 0; ilRow < ilNumRows; ++ilRow) {
 256  44
       for (ilCol = 0; ilCol < ilNumCols; ++ilCol) {
 257  33
         objlValue = model.getValueAt(ilRow, ilCol);
 258  33
         if (objlValue != null) {
 259  27
           if ((ilThisLen = objlValue.toString().length()) > ilLen[ilCol]) {
 260  9
             objlLongest[ilCol] = objlValue;
 261  9
             ilLen[ilCol] = ilThisLen;
 262  9
             LOGGER.debug("Get longest value, Checking(" + ilRow + "," + ilCol
 263  
                 + ")=" + ilThisLen);
 264  
           }
 265  
         }
 266  
       }
 267  
     }
 268  
 
 269  2
     return objlLongest;
 270  
   }
 271  
 
 272  
   /**
 273  
    * Formats the System date to appear as MM/dd/yyyy HH:mm:ss.SSS
 274  
    * 
 275  
    * @param date
 276  
    *          The system date that needs to be formatted
 277  
    * 
 278  
    * @return String The formatted date
 279  
    */
 280  
   public static String formattedDate(java.util.Date date) {
 281  1
     return formattedDate(date, "MM/dd/yyyy HH:mm:ss.SSS");
 282  
   }
 283  
 
 284  
   /**
 285  
    * Formats the System date in the order determined by the formatString
 286  
    * 
 287  
    * @param date
 288  
    *          The System Date
 289  
    * @param formatString
 290  
    *          The desired format for writing out the date
 291  
    * 
 292  
    * @return String The formatted date
 293  
    */
 294  
   public static String formattedDate(java.util.Date date, String formatString) {
 295  
     SimpleDateFormat format;
 296  
 
 297  2
     format = (SimpleDateFormat) formats.get(formatString);
 298  2
     if (format == null) {
 299  2
       format = new SimpleDateFormat(formatString);
 300  2
       formats.put(formatString, format);
 301  
     }
 302  
 
 303  2
     return format.format(date);
 304  
   }
 305  
 
 306  
   /**
 307  
    * Formats a number using the supplied format template
 308  
    * 
 309  
    * @param number
 310  
    *          The number to format
 311  
    * @param format
 312  
    *          The format template
 313  
    * 
 314  
    * @return The formatted number
 315  
    */
 316  
   public static String formattedNumber(long number, String format) {
 317  
     DecimalFormat fmt;
 318  
 
 319  1
     fmt = new DecimalFormat(format);
 320  
 
 321  1
     return fmt.format(number);
 322  
   }
 323  
 
 324  
   /**
 325  
    * Ensure that a given character is inserted at intervals along a String. This
 326  
    * is typically used to force inclusion of a space periodically.
 327  
    * 
 328  
    * The method will walk through the supplied data string, looking for
 329  
    * characters that are in the break characters String. If found, and the
 330  
    * minimum number of characters have been traversed, the insert string is
 331  
    * inserted and the character count reset. If the maximum section length is
 332  
    * reached and no break characters have been found, the insert string is
 333  
    * inserted anyway.
 334  
    * 
 335  
    * @param saData
 336  
    *          The string to have characters inserted into
 337  
    * @param saInsert
 338  
    *          The String to insert into the data string
 339  
    * @param iaMinSectionLength
 340  
    *          The minumum number of characters in the data
 341  
    *          string, that must occur between inserted strings
 342  
    * @param iaMaxSectionLength
 343  
    *          The maximum number of characters to allow
 344  
    *          in the data string between inserted strings
 345  
    * @param saBreakCharacters
 346  
    *          The characters that signal a possible insertion
 347  
    *          point in the data string
 348  
    * 
 349  
    * @return The updated data string. If the data string is null, a null
 350  
    *         String is returned.
 351  
    */
 352  
   public static final String characterInsert(String saData, String saInsert,
 353  
       int iaMinSectionLength,
 354  
       int iaMaxSectionLength,
 355  
       String saBreakCharacters) {
 356  
     String result;
 357  
     StringBuffer sblResult;
 358  
     char[] clData;
 359  
     int ilLen, ilPosit, ilSectionLength;
 360  
 
 361  2
     if (saData != null) {
 362  2
       ilLen = saData.length();
 363  2
       clData = new char[ilLen];
 364  2
       saData.getChars(0, ilLen, clData, 0);
 365  2
       ilPosit = 0;
 366  2
       ilSectionLength = 0;
 367  2
       sblResult = new StringBuffer(ilLen * 2);
 368  
 
 369  24
       for (ilPosit = 0; ilPosit < ilLen; ++ilPosit) {
 370  22
         sblResult.append(clData[ilPosit]);
 371  22
         ++ilSectionLength;
 372  22
         if (saInsert.length() == 1 && clData[ilPosit] == saInsert.charAt(0)) {
 373  1
           ilSectionLength = 0;
 374  21
         } else if ((ilSectionLength >= iaMinSectionLength && saBreakCharacters
 375  
             .indexOf(clData[ilPosit]) >= 0)
 376  
             || ilSectionLength >= iaMaxSectionLength) {
 377  6
           sblResult.append(saInsert);
 378  6
           ilSectionLength = 0;
 379  
         }
 380  
       }
 381  2
       result = sblResult.toString();
 382  
     } else {
 383  0
       result = saData;
 384  
     }
 385  
 
 386  2
     return result;
 387  
   }
 388  
 
 389  
   /**
 390  
    * Split a string at a pattern, but be sensitive to quoted sections of the
 391  
    * string. Quoted sections should not be parsed. Nested quotes and
 392  
    * apostrophes are also observed.
 393  
    * 
 394  
    * e.g. Parsing for semicolons (;), the following string:
 395  
    * 
 396  
    * This is;a test;string
 397  
    * 
 398  
    * should become three string:
 399  
    * 1) This is
 400  
    * 2) a test
 401  
    * 3) string
 402  
    * 
 403  
    * But the string:
 404  
    * 
 405  
    * This "is;a" test;string
 406  
    * 
 407  
    * should only become two strings:
 408  
    * 1) This "is;a" test
 409  
    * 2) string
 410  
    * 
 411  
    * @param data
 412  
    *          String The string to be split into separate strings
 413  
    * @param splitPattern
 414  
    *          String The pattern used to split the string
 415  
    * @return String[] The individually split strings
 416  
    */
 417  
   public static String[] splitWithQuotes(String data, String splitPattern) {
 418  
     String[] maxParts;
 419  
     List<String> tempParts;
 420  
     String recombined;
 421  
     int partIndex;
 422  
     List<Character> stackOfQuotes;
 423  
     char[] partData;
 424  
     int charIndex;
 425  
     boolean escapeNext;
 426  
 
 427  3
     stackOfQuotes = new ArrayList<Character>();
 428  3
     maxParts = data.split(splitPattern);
 429  3
     tempParts = new ArrayList<String>();
 430  3
     recombined = "";
 431  3
     escapeNext = false;
 432  
 
 433  3
     LOGGER.debug("Split [" + data + "] at [" + splitPattern + "]");
 434  
 
 435  17
     for (partIndex = 0; partIndex < maxParts.length; ++partIndex) {
 436  14
       partData = maxParts[partIndex].toCharArray();
 437  106
       for (charIndex = 0; charIndex < partData.length; ++charIndex) {
 438  92
         if (!escapeNext
 439  
             && (partData[charIndex] == '\'' || partData[charIndex] == '"')) {
 440  8
           if (stackOfQuotes.size() > 0) {
 441  5
             if (((Character) stackOfQuotes.get(0)).charValue() == partData[charIndex]) {
 442  4
               stackOfQuotes.remove(0);
 443  
             } else {
 444  1
               stackOfQuotes.add(0, new Character(partData[charIndex]));
 445  
             }
 446  
           } else {
 447  3
             stackOfQuotes.add(0, new Character(partData[charIndex]));
 448  
           }
 449  84
         } else if (!escapeNext && partData[charIndex] == '\\') {
 450  0
           escapeNext = true;
 451  84
         } else if (escapeNext) {
 452  0
           escapeNext = false;
 453  
         }
 454  
 
 455  
       }
 456  
 
 457  
       /*
 458  
        * At this point we'll either have an empty stackOfQuotes - in which case
 459  
        * this data can stand on it's own, appended to anything in recombined if
 460  
        * it is not empty. Otherwise we are in a quoted string and this value
 461  
        * must be appended to the recombined string.
 462  
        * 
 463  
        * For simplicity we'll append the string to recombined, which may be
 464  
        * empty then either add recombined to the tempParts if we are not in a
 465  
        * quote or leave it sit there if we are in a quote.
 466  
        * 
 467  
        * We append the splitPattern since that must have followed the value or
 468  
        * it wouldn't have been a separate part.
 469  
        */
 470  14
       if (recombined.length() > 0) {
 471  5
         recombined += splitPattern;
 472  
       }
 473  
 
 474  14
       recombined += maxParts[partIndex];
 475  
 
 476  
       // Not in a quoted string
 477  14
       if (stackOfQuotes.size() == 0) {
 478  9
         tempParts.add(recombined);
 479  9
         recombined = "";
 480  
       }
 481  
     }
 482  
 
 483  
     /*
 484  
      * If recombined still has a string in it, then there were one or more
 485  
      * dangling quotes - just put the string in place and allow the process to
 486  
      * continue.
 487  
      */
 488  3
     if (recombined.length() > 0) {
 489  0
       tempParts.add(recombined);
 490  
     }
 491  
 
 492  
     // Return the array of strings
 493  3
     return (String[]) tempParts.toArray(new String[tempParts.size()]);
 494  
   }
 495  
 }