/*
* Copyright 2002-2004 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package build.tools.fontchecker;
import java.io.*;
import java.util.*;
import java.awt.event.*;
import sun.font.FontManager;
/**
* FontChecker.
*
* <PRE>
* This is a FontChecker program. This class is a "parent" process
* which invokes a "child" process. The child process will test
* series of fonts and may crash as it encounters invalid fonts.
* The "parent" process must then interpret error codes passed to it
* by the "child" process and restart the "child" process if necessary.
*
* usage: java FontChecker [-v] -o outputfile
*
* -o is the name of the file to contains canonical path names of
* bad fonts that are identified. This file is not created if
* no bad fonts are found.
* -v verbose: prints progress messages.
*
* </PRE>
*
* @author Ilya Bagrak
*/
public class FontChecker implements ActionListener, FontCheckerConstants {
/**
* Output stream to subprocess.
* Corresponds to the subprocess's System.in".
*/
private PrintWriter procPipeOut;
/**
* Input stream from subprocess.
* Corresponds to the subprocess's System.out".
*/
private BufferedInputStream procPipeIn;
/**
* Child process.
*/
private Process childProc;
/**
* Name of output file to write file names of bad fonts
*/
private String outputFile;
/**
* Reference to currently executing thread.
*/
private Thread currThread;
/**
* Timeout timer for a single font check
*/
private javax.swing.Timer timeOne;
/**
* Timeout timer for all font checks
*/
private javax.swing.Timer timeAll;
/**
* max time (in milliseconds) allowed for checking a single font.
*/
private static int timeoutOne = 10000;
/**
* max time (in milliseconds) allowed for checking all fonts.
*/
private static int timeoutAll = 120000;
/**
* Boolean flag indicating whether FontChecker is required to
* check non-TrueType fonts.
*/
private boolean checkNonTTF = false;
/**
* List of bad fonts found in the system.
*/
private Vector badFonts = new Vector();
/**
* whether to print warnings messges etc to stdout/err
* default is false
*/
private static boolean verbose = false;
/* Command to use to exec sub-process. */
private static String javaCmd = "java";
static void printlnMessage(String s) {
if (verbose) {
System.out.println(s);
}
}
/**
* Event handler for timer event.
* <BR>
* Stops the timer and interrupts the current thread which is
* still waiting on I/O from the child process.
* <BR><BR>
* @param evt timer event
*/
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == timeOne) {
timeOne.stop();
printlnMessage("Child timed out: killing");
childProc.destroy();
} else {
doExit(); // went on too long (ie timeAll timed out).
}
}
/**
* Initializes a FontChecker.
* <BR>
* This method is usually called after an unrecoverable error has
* been detected and a child process has either crashed or is in bad
* state. The method creates a new child process from
* scratch and initializes it's input/output streams.
*/
public void initialize() {
try {
if (childProc != null) {
childProc.destroy();
}
String fileSeparator = System.getProperty("file.separator");
String javaHome = System.getProperty("java.home");
String classPath = System.getProperty("java.class.path");
classPath = "\"" + classPath + "\"";
String opt = "-cp " + classPath + " -Dsun.java2d.fontpath=\"" +
javaHome + fileSeparator + "lib" + fileSeparator + "fonts\"";
/* command to exec the child process with the same JRE */
String cmd =
new String(javaHome + fileSeparator + "bin" +
fileSeparator + javaCmd +
" -XXsuppressExitMessage " + opt +
" com.sun.java2d.fontchecker.FontCheckDummy");
printlnMessage("cmd="+cmd);
childProc = Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
printlnMessage("can't execute child process");
System.exit(0);
} catch (SecurityException e) {
printlnMessage("Error: access denied");
System.exit(0);
}
/* initialize input/output streams to/from child process */
procPipeOut = new PrintWriter(childProc.getOutputStream());
procPipeIn = new BufferedInputStream(childProc.getInputStream());
try {
int code = procPipeIn.read();
if (code != CHILD_STARTED_OK) {
printlnMessage("bad child process start status="+code);
doExit();
}
} catch (IOException e) {
printlnMessage("can't read child process start status unknown");
doExit();
}
}
private void doExit() {
try {
if (procPipeOut != null) {
/* Tell the child to exit */
procPipeOut.write(EXITCOMMAND+System.getProperty("line.separator"));
procPipeOut.flush();
procPipeOut.close();
}
} catch (Throwable t) {
}
System.exit(0);
}
/**
* Tries to verify integrity of a font specified by a path.
* <BR>
* This method is used to test whether a font specified by the given
* path is valid and does not crash the system.
* <BR><BR>
* @param fontPath a string representation of font path
* to standard out during while this font is tried
* @return returns <code>true</code> if font is OK, and
* <code>false</code> otherwise.
*/
public boolean tryFont(File fontFile) {
int bytesRead = 0;
String fontPath = fontFile.getAbsolutePath();
printlnMessage("Checking font "+fontPath);
/* store reference to the current thread, so that when the timer
* fires it can be interrupted
*/
currThread = Thread.currentThread();
timeOne.restart();
/* write a string command out to child process
* The command is formed by appending whether to test non-TT fonts
* and font path to be tested
*/
String command = Integer.toString(checkNonTTF ? 1 : 0) +
fontPath +
System.getProperty("line.separator");
procPipeOut.write(command);
procPipeOut.flush();
/* check if underlying stream has encountered an error after
* command has been issued
*/
if (procPipeOut.checkError()){
printlnMessage("Error: font crashed");
initialize();
return false;
}
/* trying reading error code back from child process */
try {
bytesRead = procPipeIn.read();
} catch(InterruptedIOException e) {
/* A timeout timer fired before the operation completed */
printlnMessage("Error: timeout occured");
initialize();
return false;
} catch(IOException e) {
/* there was an error reading from the stream */
timeOne.stop();
printlnMessage("Error: font crashed");
initialize();
return false;
} catch (Throwable t) {
bytesRead = ERR_FONT_READ_EXCPT;
} finally {
timeOne.stop();
}
if (bytesRead == ERR_FONT_OK) {
printlnMessage("Font integrity verified");
return true;
} else if (bytesRead > 0) {
switch(bytesRead){
case ERR_FONT_NOT_FOUND:
printlnMessage("Error: font not found!");
break;
case ERR_FONT_BAD_FORMAT:
printlnMessage("Error: incorrect font format");
break;
case ERR_FONT_READ_EXCPT:
printlnMessage("Error: exception reading font");
break;
case ERR_FONT_DISPLAY:
printlnMessage("Error: can't display characters");
break;
case ERR_FONT_CRASH:
printlnMessage("Error: font crashed");
break;
default:
printlnMessage("Error: invalid error code:"+bytesRead);
break;
}
} else if (bytesRead == ERR_FONT_EOS) {
printlnMessage("Error: end of stream marker encountered");
} else {
printlnMessage("Error: invalid error code:"+bytesRead);
}
/* if we still haven't returned from this method, some error
* condition has occured and it is safer to re-initialize
*/
initialize();
return false;
}
/**
* Checks the integrity of all system fonts.
* <BR>
* This method goes through every font in system's font path and verifies
* its integrity via the tryFont method.
* <BR><BR>
* @param restart <code>true</code> if checking of fonts should continue
* after the first bad font is found, and <code>false</code> otherwise
* @return returns <code>true</code> if all fonts are valid,
* <code>false</code> otherwise
* @see #tryFont(String, boolean, boolean)
*/
public boolean checkFonts(boolean restart) {
/* file filter to filter out none-truetype font files */
FontFileFilter fff = new FontFileFilter(checkNonTTF);
boolean checkOk = true;
/* get platform-independent font path. Note that this bypasses
* the normal GraphicsEnvironment initialisation. In conjunction with
* the headless setting above, so we want to add
* java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
* to trigger a more normal initialisation.
*/
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
String fontPath = FontManager.getFontPath(true);
StringTokenizer st =
new StringTokenizer(fontPath,
System.getProperty("path.separator"));
/* some systems may have multiple font paths separated by
* platform-dependent characters, so fontPath string needs to be
* parsed
*/
timeOne = new javax.swing.Timer(timeoutOne, this);
timeAll = new javax.swing.Timer(timeoutAll, this);
timeAll.restart();
while (st.hasMoreTokens()) {
File fontRoot = new File(st.nextToken());
File[] fontFiles = fontRoot.listFiles(fff);
for (int i = 0; i < fontFiles.length; i++) {
/* for each font file that is not a directory and passes
* through the font filter run the test
*/
if (!fontFiles[i].isDirectory() &&
!tryFont(fontFiles[i])) {
checkOk = false;
badFonts.add(fontFiles[i].getAbsolutePath());
if (!restart) {
break;
}
}
}
}
/* Tell the child to exit */
procPipeOut.write(EXITCOMMAND+System.getProperty("line.separator"));
procPipeOut.flush();
procPipeOut.close();
return checkOk;
}
public static void main(String args[]){
try {
/* Background app. */
System.setProperty("java.awt.headless", "true");
System.setProperty("sun.java2d.noddraw", "true");
boolean restart = true;
boolean errorFlag = false;
FontChecker fc = new FontChecker();
int arg = 0;
while (arg < args.length && errorFlag == false) {
if (args[arg].equals("-v")) {
verbose = true;
}
else if (args[arg].equals("-w") &&
System.getProperty("os.name", "unknown").
startsWith("Windows")) {
javaCmd = "javaw";
}
else if (args[arg].equals("-o")) {
/* set output file */
if (++arg < args.length)
fc.outputFile = args[arg];
else {
/* invalid argument format */
printlnMessage("Error: invalid argument format");
errorFlag = true;
}
}
else {
/* invalid command line argument */
printlnMessage("Error: invalid argument value");
errorFlag = true;
}
arg++;
}
if (errorFlag || fc.outputFile == null) {
System.exit(0);
}
File outfile = new File(fc.outputFile);
if (outfile.exists()) {
outfile.delete();
}
fc.initialize();
if (!fc.checkFonts(restart)) {
String[] badFonts = (String[])fc.badFonts.toArray(new String[0]);
if (badFonts.length > 0) {
printlnMessage("Bad Fonts:");
try {
FileOutputStream fos =
new FileOutputStream(fc.outputFile);
PrintStream ps = new PrintStream(fos);
for (int i = 0; i < badFonts.length; i++) {
ps.println(badFonts[i]);
printlnMessage(badFonts[i]);
}
fos.close();
} catch (IOException e) {
}
}
} else {
printlnMessage("No bad fonts found.");
}
} catch (Throwable t) {
}
System.exit(0);
}
}