/* * Copyright (C) 2010-2015 The Project Lombok Authors. * * 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 lombok.ast.grammar; import java.util.ArrayList; import java.util.Collections; import java.util.List; import lombok.ast.Ast; import lombok.ast.AstException; import lombok.ast.AstVisitor; import lombok.ast.Block; import lombok.ast.ConstructorDeclaration; import lombok.ast.EmptyStatement; import lombok.ast.Expression; import lombok.ast.ForwardingAstVisitor; import lombok.ast.Identifier; import lombok.ast.LabelledStatement; import lombok.ast.MethodDeclaration; import lombok.ast.Node; import lombok.ast.Position; import lombok.ast.Statement; import lombok.ast.TypeMember; import lombok.ast.TypeReference; import lombok.ast.VariableDeclaration; import lombok.ast.VariableDefinition; import lombok.ast.VariableReference; public class Template<T extends Node> { private static <N extends Node> N process(Source s, String name, Class<N> type) { if (!s.getProblems().isEmpty()) { throw new AstException(null, "Can't parse snippet: " + s.getProblems().get(0).getMessage()); } if (s.getNodes().isEmpty()) return null; if (s.getNodes().size() > 1) throw new AstException(null, "Can't parse snippet: more than one " + name + " in snippet"); Node n = s.getNodes().get(0); if (type.isInstance(n)) return type.cast(n); throw new AstException(null, "Can't parse snippet: Not a " + name); } /** * Parses one construct that is legal as a type member, and returns it. * * Legal type members: <ul> * <li>Constructor Declaration</li> * <li>Method Declaration</li> * <li>Static or Instance Initializer</li> * <li>Any type declaration</li> * <li>The empty declaration (lone semi-colon)</li> * <li>A variable declaration</li> * </ul> * * Note that neither annotation method declarations nor enum constants will be parsed properly by this method. */ public static TypeMember parseMember(String source) throws AstException { Source s = new Source(source, "memberSnippet"); s.parseMember(); return process(s, "type member", TypeMember.class); } public static MethodDeclaration parseMethod(String source) throws AstException { Source s = new Source(source, "methodSnippet"); s.parseMember(); return process(s, "method", MethodDeclaration.class); } public static ConstructorDeclaration parseConstructor(String source) throws AstException { Source s = new Source(source, "constructorSnippet"); s.parseMember(); return process(s, "constructor", ConstructorDeclaration.class); } public static VariableDeclaration parseField(String source) throws AstException { Source s = new Source(source, "fieldSnippet"); s.parseMember(); return process(s, "field", VariableDeclaration.class); } public static VariableDefinition parseVariableDefinition(String source) throws AstException { Source s = new Source(source, "vardefSnippet"); s.parseMember(); return process(s, "vardef", VariableDefinition.class); } public static Statement parseStatement(String source) throws AstException { Source s = new Source(source, "statementSnippet"); s.parseStatement(); return process(s, "statement", Statement.class); } public static Expression parseExpression(String source) throws AstException { Source s = new Source(source, "expressionSnippet"); s.parseExpression(); return process(s, "expression", Expression.class); } /** * NB: Do not simply pass the result of {@code parseX} to this method; parsing is extremely slow. Instead, parse a template once, * and then pass this one result every time. The template will never modify the original. */ @SuppressWarnings("unchecked") public static <N extends Node> Template<N> of(N source) throws AstException { return new Template<N>((N) source.copy()); } private final T node; private int location; private Node responsible; private List<ReplacementOrder> replacements = new ArrayList<ReplacementOrder>(); private int replacementsPointer = 0; private static class ReplacementOrder { String identifierToReplace, statementToReplace, expressionToReplace, typeReferenceToReplace; List<? extends Node> replacement; Position position; static ReplacementOrder forIdentifier(String identifier, String newValue, Position position) { ReplacementOrder order = new ReplacementOrder(); order.identifierToReplace = identifier; order.replacement = Collections.singletonList(newValue == null ? null : Identifier.of(newValue)); order.position = position; return order; } static ReplacementOrder forStatement(String label, List<? extends Node> replacements, Position position) { ReplacementOrder order = new ReplacementOrder(); order.statementToReplace = label; order.replacement = replacements == null ? Collections.<Node>emptyList() : replacements; order.position = position; return order; } static ReplacementOrder forExpression(String identifier, Node replacement, Position position) { ReplacementOrder order = new ReplacementOrder(); order.expressionToReplace = identifier; order.replacement = Collections.singletonList(replacement); order.position = position; return order; } static ReplacementOrder forTypeReference(String identifier, Node replacement, Position position) { ReplacementOrder order = new ReplacementOrder(); order.typeReferenceToReplace = identifier; order.replacement = Collections.singletonList(replacement); order.position = position; return order; } } private final AstVisitor visitor = new ForwardingAstVisitor() { @Override public boolean visitNode(Node node) { node.setPosition(new Position(location, location, responsible)); return false; } @Override public void endVisit(Node node) { node.setPosition(new Position(node.getPosition().getStart(), location, responsible)); } private ReplacementOrder currentOrder() { return (replacementsPointer < replacements.size()) ? replacements.get(replacementsPointer) : null; } @Override public boolean visitIdentifier(Identifier node) { ReplacementOrder order = currentOrder(); if (order != null && order.identifierToReplace != null) { if (order.identifierToReplace.equals(node.astValue())) { Node replacement = order.replacement.get(0); int startLoc = order.position == null ? location : order.position.getStart(); int endLoc = order.position == null ? location : order.position.getEnd(); replacement.setPosition(new Position(startLoc, endLoc, responsible)); location = endLoc; node.replace(replacement); replacementsPointer++; return true; } } return visitNode(node); } @Override public boolean visitLabelledStatement(LabelledStatement node) { ReplacementOrder order = currentOrder(); if (order != null && order.statementToReplace.equals(node.astLabel().astValue())) { if (!(node.rawStatement() instanceof EmptyStatement)) { throw new IllegalStateException("Placeholder statements in templates should be of the form: \"labelName: ;\" - i.e. a labelled empty statement"); } int startLoc, endLoc; if (order.position == null) { if (order.replacement.isEmpty() || order.replacement.get(0).getPosition().getStart() < 0) startLoc = location; else startLoc = order.replacement.get(0).getPosition().getStart(); if (order.replacement.isEmpty() || order.replacement.get(order.replacement.size() - 1).getPosition().getEnd() < 0) endLoc = location; else endLoc = order.replacement.get(order.replacement.size() - 1).getPosition().getEnd(); } else { startLoc = order.position.getStart(); endLoc = order.position.getEnd(); } switch (order.replacement.size()) { case 0: node.unparent(); break; case 1: Node replacement = order.replacement.get(0); if (replacement.getPosition().isUnplaced()) Ast.setAllPositions(replacement, new Position(startLoc, endLoc, responsible)); node.replace(replacement); break; default: // Replacing multiple statements only works in a Block. Block b = node.upToBlock(); if (b == null) throw new IllegalStateException("Replacing one placeholder statement with multiple statements is legal only if the placeholder is in a block"); Node[] statements = order.replacement.toArray(new Node[0]); for (int i = statements.length - 1; i >= 0; i--) { b.rawContents().addAfter(node, statements[i]); } node.unparent(); for (Node n : order.replacement) { if (n.getPosition().isUnplaced()) Ast.setAllPositions(n, new Position(startLoc, endLoc, responsible)); } } replacementsPointer++; location = endLoc; return true; } return visitNode(node); } @Override public boolean visitVariableReference(VariableReference node) { ReplacementOrder order = currentOrder(); if (order != null && order.expressionToReplace.equals(node.astIdentifier().astValue())) { Node replacement = order.replacement.get(0); int startLoc, endLoc; if (order.position == null) { if (order.replacement.isEmpty() || replacement.getPosition().getStart() < 0) startLoc = location; else startLoc = replacement.getPosition().getStart(); if (order.replacement.isEmpty() || replacement.getPosition().getEnd() < 0) endLoc = location; else endLoc = replacement.getPosition().getEnd(); } else { startLoc = order.position.getStart(); endLoc = order.position.getEnd(); } if (replacement.getPosition().isUnplaced()) Ast.setAllPositions(replacement, new Position(startLoc, endLoc, responsible)); location = endLoc; node.replace(replacement); replacementsPointer++; return true; } return visitNode(node); } @Override public boolean visitTypeReference(TypeReference node) { ReplacementOrder order = currentOrder(); if (order != null && node.astParts().size() == 1 && node.astParts().last().rawTypeArguments().isEmpty() && node.astParts().last().astIdentifier().astValue().equals(order.typeReferenceToReplace)) { Node replacement = order.replacement.get(0); int startLoc, endLoc; if (order.position == null) { if (order.replacement.isEmpty() || replacement.getPosition().getStart() < 0) startLoc = location; else startLoc = replacement.getPosition().getStart(); if (order.replacement.isEmpty() || replacement.getPosition().getEnd() < 0) endLoc = location; else endLoc = replacement.getPosition().getEnd(); } else { startLoc = order.position.getStart(); endLoc = order.position.getEnd(); } if (replacement.getPosition().isUnplaced()) Ast.setAllPositions(replacement, new Position(startLoc, endLoc, responsible)); location = endLoc; node.replace(replacement); replacementsPointer++; return true; } return visitNode(node); } }; private Template(T node) { this.node = node; } public Template<T> setStartPosition(int location) { this.location = location; return this; } public Template<T> setResponsibleNode(Node responsible) { this.responsible = responsible; return this; } public Template<T> replaceIdentifier(String placeholder, String replacement, Position p) { this.replacements.add(ReplacementOrder.forIdentifier(placeholder, replacement, p)); return this; } public Template<T> replaceIdentifier(String placeholder, String replacement) { return replaceIdentifier(placeholder, replacement, null); } public Template<T> replaceStatement(String placeholder, Node replacement, Position p) { this.replacements.add(ReplacementOrder.forStatement(placeholder, replacement == null ? Collections.<Statement>emptyList() : Collections.singletonList(replacement), p)); return this; } public Template<T> replaceStatement(String placeholder, Node replacement) { return replaceStatement(placeholder, replacement, null); } public Template<T> replaceStatement(String placeholder, List<? extends Node> replacement, Position p) { this.replacements.add(ReplacementOrder.forStatement(placeholder, replacement, p)); return this; } public Template<T> replaceStatement(String placeholder, List<? extends Node> replacement) { return replaceStatement(placeholder, replacement, null); } public Template<T> replaceExpression(String placeholder, Node replacement, Position p) { this.replacements.add(ReplacementOrder.forExpression(placeholder, replacement, p)); return this; } public Template<T> replaceExpression(String placeholder, Node replacement) { return replaceExpression(placeholder, replacement, null); } public Template<T> replaceTypeReference(String placeholder, Node replacement, Position p) { this.replacements.add(ReplacementOrder.forTypeReference(placeholder, replacement, p)); return this; } public Template<T> replaceTypeReference(String placeholder, Node replacement) { return replaceTypeReference(placeholder, replacement, null); } public T finish() { node.accept(visitor); return node; } }