001package org.dllearner.test; 002 003import com.google.common.collect.Sets; 004import javassist.ClassPool; 005import org.dllearner.core.AnnComponentManager; 006import org.dllearner.core.Component; 007import org.dllearner.core.annotations.NoConfigOption; 008import org.dllearner.core.annotations.OutVariable; 009import org.dllearner.core.annotations.Unused; 010import org.dllearner.core.config.ConfigOption; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014import java.io.File; 015import java.io.IOException; 016import java.lang.reflect.Field; 017import java.lang.reflect.Method; 018import java.net.MalformedURLException; 019import java.net.URL; 020import java.net.URLClassLoader; 021import java.nio.file.Files; 022import java.util.*; 023import java.util.Map.Entry; 024import java.util.regex.Pattern; 025import java.util.stream.Collectors; 026import java.util.stream.Stream; 027 028/* (no java-doc) 029 * Internal tool to help find undocumented config options 030 */ 031public class UndocumentedOptionScanner { 032 033 private static Logger logger = LoggerFactory.getLogger(UndocumentedOptionScanner.class); 034 private static AnnComponentManager cm = AnnComponentManager.getInstance(); 035 private static Class<?> currentClaz; 036 private static boolean loggedCurrentClaz; 037 038 private static void logClass() { 039 if (!loggedCurrentClaz) { 040 logger.info("\n@" + currentClaz.getCanonicalName()); 041 loggedCurrentClaz = true; 042 } 043 } 044 private static void startClass(Class<?> clazz) { 045 currentClaz = clazz; 046 loggedCurrentClaz = false; 047 } 048 public static void main(String[] args) { 049 ClassPool cp = ClassPool.getDefault(); 050 for (Class<? extends Component> c : cm.getComponents()) { 051 startClass(c); 052 Map<String, List<Field>> fields = new TreeMap<>(); 053 Map<String, List<Method>> methods = new TreeMap<>(); 054 for (Method m : c.getMethods()) { 055 String name = m.getName(); 056 List<Method> set = methods.get(name); 057 if (set == null) { 058 set = new LinkedList<>(); 059 methods.put(name, set); 060 } 061 set.add(m); 062 } 063 for ( 064 Class<?> cc = c; 065 cc != null; 066 cc = cc.getSuperclass() 067 ) { 068 for (Field f : cc.getDeclaredFields()) { 069 String name = f.getName(); 070 List<Field> set = fields.get(name); 071 if (set == null) { 072 set = new LinkedList<>(); 073 fields.put(name, set); 074 } 075 set.add(f); 076 } 077 } 078 079 Set<String> hasDoc = new TreeSet<>(); 080 Set<String> noDoc = new TreeSet<>(); 081 Set<String> notConfigSet = new TreeSet<>(); 082 for (Entry<String, List<Field>> f : fields.entrySet()) { 083 boolean isDocumented = false; 084 boolean notConfig = false; 085 for (Field fs : f.getValue()) { 086 isDocumented = isDocumented || fs.isAnnotationPresent(ConfigOption.class); 087 notConfig = notConfig || fs.isAnnotationPresent(Unused.class) 088 || fs.isAnnotationPresent(OutVariable.class) 089 || fs.isAnnotationPresent(NoConfigOption.class); 090 } 091 if (isDocumented) { 092 hasDoc.add(AnnComponentManager.getName(f.getValue().get(0))); 093 } else if (notConfig) { 094 notConfigSet.add(f.getKey()); 095 } else { 096 noDoc.add(f.getKey()); 097 } 098 } 099 Set<String> hasSetter = new TreeSet<>(); 100 Set<String> noCO = new TreeSet<>(); 101 for (Entry<String, List<Method>> m : methods.entrySet()) { 102 String cn = m.getKey(); 103 if (cn.startsWith("set")) { 104 String optionName = cn.substring(3, 4).toLowerCase() + cn.substring(4); 105 if (cn.substring(4).equals(cn.substring(4).toUpperCase())) { optionName = optionName.toLowerCase(); } 106 if (hasDoc.contains(optionName)) { 107 hasSetter.add(optionName); 108 109 } else if (noDoc.contains(optionName)) { 110 Class<?> claz = fields.get(optionName).get(0).getDeclaringClass(); 111 String fileName = getFilename(claz); 112 File file = findSource(claz); 113 int foundLine = findFieldLine(file, optionName); 114 logClass(); 115 logger.warn("setter+var but no @configOption . " + optionName + "(" + claz.getSimpleName() + ".java:"+foundLine+")"); 116 noCO.add(optionName); 117 118 } else { 119 boolean notConfig = false; 120 for (Method ms : m.getValue()) { 121 notConfig = notConfig 122 || ms.isAnnotationPresent(Deprecated.class) 123 || ms.isAnnotationPresent(NoConfigOption.class); 124 } 125 if (!notConfig && !notConfigSet.contains(optionName)) { 126 Method m0 = m.getValue().get(0); 127 Class<?> claz = m0.getDeclaringClass(); 128 String fileName = getFilename(claz); 129 File file = findSource(claz); 130 int foundLine = findFieldLine(file, m0.getName()); 131 logClass(); 132 logger.info("setter without var . "+optionName + "(" + claz.getSimpleName() + ".java:"+foundLine+")"); 133 } 134 } 135 } 136 } 137 for (String noSetter : Sets.difference(hasDoc, hasSetter)) { 138 Class<?> claz = fields.get(noSetter).get(0).getDeclaringClass(); 139 String fileName = getFilename(claz); 140 File file = findSource(claz); 141 int foundLine = findFieldLine(file, noSetter); 142 logClass(); 143 logger.warn("option without setter! . " +noSetter + "(" + claz.getSimpleName() + ".java:"+foundLine+")"); 144 } 145 } 146 } 147 148 private static String getFilename(Class<?> clazz) { 149 return clazz.getCanonicalName().replaceAll("\\.", "/") + ".java"; 150 } 151 private static File findSource(Class<?> clazz) { 152 File file = null; 153 String fileName = getFilename(clazz); 154 URL[] urls = Stream.of(System.getProperty("java.class.path").split(File.pathSeparator)) 155 .map(entry -> { 156 try { 157 return new File(entry).toURI().toURL(); 158 } catch (MalformedURLException e) { 159 throw new IllegalArgumentException("URL could not be created from '" + entry + "'", e); 160 } 161 }) 162 .toArray(URL[]::new); 163 //URLClassLoader currentClassLoader = ((URLClassLoader) (Thread.currentThread().getContextClassLoader())); 164 List<URL> sourceCP = Arrays.stream(urls).filter(url -> !url.getPath().endsWith(".jar")).collect(Collectors.toList()); 165 for (URL u : sourceCP) { 166 File tFile = new File(u.getFile() + "/../../src/main/java/" + fileName); 167 if (tFile.isFile()) { 168 file = tFile; 169 break; 170 } 171 } 172 return file; 173 } 174 175 private static int findFieldLine(File file, String optionName) { 176 int foundLine = 0; 177 if (file != null) try { 178 int lineno = 0; 179 boolean inComment = false; 180 for (String line : Files.readAllLines(file.toPath())) { 181 lineno++; 182 if (line.matches(".*/\\*.*") && !line.matches(".*\\*/.*")) { 183 inComment = true; 184 } 185 if (!inComment && !line.matches("\\s*//.*") && line.matches("(\\w|\\s|[><,])*(\\w|[><,])+\\s" + Pattern.quote(optionName) + "\\s*(\\W.*|)")) { 186 foundLine = lineno; 187 break; 188 } 189 if (inComment && !line.matches(".*/\\*.*") && line.matches(".*\\*/.*")) { 190 inComment = false; 191 } 192 } 193 } catch (IOException e) { 194 logger.warn("Problem reading " + file + " (" + e.getMessage() + ")"); 195 } 196 return foundLine; 197 } 198}