Forem

Query Filter
Query Filter

Posted on

bridge106

package com.yourpackage;

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

import java.io.*;
import java.util.*;

public class SpringVisualizer implements ApplicationContextAware, InitializingBean {

    private ConfigurableApplicationContext context;
    private final Set<String> currentBeanManualDeps = new HashSet<>();

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

    @Override
    public void afterPropertiesSet() throws Exception {
        System.err.println("!!! SPRING VISUALIZER BOOTSTRAP STARTING !!!");
        generateGraph();
    }

    public void generateGraph() {
        StringBuilder dot = new StringBuilder("digraph G {\n");
        // Using ortho splines and overlap=false to help with the 309-bean scale
        dot.append("  rankdir=LR; overlap=false; splines=ortho;\n");
        dot.append("  node [shape=plain, fontname=\"Arial\", fontsize=10];\n");
        dot.append("  edge [fontname=\"Arial\", fontsize=8];\n\n");

        Set<String> processed = new HashSet<>();
        int count = 0;

        ApplicationContext currentCtx = this.context;
        while (currentCtx != null) {
            if (currentCtx instanceof ConfigurableApplicationContext) {
                ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) currentCtx).getBeanFactory();
                for (String name : factory.getBeanDefinitionNames()) {
                    if (processed.add(name)) {
                        try {
                            BeanDefinition def = factory.getBeanDefinition(name);
                            currentBeanManualDeps.clear();
                            count++;

                            String color = name.toLowerCase().contains("comet") ? "#FFF9C4" : 
                                           (name.toLowerCase().contains("cpls") ? "#C8E6C9" : "#E1F5FE");

                            // Node definition with quoted ID to prevent syntax errors
                            dot.append("  \"").append(name).append("\" [label=<");
                            dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"").append(color).append("\">");
                            dot.append("<TR><TD COLSPAN=\"2\"><B>").append(safeXml(name)).append("</B></TD></TR>");

                            for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
                                dot.append("<TR><TD ALIGN=\"LEFT\">").append(safeXml(pv.getName())).append("</TD>");
                                dot.append("<TD ALIGN=\"LEFT\">").append(extractValue(pv.getValue(), 0)).append("</TD></TR>");
                            }
                            dot.append("</TABLE>>];\n");

                            // Connections
                            Set<String> allDeps = new HashSet<>(Arrays.asList(factory.getDependenciesForBean(name)));
                            allDeps.addAll(currentBeanManualDeps);
                            for (String dep : allDeps) {
                                // Double-quote both sides of the arrow
                                dot.append("  \"").append(name).append("\" -> \"").append(dep).append("\";\n");
                            }
                        } catch (Exception e) { /* Skip inaccessible beans */ }
                    }
                }
            }
            currentCtx = currentCtx.getParent();
        }

        dot.append("}\n");
        saveToFile(dot.toString(), count);
    }

    private String extractValue(Object value, int depth) {
        if (value == null) return "null";
        if (depth > 5) return "<i>[Max Depth]</i>";

        if (value instanceof BeanReference) {
            String bName = ((BeanReference) value).getBeanName();
            currentBeanManualDeps.add(bName);
            return "@" + safeXml(bName);
        }

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

        // The "inBoundEF" Expansion Logic (Handles Holder and Definition)
        if (value instanceof BeanDefinition || value instanceof BeanDefinitionHolder) {
            BeanDefinition inner = (value instanceof BeanDefinitionHolder) ? 
                                   ((BeanDefinitionHolder) value).getBeanDefinition() : (BeanDefinition) value;

            StringBuilder innerTbl = new StringBuilder("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"#F5F5F5\">");
            String cls = inner.getBeanClassName();
            String label = cls != null ? cls.substring(cls.lastIndexOf('.') + 1) : "InnerBean";
            innerTbl.append("<TR><TD COLSPAN=\"2\" BGCOLOR=\"#DCDCDC\"><B>").append(safeXml(label)).append("</B></TD></TR>");

            for (PropertyValue pv : inner.getPropertyValues().getPropertyValues()) {
                innerTbl.append("<TR><TD>").append(safeXml(pv.getName())).append("</TD>");
                innerTbl.append("<TD>").append(extractValue(pv.getValue(), depth + 1)).append("</TD></TR>");
            }
            innerTbl.append("</TABLE>");
            return innerTbl.toString();
        }

        if (value instanceof Iterable) {
            StringBuilder sb = new StringBuilder();
            for (Object item : (Iterable<?>) value) {
                sb.append(extractValue(item, depth + 1)).append("<BR ALIGN=\"LEFT\"/>");
            }
            return sb.toString();
        }

        if (value instanceof Map) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<?, ?> e : ((Map<?, ?>) value).entrySet()) {
                sb.append(extractValue(e.getKey(), depth + 1)).append("=").append(extractValue(e.getValue(), depth + 1)).append("<BR ALIGN=\"LEFT\"/>");
            }
            return sb.toString();
        }

        return safeXml(value.toString());
    }

    private void saveToFile(String data, int count) {
        try {
            File root = findRoot();
            File file = new File(root, "build/spring-beans.dot");
            file.getParentFile().mkdirs();

            // Using BufferedWriter with UTF-8 to ensure no weird encoding breaks the file
            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) {
                writer.write(data);
                writer.flush();
            }

            System.err.println("\n############################################################");
            System.err.println("SUCCESS: DOT file written (UTF-8)");
            System.err.println("Beans processed: " + count);
            System.err.println("Path: " + file.getAbsolutePath());
            System.err.println("URL: " + file.toURI().toString());
            System.err.println("############################################################\n");
        } catch (Exception e) { e.printStackTrace(); }
    }

    private File findRoot() {
        File f = new File(System.getProperty("user.dir"));
        while (f != null && !new File(f, "build.gradle").exists() && !new File(f, "settings.gradle").exists()) {
            f = f.getParentFile();
        }
        return f != null ? f : new File(System.getProperty("user.dir"));
    }

    private String safeXml(String s) {
        if (s == null) return "";
        // Extreme escaping for Graphviz HTML labels
        return s.trim()
                .replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&apos;");
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)