/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.asm.mixin.struct; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.util.Bytecode; /** * Structure which contains information about a SourceDebugExtension SMAP */ public class SourceMap { private static final String DEFAULT_STRATUM = "Mixin"; private static final String NEWLINE = "\n"; /** * Defines a source code file within a source map stratum */ public static class File { /** * File index in stratum */ public final int id; /** * The base line offset for this stratum, line numbers in the output * will be offset by this amount from their originals */ public final int lineOffset; /** * The size of this stratum (number of lines) */ public final int size; /** * Actual source file name to include in the smap */ public final String sourceFileName; /** * Full path to the source file */ public final String sourceFilePath; /** * Create a new SMAP Stratum * * @param lineOffset line offset * @param size total lines * @param sourceFileName source file name */ public File(int id, int lineOffset, int size, String sourceFileName) { this(id, lineOffset, size, sourceFileName, null); } /** * Create a new SMAP Stratum * * @param lineOffset line offset * @param size total lines * @param sourceFileName source file name * @param sourceFilePath source path */ public File(int id, int lineOffset, int size, String sourceFileName, String sourceFilePath) { this.id = id; this.lineOffset = lineOffset; this.size = size; this.sourceFileName = sourceFileName; this.sourceFilePath = sourceFilePath; } /** * Offset the line numbers in the target class node by the base * lineoffset for this stratum * * @param classNode class to operate upon */ public void applyOffset(ClassNode classNode) { for (MethodNode method : classNode.methods) { this.applyOffset(method); } } /** * Offset the line numbers in the target method node by the base * lineoffset for this stratum * * @param method method to operate upon */ public void applyOffset(MethodNode method) { for (Iterator iter = method.instructions.iterator(); iter.hasNext();) { AbstractInsnNode node = iter.next(); if (node instanceof LineNumberNode) { ((LineNumberNode)node).line += this.lineOffset - 1; } } } void appendFile(StringBuilder sb) { if (this.sourceFilePath != null) { sb.append("+ ").append(this.id).append(" ").append(this.sourceFileName).append(SourceMap.NEWLINE); sb.append(this.sourceFilePath).append(SourceMap.NEWLINE); } else { sb.append(this.id).append(" ").append(this.sourceFileName).append(SourceMap.NEWLINE); } } /** * Append lines representing this File to the supplied StringBuilder * * @param sb StringBuilder to append to */ public void appendLines(StringBuilder sb) { sb.append("1#").append(this.id) // Map line number 1 (onwards) in file number .append(",").append(this.size) // repeated times (eg. lines 1 to ) .append(":").append(this.lineOffset) // to output line number lineOffset in the output file .append(SourceMap.NEWLINE); } } /** * Defines a source code stratum within the source map */ static class Stratum { private static final String STRATUM_MARK = "*S"; private static final String FILE_MARK = "*F"; private static final String LINES_MARK = "*L"; /** * Stratum name */ public final String name; private final Map files = new LinkedHashMap(); public Stratum(String name) { this.name = name; } public File addFile(int lineOffset, int size, String sourceFileName, String sourceFilePath) { File file = this.files.get(sourceFilePath); if (file == null) { file = new File(this.files.size() + 1, lineOffset, size, sourceFileName, sourceFilePath); this.files.put(sourceFilePath, file); } return file; } void appendTo(StringBuilder sb) { sb.append(Stratum.STRATUM_MARK).append(" ").append(this.name).append(SourceMap.NEWLINE); sb.append(Stratum.FILE_MARK).append(SourceMap.NEWLINE); for (File file : this.files.values()) { file.appendFile(sb); } sb.append(Stratum.LINES_MARK).append(SourceMap.NEWLINE); for (File file : this.files.values()) { file.appendLines(sb); } } } /** * Original source file name */ private final String sourceFile; /** * SMAP strata */ private final Map strata = new LinkedHashMap(); private int nextLineOffset = 1; private String defaultStratum = SourceMap.DEFAULT_STRATUM; public SourceMap(String sourceFile) { this.sourceFile = sourceFile; } /** * Get the original source file */ public String getSourceFile() { return this.sourceFile; } /** * Get the generated source file */ public String getPseudoGeneratedSourceFile() { return this.sourceFile.replace(".java", "$mixin.java"); } /** * Add a file to this SourceMap in the default stratum * * @param classNode class node to read details from * @return new File */ public File addFile(ClassNode classNode) { return this.addFile(this.defaultStratum, classNode); } /** * Add a file to this SourceMap in the specified stratum * * @param stratumName name of the stratum to add to * @param classNode class node to read file details from * @return new File */ public File addFile(String stratumName, ClassNode classNode) { return this.addFile(stratumName, classNode.sourceFile, classNode.name + ".java", Bytecode.getMaxLineNumber(classNode, 500, 50)); } /** * Add a file to this SourceMap in the default stratum * * @param sourceFileName source filename * @param sourceFilePath path to source file * @param size number of lines to allocate * @return new File */ public File addFile(String sourceFileName, String sourceFilePath, int size) { return this.addFile(this.defaultStratum, sourceFileName, sourceFilePath, size); } /** * Add a file to this SourceMap in the specified stratum * * @param stratumName name of the stratum to add to * @param sourceFileName source filename * @param sourceFilePath path to source file * @param size number of lines to allocate * @return new File */ public File addFile(String stratumName, String sourceFileName, String sourceFilePath, int size) { Stratum stratum = this.strata.get(stratumName); if (stratum == null) { stratum = new Stratum(stratumName); this.strata.put(stratumName, stratum); } File file = stratum.addFile(this.nextLineOffset, size, sourceFileName, sourceFilePath); this.nextLineOffset += size; return file; } @Override public String toString() { StringBuilder sb = new StringBuilder(); this.appendTo(sb); return sb.toString(); } private void appendTo(StringBuilder sb) { // SMAP header sb.append("SMAP").append(SourceMap.NEWLINE); // Source file sb.append(this.getSourceFile()).append(SourceMap.NEWLINE); // Default stratum sb.append(this.defaultStratum).append(SourceMap.NEWLINE); for (Stratum stratum : this.strata.values()) { stratum.appendTo(sb); } // End sb.append("*E").append(SourceMap.NEWLINE); } }