1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.schemaspy.util;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.Set;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import net.sourceforge.schemaspy.Config;
32
33 public class Dot {
34 private static Dot instance = new Dot();
35 private final Version version;
36 private final Version supportedVersion = new Version("2.2.1");
37 private final Version badVersion = new Version("2.4");
38 private final String lineSeparator = System.getProperty("line.separator");
39 private String dotExe;
40 private String format = "png";
41 private String renderer;
42 private final Set<String> validatedRenderers = Collections.synchronizedSet(new HashSet<String>());
43 private final Set<String> invalidatedRenderers = Collections.synchronizedSet(new HashSet<String>());
44
45 private Dot() {
46 String versionText = null;
47
48
49
50
51 String[] dotCommand = new String[] { getExe(), "-V" };
52
53 try {
54 Process process = Runtime.getRuntime().exec(dotCommand);
55 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
56 String versionLine = reader.readLine();
57
58
59 Matcher matcher = Pattern.compile("[0-9][0-9.]+").matcher(versionLine);
60 if (matcher.find()) {
61 versionText = matcher.group();
62 } else {
63 if (Config.getInstance().isHtmlGenerationEnabled()) {
64 System.err.println();
65 System.err.println("Invalid dot configuration detected. '" +
66 getDisplayableCommand(dotCommand) + "' returned:");
67 System.err.println(" " + versionLine);
68 }
69 }
70 } catch (Exception validDotDoesntExist) {
71 if (Config.getInstance().isHtmlGenerationEnabled()) {
72 System.err.println("Failed to query Graphviz version information");
73 System.err.println(" with: " + getDisplayableCommand(dotCommand));
74 System.err.println(" " + validDotDoesntExist);
75 }
76 }
77
78 version = new Version(versionText);
79 }
80
81 public static Dot getInstance() {
82 return instance;
83 }
84
85 public boolean exists() {
86 return version.toString() != null;
87 }
88
89 public Version getVersion() {
90 return version;
91 }
92
93 public boolean isValid() {
94 return exists() && (getVersion().equals(supportedVersion) || getVersion().compareTo(badVersion) > 0);
95 }
96
97 public String getSupportedVersions() {
98 return "dot version " + supportedVersion + " or versions greater than " + badVersion;
99 }
100
101 public boolean supportsCenteredEastWestEdges() {
102 return getVersion().compareTo(new Version("2.6")) >= 0;
103 }
104
105
106
107
108
109
110
111
112 public void setFormat(String format) {
113 this.format = format;
114 }
115
116
117
118
119
120 public String getFormat() {
121 return format;
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135 public boolean requiresGdRenderer() {
136 return getVersion().compareTo(new Version("2.12")) >= 0 && supportsRenderer(":gd");
137 }
138
139
140
141
142
143
144
145
146
147
148 public void setRenderer(String renderer) {
149 this.renderer = renderer;
150 }
151
152 public String getRenderer() {
153 return renderer != null && supportsRenderer(renderer) ? renderer
154 : (requiresGdRenderer() ? ":gd" : "");
155 }
156
157
158
159
160
161
162
163
164
165 public void setHighQuality(boolean highQuality) {
166 if (highQuality && supportsRenderer(":cairo")) {
167 setRenderer(":cairo");
168 } else if (supportsRenderer(":gd")) {
169 setRenderer(":gd");
170 }
171 }
172
173
174
175
176 public boolean isHighQuality() {
177 return getRenderer().indexOf(":cairo") != -1;
178 }
179
180
181
182
183
184
185
186
187 public boolean supportsRenderer(@SuppressWarnings("hiding") String renderer) {
188 if (!exists())
189 return false;
190
191 if (validatedRenderers.contains(renderer))
192 return true;
193
194 if (invalidatedRenderers.contains(renderer))
195 return false;
196
197 try {
198 String[] dotCommand = new String[] {
199 getExe(),
200 "-T" + getFormat() + ':'
201 };
202 Process process = Runtime.getRuntime().exec(dotCommand);
203 BufferedReader errors = new BufferedReader(new InputStreamReader(process.getErrorStream()));
204 String line;
205 while ((line = errors.readLine()) != null) {
206 if (line.contains(getFormat() + renderer)) {
207 validatedRenderers.add(renderer);
208 }
209 }
210 process.waitFor();
211 } catch (Exception exc) {
212 exc.printStackTrace();
213 }
214
215 if (!validatedRenderers.contains(renderer)) {
216
217 invalidatedRenderers.add(renderer);
218 return false;
219 }
220
221 return true;
222 }
223
224
225
226
227
228
229 private String getExe() {
230 if (dotExe == null)
231 {
232 File gv = Config.getInstance().getGraphvizDir();
233
234 if (gv == null) {
235
236 dotExe = "dot";
237 } else {
238
239 dotExe = new File(new File(gv, "bin"), "dot").toString();
240 }
241 }
242
243 return dotExe;
244 }
245
246
247
248
249 public String generateDiagram(File dotFile, File diagramFile) throws DotFailure {
250 StringBuilder mapBuffer = new StringBuilder(1024);
251
252 BufferedReader mapReader = null;
253
254 String[] dotCommand = new String[] {
255 getExe(),
256 "-T" + getFormat() + getRenderer(),
257 dotFile.toString(),
258 "-o" + diagramFile,
259 "-Tcmapx"
260 };
261
262 String commandLine = getDisplayableCommand(dotCommand);
263
264 try {
265 Process process = Runtime.getRuntime().exec(dotCommand);
266 new ProcessOutputReader(commandLine, process.getErrorStream()).start();
267 mapReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
268 String line;
269 while ((line = mapReader.readLine()) != null) {
270 mapBuffer.append(line);
271 mapBuffer.append(lineSeparator);
272 }
273 int rc = process.waitFor();
274 if (rc != 0)
275 throw new DotFailure("'" + commandLine + "' failed with return code " + rc);
276 if (!diagramFile.exists())
277 throw new DotFailure("'" + commandLine + "' failed to create output file");
278
279
280 return mapBuffer.toString().replace("/>", ">");
281 } catch (InterruptedException interrupted) {
282 throw new RuntimeException(interrupted);
283 } catch (DotFailure failed) {
284 diagramFile.delete();
285 throw failed;
286 } catch (IOException failed) {
287 diagramFile.delete();
288 throw new DotFailure("'" + commandLine + "' failed with exception " + failed);
289 } finally {
290 if (mapReader != null) {
291 try {
292 mapReader.close();
293 } catch (IOException ignore) {}
294 }
295 }
296 }
297
298 public class DotFailure extends IOException {
299 private static final long serialVersionUID = 3833743270181351987L;
300
301 public DotFailure(String msg) {
302 super(msg);
303 }
304 }
305
306 private static String getDisplayableCommand(String[] command) {
307 StringBuilder displayable = new StringBuilder();
308 for (int i = 0; i < command.length; ++i) {
309 displayable.append(command[i]);
310 if (i + 1 < command.length)
311 displayable.append(' ');
312 }
313 return displayable.toString();
314 }
315
316 private static class ProcessOutputReader extends Thread {
317 private final BufferedReader processReader;
318 private final String command;
319
320 ProcessOutputReader(String command, InputStream processStream) {
321 processReader = new BufferedReader(new InputStreamReader(processStream));
322 this.command = command;
323 setDaemon(true);
324 }
325
326 @Override
327 public void run() {
328 try {
329 String line;
330 while ((line = processReader.readLine()) != null) {
331
332 if (line.indexOf("unrecognized") == -1 && line.indexOf("port") == -1)
333 System.err.println(command + ": " + line);
334 }
335 } catch (IOException ioException) {
336 ioException.printStackTrace();
337 } finally {
338 try {
339 processReader.close();
340 } catch (Exception exc) {
341 exc.printStackTrace();
342 }
343 }
344 }
345 }
346 }