DEV Community

Query Filter
Query Filter

Posted on

bridge96

package com.yourpackage;

import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.SmartLifecycle;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

public class SpringVisualizer implements ApplicationContextAware, SmartLifecycle {

    private ConfigurableApplicationContext startContext;
    private boolean isRunning = false;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.startContext = (ConfigurableApplicationContext) applicationContext;
    }

    @Override
    public void start() {
        StringBuilder dot = new StringBuilder("digraph G {\n");
        // Use single brackets [label=<...>] for HTML rendering
        dot.append("  rankdir=LR; node [shape=plain, fontname=\"Arial\", fontsize=10];\n\n");

        Set<String> processed = new HashSet<>();
        ApplicationContext current = this.startContext;

        while (current != null) {
            ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) current).getBeanFactory();
            for (String name : factory.getBeanDefinitionNames()) {
                if (processed.add(name)) {
                    BeanDefinition def = factory.getBeanDefinition(name);

                    // Logic to color-code based on your project segments
                    String color = name.toLowerCase().contains("comet") ? "#FFF9C4" : 
                                   (name.toLowerCase().contains("cpls") ? "#C8E6C9" : "#E1F5FE");

                    dot.append(String.format("  \"%s\" [label=<", name));
                    dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"").append(color).append("\">");
                    dot.append("<TR><TD COLSPAN=\"2\"><B>").append(xmlEscape(name)).append("</B></TD></TR>");

                    // 1. Process Properties (Setter injection)
                    for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
                        dot.append("<TR><TD ALIGN=\"LEFT\">").append(xmlEscape(pv.getName())).append("</TD>");
                        dot.append("<TD ALIGN=\"LEFT\">").append(extractValue(pv.getValue())).append("</TD></TR>");
                    }

                    // 2. Process Constructor Arguments (Fix for eodMarkerCache and Lists)
                    ConstructorArgumentValues cav = def.getConstructorArgumentValues();

                    // Handle Indexed (0, 1, 2...)
                    for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cav.getIndexedArgumentValues().entrySet()) {
                        dot.append("<TR><TD ALIGN=\"LEFT\"><I>arg[").append(entry.getKey()).append("]</I></TD>");
                        dot.append("<TD ALIGN=\"LEFT\">").append(extractValue(entry.getValue().getValue())).append("</TD></TR>");
                    }

                    // Handle Generic/Typed (This expands eodMarkerCache)
                    for (ConstructorArgumentValues.ValueHolder vh : cav.getGenericArgumentValues()) {
                        String type = vh.getType() != null ? vh.getType().substring(vh.getType().lastIndexOf('.') + 1) : "gen";
                        dot.append("<TR><TD ALIGN=\"LEFT\"><I>arg:").append(type).append("</I></TD>");
                        dot.append("<TD ALIGN=\"LEFT\">").append(extractValue(vh.getValue())).append("</TD></TR>");
                    }

                    dot.append("</TABLE>>];\n");

                    // 3. Draw Dependency Arrows
                    for (String dep : factory.getDependenciesForBean(name)) {
                        dot.append(String.format("  \"%s\" -> \"%s\";\n", name, dep));
                    }
                }
            }
            current = current.getParent();
        }
        dot.append("}\n");

        writeToFile(dot.toString());
        this.isRunning = true;
    }

    private String extractValue(Object value) {
        if (value == null) return "null";

        // Show references as @name
        if (value instanceof BeanReference) {
            return "@" + ((BeanReference) value).getBeanName();
        }

        // Clean up Inner Bean noise by showing only the class name
        if (value instanceof BeanDefinition) {
            String className = ((BeanDefinition) value).getBeanClassName();
            return className != null ? "<i>" + className.substring(className.lastIndexOf('.') + 1) + "</i>" : "InnerBean";
        }

        if (value instanceof TypedStringValue) {
            return xmlEscape(((TypedStringValue) value).getValue());
        }

        // Format lists vertically with BR tags
        if (value instanceof List) {
            List<String> cleaned = new ArrayList<>();
            int limit = 0;
            for (Object item : (List<?>) value) {
                if (limit++ > 12) { cleaned.add("..."); break; }
                cleaned.add(extractValue(item));
            }
            return String.join("<BR ALIGN=\"LEFT\"/>", cleaned);
        }

        // Standard string cleaning
        String s = value.toString();
        return xmlEscape(s.length() > 60 ? s.substring(0, 57) + "..." : s);
    }

    private void writeToFile(String content) {
        try {
            File buildDir = new File("build");
            if (!buildDir.exists()) buildDir.mkdirs();
            File file = new File(buildDir, "spring-beans.dot");
            try (FileWriter writer = new FileWriter(file)) {
                writer.write(content);
            }
            System.out.println("\n>>> GRAPH WRITTEN TO: " + file.getAbsolutePath());
        } catch (IOException e) {
            System.err.println("File Error: " + e.getMessage());
        }
    }

    private String xmlEscape(String input) {
        if (input == null) return "";
        return input.replace("&", "&amp;")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace("\"", "&quot;")
                    .replace("'", "&apos;")
                    // Reverse escape for our intentional line breaks so Graphviz renders them
                    .replace("&lt;BR ALIGN=&quot;LEFT&quot;/&gt;", "<BR ALIGN=\"LEFT\"/>");
    }

    @Override public int getPhase() { return Integer.MAX_VALUE; }
    @Override public boolean isAutoStartup() { return true; }
    @Override public void stop() { this.isRunning = false; }
    @Override public boolean isRunning() { return this.isRunning; }
    @Override public void stop(Runnable callback) { stop(); callback.run(); }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)