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.render.txt;
020
021import com.fasterxml.jackson.annotation.JsonProperty;
022import org.jpos.log.LogRenderer;
023import org.jpos.log.evt.SysInfo;
024
025import java.io.PrintStream;
026import java.lang.reflect.RecordComponent;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030
031/** Plain-text renderer for {@link SysInfo} audit records. */
032public final class SysInfoTxtLogRenderer implements LogRenderer<SysInfo> {
033    /** Default constructor; no instance state to initialise. */
034    public SysInfoTxtLogRenderer() {}
035    public Class<?> clazz() {
036        return SysInfo.class;
037    }
038    public Type type() {
039        return Type.TXT;
040    }
041
042    @Override
043    public void render(SysInfo info, PrintStream ps, String indent) {
044        final String pre = (indent == null) ? "" : indent;
045        /*
046         * 1) Collect all record components in declaration order
047         *    together with the effective (JSON) property name
048         *    and the value obtained from the accessor.
049         */
050        Map<String, Object> data = new LinkedHashMap<>();
051        int maxKeyLen = 0;
052
053        for (RecordComponent rc : SysInfo.class.getRecordComponents()) {
054            String key = effectiveName(rc);
055            Object val;
056            try {
057                val = rc.getAccessor().invoke(info);
058            } catch (Exception e) {
059                val = "(error " + e.getMessage() + ")";
060            }
061            data.put(key, val);
062            maxKeyLen = Math.max(maxKeyLen, key.length());
063        }
064
065        final int width = maxKeyLen;
066        data.forEach((k, v) -> {
067            if (v == null) {                         // skip nulls
068                return;
069            }
070            if (v instanceof List<?> list) {
071                if (list.isEmpty()) {
072                    return;
073                }
074                ps.printf("%s%-" + width + "s :%n", pre, k);
075                list.forEach(o -> ps.println(pre + "  • " + o));
076            } else {
077                ps.printf("%s%-" + width + "s : %s%n", pre, k, v);
078            }
079        });
080    }
081
082    /** Returns the JSON alias if present, otherwise the plain component name. */
083    private static String effectiveName(RecordComponent rc) {
084        JsonProperty jp = rc.getAnnotation(JsonProperty.class);
085        return (jp != null && !jp.value().isEmpty()) ? jp.value() : rc.getName();
086    }
087}
088