001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.util;
030
031import java.util.Arrays;
032import java.util.Objects;
033
034/**
035 * Utility class for {@code toString()} methods.
036 * 
037 * <p>Largely taken from Guava's {@code toStringHelper()}, with some 
038 * formatting differences as well as the hash code for the given instance.</p>
039 */
040public class MakeToString {
041    
042    private final ValueHolder head = new ValueHolder();
043    private final String className;
044    private final int instanceHashCode;
045    
046    private boolean omitNullValues = false;
047    private ValueHolder tail = head;
048    
049    private MakeToString(Object instance) {
050        Objects.requireNonNull(instance, "Cannot generate toString for a null object");
051        className = instance.getClass().getSimpleName();
052        instanceHashCode = instance.hashCode();
053    }
054    
055    private MakeToString(String clazzName) {
056        className = clazzName;
057        instanceHashCode = -1;
058    }
059    
060    public static MakeToString fromInstance(Object instance) {
061        return new MakeToString(instance);
062    }
063    
064    public static MakeToString fromClass(Class<?> clazz) {
065        return new MakeToString(clazz.getSimpleName());
066    }
067    
068    public MakeToString omitNullValues() {
069        omitNullValues = true;
070        return this;
071    }
072    
073    public MakeToString addQuoted(String name, Object value) {
074        return addHolder(name, '"' + String.valueOf(value) + '"');
075    }
076    
077    public MakeToString add(String name, Object value) {
078        return addHolder(name, String.valueOf(value));
079    }
080    
081    public MakeToString add(String name, long value) {
082        return addHolder(name, String.valueOf(value));
083    }
084    
085    public MakeToString add(String name, boolean value) {
086        return addHolder(name, String.valueOf(value));
087    }
088    
089    public MakeToString add(String name, int value) {
090        return addHolder(name, String.valueOf(value));
091    }
092    
093    public MakeToString add(String name, char value) {
094        return addHolder(name, String.valueOf(value));
095    }
096    
097    public MakeToString add(String name, float value) {
098        return addHolder(name, String.valueOf(value));
099    }
100    
101    public MakeToString add(String name, double value) {
102        return addHolder(name, String.valueOf(value));
103    }
104    
105    public MakeToString add(String name, double... arr) {
106        StringBuilder buf = new StringBuilder(512);
107        buf.append("[ ");
108        for (int i = 0; i < arr.length; i++) {
109            buf.append(arr[i]);
110            if ((i+1) < arr.length) {
111                buf.append(',');
112            }
113        }
114        buf.append(" ]");
115        return addHolder(name, buf.toString());
116    }
117    
118    private ValueHolder addHolder() {
119        ValueHolder valueHolder = new ValueHolder();
120        tail = tail.next = valueHolder;
121        return valueHolder;
122    }
123    
124    private MakeToString addHolder(String name, Object value) {
125        ValueHolder valueHolder = addHolder();
126        valueHolder.name = Objects.requireNonNull(name);
127        valueHolder.value = value;
128        return this;
129    }
130    
131    /**
132     * After calling this method, you can keep adding more properties to 
133     * later call toString() again and get a more complete representation of 
134     * the same object; but properties cannot be removed, so this only allows 
135     * limited reuse of the helper instance. The helper allows duplication of 
136     * properties (multiple name/value pairs with the same name can be added).
137     */
138    @Override public String toString() {
139        boolean omitNullValuesSnapshot = omitNullValues;
140        String hex = Integer.toHexString(instanceHashCode);
141        StringBuilder builder = 
142            new StringBuilder((className.length() + hex.length()) * 10);
143        builder.append('[').append(className);
144        if (instanceHashCode != -1) {
145            builder.append('@').append(hex);
146        }
147        builder.append(": ");
148        String nextSeparator = "";
149        for (ValueHolder valueHolder = head.next;
150             valueHolder != null;
151             valueHolder = valueHolder.next) 
152        {
153            Object value = valueHolder.value;
154            if (!omitNullValuesSnapshot || (value != null)) {
155                builder.append(nextSeparator);
156                nextSeparator = ", ";
157    
158                if (valueHolder.name != null) {
159                    builder.append(valueHolder.name).append('=');
160                }
161                if ((value != null) && value.getClass().isArray()) {
162                    Object[] objectArray = { value };
163                    String arrayString = Arrays.deepToString(objectArray);
164                    builder.append(arrayString, 1, arrayString.length() - 1);
165                } else {
166                    builder.append(value);
167                }
168            }
169        }
170        return builder.append(']').toString();
171    }
172    
173    private static final class ValueHolder {
174        String name;
175        Object value;
176        ValueHolder next;
177    }
178}