1 /*
   2  * Copyright 2002-2004 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package build.tools.fontchecker;
  27 
  28 import java.io.*;
  29 import java.util.*;
  30 import java.awt.event.*;
  31 import sun.font.FontManager;
  32 
  33 /**
  34  * FontChecker.
  35  *
  36  * <PRE>
  37  * This is a FontChecker program. This class is a "parent" process
  38  * which invokes a "child" process. The child process will test
  39  * series of fonts and may crash as it encounters invalid fonts.
  40  * The "parent" process must then interpret error codes passed to it
  41  * by the "child" process and restart the "child" process if necessary.
  42  *
  43  * usage: java FontChecker [-v] -o outputfile
  44  *
  45  *        -o is the name of the file to contains canonical path names of
  46  *           bad fonts that are identified. This file is not created if
  47  *           no bad fonts are found.
  48  *        -v verbose: prints progress messages.
  49  *
  50  * </PRE>
  51  *
  52  * @author Ilya Bagrak
  53  */
  54 public class FontChecker implements ActionListener, FontCheckerConstants {
  55 
  56     /**
  57      * Output stream to subprocess.
  58      * Corresponds to the subprocess's System.in".
  59      */
  60     private PrintWriter procPipeOut;
  61 
  62     /**
  63      * Input stream from subprocess.
  64      * Corresponds to the subprocess's System.out".
  65      */
  66     private BufferedInputStream procPipeIn;
  67 
  68     /**
  69      * Child process.
  70      */
  71     private Process childProc;
  72 
  73     /**
  74      * Name of output file to write file names of bad fonts
  75      */
  76     private String outputFile;
  77 
  78     /**
  79      * Reference to currently executing thread.
  80      */
  81     private Thread currThread;
  82 
  83     /**
  84      * Timeout timer for a single font check
  85      */
  86     private javax.swing.Timer timeOne;
  87 
  88     /**
  89      * Timeout timer for all font checks
  90      */
  91     private javax.swing.Timer timeAll;
  92 
  93     /**
  94      * max time (in milliseconds) allowed for checking a single font.
  95      */
  96     private static int timeoutOne = 10000;
  97 
  98     /**
  99      * max time (in milliseconds) allowed for checking all fonts.
 100      */
 101     private static int timeoutAll = 120000;
 102 
 103     /**
 104      * Boolean flag indicating whether FontChecker is required to
 105      * check non-TrueType fonts.
 106      */
 107     private boolean checkNonTTF = false;
 108 
 109     /**
 110      * List of bad fonts found in the system.
 111      */
 112     private Vector badFonts = new Vector();
 113 
 114     /**
 115      * whether to print warnings messges etc to stdout/err
 116      * default is false
 117      */
 118     private static boolean verbose = false;
 119 
 120     /* Command to use to exec sub-process. */
 121     private static String javaCmd = "java";
 122 
 123     static void printlnMessage(String s) {
 124         if (verbose) {
 125             System.out.println(s);
 126         }
 127     }
 128 
 129     /**
 130      * Event handler for timer event.
 131      * <BR>
 132      * Stops the timer and interrupts the current thread which is
 133      * still waiting on I/O from the child process.
 134      * <BR><BR>
 135      * @param evt timer event
 136      */
 137     public void actionPerformed(ActionEvent evt) {
 138         if (evt.getSource() == timeOne) {
 139             timeOne.stop();
 140             printlnMessage("Child timed out: killing");
 141             childProc.destroy();
 142         } else {
 143             doExit(); // went on too long (ie timeAll timed out).
 144         }
 145     }
 146 
 147     /**
 148      * Initializes a FontChecker.
 149      * <BR>
 150      * This method is usually called after an unrecoverable error has
 151      * been detected and a child process has  either crashed or is in bad
 152      * state. The method creates a new child process from
 153      * scratch and initializes it's input/output streams.
 154      */
 155     public void initialize() {
 156         try {
 157             if (childProc != null) {
 158                 childProc.destroy();
 159             }
 160             String fileSeparator = System.getProperty("file.separator");
 161             String javaHome = System.getProperty("java.home");
 162             String classPath =  System.getProperty("java.class.path");
 163             classPath = "\"" + classPath + "\"";
 164             String opt = "-cp " + classPath + " -Dsun.java2d.fontpath=\"" +
 165                 javaHome + fileSeparator + "lib" + fileSeparator + "fonts\"";
 166 
 167             /* command to exec the child process with the same JRE */
 168             String cmd =
 169                 new String(javaHome + fileSeparator + "bin" +
 170                            fileSeparator + javaCmd +
 171                            " -XXsuppressExitMessage " + opt +
 172                            " com.sun.java2d.fontchecker.FontCheckDummy");
 173             printlnMessage("cmd="+cmd);
 174             childProc = Runtime.getRuntime().exec(cmd);
 175 
 176         } catch (IOException e) {
 177             printlnMessage("can't execute child process");
 178             System.exit(0);
 179         } catch (SecurityException e) {
 180             printlnMessage("Error: access denied");
 181             System.exit(0);
 182         }
 183 
 184         /* initialize input/output streams to/from child process */
 185         procPipeOut = new PrintWriter(childProc.getOutputStream());
 186         procPipeIn = new BufferedInputStream(childProc.getInputStream());
 187 
 188         try {
 189             int code = procPipeIn.read();
 190             if (code != CHILD_STARTED_OK) {
 191                 printlnMessage("bad child process start status="+code);
 192                 doExit();
 193             }
 194         } catch (IOException e) {
 195             printlnMessage("can't read child process start status unknown");
 196             doExit();
 197         }
 198     }
 199 
 200     private void doExit() {
 201         try {
 202             if (procPipeOut != null) {
 203                 /* Tell the child to exit */
 204                 procPipeOut.write(EXITCOMMAND+System.getProperty("line.separator"));
 205                 procPipeOut.flush();
 206                 procPipeOut.close();
 207             }
 208         } catch (Throwable t) {
 209         }
 210         System.exit(0);
 211     }
 212 
 213     /**
 214      * Tries to verify integrity of a font specified by a path.
 215      * <BR>
 216      * This method is used to test whether a font specified by the given
 217      * path is valid and does not crash the system.
 218      * <BR><BR>
 219      * @param fontPath a string representation of font path
 220      * to standard out during while this font is tried
 221      * @return returns <code>true</code> if font is OK, and
 222      * <code>false</code> otherwise.
 223      */
 224     public boolean tryFont(File fontFile) {
 225         int bytesRead = 0;
 226         String fontPath = fontFile.getAbsolutePath();
 227 
 228         printlnMessage("Checking font "+fontPath);
 229 
 230         /* store reference to the current thread, so that when the timer
 231          * fires it can be interrupted
 232          */
 233         currThread = Thread.currentThread();
 234         timeOne.restart();
 235 
 236         /* write a string command out to child process
 237          * The command is formed by appending whether to test non-TT fonts
 238          * and font path to be tested
 239          */
 240         String command = Integer.toString(checkNonTTF ? 1 : 0) +
 241                          fontPath +
 242                          System.getProperty("line.separator");
 243         procPipeOut.write(command);
 244         procPipeOut.flush();
 245 
 246         /* check if underlying stream has encountered an error after
 247          * command has been issued
 248          */
 249         if (procPipeOut.checkError()){
 250             printlnMessage("Error: font crashed");
 251             initialize();
 252             return false;
 253         }
 254 
 255         /* trying reading error code back from child process */
 256         try {
 257             bytesRead = procPipeIn.read();
 258         } catch(InterruptedIOException e) {
 259             /* A timeout timer fired before the operation completed */
 260             printlnMessage("Error: timeout occured");
 261             initialize();
 262             return false;
 263         } catch(IOException e) {
 264             /* there was an error reading from the stream */
 265             timeOne.stop();
 266             printlnMessage("Error: font crashed");
 267             initialize();
 268             return false;
 269         } catch (Throwable t) {
 270             bytesRead = ERR_FONT_READ_EXCPT;
 271         } finally {
 272           timeOne.stop();
 273         }
 274 
 275         if (bytesRead == ERR_FONT_OK) {
 276             printlnMessage("Font integrity verified");
 277             return true;
 278         } else if (bytesRead > 0) {
 279 
 280             switch(bytesRead){
 281             case ERR_FONT_NOT_FOUND:
 282                 printlnMessage("Error: font not found!");
 283                 break;
 284             case ERR_FONT_BAD_FORMAT:
 285                 printlnMessage("Error: incorrect font format");
 286                 break;
 287             case ERR_FONT_READ_EXCPT:
 288                 printlnMessage("Error: exception reading font");
 289                 break;
 290             case ERR_FONT_DISPLAY:
 291                 printlnMessage("Error: can't display characters");
 292                 break;
 293             case ERR_FONT_CRASH:
 294                 printlnMessage("Error: font crashed");
 295                 break;
 296             default:
 297                 printlnMessage("Error: invalid error code:"+bytesRead);
 298                 break;
 299 
 300             }
 301         } else if (bytesRead == ERR_FONT_EOS) {
 302             printlnMessage("Error: end of stream marker encountered");
 303         } else {
 304             printlnMessage("Error: invalid error code:"+bytesRead);
 305         }
 306 
 307         /* if we still haven't returned from this method, some error
 308          * condition has occured and it is safer to re-initialize
 309          */
 310         initialize();
 311         return false;
 312     }
 313 
 314     /**
 315      * Checks the integrity of all system fonts.
 316      * <BR>
 317      * This method goes through every font in system's font path and verifies
 318      * its integrity via the tryFont method.
 319      * <BR><BR>
 320      * @param restart <code>true</code> if checking of fonts should continue
 321      * after the first  bad font is found, and <code>false</code> otherwise
 322      * @return returns <code>true</code> if all fonts are valid,
 323      * <code>false</code> otherwise
 324      * @see #tryFont(String, boolean, boolean)
 325      */
 326     public boolean checkFonts(boolean restart) {
 327 
 328         /* file filter to filter out none-truetype font files */
 329         FontFileFilter fff = new FontFileFilter(checkNonTTF);
 330         boolean checkOk = true;
 331 
 332         /* get platform-independent font path. Note that this bypasses
 333          * the normal GraphicsEnvironment initialisation. In conjunction with
 334          * the headless setting above, so we want to add
 335          * java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
 336          * to trigger a more normal initialisation.
 337          */
 338         java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
 339         String fontPath = FontManager.getFontPath(true);
 340         StringTokenizer st =
 341             new StringTokenizer(fontPath,
 342                                 System.getProperty("path.separator"));
 343 
 344         /* some systems may have multiple font paths separated by
 345          * platform-dependent characters, so fontPath string needs to be
 346          * parsed
 347          */
 348         timeOne = new javax.swing.Timer(timeoutOne, this);
 349         timeAll = new javax.swing.Timer(timeoutAll, this);
 350         timeAll.restart();
 351         while (st.hasMoreTokens()) {
 352             File fontRoot = new File(st.nextToken());
 353             File[] fontFiles = fontRoot.listFiles(fff);
 354 
 355             for (int i = 0; i < fontFiles.length; i++) {
 356                 /* for each font file that is not a directory and passes
 357                  * through the font filter run the test
 358                  */
 359                 if (!fontFiles[i].isDirectory() &&
 360                     !tryFont(fontFiles[i])) {
 361 
 362                     checkOk = false;
 363                     badFonts.add(fontFiles[i].getAbsolutePath());
 364                     if (!restart) {
 365                         break;
 366                     }
 367                 }
 368             }
 369         }
 370 
 371         /* Tell the child to exit */
 372         procPipeOut.write(EXITCOMMAND+System.getProperty("line.separator"));
 373         procPipeOut.flush();
 374         procPipeOut.close();
 375 
 376         return checkOk;
 377     }
 378 
 379     public static void main(String args[]){
 380         try {
 381             /* Background app. */
 382             System.setProperty("java.awt.headless", "true");
 383             System.setProperty("sun.java2d.noddraw", "true");
 384 
 385             boolean restart = true;
 386             boolean errorFlag = false;
 387 
 388             FontChecker fc = new FontChecker();
 389             int arg = 0;
 390 
 391             while (arg < args.length && errorFlag == false) {
 392                 if (args[arg].equals("-v")) {
 393                     verbose = true;
 394                 }
 395                 else if (args[arg].equals("-w") &&
 396                          System.getProperty("os.name", "unknown").
 397                          startsWith("Windows")) {
 398                     javaCmd = "javaw";
 399                 }
 400                 else if (args[arg].equals("-o")) {
 401                     /* set output file */
 402                     if (++arg < args.length)
 403                         fc.outputFile = args[arg];
 404                     else {
 405                         /* invalid argument format */
 406                         printlnMessage("Error: invalid argument format");
 407                         errorFlag = true;
 408                     }
 409                 }
 410                 else {
 411                     /* invalid command line argument */
 412                     printlnMessage("Error: invalid argument value");
 413                     errorFlag = true;
 414                 }
 415                 arg++;
 416             }
 417 
 418             if (errorFlag || fc.outputFile == null) {
 419                 System.exit(0);
 420             }
 421 
 422             File outfile = new File(fc.outputFile);
 423             if (outfile.exists()) {
 424                 outfile.delete();
 425             }
 426 
 427             fc.initialize();
 428 
 429             if (!fc.checkFonts(restart)) {
 430                 String[] badFonts = (String[])fc.badFonts.toArray(new String[0]);
 431                 if (badFonts.length > 0) {
 432                     printlnMessage("Bad Fonts:");
 433                     try {
 434                         FileOutputStream fos =
 435                             new FileOutputStream(fc.outputFile);
 436                         PrintStream ps = new  PrintStream(fos);
 437                         for (int i = 0; i < badFonts.length; i++) {
 438                             ps.println(badFonts[i]);
 439                             printlnMessage(badFonts[i]);
 440                         }
 441                         fos.close();
 442                     } catch (IOException e) {
 443                     }
 444                 }
 445             } else {
 446                 printlnMessage("No bad fonts found.");
 447         }
 448         } catch (Throwable t) {
 449         }
 450         System.exit(0);
 451     }
 452 }