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