1 /*****************************************************************************/ 2 /* Software Testing Automation Framework (STAF) */ 3 /* (C) Copyright IBM Corp. 2002 */ 4 /* */ 5 /* This software is licensed under the Eclipse Public License (EPL) V1.0. */ 6 /*****************************************************************************/ 7 8 package com.ibm.staf.service.stax; 9 10 import java.util.StringTokenizer; 11 import java.io.BufferedReader; 12 import java.io.StringReader; 13 import java.io.IOException; 14 import java.net.URI; 15 import java.net.URISyntaxException; 16 17 /* Jython 2.1: Needs the following line 18 import org.python.core.__builtin__; 19 */ 20 // Jython 2.5: Needs the following 2 lines 21 import org.python.core.Py; 22 import org.python.core.CompileMode; 23 24 import org.python.core.PyCode; 25 import org.python.core.PyException; 26 27 import org.w3c.dom.Node; 28 import org.w3c.dom.NamedNodeMap; 29 30 /** 31 * STAXUtil - This class provides STAX utility functions 32 */ 33 public class STAXUtil 34 { 35 /** 36 * Accepts Python code as niput and removes any white space from the 37 * beginning of new lines and compiles the Python code to validate its 38 * syntax and returns valid Python code. 39 * 40 * @param value A string containing Python code to be parsed for Python 41 * and compiled to validate its syntax. 42 * 43 * @param action A STAXActionDefaultImpl object that references the 44 * Python code. It's used if an error occurs compiling the Python code 45 * to provide additional information such as line number, xml file name, 46 * the element in error, the attribute in error, etc. 47 * 48 * @return String containing the valid Python code with white space 49 * removed from the beginning of new lines. 50 */ parseAndCompileForPython(String value, STAXActionDefaultImpl action)51 public static String parseAndCompileForPython(String value, 52 STAXActionDefaultImpl action) 53 throws STAXPythonCompileException 54 { 55 String parsedCode = parseForPython(value); 56 57 try 58 { 59 compileForPython(parsedCode); 60 } 61 catch (STAXPythonCompileException e) 62 { 63 throw new STAXPythonCompileException( 64 STAXUtil.formatErrorMessage(action) + "\n" + e.getMessage()); 65 } 66 67 return parsedCode; 68 } 69 70 /** 71 * Accepts Python code as niput and removes any white space from the 72 * beginning of new lines and compiles the Python code to validate its 73 * syntax and returns valid Python code. 74 * 75 * @param value A string containing Python code to be parsed for Python 76 * and compiled to validate its syntax. 77 * 78 * @param errorInfo A string containing additional error information to 79 * be prepended to the error message if a STAXPythonCompileException 80 * occurs. Allows you to identify the xml element (and attribute, if 81 * applicable) containing the invalid Python code . 82 * 83 * @return String containing the valid Python code with white space 84 * removed from the beginning of new lines. 85 */ parseAndCompileForPython(String value, String errorInfo)86 public static String parseAndCompileForPython(String value, 87 String errorInfo) 88 throws STAXPythonCompileException 89 { 90 String parsedCode = parseForPython(value); 91 92 try 93 { 94 compileForPython(parsedCode); 95 } 96 catch (STAXPythonCompileException e) 97 { 98 throw new STAXPythonCompileException(errorInfo + "\n" + 99 e.getMessage()); 100 } 101 102 return parsedCode; 103 } 104 105 106 /** 107 * Accepts Python code as input and removes any white space from the 108 * beginning of new lines and returned the parsed Python code. 109 * 110 * @param value A string containing python code to be parsed for Python 111 * 112 * @return String containing Python code with white space removed from 113 * the beginning of new lines 114 */ parseForPython(String value)115 public static String parseForPython(String value) 116 { 117 StringBuffer parsedValue = new StringBuffer(); 118 int pos = 0; // Position of first non-whitespace character 119 // in 1st line, taking into account tabs. 120 121 // Skip any beginning lines that are just white space 122 // (including tabs, carriage returns, etc.) 123 124 String line; 125 boolean firstLine = true; 126 BufferedReader br = new BufferedReader(new StringReader(value)); 127 StringTokenizer st; 128 129 try 130 { 131 while ((line = br.readLine()) != null) 132 { 133 if (firstLine) 134 { 135 // Find the number of leading whitespace characters. 136 // Go through the string character by character, and expand 137 // each tab to the appropriate number of spaces required to 138 // reach the next tab stop (set at intervals of 8 characters). 139 140 st = new StringTokenizer(line); 141 if (st.hasMoreTokens()) 142 { 143 //Found first line that isn't just whitespace 144 firstLine = false; 145 pos = 0; 146 int len = line.length(); 147 int i = 0; 148 149 // Append 8 spaces for each leading tab character. 150 for (; i < len && line.charAt(i) == '\t'; i++) 151 pos+=8; 152 153 // For remaining tabs, add enough spaces to get to the next 154 // multiple of 8 (until reach a non-whitespace character). 155 for (; i < len; i++) 156 { 157 char c = line.charAt(i); 158 if (c == ' ') 159 pos++; 160 else if (c == '\t') 161 { 162 do 163 pos++; 164 while (pos % 8 != 0); 165 } 166 else // non-whitespace character found 167 break; 168 } 169 parsedValue.append(line.substring(i)); 170 } 171 } 172 else 173 { 174 // Remove the number of leading white space characters 175 // found in the 1st line (pos) from each additional line 176 // if possible (but don't remove any non-whitespace chars). 177 178 int pos2 = 0; 179 int len = line.length(); 180 int i = 0; 181 182 for (; i < len && pos2 <= pos; i++) 183 { 184 char c = line.charAt(i); 185 if (c == ' ') 186 pos2++; 187 else if (c == '\t') 188 { 189 do 190 pos2++; 191 while (pos2 % 8 != 0); 192 } 193 else // non-whitespace character found 194 break; 195 } 196 parsedValue.append("\n"); 197 198 // Add leading blanks, if any. 199 while (pos2 > pos) 200 { 201 parsedValue.append(" "); 202 pos2--; 203 } 204 parsedValue.append(line.substring(i)); 205 } 206 } 207 } 208 catch (Exception e) 209 { 210 System.out.println(e); 211 return value; 212 } 213 finally 214 { 215 try 216 { 217 br.close(); 218 } 219 catch (IOException e) 220 { 221 System.out.println(e); 222 return value; 223 } 224 } 225 226 return parsedValue.toString(); 227 } 228 229 /** 230 * Accepts parsed Python code as input and compiles the Python code to 231 * validate its syntax. Throws a STAXPythonCompileException if an error 232 * occurs compiling the Python code. 233 * 234 * @param value A string containing parsed python code to be compiled by 235 * Python to validate its syntax. 236 */ compileForPython(String parsedCode)237 public static void compileForPython(String parsedCode) 238 throws STAXPythonCompileException 239 { 240 try 241 { 242 /* Jython 2.1: 243 PyCode code = __builtin__.compile(parsedCode, "<string>", "exec"); 244 */ 245 // Jython 2.5: 246 Py.compile_flags( 247 parsedCode, "<string>", 248 CompileMode.exec, Py.getCompilerFlags()); 249 } 250 catch (Exception e) 251 { 252 throw new STAXPythonCompileException( 253 "Python code compile failed for:\n" + parsedCode + 254 "\n\n" + e.toString()); 255 } 256 } 257 258 /** 259 * Generates a formatted error message the contains important information 260 * such as the line number of the action that failed, the element name 261 * of the action that failed. 262 * @param action The action that is in error. 263 */ formatErrorMessage(STAXActionDefaultImpl action)264 public static String formatErrorMessage(STAXActionDefaultImpl action) 265 { 266 STAXElementInfo info = action.getElementInfo(); 267 268 if (info.getElementName() == null) 269 { 270 info.setElementName(action.getElement()); 271 } 272 273 StringBuffer errMsg = new StringBuffer("File: "); 274 275 errMsg.append(action.getXmlFile()).append(", Machine: ").append( 276 action.getXmlMachine()); 277 278 errMsg.append("\nLine ").append(action.getLineNumber( 279 info.getElementName(), info.getElementIndex())).append(": "); 280 281 if ((info.getElementName() != null) && 282 (info.getAttributeName() == null)) 283 { 284 errMsg.append("Error in element type \"").append( 285 info.getElementName()).append("\"."); 286 } 287 else if ((info.getElementName() != null) && 288 (info.getAttributeName() != null)) 289 { 290 errMsg.append("Error in attribute \"").append( 291 info.getAttributeName()).append( 292 "\" associated with element type \"").append( 293 info.getElementName()).append("\"."); 294 } 295 296 if (info.getErrorMessage() != null) 297 { 298 errMsg.append("\n\n").append(info.getErrorMessage()); 299 } 300 301 return errMsg.toString(); 302 } 303 304 /** 305 * Generates a formatted string for an action. It contains the element 306 * name of the action, any info provided by the action's getInfo() method, 307 * line number of the element, the XML file containing the element, and 308 * the machine where the XML file esided. 309 * 310 * @param action The action representing the element being executed 311 * 312 * @return String containing a formatted string containing information 313 * about an action 314 */ formatActionInfo(STAXActionDefaultImpl action)315 public static String formatActionInfo(STAXActionDefaultImpl action) 316 { 317 StringBuffer actionInfo = new StringBuffer(action.getElement()); 318 319 if ((action.getInfo() != null) && (action.getInfo().length() > 0)) 320 { 321 actionInfo.append(": ").append(action.getInfo()); 322 } 323 324 actionInfo.append(" (Line: ").append(action.getLineNumber()).append( 325 ", File: ").append(action.getXmlFile()).append( 326 ", Machine: ").append(action.getXmlMachine()).append(")"); 327 328 return actionInfo.toString(); 329 } 330 331 /** Get line number by getting value of attribute _ln for the input node 332 * @param node a map of the attributes for an element in the DOM tree 333 * @return a striong containing the line number 334 */ getLineNumberFromAttrs(NamedNodeMap attrs)335 public static String getLineNumberFromAttrs(NamedNodeMap attrs) 336 { 337 String lineNumber = "Unknown"; 338 339 Node thisAttr = attrs.getNamedItem("_ln"); 340 341 if (thisAttr != null) 342 lineNumber = thisAttr.getNodeValue(); 343 344 return lineNumber; 345 } 346 347 348 /** 349 * Converts a STAF request number that is > Integer.MAX_VALUE to a 350 * negative number. 351 * 352 * Doing this for now to handle STAF request numbers > integer.MAX_VALUE 353 * so as not to impact STAX extensions that implement the 354 * STAXSTAFRequestCompleteListener interface's requestComplete() method 355 * which passes requestNumber as an int. 356 * 357 * In a future major STAX release, we may change the requestComplete() 358 * method to represent requestNumber as a long instead of an int instead 359 * of converting it to a negative number. 360 * 361 * @param requestNumberString a string containing the STAF Request Number 362 * 363 * @return an Integer object that contains the converted request number 364 */ convertRequestNumber(String requestNumberString)365 public static Integer convertRequestNumber(String requestNumberString) 366 throws NumberFormatException 367 { 368 try 369 { 370 return new Integer(requestNumberString); 371 } 372 catch (NumberFormatException e) 373 { 374 try 375 { 376 long longRequestNumber = new Long(requestNumberString). 377 longValue(); 378 379 if (longRequestNumber > Integer.MAX_VALUE) 380 { 381 int requestNumber = -1 * 382 ((int)(longRequestNumber - Integer.MAX_VALUE)); 383 384 return new Integer(requestNumber); 385 } 386 else 387 { 388 throw e; 389 } 390 } 391 catch (NumberFormatException e2) 392 { 393 throw e2; 394 } 395 } 396 } 397 398 /** 399 * This method gets the short name for a class by removing the STAX 400 * package name at the beginning of the class name (if present) and by 401 * removing the specified suffix at the end of the class name (if present) 402 * 403 * @param longClassName A string containing a STAX class name 404 * @param classSuffix A string containing a suffix to be removed from the 405 * long class name 406 * 407 * @return String containing the "short" class name 408 */ getShortClassName(String longClassName, String classSuffix)409 public static String getShortClassName(String longClassName, 410 String classSuffix) 411 { 412 String className = longClassName; 413 414 if (className.startsWith(STAX.PACKAGE_NAME)) 415 className = className.substring(STAX.PACKAGE_NAME.length()); 416 417 if (className.endsWith(classSuffix)) 418 { 419 className = className.substring( 420 0, className.length() - classSuffix.length()); 421 } 422 423 return className; 424 } 425 426 /** 427 * Normalizes a file path. 428 * 429 * @param path the file path to be normalized 430 * @param fileSep the file separator for the machine where the file resides 431 * @return a normalized file path if the path is valid; otherwise, the 432 * original path is returned 433 */ normalizeFilePath(String path, String fileSep)434 public static String normalizeFilePath(String path, String fileSep) 435 { 436 String normalized = path; 437 438 if ((path == null) || (path.length() == 0)) 439 { 440 return path; 441 } 442 443 if (fileSep.equals("\\")) 444 { 445 // Windows path 446 447 // Check for windows drive letter 448 449 char driveLetter = path.charAt(0); 450 boolean isWindowsDrivePath = path.length() > 1 && 451 path.charAt(1) == ':' && 452 (driveLetter >= 'a' && driveLetter <= 'z') || 453 (driveLetter >= 'A' && driveLetter <= 'Z'); 454 455 if (isWindowsDrivePath) 456 { 457 // Path starts with Windows drive letter + : 458 459 String uriPath = path.replace('\\', '/'); 460 461 try 462 { 463 URI uri = new URI("file:/" + uriPath); 464 uri = uri.normalize(); 465 466 // Need to remove the leading forward slash 467 normalized = uri.getPath().substring(1); 468 469 // Since Windows, use \ instead of / for the file separator 470 normalized = normalized.replace('/', '\\'); 471 } 472 catch (URISyntaxException e) 473 { 474 return path; 475 } 476 } 477 else if (path.charAt(0) == '\\' || path.charAt(0) == '/') 478 { 479 // Check if Windows UNC path, e.g. \\test.ibm.com\folder1 480 481 boolean isUNC = path.startsWith("\\\\"); 482 String uncServer = null; 483 String uriPath = path.replace('\\', '/'); 484 485 if (isUNC) 486 { 487 // Extract the server name 488 int index = uriPath.indexOf('/', 2); 489 490 if (index == -1) 491 return path; 492 493 uncServer = uriPath.substring(2, index); 494 495 // The file path to be normalized is the remainder of 496 // the path 497 uriPath = uriPath.substring(index); 498 } 499 500 try 501 { 502 URI uri = new URI("file:" + uriPath); 503 uri = uri.normalize(); 504 normalized = uri.getPath(); 505 } 506 catch (URISyntaxException e) 507 { 508 return path; 509 } 510 511 if (isUNC) 512 { 513 // Add the \\ and server name back to the normalized UNC 514 // file path 515 normalized = "\\\\" + uncServer + 516 normalized.replace('/', '\\'); 517 } 518 } 519 else 520 { 521 // Relative path (doesn't start with / or \ or a Windows 522 // drive letter) 523 524 String uriPath = path.replace('\\', '/'); 525 526 try 527 { 528 URI uri = new URI("file:/" + uriPath); 529 uri = uri.normalize(); 530 531 // Need to remove the leading forward slash 532 normalized = uri.getPath().substring(1); 533 } 534 catch (URISyntaxException e) 535 { 536 return path; 537 } 538 } 539 } 540 else 541 { 542 // Unix path 543 544 String uriPath = path; 545 546 // Replace any backslashes in the path with the percent encoded 547 // string for a backslash, %5C, as can't construct a URI for a 548 // path containing a backslash 549 550 int backslashIndex = path.indexOf('\\'); 551 552 if (backslashIndex > -1) 553 { 554 uriPath = uriPath.replaceAll("\\\\", "%5C"); 555 } 556 557 // Determine if it's an absolute or normal path 558 559 if (uriPath.charAt(0) == '/') 560 { 561 // Absolute path 562 563 try 564 { 565 URI uri = new URI("file:" + uriPath); 566 uri = uri.normalize(); 567 normalized = uri.getPath(); 568 } 569 catch (URISyntaxException e) 570 { 571 return path; 572 } 573 } 574 else 575 { 576 // Relative path (doesn't start with /) 577 578 try 579 { 580 URI uri = new URI("file:/" + uriPath); 581 uri = uri.normalize(); 582 583 // Need to remove the leading forward slash 584 normalized = uri.getPath().substring(1); 585 } 586 catch (URISyntaxException e) 587 { 588 return path; 589 } 590 } 591 } 592 593 if (normalized == null) 594 { 595 return path; 596 } 597 598 return normalized; 599 } 600 isRelativePath(String path, String fileSeparator)601 public static boolean isRelativePath(String path, String fileSeparator) 602 { 603 // Check if a relative or absolute path was specified. 604 // - On Unix, an absolute path starts with a slash or a tilde (~) 605 // and a relative path does not. 606 // - On Windows, an absolute path starts with a slash or a backslash, 607 // or with a drive specification, 'x:/' or 'x:\', where x is the 608 // drive letter. Also, even if 'x:' is not followed by a / or \, 609 // assume absolute since we won't be able to prepend a parent 610 // directory 611 // - Also, assume it's an absolute file name if it's null or an empty 612 // string, regardless of operating system. 613 // If it isn't an absolute path, it's a relative path. 614 615 if ((path == null) || (path.length() == 0)) 616 { 617 // Assume absolute file name 618 return false; 619 } 620 621 if (fileSeparator.equals("\\")) 622 { 623 // Windows path 624 625 if ((path.charAt(0) == '\\') || (path.charAt(0) == '/')) 626 { 627 // Absolute file name because starts with a slash or backslash 628 return false; 629 } 630 else if ((path.length() > 1) && 631 (path.charAt(1) == ':') && 632 ((path.charAt(0) >= 'a' && path.charAt(0) <= 'z') || 633 (path.charAt(0) >= 'A' && path.charAt(0) <= 'Z'))) 634 { 635 // Absolute file name because starts with a drive specification 636 return false; 637 } 638 } 639 else 640 { 641 // Unix path 642 643 if ((path.charAt(0) == '/') || (path.charAt(0) == '~')) 644 { 645 // Absolute file name because starts with a slash or tilde 646 return false; 647 } 648 } 649 650 // File name is a relative path since its not an absolute path 651 652 return true; 653 } 654 655 /** 656 * Returns the parent path for the specified file name. 657 * @param A string containing the file name 658 * @param A string containing the file separator for the operating system 659 * of the machine where the file resides 660 */ getParentPath(String fileName, String fileSeparator)661 public static String getParentPath(String fileName, String fileSeparator) 662 { 663 int endParentPathIndex = -1; 664 665 if (fileSeparator.equals("\\")) 666 { 667 // Windows filename 668 669 // Check if the file name ends in a \ or / (and isn't just \ or /) 670 // and if so, remove the trailing file separator 671 672 if ((fileName.length() > 1) && 673 ((fileName.endsWith("\\")) || (fileName.endsWith("/")))) 674 { 675 fileName = fileName.substring(0, fileName.length() - 1); 676 } 677 678 // Find the last slash or backslash in the file name and assign 679 // everything before it, including the last slash or backslash 680 681 int lastSlashIndex = fileName.lastIndexOf('/'); 682 int lastBackslashIndex = fileName.lastIndexOf('\\'); 683 684 if (lastSlashIndex >= lastBackslashIndex) 685 endParentPathIndex = lastSlashIndex; 686 else 687 endParentPathIndex = lastBackslashIndex; 688 } 689 else 690 { 691 // Unix filename 692 693 // Check if the file name ends with the file separator (and isn't 694 // just "/") and if so, remove the trailing file separator 695 696 if ((fileName.length() > 1) && (fileName.endsWith(fileSeparator))) 697 { 698 fileName = fileName.substring(0, fileName.length() - 1); 699 } 700 701 // Find the last slash in the file name and assign everything 702 // before it, including the last slash 703 704 endParentPathIndex = fileName.lastIndexOf('/'); 705 } 706 707 if (endParentPathIndex == -1) 708 return ""; 709 710 return fileName.substring(0, endParentPathIndex + 1); 711 } 712 } 713