package me.coley.jremapper.parse; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; import com.github.javaparser.Position; import com.github.javaparser.ast.*; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.*; import com.github.javaparser.resolution.Resolvable; import com.github.javaparser.resolution.declarations.*; import com.github.javaparser.resolution.types.*; import me.coley.jremapper.asm.Input; import me.coley.jremapper.mapping.CMap; import me.coley.jremapper.mapping.Mappings; import me.coley.jremapper.util.*; import org.objectweb.asm.ClassReader; /** * Allows linking regions of text do different mappings using the JavaParser * library. * * For reference: * <ul> * <li>Quantified name: Full name of a class, such as * <i>com.example.MyType</i></li> * <li>Simple name: Short-hand name of a class, such as <i>MyType</i></li> * </ul> * * @author Matt */ public class RegionMapper { private final Input input; private final CompilationUnit cu; private final String className; private final Map<String, CDec> decMap = new HashMap<>(); public RegionMapper(Input input, String className, CompilationUnit cu) { this.input = input; this.cu = cu; this.className = className; } /** * @return Dec of the current class. */ public CDec getHost() { return decMap.get(className); } /** * @param line * Caret line in editor. * @param column * Caret column in editor. * @return CDec at position. May be {@code null}. */ public CDec getClassFromPosition(int line, int column) { Node node = getNodeAt(line, column); if(!(node instanceof Resolvable)) return null; // Resolve node to some declaration type Resolvable<?> r = (Resolvable<?>) node; Object resolved = null; try { resolved = r.resolve(); } catch(Exception ex) { return null; } if (resolved instanceof ResolvedReferenceType) { ResolvedReferenceType type = (ResolvedReferenceType) resolved; return getClassDec(type.getQualifiedName()); } else if (resolved instanceof ResolvedReferenceTypeDeclaration) { ResolvedReferenceTypeDeclaration type = (ResolvedReferenceTypeDeclaration) resolved; return getClassDec(type.getQualifiedName()); } else if (resolved instanceof ResolvedConstructorDeclaration) { ResolvedConstructorDeclaration type = (ResolvedConstructorDeclaration) resolved; return getClassDec(type.getQualifiedName()); } return null; } /** * @param line * Caret line in editor. * @param column * Caret column in editor. * @return MDec at position. May be {@code null}. */ public MDec getMemberFromPosition(int line, int column) { Node node = getNodeAt(line, column); return resolveMethod(node); } /** * @param line * Caret line in editor. * @param column * Caret column in editor. * @return VDec at position. May be {@code null}. */ public VDec getVariableFromPosition(int line, int column) { Node node = getSimpleNodeAt(line, column); if (node instanceof SimpleName) { String name = ((SimpleName) node).asString(); while (!(node instanceof MethodDeclaration)){ Optional<Node> parent = node.getParentNode(); if (!parent.isPresent()) break; node = parent.get(); } MDec owner = resolveMethod(node); if (owner != null && owner.isMethod()) { Optional<VDec> v = owner.getVariableByName(name); if (v.isPresent()) return v.get(); } } return null; } /** * Resolve a member declaration from the given AST node. * * @param node * AST node. * * @return Member declaration wrapper. */ private MDec resolveMethod(Node node) { if(!(node instanceof Resolvable)) return null; // Resolve node to some declaration type Resolvable<?> r = (Resolvable<?>) node; Object resolved = null; try { resolved = r.resolve(); } catch(Exception ex) { return null; } if (resolved instanceof ResolvedMethodDeclaration) { ResolvedMethodDeclaration type = (ResolvedMethodDeclaration) resolved; ResolvedTypeDeclaration owner = type.declaringType(); CDec dec = getClassDec(owner.getQualifiedName()); String name = type.getName(); if (dec.uniqueName(name)) { Optional<MDec> m = dec.getByName(name); if (m.isPresent()) return m.get(); } String desc = ParserTypeUtil.getResolvedMethodDesc(type); return dec.getMember(name, desc); } else if (resolved instanceof ResolvedFieldDeclaration) { ResolvedFieldDeclaration type = (ResolvedFieldDeclaration) resolved; ResolvedTypeDeclaration owner = type.declaringType(); CDec dec = getClassDec(owner.getQualifiedName()); String name = type.getName(); if (dec.uniqueName(name)) { Optional<MDec> m = dec.getByName(name); if (m.isPresent()) return m.get(); } String desc = ParserTypeUtil.getResolvedFieldDesc(type); return dec.getMember(name, desc); } return null; } /** * @param name * Name of class. * * @return Declaration wrapper for class. */ private CDec getClassDec(String name) { if (name.contains(".")) name = name.replace('.', '/'); return decMap.computeIfAbsent(name, this::generateDec); } /** * Creates a class declaration wrapper. * * @param name * Name of class. * * @return Declaration wrapper for class. */ private CDec generateDec(String name) { CMap mapped = Mappings.INSTANCE.getClassReverseMapping(name); if (mapped != null) { name = mapped.getOriginalName(); } CDec dec = CDec.fromClass(name); ClassReader cr = reader(dec.getFullName()); if (!input.hasRawClass(dec.getFullName())) dec.setLocked(true); if(cr != null) cr.accept(new DecBuilder(dec, input), ClassReader.SKIP_FRAMES); else Logging.info("Failed class lookup for: " + dec.getFullName()); return dec; } /** * @param name * Internal class name. * * @return Reader of class. */ private ClassReader reader(String name) { byte[] code = input.getRawClass(name); if (code != null) return new ClassReader(code); try { return new ClassReader(name); } catch(IOException e) { return null; } } /** * Returns the AST node at the given position. * The child-most node may not be returned if the parent is better suited for contextual * purposes. * * @param line * Cursor line. * @param column * Cursor column. * * @return JavaParser AST node at the given position in the source code. */ private Node getNodeAt(int line, int column) { return getNodeAt(line, column, cu.findRootNode(), false); } /** * * Same as {@link #getNodeAt(int, int)} but allows returning {@link SimpleName} nodes. * * @param line * Cursor line. * @param column * Cursor column. * * @return JavaParser AST node at the given position in the source code. */ private Node getSimpleNodeAt(int line, int column) { return getNodeAt(line, column, cu.findRootNode(), true); } private Node getNodeAt(int line, int column, Node root, boolean allowSimple) { // We want to know more about this type, don't resolve down to the lowest AST // type... the parent has more data and is essentially just a wrapper around SimpleName. if (!allowSimple && root instanceof SimpleName) return null; // Verify the node range can be accessed if (!root.getBegin().isPresent() || !root.getEnd().isPresent()) return null; // Check cursor is in bounds // We won't instantly return null because the root range may be SMALLER than // the range of children. This is really stupid IMO but thats how JavaParser is... boolean bounds = true; Position cursor = Position.pos(line, column); if (cursor.isBefore(root.getBegin().get()) || cursor.isAfter(root.getEnd().get())) bounds = false; // Iterate over children, return non-null child for (Node child : root.getChildNodes()) { Node ret = getNodeAt(line, column, child, allowSimple); if (ret != null) return ret; } // If we're not in bounds and none of our children are THEN we assume this node is bad. if (!bounds) return null; // In bounds so we're good! return root; } }