001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2018
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 http://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    private ValueHolder addHolder() {
106        ValueHolder valueHolder = new ValueHolder();
107        tail = tail.next = valueHolder;
108        return valueHolder;
109    }
110    
111    private MakeToString addHolder(String name, Object value) {
112        ValueHolder valueHolder = addHolder();
113        valueHolder.name = Objects.requireNonNull(name);
114        valueHolder.value = value;
115        return this;
116    }
117    
118    /**
119     * After calling this method, you can keep adding more properties to 
120     * later call toString() again and get a more complete representation of 
121     * the same object; but properties cannot be removed, so this only allows 
122     * limited reuse of the helper instance. The helper allows duplication of 
123     * properties (multiple name/value pairs with the same name can be added).
124     */
125    @Override public String toString() {
126        boolean omitNullValuesSnapshot = omitNullValues;
127        String hex = Integer.toHexString(instanceHashCode);
128        StringBuilder builder = 
129            new StringBuilder((className.length() + hex.length()) * 10);
130        builder.append('[').append(className);
131        if (instanceHashCode != -1) {
132            builder.append('@').append(hex);
133        }
134        builder.append(": ");
135        String nextSeparator = "";
136        for (ValueHolder valueHolder = head.next;
137             valueHolder != null;
138             valueHolder = valueHolder.next) 
139        {
140            Object value = valueHolder.value;
141            if (!omitNullValuesSnapshot || (value != null)) {
142                builder.append(nextSeparator);
143                nextSeparator = ", ";
144    
145                if (valueHolder.name != null) {
146                    builder.append(valueHolder.name).append('=');
147                }
148                if ((value != null) && value.getClass().isArray()) {
149                    Object[] objectArray = { value };
150                    String arrayString = Arrays.deepToString(objectArray);
151                    builder.append(arrayString, 1, arrayString.length() - 1);
152                } else {
153                    builder.append(value);
154                }
155            }
156        }
157        return builder.append(']').toString();
158    }
159    
160    private static final class ValueHolder {
161        String name;
162        Object value;
163        ValueHolder next;
164    }
165}