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