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}