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;
// ===== SAFETY SWITCHES =====
private static final boolean LIGHT_MODE = true; // 🔥 set false for full detail
private static final int MAX_EDGES = 800;
private static final int MAX_ROWS = 6;
private static final int MAX_STRING = 20;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.startContext = (ConfigurableApplicationContext) applicationContext;
}
@Override
public void start() {
StringBuilder dot = new StringBuilder("digraph G {\n");
// ===== SAFE LAYOUT =====
dot.append(" rankdir=TB;\n");
dot.append(" nodesep=0.4;\n");
dot.append(" ranksep=1.0;\n");
dot.append(" node [fontname=\"Arial\", fontsize=10];\n");
dot.append(" edge [dir=forward];\n\n");
if (LIGHT_MODE) {
dot.append(" node [shape=box];\n");
} else {
dot.append(" node [shape=plaintext];\n");
}
Set<String> processed = new HashSet<>();
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;
if (LIGHT_MODE) {
renderSimpleNode(dot, name);
} else {
renderFullNode(dot, name, factory.getBeanDefinition(name));
}
// ===== SAFE EDGE LIMIT =====
for (String dep : factory.getDependenciesForBean(name)) {
if (edgeCount++ > MAX_EDGES) break;
dot.append("\"").append(name).append("\" -> \"")
.append(dep).append("\";\n");
}
}
current = current.getParent();
}
dot.append("}\n");
writeToFile(dot.toString());
this.isRunning = true;
}
// ===== SIMPLE MODE (FAST + SAFE) =====
private void renderSimpleNode(StringBuilder dot, String name) {
String color = getColor(name);
dot.append("\"").append(name).append("\" ")
.append("[style=filled, fillcolor=\"").append(color).append("\"];\n");
}
// ===== FULL MODE (HTML TABLE, LIMITED) =====
private void renderFullNode(StringBuilder dot, String name, BeanDefinition def) {
String color = getColor(name);
dot.append("\"").append(name).append("\" [label=<");
dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\" BGCOLOR=\"")
.append(color).append("\">");
// header
dot.append("<TR><TD COLSPAN=\"2\" BGCOLOR=\"#DDDDDD\"><B>")
.append(xmlEscape(name))
.append("</B></TD></TR>");
int rowCount = 0;
// properties
for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
if (rowCount++ > MAX_ROWS) {
addEllipsis(dot);
break;
}
dot.append("<TR>");
dot.append("<TD>").append(xmlEscape(pv.getName())).append("</TD>");
dot.append("<TD>").append(extractValue(pv.getValue())).append("</TD>");
dot.append("</TR>");
}
// constructor args
ConstructorArgumentValues cav = def.getConstructorArgumentValues();
for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> e :
cav.getIndexedArgumentValues().entrySet()) {
if (rowCount++ > MAX_ROWS) {
addEllipsis(dot);
break;
}
dot.append("<TR>");
dot.append("<TD><I>arg[").append(e.getKey()).append("]</I></TD>");
dot.append("<TD>").append(extractValue(e.getValue().getValue())).append("</TD>");
dot.append("</TR>");
}
dot.append("</TABLE>>];\n");
}
private void addEllipsis(StringBuilder dot) {
dot.append("<TR><TD COLSPAN=\"2\">...</TD></TR>");
}
private String extractValue(Object value) {
if (value == null) return "null";
if (value instanceof TypedStringValue) {
return shorten(((TypedStringValue) value).getValue());
}
if (value instanceof BeanReference) {
return "@" + ((BeanReference) value).getBeanName();
}
if (value instanceof BeanDefinition) {
String cls = ((BeanDefinition) value).getBeanClassName();
return cls != null
? "<i>" + cls.substring(cls.lastIndexOf('.') + 1) + "</i>"
: "<i>InnerBean</i>";
}
if (value instanceof Iterable) {
List<String> out = new ArrayList<>();
int i = 0;
for (Object o : (Iterable<?>) value) {
if (i++ > 5) { out.add("..."); break; }
out.add(extractValue(o));
}
return String.join("<BR/>", out);
}
return shorten(value.toString());
}
private String shorten(String s) {
if (s == null) return "";
s = trim(s);
if (s.length() > MAX_STRING) {
return xmlEscape(s.substring(0, MAX_STRING) + "...");
}
return xmlEscape(s);
}
private String getColor(String name) {
String lower = name.toLowerCase();
if (lower.contains("comet")) return "#FFF904";
if (lower.contains("citi")) return "#CBE6C7";
return "#E1F5FE";
}
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"));
}
private String xmlEscape(String input) {
if (input == null) return "";
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">");
}
@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)