001package org.javasimon.console.text;
002
003import java.util.HashMap;
004import java.util.Map;
005
006/**
007 * Dictionary (Class type,String subType)→Stringifier.
008 *
009 * @author gquintana
010 */
011public class CompositeStringifier implements Stringifier<Object> {
012        /**
013         * Key of the dictionary is the couple (Class type,String subType).
014         * subType can be null
015         */
016        private static final class StringifierKey {
017
018                private final Class<?> type;
019                private final String subType;
020
021                /**
022                 * Constructor
023                 */
024                public StringifierKey(Class<?> type, String subType) {
025                        this.type = type;
026                        this.subType = subType;
027                }
028
029                /**
030                 * Compares 2 objects including the case one of them is null.
031                 */
032                private boolean equalsTo(Object o1, Object o2) {
033                        if (o1 == o2) {
034                                return true;
035                        } else {
036                                if ((o1 == null) || (o2 == null)) {
037                                        return false;
038                                } else {
039                                        return o1.equals(o2);
040                                }
041                        }
042                }
043
044                /**
045                 * Generate an hashCode, including the case null object
046                 */
047                private int hashCode(Object o) {
048                        return o == null ? 0 : o.hashCode();
049                }
050
051                @Override
052                public boolean equals(Object obj) {
053                        if (obj == null) {
054                                return false;
055                        }
056                        if (getClass() != obj.getClass()) {
057                                return false;
058                        }
059                        final StringifierKey other = (StringifierKey) obj;
060
061                        return equalsTo(this.type, other.type) && equalsTo(this.subType, other.subType);
062                }
063
064                @Override
065                public int hashCode() {
066                        return 3 + 5 * hashCode(this.type) + 7 * hashCode(this.subType);
067                }
068
069                @Override
070                public String toString() {
071                        return "StringifierKey[" + type.getName() + "," + subType + "]";
072                }
073        }
074
075        /**
076         * Main attribute of this class as it contains the dictionnary
077         */
078        private final Map<StringifierKey, Stringifier> stringifiers = new HashMap<>();
079        /**
080         * Null stringifier used to format null values
081         */
082        private Stringifier nullStringifier;
083        /**
084         * Default Stringier used when no value is found in the dictionnary
085         */
086        private Stringifier defaultStringifier;
087
088        /**
089         * Adds a stringifier to the dictionnary
090         *
091         * @param type Type (null sub-type)
092         * @param stringifier Stringifier
093         */
094        public final <T> void add(Class<? extends T> type, Stringifier<T> stringifier) {
095                add(type, null, stringifier);
096        }
097
098        /**
099         * Adds a stringifier to the dictionnary.
100         *
101         * @param type Type
102         * @param stringifier Stringifier
103         */
104        public final <T> void add(Class<? extends T> type, String name, Stringifier<T> stringifier) {
105                stringifiers.put(new StringifierKey(type, name), stringifier);
106        }
107
108        /**
109         * Look for a stringifier in the dictionnary
110         *
111         * @param type Type (null sub-type)
112         * @return Stringifier
113         */
114        public final <T> Stringifier<T> getForType(Class<? extends T> type) {
115                return getForType(type, null);
116        }
117
118        /**
119         * Look for a stringifier in the dictionary.<ol>
120         * <li>First look with type+subtype</li>
121         * <li>If not found, try with type alone</li>
122         * </ol>
123         *
124         * @param type Type
125         * @param subType Sub type
126         * @return Stringifier
127         */
128        @SuppressWarnings("unchecked")
129        private <T> Stringifier<T> get(Class<? extends T> type, String subType) {
130                Stringifier<T> stringifier = null;
131                if (subType != null) {
132                        stringifier = stringifiers.get(new StringifierKey(type, subType));
133                }
134                if (stringifier == null) {
135                        stringifier = stringifiers.get(new StringifierKey(type, null));
136                }
137                return stringifier;
138        }
139
140        /**
141         * Look for a stringifier in the dictionary.<ol>
142         * <li>First look with type+subtype</li>
143         * <li>If not found, try with type alone</li>
144         * </ol>
145         *
146         * @param type Type
147         * @param subType Sub type
148         * @return Stringifier
149         */
150        @SuppressWarnings("unchecked")
151        public final <T> Stringifier<T> getForType(Class<? extends T> type, String subType) {
152                return NoneStringifier.checkInstance(get(type, subType));
153        }
154
155        /**
156         * Get stringifier for an instance:
157         * <ul>
158         * <li>If instance is null, return null stringifier</li>
159         * <li>Else look in the dictionary with instance's class, if found return it</li>
160         * <li>Else return default strinfigier</li>
161         * </ul>
162         *
163         * @param object Object instance
164         */
165        @SuppressWarnings("unchecked")
166        private <T> Stringifier<T> getForInstance(T object, String subType) {
167                Stringifier<T> stringifier;
168                if (object == null) {
169                        stringifier = nullStringifier;
170                } else {
171                        stringifier = (Stringifier<T>) get(object.getClass(), subType);
172                        if (stringifier == null) {
173                                stringifier = defaultStringifier;
174                        }
175                }
176                return NoneStringifier.checkInstance(stringifier);
177        }
178
179        /**
180         * Converts an object into a String looking for appropriate
181         * stringfier among dictionary
182         *
183         * @param object Object
184         * @return String representing the object
185         */
186        public String toString(Object object) {
187                return getForInstance(object, null).toString(object);
188        }
189
190        /**
191         * Converts an object into a String looking for appropriate
192         * stringfier among dictionary
193         *
194         * @param object Object
195         * @param subType Sub type
196         * @return String representing the object
197         */
198        public String toString(Object object, String subType) {
199                return getForInstance(object, subType).toString(object);
200        }
201
202        public Stringifier getDefaultStringifier() {
203                return defaultStringifier;
204        }
205
206        public void setDefaultStringifier(Stringifier defaultStringifier) {
207                this.defaultStringifier = defaultStringifier;
208        }
209
210        public Stringifier getNullStringifier() {
211                return nullStringifier;
212        }
213
214        public void setNullStringifier(Stringifier nullStringifier) {
215                this.nullStringifier = nullStringifier;
216        }
217
218}