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}