jdk/make/tools/src/build/tools/fontchecker/FontChecker.java
changeset 4135 08d5964ec7d4
parent 4134 430015834eb3
parent 4129 658d1b92c389
child 4140 9c3e48ab69a6
equal deleted inserted replaced
4134:430015834eb3 4135:08d5964ec7d4
     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 }