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}