Updated pattern name to glob to be more specific. Updated copyright.
[javainspect.git] / src / main / java / eu / svjatoslav / inspector / java / structure / ClassGraph.java
1 /*
2  * JavaInspect - Utility to visualize java software
3  * Copyright (C) 2013-2017, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 3 of the GNU Lesser General Public License
7  * or later as published by the Free Software Foundation.
8  */
9
10 package eu.svjatoslav.inspector.java.structure;
11
12 import eu.svjatoslav.commons.file.CommonPathResolver;
13 import eu.svjatoslav.commons.string.WildCardMatcher;
14 import eu.svjatoslav.inspector.java.methods.Clazz;
15 import eu.svjatoslav.inspector.java.methods.ProjectScanner;
16
17 import java.io.File;
18 import java.io.IOException;
19 import java.io.PrintWriter;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25 import static eu.svjatoslav.inspector.java.methods.JavaFile.UTF_8;
26 import static java.io.File.separator;
27
28 public class ClassGraph {
29
30     /**
31      * Maps class fully qualified names to class descriptors.
32      */
33     private final Map<String, ClassDescriptor> fullyQualifiedNameToClassMap = new HashMap<String, ClassDescriptor>();
34
35     private final List<String> blacklistClassGlobs = new ArrayList<String>();
36
37     private final List<String> whitelistClassGlobs = new ArrayList<>();
38
39     private String targetDirectoryPath = CommonPathResolver.getDesktopDirectory()
40             .getAbsolutePath() + separator;
41
42     private boolean keepDotFile;
43
44     TargetImageType targetImageType = TargetImageType.SVG;
45
46     public ClassGraph() {
47     }
48
49     /**
50      * @param objects objects that shall be added to graph
51      * @return this {@link ClassGraph}
52      */
53     public ClassGraph add(final Object... objects) {
54
55         if (objects != null)
56             for (final Object object : objects)
57                 addObject(object);
58
59         return this;
60     }
61
62     private void addObject(final Object object) {
63         if (object instanceof Class)
64             getOrCreateClassDescriptor((Class) object);
65         else
66             getOrCreateClassDescriptor(object.getClass());
67     }
68
69     /**
70      * @param path path to recursively scan for java source code could be
71      *             relative to current project or absolute
72      */
73     public void addProject(final String path) {
74         final ProjectScanner projectScanner = new ProjectScanner(new File(path));
75         for (final Clazz clazz : projectScanner.getAllClasses())
76             try {
77                 System.out.println("Class full name: " + clazz.getFullName());
78                 final Class c = this.getClass().forName(clazz.getFullName());
79                 addObject(c);
80             } catch (final Exception exception) {
81                 System.out.println("cannot add class: "
82                         + exception.getMessage());
83             }
84     }
85
86     public void blacklistClassGlob(final String glob) {
87         blacklistClassGlobs.add(glob);
88     }
89
90     public void setTargetImageType(TargetImageType targetImageType) {
91         this.targetImageType = targetImageType;
92     }
93
94     /**
95      * @param resultFileName file name for the generated graph. File extension will be
96      *                       added automatically. Existing file with the same name will be
97      *                       overwritten.
98      */
99
100     public void generateGraph(final String resultFileName) {
101
102         final String dotFilePath = targetDirectoryPath + resultFileName + ".dot";
103         final String imageFilePath = targetDirectoryPath + resultFileName + "." + targetImageType.fileExtension;
104
105         try {
106             // write DOT file to disk
107             final PrintWriter out = new PrintWriter(dotFilePath, UTF_8);
108             out.write(getDot());
109             out.close();
110
111             // execute GraphViz to visualize graph
112             try {
113                 Runtime.getRuntime()
114                         .exec(new String[]{"dot", "-T" + targetImageType.fileExtension, dotFilePath, "-o",
115                                 imageFilePath}).waitFor();
116             } catch (final InterruptedException ignored) {
117             }
118
119             if (!keepDotFile)
120                 // delete dot file
121                 if (!new File(dotFilePath).delete())
122                     throw new RuntimeException("Cannot delete file: " + dotFilePath);
123
124         } catch (final IOException e) {
125             throw new RuntimeException("Unable to generate graph: " + e.getMessage(), e);
126         }
127
128     }
129
130     private String getDot() {
131         final StringBuffer result = new StringBuffer();
132
133         result.append("digraph Java {\n");
134         result.append("graph [rankdir=LR, overlap = false, concentrate=true];\n");
135
136         for (final Map.Entry<String, ClassDescriptor> entry : fullyQualifiedNameToClassMap
137                 .entrySet())
138             result.append(entry.getValue().getDot());
139
140         result.append("}\n");
141
142         final String resultStr = result.toString();
143         return resultStr;
144     }
145
146     /**
147      * @param clazz class that shall be added to graph
148      * @return {@link ClassDescriptor} corresponding to given {@link Class}
149      */
150     protected ClassDescriptor getOrCreateClassDescriptor(final Class clazz) {
151
152         if (clazz == null)
153             return null;
154
155         final String classFullyQualifiedName = clazz.getName();
156
157         // reuse existing instance if possible
158         if (fullyQualifiedNameToClassMap.containsKey(classFullyQualifiedName))
159             return fullyQualifiedNameToClassMap.get(classFullyQualifiedName);
160
161         // create new class descriptor
162         final ClassDescriptor newClassDescriptor = new ClassDescriptor(this);
163         fullyQualifiedNameToClassMap.put(classFullyQualifiedName,
164                 newClassDescriptor);
165
166         newClassDescriptor.analyzeClass(clazz);
167
168         return newClassDescriptor;
169     }
170
171     /**
172      * Hide orphaned class that have no references
173      *
174      * @return this {@link ClassGraph}
175      */
176     public ClassGraph hideOrphanedClasses() {
177
178         for (final ClassDescriptor classDescriptor : fullyQualifiedNameToClassMap
179                 .values())
180             classDescriptor.hideClassIfNoReferences();
181
182         return this;
183     }
184
185     protected boolean isClassShown(final String className) {
186         for (final String pattern : blacklistClassGlobs)
187             if (WildCardMatcher.match(className, pattern))
188                 return false;
189
190         if (!whitelistClassGlobs.isEmpty()) {
191             for (final String pattern : whitelistClassGlobs)
192                 if (WildCardMatcher.match(className, pattern))
193                     return true;
194             return false;
195         }
196
197         return true;
198     }
199
200     public ClassGraph setKeepDotFile(final boolean keepDotFile) {
201         this.keepDotFile = keepDotFile;
202
203         return this;
204     }
205
206     public ClassGraph setTargetDirectoryPath(String directoryPath) {
207         if (!directoryPath.endsWith(separator))
208             directoryPath += separator;
209
210         targetDirectoryPath = directoryPath;
211
212         return this;
213     }
214
215     public ClassGraph whitelistClassGlob(final String glob) {
216         whitelistClassGlobs.add(glob);
217         return this;
218     }
219
220 }