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