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