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

lombok.core.handlers.YieldHandler Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2011-2012 Philipp Eichhorn
 *
 * 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.
 * 
 * Credit where credit is due:
 * ===========================
 * 
 *   Yield is based on the idea, algorithms and sources of
 *   Arthur and Vladimir Nesterovsky (http://www.nesterovsky-bros.com/weblog),
 *   who generously allowed me to use them.
 */
package lombok.core.handlers;

import static lombok.ast.AST.*;
import static lombok.core.util.Names.*;

import java.io.Closeable;
import java.util.*;

import lombok.*;
import lombok.ast.*;
import lombok.core.util.Is;

public class YieldHandler, AST_BASE_TYPE> {

	public boolean handle(final METHOD_TYPE method, final AbstractYieldDataCollector collector) {
		final boolean returnsIterable = method.returns(Iterable.class);
		final boolean returnsIterator = method.returns(Iterator.class);
		if (!(returnsIterable || returnsIterator)) {
			method.node().addError("Method that contain yield() can only return java.util.Iterator or java.lang.Iterable.");
			return true;
		}
		if (method.hasNonFinalArgument()) {
			method.node().addError("Parameters should be final.");
			return true;
		}

		final String stateName = "$state";
		final String nextName = "$next";
		final String errorName = "$yieldException";
		collector.collect(method, stateName, nextName, errorName, returnsIterable);

		if (!collector.hasYields()) {
			return true;
		}

		final String yielderName = collector.yielderName(method);
		ClassDecl yielder = collector.getYielder();
		method.editor().replaceBody(yielder, Return(New(Type(yielderName))));
		method.editor().rebuild();

		return true;
	}

	public static abstract class AbstractYieldDataCollector, AST_BASE_TYPE> {
		protected METHOD_TYPE method;
		protected List> yields = new ArrayList>();
		protected List> breaks = new ArrayList>();
		protected List> variableDecls = new ArrayList>();
		@Getter
		protected List stateVariables = new ArrayList();
		protected Scope root;
		protected Scope current;
		protected Map> allScopes = new HashMap>();
		protected List cases = new ArrayList();
		protected List> statements = new ArrayList>();
		protected List breakCases = new ArrayList();
		protected List errorHandlers = new ArrayList();
		protected int finallyBlocks;
		protected Map labelLiterals = new HashMap();
		protected Set usedLabels = new HashSet();
		protected String stateName;
		protected String nextName;
		protected String errorName;
		protected boolean returnsIterable;

		public ClassDecl getYielder() {
			final String yielderName = yielderName(method);
			final String elementType = elementType(method);
			final List variables = getStateVariables();
			final Switch stateSwitch = getStateSwitch();
			final Switch errorHandlerSwitch = getErrorHandlerSwitch();
			final Statement closeStatement = getCloseStatement();

			ClassDecl yielder = ClassDecl(yielderName).posHint(method.get()).makeLocal().implementing(Type(Iterator.class).withTypeArgument(Type(elementType))) //
					.withFields(variables) //
					.withField(FieldDecl(Type("int"), stateName).makePrivate()) //
					.withField(FieldDecl(Type("boolean"), "$hasNext").makePrivate()) //
					.withField(FieldDecl(Type("boolean"), "$nextDefined").makePrivate()) //
					.withField(FieldDecl(Type(elementType), nextName).makePrivate()) //
					.withMethod(ConstructorDecl(yielderName).withImplicitSuper().makePrivate()); //
			if (returnsIterable) {
				yielder.implementing(Type(Iterable.class).withTypeArgument(Type(elementType))) //
						.withMethod(MethodDecl(Type(Iterator.class).withTypeArgument(Type(elementType)), "iterator").makePublic() //
								.withStatement(If(Equal(Name(stateName), Number(0))).Then(Block() //
										.withStatement(Assign(Name(stateName), Number(1))) //
										.withStatement(Return(This()))) //
										.Else(Return(New(Type(yielderName))))));
			}
			yielder.implementing(Type(Closeable.class)) //
					.withMethod(MethodDecl(Type("boolean"), "hasNext").makePublic() //
							.withStatement(If(Not(Name("$nextDefined"))).Then(Block() //
									.withStatement(Assign(Name("$hasNext"), Call("getNext"))) //
									.withStatement(Assign(Name("$nextDefined"), True())))) //
							.withStatement(Return(Name("$hasNext")))) //
					.withMethod(MethodDecl(Type(elementType), "next").makePublic() //
							.withStatement(If(Not(Call("hasNext"))).Then(Block() //
									.withStatement(Throw(New(Type(NoSuchElementException.class)))))) //
							.withStatement(Assign(Name("$nextDefined"), False())) //
							.withStatement(Return(Name(nextName)))) //
					.withMethod(MethodDecl(Type("void"), "remove").makePublic() //
							.withStatement(Throw(New(Type(UnsupportedOperationException.class))))) //
					.withMethod(MethodDecl(Type("void"), "close").makePublic() //
							.withStatement(closeStatement));
			if (errorHandlerSwitch != null) {
				String caughtErrorName = errorName + "Caught";
				yielder.withMethod(MethodDecl(Type("boolean"), "getNext").makePrivate() //
						.withStatement(LocalDecl(Type(Throwable.class), errorName)) //
						.withStatement(While(True()).Do(Block() //
								.withStatement(Try(Block() //
										.withStatement(stateSwitch)) //
										.Catch(Arg(Type(Throwable.class), caughtErrorName), Block() //
												.withStatement(Assign(Name(errorName), Name(caughtErrorName))))) //
								.withStatement(errorHandlerSwitch))));
			} else {
				yielder.withMethod(MethodDecl(Type("boolean"), "getNext").makePrivate() //
						.withStatement(While(True()).Do(stateSwitch)));
			}
			return yielder;
		}

		public Switch getStateSwitch() {
			final List switchCases = new ArrayList();
			for (Case label : cases) if (label != null) switchCases.add(label);
			return Switch(Name(stateName)).withCases(switchCases).withCase(Case().withStatement(Return(False())));
		}

		public Switch getErrorHandlerSwitch() {
			if (errorHandlers.isEmpty()) {
				return null;
			} else {
				final List switchCases = new ArrayList();
				final Set labels = new HashSet();
				for (ErrorHandler handler : errorHandlers) {
					Case lastCase = null;
					for (int i = handler.begin; i < handler.end; i++) {
						Case label = cases.get(i);
						if ((label != null) && labels.add(label)) {
							lastCase = Case(label.getPattern());
							switchCases.add(lastCase);
						}
					}
					if (lastCase != null) {
						lastCase.withStatements(handler.statements);
					}
				}

				final String unhandledErrorName = errorName + "Unhandled";
				switchCases.add(Case() //
						.withStatement(setState(literal(getBreakLabel(root)))) //
						.withStatement(LocalDecl(Type(ConcurrentModificationException.class), unhandledErrorName).withInitialization(New(Type(ConcurrentModificationException.class)))) //
						.withStatement(Call(Name(unhandledErrorName), "initCause").withArgument(Name(errorName))) //
						.withStatement(Throw(Name(unhandledErrorName))));
				return Switch(Name(stateName)).withCases(switchCases);
			}
		}

		public Statement getCloseStatement() {
			final Statement statement = setState(literal(getBreakLabel(root)));
			if (breakCases.isEmpty()) {
				return statement;
			} else {
				Number prev = null;
				final List switchCases = new ArrayList();
				for (final Case breakCase : breakCases) {
					NumberLiteral literal = (NumberLiteral) breakCase.getPattern();
					Number value = literal.getNumber();
					if ((prev != null) && prev.equals(value)) continue;
					switchCases.add(breakCase);
					prev = value;
				}
				switchCases.add(Case() //
						.withStatement(statement) //
						.withStatement(Return()));
				return Do(Switch(Name(stateName)).withCases(switchCases)).While(Call("getNext"));
			}
		}

		public boolean hasYields() {
			return !yields.isEmpty();
		}

		public void collect(final METHOD_TYPE method, final String state, final String next, final String errorName, final boolean returnsIterable) {
			this.method = method;
			this.stateName = state;
			this.nextName = next;
			this.errorName = errorName;
			this.returnsIterable = returnsIterable;

			if (scan()) {
				prepareRefactor();
				refactor();
			}
		}

		public String yielderName(final METHOD_TYPE method) {
			String[] parts = method.name().split("_");
			String[] newParts = new String[parts.length + 1];
			newParts[0] = "yielder";
			System.arraycopy(parts, 0, newParts, 1, parts.length);
			return camelCase("$", newParts);
		}

		public abstract String elementType(final METHOD_TYPE method);

		public abstract boolean scan();

		public abstract void prepareRefactor();

		public void refactor() {
			current = root;

			Case begin = Case();
			Case iteratorLabel = getIterationLabel(root);

			usedLabels.add(begin);
			usedLabels.add(iteratorLabel);
			usedLabels.add(getBreakLabel(root));

			addLabel(begin);
			addStatement(setState(literal(iteratorLabel)));
			addLabel(iteratorLabel);
			root.refactor();
			endCase();

			optimizeStates();
			synchronizeLiteralsAndLabels();
		}

		public Expression getStateFromAssignment(Statement statement) {
			if (statement instanceof Assignment) {
				Assignment assign = (Assignment) statement;
				if (assign.getLeft() instanceof NameRef) {
					NameRef field = (NameRef) assign.getLeft();
					if (stateName.equals(field.getName())) {
						return assign.getRight();
					}
				}
			}
			return null;
		}

		public Case getLabel(Expression expression) {
			return expression == null ? null : labelLiterals.get(expression);
		}

		public void endCase() {
			if (!cases.isEmpty()) {
				Case lastCase = cases.get(cases.size() - 1);
				if (lastCase.getStatements().isEmpty() && !statements.isEmpty()) {
					lastCase.withStatements(statements);
					statements.clear();
				}
			}
		}

		public void addLabel(final Case label) {
			endCase();
			label.withPattern(Number(cases.size()));
			cases.add(label);
		}

		public void addStatement(final Statement statement) {
			statements.add(statement);
		}

		public Case getBreakLabel(final Scope scope) {
			Case label = scope.breakLabel;
			if (label == null) {
				label = Case();
				scope.breakLabel = label;
			}
			return label;
		}

		public Case getIterationLabel(final Scope scope) {
			Case label = scope.iterationLabel;
			if (label == null) {
				label = Case();
				scope.iterationLabel = label;
			}
			return label;
		}

		public Case getFinallyLabel(Scope scope) {
			Case label = scope.finallyLabel;
			if (label == null) {
				label = Case();
				scope.finallyLabel = label;
			}
			return label;
		}

		public Expression literal(final Case label) {
			NumberLiteral pattern = (NumberLiteral) label.getPattern();
			NumberLiteral literal = Number(pattern == null ? -1 : pattern.getNumber());
			labelLiterals.put(literal, label);
			return literal;
		}

		public Statement setState(final Expression expression) {
			return Assign(Name(stateName), expression);
		}

		public void refactorStatement(final Object statement) {
			if (statement == null) return;
			Scope scope = allScopes.get(statement);
			if (scope != null) {
				Scope previous = current;
				current = scope;
				scope.refactor();
				current = previous;
			} else {
				addStatement(Stat(statement));
			}
		}

		public void optimizeStates() {
			optimizeStateChanges();
			optimizeSuccessiveStates();
		}

		/**
		 * Handles the following scenarios:
		 * 
    *
  1. * *
    		 * case 2:
    		 * case 3:                  => merge 2 and 3 into 4
    		 * case 4:
    		 *   bodyOf4();
    		 * 
    * *
  2. *
  3. * *
    		 * case 2:
    		 *   $state = 5;            => merge 2 into 5
    		 *   continue;
    		 * // case 3-4
    		 * case 5:
    		 *   bodyOf5();
    		 * 
    * *
  4. *
*/ public void optimizeStateChanges() { int count = cases.size(); for (Map.Entry entry : labelLiterals.entrySet()) { Case label = entry.getValue(); while (label.getPattern() != null) { if (label.getStatements().isEmpty()) { NumberLiteral literal = (NumberLiteral) label.getPattern(); int i = (Integer) literal.getNumber() + 1; if (i < count) { label = cases.get(i); } else { break; } } else { Case next = getLabel(getStateFromAssignment(label.getStatements().get(0))); int numberOfStatements = label.getStatements().size(); if ((next != null) && ((numberOfStatements == 1) || ((numberOfStatements > 1) && (label.getStatements().get(1) instanceof Continue)))) { label = next; } else { break; } } } entry.setValue(label); if (label.getPattern() != null) { usedLabels.add(label); } } } /** * Handles the following scenarios: *
    *
  1. * *
    		 * case 2:
    		 *   bodyOf2();
    		 *   // doesn't end with
    		 *   //continue;
    		 *   // or                  => case 2:
    		 *   //return true;              bodyOf2();
    		 * case 3:                       bodyOf3();
    		 *   bodyOf3();
    		 * 
    * *
  2. *
  3. * *
    		 * case 2:
    		 *   bodyOf2();             => case 2:
    		 *   continue;                   bodyOf2();
    		 *   unreachableBodyOf2();       continue;
    		 * 
    * *
  4. *
*/ public void optimizeSuccessiveStates() { Case previous = null; for (int i = 0, id = 0, iend = cases.size(); i < iend; i++) { Case label = cases.get(i); if (!usedLabels.contains(label) && (previous != null)) { Statement last = previous.getStatements().get(previous.getStatements().size() - 1); if (!label.getStatements().isEmpty() && Is.noneOf(last, Continue.class, Return.class)) { previous.withStatements(label.getStatements()); } cases.set(i, null); continue; } NumberLiteral literal = (NumberLiteral) label.getPattern(); literal.setNumber(id++); if (previous == null) { previous = label; continue; } boolean found = false; boolean remove = false; List> list = previous.getStatements(); for (Iterator> iter = list.iterator(); iter.hasNext();) { Statement statement = iter.next(); if (remove || (found && (statement instanceof Continue))) { remove = true; iter.remove(); } else { found = getLabel(getStateFromAssignment(statement)) == label; } } previous = label; } } public void synchronizeLiteralsAndLabels() { for (Map.Entry entry : labelLiterals.entrySet()) { Case label = entry.getValue(); if (label != null) { NumberLiteral literal = (NumberLiteral) label.getPattern(); entry.getKey().setNumber(literal.getNumber()); } } } } public static class ErrorHandler { public int begin; public int end; public List> statements = new ArrayList>(); } @RequiredArgsConstructor public abstract static class Scope { public final Scope parent; public final AST_BASE_TYPE node; public Scope target; public String labelName; public Case iterationLabel; public Case breakLabel; public Case finallyLabel; public abstract void refactor(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy