001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.log;
020
021import java.io.PrintStream;
022import java.util.*;
023
024/**
025 * A registry for managing {@link LogRenderer} instances associated with specific class types and renderer types.
026 * This class allows for the registration, retrieval, and management of {@link LogRenderer} instances dynamically,
027 * using a thread-safe approach to ensure proper operation in multi-threaded environments.
028 */
029public class LogRendererRegistry {
030    /** Default constructor; no instance state to initialise. */
031    public LogRendererRegistry() {}
032    private static final Map<LogRendererRegistry.Key, LogRenderer<?>> renderers = Collections.synchronizedMap(
033      new LinkedHashMap<>()
034    );
035    static {
036        for (LogRenderer<?> r : ServiceLoader.load(LogRenderer.class)) {
037            register (r);
038        }
039    }
040
041    /**
042     * Registers a {@link LogRenderer} in the registry with a key generated from the renderer's class and type.
043     * @param <T> caller-side type variable (the registry stores wildcard renderers; this method is generic for caller convenience)
044     * @param renderer The renderer to register. Must not be null.
045     * @throws NullPointerException if the renderer is null.
046     */
047    public static <T> void register (LogRenderer<?> renderer) {
048        Objects.requireNonNull(renderer);
049        renderers.put(new LogRendererRegistry.Key(renderer.clazz(), renderer.type()), renderer);
050    }
051
052    /**
053     * Dumps the current state of the registry to the specified {@link PrintStream}.
054     * @param ps The {@link PrintStream} to which the dump will be written, e.g.: System.out
055     */
056    public static void dump (PrintStream ps) {
057        ps.println (LogRendererRegistry.class);
058        for (Map.Entry<LogRendererRegistry.Key, LogRenderer<?>> entry : renderers.entrySet()) {
059            ps.println("  " + entry.getKey() + ": " + entry.getValue().getClass());
060        }
061        ps.println ();
062    }
063
064    /**
065     * Retrieves a {@link LogRenderer} that matches the specified class and type. If no direct match is found,
066     * it attempts to find a renderer for any superclass or implemented interfaces. If no specific renderer is found,
067     * it defaults to a renderer for {@link Object}, if present for the given type.
068     *
069     * @param <T> expected concrete renderer type
070     * @param clazz The class for which a renderer is required.
071     * @param type The type of the renderer.
072     * @return The matching {@link LogRenderer}, or a default renderer if no specific match is found.
073     */
074    @SuppressWarnings("unchecked")
075    public static <T> LogRenderer<T> getRenderer(Class<?> clazz, LogRenderer.Type type) {
076        LogRenderer<T> renderer = getRendererForClass(clazz, type);
077        boolean needsCache = false;
078        if (renderer == null) {
079            needsCache = true;
080            renderer = getRendererForInterface(clazz, type);
081        }
082        if (renderer == null) {
083            renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(Object.class, type));
084        }
085        if (renderer != null && needsCache)
086            renderers.put(new LogRendererRegistry.Key(clazz, renderer.type(), true), renderer);
087        return renderer;
088    }
089
090    /**
091     * Recursively searches for a renderer for the given class and type, considering superclasses.
092     *
093     * @param clazz The class for which a renderer is needed.
094     * @param type The type of the renderer.
095     * @return A matching renderer, or null if none is found.
096     */
097    @SuppressWarnings("unchecked")
098    private static <T> LogRenderer<T> getRendererForClass (Class<?> clazz, LogRenderer.Type type) {
099        LogRenderer<T> renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(clazz, type));
100        if (renderer == null && clazz.getSuperclass() != Object.class) {
101            renderer = getRendererForClass(clazz.getSuperclass(), type);
102        }
103        return renderer;
104    }
105
106    /**
107     * Searches for a renderer among the interfaces implemented by the specified class.
108     *
109     * @param clazz The class whose interfaces will be checked for a matching renderer.
110     * @param type The type of the renderer.
111     * @return A matching renderer, or null if none is found.
112     */
113    @SuppressWarnings("unchecked")
114    private static <T> LogRenderer<T> getRendererForInterface (Class<?> clazz, LogRenderer.Type type) {
115        for (Class<?> i : clazz.getInterfaces()) {
116            LogRenderer<T> renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(i, type));
117            if (renderer != null)
118                return renderer;
119        }
120        return null;
121    }
122
123    /**
124     * A private key class to encapsulate the combination of class type and renderer type.
125     * This key is used to uniquely identify renderers within the registry.
126     */
127    private static class Key {
128        private final Class<?> clazz;
129        private final LogRenderer.Type type;
130        private final boolean cache;
131
132        public Key(Class<?> clazz, LogRenderer.Type type) {
133            this (clazz, type, false);
134        }
135        public Key(Class<?> clazz, LogRenderer.Type type, boolean cache) {
136            this.clazz = clazz;
137            this.type = type;
138            this.cache = cache;
139        }
140
141        @Override
142        public boolean equals(Object o) {
143            if (this == o) return true;
144            if (o == null || getClass() != o.getClass()) return false;
145            LogRendererRegistry.Key key = (LogRendererRegistry.Key) o;
146            return Objects.equals(clazz, key.clazz) && type == key.type;
147        }
148
149        @Override
150        public int hashCode() {
151            return Objects.hash(clazz, type);
152        }
153
154        @Override
155        public String toString() {
156            return "Key{" + clazz + ", type=" + type + (cache ? ", cached" : "") + '}';
157        }
158    }
159}