All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.neoforged.art.internal.JavadoctorRemapper Maven / Gradle / Ivy

There is a newer version: 2.0.5
Show newest version
/*
 * Copyright (c) Forge Development LLC and contributors
 * SPDX-License-Identifier: LGPL-2.1-only
 */

package net.neoforged.art.internal;

import net.neoforged.javadoctor.spec.ClassJavadoc;
import net.neoforged.javadoctor.spec.DocReferences;
import net.neoforged.javadoctor.spec.JavadocEntry;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Type;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.objectweb.asm.Type.ARRAY;
import static org.objectweb.asm.Type.BOOLEAN;
import static org.objectweb.asm.Type.BYTE;
import static org.objectweb.asm.Type.CHAR;
import static org.objectweb.asm.Type.DOUBLE;
import static org.objectweb.asm.Type.FLOAT;
import static org.objectweb.asm.Type.INT;
import static org.objectweb.asm.Type.LONG;
import static org.objectweb.asm.Type.OBJECT;
import static org.objectweb.asm.Type.SHORT;

public class JavadoctorRemapper {
    public static final Pattern LINKS = Pattern.compile("@(?link|linkplain|see|value)(?\\s+)(?[\\w$.]*)(?:#(?[\\w%]+)?(?\\((?[\\w$., \\[\\]]+)?\\))?)?");
    public static final Pattern LINKS_IN = Pattern.compile("^(?[\\w$.]*)(?:#(?[\\w%]+)?(?\\((?[\\w$., \\[\\]]+)?\\))?)?");

    private final EnhancedRemapper remapper;
    private final DocReferences references;

    public JavadoctorRemapper(EnhancedRemapper remapper, DocReferences references) {
        this.remapper = remapper;
        this.references = references;
    }

    public ClassJavadoc remap(String containedClass, String containedInternalName, ClassJavadoc doc) {
        final Map inners = new HashMap<>(doc.innerClasses().size(), 1f);
        doc.innerClasses().forEach((name, cdoc) -> {
            final String innerName = containedClass + "." + name;
            final String innerInternal = containedInternalName + "$" + name;
            final String remappedInner = remapper.map(innerInternal);
            inners.put(remappedInner.substring(remappedInner.lastIndexOf('$') + 1), remap(innerName, innerInternal, cdoc));
        });
        return new ClassJavadoc(
                doc.clazz() == null ? null : remap(containedClass, doc.clazz()),
                mapEntries(containedClass, doc.methods(), method -> {
                    final int start = method.indexOf('(');
                    final String name = method.substring(0, start);
                    final String desc = method.substring(start);
                    return remapper.mapMethodName(containedInternalName, name, desc) + remapper.mapMethodDesc(desc);
                }),
                mapEntries(containedClass, doc.fields(), field -> {
                    final String[] nameAndDesc = field.split(":");
                    return remapper.mapFieldName(containedInternalName, nameAndDesc[0], nameAndDesc[1]) + ":" + remapper.mapDesc(nameAndDesc[1]);
                }),
                inners
        );
    }

    @Nullable
    private Map mapEntries(String containedClass, @Nullable Map entries, UnaryOperator remapper) {
        if (entries == null) return null;
        final Map newEntries = new HashMap<>(entries.size(), 1f);
        entries.forEach((key, entry) -> newEntries.put(remapper.apply(key), remap(containedClass, entry)));
        return newEntries;
    }

    private JavadocEntry remap(String containedClass, JavadocEntry entry) {
        return new JavadocEntry(
                entry.doc() == null ? null : replaceLinks(containedClass, LINKS.matcher(entry.doc()), matcher -> "@" + matcher.group(1) + matcher.group(2)),
                entry.tags() == null ? null : mapTags(containedClass, entry.tags()),
                entry.parameters() == null ? null : mapParams(containedClass, entry.parameters()),
                entry.typeParameters() == null ? null : mapParams(containedClass, entry.typeParameters())
        );
    }

    private Map> mapTags(String containedClass, Map> in) {
        final Map> tags = new HashMap<>(in.size(), 1f);
        in.forEach((tagName, values) -> {
            final List newValues = new ArrayList<>(values);
            if (tagName.equals("see")) {
                newValues.replaceAll(seeTag -> replaceLinks(containedClass, LINKS_IN.matcher(seeTag), matcher -> ""));
            } else {
                newValues.replaceAll(tag -> replaceLinks(containedClass, LINKS.matcher(tag), matcher -> "@" + matcher.group(1) + matcher.group(2)));
            }
            tags.put(tagName, newValues);
        });
        return tags;
    }

    private String[] mapParams(String containedClass, String[] params) {
        final String[] newParams = new String[params.length];
        for (int i = 0; i < params.length; i++) {
            String param = params[i];
            if (param != null) {
                param = replaceLinks(containedClass, LINKS.matcher(param), matcher -> "@" + matcher.group(1) + matcher.group(2));
            }
            newParams[i] = param;
        }
        return newParams;
    }

    private String replaceLinks(String containedClass, Matcher matcher, Function prefix) {
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            final String matchedOwner = matcher.group("owner");
            final String owner = references.getInternalName((matchedOwner == null || matchedOwner.isEmpty()) ? containedClass : matchedOwner);
            final String mappedOwner = remapper.map(owner);
            StringBuilder replacement = new StringBuilder().append(prefix.apply(matcher))
                    .append(mappedOwner.replace('/', '.').replace('$', '.'));

            final String member = matcher.group("member");
            if (member != null) {
                replacement.append('#');
                final String descFull = matcher.group("descFull");
                final boolean hasDesc = descFull != null && !descFull.isEmpty();
                String desc = matcher.group("desc");
                if (hasDesc) {
                    final String finalDesc = desc == null ? "" : desc;
                    final String[] descSplit = finalDesc.isEmpty() ? new String[0] : finalDesc.split(",");
                    replacement.append(remapper.mapJavadocMember(owner, member, descSplit.length)
                            .orElseGet(() -> member + "(" + finalDesc + ")"));
                } else {
                    replacement.append(remapper.mapFieldName(owner, member, null));
                }
            } else if (mappedOwner.equals(owner)) {
                matcher.appendReplacement(sb, matcher.group(0));
                continue;
            }

            matcher.appendReplacement(sb, replacement.toString());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    static String getJavadocDesc(Type type) {
        return "(" + Stream.of(type.getArgumentTypes())
                .map(JavadoctorRemapper::getJavadocType)
                .collect(Collectors.joining(", "))
                + ")";
    }

    static String getJavadocType(Type type) {
        switch (type.getSort()) {
            case BOOLEAN: return "boolean";
            case INT: return "int";
            case LONG: return "long";
            case DOUBLE: return "double";
            case FLOAT: return "float";
            case CHAR: return "char";
            case SHORT: return "short";
            case BYTE: return "byte";
            case OBJECT: return type.getInternalName().replace('/', '.');
            case ARRAY: return getJavadocType(type.getElementType()) + "[]";
            default:
                throw new UnsupportedOperationException("Unknown type in javadoc: " + type.getSort());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy