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    private static final Map<LogRendererRegistry.Key, LogRenderer<?>> renderers = Collections.synchronizedMap(
031      new LinkedHashMap<>()
032    );
033    static {
034        for (LogRenderer<?> r : ServiceLoader.load(LogRenderer.class)) {
035            register (r);
036        }
037    }
038
039    /**
040     * Registers a {@link LogRenderer} in the registry with a key generated from the renderer's class and type.
041     * @param renderer The renderer to register. Must not be null.
042     * @throws NullPointerException if the renderer is null.
043     */
044    public static <T> void register (LogRenderer<?> renderer) {
045        Objects.requireNonNull(renderer);
046        renderers.put(new LogRendererRegistry.Key(renderer.clazz(), renderer.type()), renderer);
047    }
048
049    /**
050     * Dumps the current state of the registry to the specified {@link PrintStream}.
051     * @param ps The {@link PrintStream} to which the dump will be written, e.g.: System.out
052     */
053    public static void dump (PrintStream ps) {
054        ps.println (LogRendererRegistry.class);
055        for (Map.Entry<LogRendererRegistry.Key, LogRenderer<?>> entry : renderers.entrySet()) {
056            ps.println("  " + entry.getKey() + ": " + entry.getValue().getClass());
057        }
058        ps.println ();
059    }
060
061    /**
062     * Retrieves a {@link LogRenderer} that matches the specified class and type. If no direct match is found,
063     * it attempts to find a renderer for any superclass or implemented interfaces. If no specific renderer is found,
064     * it defaults to a renderer for {@link Object}, if present for the given type.
065     *
066     * @param clazz The class for which a renderer is required.
067     * @param type The type of the renderer.
068     * @return The matching {@link LogRenderer}, or a default renderer if no specific match is found.
069     */
070    @SuppressWarnings("unchecked")
071    public static <T> LogRenderer<T> getRenderer(Class<?> clazz, LogRenderer.Type type) {
072        LogRenderer<T> renderer = getRendererForClass(clazz, type);
073        boolean needsCache = false;
074        if (renderer == null) {
075            needsCache = true;
076            renderer = getRendererForInterface(clazz, type);
077        }
078        if (renderer == null) {
079            renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(Object.class, type));
080        }
081        if (renderer != null && needsCache)
082            renderers.put(new LogRendererRegistry.Key(clazz, renderer.type(), true), renderer);
083        return renderer;
084    }
085
086    /**
087     * Recursively searches for a renderer for the given class and type, considering superclasses.
088     *
089     * @param clazz The class for which a renderer is needed.
090     * @param type The type of the renderer.
091     * @return A matching renderer, or null if none is found.
092     */
093    @SuppressWarnings("unchecked")
094    private static <T> LogRenderer<T> getRendererForClass (Class<?> clazz, LogRenderer.Type type) {
095        LogRenderer<T> renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(clazz, type));
096        if (renderer == null && clazz.getSuperclass() != Object.class) {
097            renderer = getRendererForClass(clazz.getSuperclass(), type);
098        }
099        return renderer;
100    }
101
102    /**
103     * Searches for a renderer among the interfaces implemented by the specified class.
104     *
105     * @param clazz The class whose interfaces will be checked for a matching renderer.
106     * @param type The type of the renderer.
107     * @return A matching renderer, or null if none is found.
108     */
109    @SuppressWarnings("unchecked")
110    private static <T> LogRenderer<T> getRendererForInterface (Class<?> clazz, LogRenderer.Type type) {
111        for (Class<?> i : clazz.getInterfaces()) {
112            LogRenderer<T> renderer = (LogRenderer<T>) renderers.get(new LogRendererRegistry.Key(i, type));
113            if (renderer != null)
114                return renderer;
115        }
116        return null;
117    }
118
119    /**
120     * A private key class to encapsulate the combination of class type and renderer type.
121     * This key is used to uniquely identify renderers within the registry.
122     */
123    private static class Key {
124        private final Class<?> clazz;
125        private final LogRenderer.Type type;
126        private final boolean cache;
127
128        public Key(Class<?> clazz, LogRenderer.Type type) {
129            this (clazz, type, false);
130        }
131        public Key(Class<?> clazz, LogRenderer.Type type, boolean cache) {
132            this.clazz = clazz;
133            this.type = type;
134            this.cache = cache;
135        }
136
137        @Override
138        public boolean equals(Object o) {
139            if (this == o) return true;
140            if (o == null || getClass() != o.getClass()) return false;
141            LogRendererRegistry.Key key = (LogRendererRegistry.Key) o;
142            return Objects.equals(clazz, key.clazz) && type == key.type;
143        }
144
145        @Override
146        public int hashCode() {
147            return Objects.hash(clazz, type);
148        }
149
150        @Override
151        public String toString() {
152            return "Key{" + clazz + ", type=" + type + (cache ? ", cached" : "") + '}';
153        }
154    }
155}