Forem

Query Filter
Query Filter

Posted on

bridge118

package com.citi.get.cet.comet.tools;

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

import static org.apache.commons.lang.StringUtils.trim;

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");

        // VERTICAL LAYOUT BIAS - Force top-to-bottom layout
        dot.append("    rankdir=TB;\n");           // Top to Bottom direction
        dot.append("    ranksep=0.8;\n");          // Increased vertical spacing
        dot.append("    nodesep=0.5;\n");           // Horizontal spacing between nodes
        dot.append("    splines=ortho;\n");         // Orthogonal edges for cleaner layout
        dot.append("    compound=true;\n");         // Allow clustering

        // Node styling with fixed width to prevent overflow
        dot.append("    node [shape=plaintext, fontname=\"Arial\", fontsize=9, margin=0];\n");
        dot.append("    edge [penwidth=1.5, arrowsize=0.8];\n\n");

        // Collect all beans first for better organization
        Map<String, BeanInfo> allBeans = new LinkedHashMap<>();
        Set<String> referencedBeans = new HashSet<>();

        // First pass: collect all beans and dependencies
        ApplicationContext current = this.startContext;
        while (current != null) {
            ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) current).getBeanFactory();
            for (String name : factory.getBeanDefinitionNames()) {
                if (!allBeans.containsKey(name)) {
                    BeanDefinition def = factory.getBeanDefinition(name);
                    allBeans.put(name, new BeanInfo(def, factory));

                    // Collect dependencies to identify referenced beans
                    for (String dep : factory.getDependenciesForBean(name)) {
                        referencedBeans.add(dep);
                    }
                }
            }
            current = current.getParent();
        }

        // Create subgraphs to group referenced vs unreferenced beans
        dot.append("    subgraph cluster_referenced {\n");
        dot.append("        label=\"Referenced Beans\";\n");
        dot.append("        fontname=\"Arial\";\n");
        dot.append("        fontsize=12;\n");
        dot.append("        style=filled;\n");
        dot.append("        fillcolor=\"#F9F9F9\";\n");
        dot.append("        color=\"#CCCCCC\";\n");
        dot.append("        margin=20;\n\n");

        // Render referenced beans first
        for (Map.Entry<String, BeanInfo> entry : allBeans.entrySet()) {
            String name = entry.getKey();
            if (referencedBeans.contains(name) || isReferencedByOthers(name, allBeans)) {
                renderBeanNode(dot, name, entry.getValue());
            }
        }
        dot.append("    }\n\n");

        // Create subgraph for unreferenced beans (orphans)
        dot.append("    subgraph cluster_unreferenced {\n");
        dot.append("        label=\"Unreferenced Beans (Orphans)\";\n");
        dot.append("        fontname=\"Arial\";\n");
        dot.append("        fontsize=12;\n");
        dot.append("        style=filled;\n");
        dot.append("        fillcolor=\"#FFF9E6\";\n");
        dot.append("        color=\"#FFCC66\";\n");
        dot.append("        margin=20;\n\n");

        // Render unreferenced beans
        for (Map.Entry<String, BeanInfo> entry : allBeans.entrySet()) {
            String name = entry.getKey();
            if (!referencedBeans.contains(name) && !isReferencedByOthers(name, allBeans)) {
                renderBeanNode(dot, name, entry.getValue());
            }
        }
        dot.append("    }\n\n");

        // Draw dependency arrows (edges)
        for (Map.Entry<String, BeanInfo> entry : allBeans.entrySet()) {
            String name = entry.getKey();
            for (String dep : entry.getValue().dependencies) {
                if (allBeans.containsKey(dep)) {
                    dot.append(String.format("    \"%s\" -> \"%s\" [penwidth=1.5];\n", name, dep));
                }
            }
        }

        dot.append("}\n");
        writeToFile(dot.toString());
        this.isRunning = true;
    }

    private void renderBeanNode(StringBuilder dot, String beanName, BeanInfo info) {
        String color = beanName.toLowerCase().contains("comet") ? "#FFF904" :
                      (beanName.toLowerCase().contains("citi") ? "#CBE6C7" : "#E1F5FE");

        dot.append(String.format("    \"%s\" [label=<", beanName));

        // FIXED: Table with FIXED WIDTH and WRAPPING to prevent overflow
        dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"5\" ");
        dot.append("FIXEDSIZE=\"FALSE\" WIDTH=\"280\" BALIGN=\"LEFT\">");
        dot.append("<BGCOLOR=\"").append(color).append("\"/>");

        // Header with better wrapping
        dot.append("<TR><TD COLSPAN=\"2\" BGCOLOR=\"#DDDDDD\" CELLPADDING=\"6\" ");
        dot.append("WIDTH=\"280\" ALIGN=\"CENTER\"><B>");
        dot.append(wrapText(xmlEscape(trim(beanName)), 35));
        dot.append("</B></TD></TR>");

        // Property count indicator
        int propCount = info.propertyValues.size();
        int argCount = info.constructorArgs.size();
        if (propCount > 0 || argCount > 0) {
            dot.append("<TR><TD COLSPAN=\"2\" BGCOLOR=\"#EEEEEE\" CELLPADDING=\"2\" ");
            dot.append("ALIGN=\"CENTER\"><FONT POINT-SIZE=\"9\">");
            dot.append(propCount).append(" properties");
            if (argCount > 0) dot.append(" | ").append(argCount).append(" constructor args");
            dot.append("</FONT></TD></TR>");
        }

        // Constructor arguments (prioritize these first)
        for (ConstructorArg arg : info.constructorArgs) {
            dot.append("<TR>");
            dot.append("<TD ALIGN=\"LEFT\" CELLPADDING=\"3\" WIDTH=\"100\">");
            dot.append("<FONT FACE=\"Courier\" POINT-SIZE=\"9\">");
            dot.append(xmlEscape(arg.name));
            dot.append("</FONT></TD>");
            dot.append("<TD ALIGN=\"LEFT\" CELLPADDING=\"3\" WIDTH=\"180\">");
            dot.append(trimAndWrap(extractValue(arg.value), 40));
            dot.append("</TD>");
            dot.append("</TR>");
        }

        // Properties (with wrapping)
        for (PropertyValue pv : info.propertyValues) {
            dot.append("<TR>");
            dot.append("<TD ALIGN=\"LEFT\" CELLPADDING=\"3\" WIDTH=\"100\">");
            dot.append("<FONT FACE=\"Courier\" POINT-SIZE=\"9\">");
            dot.append(xmlEscape(pv.getName()));
            dot.append("</FONT></TD>");
            dot.append("<TD ALIGN=\"LEFT\" CELLPADDING=\"3\" WIDTH=\"180\">");
            dot.append(trimAndWrap(extractValue(pv.getValue()), 40));
            dot.append("</TD>");
            dot.append("</TR>");
        }

        // Show empty state if no content
        if (propCount == 0 && argCount == 0) {
            dot.append("<TR><TD COLSPAN=\"2\" ALIGN=\"CENTER\" CELLPADDING=\"8\">");
            dot.append("<FONT POINT-SIZE=\"9\" COLOR=\"#999999\">no configuration</FONT>");
            dot.append("</TD></TR>");
        }

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

    private String wrapText(String text, int maxLength) {
        if (text.length() <= maxLength) return text;
        StringBuilder wrapped = new StringBuilder();
        int start = 0;
        while (start < text.length()) {
            int end = Math.min(start + maxLength, text.length());
            wrapped.append(text.substring(start, end));
            if (end < text.length()) {
                wrapped.append("<BR ALIGN=\"LEFT\"/>");
            }
            start = end;
        }
        return wrapped.toString();
    }

    private String trimAndWrap(String text, int maxLength) {
        if (text == null) return "";
        String trimmed = text.trim();
        if (trimmed.length() <= maxLength) return trimmed;
        return trimmed.substring(0, maxLength - 3).trim() + "...";
    }

    private boolean isReferencedByOthers(String beanName, Map<String, BeanInfo> allBeans) {
        for (BeanInfo info : allBeans.values()) {
            if (info.dependencies.contains(beanName)) {
                return true;
            }
        }
        return false;
    }

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

        if (value instanceof TypedStringValue) {
            String raw = ((TypedStringValue) value).getValue();
            return xmlEscape(raw != null ? raw.trim() : "null");
        }

        if (value instanceof BeanReference) {
            return "@" + ((BeanReference) value).getBeanName();
        }

        if (value instanceof BeanDefinition) {
            String className = ((BeanDefinition) value).getBeanClassName();
            if (className != null) {
                return "<i>" + className.substring(className.lastIndexOf('.') + 1) + "</i>";
            }
            return "<i>InnerBean</i>";
        }

        if (value instanceof Iterable) {
            List<String> cleaned = new ArrayList<>();
            int count = 0;
            for (Object item : (Iterable<?>) value) {
                if (count++ >= 8) {
                    cleaned.add("...(" + ((Iterable<?>) value).spliterator().getExactSizeIfKnown() + " more)");
                    break;
                }
                cleaned.add(trim(extractValue(item)));
            }
            String result = String.join(", ", cleaned);
            return result.length() > 50 ? result.substring(0, 47) + "..." : result;
        }

        if (value instanceof Map) {
            StringBuilder mapStr = new StringBuilder();
            int count = 0;
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
                if (count++ >= 5) {
                    mapStr.append("...(").append(((Map<?, ?>) value).size() - 5).append(" more)");
                    break;
                }
                if (count > 1) mapStr.append(", ");
                mapStr.append(extractValue(entry.getKey())).append("=").append(extractValue(entry.getValue()));
            }
            return mapStr.toString();
        }

        String result = value.toString().trim();
        if (result.length() > 50) {
            result = result.substring(0, 47) + "...";
        }
        return xmlEscape(result);
    }

    private void writeToFile(String content) {
        try {
            File projectRoot = findProjectRoot();
            File buildDir = new File(projectRoot, "build");
            if (!buildDir.exists()) {
                buildDir.mkdirs();
            }
            File file = new File(buildDir, "spring-beans.dot");
            try (FileWriter writer = new FileWriter(file)) {
                writer.write(content);
            }

            String fileUrl = file.toURI().toString();
            String sep = "==================================================";
            System.out.println("\n" + sep);
            System.out.println("SPRING BEANS VISUALIZATION GENERATED");
            System.out.println("Layout: Vertical (Top-to-Bottom)");
            System.out.println("Root Detected: " + projectRoot.getAbsolutePath());
            System.out.println("Click to open: " + fileUrl);
            System.out.println(sep + "\n");
        } catch (IOException e) {
            System.err.println("CRITICAL: Failed to write DOT file: " + e.getMessage());
        }
    }

    private File findProjectRoot() {
        String userDir = System.getProperty("user.dir");
        File current = new File(userDir);
        while (current != null) {
            if (new File(current, "build.gradle").exists() ||
                new File(current, "settings.gradle").exists() ||
                new File(current, "build.gradle.kts").exists() ||
                new File(current, "pom.xml").exists()) {
                return current;
            }
            current = current.getParentFile();
        }
        return new File(userDir);
    }

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

    // Inner class to hold bean information
    private static class BeanInfo {
        List<PropertyValue> propertyValues = new ArrayList<>();
        List<ConstructorArg> constructorArgs = new ArrayList<>();
        Set<String> dependencies = new HashSet<>();

        BeanInfo(BeanDefinition def, ConfigurableListableBeanFactory factory) {
            // Collect properties
            for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
                propertyValues.add(pv);
            }

            // Collect constructor arguments
            ConstructorArgumentValues cav = def.getConstructorArgumentValues();
            for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cav.getIndexedArgumentValues().entrySet()) {
                constructorArgs.add(new ConstructorArg("arg[" + entry.getKey() + "]", entry.getValue().getValue()));
            }
            for (ConstructorArgumentValues.ValueHolder vh : cav.getGenericArgumentValues()) {
                String type = vh.getType() != null ? 
                             vh.getType().substring(vh.getType().lastIndexOf('.') + 1) : "arg";
                constructorArgs.add(new ConstructorArg(type, vh.getValue()));
            }

            // Collect dependencies
            try {
                for (String dep : factory.getDependenciesForBean(
                    def.getBeanClassName() != null ? def.getBeanClassName() : "")) {
                    dependencies.add(dep);
                }
            } catch (Exception e) {
                // Ignore dependency resolution errors
            }
        }
    }

    private static class ConstructorArg {
        String name;
        Object value;

        ConstructorArg(String name, Object value) {
            this.name = name;
            this.value = value;
        }
    }

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