001: /*
002: * Copyright 2002-2004 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.java2d.fontchecker;
027:
028: import java.io.*;
029: import java.util.*;
030: import java.awt.event.*;
031: import sun.font.FontManager;
032:
033: /**
034: * FontChecker.
035: *
036: * <PRE>
037: * This is a FontChecker program. This class is a "parent" process
038: * which invokes a "child" process. The child process will test
039: * series of fonts and may crash as it encounters invalid fonts.
040: * The "parent" process must then interpret error codes passed to it
041: * by the "child" process and restart the "child" process if necessary.
042: *
043: * usage: java FontChecker [-v] -o outputfile
044: *
045: * -o is the name of the file to contains canonical path names of
046: * bad fonts that are identified. This file is not created if
047: * no bad fonts are found.
048: * -v verbose: prints progress messages.
049: *
050: * </PRE>
051: *
052: * @author Ilya Bagrak
053: */
054: public class FontChecker implements ActionListener,
055: FontCheckerConstants {
056:
057: /**
058: * Output stream to subprocess.
059: * Corresponds to the subprocess's System.in".
060: */
061: private PrintWriter procPipeOut;
062:
063: /**
064: * Input stream from subprocess.
065: * Corresponds to the subprocess's System.out".
066: */
067: private BufferedInputStream procPipeIn;
068:
069: /**
070: * Child process.
071: */
072: private Process childProc;
073:
074: /**
075: * Name of output file to write file names of bad fonts
076: */
077: private String outputFile;
078:
079: /**
080: * Reference to currently executing thread.
081: */
082: private Thread currThread;
083:
084: /**
085: * Timeout timer for a single font check
086: */
087: private javax.swing.Timer timeOne;
088:
089: /**
090: * Timeout timer for all font checks
091: */
092: private javax.swing.Timer timeAll;
093:
094: /**
095: * max time (in milliseconds) allowed for checking a single font.
096: */
097: private static int timeoutOne = 10000;
098:
099: /**
100: * max time (in milliseconds) allowed for checking all fonts.
101: */
102: private static int timeoutAll = 120000;
103:
104: /**
105: * Boolean flag indicating whether FontChecker is required to
106: * check non-TrueType fonts.
107: */
108: private boolean checkNonTTF = false;
109:
110: /**
111: * List of bad fonts found in the system.
112: */
113: private Vector badFonts = new Vector();
114:
115: /**
116: * whether to print warnings messges etc to stdout/err
117: * default is false
118: */
119: private static boolean verbose = false;
120:
121: /* Command to use to exec sub-process. */
122: private static String javaCmd = "java";
123:
124: static void printlnMessage(String s) {
125: if (verbose) {
126: System.out.println(s);
127: }
128: }
129:
130: /**
131: * Event handler for timer event.
132: * <BR>
133: * Stops the timer and interrupts the current thread which is
134: * still waiting on I/O from the child process.
135: * <BR><BR>
136: * @param evt timer event
137: */
138: public void actionPerformed(ActionEvent evt) {
139: if (evt.getSource() == timeOne) {
140: timeOne.stop();
141: printlnMessage("Child timed out: killing");
142: childProc.destroy();
143: } else {
144: doExit(); // went on too long (ie timeAll timed out).
145: }
146: }
147:
148: /**
149: * Initializes a FontChecker.
150: * <BR>
151: * This method is usually called after an unrecoverable error has
152: * been detected and a child process has either crashed or is in bad
153: * state. The method creates a new child process from
154: * scratch and initializes it's input/output streams.
155: */
156: public void initialize() {
157: try {
158: if (childProc != null) {
159: childProc.destroy();
160: }
161: String fileSeparator = System.getProperty("file.separator");
162: String javaHome = System.getProperty("java.home");
163: String classPath = System.getProperty("java.class.path");
164: classPath = "\"" + classPath + "\"";
165: String opt = "-cp " + classPath
166: + " -Dsun.java2d.fontpath=\"" + javaHome
167: + fileSeparator + "lib" + fileSeparator + "fonts\"";
168:
169: /* command to exec the child process with the same JRE */
170: String cmd = new String(javaHome + fileSeparator + "bin"
171: + fileSeparator + javaCmd
172: + " -XXsuppressExitMessage " + opt
173: + " com.sun.java2d.fontchecker.FontCheckDummy");
174: printlnMessage("cmd=" + cmd);
175: childProc = Runtime.getRuntime().exec(cmd);
176:
177: } catch (IOException e) {
178: printlnMessage("can't execute child process");
179: System.exit(0);
180: } catch (SecurityException e) {
181: printlnMessage("Error: access denied");
182: System.exit(0);
183: }
184:
185: /* initialize input/output streams to/from child process */
186: procPipeOut = new PrintWriter(childProc.getOutputStream());
187: procPipeIn = new BufferedInputStream(childProc.getInputStream());
188:
189: try {
190: int code = procPipeIn.read();
191: if (code != CHILD_STARTED_OK) {
192: printlnMessage("bad child process start status=" + code);
193: doExit();
194: }
195: } catch (IOException e) {
196: printlnMessage("can't read child process start status unknown");
197: doExit();
198: }
199: }
200:
201: private void doExit() {
202: try {
203: if (procPipeOut != null) {
204: /* Tell the child to exit */
205: procPipeOut.write(EXITCOMMAND
206: + System.getProperty("line.separator"));
207: procPipeOut.flush();
208: procPipeOut.close();
209: }
210: } catch (Throwable t) {
211: }
212: System.exit(0);
213: }
214:
215: /**
216: * Tries to verify integrity of a font specified by a path.
217: * <BR>
218: * This method is used to test whether a font specified by the given
219: * path is valid and does not crash the system.
220: * <BR><BR>
221: * @param fontPath a string representation of font path
222: * to standard out during while this font is tried
223: * @return returns <code>true</code> if font is OK, and
224: * <code>false</code> otherwise.
225: */
226: public boolean tryFont(File fontFile) {
227: int bytesRead = 0;
228: String fontPath = fontFile.getAbsolutePath();
229:
230: printlnMessage("Checking font " + fontPath);
231:
232: /* store reference to the current thread, so that when the timer
233: * fires it can be interrupted
234: */
235: currThread = Thread.currentThread();
236: timeOne.restart();
237:
238: /* write a string command out to child process
239: * The command is formed by appending whether to test non-TT fonts
240: * and font path to be tested
241: */
242: String command = Integer.toString(checkNonTTF ? 1 : 0)
243: + fontPath + System.getProperty("line.separator");
244: procPipeOut.write(command);
245: procPipeOut.flush();
246:
247: /* check if underlying stream has encountered an error after
248: * command has been issued
249: */
250: if (procPipeOut.checkError()) {
251: printlnMessage("Error: font crashed");
252: initialize();
253: return false;
254: }
255:
256: /* trying reading error code back from child process */
257: try {
258: bytesRead = procPipeIn.read();
259: } catch (InterruptedIOException e) {
260: /* A timeout timer fired before the operation completed */
261: printlnMessage("Error: timeout occured");
262: initialize();
263: return false;
264: } catch (IOException e) {
265: /* there was an error reading from the stream */
266: timeOne.stop();
267: printlnMessage("Error: font crashed");
268: initialize();
269: return false;
270: } catch (Throwable t) {
271: bytesRead = ERR_FONT_READ_EXCPT;
272: } finally {
273: timeOne.stop();
274: }
275:
276: if (bytesRead == ERR_FONT_OK) {
277: printlnMessage("Font integrity verified");
278: return true;
279: } else if (bytesRead > 0) {
280:
281: switch (bytesRead) {
282: case ERR_FONT_NOT_FOUND:
283: printlnMessage("Error: font not found!");
284: break;
285: case ERR_FONT_BAD_FORMAT:
286: printlnMessage("Error: incorrect font format");
287: break;
288: case ERR_FONT_READ_EXCPT:
289: printlnMessage("Error: exception reading font");
290: break;
291: case ERR_FONT_DISPLAY:
292: printlnMessage("Error: can't display characters");
293: break;
294: case ERR_FONT_CRASH:
295: printlnMessage("Error: font crashed");
296: break;
297: default:
298: printlnMessage("Error: invalid error code:" + bytesRead);
299: break;
300:
301: }
302: } else if (bytesRead == ERR_FONT_EOS) {
303: printlnMessage("Error: end of stream marker encountered");
304: } else {
305: printlnMessage("Error: invalid error code:" + bytesRead);
306: }
307:
308: /* if we still haven't returned from this method, some error
309: * condition has occured and it is safer to re-initialize
310: */
311: initialize();
312: return false;
313: }
314:
315: /**
316: * Checks the integrity of all system fonts.
317: * <BR>
318: * This method goes through every font in system's font path and verifies
319: * its integrity via the tryFont method.
320: * <BR><BR>
321: * @param restart <code>true</code> if checking of fonts should continue
322: * after the first bad font is found, and <code>false</code> otherwise
323: * @return returns <code>true</code> if all fonts are valid,
324: * <code>false</code> otherwise
325: * @see #tryFont(String, boolean, boolean)
326: */
327: public boolean checkFonts(boolean restart) {
328:
329: /* file filter to filter out none-truetype font files */
330: FontFileFilter fff = new FontFileFilter(checkNonTTF);
331: boolean checkOk = true;
332:
333: /* get platform-independent font path. Note that this bypasses
334: * the normal GraphicsEnvironment initialisation. In conjunction with
335: * the headless setting above, so we want to add
336: * java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
337: * to trigger a more normal initialisation.
338: */
339: java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
340: String fontPath = FontManager.getFontPath(true);
341: StringTokenizer st = new StringTokenizer(fontPath, System
342: .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
373: + System.getProperty("line.separator"));
374: procPipeOut.flush();
375: procPipeOut.close();
376:
377: return checkOk;
378: }
379:
380: public static void main(String args[]) {
381: try {
382: /* Background app. */
383: System.setProperty("java.awt.headless", "true");
384: System.setProperty("sun.java2d.noddraw", "true");
385:
386: boolean restart = true;
387: boolean errorFlag = false;
388:
389: FontChecker fc = new FontChecker();
390: int arg = 0;
391:
392: while (arg < args.length && errorFlag == false) {
393: if (args[arg].equals("-v")) {
394: verbose = true;
395: } else if (args[arg].equals("-w")
396: && System.getProperty("os.name", "unknown")
397: .startsWith("Windows")) {
398: javaCmd = "javaw";
399: } else if (args[arg].equals("-o")) {
400: /* set output file */
401: if (++arg < args.length)
402: fc.outputFile = args[arg];
403: else {
404: /* invalid argument format */
405: printlnMessage("Error: invalid argument format");
406: errorFlag = true;
407: }
408: } else {
409: /* invalid command line argument */
410: printlnMessage("Error: invalid argument value");
411: errorFlag = true;
412: }
413: arg++;
414: }
415:
416: if (errorFlag || fc.outputFile == null) {
417: System.exit(0);
418: }
419:
420: File outfile = new File(fc.outputFile);
421: if (outfile.exists()) {
422: outfile.delete();
423: }
424:
425: fc.initialize();
426:
427: if (!fc.checkFonts(restart)) {
428: String[] badFonts = (String[]) fc.badFonts
429: .toArray(new String[0]);
430: if (badFonts.length > 0) {
431: printlnMessage("Bad Fonts:");
432: try {
433: FileOutputStream fos = new FileOutputStream(
434: fc.outputFile);
435: PrintStream ps = new PrintStream(fos);
436: for (int i = 0; i < badFonts.length; i++) {
437: ps.println(badFonts[i]);
438: printlnMessage(badFonts[i]);
439: }
440: fos.close();
441: } catch (IOException e) {
442: }
443: }
444: } else {
445: printlnMessage("No bad fonts found.");
446: }
447: } catch (Throwable t) {
448: }
449: System.exit(0);
450: }
451: }
|