/* * Copyright 2014 Red Hat, Inc. and/or its affiliates. * * Licensed under the Eclipse Public License version 1.0, available at * http://www.eclipse.org/legal/epl-v10.html */ package org.jboss.windup.config.operation; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.config.Variables; import org.jboss.windup.config.condition.GraphCondition; import org.jboss.windup.config.exception.IllegalTypeArgumentException; import org.jboss.windup.config.operation.iteration.IterationBuilderComplete; import org.jboss.windup.config.operation.iteration.IterationBuilderOtherwise; import org.jboss.windup.config.operation.iteration.IterationBuilderOver; import org.jboss.windup.config.operation.iteration.IterationBuilderPerform; import org.jboss.windup.config.operation.iteration.IterationBuilderVar; import org.jboss.windup.config.operation.iteration.IterationBuilderWhen; import org.jboss.windup.config.operation.iteration.IterationPayloadManager; import org.jboss.windup.config.operation.iteration.NamedFramesSelector; import org.jboss.windup.config.operation.iteration.NamedIterationPayloadManager; import org.jboss.windup.config.operation.iteration.TopLayerSingletonFramesSelector; import org.jboss.windup.config.operation.iteration.TypedFramesSelector; import org.jboss.windup.config.operation.iteration.TypedNamedFramesSelector; import org.jboss.windup.config.operation.iteration.TypedNamedIterationPayloadManager; import org.jboss.windup.config.selectors.FramesSelector; import org.jboss.windup.graph.model.WindupVertexFrame; import org.jboss.windup.util.exception.WindupException; import org.ocpsoft.common.util.Assert; import org.ocpsoft.rewrite.config.And; import org.ocpsoft.rewrite.config.CompositeOperation; import org.ocpsoft.rewrite.config.Condition; import org.ocpsoft.rewrite.config.ConfigurationRuleBuilder; import org.ocpsoft.rewrite.config.DefaultOperationBuilder; import org.ocpsoft.rewrite.config.NoOp; import org.ocpsoft.rewrite.config.Operation; import org.ocpsoft.rewrite.config.Perform; import org.ocpsoft.rewrite.context.EvaluationContext; import org.ocpsoft.rewrite.event.Rewrite; import com.google.common.collect.Iterables; import org.jboss.windup.util.exception.WindupStopException; import org.ocpsoft.rewrite.config.CompositeCondition; /** * Used to iterate over an implicit or explicit variable defined within the corresponding {@link ConfigurationRuleBuilder#when(Condition)} clause in * the current rule. * * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a> * @author <a href="http://ondra.zizka.cz">Ondrej Zizka, I, zizka@seznam.cz</a> */ public class Iteration extends DefaultOperationBuilder implements IterationBuilderVar, IterationBuilderOver, IterationBuilderWhen, IterationBuilderPerform, IterationBuilderOtherwise, IterationBuilderComplete, CompositeOperation { private static final String VAR_INSTANCE_STRING = "_instance"; public static final String DEFAULT_VARIABLE_LIST_STRING = "default"; public static final String DEFAULT_SINGLE_VARIABLE_STRING = singleVariableIterationName(DEFAULT_VARIABLE_LIST_STRING); private Condition condition; private Operation operationPerform; private Operation operationOtherwise; private IterationPayloadManager payloadManager; private final FramesSelector selectionManager; /** * Calculates the default name for the single variable in the selection with the given name. */ public static String singleVariableIterationName(String selectionName) { return selectionName + VAR_INSTANCE_STRING; } /** * Create a new {@link Iteration} */ private Iteration(FramesSelector selectionManager) { Assert.notNull(selectionManager, "Selection manager must not be null."); this.selectionManager = selectionManager; } /** * Begin an {@link Iteration} over the named selection of the given type. Also sets the name and type of the variable for this iteration's * "current element". The type serves for automatic type check. */ public static IterationBuilderOver over(Class<? extends WindupVertexFrame> sourceType, String source) { Iteration iterationImpl = new Iteration(new TypedNamedFramesSelector(sourceType, source)); iterationImpl.setPayloadManager(new TypedNamedIterationPayloadManager(sourceType, singleVariableIterationName(source))); return iterationImpl; } /** * Begin an {@link Iteration} over the named selection. Also sets the name of the variable for this iteration's "current element". */ public static IterationBuilderOver over(String source) { Iteration iterationImpl = new Iteration(new NamedFramesSelector(source)); iterationImpl.setPayloadManager(new NamedIterationPayloadManager(singleVariableIterationName(source))); return iterationImpl; } /** * Begin an {@link Iteration} over the selection of the given type, named with the default name. Also sets the name of the variable for this * iteration's "current element" to have the default value. */ public static IterationBuilderOver over(Class<? extends WindupVertexFrame> sourceType) { Iteration iterationImpl = new Iteration(new TypedFramesSelector(sourceType)); iterationImpl.setPayloadManager(new TypedNamedIterationPayloadManager(sourceType, DEFAULT_SINGLE_VARIABLE_STRING)); return iterationImpl; } /** * Begin an {@link Iteration} over the selection that is placed on the top of the {@link Variables}. Also sets the name of the variable for this * iteration's "current element" (i.e payload) to have the default value. */ public static IterationBuilderOver over() { Iteration iterationImpl = new Iteration(new TopLayerSingletonFramesSelector()); iterationImpl.setPayloadManager(new NamedIterationPayloadManager(DEFAULT_SINGLE_VARIABLE_STRING)); return iterationImpl; } /** * Change the name of the single variable of the given type. If this method is not called, the name is calculated using the * {@link Iteration#singleVariableIterationName(String)} method. */ @Override public IterationBuilderVar as(Class<? extends WindupVertexFrame> varType, String var) { setPayloadManager(new TypedNamedIterationPayloadManager(varType, var)); return this; } /** * Change the name of the single variable. If this method is not called, the name is calculated using the * {@link Iteration#singleVariableIterationName(String)} method. */ @Override public IterationBuilderVar as(String var) { setPayloadManager(new NamedIterationPayloadManager(var)); return this; } public IterationBuilderWhen all(Condition... condition) { this.condition = And.all(condition); return this; } @Override public IterationBuilderWhen when(Condition condition) { this.condition = condition; return this; } @Override public IterationBuilderPerform perform(Operation operation) { this.operationPerform = operation; return this; } @Override public IterationBuilderPerform perform(Operation... operations) { this.operationPerform = Perform.all(operations); return this; } @Override public IterationBuilderOtherwise otherwise(Operation operation) { this.operationOtherwise = operation; return this; } /** * Visual end of the iteration. */ @Override public IterationBuilderComplete endIteration() { return this; } /** * Called internally to actually process the Iteration. */ @Override public void perform(Rewrite event, EvaluationContext context) { perform((GraphRewrite) event, context); } /** * Called internally to actually process the Iteration. Loops over the frames to iterate, and performs their .perform( ... ) or .otherwise( ... ) * parts. */ public void perform(GraphRewrite event, EvaluationContext context) { Variables variables = Variables.instance(event); Iterable<? extends WindupVertexFrame> frames = getSelectionManager().getFrames(event, context); boolean hasCommitOperation = OperationUtil.hasCommitOperation(operationPerform) || OperationUtil.hasCommitOperation(operationOtherwise); boolean hasIterationOperation = OperationUtil.hasIterationProgress(operationPerform) || OperationUtil.hasIterationProgress(operationOtherwise); DefaultOperationBuilder commit = new NoOp(); DefaultOperationBuilder iterationProgressOperation = new NoOp(); if (!hasCommitOperation || !hasIterationOperation) { int frameCount = Iterables.size(frames); if (frameCount > 100) { if (!hasCommitOperation) commit = Commit.every(1000); if (!hasIterationOperation) { // Use 500 here as 100 might be noisy. iterationProgressOperation = IterationProgress.monitoring("Rule Progress", 500); } } } Operation commitAndProgress = commit.and(iterationProgressOperation); event.getRewriteContext().put(DEFAULT_VARIABLE_LIST_STRING, frames); // set the current frames try { for (WindupVertexFrame frame : frames) try { variables.push(); getPayloadManager().setCurrentPayload(variables, frame); boolean conditionResult = true; if (condition != null) { final String payloadVariableName = getPayloadVariableName(event, context); passInputVariableNameToConditionTree(condition, payloadVariableName); conditionResult = condition.evaluate(event, context); /* * Add special clear layer for perform, because condition used one and could have added new variables. The condition result put into * variables is ignored. */ variables.push(); getPayloadManager().setCurrentPayload(variables, frame); } if (conditionResult) { if (operationPerform != null) { operationPerform.perform(event, context); } } else if (condition != null) { if (operationOtherwise != null) { operationOtherwise.perform(event, context); } } commitAndProgress.perform(event, context); getPayloadManager().removeCurrentPayload(variables); // remove the perform layer variables.pop(); if (condition != null) { // remove the condition layer variables.pop(); } } catch (WindupStopException ex) { throw new WindupStopException("Windup stop requested in " + this.toString(), ex); } catch (Exception e) { throw new WindupException("Failed when iterating " + frame.toPrettyString() + ", due to: " + e.getMessage(), e); } } finally { event.getRewriteContext().put(DEFAULT_VARIABLE_LIST_STRING, null); } } private void passInputVariableNameToConditionTree(Condition condition, String payloadVariableName) throws IllegalStateException { // Automatically set the input variable to point to the current payload. if (condition instanceof GraphCondition) { ((GraphCondition) condition).setInputVariablesName(payloadVariableName); } // WINDUP-1057 - we need to pass the variable name manually to the GraphCondition's nested in CompositeCondition's. if (condition instanceof CompositeCondition) { CompositeCondition composite = (CompositeCondition) condition; for (Condition childCondition : composite.getConditions()) { passInputVariableNameToConditionTree(childCondition, payloadVariableName); } } } @Override public List<Operation> getOperations() { return Arrays.asList(operationPerform, operationOtherwise); } /** * Return the current {@link Iteration} payload variable name. * * @throws IllegalStateException if there is more than one variable in the {@link Variables} stack, and the payload name cannot be determined. */ public static String getPayloadVariableName(GraphRewrite event, EvaluationContext ctx) throws IllegalStateException { Variables variables = Variables.instance(event); Map<String, Iterable<? extends WindupVertexFrame>> topLayer = variables.peek(); if (topLayer.keySet().size() != 1) { throw new IllegalStateException("Cannot determine Iteration payload variable name because the top " + "layer of " + Variables.class.getSimpleName() + " stack contains " + topLayer.keySet().size() + " variables: " + topLayer.keySet()); } String name = topLayer.keySet().iterator().next(); return name; } /** * Set the current {@link Iteration} payload. */ public static void setCurrentPayload(Variables stack, String name, WindupVertexFrame frame) throws IllegalArgumentException { Map<String, Iterable<? extends WindupVertexFrame>> vars = stack.peek(); Iterable<? extends WindupVertexFrame> existingValue = vars.get(name); if (!(existingValue == null || existingValue instanceof IterationPayload)) { throw new IllegalArgumentException("Variable \"" + name + "\" has already been assigned and cannot be used as an " + Iteration.class.getSimpleName() + " variable."); } vars.put(name, new IterationPayload<>(frame)); } /** * Get the {@link Iteration} payload with the given name. * * @throws IllegalArgumentException if the given variable refers to a non-payload. */ @SuppressWarnings("unchecked") public static <FRAMETYPE extends WindupVertexFrame> FRAMETYPE getCurrentPayload(Variables stack, String name) throws IllegalStateException, IllegalArgumentException { Map<String, Iterable<? extends WindupVertexFrame>> vars = stack.peek(); Iterable<? extends WindupVertexFrame> existingValue = vars.get(name); if (!(existingValue == null || existingValue instanceof IterationPayload)) { throw new IllegalArgumentException("Variable \"" + name + "\" is not an " + Iteration.class.getSimpleName() + " variable."); } Object object = stack.findSingletonVariable(name); return (FRAMETYPE) object; } /** * Get the {@link Iteration} payload with the given name and type. * * @throws IllegalArgumentException if the given variable refers to a non-payload. */ @SuppressWarnings("unchecked") public static <FRAMETYPE extends WindupVertexFrame> FRAMETYPE getCurrentPayload(Variables stack, Class<FRAMETYPE> type, String name) throws IllegalStateException, IllegalArgumentException { Map<String, Iterable<? extends WindupVertexFrame>> vars = stack.peek(); Iterable<? extends WindupVertexFrame> existingValue = vars.get(name); if (!(existingValue == null || existingValue instanceof IterationPayload)) { throw new IllegalArgumentException("Variable \"" + name + "\" is not an " + Iteration.class.getSimpleName() + " variable."); } Object object = stack.findSingletonVariable(type, name); return (FRAMETYPE) object; } /** * Remove the current {@link Iteration} payload. */ public static <FRAMETYPE extends WindupVertexFrame> FRAMETYPE removeCurrentPayload(Variables stack, Class<FRAMETYPE> type, String name) throws IllegalStateException, IllegalTypeArgumentException { FRAMETYPE payload = getCurrentPayload(stack, type, name); Map<String, Iterable<? extends WindupVertexFrame>> vars = stack.peek(); vars.remove(name); return payload; } /** * Remove the current {@link Iteration} payload. */ public static <FRAMETYPE extends WindupVertexFrame> FRAMETYPE removeCurrentPayload(Variables stack, String name) throws IllegalStateException, IllegalTypeArgumentException { FRAMETYPE payload = getCurrentPayload(stack, name); Map<String, Iterable<? extends WindupVertexFrame>> vars = stack.peek(); vars.remove(name); return payload; } public void setPayloadManager(IterationPayloadManager payloadManager) { Assert.notNull(payloadManager, "Payload manager must not be null."); this.payloadManager = payloadManager; } public FramesSelector getSelectionManager() { return selectionManager; } public IterationPayloadManager getPayloadManager() { return payloadManager; } private static class IterationPayload<T> extends HashSet<T> { private static final long serialVersionUID = 7725055142596456025L; public IterationPayload(T element) { super(1); super.add(element); } @Override public boolean add(T e) { throw new UnsupportedOperationException("Iteration payloads are not modifiable."); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException("Iteration payloads are not modifiable."); } @Override public void clear() { throw new UnsupportedOperationException("Iteration payloads are not modifiable."); } @Override public boolean addAll(Collection<? extends T> c) { throw new UnsupportedOperationException("Iteration payloads are not modifiable."); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException("Iteration payloads are not modifiable."); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException("Iteration payloads are not modifiable."); } } /** * @return Description of this iteration, e.g. "Iteration.over(?).as(...).when(...).perform(...)". */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Iteration.over(?)"); if (!getPayloadManager().getPayLoadName().equals(DEFAULT_SINGLE_VARIABLE_STRING)) { builder.append(".as(").append(getPayloadManager().getPayLoadName()).append(")"); } if (condition != null) { builder.append(".when(").append(condition).append(")"); } if (operationPerform != null) { builder.append(".perform(").append(operationPerform).append(")"); } if (operationOtherwise != null) { builder.append(".otherwise(").append(operationOtherwise).append(")"); } return builder.toString(); } }