/* This file is part of JOP, the Java Optimized Processor see <http://www.jopdesign.com/> Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.common.graphutils; import com.jopdesign.common.config.Config; import com.jopdesign.common.config.StringOption; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.misc.AppInfoError; import org.apache.log4j.Logger; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** * This class invokes the .dot program to generate graphs. * As invoking dot is very time consuming, we cache output graphs using * md5s on the DOT file. * * @author Benedikt Huber, benedikt.huber@gmail.com */ public class InvokeDot { private static final Logger logger = Logger.getLogger(LogConfig.LOG_GRAPH+".InvokeDot"); private static final String DEFAULT_CACHE_DIR = "dot-cache"; private static final StringOption PROGRAM_DOT = new StringOption("program-dot", "if graphs should be generated from java, the path to the 'dot' binary", true); public static void registerOptions(Config config) { config.getDebugGroup().addOption(PROGRAM_DOT); } public File getCacheFile(String filename) { return new File(cacheDir, filename); } public static void invokeDot(Config config, File dotFile, File outFile) throws IOException { // do nothing if dot has not been configured if (!config.getDebugGroup().hasValue(PROGRAM_DOT)) return; File cacheDir = config.getOutDir(DEFAULT_CACHE_DIR); InvokeDot id = new InvokeDot(config.getDebugGroup().getOption(PROGRAM_DOT), cacheDir); id.runDot(dotFile, outFile); } public static String getDotBinary(Config config) { return config.getDebugGroup().getOption(PROGRAM_DOT); } public static boolean doInvokeDot(Config config) { return (getDotBinary(config) != null) && !"".equals(getDotBinary(config)); } private String dotBinary; private File cacheDir; private String format; public InvokeDot(String dotBinary, File cacheDir) { this.dotBinary = dotBinary; this.cacheDir = cacheDir; this.format = "png"; } public void setFormat(String format) { this.format = format; } public void runDot(File dotFile, File imageFile) throws IOException { byte[] md5; try { md5 = calculateMD5(dotFile); } catch (NoSuchAlgorithmException e) { throw new AppInfoError("Unexpected exception: MD5 Algorithm not available", e); } if (cacheDir != null) { File cachedFile = getCacheFile(byteArrayToString(md5) + ".png"); if (!cachedFile.exists()) { runDot(dotFile, cachedFile, format); } copyFile(cachedFile, imageFile); } else { runDot(dotFile, imageFile, format); } } private void runDot(File dotFile, File imageFile, String fmt) throws IOException { String[] cmd = {dotBinary, dotFile.getPath(), "-T" + fmt, "-o", imageFile.getPath()}; Process p; logger.debug("Invoking dot: " + Arrays.toString(cmd)); p = Runtime.getRuntime().exec(cmd); int exitCode = -1; try { exitCode = p.waitFor(); } catch (InterruptedException e) { logger.warn("Waiting for dot program interrupted: " + e); } if (exitCode != 0) { throw new IOException("Non-Zero exit code from dot program: " + exitCode); } if (!imageFile.exists()) { throw new IOException("Dot program run, but imagefile " + imageFile + " hasn't been created - Maybe an empty .dot file ?"); } } private byte[] calculateMD5(File cgdot) throws NoSuchAlgorithmException, IOException { MessageDigest m = MessageDigest.getInstance("MD5"); FileInputStream fis = new FileInputStream(cgdot); byte[] buffer = new byte[1024]; int read; //noinspection NestedAssignment while ((read = fis.read(buffer)) > 0) { m.update(buffer, 0, read); } fis.close(); return m.digest(); } private String byteArrayToString(byte[] barray) { StringBuffer buf = new StringBuffer(); for (Byte by : barray) { buf.append(String.format("%02x", (short) by)); } return buf.toString(); } public static void copyFile(File in, File out) throws IOException { FileChannel inChannel = new FileInputStream(in).getChannel(); FileChannel outChannel = new FileOutputStream(out).getChannel(); try { inChannel.transferTo(0, inChannel.size(), outChannel); } finally { if (inChannel != null) inChannel.close(); if (outChannel != null) outChannel.close(); } } }