Input:
import ppt4j.annotation.MethodProfiler;
import ppt4j.annotation.Property;
import ppt4j.database.DatabaseType;
import ppt4j.database.Vulnerability;
import ppt4j.diff.BlockDiff;
import ppt4j.diff.DiffParser;
import ppt4j.diff.FileDiff;
import ppt4j.factory.DatabaseFactory;
import ppt4j.factory.ExtractorFactory;
import ppt4j.feature.FeatureMatcher;
import ppt4j.feature.Features;
import ppt4j.feature.java.JavaExtractor;
import ppt4j.feature.java.JavaFeatures;
import ppt4j.util.StringUtils;
import lombok.extern.log4j.Log4j;
import java.io.IOException;
import java.util.*;

Context:
framework/src/main/java/ppt4j/analysis/patch/PatchAnalyzer.java
package ppt4j.analysis.patch;

@Log4j
public class PatchAnalyzer {

    @Property("ppt4j.analysis.patch.presence_threshold")

framework/src/main/java/ppt4j/factory/DatabaseFactory.java
@Log4j
public final class DatabaseFactory {

    private static final JavaCompiler
            compiler = ToolProvider.getSystemJavaCompiler();

    private static final StandardJavaFileManager
            stdManager = compiler.getStandardFileManager(null, null, null);

    private static final String classTemplate;

    private static final Map<Integer, Vulnerability>
            cachedClasses = new HashMap<>();

    private static final Map<String, Vulnerability>
            cachedClassesByCVE = new HashMap<>();

    static {
        try (InputStream data = ResourceUtils.readVulTemplate()) {
            classTemplate = new String(data.readAllBytes());
        } catch (IOException e) {
            log.error(e);
            throw new RuntimeException();
        }
    }

    public static Vulnerability makeDataset(@NonNull VulnerabilityInfo info) {
        if(cachedClasses.containsKey(StringUtils.extractDatabaseId(info.vul_id)) ||
                cachedClassesByCVE.containsKey(info.cve_id)) {
            log.error(String.format(
                    "Vulnerability with Database ID %s or" +
                            "CVE ID %s is already loaded.",
                    info.vul_id, info.cve_id));
            throw new IllegalStateException();
        }
        try {
            Vulnerability instance = compileAndLoad(info);
            cachedClasses.put(instance.getDatabaseId(), instance);
            cachedClassesByCVE.put(instance.getCVEId(), instance);
            return instance;
        } catch (Exception e) {
            log.error(e);
            throw new IllegalStateException();
        }
    }

    public static Vulnerability makeDataset(InputStream jsonInput)
            throws IOException {
        VulnerabilityInfo info = VulnerabilityInfo.fromJSON(jsonInput);
        return makeDataset(info);
    }

    public static Vulnerability getByDatabaseId(int id) {
        if(cachedClasses.containsKey(id)) {
            return cachedClasses.get(id);
        }
        InputStream data = ResourceUtils.readDatabase(String.format("VUL4J-%d.json", id));
        if(data == null) {
            log.error("No vulnerability with id " + id + " found");
            throw new IllegalStateException();
        }
        try {
            return makeDataset(data);
        } catch (IOException e) {
            log.error(e);
            throw new IllegalStateException();
        }
    }

    private static Vulnerability compileAndLoad(VulnerabilityInfo info)
            throws NoSuchMethodException, ClassNotFoundException,
            InvocationTargetException, InstantiationException,
            IllegalAccessException {
        if(info.isEmpty()) {
            log.error("VulnerabilityInfo object is not valid");
            throw new IllegalStateException();
        }
        String className = info.cve_id.replace("-", "_");
        String code = String.format(classTemplate,
                info.cve_id,
                StringUtils.extractDatabaseId(info.vul_id),
                className,
                className,
                info.project_url,
                info.fixing_commit_hash,
                info.human_patch_url + ".diff",
                StringUtils.getJavaSrcTopLevelDir(info),
                info.src_classes_dir,
                info.should_scan_all_modules,
                StringUtils.getThirdPartySrcDirsString(info),
                StringUtils.getThirdPartyLibDirsString(info)
        );
        MemoryJavaFileManager manager =
                new MemoryJavaFileManager(stdManager);
        JavaFileObject file = manager.makeStringSource(
                className + ".java", code);
        DiagnosticCollector<JavaFileObject> diagnostics =
                new DiagnosticCollector<>();
        JavaCompiler.CompilationTask task =
                compiler.getTask(
                        null, manager, diagnostics, null, null,
                        List.of(file)
                );
        Boolean result = task.call();
        if (result == null || !result) {
            for (var diagnostic : diagnostics.getDiagnostics()) {
                log.error(diagnostic.getMessage(null));
            }
            throw new RuntimeException("Compilation failed.");

        }
        Map<String, byte[]> classBytes = manager.getClassBytes();
        manager.close();
        MemoryClassLoader loader = new MemoryClassLoader(classBytes);
        Class<?> clazz = loader.loadClass("ppt4j.database." + className);
        return (Vulnerability) clazz.getConstructor().newInstance();
    }

    static class MemoryJavaFileManager
            extends ForwardingJavaFileManager<JavaFileManager> {

        final Map<String, byte[]> classBytes = new HashMap<>();

        MemoryJavaFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        public Map<String, byte[]> getClassBytes() {
            return new HashMap<>(this.classBytes);
        }

        @Override
        public void flush() {}

        @Override
        public void close() {
            classBytes.clear();
        }

        @Override
        public JavaFileObject getJavaFileForOutput(
                JavaFileManager.Location location, String className,
                JavaFileObject.Kind kind, FileObject sibling)
                throws IOException {
            if (kind == JavaFileObject.Kind.CLASS) {
                return new MemoryOutputJavaFileObject(className);
            } else {
                return super.getJavaFileForOutput(
                        location, className, kind, sibling);
            }
        }

        JavaFileObject makeStringSource(String name, String code) {
            return new MemoryInputJavaFileObject(name, code);
        }

        static class MemoryInputJavaFileObject extends SimpleJavaFileObject {

            final String code;

            MemoryInputJavaFileObject(String name, String code) {
                super(URI.create("string:///" + name), Kind.SOURCE);
                this.code = code;
            }

            @Override
            public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
                return CharBuffer.wrap(code);
            }
        }

        class MemoryOutputJavaFileObject extends SimpleJavaFileObject {
            final String name;

            MemoryOutputJavaFileObject(String name) {
                super(URI.create("string:///" + name), Kind.CLASS);
                this.name = name;
            }

            @Override
            public OutputStream openOutputStream() {
                return new FilterOutputStream(new ByteArrayOutputStream()) {
                    @Override
                    public void close() throws IOException {
                        out.close();
                        ByteArrayOutputStream bos =
                                (ByteArrayOutputStream) out;
                        classBytes.put(name, bos.toByteArray());
                    }
                };
            }

        }

    }

    static class MemoryClassLoader extends URLClassLoader {

        Map<String, byte[]> classBytes = new HashMap<>();

        public MemoryClassLoader(Map<String, byte[]> classBytes) {
            super(new URL[0], MemoryClassLoader.class.getClassLoader());
            this.classBytes.putAll(classBytes);
        }

        @Override
        protected Class<?> findClass(String name)
                throws ClassNotFoundException {
            byte[] buf = classBytes.get(name);
            if (buf == null) {
                return super.findClass(name);
            }
            classBytes.remove(name);
            return defineClass(name, buf, 0, buf.length);
        }

    }

}

framework/src/main/java/ppt4j/diff/FileDiff.java
public class FileDiff {

    @Getter
    private final List<BlockDiff> blocks = new ArrayList<>();

    public FileDiff(List<Pair<Integer, Line>> lines) {
        int offset = 0;
        int idx = 0;
        while (idx < lines.size()) {
            int start = idx;
            int end = idx;
            if (lines.get(start).getRight().getLineType() == Line.LineType.TO) {
                while (end < lines.size() - 1 &&
                        lines.get(end + 1).getLeft() == lines.get(end).getLeft() + 1 &&
                        lines.get(end + 1).getRight().getLineType() != Line.LineType.FROM) {
                    end++;
                }
                offset += end - start + 1;
            } else {
                while (end < lines.size() - 1 &&
                        lines.get(end + 1).getLeft() == lines.get(end).getLeft() + 1 &&
                        lines.get(end + 1).getRight().getLineType() != Line.LineType.TO) {
                    end++;
                }
                offset -= end - start + 1;
                if (end < lines.size() - 1 &&
                        lines.get(end + 1).getRight().getLineType() == Line.LineType.TO &&
                        lines.get(end + 1).getLeft() - offset - 1 <= lines.get(end).getLeft()) {
                    end++;
                    while (end < lines.size() - 1 &&
                            lines.get(end + 1).getLeft() == lines.get(end).getLeft() + 1) {
                        end++;
                    }
                    offset += end - start + 1;
                }
            }
            blocks.add(new BlockDiff(lines.subList(start, end + 1)));
            idx = end + 1;
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (BlockDiff block : blocks) {
            sb.append(block.toString());
            sb.append("\n");
        }
        return sb.toString();
    }

}

framework/src/main/java/ppt4j/feature/Features.java
@Getter
public class Features implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    public enum SourceType {
        JAVA, BYTECODE
    }

    @SuppressWarnings("SpellCheckingInspection")
    public enum InstType {
        THROW, MONITOR, SWITCH, INSTANCEOF, RETURN, LOOP,
        BRLT, BRLE, BRGT, BRGE, SHL, SHR, USHR
    }

    @Property("ppt4j.features.similarity.threshold")
    private static double SIM_THRESHOLD;

    @Property("ppt4j.features.similarity.algorithm")
    private static String SIM_ALGORITHM;

    protected final SourceType sourceType;

    protected String className;
    protected int lineNo;

    protected final Set<Object>      Constants           = new HashSet<>();
    protected final Set<String>      MethodInvocations   = new HashSet<>();
    protected final Set<String>      FieldAccesses       = new HashSet<>();
    protected final Set<String>      ObjCreations        = new HashSet<>();
    protected final Set<InstType>    Instructions        = new HashSet<>();
    protected final Set<String>      Misc                = new HashSet<>();

    protected Features(@NonNull SourceType sourceType,
                    @NonNull String className, int lineNo) {
        this.sourceType = sourceType;
        this.className = className;
        this.lineNo = lineNo;
    }

    public boolean isEmpty() {
        return Constants.isEmpty() &&
               MethodInvocations.isEmpty() &&
               FieldAccesses.isEmpty() &&
               ObjCreations.isEmpty() &&
               Instructions.isEmpty() &&
               Misc.isEmpty();
    }

    public int size() {
        return Constants.size() +
               MethodInvocations.size() +
               FieldAccesses.size() +
               ObjCreations.size() +
               Instructions.size() +
               Misc.size();
    }

    @Override
    public String toString() {
        String constants = String.format("Constants: %s\n",
                StringUtils.printSet(Constants, true, true));
        String methodInvocations = String.format("Method Invocations: %s\n",
                StringUtils.printSet(MethodInvocations));
        String fieldAccesses = String.format("Field Accesses: %s\n",
                StringUtils.printSet(FieldAccesses));
        String objCreations = String.format("Object Creations: %s\n",
                StringUtils.printSet(ObjCreations));
        String instructions = String.format("Instructions: %s\n",
                StringUtils.printSet(Instructions));
        String misc = String.format("Misc: %s\n",
                StringUtils.printSet(Misc));
        return constants + methodInvocations + fieldAccesses
                + objCreations + instructions + misc;
    }

    @Override
    public boolean equals(Object rhs) {
        if(rhs == null) return false;
        if(!(rhs instanceof Features _rhs)) return false;
        return FeatureMatcher.get(SIM_ALGORITHM).isMatch(this, _rhs, SIM_THRESHOLD);
    }

}

framework/src/main/java/ppt4j/feature/java/JavaExtractor.java
@SuppressWarnings("unused")
@Log4j
public final class JavaExtractor implements Extractor {

    @Serial
    private static final long serialVersionUID = 1L;

    private transient final CtClass<?> root;

    @Getter
    private final Map<String, JavaExtractor>
            innerClasses = new HashMap<>();

    @Getter
    private final Map<Integer, Features> featuresMap = new TreeMap<>();

    private boolean isParsed = false;

    @Getter
    private final String className;

    @Getter
    private final String superClassName;

    private final Set<Integer> validLines = new HashSet<>();

    private final Map<Integer, Integer> splitLinesToLogical = new TreeMap<>();

    public JavaExtractor(InputStream inputStream)
            throws IOException {
        this(Launcher.parseClass(new String(inputStream.readAllBytes())));
    }

    public JavaExtractor(String path)
            throws IOException {
        this(new FileInputStream(path));
    }

    public JavaExtractor(CtClass<?> clazz) {
        String superClassName1;
        this.root = clazz;
        this.className = clazz.getQualifiedName().replace('.', '/');
        CtTypeReference<?> superClass = clazz.getSuperclass();
        if(superClass != null) {
            superClassName1 = superClass.getQualifiedName().replace('.', '/');
        } else {
            superClassName1 = null;
        }
        this.superClassName = superClassName1;
    }

    public JavaExtractor(ClassFactory factory, String className) {
        this(factory.get(className));
    }

    private JavaExtractor() {
        root = null;
        className = "fake";
        superClassName = "fake";
    }

    public static JavaExtractor nil() {
        return new JavaExtractor();
    }

    @Override
    public Features.SourceType getSourceType() {
        return Features.SourceType.JAVA;
    }

    @Override
    public void parse() {
        if (isParsed) {
            return;
        }
        root.getFields().forEach(this::parseField);
        root.getElements(new LineFilter()).forEach(this::parseLine);
        root.getNestedTypes().forEach(ty -> {
            if (ty instanceof CtClass _class) {

                JavaExtractor ex = new JavaExtractor(_class);
                innerClasses.put(ex.getClassName(), ex);
            }
        });
        root.getElements(new AnonymousClassFilter()).forEach(ty -> {
            CtClass<?> _class = ty.getAnonymousClass();
            JavaExtractor ex = new JavaExtractor(_class);
            innerClasses.put(ex.getClassName(), ex);
        });
        innerClasses.values().forEach(JavaExtractor::parse);
        isParsed = true;
    }

    public Collection<JavaExtractor> getInnerClass() {
        return innerClasses.values();
    }

    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public boolean isValidLine(int line) {
        return validLines.contains(line);
    }

    public int getLogicalLine(int line) {
        if(!splitLinesToLogical.containsKey(line)) {
            return -1;
        }
        return splitLinesToLogical.get(line);
    }

    private <T> void parseField(CtField<T> field) {
        if(field.getPosition().equals(SourcePosition.NOPOSITION)) {
            return;
        }
        CtFieldReference<T> fieldRef = field.getReference();
        CtTypeReference<T> fieldTypeRef = fieldRef.getType();
        CtExpression<T> assignment = field.getAssignment();
        ConstPropAnalysis<?> analysis = new ConstPropAnalysis<>(assignment);
        analysis.analyze();
        if(!analysis.isLiteral()) {
            String fieldAccess =
                    StringUtils.convertQualifiedName(fieldRef.getQualifiedName(), root.getQualifiedName())
                    + ":" + StringUtils.convertToDescriptor(fieldTypeRef.getQualifiedName());
            JavaFeatures features;
            if(assignment instanceof CtStatement stmt) {
                features = new JavaFeatures(root.getQualifiedName(), stmt);
                features.getFieldAccesses().add(fieldAccess);
                putSplitLines(features);
                validLines.add(stmt.getPosition().getLine());
                putFeatures(stmt.getPosition().getLine(), features);
            }
        } else {
            LibraryConstants.put(fieldRef.getQualifiedName(), analysis.getLiteral().getValue());
        }
    }

    private void parseLine(CtStatement stmt) {
        if(stmt.getPosition().equals(SourcePosition.NOPOSITION)) {
            return;
        }
        try {
            JavaFeatures features = new JavaFeatures(root.getQualifiedName(), stmt);
            String text = features.getText();
            if(text.equals("do") || text.equals("else") || text.equals("try")) {
                return;
            }
            putSplitLines(features);
            validLines.add(stmt.getPosition().getLine());
            putFeatures(stmt.getPosition().getLine(), features);
        } catch (IllegalStateException e) {
            // comment in a single line
        }
    }

    private void putSplitLines(JavaFeatures features) {
        List<Integer> splitLines = features.getSplitLines();
        int baseLine = splitLines.get(0);
        for (Integer line : splitLines) {
            splitLinesToLogical.put(line, baseLine);
        }
    }

    private void putFeatures(int line, JavaFeatures features) {
        if(featuresMap.containsKey(line)) {
            JavaFeatures old = (JavaFeatures) featuresMap.get(line);
            old.merge(features);
        } else {
            featuresMap.put(line, features);
        }
    }

}

framework/src/main/java/ppt4j/util/StringUtils.java
public class StringUtils {

    @Property("user.home")
    private static String USER_HOME;

    @Property("ppt4j.database.root")
    private static String DB_ROOT;

    @Property("ppt4j.database.prepatch.name")
    private static String PREPATCH_NAME;

    @Property("ppt4j.database.postpatch.name")
    private static String POSTPATCH_NAME;

    @Property("ppt4j.classpath")
    private static String CLASSPATH;

    public static String printSet(Set<?> set) {
        return printSet(set, false, false);
    }

    public static String printSet(Set<?> set, boolean emphasizeString, boolean printType) {
        if(set.isEmpty()) {
            return "[]";
        }
        StringBuilder sb = new StringBuilder();
        sb.append("[ ");
        Object[] arr = set.toArray();
        for (int i = 0; i < set.size(); i++) {
            Object s = arr[i];
            if(emphasizeString && s instanceof String) {
                sb.append(String.format("\"%s\"", s));
            } else {
                sb.append(String.format("%s", s));
            }
            if(printType) {
                sb.append(String.format(":%s", s.getClass().getSimpleName()));
            }
            if(i != set.size() - 1) {
                sb.append(", ");
            }
        }
        sb.append(" ]");
        return sb.toString();
    }

    public static String convertToDescriptor(String type) {
        type = type.replaceAll("<.*>", "");
        type = type.trim().replace(".", "/");
        return switch (type) {
            case "" -> "";
            case "<unknown>", "void" -> "V";
            case "boolean" -> "Z";
            case "byte" -> "B";
            case "char" -> "C";
            case "short" -> "S";
            case "int" -> "I";
            case "long" -> "J";
            case "float" -> "F";
            case "double" -> "D";
            default -> {
                if(type.endsWith("[]")) {
                    String baseType = type.substring(0, type.length() - 2);
                    yield String.format("[%s", convertToDescriptor(baseType));
                } else {
                    yield String.format("L%s;", type);
                }
            }
        };
    }

    public static boolean isPrimitive(String type) {
        if(type.startsWith("java.lang.")) {
            type = type.substring("java.lang.".length());
        }
        return switch (type) {
            case "Boolean", "Byte", "Character", "Short",
                    "Integer", "Long", "Float", "Double",
                    "boolean", "byte", "char", "short", "int", "long", "float",
                    "double", "Z", "B", "C", "S", "I", "J", "F", "D" -> true;
            default -> false;
        };
    }

    public static String convertMethodSignature(
            String nameAndArgs, String returnType) {
        String name = nameAndArgs.substring(0, nameAndArgs.indexOf("("));
        String[] args = nameAndArgs.substring(
                    nameAndArgs.indexOf("(") + 1, nameAndArgs.indexOf(")")
                ).split(",");
        StringBuilder sb = new StringBuilder();
        sb.append(name);
        sb.append(":(");
        for (String arg : args) {
            sb.append(convertToDescriptor(arg));
        }
        sb.append(")");
        sb.append(convertToDescriptor(returnType));
        return sb.toString();
    }

    public static String convertQualifiedName(String name, String className) {
        return convertQualifiedName(name, className, true);
    }

    public static String convertQualifiedName(String name,
                                              String className,
                                              boolean retainClassName) {
        String _className = name.split("#")[0];
        if((_className.equals(className) || isPrimitive(_className)) && !retainClassName) {
            return name.split("#")[1];
        } else {
            return _className.replace(".", "/")
                    + "." + name.split("#")[1];
        }
    }

    public static boolean isJavaComment(String line) {
        String trimmed = line.trim();
        return trimmed.matches("//.*") || trimmed.matches("/\\*.*\\*/");
    }

    public static boolean isJavaCode(String line) {
        if(isJavaComment(line)) {
            return false;
        }
        String trimmed = line.trim();
        if(trimmed.isEmpty()) {
            return false;
        }
        String[] patterns = {
                "\\{",
                "\\}",
                "\\};",
                "else",
                "\\}\\s*else\\s*\\{",
                "else\\s*\\{",
                "\\}\\s*else",
        };
        for (String pattern : patterns) {
            if(trimmed.matches(pattern)) {
                return false;
            }
        }
        return true;
    }

    public static String trimJavaCodeLine(String codeLine) {
        codeLine = codeLine.replaceAll("//.*", "");
        codeLine = codeLine.replaceAll("/\\*.*\\*/", "");
        codeLine = codeLine.replaceAll("\\s+$", "");
        codeLine = codeLine.replaceAll("^\\s+", "");
        if (codeLine.endsWith("{")) {
            codeLine = codeLine.substring(0, codeLine.length() - 1);
        }
        return codeLine.trim();
    }

    public static @NonNull Main.Command matchPrefix(String prefix) {
        Main.Command matched = null;
        for (Main.Command command : Main.Command.values()) {
            if(command.name.startsWith(prefix.toLowerCase())) {
                if(matched != null) {
                    System.err.println("Ambiguous command: " + prefix);
                    System.err.println("Possible commands:");
                    for (Main.Command c : Main.Command.values()) {
                        if(c.name.startsWith(prefix)) {
                            System.err.println(c.name);
                        }
                    }
                    System.exit(1);
                }
                matched = command;
            }
        }
        if(matched == null) {
            System.err.println("Unknown command: " + prefix);
            System.exit(1);
        }
        return matched;
    }

    public static byte[] toBytes(List<String> lines) {
        return String.join("\n", lines).getBytes();
    }

    @SuppressWarnings("HttpUrlsUsage")
    public static URL toURL(String path) {
        try {
            if(path.startsWith("http://") || path.startsWith("https://")) {
                return new URL(path);
            } else {
                return new File(path).toURI().toURL();
            }
        } catch (MalformedURLException e) {
            throw new IllegalStateException(e);
        }
    }

    public static String extractClassName(String filePath,
                                          String topLevelPrefix) {
        if(topLevelPrefix.endsWith("/")) {
            topLevelPrefix =
                    topLevelPrefix.substring(0, topLevelPrefix.length() - 1);
        }
        String className = filePath.substring(
                topLevelPrefix.length() + 1, filePath.length() - 5
        );
        return className.replace("/", ".");
    }

    public static String buildMethodSignature(MethodInsnNode minsn, BasicValue[] args) {
        String returnType = minsn.desc.split("\\)")[1];
        StringBuilder argTypes = new StringBuilder();
        for(BasicValue arg : args) {
            argTypes.append(arg.getType().getDescriptor());
        }
        return "(" + argTypes + ")" + returnType;
    }

    public static String substringBetween(String str, char c1, char c2) {
        int start = str.indexOf(c1);
        int end = str.lastIndexOf(c2);
        if(start == -1 || end == -1) {
            return null;
        }
        return str.substring(start + 1, end);
    }

    public static String toWrapperType(String type) {
        return switch (type) {
            case "boolean" -> "java/lang/Boolean";
            case "byte" -> "java/lang/Byte";
            case "char" -> "java/lang/Character";
            case "short" -> "java/lang/Short";
            case "int" -> "java/lang/Integer";
            case "long" -> "java/lang/Long";
            case "float" -> "java/lang/Float";
            case "double" -> "java/lang/Double";
            default -> type;
        };
    }

    public static String resolvePath(String path) {
        // if the path begins with ~, replace it with the home directory
        if(path.startsWith("~")) {
            path = USER_HOME + path.substring(1);
        }
        return path;
    }

    public static String[] splitArgDesc(String desc) {
        String argDescs = substringBetween(desc, '(', ')');
        if(argDescs == null || argDescs.isEmpty()) {
            return new String[0];
        }
        List<String> args = new ArrayList<>();
        for(int i = 0; i < argDescs.length(); i++) {
            char c = argDescs.charAt(i);
            if(c == 'L') {
                int end = argDescs.indexOf(';', i);
                args.add(argDescs.substring(i, end + 1));
                i = end;
            } else if(c == '[') {
                int end = i;
                while(argDescs.charAt(end) == '[') {
                    end++;
                }
                if(argDescs.charAt(end) == 'L') {
                    end = argDescs.indexOf(';', end);
                }
                args.add(argDescs.substring(i, end + 1));
                i = end;
            } else {
                args.add(String.valueOf(c));
            }
        }
        return args.toArray(String[]::new);
    }

    public static String joinArgDesc(List<String> descList) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for(String desc : descList) {
            sb.append(desc);
        }
        sb.append(")");
        return sb.toString();
    }

    public static String getDatabasePrepatchSrcPath(Vulnerability vuln) {
        int id = vuln.getDatabaseId();
        String innerPath =  "/" + vuln.getJavaSrcTopLevelDir();
        return String.format("%s/%s/%d%s",
                DB_ROOT, PREPATCH_NAME, id, innerPath);
    }

    public static String getDatabasePostpatchSrcPath(Vulnerability vuln) {
        int id = vuln.getDatabaseId();
        String innerPath = "/" + vuln.getJavaSrcTopLevelDir();
        return String.format("%s/%s/%d%s",
                DB_ROOT, POSTPATCH_NAME, id, innerPath);
    }

    public static String getDatabasePrepatchClassPath(Vulnerability vuln) {
        int id = vuln.getDatabaseId();
        return resolvePath(String.format("%s/%s/%d/%s",
                DB_ROOT, PREPATCH_NAME, id, vuln.getClassesTopLevelDir()));
    }

    public static String getDatabasePostpatchClassPath(Vulnerability vuln) {
        int id = vuln.getDatabaseId();
        return resolvePath(String.format("%s/%s/%d/%s",
                DB_ROOT, POSTPATCH_NAME, id, vuln.getClassesTopLevelDir()));
    }

    public static String[] getThirdPartySrcDirsFromPrepatch(
            Vulnerability vuln) {
        int id = vuln.getDatabaseId();
        String[] thirdParties = vuln.getThirdPartySrcDirs().clone();
        for(int i = 0;i < thirdParties.length;i++) {
            thirdParties[i] = String.format("%s/%s/%d/%s",
                DB_ROOT, PREPATCH_NAME, id, thirdParties[i]);
            thirdParties[i] = resolvePath(thirdParties[i]);
        }
        return thirdParties;
    }

    public static String[] getThirdPartyLibDirsFromPrepatch(
            Vulnerability vuln) {
        int id = vuln.getDatabaseId();
        String[] thirdParties = vuln.getThirdPartyLibDirs().clone();
        for(int i = 0;i < thirdParties.length;i++) {
            thirdParties[i] = String.format("%s/%s/%d/%s",
                DB_ROOT, PREPATCH_NAME, id, thirdParties[i]);
            thirdParties[i] = resolvePath(thirdParties[i]);
        }
        return thirdParties;
    }

    public static int extractDatabaseId(String name) {
        // pattern: VUL4J-%d
        // extract the number and return it
        if(name.matches("VUL4J-\\d+")) {
            return Integer.parseInt(name.substring(6));
        } else {
            throw new IllegalStateException("Invalid database name: " + name);
        }
    }

    public static String getJavaSrcTopLevelDir(VulnerabilityInfo info) {
        if(info.src_top_level_dir != null) {
            return info.src_top_level_dir;
        }
        if(info.build_system.equals("Maven")) {
            String targetDir = info.src_classes_dir;
            if(targetDir.equals("target/classes")) {
                return "src/main/java";
            }
            int index = targetDir.indexOf("/target/classes");
            if(index == -1) {
                throw new IllegalStateException("Invalid target directory: " + targetDir);
            }
            String base = targetDir.substring(0, index);
            return base + "/src/main/java";
        }
        if(info.build_system.equals("Gradle")) {
            String targetDir = info.src_classes_dir;
            if(targetDir.equals("build/classes/java/main") ||
                    targetDir.equals("build/classes/main")) {
                return "src/main/java";
            }
            int index = targetDir.indexOf("/build/classes/java/main");
            if(index == -1) {
                index = targetDir.indexOf("/build/classes/main");
            }
            if(index == -1) {
                throw new IllegalStateException("Invalid target directory: " + targetDir);
            }
            String base = targetDir.substring(0, index);
            return base + "/src/main/java";
        }
        throw new IllegalStateException("Must specify src_top_level_dir with custom build system");
    }

    public static String buildNewStringArrayCode(String[] arr) {
        if(arr == null || arr.length == 0) {
            return "new String[0]";
        }
        StringBuilder sb = new StringBuilder();
        sb.append("new String[] {");
        for(String e : arr) {
            sb.append("\"").append(e).append("\",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("}");
        return sb.toString();
    }

    public static String getThirdPartySrcDirsString(VulnerabilityInfo info) {
        return buildNewStringArrayCode(info.third_party_src_dirs);
    }

    public static String getClassPathToLoad(Vulnerability vuln) {

        return String.join(File.pathSeparator,
                CLASSPATH,
                getDatabasePrepatchClassPath(vuln),
                getDatabasePostpatchClassPath(vuln),
                String.join(File.pathSeparator,
                        getThirdPartyLibDirsFromPrepatch(vuln)));
    }

    public static String getThirdPartyLibDirsString(VulnerabilityInfo info) {
        return buildNewStringArrayCode(info.third_party_lib_dirs);
    }
}

framework/src/main/java/ppt4j/database/Vulnerability.java
@SuppressWarnings("unused")
public interface Vulnerability extends Serializable {

    default int getDatabaseId() {
        return this.getClass().getAnnotation(Database.class).id();
    }

    default String getCVEId() {
        return this.getClass().getAnnotation(Database.class).value();
    }

    default String getProjectName() {
        return getRepoUrl().substring(getRepoUrl().lastIndexOf('/') + 1);
    }

    String getRepoUrl();

    String getPatchCommitHash();

    default String getDiffUrl() {
        return getRepoUrl() + "/commit/" + getPatchCommitHash() + ".diff";
    }

    String getJavaSrcTopLevelDir();

    String getClassesTopLevelDir();

    default String[] getThirdPartySrcDirs() {
        return new String[0];
    }

    default String[] getThirdPartyLibDirs() {
        return new String[0];
    }

    default boolean shouldScanAllModules() {
        return false;
    }

    default String[] getIgnoredFilePatterns() {
        return new String[]{
                ".*Test.*", ".*Issue.*", ".*src/test/.*", ".*/package-info\\.java",
        };
    }

    default String[] getRequiredFilePatterns() {
        return new String[]{
                ".*\\.java"
        };
    }

    default void dump(String path) {
        FileUtils.serializeObject(this, path);
    }

    static Vulnerability load(String path) {
        return FileUtils.deserializeObject(Vulnerability.class, path);
    }

}

framework/src/main/java/ppt4j/database/DatabaseType.java
public enum DatabaseType {

    PREPATCH, POSTPATCH;

    @Property("ppt4j.database.prepatch.name")
    private static String PREPATCH_DIR;

    @Property("ppt4j.database.postpatch.name")
    private static String POSTPATCH_DIR;

    @Property("ppt4j.database.root")
    private static String DATABASE_ROOT;

    public String getPath() {
        String subDir = this == PREPATCH ? PREPATCH_DIR : POSTPATCH_DIR;
        return Path.of(StringUtils.resolvePath(DATABASE_ROOT), subDir).toString();
    }

    public String getPath(int id) {
        return Path.of(getPath(), String.valueOf(id)).toString();
    }

    public String toString() {
        return this == PREPATCH ? "prepatch" : "postpatch";
    }

}

framework/src/main/java/ppt4j/feature/FeatureMatcher.java
@SuppressWarnings("unused")
public interface FeatureMatcher {

    String getAlgorithm();

    @SuppressWarnings({"SwitchStatementWithTooFewBranches", "EnhancedSwitchMigration"})
    static FeatureMatcher get(String algorithm) {
        switch (algorithm) {
            case "jaccard":
                return new JaccardMatcher();
            default:
                throw new IllegalArgumentException(
                        "Unknown algorithm: " + algorithm);
        }
    }

    static FeatureMatcher get() {
        return get("jaccard");
    }

    double match(Features f1, Features f2);

    default boolean isMatch(Features f1, Features f2, double threshold) {
        return match(f1, f2) >= threshold;
    }

}

framework/src/main/java/ppt4j/diff/DiffParser.java
@Log4j
public class DiffParser {

    private final List<Diff> diffs;

    private final List<List<Pair<Integer, Line>>>
            diffLines = new ArrayList<>();

    private final List<FileDiff> fileDiffs = new ArrayList<>();

    private boolean downloadIntegrityCheck = false;

    @Log4j
    static class SigintHandler extends Thread {

        private final DiffParser diff;
        private final File f;

        public SigintHandler(DiffParser diff, File f) {
            this.diff = diff;
            this.f = f;
        }

        public void register() {
            Runtime.getRuntime().addShutdownHook(this);
        }

        @Override
        @SuppressWarnings("ResultOfMethodCallIgnored")
        public void run() {
            if (!diff.downloadIntegrityCheck) {
                log.error("Download incomplete");
                f.delete();
            }
        }

    }

    public DiffParser(URL url) throws IOException {
        FileUtils.makeLocalDirectory(".temp");
        boolean web = url.getProtocol().equals("http")
                || url.getProtocol().equals("https");
        if (web) {
            log.trace("Downloading diff file");
            log.trace("URL: " + url);
        }
        byte[] buf = download(url);
        if(web) {
            log.trace("Download complete");
        }
        UnifiedDiffParser parser = new UnifiedDiffParser();
        diffs = parser.parse(preprocess(buf));
        buildDiffLines();
        for (List<Pair<Integer, Line>> diffLine : diffLines) {
            fileDiffs.add(new FileDiff(diffLine));
        }
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public byte[] download(URL url) throws IOException {
        byte[] buf = null;
        File f = new File(".temp/" + url.toString().hashCode());
        if(f.exists()) {
            FileInputStream fis = new FileInputStream(f);
            buf = fis.readAllBytes();
            fis.close();
        } else {
            f.createNewFile();
            new SigintHandler(this, f).register();
            try {
                URLConnection socketConn = url.openConnection();
                socketConn.setConnectTimeout(10000);
                socketConn.setReadTimeout(20000);
                BufferedInputStream bis = new BufferedInputStream(socketConn.getInputStream());
                FileOutputStream fos = new FileOutputStream(f);
                buf = bis.readAllBytes();
                fos.write(buf);
                fos.close();
                bis.close();
                downloadIntegrityCheck = true;
            } catch (IOException e) {
                log.error(e);
                System.exit(1);
            }
        }
        return buf;
    }

    public DiffParser(String patchPath) throws IOException {
        this(StringUtils.toURL(patchPath));
    }

    public int getNumOfDiffs() {
        return diffs.size();
    }

    public String getFileName(int diffIndex, boolean removePrefix) {
        String file = diffs.get(diffIndex).getFromFileName();
        if(file.equals("/dev/null")) {
            file = diffs.get(diffIndex).getToFileName();
        }
        if(removePrefix) {
            file = file.substring(file.indexOf('/') + 1);
        }
        return file;
    }

    public FileDiff getFileDiff(int diffIndex) {
        return fileDiffs.get(diffIndex);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < fileDiffs.size(); i++) {
            FileDiff fileDiff = fileDiffs.get(i);
            sb.append(getFileName(i, true)).append("\n");
            sb.append(fileDiff.toString()).append("\n");
        }
        return sb.toString();
    }

    private void buildDiffLines() {
        diffs.forEach(diff -> {
            List<Pair<Integer, Line>> lines = new ArrayList<>();
            diff.getHunks().forEach(hunk -> {
                int fromLine = hunk.getFromFileRange().getLineStart() - 1;
                int toLine = hunk.getToFileRange().getLineStart() - 1;
                List<Line> hunkLines = hunk.getLines();
                for (Line line : hunkLines) {
                    if (line.getLineType() == Line.LineType.FROM) {
                        fromLine++;
                        lines.add(Pair.of(fromLine, line));
                    } else if (line.getLineType() == Line.LineType.TO) {
                        toLine++;
                        lines.add(Pair.of(toLine, line));
                    } else {
                        fromLine++;
                        toLine++;
                    }
                }
            });
            diffLines.add(lines);
        });
    }

    // insert '\n' before the pattern "diff --git"
    private byte[] preprocess(byte[] buf) {
        String content = new String(buf);
        String[] lines = content.split("\n");
        List<String> newLines = new ArrayList<>();
        for(int i = 0;i < lines.length;i++) {
            String line = lines[i];
            if(line.startsWith("diff --git") && i > 0) {
                newLines.add("");
            }
            newLines.add(line);
        }
        return StringUtils.toBytes(newLines);
    }

}

framework/src/main/java/ppt4j/factory/ExtractorFactory.java
@Log4j
public final class ExtractorFactory {

    String prepatchPath, postpatchPath;
    String[] thirdPartySrcPath;

    @Getter
    String classPath;

    boolean isJar = false;
    private JarFile jarFile = null;

    @Setter
    Vulnerability vuln = null;

    final Map<String, JavaExtractor> cachedPreExtractors = new HashMap<>();
    final Map<String, JavaExtractor> cachedPostExtractors = new HashMap<>();
    final Map<String, BytecodeExtractor> cachedBytecodeExtractors = new HashMap<>();

    final Map<String, CrossMatcher> cachedPre2Class = new HashMap<>();
    final Map<String, CrossMatcher> cachedPost2Class = new HashMap<>();

    public static ExtractorFactory get(String prepatchPath,
                                       String postpatchPath,
                                       String classPath,
                                       String... thirdPartySrcPath) {
        return new ExtractorFactory(prepatchPath, postpatchPath,
                classPath, thirdPartySrcPath);
    }

    public static ExtractorFactory get(Vulnerability vuln,
                                       DatabaseType type) {
        String prepatchPath = StringUtils.getDatabasePrepatchSrcPath(vuln);
        String postpatchPath = StringUtils.getDatabasePostpatchSrcPath(vuln);
        String classPath = Path.of(
                                type.getPath(vuln.getDatabaseId()),
                                vuln.getClassesTopLevelDir()
                            ).toString();
        VMUtils.checkVMClassPathPresent(classPath);
        String[] thirdPartySrcPath = StringUtils.getThirdPartySrcDirsFromPrepatch(vuln);
        ExtractorFactory factory = get(prepatchPath, postpatchPath,
                classPath, thirdPartySrcPath);
        factory.vuln = vuln;
        return factory;
    }

    ExtractorFactory(String prepatchPath,
                     String postpatchPath,
                     String classPath,
                     String... thirdPartySrcPath) {
        this.prepatchPath = StringUtils.resolvePath(prepatchPath);
        this.postpatchPath = StringUtils.resolvePath(postpatchPath);
        this.thirdPartySrcPath = thirdPartySrcPath;

        this.classPath = StringUtils.resolvePath(classPath);

        if(classPath.endsWith("jar")) {
            isJar = true;
            try {
                jarFile = new JarFile(this.classPath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public JavaExtractor getPreJavaClass(String className) {
        return getJavaExtractor(className, cachedPreExtractors, DatabaseType.PREPATCH);
    }

    public JavaExtractor getPostJavaClass(String className) {
        return getJavaExtractor(className, cachedPostExtractors, DatabaseType.POSTPATCH);
    }

    @SuppressWarnings("all")
    private static class CuFilter implements CompilationUnitFilter {

        private final Set<String> includeClasses = new HashSet<>();

        private final String moduleName;
        private final String basePath;

        public CuFilter(String basePath, String className, Set<String> includeClasses) {
            this.basePath = basePath;
            this.moduleName = className.substring(0, className.lastIndexOf("."));
            this.includeClasses.addAll(includeClasses);
        }

        private boolean _exclude(String s) {
            String mod = s.substring(0, s.lastIndexOf("."));
            if(mod.endsWith(moduleName)) {
                return false;
            }
            for (String className : includeClasses) {
                if(s.endsWith(className)) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public boolean exclude(String s) {
            s = s.substring(0, s.length() - 5);
            s = s.replace("/", ".");
            return _exclude(s);
        }
    }

    private JavaExtractor getJavaExtractor(String className,
           Map<String, JavaExtractor> cachedExtractors, DatabaseType type) {
        if(cachedExtractors.containsKey(className)) {
            return cachedExtractors.get(className);
        }
        if(vuln != null) {
            InputStream is = ResourceUtils.readSerializedFile(vuln.getDatabaseId(), type, className);
            if(is != null) {
                JavaExtractor extractor = FileUtils.deserializeObject(JavaExtractor.class, is);
                assert extractor != null;
                cachedExtractors.put(className, extractor);
                return extractor;
            }
        }
        Launcher temp = new Launcher();
        String basePath = type == DatabaseType.PREPATCH ? prepatchPath : postpatchPath;
        String path = Path.of(basePath, className.replace(".", "/") + ".java").toString();
        if(!new File(path).exists()) {
            log.debug("File not found: " + path);
            return JavaExtractor.nil();
        }
        temp.addInputResource(path);
        temp.buildModel();
        ImportScannerImpl importScanner = new ImportScannerImpl();
        importScanner.scan(temp.getFactory().Class().get(className));
        Set<String> includeClasses = new HashSet<>();
        for(CtImport ctImport: importScanner.getAllImports()) {
            if(ctImport.getReference() instanceof CtTypeReference<?> ty) {
                includeClasses.add(ty.getQualifiedName());
            }
        }
        CuFilter filter = new CuFilter(basePath, className, includeClasses);
        Launcher launcher = new Launcher();
        launcher.addInputResource(basePath);
        Arrays.stream(thirdPartySrcPath).forEach(launcher::addInputResource);
        launcher.getEnvironment().setPreserveLineNumbers(true);
        launcher.getEnvironment().setIgnoreDuplicateDeclarations(true);
        try {
            launcher.getModelBuilder().addCompilationUnitFilter(filter);
            launcher.buildModel();
        } catch (Throwable e) {
            launcher = new Launcher();
            launcher.addInputResource(basePath);
            Arrays.stream(thirdPartySrcPath).forEach(launcher::addInputResource);
            launcher.getEnvironment().setPreserveLineNumbers(true);
            launcher.getEnvironment().setIgnoreDuplicateDeclarations(true);
            launcher.buildModel();
        }
        CtClass<?> clazz = launcher.getFactory().Class().get(className);
        if(clazz == null) {
            return JavaExtractor.nil();
        }
        JavaExtractor ex = new JavaExtractor(clazz);
        ex.parse();
        cachedExtractors.put(className, ex);
        return ex;
    }

    public BytecodeExtractor getBytecodeClass(String className) throws IOException {
        if(cachedBytecodeExtractors.containsKey(className)) {
            return cachedBytecodeExtractors.get(className);
        }
        BytecodeExtractor ex;
        if(isJar) {
            JarEntry entry = jarFile.getJarEntry(
                    className.replace('.', '/') + ".class");
            try {
                if(entry == null) {
                    throw new IOException("Entry is null");
                }
                InputStream is = jarFile.getInputStream(entry);
                ex = new BytecodeExtractor(is);
            } catch (IOException e) {
                log.debug("Bytecode of class " + className + " not found.");
                return BytecodeExtractor.nil();
            }
            for (Iterator<JarEntry> it = jarFile.entries().asIterator(); it.hasNext(); ) {
                JarEntry entry1 = it.next();
                if(entry1.getName().startsWith(className.replace('.', '/') + "$")) {
                    log.debug("Adding inner class " + entry1.getName() + " from jar file.");
                    BytecodeExtractor innerEx = new BytecodeExtractor(jarFile.getInputStream(entry1));
                    ex.putInnerClass(innerEx);
                }
            }
        } else {
            String dir = Path.of(classPath,
                    className.replace('.', '/') + ".class").toString();
            try {
                ex = new BytecodeExtractor(dir);
            } catch (IOException e) {
                log.debug("Bytecode of class " + className + " not found.");
                return BytecodeExtractor.nil();
            }
            dir = dir.substring(0, dir.lastIndexOf('/'));
            File packageDir = new File(dir);
            assert packageDir.exists() && packageDir.isDirectory();
            File[] files = packageDir.listFiles();
            assert files != null;
            String regex = ".*" + className.replace('.', '/') + "\\$.*\\.class";
            for (File file : files) {
                if (file.getAbsolutePath().matches(regex)) {
                    BytecodeExtractor innerEx = new BytecodeExtractor(file.getAbsolutePath());
                    ex.putInnerClass(innerEx);
                }
            }
        }
        ex.parse();
        cachedBytecodeExtractors.put(className, ex);
        return ex;
    }

    public CrossMatcher getPre2Class(String className) throws IOException {
        if(cachedPre2Class.containsKey(className)) {
            return cachedPre2Class.get(className);
        }
        CrossMatcher matcher = CrossMatcher.get(getPreJavaClass(className),
                getBytecodeClass(className), false);
        cachedPre2Class.put(className, matcher);
        return matcher;
    }

    public CrossMatcher getPost2Class(String className) throws IOException {
        if(cachedPost2Class.containsKey(className)) {
            return cachedPost2Class.get(className);
        }
        CrossMatcher matcher = CrossMatcher.get(getPostJavaClass(className),
                getBytecodeClass(className), true);
        cachedPost2Class.put(className, matcher);
        return matcher;
    }

}

Answer: private static double PATCH_PRESENCE_THRESHOLD;

<think>
The PatchAnalyzer file starts with a @Property annotation for "ppt4j.analysis.patch.presence_threshold". In other classes, such @Property annotations are followed by a private static field of appropriate type (e.g., double for thresholds). Therefore the next line should declare the static field that holds this configuration value.
</think>
private static double PRESENCE_THRESHOLD;