views:

57

answers:

3

My Java is extremely rusty and I'm stuck trying to make a user interface that simplifies the execution of shell scripts or batch files depending on whether it's Linus or Win32 respectively. The files have the following naming convention.

  module-verb-object-etc [args-list]
  mysql-connect-grid
  mysql-connect-rds
  mysql-dump-grid
  mysql-dump-grid-se314

ultimately I would like it to parse unambiguous terms so I can:

  1. tokenize the commands (e.g delimited by "-") & shorten them into simplified terms soemthing like foxpro's command window or cisco's IOS (eg "my co gr" executes "mysql-connect-grid" in unix and *.cmd in win32)
  2. and also in the style of IOS allow the user to enter abbreviated commands so that they can type in a question mark (?) and it will give them a hint as to the unique remaining (or next) command options (e.g. "my?" returns mysql & "my ?" returns connect, or dump). Othr return values would be "ambiguous" or "unknown" for commands that are not unique or could not be matched. It may seem trivial but there are many hundreds of commands in each folder and my users don't want to think...

I wrote a function to pull the list of files from a directory & retun an array of fileanmes. Then I convert that into a 2 dimensional array using the method below which returns a dynamicly sized grid of potential commands.

    /**********************************************************************************
     *  MAKE GRID: Parses array of filenames and tokenizes AWS cmds.
     * @param strs  Array of filenames
     **********************************************************************************/
     public static String [][] makeGrid(String strs[], boolean bPrint) {
       String tmpGrid[][];
       int nMaxCols = 0;
       int nRows = uniqueCount(strs);
       int nGridRow = 0; 
       tmpGrid = new String [nRows][]; 
       for (int nRow=0; nRow<nRows; nRow++) { 
 String cFilename = strs[nRow];
                if (!cFilename.endsWith(".cmd") // just list unix files (filter for batch files)
    && cFilename.indexOf("-") > 0 ) // make sure there's a dash in the filename
    {
           String strTokens[] = tokenize(strs[nRow], "-"); // the dash is our token deliminator
           int nCols = strTokens.length; 
           if (nCols>nMaxCols) nMaxCols=nCols;
           tmpGrid[nGridRow] = new String [nCols];
           for (int nCol=0; nCol<nCols; nCol++) { 
               tmpGrid[nGridRow][nCol] = strTokens[nCol];
               if (bPrint) System.out.print(" "+tmpGrid[nGridRow][nCol]);
             }
            nGridRow++;
            if (bPrint) System.out.println("");
     } //end-if
         }
       String[][] cmdGrid = new String[nGridRow][nMaxCols];
       System.arraycopy(tmpGrid, 0, cmdGrid, 0, nGridRow); // removes null rows  (&NPEs!)
       return cmdGrid;
      }

This returns a 2-d array (below), so grid[Row-N][Col-0] is a match. I'd like to pull only distinct values where row[0] is a wildcard match for cmdToken[0] && row[1] is "like" cmdToken[1] so that my users can piece together a command until "my du gr ?" returns "ENTER, [se314]" - if that makes sense...

String[][] makeGrid:
    mysql dump grid se314
    mysql connect grid
    mysql dump grid
    mysql connect rds

My Challenge: I cant seem to get my head around my matcher function in java. If it was SQL it would be something like:

"SELECT DISTINCT col2 FROM cmd_Grid
   WHERE col1 LIKE 'cmdToken1%' " 

or even better: recursively setting a int depthmark for each consecutive column

`SELECT DISTINCT col+str(depthmark+1) FROM cmd_Grid 
    WHERE col+str(depthmark) LIKE 'cmdMatchedTokens%' " 

until you have an exact match.

I found a package called joSQL that I tried out of desperation but I cant seem to get it to work in Java6. Anyway: I was also hoping for a pure java solution so that everything could be contained in a single class...

Maybe using scanner or something to parse my multidimentional array for unique values... I know I'm probably making it way more complex than it needs to be.

a gentle nudge in the right direction would be appreciated.

TIA

A: 

One exhaustive solution could be to contruct a hashMap so that the key is a possible short command like 'my co gr" and the corresponding value is "mysql-connect-grid". So there would be values in the hash map that will have "mysql-connect-grid" as the value.

But this is a feasible solution only if there is a finite number of possible keys. If that's not the case, then you can use the built in string parsing methods.

For example:

    String[][] makeGrid = new String[][]{{"mysql", "dump", "grid", "se314"}, 
              {"mysql", "connect", "grid", ""},
              {"mysql",  "dump", "grid", ""},
              {"mysql", "connect", "rds", ""}
              };
     String[] query2 = new String[]{"my", "du", "gr"};

  String[][] matchingCommands = new String[4][4];
  int resultSize = 0;
     for(int i=0; i<makeGrid.length; i++)
  {
      String[] commandColumn = makeGrid[i];
   boolean matches = false;
      for(int cnt=0; cnt<commandColumn.length; cnt++)
      {
       String commandPart = commandColumn[cnt];
       if(cnt < query2.length){
        String queryPart = query2[cnt];
     if(commandPart.startsWith(queryPart) || queryPart.equals("?")){
         matches = true;
        }else{
         matches = false;
         break;
        }
       }
      }
      if(matches){
       matchingCommands[resultSize] = commandColumn;
       resultSize++;
      }
  }

This code snippet should give you some idea of how to go about it. There is one thing to note here though. The matchingCommands array has been initialized to the 4 rows and 4 columns which is wasteful because the matches will be lesser than that. Let me know if you need help making this more efficient. Otherwise, this is a working piece of code which I think does what you want.

Prachi
very interesting... Thanks for the quick reply. I tried with a hash-map but It got way too hairy. I don't remember the exact details but I think I had an issue with the equivalent of duplicate keys. I also gave up b/c of the dynamic array dimensions..Basically the cmdGrid-rows can have any number of columns - but you gave me an idea stuffing null elements with an empty string "" not the most elegant but it could work.
WWWIZARDS
A: 

right now I was toying around with parsing each cmdString (query) for white-space delimiters & tokenizing the array. Something like:

 Scanner sCmdString = new Scanner(cInput);
 while (sCmdString.hasNext()) { 
 String cToken = sCmdString.next().toUpperCase().trim();
 System.out.println(" "+cToken+" ");
 // match cmdString[i..n] to cmdGrid
 for (int nRow=0; nRow < cmdGrid.length; nRow++) {
       for (int nCol=0; nCol < cmdGrid[nRow].length; nCol++) {
  if (cmdGrid[nRow][nCol].equalsIgnoreCase(cToken) )
     System.out.println("MATCH: "+cmdGrid[nRow][nCol]);
  else System.out.println("NO MATCH:"+cmdGrid[nRow][nCol].toUpperCase()+":"+cToken+"...");
        }
   }
   }

but I was getting NPEs with the uneven row lengths.

AND I like your idea of flattening out the columns.

I think I'd still have to remove duplicates... no?

WWWIZARDS
A: 

You could also look into using some more advanced data structures like an ArrayList instead of an array and using the StringTokenizer to generate each command Part on the fly.

It will be something like this:

ArrayList<String> matchingCommands = new ArrayList<String>();

    ArrayList<String> commandList = new ArrayList<String>();
    commandList.add("mysql dump grid se314");
    commandList.add("mysql connect grid");
    commandList.add("mysql dump grid");
    commandList.add("mysql connect rds");

    String queryCommand = "my du gr ?";

    for(int i=0; i<commandList.size(); i++)
    {
        boolean matches = false;
        String command = commandList.get(i);
        StringTokenizer commandTokenizer = new StringTokenizer(command, " "); // Using space as the deliminator
        StringTokenizer queryTokenizer = new StringTokenizer(queryCommand, " "); // Using space as the deliminator

        while(commandTokenizer.hasMoreTokens())
        {
            String queryPart = queryTokenizer.nextToken();
            String commandPart = commandTokenizer.nextToken();
            if(commandPart.startsWith(queryPart) || queryPart.equals("?")){
                matches = true;
            }else{
                matches = false;
                break;
            }
        }
        if(matches){
            matchingCommands.add(command);
        }
    }
    System.out.println(matchingCommands);

This would make sure that your program can grow dynamically and there is no wastage of memory because of null objects either.

Prachi