xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/ConfigurationHelp.java (revision c6f0939b1c668e9f8e1e276424439c3106b3a029)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2018, 2020, Chris Fraire <cfraire@me.com>.
22  */
23 package org.opengrok.indexer.configuration;
24 
25 import java.lang.annotation.Annotation;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.Modifier;
29 import java.lang.reflect.ParameterizedType;
30 import java.lang.reflect.Type;
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.TreeMap;
37 import org.opengrok.indexer.authorization.AuthControlFlag;
38 import org.opengrok.indexer.authorization.AuthorizationPlugin;
39 import org.opengrok.indexer.authorization.AuthorizationStack;
40 import org.opengrok.indexer.history.RepositoryInfo;
41 import org.opengrok.indexer.util.StringUtils;
42 
43 /**
44  * Represents a utility class to present some user-readable help regarding
45  * {@link Configuration}.
46  */
47 public class ConfigurationHelp {
48 
49     private static final String XML_COMMENT_START = "  <!-- ";
50 
ConfigurationHelp()51     private ConfigurationHelp() {
52     }
53 
54     /**
55      * Gets sample content for a configuration XML file.
56      * @return a defined instance
57      * @throws RuntimeException if an error occurs producing the sample
58      */
getSamples()59     public static String getSamples() throws RuntimeException {
60 
61         Configuration conf = new Configuration();
62         Class<?> klass = conf.getClass();
63 
64         StringBuilder b = new StringBuilder();
65         LinesBuilder h = new LinesBuilder();
66 
67         b.append("Configuration examples:\n");
68         b.append("\n");
69 
70         String sample = conf.getXMLRepresentationAsString();
71         b.append("<!-- Sample empty configuration.xml -->\n");
72         b.append(sample);
73         b.append("\n");
74 
75         List<Method> mthds = getSetters(klass);
76         for (Method mthd : mthds) {
77             // Get a pristine instance.
78             conf = new Configuration();
79             Object defaultValue = getDefaultValue(klass, mthd, conf);
80             // Get a pristine instance.
81             conf = new Configuration();
82             Object sampleValue = getSampleValue(mthd, defaultValue);
83             if (sampleValue == null) {
84                 continue;
85             }
86 
87             try {
88                 mthd.invoke(conf, sampleValue);
89             } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
90                 throw new RuntimeException("error setting sample value for " +
91                     mthd);
92             }
93 
94             sample = conf.getXMLRepresentationAsString();
95             sample = sample.replaceFirst(
96                 "(?sx)^<\\?xml.*Configuration\\d*\">\\n", "");
97             sample = sample.replaceFirst("</object>\\n</java>", "");
98 
99             h.clear();
100             h.append(XML_COMMENT_START);
101             h.append("Sample for ");
102             h.append(mthd.getName());
103             h.append(". Default is ");
104             h.appendWords(defaultValue);
105             h.appendWords(" -->");
106             h.appendLine();
107 
108             b.append(h);
109             b.append(sample);
110         }
111         return b.toString();
112     }
113 
getSetters(Class<?> klass)114     private static List<Method> getSetters(Class<?> klass) {
115         List<Method> res = new ArrayList<>();
116         Method[] methods = klass.getDeclaredMethods();
117         for (Method mth : methods) {
118             int mod = mth.getModifiers();
119             if (Modifier.isPublic(mod) && !Modifier.isStatic(mod) &&
120                 mth.getParameterCount() == 1 &&
121                 mth.getName().matches("^set.*") && !isDeprecated(mth)) {
122                 res.add(mth);
123             }
124         }
125         res.sort((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName()));
126         return res;
127     }
128 
getSampleValue(Method setter, Object defaultValue)129     private static Object getSampleValue(Method setter, Object defaultValue) {
130 
131         Class<?> paramType = setter.getParameterTypes()[0];
132         Type genType = setter.getGenericParameterTypes()[0];
133 
134         if (setter.getName().equals("setBugPattern")) {
135             return "Sample Bug \\#(\\d+)";
136         } else if (setter.getName().equals("setReviewPattern")) {
137             return "Sample Issue \\#(\\d+)";
138         } else if (paramType == String.class) {
139             return "user-specified-value";
140         } else if (paramType == int.class) {
141             return 1 + (int) defaultValue;
142         } else if (paramType == long.class) {
143             return 1 + (long) defaultValue;
144         } else if (paramType == short.class) {
145             return (short) (1 + (short) defaultValue);
146         } else if (paramType == boolean.class) {
147             if (defaultValue == null) {
148                 return null;
149             }
150             return !(boolean) defaultValue;
151         } else if (paramType == double.class) {
152             return 1 + (double) defaultValue;
153         } else if (paramType == List.class) {
154             return getSampleListValue(genType);
155         } else if (paramType == Map.class) {
156             return getSampleMapValue(genType);
157         } else if (paramType == Set.class) {
158             return getSampleSetValue(genType);
159         } else if (paramType == AuthorizationStack.class) {
160             AuthorizationStack astck = new AuthorizationStack(
161                 AuthControlFlag.REQUIRED, "user-specified-value");
162             astck.add(new AuthorizationPlugin(AuthControlFlag.REQUISITE,
163                 "user-specified-value"));
164             return astck;
165         } else if (paramType == Filter.class) {
166             Filter flt = new Filter();
167             flt.add("user-specified-(patterns)*");
168             flt.add("user-specified-filename");
169             flt.add("user/specified/path");
170             return flt;
171         } else if (paramType == IgnoredNames.class) {
172             IgnoredNames inm = new IgnoredNames();
173             inm.add("f:user-specified-value");
174             inm.add("d:user-specified-value");
175             return inm;
176         } else if (paramType.isEnum()) {
177             for (Object value : paramType.getEnumConstants()) {
178                 if (!value.equals(defaultValue)) {
179                     return value;
180                 }
181             }
182             return null;
183         } else if (paramType == SuggesterConfig.class) {
184             return SuggesterConfig.getForHelp();
185         } else if (paramType == StatsdConfig.class) {
186             return StatsdConfig.getForHelp();
187         } else {
188             throw new UnsupportedOperationException("getSampleValue() for " +
189                 paramType + ", " + genType);
190         }
191     }
192 
getSampleListValue(Type genType)193     private static Object getSampleListValue(Type genType) {
194         if (!(genType instanceof ParameterizedType)) {
195             return null;
196         }
197         ParameterizedType genParamType = (ParameterizedType) genType;
198         Type actType = genParamType.getActualTypeArguments()[0];
199 
200         if (actType != RepositoryInfo.class) {
201             throw new UnsupportedOperationException("Not supported yet for " + actType);
202         }
203         return null;
204     }
205 
getSampleMapValue(Type genType)206     private static Object getSampleMapValue(Type genType) {
207         if (!(genType instanceof ParameterizedType)) {
208             return null;
209         }
210         ParameterizedType genParamType = (ParameterizedType) genType;
211         Type[] actualTypeArguments = genParamType.getActualTypeArguments();
212         Type actType0 = actualTypeArguments[0];
213         Type actType1 = actualTypeArguments[1];
214         Object res;
215 
216         if (actType0 == String.class) {
217             if (actType1 == String.class) {
218                 Map<String, String> strmap = new TreeMap<>();
219                 strmap.put("user-defined-key", "user-defined-value");
220                 res = strmap;
221             } else if (actType1 == Project.class) {
222                 Map<String, Project> strmap = new TreeMap<>();
223                 String nm = "user-defined-key";
224                 strmap.put(nm, getSampleProject(nm));
225                 res = strmap;
226             } else {
227                 throw new UnsupportedOperationException(
228                     "Not supported yet for " + actType0 + " " + actType1);
229             }
230         } else {
231             throw new UnsupportedOperationException("Not supported yet for " +
232                 actType0 + " " + actType1);
233         }
234         return res;
235     }
236 
getSampleSetValue(Type genType)237     private static Object getSampleSetValue(Type genType) {
238         if (!(genType instanceof ParameterizedType)) {
239             return null;
240         }
241         ParameterizedType genParamType = (ParameterizedType) genType;
242         Type actType = genParamType.getActualTypeArguments()[0];
243         Object res;
244 
245         if (actType == String.class) {
246             Set<String> strset = new HashSet<>();
247             strset.add("user-defined-element");
248             res = strset;
249         } else if (actType == Group.class) {
250             Set<Group> grpset = new HashSet<>();
251             Group g = new Group("user-defined-name", "user-defined-pattern");
252             grpset.add(g);
253             res = grpset;
254         } else if (actType == Project.class) {
255             Set<Project> prjset = new HashSet<>();
256             Project p = getSampleProject("user-defined-name");
257             prjset.add(p);
258             res = prjset;
259         } else {
260             throw new UnsupportedOperationException("Not supported yet for " +
261                 actType);
262         }
263         return res;
264     }
265 
getSampleProject(String name)266     private static Project getSampleProject(String name) {
267         Project p = new Project(name, "/user/defined/path");
268         p.setNavigateWindowEnabled(true);
269         p.setTabSize(8);
270         return p;
271     }
272 
getDefaultValue(Class<?> klass, Method setter, Configuration cinst)273     private static Object getDefaultValue(Class<?> klass, Method setter,
274         Configuration cinst) {
275 
276         String gname = setter.getName().replaceFirst("^set", "get");
277         Method getter;
278         try {
279             getter = klass.getDeclaredMethod(gname);
280         } catch (NoSuchMethodException | SecurityException ex) {
281             gname = setter.getName().replaceFirst("^set", "is");
282             try {
283                 getter = klass.getDeclaredMethod(gname);
284             } catch (NoSuchMethodException | SecurityException ex2) {
285                 return null;
286             }
287         }
288 
289         // Return a text override for some objects.
290         switch (gname) {
291             case "getSuggesterConfig":
292                 return "as below but with Boolean opposites, non-zeroes decremented by 1, null " +
293                         "for allowed-projects, and also including \"full\" in allowed-fields";
294             case "getPluginStack":
295                 return "an empty stack";
296             case "getIncludedNames":
297                 return "an empty filter";
298             case "getIgnoredNames":
299                 return "OpenGrok's standard set of ignored files and directories";
300         }
301 
302         try {
303             return getter.invoke(cinst);
304         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
305             return null;
306         }
307     }
308 
isDeprecated(Method mth)309     private static boolean isDeprecated(Method mth) {
310         for (Annotation annotation : mth.getAnnotations()) {
311             if (annotation instanceof Deprecated) {
312                 return true;
313             }
314         }
315         return false;
316     }
317 
318     private static class LinesBuilder {
319         static final int LINE_LENGTH = 80;
320 
321         final StringBuilder b = new StringBuilder();
322         int length;
323 
clear()324         void clear() {
325             b.setLength(0);
326             length = 0;
327         }
328 
append(String value)329         void append(String value) {
330             b.append(value);
331             length += value.length();
332         }
333 
appendWords(Object value)334         void appendWords(Object value) {
335             if (value == null) {
336                 appendWords("null");
337             } else {
338                 appendWords(value.toString());
339             }
340         }
341 
appendWords(String value)342         void appendWords(String value) {
343             if (length > LINE_LENGTH) {
344                 appendLine();
345             }
346 
347             int i = 0;
348             while (true) {
349                 int spaceLen = StringUtils.whitespaceOrControlLength(value, i, true);
350                 int wordLen = StringUtils.whitespaceOrControlLength(value, i + spaceLen, false);
351 
352                 if (wordLen < 1) {
353                     break;
354                 }
355 
356                 String word = value.substring(i + spaceLen, i + spaceLen + wordLen);
357                 if (length + spaceLen + wordLen > LINE_LENGTH) {
358                     appendLine();
359                     for (int j = 0; j < XML_COMMENT_START.length(); ++j) {
360                         append(" ");
361                     }
362                 } else {
363                     append(value.substring(i, i + spaceLen));
364                 }
365                 append(word);
366 
367                 i += spaceLen + wordLen;
368             }
369         }
370 
appendLine()371         void appendLine() {
372             b.append("\n");
373             length = 0;
374         }
375 
376         @Override
toString()377         public String toString() {
378             return b.toString();
379         }
380     }
381 }
382