xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/ConfigurationHelp.java (revision c6f0939b1c668e9f8e1e276424439c3106b3a029)
1b5840353SAdam Hornáček /*
2b5840353SAdam Hornáček  * CDDL HEADER START
3b5840353SAdam Hornáček  *
4b5840353SAdam Hornáček  * The contents of this file are subject to the terms of the
5b5840353SAdam Hornáček  * Common Development and Distribution License (the "License").
6b5840353SAdam Hornáček  * You may not use this file except in compliance with the License.
7b5840353SAdam Hornáček  *
8b5840353SAdam Hornáček  * See LICENSE.txt included in this distribution for the specific
9b5840353SAdam Hornáček  * language governing permissions and limitations under the License.
10b5840353SAdam Hornáček  *
11b5840353SAdam Hornáček  * When distributing Covered Code, include this CDDL HEADER in each
12b5840353SAdam Hornáček  * file and include the License file at LICENSE.txt.
13b5840353SAdam Hornáček  * If applicable, add the following below this CDDL HEADER, with the
14b5840353SAdam Hornáček  * fields enclosed by brackets "[]" replaced with your own identifying
15b5840353SAdam Hornáček  * information: Portions Copyright [yyyy] [name of copyright owner]
16b5840353SAdam Hornáček  *
17b5840353SAdam Hornáček  * CDDL HEADER END
18b5840353SAdam Hornáček  */
19b5840353SAdam Hornáček 
20b5840353SAdam Hornáček /*
215d9f3aa0SAdam Hornáček  * Copyright (c) 2018, 2020, Chris Fraire <cfraire@me.com>.
22b5840353SAdam Hornáček  */
239805b761SAdam Hornáček package org.opengrok.indexer.configuration;
24b5840353SAdam Hornáček 
25b5840353SAdam Hornáček import java.lang.annotation.Annotation;
26b5840353SAdam Hornáček import java.lang.reflect.InvocationTargetException;
27b5840353SAdam Hornáček import java.lang.reflect.Method;
28b5840353SAdam Hornáček import java.lang.reflect.Modifier;
29b5840353SAdam Hornáček import java.lang.reflect.ParameterizedType;
30b5840353SAdam Hornáček import java.lang.reflect.Type;
31b5840353SAdam Hornáček import java.util.ArrayList;
32b5840353SAdam Hornáček import java.util.HashSet;
33b5840353SAdam Hornáček import java.util.List;
34b5840353SAdam Hornáček import java.util.Map;
35b5840353SAdam Hornáček import java.util.Set;
36b5840353SAdam Hornáček import java.util.TreeMap;
379805b761SAdam Hornáček import org.opengrok.indexer.authorization.AuthControlFlag;
389805b761SAdam Hornáček import org.opengrok.indexer.authorization.AuthorizationPlugin;
399805b761SAdam Hornáček import org.opengrok.indexer.authorization.AuthorizationStack;
409805b761SAdam Hornáček import org.opengrok.indexer.history.RepositoryInfo;
416c593e21SChris Fraire import org.opengrok.indexer.util.StringUtils;
42b5840353SAdam Hornáček 
43b5840353SAdam Hornáček /**
44b5840353SAdam Hornáček  * Represents a utility class to present some user-readable help regarding
45b5840353SAdam Hornáček  * {@link Configuration}.
46b5840353SAdam Hornáček  */
47b5840353SAdam Hornáček public class ConfigurationHelp {
48ff44f24aSAdam Hornáček 
496c593e21SChris Fraire     private static final String XML_COMMENT_START = "  <!-- ";
506c593e21SChris Fraire 
ConfigurationHelp()51ff44f24aSAdam Hornáček     private ConfigurationHelp() {
52ff44f24aSAdam Hornáček     }
53ff44f24aSAdam Hornáček 
54b5840353SAdam Hornáček     /**
55b5840353SAdam Hornáček      * Gets sample content for a configuration XML file.
56b5840353SAdam Hornáček      * @return a defined instance
57b5840353SAdam Hornáček      * @throws RuntimeException if an error occurs producing the sample
58b5840353SAdam Hornáček      */
getSamples()598ae5e262SAdam Hornacek     public static String getSamples() throws RuntimeException {
60b5840353SAdam Hornáček 
61b5840353SAdam Hornáček         Configuration conf = new Configuration();
628ae5e262SAdam Hornacek         Class<?> klass = conf.getClass();
63b5840353SAdam Hornáček 
64b5840353SAdam Hornáček         StringBuilder b = new StringBuilder();
656c593e21SChris Fraire         LinesBuilder h = new LinesBuilder();
666c593e21SChris Fraire 
67b5840353SAdam Hornáček         b.append("Configuration examples:\n");
68b5840353SAdam Hornáček         b.append("\n");
69b5840353SAdam Hornáček 
70b5840353SAdam Hornáček         String sample = conf.getXMLRepresentationAsString();
71b5840353SAdam Hornáček         b.append("<!-- Sample empty configuration.xml -->\n");
72b5840353SAdam Hornáček         b.append(sample);
73b5840353SAdam Hornáček         b.append("\n");
74b5840353SAdam Hornáček 
75b5840353SAdam Hornáček         List<Method> mthds = getSetters(klass);
76b5840353SAdam Hornáček         for (Method mthd : mthds) {
77b5840353SAdam Hornáček             // Get a pristine instance.
78b5840353SAdam Hornáček             conf = new Configuration();
79b5840353SAdam Hornáček             Object defaultValue = getDefaultValue(klass, mthd, conf);
80b5840353SAdam Hornáček             // Get a pristine instance.
81b5840353SAdam Hornáček             conf = new Configuration();
82b5840353SAdam Hornáček             Object sampleValue = getSampleValue(mthd, defaultValue);
83a72324b1SAdam Hornáček             if (sampleValue == null) {
84a72324b1SAdam Hornáček                 continue;
85a72324b1SAdam Hornáček             }
86b5840353SAdam Hornáček 
87b5840353SAdam Hornáček             try {
88b5840353SAdam Hornáček                 mthd.invoke(conf, sampleValue);
89ff44f24aSAdam Hornáček             } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
90b5840353SAdam Hornáček                 throw new RuntimeException("error setting sample value for " +
91b5840353SAdam Hornáček                     mthd);
92b5840353SAdam Hornáček             }
93b5840353SAdam Hornáček 
94b5840353SAdam Hornáček             sample = conf.getXMLRepresentationAsString();
95b5840353SAdam Hornáček             sample = sample.replaceFirst(
96b5840353SAdam Hornáček                 "(?sx)^<\\?xml.*Configuration\\d*\">\\n", "");
97b5840353SAdam Hornáček             sample = sample.replaceFirst("</object>\\n</java>", "");
98b5840353SAdam Hornáček 
996c593e21SChris Fraire             h.clear();
1006c593e21SChris Fraire             h.append(XML_COMMENT_START);
1016c593e21SChris Fraire             h.append("Sample for ");
1026c593e21SChris Fraire             h.append(mthd.getName());
1036c593e21SChris Fraire             h.append(". Default is ");
1046c593e21SChris Fraire             h.appendWords(defaultValue);
1056c593e21SChris Fraire             h.appendWords(" -->");
1066c593e21SChris Fraire             h.appendLine();
1076c593e21SChris Fraire 
1086c593e21SChris Fraire             b.append(h);
109b5840353SAdam Hornáček             b.append(sample);
110b5840353SAdam Hornáček         }
111b5840353SAdam Hornáček         return b.toString();
112b5840353SAdam Hornáček     }
113b5840353SAdam Hornáček 
getSetters(Class<?> klass)1148ae5e262SAdam Hornacek     private static List<Method> getSetters(Class<?> klass) {
115b5840353SAdam Hornáček         List<Method> res = new ArrayList<>();
116b5840353SAdam Hornáček         Method[] methods = klass.getDeclaredMethods();
117b5840353SAdam Hornáček         for (Method mth : methods) {
118b5840353SAdam Hornáček             int mod = mth.getModifiers();
119b5840353SAdam Hornáček             if (Modifier.isPublic(mod) && !Modifier.isStatic(mod) &&
120b5840353SAdam Hornáček                 mth.getParameterCount() == 1 &&
121b5840353SAdam Hornáček                 mth.getName().matches("^set.*") && !isDeprecated(mth)) {
122b5840353SAdam Hornáček                 res.add(mth);
123b5840353SAdam Hornáček             }
124b5840353SAdam Hornáček         }
1256c593e21SChris Fraire         res.sort((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName()));
126b5840353SAdam Hornáček         return res;
127b5840353SAdam Hornáček     }
128b5840353SAdam Hornáček 
getSampleValue(Method setter, Object defaultValue)129b5840353SAdam Hornáček     private static Object getSampleValue(Method setter, Object defaultValue) {
130b5840353SAdam Hornáček 
1318ae5e262SAdam Hornacek         Class<?> paramType = setter.getParameterTypes()[0];
132b5840353SAdam Hornáček         Type genType = setter.getGenericParameterTypes()[0];
133b5840353SAdam Hornáček 
134b5840353SAdam Hornáček         if (setter.getName().equals("setBugPattern")) {
135b5840353SAdam Hornáček             return "Sample Bug \\#(\\d+)";
136b5840353SAdam Hornáček         } else if (setter.getName().equals("setReviewPattern")) {
137b5840353SAdam Hornáček             return "Sample Issue \\#(\\d+)";
138b5840353SAdam Hornáček         } else if (paramType == String.class) {
139b5840353SAdam Hornáček             return "user-specified-value";
140b5840353SAdam Hornáček         } else if (paramType == int.class) {
141b5840353SAdam Hornáček             return 1 + (int) defaultValue;
142b124dbe6SVladimir Kotal         } else if (paramType == long.class) {
143b124dbe6SVladimir Kotal             return 1 + (long) defaultValue;
144b5840353SAdam Hornáček         } else if (paramType == short.class) {
145b5840353SAdam Hornáček             return (short) (1 + (short) defaultValue);
146b5840353SAdam Hornáček         } else if (paramType == boolean.class) {
147a72324b1SAdam Hornáček             if (defaultValue == null) {
148a72324b1SAdam Hornáček                 return null;
149a72324b1SAdam Hornáček             }
150b5840353SAdam Hornáček             return !(boolean) defaultValue;
151b5840353SAdam Hornáček         } else if (paramType == double.class) {
152b5840353SAdam Hornáček             return 1 + (double) defaultValue;
153b5840353SAdam Hornáček         } else if (paramType == List.class) {
154b5840353SAdam Hornáček             return getSampleListValue(genType);
155b5840353SAdam Hornáček         } else if (paramType == Map.class) {
156b5840353SAdam Hornáček             return getSampleMapValue(genType);
157b5840353SAdam Hornáček         } else if (paramType == Set.class) {
158b5840353SAdam Hornáček             return getSampleSetValue(genType);
159b5840353SAdam Hornáček         } else if (paramType == AuthorizationStack.class) {
160b5840353SAdam Hornáček             AuthorizationStack astck = new AuthorizationStack(
161b5840353SAdam Hornáček                 AuthControlFlag.REQUIRED, "user-specified-value");
162b5840353SAdam Hornáček             astck.add(new AuthorizationPlugin(AuthControlFlag.REQUISITE,
163b5840353SAdam Hornáček                 "user-specified-value"));
164b5840353SAdam Hornáček             return astck;
165b5840353SAdam Hornáček         } else if (paramType == Filter.class) {
166b5840353SAdam Hornáček             Filter flt = new Filter();
167b5840353SAdam Hornáček             flt.add("user-specified-(patterns)*");
168b5840353SAdam Hornáček             flt.add("user-specified-filename");
169b5840353SAdam Hornáček             flt.add("user/specified/path");
170b5840353SAdam Hornáček             return flt;
171b5840353SAdam Hornáček         } else if (paramType == IgnoredNames.class) {
172b5840353SAdam Hornáček             IgnoredNames inm = new IgnoredNames();
173b5840353SAdam Hornáček             inm.add("f:user-specified-value");
174b5840353SAdam Hornáček             inm.add("d:user-specified-value");
175b5840353SAdam Hornáček             return inm;
176b5840353SAdam Hornáček         } else if (paramType.isEnum()) {
177b5840353SAdam Hornáček             for (Object value : paramType.getEnumConstants()) {
178a72324b1SAdam Hornáček                 if (!value.equals(defaultValue)) {
179a72324b1SAdam Hornáček                     return value;
180a72324b1SAdam Hornáček                 }
181b5840353SAdam Hornáček             }
182b5840353SAdam Hornáček             return null;
183911e8af0SAdam Hornáček         } else if (paramType == SuggesterConfig.class) {
1846c593e21SChris Fraire             return SuggesterConfig.getForHelp();
1850d7ace53SVladimir Kotal         } else if (paramType == StatsdConfig.class) {
1860d7ace53SVladimir Kotal             return StatsdConfig.getForHelp();
187b5840353SAdam Hornáček         } else {
188b5840353SAdam Hornáček             throw new UnsupportedOperationException("getSampleValue() for " +
189b5840353SAdam Hornáček                 paramType + ", " + genType);
190b5840353SAdam Hornáček         }
191b5840353SAdam Hornáček     }
192b5840353SAdam Hornáček 
getSampleListValue(Type genType)193b5840353SAdam Hornáček     private static Object getSampleListValue(Type genType) {
194b5840353SAdam Hornáček         if (!(genType instanceof ParameterizedType)) {
195b5840353SAdam Hornáček             return null;
196b5840353SAdam Hornáček         }
197b5840353SAdam Hornáček         ParameterizedType genParamType = (ParameterizedType) genType;
198b5840353SAdam Hornáček         Type actType = genParamType.getActualTypeArguments()[0];
199b5840353SAdam Hornáček 
200ff44f24aSAdam Hornáček         if (actType != RepositoryInfo.class) {
201ff44f24aSAdam Hornáček             throw new UnsupportedOperationException("Not supported yet for " + actType);
202b5840353SAdam Hornáček         }
2036c593e21SChris Fraire         return null;
204b5840353SAdam Hornáček     }
205b5840353SAdam Hornáček 
getSampleMapValue(Type genType)206b5840353SAdam Hornáček     private static Object getSampleMapValue(Type genType) {
207b5840353SAdam Hornáček         if (!(genType instanceof ParameterizedType)) {
208b5840353SAdam Hornáček             return null;
209b5840353SAdam Hornáček         }
210b5840353SAdam Hornáček         ParameterizedType genParamType = (ParameterizedType) genType;
211b5840353SAdam Hornáček         Type[] actualTypeArguments = genParamType.getActualTypeArguments();
212b5840353SAdam Hornáček         Type actType0 = actualTypeArguments[0];
213b5840353SAdam Hornáček         Type actType1 = actualTypeArguments[1];
2146c593e21SChris Fraire         Object res;
215b5840353SAdam Hornáček 
216b5840353SAdam Hornáček         if (actType0 == String.class) {
217b5840353SAdam Hornáček             if (actType1 == String.class) {
218b5840353SAdam Hornáček                 Map<String, String> strmap = new TreeMap<>();
219b5840353SAdam Hornáček                 strmap.put("user-defined-key", "user-defined-value");
220b5840353SAdam Hornáček                 res = strmap;
221b5840353SAdam Hornáček             } else if (actType1 == Project.class) {
222b5840353SAdam Hornáček                 Map<String, Project> strmap = new TreeMap<>();
223b5840353SAdam Hornáček                 String nm = "user-defined-key";
224b5840353SAdam Hornáček                 strmap.put(nm, getSampleProject(nm));
225b5840353SAdam Hornáček                 res = strmap;
226b5840353SAdam Hornáček             } else {
227b5840353SAdam Hornáček                 throw new UnsupportedOperationException(
228b5840353SAdam Hornáček                     "Not supported yet for " + actType0 + " " + actType1);
229b5840353SAdam Hornáček             }
230b5840353SAdam Hornáček         } else {
231b5840353SAdam Hornáček             throw new UnsupportedOperationException("Not supported yet for " +
232b5840353SAdam Hornáček                 actType0 + " " + actType1);
233b5840353SAdam Hornáček         }
234b5840353SAdam Hornáček         return res;
235b5840353SAdam Hornáček     }
236b5840353SAdam Hornáček 
getSampleSetValue(Type genType)237b5840353SAdam Hornáček     private static Object getSampleSetValue(Type genType) {
238b5840353SAdam Hornáček         if (!(genType instanceof ParameterizedType)) {
239b5840353SAdam Hornáček             return null;
240b5840353SAdam Hornáček         }
241b5840353SAdam Hornáček         ParameterizedType genParamType = (ParameterizedType) genType;
242b5840353SAdam Hornáček         Type actType = genParamType.getActualTypeArguments()[0];
2436c593e21SChris Fraire         Object res;
244b5840353SAdam Hornáček 
245b5840353SAdam Hornáček         if (actType == String.class) {
246b5840353SAdam Hornáček             Set<String> strset = new HashSet<>();
247b5840353SAdam Hornáček             strset.add("user-defined-element");
248b5840353SAdam Hornáček             res = strset;
249b5840353SAdam Hornáček         } else if (actType == Group.class) {
250b5840353SAdam Hornáček             Set<Group> grpset = new HashSet<>();
251b5840353SAdam Hornáček             Group g = new Group("user-defined-name", "user-defined-pattern");
252b5840353SAdam Hornáček             grpset.add(g);
253b5840353SAdam Hornáček             res = grpset;
254b5840353SAdam Hornáček         } else if (actType == Project.class) {
255b5840353SAdam Hornáček             Set<Project> prjset = new HashSet<>();
256b5840353SAdam Hornáček             Project p = getSampleProject("user-defined-name");
257b5840353SAdam Hornáček             prjset.add(p);
258b5840353SAdam Hornáček             res = prjset;
259b5840353SAdam Hornáček         } else {
260b5840353SAdam Hornáček             throw new UnsupportedOperationException("Not supported yet for " +
261b5840353SAdam Hornáček                 actType);
262b5840353SAdam Hornáček         }
263b5840353SAdam Hornáček         return res;
264b5840353SAdam Hornáček     }
265b5840353SAdam Hornáček 
getSampleProject(String name)266b5840353SAdam Hornáček     private static Project getSampleProject(String name) {
267b5840353SAdam Hornáček         Project p = new Project(name, "/user/defined/path");
268b5840353SAdam Hornáček         p.setNavigateWindowEnabled(true);
269b5840353SAdam Hornáček         p.setTabSize(8);
270b5840353SAdam Hornáček         return p;
271b5840353SAdam Hornáček     }
272b5840353SAdam Hornáček 
getDefaultValue(Class<?> klass, Method setter, Configuration cinst)273b5840353SAdam Hornáček     private static Object getDefaultValue(Class<?> klass, Method setter,
274b5840353SAdam Hornáček         Configuration cinst) {
275b5840353SAdam Hornáček 
276b5840353SAdam Hornáček         String gname = setter.getName().replaceFirst("^set", "get");
277b5840353SAdam Hornáček         Method getter;
278b5840353SAdam Hornáček         try {
279b5840353SAdam Hornáček             getter = klass.getDeclaredMethod(gname);
280b5840353SAdam Hornáček         } catch (NoSuchMethodException | SecurityException ex) {
281b5840353SAdam Hornáček             gname = setter.getName().replaceFirst("^set", "is");
282b5840353SAdam Hornáček             try {
283b5840353SAdam Hornáček                 getter = klass.getDeclaredMethod(gname);
284b5840353SAdam Hornáček             } catch (NoSuchMethodException | SecurityException ex2) {
285b5840353SAdam Hornáček                 return null;
286b5840353SAdam Hornáček             }
287b5840353SAdam Hornáček         }
288b5840353SAdam Hornáček 
2896c593e21SChris Fraire         // Return a text override for some objects.
2906c593e21SChris Fraire         switch (gname) {
2916c593e21SChris Fraire             case "getSuggesterConfig":
2926c593e21SChris Fraire                 return "as below but with Boolean opposites, non-zeroes decremented by 1, null " +
2936c593e21SChris Fraire                         "for allowed-projects, and also including \"full\" in allowed-fields";
2946c593e21SChris Fraire             case "getPluginStack":
2956c593e21SChris Fraire                 return "an empty stack";
2966c593e21SChris Fraire             case "getIncludedNames":
2976c593e21SChris Fraire                 return "an empty filter";
2986c593e21SChris Fraire             case "getIgnoredNames":
2996c593e21SChris Fraire                 return "OpenGrok's standard set of ignored files and directories";
3006c593e21SChris Fraire         }
3016c593e21SChris Fraire 
302b5840353SAdam Hornáček         try {
303b5840353SAdam Hornáček             return getter.invoke(cinst);
304ff44f24aSAdam Hornáček         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
305b5840353SAdam Hornáček             return null;
306b5840353SAdam Hornáček         }
307b5840353SAdam Hornáček     }
308b5840353SAdam Hornáček 
isDeprecated(Method mth)309b5840353SAdam Hornáček     private static boolean isDeprecated(Method mth) {
310b5840353SAdam Hornáček         for (Annotation annotation : mth.getAnnotations()) {
311a72324b1SAdam Hornáček             if (annotation instanceof Deprecated) {
312a72324b1SAdam Hornáček                 return true;
313a72324b1SAdam Hornáček             }
314b5840353SAdam Hornáček         }
315b5840353SAdam Hornáček         return false;
316b5840353SAdam Hornáček     }
3176c593e21SChris Fraire 
3186c593e21SChris Fraire     private static class LinesBuilder {
3196c593e21SChris Fraire         static final int LINE_LENGTH = 80;
3206c593e21SChris Fraire 
3216c593e21SChris Fraire         final StringBuilder b = new StringBuilder();
3226c593e21SChris Fraire         int length;
3236c593e21SChris Fraire 
clear()3246c593e21SChris Fraire         void clear() {
3256c593e21SChris Fraire             b.setLength(0);
3266c593e21SChris Fraire             length = 0;
3276c593e21SChris Fraire         }
3286c593e21SChris Fraire 
append(String value)3296c593e21SChris Fraire         void append(String value) {
3306c593e21SChris Fraire             b.append(value);
3316c593e21SChris Fraire             length += value.length();
3326c593e21SChris Fraire         }
3336c593e21SChris Fraire 
appendWords(Object value)3346c593e21SChris Fraire         void appendWords(Object value) {
3356c593e21SChris Fraire             if (value == null) {
3366c593e21SChris Fraire                 appendWords("null");
3376c593e21SChris Fraire             } else {
3386c593e21SChris Fraire                 appendWords(value.toString());
3396c593e21SChris Fraire             }
3406c593e21SChris Fraire         }
3416c593e21SChris Fraire 
appendWords(String value)3426c593e21SChris Fraire         void appendWords(String value) {
3436c593e21SChris Fraire             if (length > LINE_LENGTH) {
3446c593e21SChris Fraire                 appendLine();
3456c593e21SChris Fraire             }
3466c593e21SChris Fraire 
3476c593e21SChris Fraire             int i = 0;
3486c593e21SChris Fraire             while (true) {
3496c593e21SChris Fraire                 int spaceLen = StringUtils.whitespaceOrControlLength(value, i, true);
3506c593e21SChris Fraire                 int wordLen = StringUtils.whitespaceOrControlLength(value, i + spaceLen, false);
3516c593e21SChris Fraire 
3526c593e21SChris Fraire                 if (wordLen < 1) {
3536c593e21SChris Fraire                     break;
3546c593e21SChris Fraire                 }
3556c593e21SChris Fraire 
3566c593e21SChris Fraire                 String word = value.substring(i + spaceLen, i + spaceLen + wordLen);
3576c593e21SChris Fraire                 if (length + spaceLen + wordLen > LINE_LENGTH) {
3586c593e21SChris Fraire                     appendLine();
3596c593e21SChris Fraire                     for (int j = 0; j < XML_COMMENT_START.length(); ++j) {
3606c593e21SChris Fraire                         append(" ");
3616c593e21SChris Fraire                     }
3626c593e21SChris Fraire                 } else {
3636c593e21SChris Fraire                     append(value.substring(i, i + spaceLen));
3646c593e21SChris Fraire                 }
365*c6f0939bSAdam Hornacek                 append(word);
3666c593e21SChris Fraire 
3676c593e21SChris Fraire                 i += spaceLen + wordLen;
3686c593e21SChris Fraire             }
3696c593e21SChris Fraire         }
3706c593e21SChris Fraire 
appendLine()3716c593e21SChris Fraire         void appendLine() {
3726c593e21SChris Fraire             b.append("\n");
3736c593e21SChris Fraire             length = 0;
3746c593e21SChris Fraire         }
3756c593e21SChris Fraire 
3766c593e21SChris Fraire         @Override
toString()3776c593e21SChris Fraire         public String toString() {
3786c593e21SChris Fraire             return b.toString();
3796c593e21SChris Fraire         }
3806c593e21SChris Fraire     }
381b5840353SAdam Hornáček }
382