package com.citi.get.cet.comet.tools;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.*;
import org.springframework.context.*;
import org.springframework.context.SmartLifecycle;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import static org.apache.commons.lang.StringUtils.trim;
public class SpringVisualizer implements ApplicationContextAware, SmartLifecycle {
private ConfigurableApplicationContext startContext;
private boolean isRunning = false;
// ===== TUNING =====
private static final int MAX_EDGES = 1000;
private static final int MAX_ROWS = 6;
private static final int MAX_STRING = 40;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.startContext = (ConfigurableApplicationContext) applicationContext;
}
@Override
public void start() {
StringBuilder dot = new StringBuilder("digraph G {\n");
// ===== VERTICAL LAYOUT =====
dot.append("rankdir=TB;\n");
dot.append("nodesep=0.4;\n");
dot.append("ranksep=1.2;\n");
dot.append("node [shape=box, fontname=\"Arial\", fontsize=10];\n");
dot.append("edge [dir=forward];\n\n");
Set<String> processed = new HashSet<>();
List<String> isolated = new ArrayList<>();
ApplicationContext current = this.startContext;
int edgeCount = 0;
while (current != null) {
ConfigurableListableBeanFactory factory =
((ConfigurableApplicationContext) current).getBeanFactory();
for (String name : factory.getBeanDefinitionNames()) {
if (!processed.add(name)) continue;
boolean hasDeps = factory.getDependenciesForBean(name).length > 0;
boolean hasDependents = factory.getDependentBeans(name).length > 0;
if (!hasDeps && !hasDependents) {
isolated.add(name);
}
renderNode(dot, name, factory.getBeanDefinition(name));
// ===== EDGES =====
for (String dep : factory.getDependenciesForBean(name)) {
if (edgeCount++ > MAX_EDGES) break;
dot.append("\"").append(name).append("\" -> \"")
.append(dep).append("\";\n");
}
}
current = current.getParent();
}
// ===== FORCE VERTICAL STACK FOR DISCONNECTED =====
for (int i = 0; i < isolated.size() - 1; i++) {
dot.append("\"").append(isolated.get(i)).append("\" -> \"")
.append(isolated.get(i + 1))
.append("\" [style=invis];\n");
}
dot.append("}\n");
writeToFile(dot.toString());
this.isRunning = true;
}
// ===== NODE RENDERING =====
private void renderNode(StringBuilder dot, String name, BeanDefinition def) {
StringBuilder label = new StringBuilder();
// Header (bean name)
label.append(shorten(name));
int rowCount = 0;
// Properties
for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
if (rowCount++ >= MAX_ROWS) {
label.append("\\n...");
break;
}
label.append("\\n")
.append(shorten(pv.getName()))
.append("=")
.append(shorten(extractValue(pv.getValue())));
}
// Constructor args
ConstructorArgumentValues cav = def.getConstructorArgumentValues();
for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> e :
cav.getIndexedArgumentValues().entrySet()) {
if (rowCount++ >= MAX_ROWS) {
label.append("\\n...");
break;
}
label.append("\\narg[")
.append(e.getKey())
.append("]=")
.append(shorten(extractValue(e.getValue().getValue())));
}
dot.append("\"").append(name).append("\" ")
.append("[label=\"")
.append(escape(label.toString()))
.append("\", style=filled, fillcolor=\"")
.append(getColor(name))
.append("\"];\n");
}
// ===== VALUE EXTRACTION =====
private String extractValue(Object value) {
if (value == null) return "null";
if (value instanceof TypedStringValue) {
return ((TypedStringValue) value).getValue();
}
if (value instanceof BeanReference) {
return "@" + ((BeanReference) value).getBeanName();
}
if (value instanceof BeanDefinition) {
String cls = ((BeanDefinition) value).getBeanClassName();
return cls != null
? cls.substring(cls.lastIndexOf('.') + 1)
: "InnerBean";
}
if (value instanceof Iterable) {
List<String> out = new ArrayList<>();
int i = 0;
for (Object o : (Iterable<?>) value) {
if (i++ > 3) { out.add("..."); break; }
out.add(shorten(extractValue(o)));
}
return String.join(",", out);
}
return value.toString();
}
// ===== STRING SHORTENING =====
private String shorten(String s) {
if (s == null) return "";
s = trim(s);
if (s.length() > MAX_STRING) {
return s.substring(0, MAX_STRING - 3) + "...";
}
return s;
}
// ===== COLOR =====
private String getColor(String name) {
String lower = name.toLowerCase();
if (lower.contains("comet")) return "#FFF904";
if (lower.contains("citi")) return "#CBE6C7";
return "#E1F5FE";
}
// ===== ESCAPE FOR DOT =====
private String escape(String s) {
return s.replace("\"", "'")
.replace("\n", "\\n");
}
// ===== FILE OUTPUT =====
private void writeToFile(String content) {
try {
File root = findProjectRoot();
File buildDir = new File(root, "build");
buildDir.mkdirs();
File file = new File(buildDir, "spring-beans.dot");
try (FileWriter writer = new FileWriter(file)) {
writer.write(content);
}
System.out.println("\n==== GRAPH GENERATED ====");
System.out.println(file.toURI());
System.out.println("========================\n");
} catch (IOException e) {
System.err.println("Failed to write DOT file: " + e.getMessage());
}
}
private File findProjectRoot() {
File current = new File(System.getProperty("user.dir"));
while (current != null) {
if (new File(current, "build.gradle").exists() ||
new File(current, "settings.gradle").exists()) {
return current;
}
current = current.getParentFile();
}
return new File(System.getProperty("user.dir"));
}
@Override public int getPhase() { return Integer.MAX_VALUE; }
@Override public boolean isAutoStartup() { return true; }
@Override public void stop() { isRunning = false; }
@Override public boolean isRunning() { return isRunning; }
@Override public void stop(Runnable callback) { stop(); callback.run(); }
}
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)