From: Svjatoslav Agejenko Date: Fri, 17 Apr 2026 14:48:22 +0000 (+0300) Subject: Initial commit X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=15ef7db990cd92de2d005a397b61c60188c7f0ef;p=javainspect.git Initial commit --- 15ef7db990cd92de2d005a397b61c60188c7f0ef diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..0809663 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/.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/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..93ead91 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,235 @@ +# 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 + + eu.svjatoslav + javainspect + 1.8 + + + + svjatoslav.eu + https://www3.svjatoslav.eu/maven/ + +``` + +--- + +# 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 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/COPYING @@ -0,0 +1,121 @@ +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. diff --git a/TODO.org b/TODO.org new file mode 100644 index 0000000..3606460 --- /dev/null +++ b/TODO.org @@ -0,0 +1,139 @@ +* 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 diff --git a/Tools/Open with IntelliJ IDEA b/Tools/Open with IntelliJ IDEA new file mode 100755 index 0000000..de9bae5 --- /dev/null +++ b/Tools/Open with IntelliJ IDEA @@ -0,0 +1,18 @@ +#!/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 diff --git a/Tools/Update web site b/Tools/Update web site new file mode 100755 index 0000000..86d62a0 --- /dev/null +++ b/Tools/Update web site @@ -0,0 +1,127 @@ +#!/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 diff --git a/commandline launcher/install b/commandline launcher/install new file mode 100755 index 0000000..b7c7b3e --- /dev/null +++ b/commandline launcher/install @@ -0,0 +1,25 @@ +#!/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/ diff --git a/commandline launcher/javainspect b/commandline launcher/javainspect new file mode 100755 index 0000000..62971e5 --- /dev/null +++ b/commandline launcher/javainspect @@ -0,0 +1,7 @@ +#!/bin/bash +# +# Launcher script for JavaInspect utility: +# https://www3.svjatoslav.eu/projects/javainspect/ +# + +java -jar /opt/javainspect/javainspect.jar "$@" diff --git a/doc/JavaInspect full project.png b/doc/JavaInspect full project.png new file mode 100644 index 0000000..4d5ebba Binary files /dev/null and b/doc/JavaInspect full project.png differ diff --git a/doc/JavaInspect.dot b/doc/JavaInspect.dot new file mode 100644 index 0000000..660c81c --- /dev/null +++ b/doc/JavaInspect.dot @@ -0,0 +1,316 @@ +digraph Java { +graph [rankdir=LR, overlap = false, concentrate=true]; + +// Class: eu.svjatoslav.inspector.java.structure.Filter + class_eu_svjatoslav_inspector_java_structure_Filter[label=< + + // class descriptor header + + + // fields: + // blacklistClassPatterns + + // whitelistClassPatterns + + + // methods: + // blacklistClassPattern + + // isClassShown + + // whitelistClassPattern + +
eu.svjatoslav.inspector.java.structure
Filter
ListblacklistClassPatterns
ListwhitelistClassPatterns
voidblacklistClassPattern
booleanisClassShown
voidwhitelistClassPattern
>, 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=< + + // class descriptor header + + + // fields: + // name + + // parent + + // type + + // typeArguments + + + // methods: + // getDot + + // getEmbeddedDot + + // getGraphId + + // getOutsideVisibleReferencesCount + + // isVisible + +
eu.svjatoslav.inspector.java.structure
FieldDescriptor
Stringname
ClassDescriptorparent
ClassDescriptortype
ListtypeArguments
StringgetDot
StringgetEmbeddedDot
StringgetGraphId
intgetOutsideVisibleReferencesCount
booleanisVisible
>, 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=< + + // class descriptor header + + + // fields: + // name + + // parent + + // returnType + + // typeArguments + + + // methods: + // compareTo + + // compareTo + + // getDot + + // getEmbeddedDot + + // getGraphId + + // getMethodLabel + + // getOutsideVisibleReferencesCount + + // isVisible + +
eu.svjatoslav.inspector.java.structure
MethodDescriptor
Stringname
ClassDescriptorparent
ClassDescriptorreturnType
ListtypeArguments
intcompareTo
intcompareTo
StringgetDot
StringgetEmbeddedDot
StringgetGraphId
StringgetMethodLabel
intgetOutsideVisibleReferencesCount
booleanisVisible
>, 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=< + + // class descriptor header + + + // fields: + // darkColors + + // enumMethods + + // lastChosenDarkColor + + // lastChosenLightColor + + // lightColors + + // systemDataTypes + + // systemMethods + + // systemPackages + + + // methods: + // getNextDarkColor + + // getNextLightColor + + // initDarkColors + + // initEnumMethods + + // initLightColors + + // initSystemDataTypes + + // initSystemMethods + + // initSystemPackages + + // isEnumMethod + + // isSystemDataType + + // isSystemMethod + + // isSystemPackage + +
eu.svjatoslav.inspector.java.structure
Utils
ListdarkColors
ListenumMethods
intlastChosenDarkColor
intlastChosenLightColor
ListlightColors
ListsystemDataTypes
ListsystemMethods
ListsystemPackages
StringgetNextDarkColor
StringgetNextLightColor
voidinitDarkColors
voidinitEnumMethods
voidinitLightColors
voidinitSystemDataTypes
voidinitSystemMethods
voidinitSystemPackages
booleanisEnumMethod
booleanisSystemDataType
booleanisSystemMethod
booleanisSystemPackage
>, 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=< + + // class descriptor header + + + // methods: + // getDot + + // getEmbeddedDot + + // getGraphId + + // isVisible + +
eu.svjatoslav.inspector.java.structure
GraphElement
StringgetDot
StringgetEmbeddedDot
StringgetGraphId
booleanisVisible
>, shape="none"]; + + // method references to other classes + +// Class: eu.svjatoslav.inspector.java.structure.ClassGraph + class_eu_svjatoslav_inspector_java_structure_ClassGraph[label=< + + // class descriptor header + + + // fields: + // filter + + // nameToClassMap + + + // methods: + // addClass + + // addObject + + // addProject + + // generateGraph + + // generateGraph + + // hideOrphanedClasses + + // render + +
eu.svjatoslav.inspector.java.structure
ClassGraph
Filterfilter
MapnameToClassMap
ClassDescriptoraddClass
ClassDescriptoraddObject
voidaddProject
voidgenerateGraph
voidgenerateGraph
voidhideOrphanedClasses
voidrender
>, 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=< + + // class descriptor header + + + // fields: + // MAX_REFERECNES_COUNT + + // classGraph + + // distinctiveReferenceColor + + // fullyQualifiedName + + // incomingReferencesCount + + // interfaceColor + + // interfaces + + // isArray + + // isEnum + + // isInterface + + // isShown + + // methods + + // nameToFieldMap + + // superClass + + // superClassColor + + + // methods: + // areReferencesShown + + // enlistFieldReferences + + // enlistFields + + // enlistImplementedInterfaces + + // enlistMethodReferences + + // enlistMethods + + // enlistSuperClass + + // generateDotHeader + + // getAllFields + + // getBackgroundColor + + // getBorderWidth + + // getClassName + + // getColor + + // getDistinctiveColor + + // getDot + + // getEmbeddedDot + + // getGraphId + + // getPackageName + + // getParentClassesName + + // hide + + // hideClassIfNoReferences + + // indexFields + + // isVisible + + // registerReference + + // setDistinctiveColor + +
eu.svjatoslav.inspector.java.structure
ClassDescriptor
intMAX_REFERECNES_COUNT
ClassGraphclassGraph
StringdistinctiveReferenceColor
StringfullyQualifiedName
intincomingReferencesCount
StringinterfaceColor
Listinterfaces
booleanisArray
booleanisEnum
booleanisInterface
booleanisShown
SortedSetmethods
MapnameToFieldMap
ClassDescriptorsuperClass
StringsuperClassColor
booleanareReferencesShown
voidenlistFieldReferences
voidenlistFields
voidenlistImplementedInterfaces
voidenlistMethodReferences
voidenlistMethods
voidenlistSuperClass
voidgenerateDotHeader
ListgetAllFields
StringgetBackgroundColor
StringgetBorderWidth
StringgetClassName
StringgetColor
StringgetDistinctiveColor
StringgetDot
StringgetEmbeddedDot
StringgetGraphId
StringgetPackageName
StringgetParentClassesName
voidhide
booleanhideClassIfNoReferences
voidindexFields
booleanisVisible
voidregisterReference
voidsetDistinctiveColor
>, 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"]; +} diff --git a/doc/JavaInspect.png b/doc/JavaInspect.png new file mode 100644 index 0000000..625a2da Binary files /dev/null and b/doc/JavaInspect.png differ diff --git a/doc/example-thumbnail.png b/doc/example-thumbnail.png new file mode 100644 index 0000000..04f4195 Binary files /dev/null and b/doc/example-thumbnail.png differ diff --git a/doc/example.png b/doc/example.png new file mode 100644 index 0000000..d773c31 Binary files /dev/null and b/doc/example.png differ diff --git a/doc/index.org b/doc/index.org new file mode 100644 index 0000000..145282f --- /dev/null +++ b/doc/index.org @@ -0,0 +1,237 @@ +#+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 + + ... + + eu.svjatoslav + javainspect + 1.7 + + ... + +#+END_SRC + + +Add Maven repository to retrieve artifact from: +#+BEGIN_SRC xml + + ... + + svjatoslav.eu + Svjatoslav repository + https://www3.svjatoslav.eu/maven/ + + ... + +#+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 diff --git a/doc/legend.png b/doc/legend.png new file mode 100644 index 0000000..e206b4f Binary files /dev/null and b/doc/legend.png differ diff --git a/doc/usage cli.org b/doc/usage cli.org new file mode 100644 index 0000000..d77bdef --- /dev/null +++ b/doc/usage cli.org @@ -0,0 +1,111 @@ +: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 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e27daad --- /dev/null +++ b/pom.xml @@ -0,0 +1,160 @@ + + 4.0.0 + eu.svjatoslav + javainspect + 1.8-SNAPSHOT + jar + JavaInspect + Utility to visualize Java code + + + 1.8 + 1.8 + 1.8 + UTF-8 + UTF-8 + + + + svjatoslav.eu + http://svjatoslav.eu + + + + javainspect + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + true + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + + + foo + bar + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.2 + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.9.4 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + package + + shade + + + + + eu.svjatoslav.inspector.java.commandline.Main + + + + + + + + + + + org.apache.maven.wagon + wagon-ssh-external + 2.6 + + + + + + + eu.svjatoslav + svjatoslavcommons + 1.9 + + + eu.svjatoslav + cli-helper + 1.0 + + + + + + svjatoslav.eu + svjatoslav.eu + scpexe://svjatoslav.eu:10006/srv/maven + + + svjatoslav.eu + svjatoslav.eu + scpexe://svjatoslav.eu:10006/srv/maven + + + + + + svjatoslav.eu + Svjatoslav repository + https://www3.svjatoslav.eu/maven/ + + + + + + scm:git:ssh://n0@svjatoslav.eu:10006/home/n0/git/javainspect.git + scm:git:ssh://n0@svjatoslav.eu:10006/home/n0/git/javainspect.git + HEAD + + + diff --git a/src/main/java/eu/svjatoslav/inspector/java/RenderJavaInspect.java b/src/main/java/eu/svjatoslav/inspector/java/RenderJavaInspect.java new file mode 100755 index 0000000..4d4f39c --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/RenderJavaInspect.java @@ -0,0 +1,67 @@ +/* + * 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. + * + *

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.

+ * + * @see ClassGraph + */ +public class RenderJavaInspect { + + /** + * Demonstrates generating a class graph from hand-picked classes. + * + *

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:

+ * + * + */ + 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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/commandline/CommandlineConfiguration.java b/src/main/java/eu/svjatoslav/inspector/java/commandline/CommandlineConfiguration.java new file mode 100644 index 0000000..c22f5a7 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/commandline/CommandlineConfiguration.java @@ -0,0 +1,196 @@ +/* + * 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. + * + *

This class parses and stores all command-line options for the + * JavaInspect tool, providing easy access to JAR files, graph settings, + * and filter patterns.

+ * + *

Supported options:

+ * + * + * @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. + * + *

This method creates a Parser and registers all parameters with + * their descriptions, default values, and aliases.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/commandline/Main.java b/src/main/java/eu/svjatoslav/inspector/java/commandline/Main.java new file mode 100644 index 0000000..ace1324 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/commandline/Main.java @@ -0,0 +1,262 @@ +/* + * 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. + * + *

This class provides the main method for running JavaInspect from the + * command line, parsing arguments and generating class visualizations.

+ * + *

Usage:

+ *
+ * java -jar javainspect.jar -j myapp.jar -n MyAppGraph
+ * 
+ * + *

Common options:

+ * + * + * @see CommandlineConfiguration + * @see ClassGraph + */ +public class Main { + + /** + * Main entry point for the command-line interface. + * + *

Parses command-line arguments, builds the class graph from JAR files, + * and generates the visualization.

+ * + * @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. + * + *

This method:

+ *
    + *
  1. Creates a URLClassLoader from the JAR files and classpath
  2. + *
  3. Creates a ClassGraph and configures output options
  4. + *
  5. Loads either specified root classes or all classes from JARs
  6. + *
  7. Applies whitelist/blacklist filters
  8. + *
  9. Hides orphaned classes if requested
  10. + *
+ * + * @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 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 jarFiles, List classpathDirectories) { + List 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. + * + *

Iterates through all .class entries in the JAR and attempts to + * load each one into the graph.

+ * + * @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. + * + *

This method gracefully handles NoClassDefFoundError, which can occur + * when a class references other classes not present in the JAR.

+ * + * @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. + * + *

Scans the JAR for .class entries and converts their paths to + * fully qualified class names.

+ * + * @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 getClassNamesFromJar(File jarFile) throws IOException { + List 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. + * + *

Converts path separators (/) to package separators (.) and + * removes the .class extension.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/commandline/TargetImageTypeParameter.java b/src/main/java/eu/svjatoslav/inspector/java/commandline/TargetImageTypeParameter.java new file mode 100644 index 0000000..917be06 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/commandline/TargetImageTypeParameter.java @@ -0,0 +1,84 @@ +/* + * 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. + * + *

This parameter accepts "svg" or "png" values and converts them + * to the corresponding {@link TargetImageType} enum values.

+ * + * @see TargetImageType + * @see CommandlineConfiguration + */ +public class TargetImageTypeParameter extends Parameter { + + /** + * Creates a target image type parameter with default SVG format. + * + *

The parameter accepts a single argument value: either "svg" or "png".

+ */ + public TargetImageTypeParameter(){ + super("Target image type. Default is: svg.", SINGLE); + } + + /** + * Describes the valid format options for this parameter. + * + *

Returns a comma-separated list of available image types.

+ * + * @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. + * + *

Converts the argument to uppercase and looks up the corresponding + * enum constant.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/commandline/package-info.java b/src/main/java/eu/svjatoslav/inspector/java/commandline/package-info.java new file mode 100644 index 0000000..a3a051c --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/commandline/package-info.java @@ -0,0 +1,15 @@ +/* + * JavaInspect - Utility to visualize java software. + * This project is released under Creative Commons Zero (CC0) license. + */ + +/** + * Provides the command-line interface for JavaInspect. + * + *

This package contains classes for parsing command-line arguments + * and launching the class graph generation process.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/ClassDescriptor.java b/src/main/java/eu/svjatoslav/inspector/java/structure/ClassDescriptor.java new file mode 100755 index 0000000..7e40e89 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/ClassDescriptor.java @@ -0,0 +1,836 @@ +/* + * 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. + * + *

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.

+ * + *

Classes are analyzed using Java reflection to extract:

+ *
    + *
  • Superclass relationships
  • + *
  • Implemented interfaces
  • + *
  • Declared fields and their types
  • + *
  • Methods and their return types
  • + *
  • Generic type information
  • + *
+ * + * @see ClassGraph + * @see FieldDescriptor + * @see MethodDescriptor + * @see GraphElement + */ +public class ClassDescriptor implements GraphElement, Comparable { + + /** + * Maximum number of incoming references to show for a class. + * + *

Classes with more than this number of references are drawn with + * thicker borders to indicate high coupling.

+ */ + private static final int MAX_REFERECNES_COUNT = 10; + + /** + * List of interfaces implemented by this class. + */ + final List interfaces = new ArrayList<>(); + + /** + * Map of field names to their descriptors. + */ + private final Map nameToFieldMap = new TreeMap<>(); + + /** + * Sorted set of method descriptors. + */ + private final SortedSet 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. + * + *

Assigned from the dark color palette to distinguish this class's + * incoming edges from others.

+ */ + 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. + * + *

Classes can be hidden if they are system types, filtered by glob + * patterns, or have no interesting references.

+ */ + private boolean isShown = true; + + /** + * Count of field and method references pointing to this class. + * + *

Used to determine if the class should show its reference arrows.

+ */ + private int referencesCount = 0; + + /** + * Count of implementations for interface classes. + * + *

Only relevant for interfaces; counts how many classes implement this interface.

+ */ + private int implementationsCount = 0; + + /** + * Count of extensions for this class. + * + *

Counts how many classes extend this class as their superclass.

+ */ + private int extensionsCount = 0; + + /** + * The component type for array classes. + * + *

For example, for String[] this would be the String descriptor.

+ */ + 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. + * + *

This method extracts the class name, type information (enum, interface, array), + * declared fields, methods, superclass, and implemented interfaces.

+ * + * @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. + * + *

Classes with too many references are visually cluttered if all arrows + * are shown, so this method returns false above the threshold.

+ * + * @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 entry : nameToFieldMap + .entrySet()) + result.append(entry.getValue().getEmbeddedDot()); + } + + /** + * Appends DOT format edges for implemented interface relationships. + * + *

Interface relationships are shown as dotted forward arrows.

+ * + * @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. + * + *

Superclass arrows point from parent to child with thick styling.

+ * + * @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. + * + *

This creates the HTML table structure with package name, parent class + * name (for nested classes), and the class name itself.

+ * + * @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=<\n"); + + result.append("\n"); + result.append(" // class descriptor header\n"); + result.append(" \n"); + } + + /** + * Returns the HTML background color attribute for this class. + * + *

Enums have navajowhite2, interfaces have darkslategray1, + * regular classes have no background color.

+ * + * @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. + * + *

Classes with many references have thicker borders (4) to indicate + * high coupling, normal classes have thin borders (1).

+ * + * @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. + * + *

For nested classes, dollar signs are replaced with dots. + * For array classes, the component type name is returned instead.

+ * + * @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. + * + *

Colors are assigned from the dark palette on first access.

+ * + * @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. + * + *

This creates the class node (HTML table) and all outgoing edges + * for fields, methods, interfaces, and superclass.

+ * + * @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("
" + + "" + getPackageName() + + "
"); + + final String parentClassesName = getParentClassesName(); + if (parentClassesName.length() > 0) + result.append("" + parentClassesName + + "
\n"); + + result.append("" + getClassName(false) + + "" + "
>, shape=\"none\"];\n"); + + enlistFieldReferences(result); + + enlistMethodReferences(result); + + enlistImplementedInterfaces(result); + + enlistSuperClass(result); + + return result.toString(); + } + + /** + * Returns null as classes are not embedded within other nodes. + * + *

This method is not applicable for class-level descriptors.

+ * + * @return null + */ + @Override + public String getEmbeddedDot() { + return null; + } + + /** + * Returns a field matching the given name, ignoring case. + * + *

Used to check if getter/setter methods correspond to existing fields.

+ * + * @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. + * + *

The ID is derived from the fully qualified name, with dots and + * special characters replaced with underscores.

+ * + * @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. + * + *

If a descriptor for the field already exists, it is returned. + * Otherwise, a new descriptor is created, added to the map, and analyzed.

+ * + * @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. + * + *

Includes method return types, field types, implemented interfaces, + * and superclass.

+ * + * @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. + * + *

For nested classes like Outer.Inner, this returns "Outer".

+ * + * @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. + * + *

Orphaned classes with no connections add visual noise without + * providing useful information about relationships.

+ */ + 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. + * + *

A class is hidden if it is:

+ *
    + *
  • A primitive or system data type
  • + *
  • In a system package (java.*, javax.*, sun.*)
  • + *
  • Filtered by blacklist/whitelist glob patterns
  • + *
  • An array of primitives
  • + *
  • Explicitly marked as hidden
  • + *
+ * + * @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. + * + *

Equality is based on fully qualified name.

+ * + * @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. + * + *

Classes are sorted by fully qualified name.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/ClassGraph.java b/src/main/java/eu/svjatoslav/inspector/java/structure/ClassGraph.java new file mode 100755 index 0000000..8d2a759 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/ClassGraph.java @@ -0,0 +1,333 @@ +/* + * 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. + * + *

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.

+ * + *

Typical usage:

+ *
+ * ClassGraph graph = new ClassGraph();
+ * graph.add(myObject);
+ * graph.add(MyClass.class);
+ * graph.setKeepDotFile(true);
+ * graph.generateGraph("MyVisualization");
+ * 
+ * + *

The graph can be configured with:

+ *
    + *
  • Target directory for output files
  • + *
  • Image format (SVG or PNG)
  • + *
  • Whitelist/blacklist glob patterns for filtering classes
  • + *
  • Option to hide orphaned classes with no connections
  • + *
+ * + * @see ClassDescriptor + * @see TargetImageType + */ +public class ClassGraph { + + /** + * Maps fully qualified class names to their descriptors. + * + *

This is the primary storage for all analyzed classes in the graph.

+ */ + private final Map fullyQualifiedNameToClassMap = new HashMap<>(); + + /** + * List of glob patterns for classes to exclude from the graph. + */ + private final List blacklistClassGlobs = new ArrayList<>(); + + /** + * List of glob patterns for classes to include in the graph. + * + *

If non-empty, only classes matching a whitelist pattern are shown.

+ */ + private final List whitelistClassGlobs = new ArrayList<>(); + + /** + * The output image format for generated graphs. + * + *

SVG is recommended for large diagrams; PNG for small ones.

+ */ + TargetImageType targetImageType = TargetImageType.SVG; + + /** + * Whether to keep the intermediate DOT file after rendering. + */ + private boolean keepDotFile; + + /** + * Directory where output files will be written. + * + *

Defaults to the current working directory.

+ */ + 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. + * + *

Each object's class is analyzed and added to the graph. + * If a Class object is passed, the class itself is added.

+ * + * @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. + * + *

If the object is a Class instance, that class is added directly. + * Otherwise, the object's runtime class is added.

+ * + * @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. + * + *

Classes matching any blacklist pattern are excluded from the graph. + * Glob patterns support wildcards: * matches any sequence, ? matches single characters.

+ * + *

Example patterns:

+ *
    + *
  • "java.*" - excludes all java standard library
  • + *
  • "*Test" - excludes all test classes
  • + *
+ * + * @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. + * + *

This method:

+ *
    + *
  1. Writes the DOT file to the target directory
  2. + *
  3. Executes GraphViz (dot command) to render the image
  4. + *
  5. Deletes the DOT file unless keepDotFile is true
  6. + *
+ * + *

The GraphViz 'dot' executable must be available on the system PATH.

+ * + * @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. + * + *

This creates a digraph with all class nodes and their relationships.

+ * + * @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 entry : fullyQualifiedNameToClassMap + .entrySet()) + result.append(entry.getValue().getDot()); + + result.append("}\n"); + + return result.toString(); + } + + /** + * Creates or retrieves a class descriptor for a Java class. + * + *

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.

+ * + * @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. + * + *

Orphaned classes add visual clutter without showing meaningful + * relationships. This method removes them from the graph output.

+ * + * @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. + * + *

A class is hidden if it matches any blacklist pattern, or if + * a whitelist exists and it doesn't match any whitelist pattern.

+ * + * @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. + * + *

Keeping the DOT file is useful for debugging or manual customization.

+ * + * @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. + * + *

If any whitelist patterns are defined, only classes matching at least + * one pattern will be shown in the graph.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/FieldDescriptor.java b/src/main/java/eu/svjatoslav/inspector/java/structure/FieldDescriptor.java new file mode 100755 index 0000000..dc1d20e --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/FieldDescriptor.java @@ -0,0 +1,249 @@ +/* + * 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. + * + *

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.

+ * + * @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. + * + *

For example, a field of type {@code List} would have + * String as a type argument.

+ */ + private final List 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. + * + *

Inherited fields are typically hidden in visualizations.

+ */ + 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. + * + *

This method extracts the field name, type, and generic type arguments, + * registering references with the class graph for later visualization.

+ * + * @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. + * + *

This creates the arrows pointing from the field to its type and + * generic type arguments in the class diagram.

+ * + * @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. + * + *

This creates a table row showing the field type and name inside + * the class box in the diagram.

+ * + * @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(" "); + result.append(name + "\n"); + } else { + result.append(" "); + result.append(""); + result.append(describeType() + ""); + result.append(""); + result.append(name + "\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. + * + *

The ID combines the parent class ID with the field name, + * allowing precise edge targeting in the diagram.

+ * + * @return unique identifier in format "classId:fieldName" + */ + @Override + public String getGraphId() { + return parentClassDescriptor.getGraphId() + ":" + name; + } + + /** + * Counts visible external references from this field. + * + *

This is used to determine whether a class has enough connections + * to be worth displaying.

+ * + * @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. + * + *

Fields are hidden if they are inherited, contain dollar signs + * (compiler-generated), or are serialVersionUID constants.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/GraphElement.java b/src/main/java/eu/svjatoslav/inspector/java/structure/GraphElement.java new file mode 100755 index 0000000..b5873d4 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/GraphElement.java @@ -0,0 +1,59 @@ +/* + * 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. + * + *

All graph elements (classes, fields, methods) implement this interface + * to provide DOT format output for visualization.

+ * + * @see ClassDescriptor + * @see FieldDescriptor + * @see MethodDescriptor + */ +public interface GraphElement { + + /** + * Generates the DOT format representation for external references. + * + *

This method produces the edges (arrows) connecting this element + * to other elements in the graph.

+ * + * @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. + * + *

This method produces the HTML-like table cells that appear inside + * the parent class node in the graph.

+ * + * @return DOT format string for embedded content, or empty string if not visible + */ + String getEmbeddedDot(); + + /** + * Returns the unique GraphViz identifier for this element. + * + *

The ID is used to reference this element in edges and node definitions.

+ * + * @return unique identifier string in GraphViz format + */ + String getGraphId(); + + /** + * Determines whether this element should be included in the graph output. + * + *

Visibility is controlled by various factors including system package + * filtering, inheritance status, and reference count thresholds.

+ * + * @return true if the element should be rendered, false otherwise + */ + boolean isVisible(); + +} \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/MethodDescriptor.java b/src/main/java/eu/svjatoslav/inspector/java/structure/MethodDescriptor.java new file mode 100755 index 0000000..0324469 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/MethodDescriptor.java @@ -0,0 +1,319 @@ +/* + * 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. + * + *

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.

+ * + * @see ClassDescriptor + * @see GraphElement + */ +public class MethodDescriptor implements GraphElement, + Comparable { + + /** + * 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. + * + *

For methods returning parameterized types like {@code List}, + * this captures the generic type arguments.

+ */ + private final List 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. + * + *

Inherited methods are typically hidden in visualizations to reduce noise.

+ */ + 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. + * + *

This method extracts the return type and generic type information, + * registering references with the class graph for later visualization.

+ * + * @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. + * + *

Equality is based on method name, parent class, and argument types.

+ * + * @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. + * + *

This creates dotted arrows pointing from the method to its return type + * and generic type arguments in the class diagram.

+ * + * @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. + * + *

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.

+ * + * @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(" " + + "" + describeReturnType() + + "" + "" + + getMethodLabel() + "\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. + * + *

The ID combines the parent class ID with the method name.

+ * + * @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. + * + *

This counts both the return type and any generic type arguments + * that are visible in the graph.

+ * + * @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. + * + *

Methods are hidden if they are:

+ *
    + *
  • Inherited from parent classes
  • + *
  • Common Object methods (wait, equals, toString, etc.)
  • + *
  • Enum methods (values, valueOf, etc.) for enum classes
  • + *
  • Getter/setter methods that correspond to existing fields
  • + *
+ * + * @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. + * + *

Methods are sorted primarily by name, then by parent class.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/TargetImageType.java b/src/main/java/eu/svjatoslav/inspector/java/structure/TargetImageType.java new file mode 100644 index 0000000..80e7fc9 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/TargetImageType.java @@ -0,0 +1,48 @@ +/* + * 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. + * + *

JavaInspect uses GraphViz to generate visual representations of + * class relationships. This enum defines the available output formats.

+ * + * @see ClassGraph#setTargetImageType(TargetImageType) + */ +public enum TargetImageType { + + /** + * Portable Network Graphics format. + * + *

A raster image format suitable for embedding in documents + * and web pages where scalability is not required.

+ */ + PNG("png"), + + /** + * Scalable Vector Graphics format. + * + *

A vector image format that scales infinitely without quality loss, + * ideal for large class diagrams and high-resolution displays.

+ */ + 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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/Utils.java b/src/main/java/eu/svjatoslav/inspector/java/structure/Utils.java new file mode 100755 index 0000000..0419aa8 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/Utils.java @@ -0,0 +1,251 @@ +/* + * 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. + * + *

This class maintains static collections of system data types, common methods, + * system packages, and color palettes used throughout the visualization process.

+ * + *

All methods are static and the class is not intended to be instantiated.

+ * + * @see ClassDescriptor + * @see MethodDescriptor + */ +public class Utils { + + /** + * List of primitive and system data type names to exclude from visualization. + */ + private static final List systemDataTypes = new ArrayList<>(); + + /** + * List of common {@link Object} method names to hide in method listings. + */ + private static final List commonObjectMethods = new ArrayList<>(); + + /** + * List of system package prefixes to exclude from visualization. + */ + private static final List systemPackages = new ArrayList<>(); + + /** + * Palette of dark colors for reference edges (incoming arrows). + */ + private static final List darkColors = new ArrayList<>(); + + /** + * Palette of light colors for inheritance edges (superclass arrows). + */ + private static final List lightColors = new ArrayList<>(); + + /** + * List of enum-specific method names to hide for enum classes. + */ + private static final List 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. + * + *

Colors are selected cyclically, wrapping around to the first color + * after the last one has been used.

+ * + * @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. + * + *

Colors are selected cyclically, wrapping around to the first color + * after the last one has been used.

+ * + * @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. + * + *

These methods (wait, equals, toString, etc.) are inherited by all + * objects and typically add noise to class diagrams.

+ */ + 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. + * + *

Dark colors are used for incoming reference arrows to distinguish + * them from inheritance relationships.

+ */ + 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. + * + *

These methods (values, valueOf, name, etc.) are present in all + * enum classes and are typically not relevant for visualization.

+ */ + 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. + * + *

Light colors are used for superclass arrows to visually distinguish + * inheritance relationships from field/method references.

+ */ + 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. + * + *

These types (int, long, boolean, etc.) are fundamental and do not + * represent interesting relationships in class diagrams.

+ */ + 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. + * + *

Classes from these packages (java.*, javax.*, sun.*) are standard + * library classes and typically excluded from application diagrams.

+ */ + 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. + * + *

System packages include java.*, javax.*, and sun.* prefixes.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/inspector/java/structure/package-info.java b/src/main/java/eu/svjatoslav/inspector/java/structure/package-info.java new file mode 100755 index 0000000..6eab749 --- /dev/null +++ b/src/main/java/eu/svjatoslav/inspector/java/structure/package-info.java @@ -0,0 +1,18 @@ +/* + * 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. + * + *

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.

+ * + * @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 diff --git a/usage examples/demo project/.gitignore b/usage examples/demo project/.gitignore new file mode 100644 index 0000000..49ea519 --- /dev/null +++ b/usage examples/demo project/.gitignore @@ -0,0 +1,4 @@ +/.idea/ +/target/ +/example.svg +/*.iml \ No newline at end of file diff --git a/usage examples/demo project/pom.xml b/usage examples/demo project/pom.xml new file mode 100644 index 0000000..6a16ce8 --- /dev/null +++ b/usage examples/demo project/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + eu.svjatoslav + javainspect-demo + 1.0-SNAPSHOT + jar + JavaInspect demo + Demonstration project for JavaInspect utility + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.8 + 1.8 + UTF-8 + + + + + + + + eu.svjatoslav + javainspect + 1.7-SNAPSHOT + + + + + + svjatoslav.eu + Svjatoslav repository + http://www2.svjatoslav.eu/maven/ + + + diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/RenderUsingReflection.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/RenderUsingReflection.java new file mode 100755 index 0000000..d471429 --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/RenderUsingReflection.java @@ -0,0 +1,27 @@ +/* + * 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"); + } + +} diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/ObjectReturnedByMethod.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/ObjectReturnedByMethod.java new file mode 100755 index 0000000..143ab77 --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/ObjectReturnedByMethod.java @@ -0,0 +1,5 @@ +package eu.svjatoslav.inspector.java.structure.example.torender; + +public class ObjectReturnedByMethod { + +} diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/ObjectVisibleAsClassField.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/ObjectVisibleAsClassField.java new file mode 100755 index 0000000..db5b5cc --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/ObjectVisibleAsClassField.java @@ -0,0 +1,5 @@ +package eu.svjatoslav.inspector.java.structure.example.torender; + +public class ObjectVisibleAsClassField { + +} diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleClass.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleClass.java new file mode 100755 index 0000000..9720c44 --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleClass.java @@ -0,0 +1,11 @@ +package eu.svjatoslav.inspector.java.structure.example.torender; + +public class SampleClass extends SampleSuperClass { + + ObjectVisibleAsClassField sampleClassField; + + public ObjectReturnedByMethod sampleMethod() { + return new ObjectReturnedByMethod(); + } + +} diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleClass2.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleClass2.java new file mode 100644 index 0000000..a2eba71 --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleClass2.java @@ -0,0 +1,5 @@ +package eu.svjatoslav.inspector.java.structure.example.torender; + +public class SampleClass2 extends SampleSuperClass { + +} diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleEnum.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleEnum.java new file mode 100755 index 0000000..ee8bf9e --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleEnum.java @@ -0,0 +1,7 @@ +package eu.svjatoslav.inspector.java.structure.example.torender; + +public enum SampleEnum { + + ONE, TWO, THREE, FOUR + +} diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleInterface.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleInterface.java new file mode 100755 index 0000000..648327f --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleInterface.java @@ -0,0 +1,5 @@ +package eu.svjatoslav.inspector.java.structure.example.torender; + +public interface SampleInterface { + SampleEnum getSomeValue(); +} diff --git a/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleSuperClass.java b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleSuperClass.java new file mode 100755 index 0000000..9230509 --- /dev/null +++ b/usage examples/demo project/src/main/java/eu/svjatoslav/inspector/java/structure/example/torender/SampleSuperClass.java @@ -0,0 +1,10 @@ +package eu.svjatoslav.inspector.java.structure.example.torender; + +public class SampleSuperClass implements SampleInterface { + + @Override + public SampleEnum getSomeValue() { + return SampleEnum.ONE; + } + +} diff --git a/usage examples/demo project/tools/open with IntelliJ IDEA b/usage examples/demo project/tools/open with IntelliJ IDEA new file mode 100755 index 0000000..1f82875 --- /dev/null +++ b/usage examples/demo project/tools/open with IntelliJ IDEA @@ -0,0 +1,6 @@ +#!/bin/bash + +cd "${0%/*}" + +cd .. +idea .