/*
* JavaInspect - Utility to visualize java software
- * Copyright (C) 2013, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
- *
+ * Copyright (C) 2013-2017, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ *
* This program is free software; you can redistribute it and/or
- * modify it under the terms of version 2 of the GNU General Public License
- * as published by the Free Software Foundation.
+ * modify it under the terms of version 3 of the GNU Lesser General Public License
+ * or later as published by the Free Software Foundation.
*/
package eu.svjatoslav.inspector.java.structure;
+import eu.svjatoslav.commons.file.CommonPathResolver;
+import eu.svjatoslav.commons.string.WildCardMatcher;
+import eu.svjatoslav.inspector.java.methods.Clazz;
+import eu.svjatoslav.inspector.java.methods.ProjectScanner;
+
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
-import eu.svjatoslav.commons.file.CommonPathResolver;
+import static eu.svjatoslav.inspector.java.methods.JavaFile.UTF_8;
+import static java.io.File.separator;
public class ClassGraph {
- /**
- * Maps class fully qualified names to class descriptors.
- */
- Map<String, ClassDescriptor> nameToClassMap = new HashMap<String, ClassDescriptor>();
-
- public ClassGraph() {
- }
-
- public ClassGraph(final Class<? extends Object> clazz) {
- addClass(clazz);
- }
-
- public ClassGraph(final Object root) {
- addClass(root.getClass());
- }
-
- public ClassDescriptor addClass(final Class<? extends Object> clazz) {
-
- if (clazz == null)
- return null;
-
- final String className = clazz.getName();
-
- if (nameToClassMap.containsKey(className))
- return nameToClassMap.get(className);
-
- return new ClassDescriptor(clazz, this);
- }
-
- public ClassDescriptor addObject(final Object object) {
- return addClass(object.getClass());
- }
-
- public void generateGraph(final String graphName) {
- generateGraph(graphName, false);
- }
-
- public void generateGraph(final String graphName, final boolean keepDotFile) {
-
- final String desktopPath = CommonPathResolver.getDesktopDirectory()
- .getAbsolutePath() + "/";
+ /**
+ * Maps class fully qualified names to class descriptors.
+ */
+ private final Map<String, ClassDescriptor> fullyQualifiedNameToClassMap = new HashMap<String, ClassDescriptor>();
+
+ private final List<String> blacklistClassGlobs = new ArrayList<String>();
+
+ private final List<String> whitelistClassGlobs = new ArrayList<>();
+
+ private String targetDirectoryPath = CommonPathResolver.getDesktopDirectory()
+ .getAbsolutePath() + separator;
+
+ private boolean keepDotFile;
+
+ TargetImageType targetImageType = TargetImageType.SVG;
+
+ public ClassGraph() {
+ }
+
+ /**
+ * @param objects objects that shall be added to graph
+ * @return this {@link ClassGraph}
+ */
+ public ClassGraph add(final Object... objects) {
+
+ if (objects != null)
+ for (final Object object : objects)
+ addObject(object);
+
+ return this;
+ }
+
+ private void addObject(final Object object) {
+ if (object instanceof Class)
+ getOrCreateClassDescriptor((Class) object);
+ else
+ getOrCreateClassDescriptor(object.getClass());
+ }
+
+ /**
+ * @param path path to recursively scan for java source code could be
+ * relative to current project or absolute
+ */
+ public void addProject(final String path) {
+ final ProjectScanner projectScanner = new ProjectScanner(new File(path));
+ for (final Clazz clazz : projectScanner.getAllClasses())
+ try {
+ System.out.println("Class full name: " + clazz.getFullName());
+ final Class c = this.getClass().forName(clazz.getFullName());
+ addObject(c);
+ } catch (final Exception exception) {
+ System.out.println("cannot add class: "
+ + exception.getMessage());
+ }
+ }
+
+ public void blacklistClassGlob(final String glob) {
+ blacklistClassGlobs.add(glob);
+ }
+
+ public void setTargetImageType(TargetImageType targetImageType) {
+ this.targetImageType = targetImageType;
+ }
+
+ /**
+ * @param resultFileName file name for the generated graph. File extension will be
+ * added automatically. Existing file with the same name will be
+ * overwritten.
+ */
+
+ public void generateGraph(final String resultFileName) {
+
+ final String dotFilePath = targetDirectoryPath + resultFileName + ".dot";
+ final String imageFilePath = targetDirectoryPath + resultFileName + "." + targetImageType.fileExtension;
+
+ try {
+ // write DOT file to disk
+ final PrintWriter out = new PrintWriter(dotFilePath, UTF_8);
+ out.write(getDot());
+ out.close();
+
+ // execute GraphViz to visualize graph
+ try {
+ Runtime.getRuntime()
+ .exec(new String[]{"dot", "-T" + targetImageType.fileExtension, dotFilePath, "-o",
+ imageFilePath}).waitFor();
+ } catch (final InterruptedException ignored) {
+ }
+
+ if (!keepDotFile)
+ // delete dot file
+ if (!new File(dotFilePath).delete())
+ throw new RuntimeException("Cannot delete file: " + dotFilePath);
+
+ } catch (final IOException e) {
+ throw new RuntimeException("Unable to generate graph: " + e.getMessage(), e);
+ }
+
+ }
+
+ private String getDot() {
+ final StringBuffer result = new StringBuffer();
+
+ result.append("digraph Java {\n");
+ result.append("graph [rankdir=LR, overlap = false, concentrate=true];\n");
+
+ for (final Map.Entry<String, ClassDescriptor> entry : fullyQualifiedNameToClassMap
+ .entrySet())
+ result.append(entry.getValue().getDot());
+
+ result.append("}\n");
+
+ final String resultStr = result.toString();
+ return resultStr;
+ }
+
+ /**
+ * @param clazz class that shall be added to graph
+ * @return {@link ClassDescriptor} corresponding to given {@link Class}
+ */
+ protected ClassDescriptor getOrCreateClassDescriptor(final Class clazz) {
+
+ if (clazz == null)
+ return null;
+
+ final String classFullyQualifiedName = clazz.getName();
+
+ // reuse existing instance if possible
+ if (fullyQualifiedNameToClassMap.containsKey(classFullyQualifiedName))
+ return fullyQualifiedNameToClassMap.get(classFullyQualifiedName);
+
+ // create new class descriptor
+ final ClassDescriptor newClassDescriptor = new ClassDescriptor(this);
+ fullyQualifiedNameToClassMap.put(classFullyQualifiedName,
+ newClassDescriptor);
+
+ newClassDescriptor.analyzeClass(clazz);
+
+ return newClassDescriptor;
+ }
+
+ /**
+ * Hide orphaned class that have no references
+ *
+ * @return this {@link ClassGraph}
+ */
+ public ClassGraph hideOrphanedClasses() {
+
+ for (final ClassDescriptor classDescriptor : fullyQualifiedNameToClassMap
+ .values())
+ classDescriptor.hideClassIfNoReferences();
- final String dotFilePath = desktopPath + graphName + ".dot";
- final String imageFilePath = desktopPath + graphName + ".png";
+ return this;
+ }
- System.out.println("Dot file path:" + dotFilePath);
+ protected boolean isClassShown(final String className) {
+ for (final String pattern : blacklistClassGlobs)
+ if (WildCardMatcher.match(className, pattern))
+ return false;
- try {
- // write DOT file to disk
- final PrintWriter out = new PrintWriter(dotFilePath);
- out.write(getDot());
- out.close();
+ if (!whitelistClassGlobs.isEmpty()) {
+ for (final String pattern : whitelistClassGlobs)
+ if (WildCardMatcher.match(className, pattern))
+ return true;
+ return false;
+ }
- // execute GraphViz to visualize graph
- try {
- Runtime.getRuntime()
- .exec(new String[] { "dot", "-Tpng", dotFilePath, "-o",
- imageFilePath }).waitFor();
- } catch (final InterruptedException e) {
- } finally {
- }
+ return true;
+ }
- if (!keepDotFile) {
- // delete dot file
- final File dotFile = new File(dotFilePath);
- dotFile.delete();
- }
- } catch (final IOException e) {
- System.err.println(e);
- }
- }
+ public ClassGraph setKeepDotFile(final boolean keepDotFile) {
+ this.keepDotFile = keepDotFile;
- private String getDot() {
- final StringBuffer result = new StringBuffer();
+ return this;
+ }
- result.append("digraph Java {\n");
- result.append("graph [rankdir=LR, overlap = false, concentrate=true];\n");
+ public ClassGraph setTargetDirectoryPath(String directoryPath) {
+ if (!directoryPath.endsWith(separator))
+ directoryPath += separator;
- for (final Map.Entry<String, ClassDescriptor> entry : nameToClassMap
- .entrySet())
- result.append(entry.getValue().getDot());
+ targetDirectoryPath = directoryPath;
- result.append("}\n");
+ return this;
+ }
- final String resultStr = result.toString();
- return resultStr;
- }
+ public ClassGraph whitelistClassGlob(final String glob) {
+ whitelistClassGlobs.add(glob);
+ return this;
+ }
}