1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18/* 19 * $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $ 20 */ 21package org.apache.xalan.xslt; 22 23import java.io.File; 24import java.io.FileWriter; 25import java.io.PrintWriter; 26import java.lang.reflect.Field; 27import java.lang.reflect.Method; 28import java.util.Enumeration; 29import java.util.Hashtable; 30import java.util.StringTokenizer; 31import java.util.Vector; 32 33import org.w3c.dom.Document; 34import org.w3c.dom.Element; 35import org.w3c.dom.Node; 36 37/** 38 * Utility class to report simple information about the environment. 39 * Simplistic reporting about certain classes found in your JVM may 40 * help answer some FAQs for simple problems. 41 * 42 * <p>Usage-command line: 43 * <code> 44 * java org.apache.xalan.xslt.EnvironmentCheck [-out outFile] 45 * </code></p> 46 * 47 * <p>Usage-from program: 48 * <code> 49 * boolean environmentOK = 50 * (new EnvironmentCheck()).checkEnvironment(yourPrintWriter); 51 * </code></p> 52 * 53 * <p>Usage-from stylesheet: 54 * <code><pre> 55 * <?xml version="1.0"?> 56 * <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" 57 * xmlns:xalan="http://xml.apache.org/xalan" 58 * exclude-result-prefixes="xalan"> 59 * <xsl:output indent="yes"/> 60 * <xsl:template match="/"> 61 * <xsl:copy-of select="xalan:checkEnvironment()"/> 62 * </xsl:template> 63 * </xsl:stylesheet> 64 * </pre></code></p> 65 * 66 * <p>Xalan users reporting problems are encouraged to use this class 67 * to see if there are potential problems with their actual 68 * Java environment <b>before</b> reporting a bug. Note that you 69 * should both check from the JVM/JRE's command line as well as 70 * temporarily calling checkEnvironment() directly from your code, 71 * since the classpath may differ (especially for servlets, etc).</p> 72 * 73 * <p>Also see http://xml.apache.org/xalan-j/faq.html</p> 74 * 75 * <p>Note: This class is pretty simplistic: 76 * results are not necessarily definitive nor will it find all 77 * problems related to environment setup. Also, you should avoid 78 * calling this in deployed production code, both because it is 79 * quite slow and because it forces classes to get loaded.</p> 80 * 81 * <p>Note: This class explicitly has very limited compile-time 82 * dependencies to enable easy compilation and usage even when 83 * Xalan, DOM/SAX/JAXP, etc. are not present.</p> 84 * 85 * <p>Note: for an improved version of this utility, please see 86 * the xml-commons' project Which utility which does the same kind 87 * of thing but in a much simpler manner.</p> 88 * 89 * @author Shane_Curcuru@us.ibm.com 90 * @version $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $ 91 */ 92public class EnvironmentCheck 93{ 94 95 /** 96 * Command line runnability: checks for [-out outFilename] arg. 97 * <p>Command line entrypoint; Sets output and calls 98 * {@link #checkEnvironment(PrintWriter)}.</p> 99 * @param args command line args 100 */ 101 public static void main(String[] args) 102 { 103 // Default to System.out, autoflushing 104 PrintWriter sendOutputTo = new PrintWriter(System.out, true); 105 106 // Read our simplistic input args, if supplied 107 for (int i = 0; i < args.length; i++) 108 { 109 if ("-out".equalsIgnoreCase(args[i])) 110 { 111 i++; 112 113 if (i < args.length) 114 { 115 try 116 { 117 sendOutputTo = new PrintWriter(new FileWriter(args[i], true)); 118 } 119 catch (Exception e) 120 { 121 System.err.println("# WARNING: -out " + args[i] + " threw " 122 + e.toString()); 123 } 124 } 125 else 126 { 127 System.err.println( 128 "# WARNING: -out argument should have a filename, output sent to console"); 129 } 130 } 131 } 132 133 EnvironmentCheck app = new EnvironmentCheck(); 134 app.checkEnvironment(sendOutputTo); 135 } 136 137 /** 138 * Programmatic entrypoint: Report on basic Java environment 139 * and CLASSPATH settings that affect Xalan. 140 * 141 * <p>Note that this class is not advanced enough to tell you 142 * everything about the environment that affects Xalan, and 143 * sometimes reports errors that will not actually affect 144 * Xalan's behavior. Currently, it very simplistically 145 * checks the JVM's environment for some basic properties and 146 * logs them out; it will report a problem if it finds a setting 147 * or .jar file that is <i>likely</i> to cause problems.</p> 148 * 149 * <p>Advanced users can peruse the code herein to help them 150 * investigate potential environment problems found; other users 151 * may simply send the output from this tool along with any bugs 152 * they submit to help us in the debugging process.</p> 153 * 154 * @param pw PrintWriter to send output to; can be sent to a 155 * file that will look similar to a Properties file; defaults 156 * to System.out if null 157 * @return true if your environment appears to have no major 158 * problems; false if potential environment problems found 159 * @see #getEnvironmentHash() 160 */ 161 public boolean checkEnvironment(PrintWriter pw) 162 { 163 164 // Use user-specified output writer if non-null 165 if (null != pw) 166 outWriter = pw; 167 168 // Setup a hash to store various environment information in 169 Hashtable hash = getEnvironmentHash(); 170 171 // Check for ERROR keys in the hashtable, and print report 172 boolean environmentHasErrors = writeEnvironmentReport(hash); 173 174 if (environmentHasErrors) 175 { 176 // Note: many logMsg calls have # at the start to 177 // fake a property-file like output 178 logMsg("# WARNING: Potential problems found in your environment!"); 179 logMsg("# Check any 'ERROR' items above against the Xalan FAQs"); 180 logMsg("# to correct potential problems with your classes/jars"); 181 logMsg("# http://xml.apache.org/xalan-j/faq.html"); 182 if (null != outWriter) 183 outWriter.flush(); 184 return false; 185 } 186 else 187 { 188 logMsg("# YAHOO! Your environment seems to be OK."); 189 if (null != outWriter) 190 outWriter.flush(); 191 return true; 192 } 193 } 194 195 /** 196 * Fill a hash with basic environment settings that affect Xalan. 197 * 198 * <p>Worker method called from various places.</p> 199 * <p>Various system and CLASSPATH, etc. properties are put into 200 * the hash as keys with a brief description of the current state 201 * of that item as the value. Any serious problems will be put in 202 * with a key that is prefixed with {@link #ERROR 'ERROR.'} so it 203 * stands out in any resulting report; also a key with just that 204 * constant will be set as well for any error.</p> 205 * <p>Note that some legitimate cases are flaged as potential 206 * errors - namely when a developer recompiles xalan.jar on their 207 * own - and even a non-error state doesn't guaruntee that 208 * everything in the environment is correct. But this will help 209 * point out the most common classpath and system property 210 * problems that we've seen.</p> 211 * 212 * @return Hashtable full of useful environment info about Xalan 213 * and related system properties, etc. 214 */ 215 public Hashtable getEnvironmentHash() 216 { 217 // Setup a hash to store various environment information in 218 Hashtable hash = new Hashtable(); 219 220 // Call various worker methods to fill in the hash 221 // These are explicitly separate for maintenance and so 222 // advanced users could call them standalone 223 checkJAXPVersion(hash); 224 checkProcessorVersion(hash); 225 checkParserVersion(hash); 226 checkAntVersion(hash); 227 checkDOMVersion(hash); 228 checkSAXVersion(hash); 229 checkSystemProperties(hash); 230 231 return hash; 232 } 233 234 /** 235 * Dump a basic Xalan environment report to outWriter. 236 * 237 * <p>This dumps a simple header and then each of the entries in 238 * the Hashtable to our PrintWriter; it does special processing 239 * for entries that are .jars found in the classpath.</p> 240 * 241 * @param h Hashtable of items to report on; presumably 242 * filled in by our various check*() methods 243 * @return true if your environment appears to have no major 244 * problems; false if potential environment problems found 245 * @see #appendEnvironmentReport(Node, Document, Hashtable) 246 * for an equivalent that appends to a Node instead 247 */ 248 protected boolean writeEnvironmentReport(Hashtable h) 249 { 250 251 if (null == h) 252 { 253 logMsg("# ERROR: writeEnvironmentReport called with null Hashtable"); 254 return false; 255 } 256 257 boolean errors = false; 258 259 logMsg( 260 "#---- BEGIN writeEnvironmentReport($Revision: 468646 $): Useful stuff found: ----"); 261 262 // Fake the Properties-like output 263 for (Enumeration keys = h.keys(); 264 keys.hasMoreElements(); 265 /* no increment portion */ 266 ) 267 { 268 Object key = keys.nextElement(); 269 String keyStr = (String) key; 270 try 271 { 272 // Special processing for classes found.. 273 if (keyStr.startsWith(FOUNDCLASSES)) 274 { 275 Vector v = (Vector) h.get(keyStr); 276 errors |= logFoundJars(v, keyStr); 277 } 278 // ..normal processing for all other entries 279 else 280 { 281 // Note: we could just check for the ERROR key by itself, 282 // since we now set that, but since we have to go 283 // through the whole hash anyway, do it this way, 284 // which is safer for maintenance 285 if (keyStr.startsWith(ERROR)) 286 { 287 errors = true; 288 } 289 logMsg(keyStr + "=" + h.get(keyStr)); 290 } 291 } 292 catch (Exception e) 293 { 294 logMsg("Reading-" + key + "= threw: " + e.toString()); 295 } 296 } 297 298 logMsg( 299 "#----- END writeEnvironmentReport: Useful properties found: -----"); 300 301 return errors; 302 } 303 304 /** Prefixed to hash keys that signify serious problems. */ 305 public static final String ERROR = "ERROR."; 306 307 /** Added to descriptions that signify potential problems. */ 308 public static final String WARNING = "WARNING."; 309 310 /** Value for any error found. */ 311 public static final String ERROR_FOUND = "At least one error was found!"; 312 313 /** Prefixed to hash keys that signify version numbers. */ 314 public static final String VERSION = "version."; 315 316 /** Prefixed to hash keys that signify .jars found in classpath. */ 317 public static final String FOUNDCLASSES = "foundclasses."; 318 319 /** Marker that a class or .jar was found. */ 320 public static final String CLASS_PRESENT = "present-unknown-version"; 321 322 /** Marker that a class or .jar was not found. */ 323 public static final String CLASS_NOTPRESENT = "not-present"; 324 325 /** Listing of common .jar files that include Xalan-related classes. */ 326 public String[] jarNames = 327 { 328 "xalan.jar", "xalansamples.jar", "xalanj1compat.jar", "xalanservlet.jar", 329 "serializer.jar", // Serializer (shared between Xalan & Xerces) 330 "xerces.jar", // Xerces-J 1.x 331 "xercesImpl.jar", // Xerces-J 2.x 332 "testxsl.jar", 333 "crimson.jar", 334 "lotusxsl.jar", 335 "jaxp.jar", "parser.jar", "dom.jar", "sax.jar", "xml.jar", 336 "xml-apis.jar", 337 "xsltc.jar" 338 }; 339 340 /** 341 * Print out report of .jars found in a classpath. 342 * 343 * Takes the information encoded from a checkPathForJars() 344 * call and dumps it out to our PrintWriter. 345 * 346 * @param v Vector of Hashtables of .jar file info 347 * @param desc description to print out in header 348 * 349 * @return false if OK, true if any .jars were reported 350 * as having errors 351 * @see #checkPathForJars(String, String[]) 352 */ 353 protected boolean logFoundJars(Vector v, String desc) 354 { 355 356 if ((null == v) || (v.size() < 1)) 357 return false; 358 359 boolean errors = false; 360 361 logMsg("#---- BEGIN Listing XML-related jars in: " + desc + " ----"); 362 363 for (int i = 0; i < v.size(); i++) 364 { 365 Hashtable subhash = (Hashtable) v.elementAt(i); 366 367 for (Enumeration keys = subhash.keys(); 368 keys.hasMoreElements(); 369 /* no increment portion */ 370 ) 371 { 372 Object key = keys.nextElement(); 373 String keyStr = (String) key; 374 try 375 { 376 if (keyStr.startsWith(ERROR)) 377 { 378 errors = true; 379 } 380 logMsg(keyStr + "=" + subhash.get(keyStr)); 381 382 } 383 catch (Exception e) 384 { 385 errors = true; 386 logMsg("Reading-" + key + "= threw: " + e.toString()); 387 } 388 } 389 } 390 391 logMsg("#----- END Listing XML-related jars in: " + desc + " -----"); 392 393 return errors; 394 } 395 396 /** 397 * Stylesheet extension entrypoint: Dump a basic Xalan 398 * environment report from getEnvironmentHash() to a Node. 399 * 400 * <p>Copy of writeEnvironmentReport that creates a Node suitable 401 * for other processing instead of a properties-like text output. 402 * </p> 403 * @param container Node to append our report to 404 * @param factory Document providing createElement, etc. services 405 * @param h Hash presumably from {@link #getEnvironmentHash()} 406 * @see #writeEnvironmentReport(Hashtable) 407 * for an equivalent that writes to a PrintWriter instead 408 */ 409 public void appendEnvironmentReport(Node container, Document factory, Hashtable h) 410 { 411 if ((null == container) || (null == factory)) 412 { 413 return; 414 } 415 416 try 417 { 418 Element envCheckNode = factory.createElement("EnvironmentCheck"); 419 envCheckNode.setAttribute("version", "$Revision: 468646 $"); 420 container.appendChild(envCheckNode); 421 422 if (null == h) 423 { 424 Element statusNode = factory.createElement("status"); 425 statusNode.setAttribute("result", "ERROR"); 426 statusNode.appendChild(factory.createTextNode("appendEnvironmentReport called with null Hashtable!")); 427 envCheckNode.appendChild(statusNode); 428 return; 429 } 430 431 boolean errors = false; 432 433 Element hashNode = factory.createElement("environment"); 434 envCheckNode.appendChild(hashNode); 435 436 for (Enumeration keys = h.keys(); 437 keys.hasMoreElements(); 438 /* no increment portion */ 439 ) 440 { 441 Object key = keys.nextElement(); 442 String keyStr = (String) key; 443 try 444 { 445 // Special processing for classes found.. 446 if (keyStr.startsWith(FOUNDCLASSES)) 447 { 448 Vector v = (Vector) h.get(keyStr); 449 // errors |= logFoundJars(v, keyStr); 450 errors |= appendFoundJars(hashNode, factory, v, keyStr); 451 } 452 // ..normal processing for all other entries 453 else 454 { 455 // Note: we could just check for the ERROR key by itself, 456 // since we now set that, but since we have to go 457 // through the whole hash anyway, do it this way, 458 // which is safer for maintenance 459 if (keyStr.startsWith(ERROR)) 460 { 461 errors = true; 462 } 463 Element node = factory.createElement("item"); 464 node.setAttribute("key", keyStr); 465 node.appendChild(factory.createTextNode((String)h.get(keyStr))); 466 hashNode.appendChild(node); 467 } 468 } 469 catch (Exception e) 470 { 471 errors = true; 472 Element node = factory.createElement("item"); 473 node.setAttribute("key", keyStr); 474 node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString())); 475 hashNode.appendChild(node); 476 } 477 } // end of for... 478 479 Element statusNode = factory.createElement("status"); 480 statusNode.setAttribute("result", (errors ? "ERROR" : "OK" )); 481 envCheckNode.appendChild(statusNode); 482 } 483 catch (Exception e2) 484 { 485 System.err.println("appendEnvironmentReport threw: " + e2.toString()); 486 e2.printStackTrace(); 487 } 488 } 489 490 /** 491 * Print out report of .jars found in a classpath. 492 * 493 * Takes the information encoded from a checkPathForJars() 494 * call and dumps it out to our PrintWriter. 495 * 496 * @param container Node to append our report to 497 * @param factory Document providing createElement, etc. services 498 * @param v Vector of Hashtables of .jar file info 499 * @param desc description to print out in header 500 * 501 * @return false if OK, true if any .jars were reported 502 * as having errors 503 * @see #checkPathForJars(String, String[]) 504 */ 505 protected boolean appendFoundJars(Node container, Document factory, 506 Vector v, String desc) 507 { 508 509 if ((null == v) || (v.size() < 1)) 510 return false; 511 512 boolean errors = false; 513 514 for (int i = 0; i < v.size(); i++) 515 { 516 Hashtable subhash = (Hashtable) v.elementAt(i); 517 518 for (Enumeration keys = subhash.keys(); 519 keys.hasMoreElements(); 520 /* no increment portion */ 521 ) 522 { 523 Object key = keys.nextElement(); 524 try 525 { 526 String keyStr = (String) key; 527 if (keyStr.startsWith(ERROR)) 528 { 529 errors = true; 530 } 531 Element node = factory.createElement("foundJar"); 532 node.setAttribute("name", keyStr.substring(0, keyStr.indexOf("-"))); 533 node.setAttribute("desc", keyStr.substring(keyStr.indexOf("-") + 1)); 534 node.appendChild(factory.createTextNode((String)subhash.get(keyStr))); 535 container.appendChild(node); 536 } 537 catch (Exception e) 538 { 539 errors = true; 540 Element node = factory.createElement("foundJar"); 541 node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString())); 542 container.appendChild(node); 543 } 544 } 545 } 546 return errors; 547 } 548 549 /** 550 * Fillin hash with info about SystemProperties. 551 * 552 * Logs java.class.path and other likely paths; then attempts 553 * to search those paths for .jar files with Xalan-related classes. 554 * 555 * //@todo NOTE: We don't actually search java.ext.dirs for 556 * // *.jar files therein! This should be updated 557 * 558 * @param h Hashtable to put information in 559 * @see #jarNames 560 * @see #checkPathForJars(String, String[]) 561 */ 562 protected void checkSystemProperties(Hashtable h) 563 { 564 565 if (null == h) 566 h = new Hashtable(); 567 568 // Grab java version for later use 569 try 570 { 571 String javaVersion = System.getProperty("java.version"); 572 573 h.put("java.version", javaVersion); 574 } 575 catch (SecurityException se) 576 { 577 578 // For applet context, etc. 579 h.put( 580 "java.version", 581 "WARNING: SecurityException thrown accessing system version properties"); 582 } 583 584 // Printout jar files on classpath(s) that may affect operation 585 // Do this in order 586 try 587 { 588 589 // This is present in all JVM's 590 String cp = System.getProperty("java.class.path"); 591 592 h.put("java.class.path", cp); 593 594 Vector classpathJars = checkPathForJars(cp, jarNames); 595 596 if (null != classpathJars) 597 h.put(FOUNDCLASSES + "java.class.path", classpathJars); 598 599 // Also check for JDK 1.2+ type classpaths 600 String othercp = System.getProperty("sun.boot.class.path"); 601 602 if (null != othercp) 603 { 604 h.put("sun.boot.class.path", othercp); 605 606 classpathJars = checkPathForJars(othercp, jarNames); 607 608 if (null != classpathJars) 609 h.put(FOUNDCLASSES + "sun.boot.class.path", classpathJars); 610 } 611 612 //@todo NOTE: We don't actually search java.ext.dirs for 613 // *.jar files therein! This should be updated 614 othercp = System.getProperty("java.ext.dirs"); 615 616 if (null != othercp) 617 { 618 h.put("java.ext.dirs", othercp); 619 620 classpathJars = checkPathForJars(othercp, jarNames); 621 622 if (null != classpathJars) 623 h.put(FOUNDCLASSES + "java.ext.dirs", classpathJars); 624 } 625 626 //@todo also check other System properties' paths? 627 // v2 = checkPathForJars(System.getProperty("sun.boot.library.path"), jarNames); // ?? may not be needed 628 // v3 = checkPathForJars(System.getProperty("java.library.path"), jarNames); // ?? may not be needed 629 } 630 catch (SecurityException se2) 631 { 632 // For applet context, etc. 633 h.put( 634 "java.class.path", 635 "WARNING: SecurityException thrown accessing system classpath properties"); 636 } 637 } 638 639 /** 640 * Cheap-o listing of specified .jars found in the classpath. 641 * 642 * cp should be separated by the usual File.pathSeparator. We 643 * then do a simplistic search of the path for any requested 644 * .jar filenames, and return a listing of their names and 645 * where (apparently) they came from. 646 * 647 * @param cp classpath to search 648 * @param jars array of .jar base filenames to look for 649 * 650 * @return Vector of Hashtables filled with info about found .jars 651 * @see #jarNames 652 * @see #logFoundJars(Vector, String) 653 * @see #appendFoundJars(Node, Document, Vector, String ) 654 * @see #getApparentVersion(String, long) 655 */ 656 protected Vector checkPathForJars(String cp, String[] jars) 657 { 658 659 if ((null == cp) || (null == jars) || (0 == cp.length()) 660 || (0 == jars.length)) 661 return null; 662 663 Vector v = new Vector(); 664 StringTokenizer st = new StringTokenizer(cp, File.pathSeparator); 665 666 while (st.hasMoreTokens()) 667 { 668 669 // Look at each classpath entry for each of our requested jarNames 670 String filename = st.nextToken(); 671 672 for (int i = 0; i < jars.length; i++) 673 { 674 if (filename.indexOf(jars[i]) > -1) 675 { 676 File f = new File(filename); 677 678 if (f.exists()) 679 { 680 681 // If any requested jarName exists, report on 682 // the details of that .jar file 683 try 684 { 685 Hashtable h = new Hashtable(2); 686 // Note "-" char is looked for in appendFoundJars 687 h.put(jars[i] + "-path", f.getAbsolutePath()); 688 689 // We won't bother reporting on the xalan.jar apparent version 690 // since this requires knowing the jar size of the xalan.jar 691 // before we build it. 692 // For other jars, eg. xml-apis.jar and xercesImpl.jar, we 693 // report the apparent version of the file we've found 694 if (!("xalan.jar".equalsIgnoreCase(jars[i]))) { 695 h.put(jars[i] + "-apparent.version", 696 getApparentVersion(jars[i], f.length())); 697 } 698 v.addElement(h); 699 } 700 catch (Exception e) 701 { 702 703 /* no-op, don't add it */ 704 } 705 } 706 else 707 { 708 Hashtable h = new Hashtable(2); 709 // Note "-" char is looked for in appendFoundJars 710 h.put(jars[i] + "-path", WARNING + " Classpath entry: " 711 + filename + " does not exist"); 712 h.put(jars[i] + "-apparent.version", CLASS_NOTPRESENT); 713 v.addElement(h); 714 } 715 } 716 } 717 } 718 719 return v; 720 } 721 722 /** 723 * Cheap-o method to determine the product version of a .jar. 724 * 725 * Currently does a lookup into a local table of some recent 726 * shipped Xalan builds to determine where the .jar probably 727 * came from. Note that if you recompile Xalan or Xerces 728 * yourself this will likely report a potential error, since 729 * we can't certify builds other than the ones we ship. 730 * Only reports against selected posted Xalan-J builds. 731 * 732 * //@todo actually look up version info in manifests 733 * 734 * @param jarName base filename of the .jarfile 735 * @param jarSize size of the .jarfile 736 * 737 * @return String describing where the .jar file probably 738 * came from 739 */ 740 protected String getApparentVersion(String jarName, long jarSize) 741 { 742 // If we found a matching size and it's for our 743 // jar, then return it's description 744 // Lookup in static jarVersions Hashtable 745 String foundSize = (String) jarVersions.get(new Long(jarSize)); 746 747 if ((null != foundSize) && (foundSize.startsWith(jarName))) 748 { 749 return foundSize; 750 } 751 else 752 { 753 if ("xerces.jar".equalsIgnoreCase(jarName) 754 || "xercesImpl.jar".equalsIgnoreCase(jarName)) 755// || "xalan.jar".equalsIgnoreCase(jarName)) 756 { 757 758 // For xalan.jar and xerces.jar/xercesImpl.jar, which we ship together: 759 // The jar is not from a shipped copy of xalan-j, so 760 // it's up to the user to ensure that it's compatible 761 return jarName + " " + WARNING + CLASS_PRESENT; 762 } 763 else 764 { 765 766 // Otherwise, it's just a jar we don't have the version info calculated for 767 return jarName + " " + CLASS_PRESENT; 768 } 769 } 770 } 771 772 /** 773 * Report version information about JAXP interfaces. 774 * 775 * Currently distinguishes between JAXP 1.0.1 and JAXP 1.1, 776 * and not found; only tests the interfaces, and does not 777 * check for reference implementation versions. 778 * 779 * @param h Hashtable to put information in 780 */ 781 protected void checkJAXPVersion(Hashtable h) 782 { 783 784 if (null == h) 785 h = new Hashtable(); 786 787 final Class noArgs[] = new Class[0]; 788 Class clazz = null; 789 790 try 791 { 792 final String JAXP1_CLASS = "javax.xml.parsers.DocumentBuilder"; 793 final String JAXP11_METHOD = "getDOMImplementation"; 794 795 clazz = ObjectFactory.findProviderClass( 796 JAXP1_CLASS, ObjectFactory.findClassLoader(), true); 797 798 Method method = clazz.getMethod(JAXP11_METHOD, noArgs); 799 800 // If we succeeded, we at least have JAXP 1.1 available 801 h.put(VERSION + "JAXP", "1.1 or higher"); 802 } 803 catch (Exception e) 804 { 805 if (null != clazz) 806 { 807 808 // We must have found the class itself, just not the 809 // method, so we (probably) have JAXP 1.0.1 810 h.put(ERROR + VERSION + "JAXP", "1.0.1"); 811 h.put(ERROR, ERROR_FOUND); 812 } 813 else 814 { 815 // We couldn't even find the class, and don't have 816 // any JAXP support at all, or only have the 817 // transform half of it 818 h.put(ERROR + VERSION + "JAXP", CLASS_NOTPRESENT); 819 h.put(ERROR, ERROR_FOUND); 820 } 821 } 822 } 823 824 /** 825 * Report product version information from Xalan-J. 826 * 827 * Looks for version info in xalan.jar from Xalan-J products. 828 * 829 * @param h Hashtable to put information in 830 */ 831 protected void checkProcessorVersion(Hashtable h) 832 { 833 834 if (null == h) 835 h = new Hashtable(); 836 837 try 838 { 839 final String XALAN1_VERSION_CLASS = 840 "org.apache.xalan.xslt.XSLProcessorVersion"; 841 842 Class clazz = ObjectFactory.findProviderClass( 843 XALAN1_VERSION_CLASS, ObjectFactory.findClassLoader(), true); 844 845 // Found Xalan-J 1.x, grab it's version fields 846 StringBuffer buf = new StringBuffer(); 847 Field f = clazz.getField("PRODUCT"); 848 849 buf.append(f.get(null)); 850 buf.append(';'); 851 852 f = clazz.getField("LANGUAGE"); 853 854 buf.append(f.get(null)); 855 buf.append(';'); 856 857 f = clazz.getField("S_VERSION"); 858 859 buf.append(f.get(null)); 860 buf.append(';'); 861 h.put(VERSION + "xalan1", buf.toString()); 862 } 863 catch (Exception e1) 864 { 865 h.put(VERSION + "xalan1", CLASS_NOTPRESENT); 866 } 867 868 try 869 { 870 // NOTE: This is the old Xalan 2.0, 2.1, 2.2 version class, 871 // is being replaced by class below 872 final String XALAN2_VERSION_CLASS = 873 "org.apache.xalan.processor.XSLProcessorVersion"; 874 875 Class clazz = ObjectFactory.findProviderClass( 876 XALAN2_VERSION_CLASS, ObjectFactory.findClassLoader(), true); 877 878 // Found Xalan-J 2.x, grab it's version fields 879 StringBuffer buf = new StringBuffer(); 880 Field f = clazz.getField("S_VERSION"); 881 buf.append(f.get(null)); 882 883 h.put(VERSION + "xalan2x", buf.toString()); 884 } 885 catch (Exception e2) 886 { 887 h.put(VERSION + "xalan2x", CLASS_NOTPRESENT); 888 } 889 try 890 { 891 // NOTE: This is the new Xalan 2.2+ version class 892 final String XALAN2_2_VERSION_CLASS = 893 "org.apache.xalan.Version"; 894 final String XALAN2_2_VERSION_METHOD = "getVersion"; 895 final Class noArgs[] = new Class[0]; 896 897 Class clazz = ObjectFactory.findProviderClass( 898 XALAN2_2_VERSION_CLASS, ObjectFactory.findClassLoader(), true); 899 900 Method method = clazz.getMethod(XALAN2_2_VERSION_METHOD, noArgs); 901 Object returnValue = method.invoke(null, new Object[0]); 902 903 h.put(VERSION + "xalan2_2", (String)returnValue); 904 } 905 catch (Exception e2) 906 { 907 h.put(VERSION + "xalan2_2", CLASS_NOTPRESENT); 908 } 909 } 910 911 /** 912 * Report product version information from common parsers. 913 * 914 * Looks for version info in xerces.jar/xercesImpl.jar/crimson.jar. 915 * 916 * //@todo actually look up version info in crimson manifest 917 * 918 * @param h Hashtable to put information in 919 */ 920 protected void checkParserVersion(Hashtable h) 921 { 922 923 if (null == h) 924 h = new Hashtable(); 925 926 try 927 { 928 final String XERCES1_VERSION_CLASS = "org.apache.xerces.framework.Version"; 929 930 Class clazz = ObjectFactory.findProviderClass( 931 XERCES1_VERSION_CLASS, ObjectFactory.findClassLoader(), true); 932 933 // Found Xerces-J 1.x, grab it's version fields 934 Field f = clazz.getField("fVersion"); 935 String parserVersion = (String) f.get(null); 936 937 h.put(VERSION + "xerces1", parserVersion); 938 } 939 catch (Exception e) 940 { 941 h.put(VERSION + "xerces1", CLASS_NOTPRESENT); 942 } 943 944 // Look for xerces1 and xerces2 parsers separately 945 try 946 { 947 final String XERCES2_VERSION_CLASS = "org.apache.xerces.impl.Version"; 948 949 Class clazz = ObjectFactory.findProviderClass( 950 XERCES2_VERSION_CLASS, ObjectFactory.findClassLoader(), true); 951 952 // Found Xerces-J 2.x, grab it's version fields 953 Field f = clazz.getField("fVersion"); 954 String parserVersion = (String) f.get(null); 955 956 h.put(VERSION + "xerces2", parserVersion); 957 } 958 catch (Exception e) 959 { 960 h.put(VERSION + "xerces2", CLASS_NOTPRESENT); 961 } 962 963 try 964 { 965 final String CRIMSON_CLASS = "org.apache.crimson.parser.Parser2"; 966 967 Class clazz = ObjectFactory.findProviderClass( 968 CRIMSON_CLASS, ObjectFactory.findClassLoader(), true); 969 970 //@todo determine specific crimson version 971 h.put(VERSION + "crimson", CLASS_PRESENT); 972 } 973 catch (Exception e) 974 { 975 h.put(VERSION + "crimson", CLASS_NOTPRESENT); 976 } 977 } 978 979 /** 980 * Report product version information from Ant. 981 * 982 * @param h Hashtable to put information in 983 */ 984 protected void checkAntVersion(Hashtable h) 985 { 986 987 if (null == h) 988 h = new Hashtable(); 989 990 try 991 { 992 final String ANT_VERSION_CLASS = "org.apache.tools.ant.Main"; 993 final String ANT_VERSION_METHOD = "getAntVersion"; // noArgs 994 final Class noArgs[] = new Class[0]; 995 996 Class clazz = ObjectFactory.findProviderClass( 997 ANT_VERSION_CLASS, ObjectFactory.findClassLoader(), true); 998 999 Method method = clazz.getMethod(ANT_VERSION_METHOD, noArgs); 1000 Object returnValue = method.invoke(null, new Object[0]); 1001 1002 h.put(VERSION + "ant", (String)returnValue); 1003 } 1004 catch (Exception e) 1005 { 1006 h.put(VERSION + "ant", CLASS_NOTPRESENT); 1007 } 1008 } 1009 1010 /** 1011 * Report version info from DOM interfaces. 1012 * 1013 * Currently distinguishes between pre-DOM level 2, the DOM 1014 * level 2 working draft, the DOM level 2 final draft, 1015 * and not found. 1016 * 1017 * @param h Hashtable to put information in 1018 */ 1019 protected void checkDOMVersion(Hashtable h) 1020 { 1021 1022 if (null == h) 1023 h = new Hashtable(); 1024 1025 final String DOM_LEVEL2_CLASS = "org.w3c.dom.Document"; 1026 final String DOM_LEVEL2_METHOD = "createElementNS"; // String, String 1027 final String DOM_LEVEL2WD_CLASS = "org.w3c.dom.Node"; 1028 final String DOM_LEVEL2WD_METHOD = "supported"; // String, String 1029 final String DOM_LEVEL2FD_CLASS = "org.w3c.dom.Node"; 1030 final String DOM_LEVEL2FD_METHOD = "isSupported"; // String, String 1031 final Class twoStringArgs[] = { java.lang.String.class, 1032 java.lang.String.class }; 1033 1034 try 1035 { 1036 Class clazz = ObjectFactory.findProviderClass( 1037 DOM_LEVEL2_CLASS, ObjectFactory.findClassLoader(), true); 1038 1039 Method method = clazz.getMethod(DOM_LEVEL2_METHOD, twoStringArgs); 1040 1041 // If we succeeded, we have loaded interfaces from a 1042 // level 2 DOM somewhere 1043 h.put(VERSION + "DOM", "2.0"); 1044 1045 try 1046 { 1047 // Check for the working draft version, which is 1048 // commonly found, but won't work anymore 1049 clazz = ObjectFactory.findProviderClass( 1050 DOM_LEVEL2WD_CLASS, ObjectFactory.findClassLoader(), true); 1051 1052 method = clazz.getMethod(DOM_LEVEL2WD_METHOD, twoStringArgs); 1053 1054 h.put(ERROR + VERSION + "DOM.draftlevel", "2.0wd"); 1055 h.put(ERROR, ERROR_FOUND); 1056 } 1057 catch (Exception e2) 1058 { 1059 try 1060 { 1061 // Check for the final draft version as well 1062 clazz = ObjectFactory.findProviderClass( 1063 DOM_LEVEL2FD_CLASS, ObjectFactory.findClassLoader(), true); 1064 1065 method = clazz.getMethod(DOM_LEVEL2FD_METHOD, twoStringArgs); 1066 1067 h.put(VERSION + "DOM.draftlevel", "2.0fd"); 1068 } 1069 catch (Exception e3) 1070 { 1071 h.put(ERROR + VERSION + "DOM.draftlevel", "2.0unknown"); 1072 h.put(ERROR, ERROR_FOUND); 1073 } 1074 } 1075 } 1076 catch (Exception e) 1077 { 1078 h.put(ERROR + VERSION + "DOM", 1079 "ERROR attempting to load DOM level 2 class: " + e.toString()); 1080 h.put(ERROR, ERROR_FOUND); 1081 } 1082 1083 //@todo load an actual DOM implmementation and query it as well 1084 //@todo load an actual DOM implmementation and check if 1085 // isNamespaceAware() == true, which is needed to parse 1086 // xsl stylesheet files into a DOM 1087 } 1088 1089 /** 1090 * Report version info from SAX interfaces. 1091 * 1092 * Currently distinguishes between SAX 2, SAX 2.0beta2, 1093 * SAX1, and not found. 1094 * 1095 * @param h Hashtable to put information in 1096 */ 1097 protected void checkSAXVersion(Hashtable h) 1098 { 1099 1100 if (null == h) 1101 h = new Hashtable(); 1102 1103 final String SAX_VERSION1_CLASS = "org.xml.sax.Parser"; 1104 final String SAX_VERSION1_METHOD = "parse"; // String 1105 final String SAX_VERSION2_CLASS = "org.xml.sax.XMLReader"; 1106 final String SAX_VERSION2_METHOD = "parse"; // String 1107 final String SAX_VERSION2BETA_CLASSNF = "org.xml.sax.helpers.AttributesImpl"; 1108 final String SAX_VERSION2BETA_METHODNF = "setAttributes"; // Attributes 1109 final Class oneStringArg[] = { java.lang.String.class }; 1110 // Note this introduces a minor compile dependency on SAX... 1111 final Class attributesArg[] = { org.xml.sax.Attributes.class }; 1112 1113 try 1114 { 1115 // This method was only added in the final SAX 2.0 release; 1116 // see changes.html "Changes from SAX 2.0beta2 to SAX 2.0prerelease" 1117 Class clazz = ObjectFactory.findProviderClass( 1118 SAX_VERSION2BETA_CLASSNF, ObjectFactory.findClassLoader(), true); 1119 1120 Method method = clazz.getMethod(SAX_VERSION2BETA_METHODNF, attributesArg); 1121 1122 // If we succeeded, we have loaded interfaces from a 1123 // real, final SAX version 2.0 somewhere 1124 h.put(VERSION + "SAX", "2.0"); 1125 } 1126 catch (Exception e) 1127 { 1128 // If we didn't find the SAX 2.0 class, look for a 2.0beta2 1129 h.put(ERROR + VERSION + "SAX", 1130 "ERROR attempting to load SAX version 2 class: " + e.toString()); 1131 h.put(ERROR, ERROR_FOUND); 1132 1133 try 1134 { 1135 Class clazz = ObjectFactory.findProviderClass( 1136 SAX_VERSION2_CLASS, ObjectFactory.findClassLoader(), true); 1137 1138 Method method = clazz.getMethod(SAX_VERSION2_METHOD, oneStringArg); 1139 1140 // If we succeeded, we have loaded interfaces from a 1141 // SAX version 2.0beta2 or earlier; these might work but 1142 // you should really have the final SAX 2.0 1143 h.put(VERSION + "SAX-backlevel", "2.0beta2-or-earlier"); 1144 } 1145 catch (Exception e2) 1146 { 1147 // If we didn't find the SAX 2.0beta2 class, look for a 1.0 one 1148 h.put(ERROR + VERSION + "SAX", 1149 "ERROR attempting to load SAX version 2 class: " + e.toString()); 1150 h.put(ERROR, ERROR_FOUND); 1151 1152 try 1153 { 1154 Class clazz = ObjectFactory.findProviderClass( 1155 SAX_VERSION1_CLASS, ObjectFactory.findClassLoader(), true); 1156 1157 Method method = clazz.getMethod(SAX_VERSION1_METHOD, oneStringArg); 1158 1159 // If we succeeded, we have loaded interfaces from a 1160 // SAX version 1.0 somewhere; which won't work very 1161 // well for JAXP 1.1 or beyond! 1162 h.put(VERSION + "SAX-backlevel", "1.0"); 1163 } 1164 catch (Exception e3) 1165 { 1166 // If we didn't find the SAX 2.0 class, look for a 1.0 one 1167 // Note that either 1.0 or no SAX are both errors 1168 h.put(ERROR + VERSION + "SAX-backlevel", 1169 "ERROR attempting to load SAX version 1 class: " + e3.toString()); 1170 1171 } 1172 } 1173 } 1174 } 1175 1176 /** 1177 * Manual table of known .jar sizes. 1178 * Only includes shipped versions of certain projects. 1179 * key=jarsize, value=jarname ' from ' distro name 1180 * Note assumption: two jars cannot have the same size! 1181 * 1182 * @see #getApparentVersion(String, long) 1183 */ 1184 private static Hashtable jarVersions = new Hashtable(); 1185 1186 /** 1187 * Static initializer for jarVersions table. 1188 * Doing this just once saves time and space. 1189 * 1190 * @see #getApparentVersion(String, long) 1191 */ 1192 static 1193 { 1194 // Note: hackish Hashtable, this could use improvement 1195 jarVersions.put(new Long(857192), "xalan.jar from xalan-j_1_1"); 1196 jarVersions.put(new Long(440237), "xalan.jar from xalan-j_1_2"); 1197 jarVersions.put(new Long(436094), "xalan.jar from xalan-j_1_2_1"); 1198 jarVersions.put(new Long(426249), "xalan.jar from xalan-j_1_2_2"); 1199 jarVersions.put(new Long(702536), "xalan.jar from xalan-j_2_0_0"); 1200 jarVersions.put(new Long(720930), "xalan.jar from xalan-j_2_0_1"); 1201 jarVersions.put(new Long(732330), "xalan.jar from xalan-j_2_1_0"); 1202 jarVersions.put(new Long(872241), "xalan.jar from xalan-j_2_2_D10"); 1203 jarVersions.put(new Long(882739), "xalan.jar from xalan-j_2_2_D11"); 1204 jarVersions.put(new Long(923866), "xalan.jar from xalan-j_2_2_0"); 1205 jarVersions.put(new Long(905872), "xalan.jar from xalan-j_2_3_D1"); 1206 jarVersions.put(new Long(906122), "xalan.jar from xalan-j_2_3_0"); 1207 jarVersions.put(new Long(906248), "xalan.jar from xalan-j_2_3_1"); 1208 jarVersions.put(new Long(983377), "xalan.jar from xalan-j_2_4_D1"); 1209 jarVersions.put(new Long(997276), "xalan.jar from xalan-j_2_4_0"); 1210 jarVersions.put(new Long(1031036), "xalan.jar from xalan-j_2_4_1"); 1211 // Stop recording xalan.jar sizes as of Xalan Java 2.5.0 1212 1213 jarVersions.put(new Long(596540), "xsltc.jar from xalan-j_2_2_0"); 1214 jarVersions.put(new Long(590247), "xsltc.jar from xalan-j_2_3_D1"); 1215 jarVersions.put(new Long(589914), "xsltc.jar from xalan-j_2_3_0"); 1216 jarVersions.put(new Long(589915), "xsltc.jar from xalan-j_2_3_1"); 1217 jarVersions.put(new Long(1306667), "xsltc.jar from xalan-j_2_4_D1"); 1218 jarVersions.put(new Long(1328227), "xsltc.jar from xalan-j_2_4_0"); 1219 jarVersions.put(new Long(1344009), "xsltc.jar from xalan-j_2_4_1"); 1220 jarVersions.put(new Long(1348361), "xsltc.jar from xalan-j_2_5_D1"); 1221 // Stop recording xsltc.jar sizes as of Xalan Java 2.5.0 1222 1223 jarVersions.put(new Long(1268634), "xsltc.jar-bundled from xalan-j_2_3_0"); 1224 1225 jarVersions.put(new Long(100196), "xml-apis.jar from xalan-j_2_2_0 or xalan-j_2_3_D1"); 1226 jarVersions.put(new Long(108484), "xml-apis.jar from xalan-j_2_3_0, or xalan-j_2_3_1 from xml-commons-1.0.b2"); 1227 jarVersions.put(new Long(109049), "xml-apis.jar from xalan-j_2_4_0 from xml-commons RIVERCOURT1 branch"); 1228 jarVersions.put(new Long(113749), "xml-apis.jar from xalan-j_2_4_1 from factoryfinder-build of xml-commons RIVERCOURT1"); 1229 jarVersions.put(new Long(124704), "xml-apis.jar from tck-jaxp-1_2_0 branch of xml-commons"); 1230 jarVersions.put(new Long(124724), "xml-apis.jar from tck-jaxp-1_2_0 branch of xml-commons, tag: xml-commons-external_1_2_01"); 1231 jarVersions.put(new Long(194205), "xml-apis.jar from head branch of xml-commons, tag: xml-commons-external_1_3_02"); 1232 1233 // If the below were more common I would update it to report 1234 // errors better; but this is so old hardly anyone has it 1235 jarVersions.put(new Long(424490), "xalan.jar from Xerces Tools releases - ERROR:DO NOT USE!"); 1236 1237 jarVersions.put(new Long(1591855), "xerces.jar from xalan-j_1_1 from xerces-1..."); 1238 jarVersions.put(new Long(1498679), "xerces.jar from xalan-j_1_2 from xerces-1_2_0.bin"); 1239 jarVersions.put(new Long(1484896), "xerces.jar from xalan-j_1_2_1 from xerces-1_2_1.bin"); 1240 jarVersions.put(new Long(804460), "xerces.jar from xalan-j_1_2_2 from xerces-1_2_2.bin"); 1241 jarVersions.put(new Long(1499244), "xerces.jar from xalan-j_2_0_0 from xerces-1_2_3.bin"); 1242 jarVersions.put(new Long(1605266), "xerces.jar from xalan-j_2_0_1 from xerces-1_3_0.bin"); 1243 jarVersions.put(new Long(904030), "xerces.jar from xalan-j_2_1_0 from xerces-1_4.bin"); 1244 jarVersions.put(new Long(904030), "xerces.jar from xerces-1_4_0.bin"); 1245 jarVersions.put(new Long(1802885), "xerces.jar from xerces-1_4_2.bin"); 1246 jarVersions.put(new Long(1734594), "xerces.jar from Xerces-J-bin.2.0.0.beta3"); 1247 jarVersions.put(new Long(1808883), "xerces.jar from xalan-j_2_2_D10,D11,D12 or xerces-1_4_3.bin"); 1248 jarVersions.put(new Long(1812019), "xerces.jar from xalan-j_2_2_0"); 1249 jarVersions.put(new Long(1720292), "xercesImpl.jar from xalan-j_2_3_D1"); 1250 jarVersions.put(new Long(1730053), "xercesImpl.jar from xalan-j_2_3_0 or xalan-j_2_3_1 from xerces-2_0_0"); 1251 jarVersions.put(new Long(1728861), "xercesImpl.jar from xalan-j_2_4_D1 from xerces-2_0_1"); 1252 jarVersions.put(new Long(972027), "xercesImpl.jar from xalan-j_2_4_0 from xerces-2_1"); 1253 jarVersions.put(new Long(831587), "xercesImpl.jar from xalan-j_2_4_1 from xerces-2_2"); 1254 jarVersions.put(new Long(891817), "xercesImpl.jar from xalan-j_2_5_D1 from xerces-2_3"); 1255 jarVersions.put(new Long(895924), "xercesImpl.jar from xerces-2_4"); 1256 jarVersions.put(new Long(1010806), "xercesImpl.jar from Xerces-J-bin.2.6.2"); 1257 jarVersions.put(new Long(1203860), "xercesImpl.jar from Xerces-J-bin.2.7.1"); 1258 1259 jarVersions.put(new Long(37485), "xalanj1compat.jar from xalan-j_2_0_0"); 1260 jarVersions.put(new Long(38100), "xalanj1compat.jar from xalan-j_2_0_1"); 1261 1262 jarVersions.put(new Long(18779), "xalanservlet.jar from xalan-j_2_0_0"); 1263 jarVersions.put(new Long(21453), "xalanservlet.jar from xalan-j_2_0_1"); 1264 jarVersions.put(new Long(24826), "xalanservlet.jar from xalan-j_2_3_1 or xalan-j_2_4_1"); 1265 jarVersions.put(new Long(24831), "xalanservlet.jar from xalan-j_2_4_1"); 1266 // Stop recording xalanservlet.jar sizes as of Xalan Java 2.5.0; now a .war file 1267 1268 // For those who've downloaded JAXP from sun 1269 jarVersions.put(new Long(5618), "jaxp.jar from jaxp1.0.1"); 1270 jarVersions.put(new Long(136133), "parser.jar from jaxp1.0.1"); 1271 jarVersions.put(new Long(28404), "jaxp.jar from jaxp-1.1"); 1272 jarVersions.put(new Long(187162), "crimson.jar from jaxp-1.1"); 1273 jarVersions.put(new Long(801714), "xalan.jar from jaxp-1.1"); 1274 jarVersions.put(new Long(196399), "crimson.jar from crimson-1.1.1"); 1275 jarVersions.put(new Long(33323), "jaxp.jar from crimson-1.1.1 or jakarta-ant-1.4.1b1"); 1276 jarVersions.put(new Long(152717), "crimson.jar from crimson-1.1.2beta2"); 1277 jarVersions.put(new Long(88143), "xml-apis.jar from crimson-1.1.2beta2"); 1278 jarVersions.put(new Long(206384), "crimson.jar from crimson-1.1.3 or jakarta-ant-1.4.1b1"); 1279 1280 // jakarta-ant: since many people use ant these days 1281 jarVersions.put(new Long(136198), "parser.jar from jakarta-ant-1.3 or 1.2"); 1282 jarVersions.put(new Long(5537), "jaxp.jar from jakarta-ant-1.3 or 1.2"); 1283 } 1284 1285 /** Simple PrintWriter we send output to; defaults to System.out. */ 1286 protected PrintWriter outWriter = new PrintWriter(System.out, true); 1287 1288 /** 1289 * Bottleneck output: calls outWriter.println(s). 1290 * @param s String to print 1291 */ 1292 protected void logMsg(String s) 1293 { 1294 outWriter.println(s); 1295 } 1296} 1297