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

net.minecraftforge.gradle.common.util.McpNames Maven / Gradle / Ivy

Go to download

Minecraft mod development framework used by Forge and FML for the gradle build system adapted for mohist api.

The newest version!
/*
 * ForgeGradle
 * Copyright (C) 2018 Forge Development LLC
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

package net.minecraftforge.gradle.common.util;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;

import de.siegmar.fastcsv.reader.NamedCsvReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.annotation.Nullable;

public class McpNames {
    private static final String NEWLINE = System.getProperty("line.separator");
    private static final Pattern SRG_FINDER             = Pattern.compile("[fF]unc_\\d+_[a-zA-Z_]+|m_\\d+_|[fF]ield_\\d+_[a-zA-Z_]+|f_\\d+_|p_\\w+_\\d+_|p_\\d+_");
    private static final Pattern CONSTRUCTOR_JAVADOC_PATTERN = Pattern.compile("^(?(?: {3})+|\\t+)(public |private|protected |)(?<[\\w\\W]*>\\s+)?(?[\\w.]+)\\((?.*)\\)\\s+(?:throws[\\w.,\\s]+)?\\{");
    private static final Pattern METHOD_JAVADOC_PATTERN = Pattern.compile("^(?(?: {3})+|\\t+)(?!return)(?:\\w+\\s+)*(?<[\\w\\W]*>\\s+)?(?\\w+[\\w$.]*(?:<[\\w\\W]*>)?[\\[\\]]*)\\s+(?(?:func_|m_)[0-9]+_[a-zA-Z_]*)\\(");
    private static final Pattern FIELD_JAVADOC_PATTERN  = Pattern.compile("^(?(?: {3})+|\\t+)(?!return)(?:\\w+\\s+)*\\w+[\\w$.]*(?:<[\\w\\W]*>)?[\\[\\]]*\\s+(?(?:field_|f_)[0-9]+_[a-zA-Z_]*) *[=;]");
    private static final Pattern CLASS_JAVADOC_PATTERN  = Pattern.compile("^(? *|\\t*)([\\w|@]*\\s)*(class|interface|@interface|enum) (?[\\w]+)");
    private static final Pattern CLOSING_CURLY_BRACE    = Pattern.compile("^(? *|\\t*)}");
    private static final Pattern PACKAGE_DECL           = Pattern.compile("^[\\s]*package(\\s)*(?[\\w|.]+);$");
    private static final Pattern LAMBDA_DECL            = Pattern.compile("\\((?(?:(?:, ){0,1}p_[\\w]+_\\d+_\\b)+)\\) ->");

    public static McpNames load(File data) throws IOException {
        Map names = new HashMap<>();
        Map docs = new HashMap<>();
        try (ZipFile zip = new ZipFile(data)) {
            List entries = zip.stream().filter(e -> e.getName().endsWith(".csv")).collect(Collectors.toList());
            for (ZipEntry entry : entries) {
                try (NamedCsvReader reader = NamedCsvReader.builder().build(new InputStreamReader(zip.getInputStream(entry)))) {
                    String obf = reader.getHeader().contains("searge") ? "searge" : "param";
                    boolean hasDesc = reader.getHeader().contains("desc");
                    reader.forEach(row -> {
                        String searge = row.getField(obf);
                        names.put(searge, row.getField("name"));
                        if (hasDesc) {
                            String desc = row.getField("desc");
                            if (!desc.isEmpty())
                                docs.put(searge, desc);
                        }
                    });
                }
            }
        }

        return new McpNames(HashFunction.SHA1.hash(data), names, docs);
    }

    private final Map names;
    private final Map docs;
    public final String hash;

    private McpNames(String hash, Map names, Map docs) {
        this.hash = hash;
        this.names = names;
        this.docs = docs;
    }

    public String rename(InputStream stream, boolean javadocs) throws IOException {
        return rename(stream, javadocs, true, StandardCharsets.UTF_8);
    }

    public String rename(InputStream stream, boolean javadocs, boolean lambdas) throws IOException {
        return rename(stream, javadocs, lambdas, StandardCharsets.UTF_8);
    }

    public String rename(InputStream stream, boolean javadocs, boolean lambdas, Charset sourceFileCharset)
            throws IOException {

        String data = IOUtils.toString(stream, sourceFileCharset);
        List input = IOUtils.readLines(new StringReader(data));

        // Return early on emtpy files
        if (data.isEmpty())
            return "";

        //Reader doesn't give us the empty line if the file ends with a newline.. so add one.
        if (data.charAt(data.length() - 1) == '\r' || data.charAt(data.length() - 1) == '\n')
            input.add("");

        List lines = new ArrayList<>();
        Deque> innerClasses = new LinkedList<>(); //pair of inner class name & indentation
        String _package = ""; //default package
        Set blacklist = null;

        if (!lambdas) {
            blacklist = new HashSet<>();
            for (String line : input) {
                Matcher m = LAMBDA_DECL.matcher(line);
                if (!m.find())
                    continue;
                blacklist.addAll(Arrays.asList(m.group("args").split(", ")));
            }
        }

        for (String line : input) {
            Matcher m = PACKAGE_DECL.matcher(line);
            if(m.find())
                _package = m.group("name") + ".";

            if (javadocs) {
                if (!injectJavadoc(lines, line, _package, innerClasses))
                    javadocs = false;
            }
            lines.add(replaceInLine(line, blacklist));
        }
        return String.join(NEWLINE, lines);
    }

    public String rename(String entry) {
        return names.getOrDefault(entry, entry);
    }

    /**
     * Injects a javadoc into the given list of lines, if the given line is a
     * method or field declaration.
     * @param lines The current file content (to be modified by this method)
     * @param line The line that was just read (will not be in the list)
     * @param _package the name of the package this file is declared to be in, in com.example format;
     * @param innerClasses current position in inner class
     */
    private boolean injectJavadoc(List lines, String line, String _package, Deque> innerClasses) {
        // constructors
        Matcher matcher = CONSTRUCTOR_JAVADOC_PATTERN.matcher(line);
        boolean isConstructor = matcher.find() && !innerClasses.isEmpty() && innerClasses.peek().getLeft().contains(matcher.group("name"));
        // methods
        if (!isConstructor)
            matcher = METHOD_JAVADOC_PATTERN.matcher(line);

        if (isConstructor || matcher.find()) {
            String name = isConstructor ? "" : matcher.group("name");
            String javadoc = docs.get(name);
            if (javadoc == null && !innerClasses.isEmpty() && !name.startsWith("func_") && !name.startsWith("m_")) {
                String currentClass = innerClasses.peek().getLeft();
                javadoc = docs.get(currentClass + '#' + name);
            }
            if (javadoc != null)
                insertAboveAnnotations(lines, JavadocAdder.buildJavadoc(matcher.group("indent"), javadoc, true));

            // worked, so return and don't try the fields.
            return true;
        }

        // fields
        matcher = FIELD_JAVADOC_PATTERN.matcher(line);
        if (matcher.find()) {
            String name = matcher.group("name");
            String javadoc = docs.get(name);
            if (javadoc == null && !innerClasses.isEmpty() && !name.startsWith("field_") && !name.startsWith("f_")) {
                String currentClass = innerClasses.peek().getLeft();
                javadoc = docs.get(currentClass + '#' + name);
            }
            if (javadoc != null)
                insertAboveAnnotations(lines, JavadocAdder.buildJavadoc(matcher.group("indent"), javadoc, false));

            return true;
        }

        //classes
        matcher = CLASS_JAVADOC_PATTERN.matcher(line);
        if(matcher.find()) {
            //we maintain a stack of the current (inner) class in com.example.ClassName$Inner format (along with indentation)
            //if the stack is not empty we are entering a new inner class
            String currentClass = (innerClasses.isEmpty() ? _package : innerClasses.peek().getLeft() + "$") + matcher.group("name");
            innerClasses.push(Pair.of(currentClass, matcher.group("indent").length()));
            String javadoc = docs.get(currentClass);
            if (javadoc != null) {
                insertAboveAnnotations(lines, JavadocAdder.buildJavadoc(matcher.group("indent"), javadoc, true));
            }

            return true;
        }

        //detect curly braces for inner class stacking/end identification
        matcher = CLOSING_CURLY_BRACE.matcher(line);
        if(matcher.find()){
            if(!innerClasses.isEmpty()) {
                int len = matcher.group("indent").length();
                if (len == innerClasses.peek().getRight()) {
                    innerClasses.pop();
                } else if (len < innerClasses.peek().getRight()) {
                    System.err.println("Failed to properly track class blocks around class " + innerClasses.peek().getLeft() + ":" + (lines.size() + 1));
                    return false;
                }
            }
        }

        return true;
    }

    /** Inserts the given javadoc line into the list of lines before any annotations */
    private static void insertAboveAnnotations(List list, String line) {
        int back = 0;
        while (list.get(list.size() - 1 - back).trim().startsWith("@"))
            back++;
        list.add(list.size() - back, line);
    }

    /*
     * There are certain times, such as Mixin Accessors that we wish to have the name of this method with the first character upper case.
     */
    private String getMapped(String srg, @Nullable Set blacklist) {
        if (blacklist != null && blacklist.contains(srg))
            return srg;

        boolean cap = srg.charAt(0) == 'F';
        if (cap)
            srg = 'f' + srg.substring(1);

        String ret = names.getOrDefault(srg, srg);
        if (cap)
            ret = ret.substring(0, 1).toUpperCase(Locale.ENGLISH) + ret.substring(1);
        return ret;
    }

    private String replaceInLine(String line, @Nullable Set blacklist) {
        StringBuffer buf = new StringBuffer();
        Matcher matcher = SRG_FINDER.matcher(line);
        while (matcher.find()) {
            // Since '$' is a valid character in identifiers, but we need to NOT treat this as a regex group, escape any occurrences
            matcher.appendReplacement(buf, Matcher.quoteReplacement(getMapped(matcher.group(), blacklist)));
        }
        matcher.appendTail(buf);
        return buf.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy