--- /dev/null
+/.settings/
+/.project
+/.classpath
+/target/
+/.idea/
+dependency-reduced-pom.xml
+/example/target
+/*.iml
+/JavaInspect.dot
+/JavaInspect.svg
+/doc/index.html
+/doc/usage\ cli.html
+/doc/graphs/
+/doc/apidocs/
--- /dev/null
+# JavaInspect - Quick Reference
+
+Utility to visualize Java software structure through reflection and GraphViz.
+
+---
+
+# Quick Lookup: "I Want To..."
+
+| Task | Class (path) | Key Method |
+|--------------------------------|--------------------------------------------|---------------------------------------------|
+| **Create a graph** | `ClassGraph` (`structure/ClassGraph.java`) | `new ClassGraph()` |
+| **Add classes to graph** | `ClassGraph` | `.add(object)` or `.add(Class)` |
+| **Generate visualization** | `ClassGraph` | `.generateGraph("name")` → produces SVG/PNG |
+| **Set output directory** | `ClassGraph` | `.setTargetDirectory(new File(path))` |
+| **Keep DOT file** | `ClassGraph` | `.setKeepDotFile(true)` |
+| **Filter classes (blacklist)** | `ClassGraph` | `.blacklistClassGlob("java.*")` |
+| **Filter classes (whitelist)** | `ClassGraph` | `.whitelistClassGlob("com.myapp.*")` |
+| **Hide orphaned classes** | `ClassGraph` | `.hideOrphanedClasses()` |
+| **Set output format** | `ClassGraph` | `.setTargetImageType(TargetImageType.SVG)` |
+| **Run from command line** | `Main` (`commandline/Main.java`) | `java -jar javainspect.jar -j myapp.jar` |
+
+---
+
+# Code Examples
+
+## Basic Usage
+
+```java
+import eu.svjatoslav.inspector.java.structure.ClassGraph;
+
+// Simple one-liner
+new ClassGraph().add(myObject, MyClass.class).generateGraph("MyGraph");
+
+// With configuration
+ClassGraph graph = new ClassGraph();
+graph.add(myObject); // Add object (class detected automatically)
+graph.add(MyClass.class); // Add class directly
+graph.setKeepDotFile(true); // Keep intermediate DOT file
+graph.setTargetDirectory(new File("/tmp"));
+graph.generateGraph("MyApp"); // Generates MyApp.svg
+```
+
+## Filtering with Glob Patterns
+
+```java
+ClassGraph graph = new ClassGraph();
+graph.add(myApp);
+
+// Hide standard library classes
+graph.blacklistClassGlob("java.*");
+graph.blacklistClassGlob("javax.*");
+
+// Only show my package
+graph.whitelistClassGlob("com.mycompany.*");
+
+// Hide test classes
+graph.blacklistClassGlob("*Test");
+
+// Hide classes with no connections
+graph.hideOrphanedClasses();
+
+graph.generateGraph("filtered");
+```
+
+## Analyzing JAR Files from Command Line
+
+```bash
+# Basic usage
+javainspect -j myapp.jar -n MyAppGraph
+
+# Multiple JARs with classpath
+javainspect -j app.jar -j lib.jar -c /path/to/classes -n Combined
+
+# With filters
+javainspect -j myapp.jar -n graph \
+ -b "java.*" -b "*Test" \
+ -w "com.mycompany.*" \
+ -ho # hide orphaned classes
+
+# Specify output format and directory
+javainspect -j myapp.jar -n graph -t png -d /tmp/graphs
+
+# Keep DOT file for debugging
+javainspect -j myapp.jar -n graph -k
+
+# Debug mode
+javainspect -j myapp.jar -n graph --debug
+```
+
+## Maven Integration
+
+```xml
+<dependency>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>javainspect</artifactId>
+ <version>1.8</version>
+</dependency>
+
+<repository>
+ <id>svjatoslav.eu</id>
+ <url>https://www3.svjatoslav.eu/maven/</url>
+</repository>
+```
+
+---
+
+# Class Catalog
+
+## Core Structure (`structure/`)
+
+| Class | File | Purpose | Key Methods |
+|--------------------|-------------------------|---------------------------------------|----------------------------------------------------------------------------------------------------------|
+| `ClassGraph` | `ClassGraph.java` | Main entry point, graph container | `.add()`, `.generateGraph()`, `.blacklistClassGlob()`, `.whitelistClassGlob()`, `.hideOrphanedClasses()` |
+| `ClassDescriptor` | `ClassDescriptor.java` | Represents a single class node | `.getDot()`, `.getGraphId()`, `.isVisible()`, `.analyzeClass()` |
+| `FieldDescriptor` | `FieldDescriptor.java` | Represents a field within a class | `.getDot()`, `.getEmbeddedDot()`, `.analyzeField()` |
+| `MethodDescriptor` | `MethodDescriptor.java` | Represents a method within a class | `.getDot()`, `.getEmbeddedDot()`, `.analyze()` |
+| `GraphElement` | `GraphElement.java` | Interface for DOT-renderable elements | `.getDot()`, `.getEmbeddedDot()`, `.getGraphId()`, `.isVisible()` |
+| `Utils` | `Utils.java` | Filtering helpers, color palettes | `.isSystemDataType()`, `.isSystemPackage()`, `.isCommonObjectMethod()`, `.getNextDarkColor()` |
+| `TargetImageType` | `TargetImageType.java` | Output format enum | `SVG`, `PNG` (field: `.fileExtension`) |
+
+## Command Line (`commandline/`)
+
+| Class | File | Purpose |
+|----------------------------|---------------------------------|----------------------------------------|
+| `Main` | `Main.java` | CLI entry point, JAR processing |
+| `CommandlineConfiguration` | `CommandlineConfiguration.java` | Argument parsing, option storage |
+| `TargetImageTypeParameter` | `TargetImageTypeParameter.java` | Custom parameter type for image format |
+
+---
+
+# Architecture
+
+## Graph Building Pipeline
+
+```
+ClassGraph.add(object/class)
+ → getOrCreateClassDescriptor(clazz)
+ → ClassDescriptor.analyzeClass(clazz)
+ → indexFields() → FieldDescriptor.analyzeField()
+ → indexMethods() → MethodDescriptor.analyze()
+ → getOrCreateClassDescriptor(superClass, interfaces)
+ → (recursive for all referenced types)
+
+ClassGraph.generateGraph(name)
+ → getDot() → iterate all ClassDescriptor.getDot()
+ → write DOT file
+ → execute GraphViz (dot -Tsvg)
+ → optionally delete DOT file
+```
+
+## Visibility Filtering
+
+Classes are hidden based on:
+
+1. **System data types**: `void`, `int`, `long`, `boolean`, etc.
+2. **System packages**: `java.*`, `javax.*`, `sun.*`
+3. **Glob blacklist**: classes matching any blacklist pattern
+4. **Glob whitelist**: if defined, only classes matching whitelist shown
+5. **Orphan check**: classes with zero incoming/outgoing references
+
+## DOT Output Structure
+
+Each visible class generates:
+
+1. **Node**: HTML-like table with:
+ - Package name (small font)
+ - Class name (large font)
+ - Fields (type + name rows)
+ - Methods (return type + name rows, red color)
+
+2. **Edges**:
+ - Field → type references (solid arrows)
+ - Method → return type references (dotted arrows)
+ - Interface → implementation (dotted forward arrows)
+ - Superclass → subclass (thick forward arrows)
+
+---
+
+# Build & Test
+
+```bash
+# Build
+mvn clean package
+
+# Run tests
+mvn test
+
+# Generate Javadoc
+mvn javadoc:javadoc
+
+# Run self-visualization demo
+mvn exec:java -Dexec.mainClass="eu.svjatoslav.inspector.java.RenderJavaInspect"
+```
+
+---
+
+# Command Line Options
+
+| Option | Aliases | Description |
+|---------------|----------------|-------------------------------------|
+| JAR files | `-j` | JAR file(s) to analyze |
+| Classpath | `-c` | Additional classpath directories |
+| Graph name | `-n` | Output file name (default: "graph") |
+| Image type | `-t` | svg or png (default: svg) |
+| Target dir | `-d` | Output directory (default: current) |
+| Keep DOT | `-k` | Keep intermediate DOT file |
+| Hide orphaned | `-ho` | Hide classes with no connections |
+| Whitelist | `-w` | Glob patterns to include |
+| Blacklist | `-b` | Glob patterns to exclude |
+| Root classes | `-r` | Specific classes to start from |
+| Debug | `--debug` | Show processing details |
+| Help | `-h`, `--help` | Show usage |
+
+---
+
+# Tips for AI Agents
+
+1. **ClassGraph is the entry point**: Always start with `new ClassGraph()`
+2. **Method chaining**: `ClassGraph` methods return `this` for chaining
+3. **Glob patterns**: Use `*` for any sequence, `?` for single char
+4. **Reflection-based**: Classes must be loadable in JVM (classpath required)
+5. **GraphViz required**: `dot` binary must be on system PATH
+6. **SVG recommended**: Better for large graphs, PNG for small ones
+7. **Filter early**: Large applications produce huge graphs - use blacklist/whitelist
+8. **Hide orphaned**: Reduces visual noise from disconnected utility classes
+
+---
+
+# Documentation
+
+| Path | Topic |
+|-----------------|-------------------------------------------------------|
+| `doc/index.org` | Main documentation, usage examples, Maven integration |
+| `doc/apidocs/` | Generated Javadoc |
+| `COPYING` | CC0 license full text |
\ No newline at end of file
--- /dev/null
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
--- /dev/null
+* Bugs
+:PROPERTIES:
+:CUSTOM_ID: bugs
+:END:
+
+** Reference counting should exclude non-visible classes
+:PROPERTIES:
+:CUSTOM_ID: ref-count-exclude-nonvisible
+:END:
+Should not hide references if there are too many of them to classes if
+referring classes are not visible anyway because of blacklist/whitelist
+rules. Basically reference counting should exclude not visible classes.
+
+** Orphaned class removal does not work always
+:PROPERTIES:
+:CUSTOM_ID: orphaned-removal-bugs
+:END:
+There are many bugs and corner cases to find and fix still.
+
+** Code readability needs improvement
+:PROPERTIES:
+:CUSTOM_ID: code-readability
+:END:
+Document and refactor for better maintainability.
+
+* Packaging
+:PROPERTIES:
+:CUSTOM_ID: packaging
+:END:
+
+** Create installable DEB package
+:PROPERTIES:
+:CUSTOM_ID: create-deb-package
+:END:
+Submit it to some Debian developer for integration or become Debian
+package maintainer.
+
+* Architecture
+:PROPERTIES:
+:CUSTOM_ID: architecture
+:END:
+
+** Make modular with central application model
+:PROPERTIES:
+:CUSTOM_ID: modular-architecture
+:END:
+Central part, an application model could be standalone and serializable.
+
+Multiple ways to acquire model:
++ By introspecting application via Java reflections (current mode)
++ By parsing java source (unfinished)
+
+Multiple ways to manipulate model:
++ Store/load/compare
++ Trim uninteresting parts
++ Highlight important parts
+
+Multiple ways to render model:
++ PNG/SVG (currently implemented)
++ PlantUML (TODO)
++ Interactive 3D visualization (TODO)
+
+* Features
+:PROPERTIES:
+:CUSTOM_ID: features
+:END:
+
+** Implement Java source parser
+:PROPERTIES:
+:CUSTOM_ID: java-source-parser
+:END:
+Implement (or integrate existing java parser
+https://javaparser.org/) to be able to produce code visualizations
+based on source code (in addition to current reflection based approach).
+
+** Integrate with PlantUML
+:PROPERTIES:
+:CUSTOM_ID: plantuml-integration
+:END:
+http://plantuml.com/class-diagram
+
+** Add dark theme for generated graphs
+:PROPERTIES:
+:CUSTOM_ID: dark-theme
+:END:
+
+** Sort class fields alphabetically
+:PROPERTIES:
+:CUSTOM_ID: sort-fields-alphabetically
+:END:
+
+** Visualize concrete field values
+:PROPERTIES:
+:CUSTOM_ID: visualize-field-values
+:END:
+Could be used as ultra cool runtime logging/debugging framework.
+
+** Visualize from JVM snapshot
+:PROPERTIES:
+:CUSTOM_ID: jvm-snapshot
+:END:
+
+** Attach to remote process via JVM debug port
+:PROPERTIES:
+:CUSTOM_ID: remote-process-debug
+:END:
+
+** Attach to JVM using JVM agent
+:PROPERTIES:
+:CUSTOM_ID: jvm-agent
+:END:
+
+** 3D visualization with Sixth 3D engine
+:PROPERTIES:
+:CUSTOM_ID: 3d-visualization
+:END:
+https://www3.svjatoslav.eu/projects/sixth-3d/
+
+** Add graph query language
+:PROPERTIES:
+:CUSTOM_ID: graph-query-language
+:END:
+Select classes/fields/values to be visualized in some graph query
+language. For greater flexibility in comparison to currently supported
+glob syntax.
+
+** Add JSON/XML config file support
+:PROPERTIES:
+:CUSTOM_ID: config-file
+:END:
+Different graphs for given project could be defined once in plain text
+config, possibly with the aid of some interactive utility. Then defined
+graphs could be updated as part of project build or release process.
+
+** Configurable Maven plugin
+:PROPERTIES:
+:CUSTOM_ID: maven-plugin
+:END:
+Generate graphs as part of the project build/release process.
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+#
+# This is a helper bash script that starts IntelliJ with the current project.
+# Script is written is such a way that you can simply click on it in file
+# navigator to run it.
+#
+#
+# Script assumes:
+#
+# + GNU operating system
+# + IntelliJ is installed and commandline launcher "idea" is enabled.
+#
+
+cd "${0%/*}"
+cd ..
+
+setsid idea . &>/dev/null
--- /dev/null
+#!/bin/bash
+cd "${0%/*}"; if [ "$1" != "T" ]; then gnome-terminal -- "$0" T; exit; fi;
+
+cd ..
+
+# Function to export org to html using emacs in batch mode
+export_org_to_html() {
+ local org_file=$1
+ local dir=$(dirname "$org_file")
+ local base=$(basename "$org_file" .org)
+ (
+ cd "$dir" || return 1
+ local html_file="${base}.html"
+ if [ -f "$html_file" ]; then
+ rm -f "$html_file"
+ fi
+ echo "Exporting: $org_file → $dir/$html_file"
+ emacs --batch -l ~/.emacs --visit="${base}.org" --funcall=org-html-export-to-html --kill
+ if [ $? -eq 0 ]; then
+ echo "✓ Successfully exported $org_file"
+ else
+ echo "✗ Failed to export $org_file"
+ return 1
+ fi
+ )
+}
+
+export_org_files_to_html() {
+ echo "🔍 Searching for .org files in doc/ ..."
+ echo "======================================="
+
+ mapfile -t ORG_FILES < <(find doc -type f -name "*.org" | sort)
+
+ if [ ${#ORG_FILES[@]} -eq 0 ]; then
+ echo "❌ No .org files found!"
+ return 1
+ fi
+
+ echo "Found ${#ORG_FILES[@]} .org file(s):"
+ printf '%s\n' "${ORG_FILES[@]}"
+ echo "======================================="
+
+ SUCCESS_COUNT=0
+ FAILED_COUNT=0
+
+ for org_file in "${ORG_FILES[@]}"; do
+ export_org_to_html "$org_file"
+ if [ $? -eq 0 ]; then
+ ((SUCCESS_COUNT++))
+ else
+ ((FAILED_COUNT++))
+ fi
+ done
+
+ echo "======================================="
+ echo "📊 SUMMARY:"
+ echo " ✓ Successful: $SUCCESS_COUNT"
+ echo " ✗ Failed: $FAILED_COUNT"
+ echo " Total: $((SUCCESS_COUNT + FAILED_COUNT))"
+ echo ""
+}
+
+build_visualization_graphs() {
+ echo "🎨 Generating class visualization graphs..."
+ echo "======================================="
+
+ rm -rf doc/graphs/
+ mkdir -p doc/graphs/
+
+ # JavaInspect analyzing itself - meta visualization!
+ javainspect -j target/javainspect.jar -d doc/graphs/ -n "All classes" -t png
+ javainspect -j target/javainspect.jar -d doc/graphs/ -n "Inspector" -t png -w "eu.svjatoslav.inspector.*"
+ javainspect -j target/javainspect.jar -d doc/graphs/ -n "Java parser" -t png -w "eu.svjatoslav.inspector.java.*"
+
+ # Generate index page for graphs
+ meviz index -w doc/graphs/ -t "JavaInspect classes"
+
+ echo "✓ Visualization graphs generated"
+ echo ""
+}
+
+# Build project jar file and JavaDocs
+echo "🔨 Building project..."
+echo "======================================="
+mvn clean package
+
+if [ $? -ne 0 ]; then
+ echo "✗ Maven build failed!"
+ echo ""
+ echo "Press ENTER to close this window."
+ read
+ exit 1
+fi
+
+echo "✓ Maven build completed"
+echo ""
+
+# Put generated JavaDoc HTML files to documentation directory
+echo "📚 Copying JavaDocs..."
+echo "======================================="
+rm -rf doc/apidocs/
+cp -r target/apidocs/ doc/
+
+echo "✓ JavaDocs copied to doc/apidocs/"
+echo ""
+
+# Publish Emacs org-mode files into HTML format
+export_org_files_to_html
+
+# Generate nice looking code visualization diagrams
+build_visualization_graphs
+
+## Upload assembled documentation to server
+echo "📤 Uploading to server..."
+echo "======================================="
+rsync -avz --delete -e 'ssh -p 10006' doc/ \
+ n0@www3.svjatoslav.eu:/mnt/big/projects/javainspect/
+
+if [ $? -eq 0 ]; then
+ echo "✓ Upload completed successfully!"
+else
+ echo "✗ Upload failed!"
+fi
+
+echo ""
+echo "Press ENTER to close this window."
+read
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+#
+# This script installs JavaInspect into /opt/javainspect/ directory.
+# It also enables javainspect command at the commandline.
+#
+
+(
+ # go to project root directory
+ cd ..
+
+ # remove old installation (if any)
+ sudo rm -rf /opt/javainspect/
+
+ # compile new package
+ mvn clean package
+
+ # create installation directory
+ sudo mkdir -p /opt/javainspect/
+
+ # copy javainspect jar file to opt installation directory
+ sudo cp target/javainspect.jar /opt/javainspect/
+)
+
+# deploy launcher script to /usr/local/bin/javainspect
+sudo cp javainspect /usr/local/bin/
--- /dev/null
+#!/bin/bash
+#
+# Launcher script for JavaInspect utility:
+# https://www3.svjatoslav.eu/projects/javainspect/
+#
+
+java -jar /opt/javainspect/javainspect.jar "$@"
--- /dev/null
+digraph Java {
+graph [rankdir=LR, overlap = false, concentrate=true];
+
+// Class: eu.svjatoslav.inspector.java.structure.Filter
+ class_eu_svjatoslav_inspector_java_structure_Filter[label=<<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0">
+
+ // class descriptor header
+ <TR><TD colspan="2" PORT="f0"><FONT POINT-SIZE="8.0" >eu.svjatoslav.inspector.java.structure</FONT><br/><FONT POINT-SIZE="25.0"><B>Filter</B></FONT></TD></TR>
+
+ // fields:
+ // blacklistClassPatterns
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="blacklistClassPatterns" ALIGN="left"><FONT POINT-SIZE="11.0">blacklistClassPatterns</FONT></TD></TR>
+ // whitelistClassPatterns
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="whitelistClassPatterns" ALIGN="left"><FONT POINT-SIZE="11.0">whitelistClassPatterns</FONT></TD></TR>
+
+ // methods:
+ // blacklistClassPattern
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="blacklistClassPattern" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">blacklistClassPattern</FONT></TD></TR>
+ // isClassShown
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isClassShown" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isClassShown</FONT></TD></TR>
+ // whitelistClassPattern
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="whitelistClassPattern" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">whitelistClassPattern</FONT></TD></TR>
+ </TABLE>>, shape="none"];
+
+ // field references to other classes
+
+ // method references to other classes
+
+// Class: eu.svjatoslav.inspector.java.structure.FieldDescriptor
+ class_eu_svjatoslav_inspector_java_structure_FieldDescriptor[label=<<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0">
+
+ // class descriptor header
+ <TR><TD colspan="2" PORT="f0"><FONT POINT-SIZE="8.0" >eu.svjatoslav.inspector.java.structure</FONT><br/><FONT POINT-SIZE="25.0"><B>FieldDescriptor</B></FONT></TD></TR>
+
+ // fields:
+ // name
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="name" ALIGN="left"><FONT POINT-SIZE="11.0">name</FONT></TD></TR>
+ // parent
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassDescriptor</FONT></td><TD PORT="parent" ALIGN="left"><FONT POINT-SIZE="11.0">parent</FONT></TD></TR>
+ // type
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassDescriptor</FONT></td><TD PORT="type" ALIGN="left"><FONT POINT-SIZE="11.0">type</FONT></TD></TR>
+ // typeArguments
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="typeArguments" ALIGN="left"><FONT POINT-SIZE="11.0">typeArguments</FONT></TD></TR>
+
+ // methods:
+ // getDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getDot</FONT></TD></TR>
+ // getEmbeddedDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getEmbeddedDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getEmbeddedDot</FONT></TD></TR>
+ // getGraphId
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getGraphId" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getGraphId</FONT></TD></TR>
+ // getOutsideVisibleReferencesCount
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="getOutsideVisibleReferencesCount" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getOutsideVisibleReferencesCount</FONT></TD></TR>
+ // isVisible
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isVisible" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isVisible</FONT></TD></TR>
+ </TABLE>>, shape="none"];
+
+ // field references to other classes
+
+ // method references to other classes
+
+ // interfaces implemented by class: eu.svjatoslav.inspector.java.structure.FieldDescriptor
+ class_eu_svjatoslav_inspector_java_structure_GraphElement -> class_eu_svjatoslav_inspector_java_structure_FieldDescriptor[style="dotted, tapered", color="olivedrab2", penwidth=20, dir="forward"];
+
+// Class: eu.svjatoslav.inspector.java.structure.MethodDescriptor
+ class_eu_svjatoslav_inspector_java_structure_MethodDescriptor[label=<<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0">
+
+ // class descriptor header
+ <TR><TD colspan="2" PORT="f0"><FONT POINT-SIZE="8.0" >eu.svjatoslav.inspector.java.structure</FONT><br/><FONT POINT-SIZE="25.0"><B>MethodDescriptor</B></FONT></TD></TR>
+
+ // fields:
+ // name
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="name" ALIGN="left"><FONT POINT-SIZE="11.0">name</FONT></TD></TR>
+ // parent
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassDescriptor</FONT></td><TD PORT="parent" ALIGN="left"><FONT POINT-SIZE="11.0">parent</FONT></TD></TR>
+ // returnType
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassDescriptor</FONT></td><TD PORT="returnType" ALIGN="left"><FONT POINT-SIZE="11.0">returnType</FONT></TD></TR>
+ // typeArguments
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="typeArguments" ALIGN="left"><FONT POINT-SIZE="11.0">typeArguments</FONT></TD></TR>
+
+ // methods:
+ // compareTo
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="compareTo" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">compareTo</FONT></TD></TR>
+ // compareTo
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="compareTo" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">compareTo</FONT></TD></TR>
+ // getDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getDot</FONT></TD></TR>
+ // getEmbeddedDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getEmbeddedDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getEmbeddedDot</FONT></TD></TR>
+ // getGraphId
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getGraphId" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getGraphId</FONT></TD></TR>
+ // getMethodLabel
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getMethodLabel" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getMethodLabel</FONT></TD></TR>
+ // getOutsideVisibleReferencesCount
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="getOutsideVisibleReferencesCount" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getOutsideVisibleReferencesCount</FONT></TD></TR>
+ // isVisible
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isVisible" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isVisible</FONT></TD></TR>
+ </TABLE>>, shape="none"];
+
+ // field references to other classes
+
+ // method references to other classes
+
+ // interfaces implemented by class: eu.svjatoslav.inspector.java.structure.MethodDescriptor
+ class_eu_svjatoslav_inspector_java_structure_GraphElement -> class_eu_svjatoslav_inspector_java_structure_MethodDescriptor[style="dotted, tapered", color="olivedrab2", penwidth=20, dir="forward"];
+
+// Class: eu.svjatoslav.inspector.java.structure.Utils
+ class_eu_svjatoslav_inspector_java_structure_Utils[label=<<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0">
+
+ // class descriptor header
+ <TR><TD colspan="2" PORT="f0"><FONT POINT-SIZE="8.0" >eu.svjatoslav.inspector.java.structure</FONT><br/><FONT POINT-SIZE="25.0"><B>Utils</B></FONT></TD></TR>
+
+ // fields:
+ // darkColors
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="darkColors" ALIGN="left"><FONT POINT-SIZE="11.0">darkColors</FONT></TD></TR>
+ // enumMethods
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="enumMethods" ALIGN="left"><FONT POINT-SIZE="11.0">enumMethods</FONT></TD></TR>
+ // lastChosenDarkColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="lastChosenDarkColor" ALIGN="left"><FONT POINT-SIZE="11.0">lastChosenDarkColor</FONT></TD></TR>
+ // lastChosenLightColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="lastChosenLightColor" ALIGN="left"><FONT POINT-SIZE="11.0">lastChosenLightColor</FONT></TD></TR>
+ // lightColors
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="lightColors" ALIGN="left"><FONT POINT-SIZE="11.0">lightColors</FONT></TD></TR>
+ // systemDataTypes
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="systemDataTypes" ALIGN="left"><FONT POINT-SIZE="11.0">systemDataTypes</FONT></TD></TR>
+ // systemMethods
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="systemMethods" ALIGN="left"><FONT POINT-SIZE="11.0">systemMethods</FONT></TD></TR>
+ // systemPackages
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="systemPackages" ALIGN="left"><FONT POINT-SIZE="11.0">systemPackages</FONT></TD></TR>
+
+ // methods:
+ // getNextDarkColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getNextDarkColor" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getNextDarkColor</FONT></TD></TR>
+ // getNextLightColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getNextLightColor" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getNextLightColor</FONT></TD></TR>
+ // initDarkColors
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="initDarkColors" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">initDarkColors</FONT></TD></TR>
+ // initEnumMethods
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="initEnumMethods" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">initEnumMethods</FONT></TD></TR>
+ // initLightColors
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="initLightColors" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">initLightColors</FONT></TD></TR>
+ // initSystemDataTypes
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="initSystemDataTypes" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">initSystemDataTypes</FONT></TD></TR>
+ // initSystemMethods
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="initSystemMethods" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">initSystemMethods</FONT></TD></TR>
+ // initSystemPackages
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="initSystemPackages" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">initSystemPackages</FONT></TD></TR>
+ // isEnumMethod
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isEnumMethod" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isEnumMethod</FONT></TD></TR>
+ // isSystemDataType
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isSystemDataType" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isSystemDataType</FONT></TD></TR>
+ // isSystemMethod
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isSystemMethod" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isSystemMethod</FONT></TD></TR>
+ // isSystemPackage
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isSystemPackage" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isSystemPackage</FONT></TD></TR>
+ </TABLE>>, shape="none"];
+
+ // field references to other classes
+
+ // method references to other classes
+
+// Class: eu.svjatoslav.inspector.java.structure.GraphElement
+ class_eu_svjatoslav_inspector_java_structure_GraphElement[label=<<TABLE bgcolor="darkslategray1" BORDER="1" CELLBORDER="1" CELLSPACING="0">
+
+ // class descriptor header
+ <TR><TD colspan="2" PORT="f0"><FONT POINT-SIZE="8.0" >eu.svjatoslav.inspector.java.structure</FONT><br/><FONT POINT-SIZE="25.0"><B>GraphElement</B></FONT></TD></TR>
+
+ // methods:
+ // getDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getDot</FONT></TD></TR>
+ // getEmbeddedDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getEmbeddedDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getEmbeddedDot</FONT></TD></TR>
+ // getGraphId
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getGraphId" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getGraphId</FONT></TD></TR>
+ // isVisible
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isVisible" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isVisible</FONT></TD></TR>
+ </TABLE>>, shape="none"];
+
+ // method references to other classes
+
+// Class: eu.svjatoslav.inspector.java.structure.ClassGraph
+ class_eu_svjatoslav_inspector_java_structure_ClassGraph[label=<<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0">
+
+ // class descriptor header
+ <TR><TD colspan="2" PORT="f0"><FONT POINT-SIZE="8.0" >eu.svjatoslav.inspector.java.structure</FONT><br/><FONT POINT-SIZE="25.0"><B>ClassGraph</B></FONT></TD></TR>
+
+ // fields:
+ // filter
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">Filter</FONT></td><TD PORT="filter" ALIGN="left"><FONT POINT-SIZE="11.0">filter</FONT></TD></TR>
+ // nameToClassMap
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">Map</FONT></td><TD PORT="nameToClassMap" ALIGN="left"><FONT POINT-SIZE="11.0">nameToClassMap</FONT></TD></TR>
+
+ // methods:
+ // addClass
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassDescriptor</FONT></td><TD PORT="addClass" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">addClass</FONT></TD></TR>
+ // addObject
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassDescriptor</FONT></td><TD PORT="addObject" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">addObject</FONT></TD></TR>
+ // addProject
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="addProject" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">addProject</FONT></TD></TR>
+ // generateGraph
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="generateGraph" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">generateGraph</FONT></TD></TR>
+ // generateGraph
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="generateGraph" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">generateGraph</FONT></TD></TR>
+ // hideOrphanedClasses
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="hideOrphanedClasses" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">hideOrphanedClasses</FONT></TD></TR>
+ // render
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="render" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">render</FONT></TD></TR>
+ </TABLE>>, shape="none"];
+
+ // field references to other classes
+ class_eu_svjatoslav_inspector_java_structure_ClassGraph:filter -> class_eu_svjatoslav_inspector_java_structure_Filter[label="filter", color="antiquewhite4", style="bold"];
+
+ // method references to other classes
+
+// Class: eu.svjatoslav.inspector.java.structure.ClassDescriptor
+ class_eu_svjatoslav_inspector_java_structure_ClassDescriptor[label=<<TABLE BORDER="4" CELLBORDER="1" CELLSPACING="0">
+
+ // class descriptor header
+ <TR><TD colspan="2" PORT="f0"><FONT POINT-SIZE="8.0" >eu.svjatoslav.inspector.java.structure</FONT><br/><FONT POINT-SIZE="25.0"><B>ClassDescriptor</B></FONT></TD></TR>
+
+ // fields:
+ // MAX_REFERECNES_COUNT
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="MAX_REFERECNES_COUNT" ALIGN="left"><FONT POINT-SIZE="11.0">MAX_REFERECNES_COUNT</FONT></TD></TR>
+ // classGraph
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassGraph</FONT></td><TD PORT="classGraph" ALIGN="left"><FONT POINT-SIZE="11.0">classGraph</FONT></TD></TR>
+ // distinctiveReferenceColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="distinctiveReferenceColor" ALIGN="left"><FONT POINT-SIZE="11.0">distinctiveReferenceColor</FONT></TD></TR>
+ // fullyQualifiedName
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="fullyQualifiedName" ALIGN="left"><FONT POINT-SIZE="11.0">fullyQualifiedName</FONT></TD></TR>
+ // incomingReferencesCount
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">int</FONT></td><TD PORT="incomingReferencesCount" ALIGN="left"><FONT POINT-SIZE="11.0">incomingReferencesCount</FONT></TD></TR>
+ // interfaceColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="interfaceColor" ALIGN="left"><FONT POINT-SIZE="11.0">interfaceColor</FONT></TD></TR>
+ // interfaces
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="interfaces" ALIGN="left"><FONT POINT-SIZE="11.0">interfaces</FONT></TD></TR>
+ // isArray
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isArray" ALIGN="left"><FONT POINT-SIZE="11.0">isArray</FONT></TD></TR>
+ // isEnum
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isEnum" ALIGN="left"><FONT POINT-SIZE="11.0">isEnum</FONT></TD></TR>
+ // isInterface
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isInterface" ALIGN="left"><FONT POINT-SIZE="11.0">isInterface</FONT></TD></TR>
+ // isShown
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isShown" ALIGN="left"><FONT POINT-SIZE="11.0">isShown</FONT></TD></TR>
+ // methods
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">SortedSet</FONT></td><TD PORT="methods" ALIGN="left"><FONT POINT-SIZE="11.0">methods</FONT></TD></TR>
+ // nameToFieldMap
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">Map</FONT></td><TD PORT="nameToFieldMap" ALIGN="left"><FONT POINT-SIZE="11.0">nameToFieldMap</FONT></TD></TR>
+ // superClass
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">ClassDescriptor</FONT></td><TD PORT="superClass" ALIGN="left"><FONT POINT-SIZE="11.0">superClass</FONT></TD></TR>
+ // superClassColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="superClassColor" ALIGN="left"><FONT POINT-SIZE="11.0">superClassColor</FONT></TD></TR>
+
+ // methods:
+ // areReferencesShown
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="areReferencesShown" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">areReferencesShown</FONT></TD></TR>
+ // enlistFieldReferences
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="enlistFieldReferences" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">enlistFieldReferences</FONT></TD></TR>
+ // enlistFields
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="enlistFields" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">enlistFields</FONT></TD></TR>
+ // enlistImplementedInterfaces
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="enlistImplementedInterfaces" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">enlistImplementedInterfaces</FONT></TD></TR>
+ // enlistMethodReferences
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="enlistMethodReferences" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">enlistMethodReferences</FONT></TD></TR>
+ // enlistMethods
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="enlistMethods" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">enlistMethods</FONT></TD></TR>
+ // enlistSuperClass
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="enlistSuperClass" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">enlistSuperClass</FONT></TD></TR>
+ // generateDotHeader
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="generateDotHeader" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">generateDotHeader</FONT></TD></TR>
+ // getAllFields
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">List</FONT></td><TD PORT="getAllFields" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getAllFields</FONT></TD></TR>
+ // getBackgroundColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getBackgroundColor" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getBackgroundColor</FONT></TD></TR>
+ // getBorderWidth
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getBorderWidth" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getBorderWidth</FONT></TD></TR>
+ // getClassName
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getClassName" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getClassName</FONT></TD></TR>
+ // getColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getColor" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getColor</FONT></TD></TR>
+ // getDistinctiveColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getDistinctiveColor" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getDistinctiveColor</FONT></TD></TR>
+ // getDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getDot</FONT></TD></TR>
+ // getEmbeddedDot
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getEmbeddedDot" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getEmbeddedDot</FONT></TD></TR>
+ // getGraphId
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getGraphId" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getGraphId</FONT></TD></TR>
+ // getPackageName
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getPackageName" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getPackageName</FONT></TD></TR>
+ // getParentClassesName
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">String</FONT></td><TD PORT="getParentClassesName" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">getParentClassesName</FONT></TD></TR>
+ // hide
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="hide" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">hide</FONT></TD></TR>
+ // hideClassIfNoReferences
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="hideClassIfNoReferences" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">hideClassIfNoReferences</FONT></TD></TR>
+ // indexFields
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="indexFields" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">indexFields</FONT></TD></TR>
+ // isVisible
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">boolean</FONT></td><TD PORT="isVisible" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">isVisible</FONT></TD></TR>
+ // registerReference
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="registerReference" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">registerReference</FONT></TD></TR>
+ // setDistinctiveColor
+ <TR><td ALIGN="right"><FONT POINT-SIZE="8.0">void</FONT></td><TD PORT="setDistinctiveColor" ALIGN="left"><FONT COLOR ="red" POINT-SIZE="11.0">setDistinctiveColor</FONT></TD></TR>
+ </TABLE>>, shape="none"];
+
+ // field references to other classes
+ class_eu_svjatoslav_inspector_java_structure_ClassDescriptor:classGraph -> class_eu_svjatoslav_inspector_java_structure_ClassGraph[label="classGraph", color="blueviolet", style="bold"];
+ class_eu_svjatoslav_inspector_java_structure_ClassDescriptor:methods -> class_eu_svjatoslav_inspector_java_structure_MethodDescriptor[label="methods", color="brown4", style="bold"];
+ class_eu_svjatoslav_inspector_java_structure_ClassDescriptor:nameToFieldMap -> class_eu_svjatoslav_inspector_java_structure_FieldDescriptor[label="nameToFieldMap", color="chartreuse4", style="bold"];
+
+ // method references to other classes
+ class_eu_svjatoslav_inspector_java_structure_ClassDescriptor:getAllFields -> class_eu_svjatoslav_inspector_java_structure_FieldDescriptor[label="getAllFields", color="chartreuse4", style="dotted, bold"];
+
+ // interfaces implemented by class: eu.svjatoslav.inspector.java.structure.ClassDescriptor
+ class_eu_svjatoslav_inspector_java_structure_GraphElement -> class_eu_svjatoslav_inspector_java_structure_ClassDescriptor[style="dotted, tapered", color="olivedrab2", penwidth=20, dir="forward"];
+}
--- /dev/null
+#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme
+#+TITLE: JavaInspect - Utility to visualize java software
+#+LANGUAGE: en
+#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry}
+#+LATEX_HEADER: \usepackage{parskip}
+#+LATEX_HEADER: \usepackage[none]{hyphenat}
+
+#+OPTIONS: H:20 num:20
+#+OPTIONS: author:nil
+
+* Introduction
+:PROPERTIES:
+:CUSTOM_ID: overview
+:END:
+
+*JavaInspect* utility simplifies understanding the computer program
+code by automatically visualizing its structure.
+
+[[https://www3.svjatoslav.eu/projects/sixth-3d/graphs/][See example produced graphs]] for [[https://www3.svjatoslav.eu/projects/sixth-3d/][Sixth 3D - 3D engine project]].
+
+JavaInspect can be used as a [[id:acf1896a-74b4-4914-acf6-a77075e07f25][standalone commandline utility]] as well as
+[[id:bbeeffc8-3767-440d-8d93-ec9124dd60ee][java library]]. JavaInspect uses primarily Java built-in reflection to
+discover and visualize any part of Java program.
+
+JavaInspect currently has no graphical user interface, configuration
+files, embedded scripting support, direct Maven, Gradle or Ant
+integration. See [[id:2ad2889e-6c95-4662-b3f4-2c341fc74522][usage]] to learn how to instuct Javainspect what to do.
+
+After discovering application structure and optionally filtering out
+unimportant parts, JavaInspect produces GraphViz dot file that
+describes data to be visualized. Then launches [[https://graphviz.org/][GraphViz]] to generate
+bitmap graph in PNG or SVG format.
+
+Notes:
++ JavaInspect is developed and tested so far only on GNU/Linux.
+
+
+*A very simple example:*
+
+[[file:example.png][file:example-thumbnail.png]]
+
+Graph legend:
+file:legend.png
+
+
++ [[https://www3.svjatoslav.eu/projects/sixth-3d/graphs/][See example produced graphs]] for [[https://www3.svjatoslav.eu/projects/sixth-3d/][Sixth 3D - 3D engine project]].
+
+** See also
+:PROPERTIES:
+:CUSTOM_ID: see-also
+:END:
+
+Similar or alternative solutions:
++ http://www.class-visualizer.net/
++ [[https://github.com/pahen/madge][Madge - similar tool for JavaScript]]
+
+* Installation
+:PROPERTIES:
+:CUSTOM_ID: installation
+:END:
+[[http://www.graphviz.org/][GraphViz]] - shall be installed on the computer.
+
+On Ubuntu/Debian GraphViz can be installed using:
+#+BEGIN_SRC sh
+sudo apt-get install graphviz
+#+END_SRC
+
+To use JavaInspect via Java API, no further installation is
+needed. JavaInspect will be embedded into your project as dependency.
+This is described in [[id:bbeeffc8-3767-440d-8d93-ec9124dd60ee][usage via Java API]]. It will expect GraphViz to be
+available in the system.
+
+To use JavaInspect as a commandline tool, JavaInspect source
+repository has to be cloned locally. See: [[id:c47ff9a6-d737-4b73-9393-1c63d2ca1101][Getting the source code]].
+
+Then study and execute installation script:
+#+begin_src sh
+ cd commandline\ launcher
+ ./install
+#+end_src
+
+After installation, new commandline tool should be available
+: javainspect
+
+Quick commandline usage help can be viewed by issuing
+: javainspect --help
+
+* Usage
+:PROPERTIES:
+:ID: 2ad2889e-6c95-4662-b3f4-2c341fc74522
+:CUSTOM_ID: usage
+:END:
+JavaInspect utility can be used in 2 different ways:
++ [[id:acf1896a-74b4-4914-acf6-a77075e07f25][As standalone commandline utility]]
++ [[id:bbeeffc8-3767-440d-8d93-ec9124dd60ee][As embedded Java library via Java API]]
+
+** Usage via Java API
+:PROPERTIES:
+:ID: bbeeffc8-3767-440d-8d93-ec9124dd60ee
+:CUSTOM_ID: usage-via-java-api
+:END:
+Requires that classes to be visualised are available in the classpath.
+
+To get JavaInspect into same classpath with your projecs I so far came
+up with 2 solutions:
+
+1. Add JavaInspect library in your project as a dependency.
+
+2. Create new Java project for the purpose visualizing your other
+ projects and include JavaInspect and your projecs binary artifacts
+ (Jar's) into new project classpath. Built binary Jar's (with no
+ source code) are sufficient because JavaInspect operates via
+ reflection.
+
+Simple Java based control/configuration code needs to be written for
+each project. I usually put such code into directories devoted for
+JUnit tests. Because it needs not to be compiled/embedded into final
+product or project artifact I'm just willing to visualize.
+
+Control code in general does the following:
+1. Create graph object.
+2. Java reflection/classloaders does not provide mechanism for
+ discovering all classes under given package. Therefore you need to
+ declare at least some classes to be added to the graph by manually
+ adding individual classes to the graph. For every class added to
+ the graph, GraphViz will recursively inspect it and add all
+ referecned classes to the graph as well.
+3. Graphs easilly get very big and complex so optionally we filter
+ important code using classname [[https://en.wikipedia.org/wiki/Glob_(programming)][glob]] patterns based blacklist and/or
+ whitelist.
+4. Optionally we can tune some rendering parameters like:
+ + Possibility to remove orphaned classes (classes with no
+ references) from the graph.
+ + Specify target directory for generated visualization
+ files. (Default is current directory)
+ + Keep intermediate GraphViz dot file for later inspection.
+5. Render graph.
+
+
+*** Example 1: individually picked objects
+:PROPERTIES:
+:CUSTOM_ID: example-1-individually-picked-objects
+:END:
+This example demonstrates generating of class graph from hand picked
+classes and visualizing GraphViz itself.
+
+#+BEGIN_SRC java
+
+// Create graph
+final ClassGraph graph = new ClassGraph();
+
+// Add some random object to the graph. GraphViz will detect Class from
+// the object.
+graph.add(graph);
+
+// Also add some random class to the graph.
+graph.add(Utils.class);
+
+// Keep intermediary GraphViz DOT file for reference.
+graph.setKeepDotFile(true);
+
+// Produce bitmap image titled "JavaInspect.png" to the user Desktop
+// directory
+graph.generateGraph("JavaInspect");
+
+#+END_SRC
+
+Note: if desired, more compact version of the above:
+#+BEGIN_SRC java
+new ClassGraph().add(randomObject, RandomClass.class)
+ .setKeepDotFile(true).generateGraph("JavaInspect");
+#+END_SRC
+
+
+Result:
+ - Generated DOT file: [[file:JavaInspect.dot][JavaInspect.dot]]
+ - Generated PNG image: [[file:JavaInspect.png][JavaInspect.png]]
+
+*** Example 2: GraphViz embedded in another project
+:PROPERTIES:
+:CUSTOM_ID: example-2-graphviz-embedded-in-another-project
+:END:
+1. Download project Sixth [[https://www2.svjatoslav.eu/gitweb/?p=sixth.git;a=snapshot;h=HEAD;sf=tgz][code snapshot]].
+2. Inspect and run *DataGraph.java*.
+
+*** Embedding JavaInspect in your Maven project
+:PROPERTIES:
+:CUSTOM_ID: embedding-javainspect-in-your-maven-project
+:END:
+
+Declare JavaInspect as dependency:
+#+BEGIN_SRC xml
+<dependencies>
+ ...
+ <dependency>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>javainspect</artifactId>
+ <version>1.7</version>
+ </dependency>
+ ...
+</dependencies>
+#+END_SRC
+
+
+Add Maven repository to retrieve artifact from:
+#+BEGIN_SRC xml
+<repositories>
+ ...
+ <repository>
+ <id>svjatoslav.eu</id>
+ <name>Svjatoslav repository</name>
+ <url>https://www3.svjatoslav.eu/maven/</url>
+ </repository>
+ ...
+</repositories>
+#+END_SRC
+
+* Source code
+:PROPERTIES:
+:ID: c47ff9a6-d737-4b73-9393-1c63d2ca1101
+:CUSTOM_ID: getting-the-source-code
+:END:
+
+*This program is free software: released under Creative Commons Zero
+(CC0) license*
+
+Program author:
+- Svjatoslav Agejenko
+- Homepage: https://svjatoslav.eu
+- Email: mailto://svjatoslav@svjatoslav.eu
+- See also: [[https://www.svjatoslav.eu/projects/][Other software projects hosted at svjatoslav.eu]]
+
+*Getting the source code:*
+- [[https://www2.svjatoslav.eu/gitweb/?p=javainspect.git;a=snapshot;h=HEAD;sf=tgz][Download latest source code snapshot in TAR GZ format]]
+- [[https://www2.svjatoslav.eu/gitweb/?p=javainspect.git;a=summary][Browse Git repository online]]
+- You can clone Git repository using git:
+ : git clone https://www3.svjatoslav.eu/git/javainspect.git
--- /dev/null
+:PROPERTIES:
+:ID: acf1896a-74b4-4914-acf6-a77075e07f25
+:END:
+
+#+TITLE: JavaInspect - usage from commandline
+#+LANGUAGE: en
+#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry}
+#+LATEX_HEADER: \usepackage{parskip}
+#+LATEX_HEADER: \usepackage[none]{hyphenat}
+
+#+OPTIONS: H:20 num:20
+#+OPTIONS: author:nil
+
+
+* Available commandline arguments
+:PROPERTIES:
+:CUSTOM_ID: available-commandline-arguments
+:END:
+
+
+- -j (existing files)... :: JAR file(s) to render.
+
+- -c (existing directories)... :: Classpath directories
+
+- -n (string) :: Graph name. (default: "graph")
+
+- --debug :: Show debug info.
+
+- -h, --help :: Show commandline usage help.
+
+- -k :: Keep dot file.
+
+- -ho ::
+ Hide orphaned classes.
+
+- -w (one to many strings)... :: Whitelist glob(s).
+
+- -b (one to many strings)... :: Blacklist glob(s).
+
+- -r (one to many strings)... :: root class(es).
+
+- -d (existing directory) :: Target directory. Default is current directory.
+
+- -t (options: png, svg) :: Target image type. Default is: svg.
+
+* Specifying classes to render
+:PROPERTIES:
+:CUSTOM_ID: specifying-classes-to-render
+:END:
+
+Normal Java application has immense complexity. In addition to code
+that was directly written by particular project developers, lots of
+functionality is typically added as frameworks or libraries to the
+project. In addition there is significant Java standard library.
+
+Because JavaInspect uses reflection, it does not easily distinguish
+between those. In normal situation you would rather want to visualize
+only code that was developed specifically for your project and leave
+frameworks like Spring etc. out. If you visualize all classes that are
+possibly reachable from you project, you will easily get huge and
+incomprehensible graph.
+
+JavaInspect can digest compiled Java classes in 2 modes:
+1. Provide list of Jar files. Use *-j* option.
+2. Provide list of filesystem directories that can be used as
+ classpath root. Use *-c* option.
+
+Currently JavaInspect uses following algorithm to add classes to
+rendered graph:
+
+- All classes that were found in Jar files are added to graph by default.
+- None of the classes that were found in filesystem directories are
+ added to the graph by default (unless explicitly referenced). (TODO:
+ for consistency it would be better to add them too by default)
+- If whitelist is specified (*-w* option) everything that is not
+ matched by whitelist pattern(s) will be removed from the graph.
+- If blacklist is specified (*-b* option) everything that is matched
+ by blacklist pattern(s) will be removed from the graph.
+- Root classes can be specified using *-r* option. Root classes will
+ be added to the graph. JavaInspect will then try to recursively
+ discover all classes that were referenced by root class and add
+ those also to the graph.
+
+* Examples
+:PROPERTIES:
+:CUSTOM_ID: examples
+:END:
+
+Visualize java Jar file. All classes. Hide orphaned classes:
+
+#+begin_src sh
+ javainspect \
+ -j target/sixth-3d-*-SNAPSHOT.jar \
+ -d doc/graphs/ \
+ -n "all classes" \
+ -t png -ho
+#+end_src
+
+
+Visualize java Jar file. All classes. Hide orphaned classes. Apply
+whitelist:
+
+#+begin_src sh
+ javainspect \
+ -j target/sixth-3d-*-SNAPSHOT.jar \
+ -d doc/graphs/ \
+ -n "GUI" \
+ -t png \
+ -w "eu.svjatoslav.sixth.e3d.gui.*" \
+ -ho
+#+end_src
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>javainspect</artifactId>
+ <version>1.8-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <name>JavaInspect</name>
+ <description>Utility to visualize Java code</description>
+
+ <properties>
+ <java.version>1.8</java.version>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+
+ <organization>
+ <name>svjatoslav.eu</name>
+ <url>http://svjatoslav.eu</url>
+ </organization>
+
+ <build>
+ <finalName>javainspect</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <optimize>true</optimize>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.2.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.10.4</version>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <!-- workaround for https://bugs.openjdk.java.net/browse/JDK-8212233 -->
+ <javaApiLinks>
+ <property>
+ <name>foo</name>
+ <value>bar</value>
+ </property>
+ </javaApiLinks>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.5.2</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.scm</groupId>
+ <artifactId>maven-scm-provider-gitexe</artifactId>
+ <version>1.9.4</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>2.4.3</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>eu.svjatoslav.inspector.java.commandline.Main</mainClass>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ <extensions>
+ <extension>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-ssh-external</artifactId>
+ <version>2.6</version>
+ </extension>
+ </extensions>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>svjatoslavcommons</artifactId>
+ <version>1.9</version>
+ </dependency>
+ <dependency>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>cli-helper</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+
+ <distributionManagement>
+ <snapshotRepository>
+ <id>svjatoslav.eu</id>
+ <name>svjatoslav.eu</name>
+ <url>scpexe://svjatoslav.eu:10006/srv/maven</url>
+ </snapshotRepository>
+ <repository>
+ <id>svjatoslav.eu</id>
+ <name>svjatoslav.eu</name>
+ <url>scpexe://svjatoslav.eu:10006/srv/maven</url>
+ </repository>
+ </distributionManagement>
+
+ <repositories>
+ <repository>
+ <id>svjatoslav.eu</id>
+ <name>Svjatoslav repository</name>
+ <url>https://www3.svjatoslav.eu/maven/</url>
+ </repository>
+ </repositories>
+
+
+ <scm>
+ <connection>scm:git:ssh://n0@svjatoslav.eu:10006/home/n0/git/javainspect.git</connection>
+ <developerConnection>scm:git:ssh://n0@svjatoslav.eu:10006/home/n0/git/javainspect.git</developerConnection>
+ <tag>HEAD</tag>
+ </scm>
+
+</project>
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java;
+
+import eu.svjatoslav.inspector.java.structure.ClassGraph;
+import eu.svjatoslav.inspector.java.structure.Utils;
+
+/**
+ * Demo application that generates a self-visualization of JavaInspect.
+ *
+ * <p>This class demonstrates how to use JavaInspect to analyze and visualize
+ * its own codebase, producing a class diagram showing the internal structure
+ * of the tool.</p>
+ *
+ * @see ClassGraph
+ */
+public class RenderJavaInspect {
+
+ /**
+ * Demonstrates generating a class graph from hand-picked classes.
+ *
+ * <p>This method creates a graph visualization of JavaInspect itself,
+ * including the {@link ClassGraph} and {@link Utils} classes. The output
+ * files are generated in the project root directory:</p>
+ *
+ * <ul>
+ * <li>JavaInspect.svg - Scalable vector graphics visualization</li>
+ * <li>JavaInspect.dot - GraphViz DOT source file (kept for reference)</li>
+ * </ul>
+ */
+ private static void handpickClassesExample() {
+ /*
+ * This example demonstrates generating of class graph from hand picked
+ * classes and visualizing GraphViz itself.
+ */
+
+ // Create graph
+ final ClassGraph graph = new ClassGraph();
+
+ // Add some random object to the graph. GraphViz will detect Class from
+ // the object.
+ graph.add(graph);
+
+ // Also add some random class to the graph.
+ graph.add(Utils.class);
+
+ // Keep intermediary GraphViz DOT file for reference.
+ graph.setKeepDotFile(true);
+
+ // Produce SVG image titled "JavaInspect.svg" to the user Desktop
+ // directory
+ graph.generateGraph("JavaInspect");
+ }
+
+ /**
+ * Entry point for the demo application.
+ *
+ * @param args command-line arguments (ignored)
+ */
+ public static void main(final String[] args) {
+
+ handpickClassesExample();
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.commandline;
+
+
+import eu.svjatoslav.commons.cli_helper.parameter_parser.Parser;
+import eu.svjatoslav.commons.cli_helper.parameter_parser.parameter.*;
+
+/**
+ * Configuration holder for JavaInspect command-line arguments.
+ *
+ * <p>This class parses and stores all command-line options for the
+ * JavaInspect tool, providing easy access to JAR files, graph settings,
+ * and filter patterns.</p>
+ *
+ * <p>Supported options:</p>
+ * <ul>
+ * <li>-j, --jar: JAR files to analyze (required unless -r is specified)</li>
+ * <li>-c, --classpath: Additional classpath directories</li>
+ * <li>-n, --name: Graph name for output files</li>
+ * <li>-t, --type: Output image type (svg or png)</li>
+ * <li>-d, --directory: Target output directory</li>
+ * <li>-k, --keep-dot: Keep intermediate DOT file</li>
+ * <li>-w, --whitelist: Whitelist glob patterns</li>
+ * <li>-b, --blacklist: Blacklist glob patterns</li>
+ * <li>-r, --root: Root classes to analyze (instead of all JAR classes)</li>
+ * <li>-ho, --hide-orphaned: Hide classes with no connections</li>
+ * <li>--debug: Show debug output</li>
+ * <li>-h, --help: Show usage help</li>
+ * </ul>
+ *
+ * @see Main
+ * @see Parser
+ */
+public class CommandlineConfiguration {
+
+ /**
+ * JAR files containing classes to analyze.
+ */
+ public FileParameters jarFiles;
+
+ /**
+ * Name for the generated graph files.
+ */
+ public StringParameter graphName;
+
+ /**
+ * Flag to enable debug output during processing.
+ */
+ private NullParameter showDebug;
+
+ /**
+ * Glob patterns for classes to include in the graph.
+ */
+ public StringParameters whitelistGlob;
+
+ /**
+ * Glob patterns for classes to exclude from the graph.
+ */
+ public StringParameters blacklistGlob;
+
+ /**
+ * Specific root classes to analyze (instead of all JAR classes).
+ */
+ public StringParameters rootClasses;
+
+ /**
+ * Flag to show command-line usage help.
+ */
+ public NullParameter showHelp;
+
+ /**
+ * Output image type (SVG or PNG).
+ */
+ public TargetImageTypeParameter targetImageType;
+
+ /**
+ * Flag to keep the intermediate DOT file.
+ */
+ public NullParameter keepDotFile;
+
+ /**
+ * Directory for output files.
+ */
+ public DirectoryParameter targetDirectory;
+
+ /**
+ * Additional classpath directories for resolving dependencies.
+ */
+ public DirectoryParameters classPaths;
+
+ /**
+ * Whether the command-line arguments were parsed successfully.
+ */
+ public final boolean configurationOk;
+
+ /**
+ * Flag to hide orphaned classes (classes with no connections).
+ */
+ public NullParameter hideOrphanedClasses;
+
+ /**
+ * The argument parser instance.
+ */
+ Parser parser;
+
+ /**
+ * Default graph name if not specified on command line.
+ */
+ public static final String DEFAULT_GRAPH_NAME = "graph";
+
+ /**
+ * Parses command-line arguments and initializes all configuration options.
+ *
+ * @param args the command-line arguments to parse
+ */
+ public CommandlineConfiguration(String[] args) {
+ parser = buildCommandlineParameterParser();
+ configurationOk = parser.parse(args);
+ }
+
+ /**
+ * Checks if debug mode is enabled.
+ *
+ * @return true if --debug was specified
+ */
+ public boolean isDebug() {
+ return showDebug.isSpecified();
+ }
+
+ /**
+ * Builds the command-line argument parser with all supported options.
+ *
+ * <p>This method creates a Parser and registers all parameters with
+ * their descriptions, default values, and aliases.</p>
+ *
+ * @return the configured Parser instance
+ */
+ public Parser buildCommandlineParameterParser() {
+ Parser parser = new Parser();
+
+ jarFiles = parser.add(
+ new FileParameters("JAR file(s) to render."))
+ .mustExist()
+ .addAliases("-j");
+
+ classPaths = parser.add(
+ new DirectoryParameters("Classpath directories"))
+ .mustExist()
+ .addAliases("-c");
+
+ graphName = parser.add(
+ new StringParameter("Graph name. (default: \"" + DEFAULT_GRAPH_NAME + "\")", DEFAULT_GRAPH_NAME))
+ .addAliases("-n");
+
+ showDebug = parser.add(
+ new NullParameter("Show debug info."))
+ .addAliases("--debug");
+
+ showHelp = parser.add(new NullParameter("Show commandline usage help."))
+ .addAliases("-h", "--help");
+
+ keepDotFile = parser.add(
+ new NullParameter("Keep dot file."))
+ .addAliases("-k");
+
+ hideOrphanedClasses = parser.add(
+ new NullParameter("Hide orphaned classes."))
+ .addAliases("-ho");
+
+ whitelistGlob = parser.add(
+ new StringParameters("Whitelist glob(s)."))
+ .addAliases("-w");
+
+ blacklistGlob = parser.add(
+ new StringParameters("Blacklist glob(s)."))
+ .addAliases("-b");
+
+ rootClasses = parser.add(
+ new StringParameters("root class(es)."))
+ .addAliases("-r");
+
+
+ targetDirectory = parser.add(new DirectoryParameter("Target directory. " +
+ "Default is current directory.").mustExist())
+ .addAliases("-d");
+
+ targetImageType = parser.add(new TargetImageTypeParameter()).addAliases("-t");
+
+ return parser;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.commandline;
+
+import eu.svjatoslav.inspector.java.structure.ClassGraph;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+/**
+ * Command-line interface entry point for JavaInspect.
+ *
+ * <p>This class provides the main method for running JavaInspect from the
+ * command line, parsing arguments and generating class visualizations.</p>
+ *
+ * <p>Usage:</p>
+ * <pre>
+ * java -jar javainspect.jar -j myapp.jar -n MyAppGraph
+ * </pre>
+ *
+ * <p>Common options:</p>
+ * <ul>
+ * <li>-j: JAR files to analyze</li>
+ * <li>-n: Graph name for output files</li>
+ * <li>-t: Output format (svg or png)</li>
+ * <li>-d: Target directory for output</li>
+ * <li>-w/-b: Whitelist/blacklist glob patterns</li>
+ * </ul>
+ *
+ * @see CommandlineConfiguration
+ * @see ClassGraph
+ */
+public class Main {
+
+ /**
+ * Main entry point for the command-line interface.
+ *
+ * <p>Parses command-line arguments, builds the class graph from JAR files,
+ * and generates the visualization.</p>
+ *
+ * @param args command-line arguments
+ * @throws IOException if JAR files cannot be read
+ * @throws ClassNotFoundException if classes cannot be loaded
+ */
+ public static void main(String[] args) throws IOException, ClassNotFoundException {
+ CommandlineConfiguration configuration = new CommandlineConfiguration(args);
+ if (!configuration.configurationOk) {
+ configuration.parser.showHelp();
+ System.exit(1);
+ }
+
+ if (configuration.showHelp.isSpecified()) configuration.parser.showHelp();
+
+ getClassGraph(configuration).generateGraph(configuration.graphName.getValue());
+
+ if (configuration.isDebug())
+ System.out.println("Graph ready.");
+ }
+
+ /**
+ * Builds and configures the ClassGraph from command-line options.
+ *
+ * <p>This method:</p>
+ * <ol>
+ * <li>Creates a URLClassLoader from the JAR files and classpath</li>
+ * <li>Creates a ClassGraph and configures output options</li>
+ * <li>Loads either specified root classes or all classes from JARs</li>
+ * <li>Applies whitelist/blacklist filters</li>
+ * <li>Hides orphaned classes if requested</li>
+ * </ol>
+ *
+ * @param configuration the parsed command-line configuration
+ * @return the configured ClassGraph ready for generation
+ * @throws IOException if JAR files cannot be read
+ * @throws ClassNotFoundException if classes cannot be loaded
+ */
+ private static ClassGraph getClassGraph(CommandlineConfiguration configuration) throws IOException, ClassNotFoundException {
+ List<File> jarFiles = configuration.jarFiles.getValue();
+
+ URLClassLoader classLoader = new URLClassLoader(
+ getClasspathUrls(jarFiles, configuration.classPaths.getValue()),
+ configuration.getClass().getClassLoader());
+
+ ClassGraph classGraph = new ClassGraph();
+
+ if (configuration.targetDirectory.isSpecified())
+ classGraph.setTargetDirectory(configuration.targetDirectory.getValue());
+
+ if (configuration.targetImageType.isSpecified())
+ classGraph.setTargetImageType(configuration.targetImageType.getValue());
+
+ classGraph.setKeepDotFile(configuration.keepDotFile.getValue());
+
+ if (configuration.rootClasses.isSpecified()) {
+ // add only selected root classes
+ for (String rootClass : configuration.rootClasses.getValue())
+ attemptClassAdditionByName(classLoader, classGraph, configuration, rootClass);
+
+ } else {
+ // add all classes in the jar files to graph
+ for (File jarFile : jarFiles)
+ addJarToGraph(jarFile, classLoader, classGraph, configuration);
+ }
+ configuration.blacklistGlob.getValue().forEach(classGraph::blacklistClassGlob);
+ configuration.whitelistGlob.getValue().forEach(classGraph::whitelistClassGlob);
+
+ if (configuration.hideOrphanedClasses.getValue())
+ classGraph.hideOrphanedClasses();
+
+ return classGraph;
+ }
+
+ /**
+ * Builds an array of URLs for the classpath from JAR files and directories.
+ *
+ * @param jarFiles list of JAR files to include
+ * @param classpathDirectories list of classpath directories to include
+ * @return array of URLs for the class loader
+ */
+ private static URL[] getClasspathUrls(List<File> jarFiles, List<File> classpathDirectories) {
+ List<URL> urls = new ArrayList<>();
+
+ classpathDirectories.forEach(classpathDirectory -> {
+ try {
+ urls.add(classpathDirectory.toURI().toURL());
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ jarFiles.forEach((File file) -> {
+ try {
+ urls.add(file.toURI().toURL());
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ return urls.toArray(new URL[urls.size()]);
+ }
+
+ /**
+ * Adds all classes from a JAR file to the class graph.
+ *
+ * <p>Iterates through all .class entries in the JAR and attempts to
+ * load each one into the graph.</p>
+ *
+ * @param jarFile the JAR file to process
+ * @param classLoader the URLClassLoader for loading classes
+ * @param classGraph the graph to add classes to
+ * @param configuration the command-line configuration for debug output
+ * @throws IOException if the JAR cannot be read
+ * @throws ClassNotFoundException if a class cannot be found
+ */
+ private static void addJarToGraph(
+ File jarFile, URLClassLoader classLoader, ClassGraph classGraph, CommandlineConfiguration configuration)
+ throws IOException, ClassNotFoundException {
+
+ for (String className : getClassNamesFromJar(jarFile))
+ attemptClassAdditionByName(classLoader, classGraph, configuration, className);
+ }
+
+ /**
+ * Attempts to load and add a class to the graph by its name.
+ *
+ * <p>This method gracefully handles NoClassDefFoundError, which can occur
+ * when a class references other classes not present in the JAR.</p>
+ *
+ * @param classLoader the URLClassLoader for loading classes
+ * @param classGraph the graph to add the class to
+ * @param configuration the command-line configuration for debug output
+ * @param className the fully qualified class name
+ * @throws ClassNotFoundException if the class cannot be found
+ */
+ private static void attemptClassAdditionByName(URLClassLoader classLoader, ClassGraph classGraph, CommandlineConfiguration configuration, String className) throws ClassNotFoundException {
+ if (configuration.isDebug())
+ System.out.println("Adding class to graph: " + className);
+ try {
+ classGraph.add(loadClassByName(classLoader, className));
+ } catch (NoClassDefFoundError e) {
+ if (configuration.isDebug())
+ System.out.println("Class definition was not found.");
+ // Sometimes referenced classes are not found in the same Jar.
+ // Let's ignore this and proceed with the classes that we have.
+ }
+ }
+
+ /**
+ * Loads a class by its fully qualified name using the class loader.
+ *
+ * @param classLoader the URLClassLoader for loading classes
+ * @param className the fully qualified class name
+ * @return the loaded Class object
+ * @throws ClassNotFoundException if the class cannot be found
+ */
+ private static Class loadClassByName(URLClassLoader classLoader, String className) throws ClassNotFoundException {
+ return Class.forName(className, true, classLoader);
+ }
+
+ /**
+ * Extracts all class names from a JAR file.
+ *
+ * <p>Scans the JAR for .class entries and converts their paths to
+ * fully qualified class names.</p>
+ *
+ * @param jarFile the JAR file to scan
+ * @return list of fully qualified class names
+ * @throws IOException if the JAR cannot be read
+ */
+ public static List<String> getClassNamesFromJar(File jarFile) throws IOException {
+ List<String> result = new ArrayList<>();
+ try (
+ JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarFile))
+ ) {
+ while (true) {
+ JarEntry jarEntry = jarInputStream.getNextJarEntry();
+ if (jarEntry == null)
+ break;
+
+ if (isClassFile(jarEntry))
+ result.add(getClassNameFromFileName(jarEntry));
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Checks if a JAR entry is a Java class file.
+ *
+ * @param jarEntry the JAR entry to check
+ * @return true if the entry name ends with .class
+ */
+ private static boolean isClassFile(JarEntry jarEntry) {
+ return jarEntry.getName().endsWith(".class");
+ }
+
+ /**
+ * Converts a JAR entry path to a fully qualified class name.
+ *
+ * <p>Converts path separators (/) to package separators (.) and
+ * removes the .class extension.</p>
+ *
+ * @param jarEntry the JAR entry containing the class file path
+ * @return the fully qualified class name
+ */
+ private static String getClassNameFromFileName(JarEntry jarEntry) {
+ String result = jarEntry.getName().replaceAll("/", "\\.");
+ return result.substring(0, result.lastIndexOf('.'));
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.commandline;
+
+import eu.svjatoslav.commons.cli_helper.parameter_parser.Parameter;
+import eu.svjatoslav.commons.string.String2;
+import eu.svjatoslav.inspector.java.structure.TargetImageType;
+
+import java.util.Arrays;
+
+import static eu.svjatoslav.commons.cli_helper.parameter_parser.ArgumentCount.SINGLE;
+
+/**
+ * Command-line parameter type for specifying output image format.
+ *
+ * <p>This parameter accepts "svg" or "png" values and converts them
+ * to the corresponding {@link TargetImageType} enum values.</p>
+ *
+ * @see TargetImageType
+ * @see CommandlineConfiguration
+ */
+public class TargetImageTypeParameter extends Parameter<TargetImageType, TargetImageTypeParameter> {
+
+ /**
+ * Creates a target image type parameter with default SVG format.
+ *
+ * <p>The parameter accepts a single argument value: either "svg" or "png".</p>
+ */
+ public TargetImageTypeParameter(){
+ super("Target image type. Default is: svg.", SINGLE);
+ }
+
+ /**
+ * Describes the valid format options for this parameter.
+ *
+ * <p>Returns a comma-separated list of available image types.</p>
+ *
+ * @return description string like "options: svg, png"
+ */
+ @Override
+ public String describeFormat() {
+ String2 result = new String2("");
+
+ Arrays.stream(TargetImageType.values())
+ .forEach(targetImageType -> result.appendWithSeparator(", ", targetImageType.fileExtension));
+
+ result.prepend("options: ");
+
+ return result.toString();
+ }
+
+ /**
+ * Returns the parsed TargetImageType value from the command-line argument.
+ *
+ * <p>Converts the argument to uppercase and looks up the corresponding
+ * enum constant.</p>
+ *
+ * @return the TargetImageType matching the argument value
+ */
+ @Override
+ public TargetImageType getValue() {
+ return TargetImageType.valueOf(arguments.get(0).toUpperCase());
+ }
+
+ /**
+ * Validates that a value is a valid image type name.
+ *
+ * @param value the value to validate
+ * @return true if the value is "svg" or "png", false otherwise
+ */
+ @Override
+ public boolean validate(String value) {
+ try {
+ TargetImageType.valueOf(value.toUpperCase());
+ } catch (final IllegalArgumentException exception) {
+ return false;
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Provides the command-line interface for JavaInspect.
+ *
+ * <p>This package contains classes for parsing command-line arguments
+ * and launching the class graph generation process.</p>
+ *
+ * @see eu.svjatoslav.inspector.java.commandline.Main
+ * @see eu.svjatoslav.inspector.java.commandline.CommandlineConfiguration
+ */
+package eu.svjatoslav.inspector.java.commandline;
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.structure;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * Represents a single Java class for visualization in the class graph.
+ *
+ * <p>Class descriptors are the primary nodes in the visualization, capturing
+ * class metadata, fields, methods, inheritance relationships, and interface
+ * implementations. Each descriptor generates GraphViz DOT format output
+ * for rendering as an HTML-like table node with connection edges.</p>
+ *
+ * <p>Classes are analyzed using Java reflection to extract:</p>
+ * <ul>
+ * <li>Superclass relationships</li>
+ * <li>Implemented interfaces</li>
+ * <li>Declared fields and their types</li>
+ * <li>Methods and their return types</li>
+ * <li>Generic type information</li>
+ * </ul>
+ *
+ * @see ClassGraph
+ * @see FieldDescriptor
+ * @see MethodDescriptor
+ * @see GraphElement
+ */
+public class ClassDescriptor implements GraphElement, Comparable<ClassDescriptor> {
+
+ /**
+ * Maximum number of incoming references to show for a class.
+ *
+ * <p>Classes with more than this number of references are drawn with
+ * thicker borders to indicate high coupling.</p>
+ */
+ private static final int MAX_REFERECNES_COUNT = 10;
+
+ /**
+ * List of interfaces implemented by this class.
+ */
+ final List<ClassDescriptor> interfaces = new ArrayList<>();
+
+ /**
+ * Map of field names to their descriptors.
+ */
+ private final Map<String, FieldDescriptor> nameToFieldMap = new TreeMap<>();
+
+ /**
+ * Sorted set of method descriptors.
+ */
+ private final SortedSet<MethodDescriptor> methods = new TreeSet<>();
+
+ /**
+ * The class graph that owns this descriptor.
+ */
+ private final ClassGraph classGraph;
+
+ /**
+ * Whether this class is an enum.
+ */
+ boolean isEnum;
+
+ /**
+ * Whether this class is an interface.
+ */
+ boolean isInterface;
+
+ /**
+ * Whether this class represents an array type.
+ */
+ boolean isArray;
+
+ /**
+ * The superclass descriptor, or null for Object or primitives.
+ */
+ ClassDescriptor superClass;
+
+ /**
+ * The fully qualified class name (e.g., "java.lang.String").
+ */
+ private String fullyQualifiedName;
+
+ /**
+ * Color for incoming reference arrows to this class.
+ *
+ * <p>Assigned from the dark color palette to distinguish this class's
+ * incoming edges from others.</p>
+ */
+ private String distinctiveReferenceColor;
+
+ /**
+ * Color for interface implementation arrows.
+ */
+ private String interfaceColor;
+
+ /**
+ * Color for superclass inheritance arrows.
+ */
+ private String superClassColor;
+
+ /**
+ * Whether this class should be rendered in the graph.
+ *
+ * <p>Classes can be hidden if they are system types, filtered by glob
+ * patterns, or have no interesting references.</p>
+ */
+ private boolean isShown = true;
+
+ /**
+ * Count of field and method references pointing to this class.
+ *
+ * <p>Used to determine if the class should show its reference arrows.</p>
+ */
+ private int referencesCount = 0;
+
+ /**
+ * Count of implementations for interface classes.
+ *
+ * <p>Only relevant for interfaces; counts how many classes implement this interface.</p>
+ */
+ private int implementationsCount = 0;
+
+ /**
+ * Count of extensions for this class.
+ *
+ * <p>Counts how many classes extend this class as their superclass.</p>
+ */
+ private int extensionsCount = 0;
+
+ /**
+ * The component type for array classes.
+ *
+ * <p>For example, for String[] this would be the String descriptor.</p>
+ */
+ private ClassDescriptor arrayComponent;
+
+ /**
+ * Creates a class descriptor associated with a class graph.
+ *
+ * @param classGraph the graph that will contain this descriptor
+ */
+ public ClassDescriptor(final ClassGraph classGraph) {
+ this.classGraph = classGraph;
+ }
+
+ /**
+ * Analyzes a Java class using reflection to populate this descriptor.
+ *
+ * <p>This method extracts the class name, type information (enum, interface, array),
+ * declared fields, methods, superclass, and implemented interfaces.</p>
+ *
+ * @param clazz the Java Class to analyze
+ */
+ protected void analyzeClass(final Class<?> clazz) {
+
+ fullyQualifiedName = clazz.getName();
+
+ isArray = clazz.isArray();
+
+ if (isArray) {
+ final Class<?> componentType = clazz.getComponentType();
+ arrayComponent = getClassGraph().getOrCreateClassDescriptor(
+ componentType);
+ }
+
+ // System.out.println("class: " + fullyQualifiedName);
+
+ isEnum = clazz.isEnum();
+
+ isInterface = clazz.isInterface();
+
+ if (!isVisible())
+ return;
+
+ try {
+ indexFields(clazz.getDeclaredFields());
+ indexFields(clazz.getFields());
+ } catch (NoClassDefFoundError error) {
+ // TODO: better logging of this error
+ System.out.println(error.toString());
+ }
+
+ indexMethods(clazz);
+
+ for (final Class interfaceClass : clazz.getInterfaces()) {
+ final ClassDescriptor interfaceClassDescriptor = getClassGraph()
+ .getOrCreateClassDescriptor(interfaceClass);
+ interfaceClassDescriptor.registerImplementation();
+ interfaces.add(interfaceClassDescriptor);
+ }
+
+ superClass = getClassGraph().getOrCreateClassDescriptor(
+ clazz.getSuperclass());
+ if (superClass != null)
+ superClass.registerExtension();
+
+ }
+
+ /**
+ * Determines whether incoming reference arrows should be shown for this class.
+ *
+ * <p>Classes with too many references are visually cluttered if all arrows
+ * are shown, so this method returns false above the threshold.</p>
+ *
+ * @return true if reference count is within the display threshold
+ */
+ protected boolean areReferencesShown() {
+ return referencesCount <= MAX_REFERECNES_COUNT;
+ }
+
+ /**
+ * Appends DOT format edges for field references to other classes.
+ *
+ * @param result the StringBuffer to append to
+ */
+ private void enlistFieldReferences(final StringBuffer result) {
+ if (nameToFieldMap.isEmpty())
+ return;
+
+ result.append("\n");
+ result.append(" // field references to other classes\n");
+ nameToFieldMap.forEach((fieldName, field) -> result.append(field.getDot()));
+ }
+
+ /**
+ * Appends DOT format HTML rows for fields within the class node.
+ *
+ * @param result the StringBuffer to append to
+ */
+ private void enlistFields(final StringBuffer result) {
+ if (nameToFieldMap.isEmpty())
+ return;
+
+ result.append("\n");
+ result.append(" // fields:\n");
+
+ // enlist fields
+ for (final Map.Entry<String, FieldDescriptor> entry : nameToFieldMap
+ .entrySet())
+ result.append(entry.getValue().getEmbeddedDot());
+ }
+
+ /**
+ * Appends DOT format edges for implemented interface relationships.
+ *
+ * <p>Interface relationships are shown as dotted forward arrows.</p>
+ *
+ * @param result the StringBuffer to append to
+ */
+ private void enlistImplementedInterfaces(final StringBuffer result) {
+ if (interfaces.isEmpty())
+ return;
+
+ result.append("\n");
+ result.append(" // interfaces implemented by class: "
+ + fullyQualifiedName + "\n");
+
+ for (final ClassDescriptor interfaceDescriptor : interfaces) {
+ if (!interfaceDescriptor.isVisible())
+ continue;
+
+ if (!interfaceDescriptor.areReferencesShown())
+ continue;
+
+ result.append(" " + interfaceDescriptor.getGraphId() + " -> "
+ + getGraphId() + "[style=\"dotted\", color=\""
+ + interfaceDescriptor.getInterfaceColor()
+ + "\", penwidth=10, dir=\"forward\"];\n");
+ }
+ }
+
+ /**
+ * Appends DOT format edges for method return type references.
+ *
+ * @param result the StringBuffer to append to
+ */
+ private void enlistMethodReferences(final StringBuffer result) {
+ if (methods.isEmpty())
+ return;
+
+ result.append("\n");
+ result.append(" // method references to other classes\n");
+ for (final MethodDescriptor methodDescriptor : methods)
+ result.append(methodDescriptor.getDot());
+ }
+
+ /**
+ * Appends DOT format HTML rows for methods within the class node.
+ *
+ * @param result the StringBuffer to append to
+ */
+ private void enlistMethods(final StringBuffer result) {
+ if (methods.isEmpty())
+ return;
+
+ result.append("\n");
+ result.append(" // methods:\n");
+
+ // enlist methods
+ for (final MethodDescriptor methodDescriptor : methods)
+ result.append(methodDescriptor.getEmbeddedDot());
+ }
+
+ /**
+ * Appends DOT format edge for the superclass relationship.
+ *
+ * <p>Superclass arrows point from parent to child with thick styling.</p>
+ *
+ * @param result the StringBuffer to append to
+ */
+ private void enlistSuperClass(final StringBuffer result) {
+ if (superClass == null)
+ return;
+
+ if (!superClass.isVisible())
+ return;
+
+ if (!superClass.areReferencesShown())
+ return;
+
+ result.append("\n");
+ result.append(" // super class for: " + fullyQualifiedName + "\n");
+
+ result.append(" " + superClass.getGraphId() + " -> " + getGraphId()
+ + "[ color=\"" + superClass.getSuperClassColor()
+ + "\", penwidth=10, dir=\"forward\"];\n");
+ }
+
+ /**
+ * Generates the DOT format header for the class node.
+ *
+ * <p>This creates the HTML table structure with package name, parent class
+ * name (for nested classes), and the class name itself.</p>
+ *
+ * @param result the StringBuffer to append to
+ */
+ private void generateDotHeader(final StringBuffer result) {
+ result.append("\n");
+ result.append("// Class: " + fullyQualifiedName + "\n");
+
+ result.append(" " + getGraphId() + "[label=<<TABLE "
+ + getBackgroundColor() + " BORDER=\"" + getBorderWidth()
+ + "\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+
+ result.append("\n");
+ result.append(" // class descriptor header\n");
+ result.append(" <TR><TD colspan=\"2\" PORT=\"f0\">"
+ + "<FONT POINT-SIZE=\"8.0\" >" + getPackageName()
+ + "</FONT><br/>");
+
+ final String parentClassesName = getParentClassesName();
+ if (parentClassesName.length() > 0)
+ result.append("<FONT POINT-SIZE=\"12.0\"><B>" + parentClassesName
+ + "</B></FONT><br/>\n");
+
+ result.append("<FONT POINT-SIZE=\"25.0\"><B>" + getClassName(false)
+ + "</B></FONT>" + "</TD></TR>\n");
+ }
+
+ /**
+ * Returns the HTML background color attribute for this class.
+ *
+ * <p>Enums have navajowhite2, interfaces have darkslategray1,
+ * regular classes have no background color.</p>
+ *
+ * @return the bgcolor attribute string, or empty string for regular classes
+ */
+ private String getBackgroundColor() {
+ String bgColor = "";
+
+ if (isEnum)
+ bgColor = "bgcolor=\"navajowhite2\"";
+
+ if (isInterface)
+ bgColor = "bgcolor=\"darkslategray1\"";
+
+ return bgColor;
+ }
+
+ /**
+ * Returns the border width for this class node.
+ *
+ * <p>Classes with many references have thicker borders (4) to indicate
+ * high coupling, normal classes have thin borders (1).</p>
+ *
+ * @return "4" for highly referenced classes, "1" otherwise
+ */
+ private String getBorderWidth() {
+
+ if (!areReferencesShown())
+ return "4";
+ return "1";
+ }
+
+ /**
+ * Returns the owning class graph.
+ *
+ * @return the ClassGraph instance that created this descriptor
+ */
+ protected ClassGraph getClassGraph() {
+ return classGraph;
+ }
+
+ /**
+ * Returns the simple class name, optionally with array indicator.
+ *
+ * <p>For nested classes, dollar signs are replaced with dots.
+ * For array classes, the component type name is returned instead.</p>
+ *
+ * @param differentiateArray if true, append "[]" for array types
+ * @return the simple class name
+ */
+ protected String getClassName(final boolean differentiateArray) {
+ // this is needed for nested classes
+ final String actualClassName = fullyQualifiedName.replace('$', '.');
+
+ String result;
+ if (isArray) {
+ // for arrays use array component instead of array class name
+ result = arrayComponent.fullyQualifiedName;
+ if (result.contains(".")) {
+ final int i = result.lastIndexOf('.');
+ result = result.substring(i + 1);
+ }
+ } else {
+ final int i = actualClassName.lastIndexOf('.');
+ result = actualClassName.substring(i + 1);
+ }
+
+ if (differentiateArray)
+ if (isArray)
+ result += " []";
+
+ // this is needed for nested classes
+ // result = result.replace('$', '.');
+ return result;
+ }
+
+ /**
+ * Returns the distinctive color for incoming reference arrows.
+ *
+ * <p>Colors are assigned from the dark palette on first access.</p>
+ *
+ * @return the GraphViz color name for this class's reference edges
+ */
+ protected String getColor() {
+ if (distinctiveReferenceColor == null)
+ distinctiveReferenceColor = Utils.getNextDarkColor();
+
+ return distinctiveReferenceColor;
+ }
+
+ /**
+ * Generates the complete DOT format representation for this class.
+ *
+ * <p>This creates the class node (HTML table) and all outgoing edges
+ * for fields, methods, interfaces, and superclass.</p>
+ *
+ * @return DOT format string for the class, or empty string if not visible
+ */
+ @Override
+ public String getDot() {
+ if (!isVisible())
+ return "";
+
+ if (isArray)
+ return "";
+
+ final StringBuffer result = new StringBuffer();
+
+ generateDotHeader(result);
+
+ enlistFields(result);
+
+ enlistMethods(result);
+
+ result.append(" </TABLE>>, shape=\"none\"];\n");
+
+ enlistFieldReferences(result);
+
+ enlistMethodReferences(result);
+
+ enlistImplementedInterfaces(result);
+
+ enlistSuperClass(result);
+
+ return result.toString();
+ }
+
+ /**
+ * Returns null as classes are not embedded within other nodes.
+ *
+ * <p>This method is not applicable for class-level descriptors.</p>
+ *
+ * @return null
+ */
+ @Override
+ public String getEmbeddedDot() {
+ return null;
+ }
+
+ /**
+ * Returns a field matching the given name, ignoring case.
+ *
+ * <p>Used to check if getter/setter methods correspond to existing fields.</p>
+ *
+ * @param fieldToSearch the field name to search for (case insensitive)
+ * @return the matching FieldDescriptor, or null if not found
+ */
+ protected FieldDescriptor getFieldIgnoreCase(final String fieldToSearch) {
+
+ for (final String fieldName : nameToFieldMap.keySet())
+ if (fieldToSearch.equalsIgnoreCase(fieldName))
+ return nameToFieldMap.get(fieldName);
+
+ return null;
+ }
+
+ /**
+ * Returns the fully qualified class name.
+ *
+ * @return the fully qualified name (e.g., "java.lang.String")
+ */
+ protected String getFullyQualifiedName() {
+ return fullyQualifiedName;
+ }
+
+ /**
+ * Returns the GraphViz identifier for this class.
+ *
+ * <p>The ID is derived from the fully qualified name, with dots and
+ * special characters replaced with underscores.</p>
+ *
+ * @return unique identifier in format "class_fully_qualified_name"
+ */
+ @Override
+ public String getGraphId() {
+
+ return "class_"
+ + fullyQualifiedName
+ .replace('.', '_')
+ .replace(";", "")
+ .replace("[[", "")
+ .replace("[L", "")
+ .replace("[[L", "") // array of arrays
+ .replace("[[[L", "") // array of arrays of arrays
+ .replace('$', '_');
+ }
+
+ /**
+ * Returns the color for interface implementation arrows.
+ *
+ * @return the GraphViz color name from the dark palette
+ */
+ private String getInterfaceColor() {
+ if (interfaceColor == null)
+ interfaceColor = Utils.getNextDarkColor();
+
+ return interfaceColor;
+ }
+
+ /**
+ * Creates or retrieves a field descriptor for a reflection Field.
+ *
+ * <p>If a descriptor for the field already exists, it is returned.
+ * Otherwise, a new descriptor is created, added to the map, and analyzed.</p>
+ *
+ * @param field the Java reflection Field
+ * @return the FieldDescriptor for the field
+ */
+ private FieldDescriptor getOrCreateFieldDescriptor(final Field field) {
+
+ final String fieldName = field.getName();
+
+ if (nameToFieldMap.containsKey(fieldName))
+ return nameToFieldMap.get(fieldName);
+ final FieldDescriptor newFieldDescriptor = new FieldDescriptor(this);
+ nameToFieldMap.put(fieldName, newFieldDescriptor);
+
+ newFieldDescriptor.analyzeField(field);
+
+ return newFieldDescriptor;
+ }
+
+ /**
+ * Counts the total outgoing references from this class.
+ *
+ * <p>Includes method return types, field types, implemented interfaces,
+ * and superclass.</p>
+ *
+ * @return the count of visible outgoing references
+ */
+ private int getOutgoingReferencesCount() {
+ int result = 0;
+
+ // count method references
+ for (final MethodDescriptor methodDescriptor : methods)
+ result += methodDescriptor.getOutsideVisibleReferencesCount();
+
+ // count field references
+ for (final FieldDescriptor fieldDescriptor : nameToFieldMap.values())
+ result += fieldDescriptor.getOutsideVisibleReferencesCount();
+
+ // count implemented interfaces
+ for (final ClassDescriptor classDescriptor : interfaces)
+ if (classDescriptor.isVisible())
+ result++;
+
+ // count superclass
+ if (superClass != null)
+ if (superClass.isVisible())
+ result++;
+
+ return result;
+ }
+
+ /**
+ * Returns the package name portion of the fully qualified name.
+ *
+ * @return the package name, or empty string for default package
+ */
+ private String getPackageName() {
+
+ final int i = fullyQualifiedName.lastIndexOf('.');
+
+ if (i == -1)
+ return "";
+
+ return fullyQualifiedName.substring(0, i).replace("[L", "");
+ }
+
+ /**
+ * Returns the parent class names for nested classes.
+ *
+ * <p>For nested classes like Outer.Inner, this returns "Outer".</p>
+ *
+ * @return the parent class path, or empty string for non-nested classes
+ */
+ private String getParentClassesName() {
+ int i = fullyQualifiedName.lastIndexOf('.');
+ final String fullClassName = fullyQualifiedName.substring(i + 1);
+
+ i = fullClassName.lastIndexOf('$');
+ if (i == -1)
+ return "";
+ final String parentClassesName = fullClassName.substring(0, i);
+ return parentClassesName.replace('$', '.');
+ }
+
+ /**
+ * Returns the color for superclass inheritance arrows.
+ *
+ * @return the GraphViz color name from the light palette
+ */
+ private String getSuperClassColor() {
+ if (superClassColor == null)
+ superClassColor = Utils.getNextLightColor();
+
+ return superClassColor;
+ }
+
+ /**
+ * Checks if this class has a field matching the given name, ignoring case.
+ *
+ * @param fieldToSearch the field name to search for (case insensitive)
+ * @return true if a matching field exists
+ */
+ protected boolean hasFieldIgnoreCase(final String fieldToSearch) {
+
+ for (final String fieldName : nameToFieldMap.keySet())
+ if (fieldToSearch.equalsIgnoreCase(fieldName))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Marks this class as hidden, excluding it from the graph output.
+ */
+ private void hide() {
+ isShown = false;
+ }
+
+ /**
+ * Hides this class if it has no incoming or outgoing references.
+ *
+ * <p>Orphaned classes with no connections add visual noise without
+ * providing useful information about relationships.</p>
+ */
+ protected void hideClassIfNoReferences() {
+ if (!isVisible())
+ return;
+
+ final int totalReferencesCount = getOutgoingReferencesCount()
+ + referencesCount + extensionsCount + implementationsCount;
+
+ if (totalReferencesCount == 0) {
+ hide();
+ return;
+ }
+
+ }
+
+ /**
+ * Adds all fields from an array to the field map.
+ *
+ * @param fields the array of Java reflection Fields to index
+ */
+ private void indexFields(final Field[] fields) {
+ for (final Field field : fields)
+ getOrCreateFieldDescriptor(field);
+ }
+
+ /**
+ * Analyzes and adds all public methods from a class.
+ *
+ * @param clazz the Java Class to extract methods from
+ */
+ private void indexMethods(final Class<?> clazz) {
+ for (final Method method : clazz.getMethods()) {
+ final MethodDescriptor methodDescriptor = new MethodDescriptor(
+ this, method.getName());
+
+ methods.add(methodDescriptor);
+
+ methodDescriptor.analyze(method);
+ }
+
+ }
+
+ /**
+ * Determines whether this class should be included in the graph.
+ *
+ * <p>A class is hidden if it is:</p>
+ * <ul>
+ * <li>A primitive or system data type</li>
+ * <li>In a system package (java.*, javax.*, sun.*)</li>
+ * <li>Filtered by blacklist/whitelist glob patterns</li>
+ * <li>An array of primitives</li>
+ * <li>Explicitly marked as hidden</li>
+ * </ul>
+ *
+ * @return true if the class should be rendered, false otherwise
+ */
+ @Override
+ public boolean isVisible() {
+
+ if (Utils.isSystemDataType(fullyQualifiedName))
+ return false;
+
+ if (Utils.isSystemPackage(fullyQualifiedName))
+ return false;
+
+ if (!getClassGraph().isClassShown(fullyQualifiedName))
+ return false;
+
+ if (isArray)
+ if (arrayComponent != null)
+ if (Utils.isSystemDataType(arrayComponent.fullyQualifiedName))
+ // Do not show references to primitive data types in arrays.
+ // That is: there is no point to show reference to byte when
+ // we have class with byte array field.
+ return false;
+
+ return isShown;
+ }
+
+ /**
+ * Registers that another class extends this class.
+ */
+ protected void registerExtension() {
+ extensionsCount++;
+ }
+
+ /**
+ * Registers that another class implements this interface.
+ */
+ protected void registerImplementation() {
+ implementationsCount++;
+ }
+
+ /**
+ * Registers a field or method reference to this class.
+ */
+ protected void registerReference() {
+ referencesCount++;
+ }
+
+ /**
+ * Compares this descriptor to another for equality.
+ *
+ * <p>Equality is based on fully qualified name.</p>
+ *
+ * @param o the object to compare
+ * @return true if the objects represent the same class
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ClassDescriptor)) return false;
+
+ ClassDescriptor that = (ClassDescriptor) o;
+
+ return getFullyQualifiedName().equals(that.getFullyQualifiedName());
+
+ }
+
+ /**
+ * Returns a hash code based on the fully qualified name.
+ *
+ * @return hash code for this descriptor
+ */
+ @Override
+ public int hashCode() {
+ return getFullyQualifiedName().hashCode();
+ }
+
+ /**
+ * Compares this descriptor to another for sorting.
+ *
+ * <p>Classes are sorted by fully qualified name.</p>
+ *
+ * @param o the other descriptor to compare
+ * @return negative, zero, or positive based on name comparison
+ */
+ @Override
+ public int compareTo(ClassDescriptor o) {
+ return fullyQualifiedName.compareTo(o.fullyQualifiedName);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.structure;
+
+import eu.svjatoslav.commons.string.GlobMatcher;
+
+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 static java.io.File.separator;
+import static java.lang.System.getProperty;
+
+/**
+ * Central graph container for collecting and visualizing Java class relationships.
+ *
+ * <p>ClassGraph is the main entry point for JavaInspect. It collects classes
+ * through reflection, builds a graph of relationships, and generates GraphViz
+ * DOT format output that can be rendered to SVG or PNG images.</p>
+ *
+ * <p>Typical usage:</p>
+ * <pre>
+ * ClassGraph graph = new ClassGraph();
+ * graph.add(myObject);
+ * graph.add(MyClass.class);
+ * graph.setKeepDotFile(true);
+ * graph.generateGraph("MyVisualization");
+ * </pre>
+ *
+ * <p>The graph can be configured with:</p>
+ * <ul>
+ * <li>Target directory for output files</li>
+ * <li>Image format (SVG or PNG)</li>
+ * <li>Whitelist/blacklist glob patterns for filtering classes</li>
+ * <li>Option to hide orphaned classes with no connections</li>
+ * </ul>
+ *
+ * @see ClassDescriptor
+ * @see TargetImageType
+ */
+public class ClassGraph {
+
+ /**
+ * Maps fully qualified class names to their descriptors.
+ *
+ * <p>This is the primary storage for all analyzed classes in the graph.</p>
+ */
+ private final Map<String, ClassDescriptor> fullyQualifiedNameToClassMap = new HashMap<>();
+
+ /**
+ * List of glob patterns for classes to exclude from the graph.
+ */
+ private final List<String> blacklistClassGlobs = new ArrayList<>();
+
+ /**
+ * List of glob patterns for classes to include in the graph.
+ *
+ * <p>If non-empty, only classes matching a whitelist pattern are shown.</p>
+ */
+ private final List<String> whitelistClassGlobs = new ArrayList<>();
+
+ /**
+ * The output image format for generated graphs.
+ *
+ * <p>SVG is recommended for large diagrams; PNG for small ones.</p>
+ */
+ TargetImageType targetImageType = TargetImageType.SVG;
+
+ /**
+ * Whether to keep the intermediate DOT file after rendering.
+ */
+ private boolean keepDotFile;
+
+ /**
+ * Directory where output files will be written.
+ *
+ * <p>Defaults to the current working directory.</p>
+ */
+ private File targetDirectory = new File(getProperty("user.dir") + separator);
+
+ /**
+ * Creates an empty class graph ready to accept classes.
+ */
+ public ClassGraph() {
+ }
+
+ /**
+ * Adds objects or classes to the graph for analysis.
+ *
+ * <p>Each object's class is analyzed and added to the graph.
+ * If a Class object is passed, the class itself is added.</p>
+ *
+ * @param objects objects or Class instances to add to the graph
+ * @return this ClassGraph instance for method chaining
+ */
+ public ClassGraph add(final Object... objects) {
+
+ if (objects != null)
+ for (final Object object : objects)
+ addObject(object);
+
+ return this;
+ }
+
+ /**
+ * Adds a single object's class to the graph.
+ *
+ * <p>If the object is a Class instance, that class is added directly.
+ * Otherwise, the object's runtime class is added.</p>
+ *
+ * @param object the object or Class to add
+ */
+ private void addObject(final Object object) {
+ if (object instanceof Class)
+ getOrCreateClassDescriptor((Class) object);
+ else
+ getOrCreateClassDescriptor(object.getClass());
+ }
+
+ /**
+ * Adds a glob pattern to the blacklist for filtering classes.
+ *
+ * <p>Classes matching any blacklist pattern are excluded from the graph.
+ * Glob patterns support wildcards: * matches any sequence, ? matches single characters.</p>
+ *
+ * <p>Example patterns:</p>
+ * <ul>
+ * <li>"java.*" - excludes all java standard library</li>
+ * <li>"*Test" - excludes all test classes</li>
+ * </ul>
+ *
+ * @param glob the glob pattern to add to the blacklist
+ */
+ public void blacklistClassGlob(final String glob) {
+ blacklistClassGlobs.add(glob);
+ }
+
+ /**
+ * Sets the output image format for graph rendering.
+ *
+ * @param targetImageType the image format (SVG or PNG)
+ */
+ public void setTargetImageType(TargetImageType targetImageType) {
+ this.targetImageType = targetImageType;
+ }
+
+ /**
+ * Generates the graph visualization as an image file.
+ *
+ * <p>This method:</p>
+ * <ol>
+ * <li>Writes the DOT file to the target directory</li>
+ * <li>Executes GraphViz (dot command) to render the image</li>
+ * <li>Deletes the DOT file unless keepDotFile is true</li>
+ * </ol>
+ *
+ * <p>The GraphViz 'dot' executable must be available on the system PATH.</p>
+ *
+ * @param resultFileName the base name for output files (extension added automatically)
+ */
+ public void generateGraph(final String resultFileName) {
+
+ final File dotFile = new File(targetDirectory, resultFileName + ".dot");
+ final File imageFile = new File(targetDirectory, resultFileName + "." + targetImageType.fileExtension);
+
+ try {
+ // write DOT file to disk
+ final PrintWriter out = new PrintWriter(dotFile, "UTF-8");
+ out.write(getDot());
+ out.close();
+
+ // execute GraphViz to visualize graph
+ try {
+ Runtime.getRuntime()
+ .exec(new String[]{"dot",
+ "-T" + targetImageType.fileExtension,
+ dotFile.getAbsolutePath(),
+ "-o",
+ imageFile.getAbsolutePath()}).waitFor();
+ } catch (final InterruptedException ignored) {
+ }
+
+ if (!keepDotFile)
+ // delete dot file
+ if (!dotFile.delete())
+ throw new RuntimeException("Cannot delete file: " + dotFile.getAbsolutePath());
+
+ } catch (final IOException e) {
+ throw new RuntimeException("Unable to generate graph: " + e.getMessage(), e);
+ }
+
+ }
+
+ /**
+ * Generates the complete DOT format representation of the graph.
+ *
+ * <p>This creates a digraph with all class nodes and their relationships.</p>
+ *
+ * @return the DOT format string for the entire graph
+ */
+ private String getDot() {
+ final StringBuilder result = new StringBuilder();
+
+ 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");
+
+ return result.toString();
+ }
+
+ /**
+ * Creates or retrieves a class descriptor for a Java class.
+ *
+ * <p>If a descriptor for the class already exists in the graph, it is returned.
+ * Otherwise, a new descriptor is created, added to the map, and analyzed.</p>
+ *
+ * @param clazz the Java Class to get a descriptor for
+ * @return the ClassDescriptor for the class, or null if clazz is null
+ */
+ 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;
+ }
+
+ /**
+ * Hides classes that have no incoming or outgoing references.
+ *
+ * <p>Orphaned classes add visual clutter without showing meaningful
+ * relationships. This method removes them from the graph output.</p>
+ *
+ * @return this ClassGraph instance for method chaining
+ */
+ public ClassGraph hideOrphanedClasses() {
+
+ for (final ClassDescriptor classDescriptor : fullyQualifiedNameToClassMap
+ .values())
+ classDescriptor.hideClassIfNoReferences();
+
+ return this;
+ }
+
+ /**
+ * Checks whether a class should be shown based on filter patterns.
+ *
+ * <p>A class is hidden if it matches any blacklist pattern, or if
+ * a whitelist exists and it doesn't match any whitelist pattern.</p>
+ *
+ * @param className the fully qualified class name to check
+ * @return true if the class passes the filter, false if it should be hidden
+ */
+ protected boolean isClassShown(final String className) {
+ for (final String pattern : blacklistClassGlobs)
+ if (GlobMatcher.match(className, pattern))
+ return false;
+
+ if (!whitelistClassGlobs.isEmpty()) {
+ for (final String pattern : whitelistClassGlobs)
+ if (GlobMatcher.match(className, pattern))
+ return true;
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets whether to keep the intermediate DOT file after rendering.
+ *
+ * <p>Keeping the DOT file is useful for debugging or manual customization.</p>
+ *
+ * @param keepDotFile true to keep the DOT file, false to delete it
+ * @return this ClassGraph instance for method chaining
+ */
+ public ClassGraph setKeepDotFile(final boolean keepDotFile) {
+ this.keepDotFile = keepDotFile;
+
+ return this;
+ }
+
+ /**
+ * Sets the target directory for output files.
+ *
+ * @param targetDirectory the directory where DOT and image files will be written
+ * @return this ClassGraph instance for method chaining
+ */
+ public ClassGraph setTargetDirectory(File targetDirectory) {
+ this.targetDirectory = targetDirectory;
+ return this;
+ }
+
+ /**
+ * Adds a glob pattern to the whitelist for including classes.
+ *
+ * <p>If any whitelist patterns are defined, only classes matching at least
+ * one pattern will be shown in the graph.</p>
+ *
+ * @param glob the glob pattern to add to the whitelist
+ * @return this ClassGraph instance for method chaining
+ */
+ public ClassGraph whitelistClassGlob(final String glob) {
+ whitelistClassGlobs.add(glob);
+ return this;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.structure;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a single field within a Java class for visualization purposes.
+ *
+ * <p>Field descriptors capture field names, types, and generic type arguments,
+ * generating GraphViz DOT format output for both embedded display within class
+ * nodes and external reference edges.</p>
+ *
+ * @see ClassDescriptor
+ * @see GraphElement
+ */
+public class FieldDescriptor implements GraphElement {
+
+ /**
+ * The parent class that contains this field.
+ */
+ private final ClassDescriptor parentClassDescriptor;
+
+ /**
+ * Generic type arguments for parameterized field types.
+ *
+ * <p>For example, a field of type {@code List<String>} would have
+ * String as a type argument.</p>
+ */
+ private final List<ClassDescriptor> typeArguments = new ArrayList<>();
+
+ /**
+ * The simple name of the field.
+ */
+ private String name;
+
+ /**
+ * The type descriptor for the field's declared type.
+ */
+ private ClassDescriptor type;
+
+ /**
+ * Whether this field is inherited from a parent class.
+ *
+ * <p>Inherited fields are typically hidden in visualizations.</p>
+ */
+ private boolean isInherited;
+
+ /**
+ * Creates a field descriptor associated with a parent class.
+ *
+ * @param parent the class descriptor that contains this field
+ */
+ public FieldDescriptor(final ClassDescriptor parent) {
+ parentClassDescriptor = parent;
+ }
+
+ /**
+ * Analyzes a Java reflection Field to populate this descriptor.
+ *
+ * <p>This method extracts the field name, type, and generic type arguments,
+ * registering references with the class graph for later visualization.</p>
+ *
+ * @param field the Java reflection Field to analyze
+ */
+ public void analyzeField(final Field field) {
+
+ if (!field.getDeclaringClass().getName()
+ .equals(parentClassDescriptor.getFullyQualifiedName()))
+ isInherited = true;
+
+ name = field.getName();
+ type = parentClassDescriptor.getClassGraph().getOrCreateClassDescriptor(
+ field.getType());
+ type.registerReference();
+
+ final Type fieldGenericType = field.getGenericType();
+ if (fieldGenericType instanceof ParameterizedType) {
+ final ParameterizedType fieldParameterizedGenericType = (ParameterizedType) fieldGenericType;
+ for (final Type type : fieldParameterizedGenericType.getActualTypeArguments())
+ if (type instanceof Class) {
+ final Class aClass = (Class) type;
+ final ClassDescriptor genericTypeDescriptor = parentClassDescriptor
+ .getClassGraph().getOrCreateClassDescriptor(aClass);
+ genericTypeDescriptor.registerReference();
+ typeArguments.add(genericTypeDescriptor);
+ }
+
+ }
+ }
+
+ /**
+ * Generates DOT format edges showing references from this field to other classes.
+ *
+ * <p>This creates the arrows pointing from the field to its type and
+ * generic type arguments in the class diagram.</p>
+ *
+ * @return DOT format string for edges, or empty string if not visible
+ */
+ @Override
+ public String getDot() {
+
+ if (!isVisible())
+ return "";
+
+ final StringBuilder result = new StringBuilder();
+
+ // describe associated types
+ for (final ClassDescriptor classDescriptor : typeArguments)
+ if (classDescriptor.isVisible())
+ if (classDescriptor.areReferencesShown())
+ result.append(" " + getGraphId() + " -> "
+ + classDescriptor.getGraphId() + "[label=\"" + name
+ + "\", color=\"" + classDescriptor.getColor()
+ + "\", style=\"bold\"];\n");
+
+ if (type == null) return result.toString();
+ if (!type.isVisible())
+ return result.toString();
+
+ // main type
+ boolean showLink = type.areReferencesShown();
+
+ if (type == parentClassDescriptor)
+ showLink = false;
+
+ if (parentClassDescriptor.isEnum)
+ showLink = false;
+
+ if (showLink)
+ result.append(" " + getGraphId() + " -> " + type.getGraphId()
+ + "[label=\"" + name + "\"," + " color=\""
+ + type.getColor() + "\", style=\"bold\"];\n");
+
+ return result.toString();
+ }
+
+ /**
+ * Generates embedded DOT format HTML table row for display within the parent class node.
+ *
+ * <p>This creates a table row showing the field type and name inside
+ * the class box in the diagram.</p>
+ *
+ * @return DOT format HTML table row, or empty string if not visible
+ */
+ @Override
+ public String getEmbeddedDot() {
+
+ if (!isVisible())
+ return "";
+
+ final StringBuilder result = new StringBuilder();
+
+ result.append(" // " + name + "\n");
+ if (parentClassDescriptor.isEnum && (type == parentClassDescriptor)) {
+ result.append(" <TR><TD colspan=\"2\" PORT=\"" + name);
+ result.append("\" ALIGN=\"left\"><FONT POINT-SIZE=\"11.0\">");
+ result.append(name + "</FONT></TD></TR>\n");
+ } else {
+ result.append(" <TR><td ALIGN=\"right\">");
+ result.append("<FONT POINT-SIZE=\"8.0\">");
+ result.append(describeType() + "</FONT>");
+ result.append("</td><TD PORT=\"" + name);
+ result.append("\" ALIGN=\"left\"><FONT POINT-SIZE=\"11.0\">");
+ result.append(name + "</FONT></TD></TR>\n");
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a human-readable description of the field's type.
+ *
+ * @return the type name with array indicator if applicable, or "-null-" if type is null
+ */
+ private String describeType() {
+ if (type == null) return "-null-";
+ return type.getClassName(true);
+ }
+
+ /**
+ * Returns the GraphViz identifier for this field.
+ *
+ * <p>The ID combines the parent class ID with the field name,
+ * allowing precise edge targeting in the diagram.</p>
+ *
+ * @return unique identifier in format "classId:fieldName"
+ */
+ @Override
+ public String getGraphId() {
+ return parentClassDescriptor.getGraphId() + ":" + name;
+ }
+
+ /**
+ * Counts visible external references from this field.
+ *
+ * <p>This is used to determine whether a class has enough connections
+ * to be worth displaying.</p>
+ *
+ * @return 1 if the field type is visible, 0 otherwise
+ */
+ protected int getOutsideVisibleReferencesCount() {
+
+ if (!isVisible())
+ return 0;
+
+ if (type != null)
+ if (type.isVisible())
+ return 1;
+
+ return 0;
+ }
+
+ /**
+ * Returns the type descriptor for this field.
+ *
+ * @return the ClassDescriptor representing the field's type
+ */
+ protected ClassDescriptor getType() {
+ return type;
+ }
+
+ /**
+ * Determines whether this field should be included in the visualization.
+ *
+ * <p>Fields are hidden if they are inherited, contain dollar signs
+ * (compiler-generated), or are serialVersionUID constants.</p>
+ *
+ * @return true if the field should be displayed, false otherwise
+ */
+ @Override
+ public boolean isVisible() {
+ if (isInherited)
+ return false;
+
+ if (name.contains("$"))
+ return false;
+
+ return !name.equals("serialVersionUID");
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.structure;
+
+/**
+ * Common interface for elements that can be rendered as GraphViz DOT nodes.
+ *
+ * <p>All graph elements (classes, fields, methods) implement this interface
+ * to provide DOT format output for visualization.</p>
+ *
+ * @see ClassDescriptor
+ * @see FieldDescriptor
+ * @see MethodDescriptor
+ */
+public interface GraphElement {
+
+ /**
+ * Generates the DOT format representation for external references.
+ *
+ * <p>This method produces the edges (arrows) connecting this element
+ * to other elements in the graph.</p>
+ *
+ * @return DOT format string for edges, or empty string if not visible
+ */
+ String getDot();
+
+ /**
+ * Generates the embedded DOT representation for display within a parent node.
+ *
+ * <p>This method produces the HTML-like table cells that appear inside
+ * the parent class node in the graph.</p>
+ *
+ * @return DOT format string for embedded content, or empty string if not visible
+ */
+ String getEmbeddedDot();
+
+ /**
+ * Returns the unique GraphViz identifier for this element.
+ *
+ * <p>The ID is used to reference this element in edges and node definitions.</p>
+ *
+ * @return unique identifier string in GraphViz format
+ */
+ String getGraphId();
+
+ /**
+ * Determines whether this element should be included in the graph output.
+ *
+ * <p>Visibility is controlled by various factors including system package
+ * filtering, inheritance status, and reference count thresholds.</p>
+ *
+ * @return true if the element should be rendered, false otherwise
+ */
+ boolean isVisible();
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.structure;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a single method within a Java class for visualization purposes.
+ *
+ * <p>Method descriptors capture method names, return types, and generic type
+ * information, generating GraphViz DOT format output for both embedded display
+ * within class nodes and external reference edges.</p>
+ *
+ * @see ClassDescriptor
+ * @see GraphElement
+ */
+public class MethodDescriptor implements GraphElement,
+ Comparable<MethodDescriptor> {
+
+ /**
+ * The simple name of the method.
+ */
+ private final String methodName;
+
+ /**
+ * The parent class that contains this method.
+ */
+ private final ClassDescriptor parentClass;
+
+ /**
+ * Type arguments extracted from generic return types.
+ *
+ * <p>For methods returning parameterized types like {@code List<String>},
+ * this captures the generic type arguments.</p>
+ */
+ private final List<ClassDescriptor> argumentTypes = new ArrayList<>();
+
+ /**
+ * The type descriptor for the method's return type.
+ */
+ private ClassDescriptor returnType;
+
+ /**
+ * Whether this method is inherited from a parent class or interface.
+ *
+ * <p>Inherited methods are typically hidden in visualizations to reduce noise.</p>
+ */
+ private boolean isInherited;
+
+ /**
+ * Creates a method descriptor associated with a parent class.
+ *
+ * @param parent the class descriptor that contains this method
+ * @param methodName the simple name of the method
+ */
+ public MethodDescriptor(final ClassDescriptor parent,
+ final String methodName) {
+ parentClass = parent;
+ this.methodName = methodName;
+ }
+
+ /**
+ * Analyzes a Java reflection Method to populate this descriptor.
+ *
+ * <p>This method extracts the return type and generic type information,
+ * registering references with the class graph for later visualization.</p>
+ *
+ * @param method the Java reflection Method to analyze
+ */
+ public void analyze(final Method method) {
+
+ if (!method.getDeclaringClass().getName()
+ .equals(parentClass.getFullyQualifiedName()))
+ isInherited = true;
+
+ returnType = parentClass.getClassGraph().getOrCreateClassDescriptor(
+ method.getReturnType());
+ returnType.registerReference();
+
+ final Type genericType = method.getGenericReturnType();
+ if (genericType instanceof ParameterizedType) {
+ final ParameterizedType pt = (ParameterizedType) genericType;
+ for (final Type t : pt.getActualTypeArguments())
+ if (t instanceof Class) {
+ final Class cl = (Class) t;
+ final ClassDescriptor classDescriptor = parentClass
+ .getClassGraph().getOrCreateClassDescriptor(cl);
+ classDescriptor.registerReference();
+ argumentTypes.add(classDescriptor);
+ }
+
+ }
+ }
+
+ /**
+ * Compares this method descriptor to another object for equality.
+ *
+ * <p>Equality is based on method name, parent class, and argument types.</p>
+ *
+ * @param o the object to compare
+ * @return true if the objects are equal
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MethodDescriptor)) return false;
+
+ MethodDescriptor that = (MethodDescriptor) o;
+
+ if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false;
+ if (parentClass != null ? !parentClass.equals(that.parentClass) : that.parentClass != null) return false;
+ return argumentTypes != null ? argumentTypes.equals(that.argumentTypes) : that.argumentTypes == null;
+
+ }
+
+
+ /**
+ * Returns a hash code for this method descriptor.
+ *
+ * @return hash code based on method name, parent class, and argument types
+ */
+ @Override
+ public int hashCode() {
+ int result = methodName != null ? methodName.hashCode() : 0;
+ result = 31 * result + (parentClass != null ? parentClass.hashCode() : 0);
+ result = 31 * result + (argumentTypes != null ? argumentTypes.hashCode() : 0);
+ return result;
+ }
+
+ /**
+ * Generates DOT format edges showing references from this method to other classes.
+ *
+ * <p>This creates dotted arrows pointing from the method to its return type
+ * and generic type arguments in the class diagram.</p>
+ *
+ * @return DOT format string for edges, or empty string if not visible
+ */
+ @Override
+ public String getDot() {
+
+ if (!isVisible())
+ return "";
+
+ final StringBuilder result = new StringBuilder();
+
+ // describe associated types
+ for (final ClassDescriptor classDescriptor : argumentTypes)
+ if (classDescriptor.isVisible())
+ if (classDescriptor.areReferencesShown())
+ result.append(" " + getGraphId() + " -> "
+ + classDescriptor.getGraphId() + "[label=\""
+ + methodName + "\", color=\""
+ + classDescriptor.getColor()
+ + "\", style=\"dotted, bold\"];\n");
+
+ if (returnType == null) return result.toString();
+ if (!returnType.isVisible()) return result.toString();
+
+ // main type
+ if (returnType.areReferencesShown())
+ result.append(" " + getGraphId() + " -> "
+ + returnType.getGraphId() + "[label=\"" + methodName
+ + "\"," + " color=\"" + returnType.getColor()
+ + "\", style=\"dotted, bold\"];\n");
+
+ return result.toString();
+ }
+
+ /**
+ * Generates embedded DOT format HTML table row for display within the parent class node.
+ *
+ * <p>This creates a table row showing the return type and method name
+ * inside the class box in the diagram. Method names are displayed in red.</p>
+ *
+ * @return DOT format HTML table row, or empty string if not visible
+ */
+ @Override
+ public String getEmbeddedDot() {
+ if (!isVisible())
+ return "";
+
+ final StringBuilder result = new StringBuilder();
+
+ result.append(" // " + methodName + "\n");
+
+ result.append(" <TR><td ALIGN=\"right\">"
+ + "<FONT POINT-SIZE=\"8.0\">" + describeReturnType()
+ + "</FONT>" + "</td><TD PORT=\"" + getMethodLabel()
+ + "\" ALIGN=\"left\"><FONT COLOR =\"red\" POINT-SIZE=\"11.0\">"
+ + getMethodLabel() + "</FONT></TD></TR>\n");
+
+ return result.toString();
+ }
+
+ /**
+ * Returns a human-readable description of the method's return type.
+ *
+ * @return the return type name with array indicator if applicable, or "-null-" if null
+ */
+ private String describeReturnType() {
+ if (returnType == null) return "-null-";
+
+ return returnType.getClassName(true);
+ }
+
+ /**
+ * Returns the GraphViz identifier for this method.
+ *
+ * <p>The ID combines the parent class ID with the method name.</p>
+ *
+ * @return unique identifier in format "classId:methodName"
+ */
+ @Override
+ public String getGraphId() {
+ return parentClass.getGraphId() + ":" + methodName;
+ }
+
+ /**
+ * Returns the label used for displaying the method in the diagram.
+ *
+ * @return the method name
+ */
+ private String getMethodLabel() {
+ return methodName;
+ }
+
+ /**
+ * Counts visible external references from this method.
+ *
+ * <p>This counts both the return type and any generic type arguments
+ * that are visible in the graph.</p>
+ *
+ * @return the number of visible referenced types
+ */
+ protected int getOutsideVisibleReferencesCount() {
+ int result = 0;
+
+ if (returnType != null)
+ if (returnType.isVisible())
+ result++;
+
+ for (final ClassDescriptor classDescriptor : argumentTypes)
+ if (classDescriptor.isVisible())
+ result++;
+
+ return result;
+ }
+
+ /**
+ * Determines whether this method should be included in the visualization.
+ *
+ * <p>Methods are hidden if they are:</p>
+ * <ul>
+ * <li>Inherited from parent classes</li>
+ * <li>Common Object methods (wait, equals, toString, etc.)</li>
+ * <li>Enum methods (values, valueOf, etc.) for enum classes</li>
+ * <li>Getter/setter methods that correspond to existing fields</li>
+ * </ul>
+ *
+ * @return true if the method should be displayed, false otherwise
+ */
+ @Override
+ public boolean isVisible() {
+
+ // hide inherited methods
+ if (isInherited)
+ return false;
+
+ // hide common object methods
+ if (Utils.isCommonObjectMethod(methodName))
+ return false;
+
+ // hide common Enumeration methods
+ if (parentClass.isEnum && Utils.isEnumMethod(methodName))
+ return false;
+
+ // hide get/set methods for the field of the same name
+ if (methodName.startsWith("get") || methodName.startsWith("set"))
+ if (parentClass.hasFieldIgnoreCase(methodName.substring(3)))
+ return false;
+
+ // hide is methods for the boolean field of the same name
+ if (methodName.startsWith("is")) {
+ final FieldDescriptor field = parentClass
+ .getFieldIgnoreCase(methodName.substring(2));
+ if (field != null)
+ if ("boolean".equals(field.getType().getFullyQualifiedName()))
+ return false;
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Compares this method descriptor to another for sorting purposes.
+ *
+ * <p>Methods are sorted primarily by name, then by parent class.</p>
+ *
+ * @param that the other method descriptor to compare
+ * @return negative if this comes before that, positive if after, zero if equal
+ */
+ @Override
+ public int compareTo(MethodDescriptor that) {
+ if (this == that) return 0;
+
+ int comparisonResult = methodName.compareTo(that.methodName);
+ if (comparisonResult != 0) return comparisonResult;
+
+ return parentClass.compareTo(that.parentClass);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.structure;
+
+/**
+ * Supported output image formats for class graph visualization.
+ *
+ * <p>JavaInspect uses GraphViz to generate visual representations of
+ * class relationships. This enum defines the available output formats.</p>
+ *
+ * @see ClassGraph#setTargetImageType(TargetImageType)
+ */
+public enum TargetImageType {
+
+ /**
+ * Portable Network Graphics format.
+ *
+ * <p>A raster image format suitable for embedding in documents
+ * and web pages where scalability is not required.</p>
+ */
+ PNG("png"),
+
+ /**
+ * Scalable Vector Graphics format.
+ *
+ * <p>A vector image format that scales infinitely without quality loss,
+ * ideal for large class diagrams and high-resolution displays.</p>
+ */
+ SVG("svg");
+
+ /**
+ * The file extension used for this image format.
+ */
+ public final String fileExtension;
+
+ /**
+ * Creates a target image type with the specified file extension.
+ *
+ * @param fileExtension the file extension without the leading dot
+ */
+ TargetImageType(String fileExtension) {
+ this.fileExtension = fileExtension;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.inspector.java.structure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class providing filtering and styling helpers for class graph generation.
+ *
+ * <p>This class maintains static collections of system data types, common methods,
+ * system packages, and color palettes used throughout the visualization process.</p>
+ *
+ * <p>All methods are static and the class is not intended to be instantiated.</p>
+ *
+ * @see ClassDescriptor
+ * @see MethodDescriptor
+ */
+public class Utils {
+
+ /**
+ * List of primitive and system data type names to exclude from visualization.
+ */
+ private static final List<String> systemDataTypes = new ArrayList<>();
+
+ /**
+ * List of common {@link Object} method names to hide in method listings.
+ */
+ private static final List<String> commonObjectMethods = new ArrayList<>();
+
+ /**
+ * List of system package prefixes to exclude from visualization.
+ */
+ private static final List<String> systemPackages = new ArrayList<>();
+
+ /**
+ * Palette of dark colors for reference edges (incoming arrows).
+ */
+ private static final List<String> darkColors = new ArrayList<>();
+
+ /**
+ * Palette of light colors for inheritance edges (superclass arrows).
+ */
+ private static final List<String> lightColors = new ArrayList<>();
+
+ /**
+ * List of enum-specific method names to hide for enum classes.
+ */
+ private static final List<String> enumMethods = new ArrayList<>();
+
+ /**
+ * Current index in the dark color palette for cyclic selection.
+ */
+ private static int lastChosenDarkColor = -1;
+
+ /**
+ * Current index in the light color palette for cyclic selection.
+ */
+ private static int lastChosenLightColor = -1;
+
+ /**
+ * Static initializer that populates all filter and color lists.
+ */
+ static {
+ initEnumMethods();
+ initSystemDataTypes();
+ initDarkColors();
+ initLightColors();
+ initCommonObjectMethods();
+ initSystemPackages();
+ }
+
+ /**
+ * Returns the next color from the dark color palette.
+ *
+ * <p>Colors are selected cyclically, wrapping around to the first color
+ * after the last one has been used.</p>
+ *
+ * @return the name of the next dark color for GraphViz styling
+ */
+ protected static String getNextDarkColor() {
+ lastChosenDarkColor++;
+ if (lastChosenDarkColor >= darkColors.size())
+ lastChosenDarkColor = 0;
+
+ return darkColors.get(lastChosenDarkColor);
+ }
+
+ /**
+ * Returns the next color from the light color palette.
+ *
+ * <p>Colors are selected cyclically, wrapping around to the first color
+ * after the last one has been used.</p>
+ *
+ * @return the name of the next light color for GraphViz styling
+ */
+ protected static String getNextLightColor() {
+ lastChosenLightColor++;
+ if (lastChosenLightColor >= lightColors.size())
+ lastChosenLightColor = 0;
+
+ return lightColors.get(lastChosenLightColor);
+ }
+
+ /**
+ * Populates the list of common Object methods to filter out.
+ *
+ * <p>These methods (wait, equals, toString, etc.) are inherited by all
+ * objects and typically add noise to class diagrams.</p>
+ */
+ private static void initCommonObjectMethods() {
+ commonObjectMethods.add("wait");
+ commonObjectMethods.add("equals");
+ commonObjectMethods.add("toString");
+ commonObjectMethods.add("hashCode");
+ commonObjectMethods.add("notify");
+ commonObjectMethods.add("notifyAll");
+ commonObjectMethods.add("getClass");
+ }
+
+ /**
+ * Populates the dark color palette for reference edges.
+ *
+ * <p>Dark colors are used for incoming reference arrows to distinguish
+ * them from inheritance relationships.</p>
+ */
+ protected static void initDarkColors() {
+ darkColors.add("antiquewhite4");
+ darkColors.add("blueviolet");
+ darkColors.add("brown4");
+ darkColors.add("chartreuse4");
+ darkColors.add("cyan4");
+ darkColors.add("deeppink1");
+ darkColors.add("deepskyblue3");
+ darkColors.add("firebrick1");
+ darkColors.add("goldenrod3");
+ darkColors.add("gray0");
+ }
+
+ /**
+ * Populates the list of enum-specific methods to filter out.
+ *
+ * <p>These methods (values, valueOf, name, etc.) are present in all
+ * enum classes and are typically not relevant for visualization.</p>
+ */
+ private static void initEnumMethods() {
+ enumMethods.add("values");
+ enumMethods.add("valueOf");
+ enumMethods.add("name");
+ enumMethods.add("compareTo");
+ enumMethods.add("valueOf");
+ enumMethods.add("getDeclaringClass");
+ enumMethods.add("ordinal");
+ }
+
+ /**
+ * Populates the light color palette for inheritance edges.
+ *
+ * <p>Light colors are used for superclass arrows to visually distinguish
+ * inheritance relationships from field/method references.</p>
+ */
+ private static void initLightColors() {
+ lightColors.add("olivedrab2");
+ lightColors.add("peachpuff2");
+ lightColors.add("seagreen1");
+ lightColors.add("violet");
+ lightColors.add("cyan");
+ lightColors.add("orange");
+ }
+
+ /**
+ * Populates the list of primitive and system data types to filter out.
+ *
+ * <p>These types (int, long, boolean, etc.) are fundamental and do not
+ * represent interesting relationships in class diagrams.</p>
+ */
+ private static void initSystemDataTypes() {
+ systemDataTypes.add("void");
+ systemDataTypes.add("int");
+ systemDataTypes.add("long");
+ systemDataTypes.add("float");
+ systemDataTypes.add("double");
+ systemDataTypes.add("boolean");
+ systemDataTypes.add("char");
+ systemDataTypes.add("short");
+ systemDataTypes.add("byte");
+ }
+
+ /**
+ * Populates the list of system package prefixes to filter out.
+ *
+ * <p>Classes from these packages (java.*, javax.*, sun.*) are standard
+ * library classes and typically excluded from application diagrams.</p>
+ */
+ private static void initSystemPackages() {
+ systemPackages.add("java.");
+ systemPackages.add("javax.");
+ systemPackages.add("sun.");
+ }
+
+ /**
+ * Checks if a method name is a common Object method.
+ *
+ * @param name the method name to check
+ * @return true if the method is a common Object method like equals or toString
+ */
+ protected static boolean isCommonObjectMethod(final String name) {
+ return commonObjectMethods.contains(name);
+ }
+
+ /**
+ * Checks if a method name is an enum-specific method.
+ *
+ * @param name the method name to check
+ * @return true if the method is an enum method like values or valueOf
+ */
+ protected static boolean isEnumMethod(final String name) {
+ return enumMethods.contains(name);
+ }
+
+ /**
+ * Checks if a type name is a primitive or system data type.
+ *
+ * @param name the fully qualified type name to check
+ * @return true if the type is a primitive like int, boolean, or void
+ */
+ protected static boolean isSystemDataType(final String name) {
+ return systemDataTypes.contains(name);
+ }
+
+ /**
+ * Checks if a class belongs to a system package.
+ *
+ * <p>System packages include java.*, javax.*, and sun.* prefixes.</p>
+ *
+ * @param name the fully qualified class name to check
+ * @return true if the class is from a system package
+ */
+ protected static boolean isSystemPackage(final String name) {
+
+ for (final String packagePrefix : systemPackages)
+ if (name.startsWith(packagePrefix))
+ return true;
+
+ return false;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Provides classes for inspecting Java application structure through runtime reflection.
+ *
+ * <p>This package contains the core classes that traverse class references,
+ * extract field and method information, and generate GraphViz DOT format
+ * visualizations of Java class relationships.</p>
+ *
+ * @see eu.svjatoslav.inspector.java.structure.ClassGraph
+ * @see eu.svjatoslav.inspector.java.structure.ClassDescriptor
+ * @see eu.svjatoslav.inspector.java.structure.FieldDescriptor
+ * @see eu.svjatoslav.inspector.java.structure.MethodDescriptor
+ */
+package eu.svjatoslav.inspector.java.structure;
\ No newline at end of file
--- /dev/null
+/.idea/
+/target/
+/example.svg
+/*.iml
\ No newline at end of file
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>javainspect-demo</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <name>JavaInspect demo</name>
+ <description>Demonstration project for JavaInspect utility</description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>javainspect</artifactId>
+ <version>1.7-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <id>svjatoslav.eu</id>
+ <name>Svjatoslav repository</name>
+ <url>http://www2.svjatoslav.eu/maven/</url>
+ </repository>
+ </repositories>
+</project>
--- /dev/null
+/*
+ * JavaInspect - Utility to visualize java software
+ * Copyright (C) 2013-2020, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ *
+ * This program is free software; you can redistribute it and/or
+ * 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.example;
+
+import eu.svjatoslav.inspector.java.structure.ClassGraph;
+import eu.svjatoslav.inspector.java.structure.example.torender.SampleClass;
+import eu.svjatoslav.inspector.java.structure.example.torender.SampleClass2;
+
+public class RenderUsingReflection {
+
+ /**
+ * If you run this method using IDE, then example.svg file shall appear in project root directory.
+ */
+ public static void main(final String[] args) {
+
+ new ClassGraph().add(SampleClass.class, SampleClass2.class)
+ .generateGraph("example");
+ }
+
+}
--- /dev/null
+package eu.svjatoslav.inspector.java.structure.example.torender;
+
+public class ObjectReturnedByMethod {
+
+}
--- /dev/null
+package eu.svjatoslav.inspector.java.structure.example.torender;
+
+public class ObjectVisibleAsClassField {
+
+}
--- /dev/null
+package eu.svjatoslav.inspector.java.structure.example.torender;
+
+public class SampleClass extends SampleSuperClass {
+
+ ObjectVisibleAsClassField sampleClassField;
+
+ public ObjectReturnedByMethod sampleMethod() {
+ return new ObjectReturnedByMethod();
+ }
+
+}
--- /dev/null
+package eu.svjatoslav.inspector.java.structure.example.torender;
+
+public class SampleClass2 extends SampleSuperClass {
+
+}
--- /dev/null
+package eu.svjatoslav.inspector.java.structure.example.torender;
+
+public enum SampleEnum {
+
+ ONE, TWO, THREE, FOUR
+
+}
--- /dev/null
+package eu.svjatoslav.inspector.java.structure.example.torender;
+
+public interface SampleInterface {
+ SampleEnum getSomeValue();
+}
--- /dev/null
+package eu.svjatoslav.inspector.java.structure.example.torender;
+
+public class SampleSuperClass implements SampleInterface {
+
+ @Override
+ public SampleEnum getSomeValue() {
+ return SampleEnum.ONE;
+ }
+
+}
--- /dev/null
+#!/bin/bash
+
+cd "${0%/*}"
+
+cd ..
+idea .