DEV Community

Query Filter
Query Filter

Posted on

bridge85

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.util.*;

public class SpringVisualizer implements ApplicationContextAware, SmartLifecycle {

    private ConfigurableApplicationContext startContext;
    private ConfigurableListableBeanFactory globalFactory;
    private boolean isRunning = false;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.startContext = (ConfigurableApplicationContext) applicationContext;
        // Keep a reference to the main factory for lookups
        this.globalFactory = this.startContext.getBeanFactory();
    }

    @Override
    public void start() {
        System.out.println("\n=== GENERATING DEEP INSPECTION GRAPH ===");
        generateDeepGraph();
        this.isRunning = true;
    }

    private void generateDeepGraph() {
        StringBuilder dot = new StringBuilder("digraph G {\n");
        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);
                    String color = name.toLowerCase().contains("comet") ? "#FFF9C4" : 
                                   (name.toLowerCase().contains("cpls") ? "#C8E6C9" : "#E1F5FE");

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

                    for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
                        // Pass 'true' to resolveReferences for this specific run
                        String cleanVal = extractValue(pv.getValue(), true); 
                        if (cleanVal.length() > 100) cleanVal = cleanVal.substring(0, 97) + "...";

                        dot.append("    <TR><TD ALIGN=\"LEFT\">").append(xmlEscape(pv.getName())).append("</TD>");
                        dot.append("<TD ALIGN=\"LEFT\">").append(xmlEscape(cleanVal)).append("</TD></TR>\n");
                    }
                    dot.append("  </TABLE>>];\n");

                    for (String dep : factory.getDependenciesForBean(name)) {
                        dot.append(String.format("  \"%s\" -> \"%s\";\n", name, dep));
                    }
                }
            }
            current = current.getParent();
        }
        dot.append("}\n");
        System.out.println("----- COPY START -----\n" + dot.toString() + "\n----- COPY END -----");
    }

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

        // NEW: If it's a reference and we want to "see inside"
        if (value instanceof BeanReference && resolveReferences) {
            String refName = ((BeanReference) value).getBeanName();
            try {
                // Look up the definition of the referenced bean
                BeanDefinition refDef = globalFactory.getBeanDefinition(refName);

                // If it's a simple list/collection bean (like cxlEventStatusList often is)
                // we try to extract its internal values. We pass 'false' to avoid infinite loops.
                Object innerValue = refDef.getPropertyValues(); 
                // Note: For List beans, the data is often in the 'source' or constructor args
                return "@" + refName + " -> " + extractValue(refDef.getSource(), false);
            } catch (Exception e) {
                return "@" + refName; // Fallback to just the name if lookup fails
            }
        }

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

        if (value instanceof List) {
            List<String> cleaned = new ArrayList<>();
            for (Object item : (List<?>) value) cleaned.add(extractValue(item, false));
            return cleaned.toString();
        }

        if (value instanceof Map) {
            StringBuilder mapStr = new StringBuilder("{");
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
                mapStr.append(extractValue(entry.getKey(), false)).append("=").append(extractValue(entry.getValue(), false)).append(", ");
            }
            if (mapStr.length() > 1) mapStr.setLength(mapStr.length() - 2);
            return mapStr.append("}").toString();
        }

        return value.toString();
    }

    private String xmlEscape(String input) {
        if (input == null) return "null";
        return input.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
                    .replace("\"", "&quot;").replace("'", "&apos;").replace("\n", " ");
    }

    @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)