/*
* Copyright 2015 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.rf.ide.core.testdata.model.table.exec;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.rf.ide.core.testdata.model.AModelElement;
import org.rf.ide.core.testdata.model.FilePosition;
import org.rf.ide.core.testdata.model.table.ARobotSectionTable;
import org.rf.ide.core.testdata.model.table.IExecutableStepsHolder;
import org.rf.ide.core.testdata.model.table.RobotExecutableRow;
import org.rf.ide.core.testdata.model.table.exec.descs.IExecutableRowDescriptor;
import org.rf.ide.core.testdata.model.table.exec.descs.IExecutableRowDescriptor.ERowType;
import org.rf.ide.core.testdata.model.table.exec.descs.IExecutableRowDescriptor.IRowType;
import org.rf.ide.core.testdata.model.table.exec.descs.RobotAction;
import org.rf.ide.core.testdata.text.read.recognizer.RobotToken;
import org.rf.ide.core.testdata.text.read.recognizer.RobotTokenType;
public class ExecutableUnitsFixer {
public <T extends AModelElement<? extends ARobotSectionTable>> List<RobotExecutableRow<T>> applyFix(
final IExecutableStepsHolder<T> execUnit) {
final List<RobotExecutableRow<T>> newExecutionContext = new ArrayList<>(0);
final List<RobotExecutableRow<T>> executionContext = execUnit.getExecutionContext();
final List<IExecutableRowDescriptor<T>> preBuildDescriptors = preBuildDescriptors(executionContext);
int lastForIndex = -1;
int lastForExecutableIndex = -1;
final int lineNumbers = preBuildDescriptors.size();
for (int lineId = 0; lineId < lineNumbers; lineId++) {
final IExecutableRowDescriptor<T> currentExecLine = preBuildDescriptors.get(lineId);
final IRowType rowType = currentExecLine.getRowType();
final RobotExecutableRow<T> row = currentExecLine.getRow();
if (rowType == ERowType.FOR) {
if (lastForIndex > -1 && lastForExecutableIndex > -1) {
applyArtifactalForLineContinue(newExecutionContext, lastForIndex, lastForExecutableIndex);
}
lastForIndex = newExecutionContext.size();
newExecutionContext.add(row);
lastForExecutableIndex = -1;
} else {
if (rowType == ERowType.FOR_CONTINUE) {
final Optional<RobotToken> previousLineContinue = getPreviouseLineContinueToken(row.getElementTokens());
if (previousLineContinue.isPresent()) {
merge(execUnit, newExecutionContext, preBuildDescriptors, lineId, previousLineContinue.get());
} else {
newExecutionContext.add(row);
if (lastForIndex > -1 && lastForExecutableIndex > -1) {
lastForExecutableIndex = newExecutionContext.size() - 1;
applyArtifactalForLineContinue(newExecutionContext, lastForIndex, lastForExecutableIndex);
}
}
} else if (rowType == ERowType.SIMPLE) {
if (containsArtifactalContinueAfectingForLoop(currentExecLine)) {
lastForExecutableIndex = newExecutionContext.size() - 1;
if (lastForIndex == -1) {
final Optional<Integer> execLine = findLineWithExecAction(newExecutionContext);
if (execLine.isPresent()) {
final int parentLine = execLine.get();
final RobotExecutableRow<T> toMergeLine = newExecutionContext.get(parentLine);
final int numberOfMerges = lastForExecutableIndex - parentLine;
for (int i = 0; i < numberOfMerges; i++) {
merge(execUnit, toMergeLine, newExecutionContext.get(parentLine + 1));
newExecutionContext.remove(parentLine + 1);
}
merge(execUnit, toMergeLine, row);
} else {
newExecutionContext.add(row);
}
} else {
newExecutionContext.add(row);
if (lastForIndex > -1 && lastForExecutableIndex > -1) {
lastForExecutableIndex = newExecutionContext.size() - 1;
applyArtifactalForLineContinue(newExecutionContext, lastForIndex,
lastForExecutableIndex);
}
}
} else {
final Optional<RobotToken> previousLineContinue = getPreviouseLineContinueToken(
row.getElementTokens());
if (previousLineContinue.isPresent()) {
merge(execUnit, newExecutionContext, preBuildDescriptors, lineId,
previousLineContinue.get());
} else {
newExecutionContext.add(row);
}
if (lastForIndex > -1 && lastForExecutableIndex > -1) {
applyArtifactalForLineContinue(newExecutionContext, lastForIndex, lastForExecutableIndex);
}
lastForIndex = -1;
lastForExecutableIndex = -1;
}
} else if (rowType == ERowType.COMMENTED_HASH) {
newExecutionContext.add(row);
} else {
throw new IllegalStateException("Unsupported executable row type " + rowType + ". In file "
+ execUnit.getHolder().getParent().getParent().getParent().getProcessedFile() + " near "
+ row.getBeginPosition());
}
}
}
if (lastForIndex > -1 && lastForExecutableIndex > -1) {
applyArtifactalForLineContinue(newExecutionContext, lastForIndex, lastForExecutableIndex);
}
boolean isContinue = false;
final int size = newExecutionContext.size();
for (int i = size - 1; i >= 0; i--) {
final RobotExecutableRow<T> execLine = newExecutionContext.get(i);
final IExecutableRowDescriptor<T> lineDescription = execLine.buildLineDescription();
final IRowType rowType = lineDescription.getRowType();
if (rowType == ERowType.FOR_CONTINUE) {
if (execLine.getAction().getText().isEmpty()) {
if (execLine.getAction().getTypes().contains(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN)) {
if (isActionNotTheFirstElement(execLine.getAction(), execLine.getElementTokens())) {
final RobotToken actionToBeArgument = execLine.getAction().copy();
actionToBeArgument.getTypes().remove(RobotTokenType.KEYWORD_ACTION_NAME);
actionToBeArgument.getTypes().remove(RobotTokenType.TEST_CASE_ACTION_NAME);
actionToBeArgument.getTypes().remove(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN);
actionToBeArgument.setRaw("\\");
actionToBeArgument.setText("\\");
execLine.addArgument(0, actionToBeArgument);
}
execLine.getAction().setText("\\");
execLine.getAction().setRaw("\\");
} else {
execLine.getAction().setText("\\");
execLine.getAction().setRaw("\\");
execLine.getAction().getTypes().add(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN);
}
} else if (!execLine.getAction().getText().equals("\\")) {
final RobotToken actionToBeArgument = execLine.getAction().copy();
actionToBeArgument.getTypes().remove(RobotTokenType.KEYWORD_ACTION_NAME);
actionToBeArgument.getTypes().remove(RobotTokenType.TEST_CASE_ACTION_NAME);
actionToBeArgument.getTypes().remove(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN);
execLine.addArgument(0, actionToBeArgument);
execLine.getAction().setText("\\");
execLine.getAction().setRaw("\\");
execLine.getAction().getTypes().add(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN);
} else {
execLine.getAction().getTypes().remove(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN);
}
isContinue = true;
} else if (rowType == ERowType.COMMENTED_HASH) {
if (isContinue) {
if (!execLine.getAction().getText().equals("\\")) {
execLine.getAction().setText("\\");
execLine.getAction().setRaw("\\");
execLine.getAction().getTypes().add(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN);
}
} else {
execLine.getAction().setType(RobotTokenType.START_HASH_COMMENT);
}
} else if (rowType == ERowType.FOR || rowType == ERowType.SIMPLE) {
isContinue = false;
}
}
return newExecutionContext;
}
/**
* Special handling for TSV format:
* <code>
* :FOR ${x} IN 10
# d
... kw_w
\ kw_w
* </code>
*
* @param action
* @param elementTokens
* @return
*/
private boolean isActionNotTheFirstElement(final RobotToken action, final List<RobotToken> elementTokens) {
final FilePosition actionPosition = action.getFilePosition();
if (!actionPosition.isNotSet()) {
for (final RobotToken tok : elementTokens) {
final FilePosition currentTokenPosition = tok.getFilePosition();
if (!currentTokenPosition.isNotSet()) {
if (currentTokenPosition.isBefore(actionPosition)) {
return true;
}
}
}
}
return false;
}
private <T> Optional<Integer> findLineWithExecAction(final List<RobotExecutableRow<T>> newExecutionContext) {
for (int i = newExecutionContext.size() - 1; i >= 0; i--) {
if (newExecutionContext.get(i).isExecutable()) {
return Optional.of(i);
}
}
return Optional.empty();
}
private <T extends AModelElement<? extends ARobotSectionTable>> void merge(final IExecutableStepsHolder<T> execUnit,
final RobotExecutableRow<T> outputLine, final RobotExecutableRow<T> toMerge) {
if (!toMerge.getAction().getFilePosition().isNotSet()) {
outputLine.addArgument(toMerge.getAction());
}
final List<RobotToken> arguments = toMerge.getArguments();
for (final RobotToken t : arguments) {
outputLine.addArgument(t);
}
final List<RobotToken> comments = toMerge.getComment();
for (final RobotToken robotToken : comments) {
outputLine.addCommentPart(robotToken);
}
}
@SuppressWarnings("unchecked")
private <T extends AModelElement<? extends ARobotSectionTable>> void merge(final IExecutableStepsHolder<T> execUnit,
final List<RobotExecutableRow<T>> newExecutionContext,
final List<IExecutableRowDescriptor<T>> preBuildDescriptors, final int currentLine,
final RobotToken previousLineContinueToken) {
final IExecutableRowDescriptor<T> rowDesc = preBuildDescriptors.get(currentLine);
RobotExecutableRow<T> toUpdate = null;
if (newExecutionContext.isEmpty()) {
toUpdate = new RobotExecutableRow<>();
toUpdate.setParent((T) execUnit);
newExecutionContext.add(toUpdate);
} else {
toUpdate = newExecutionContext.get(newExecutionContext.size() - 1);
}
boolean wasComment = false;
boolean shouldMerge = false;
final List<RobotToken> elementTokens = rowDesc.getRow().getElementTokens();
final int size = elementTokens.size();
for (int i = 0; i < size; i++) {
final RobotToken rt = elementTokens.get(i);
if (rowDesc.getRowType() == ERowType.FOR_CONTINUE && toUpdate.getAction().getFilePosition().isNotSet()
&& "\\".equals(rt.getText())) {
toUpdate.setAction(rt);
}
if (rt == previousLineContinueToken) {
shouldMerge = true;
continue;
}
if (shouldMerge) {
if (rt.getTypes().contains(RobotTokenType.START_HASH_COMMENT) || wasComment) {
wasComment = true;
toUpdate.addCommentPart(rt);
} else {
if (toUpdate.getAction().getFilePosition().isNotSet()) {
toUpdate.setAction(rt);
} else {
toUpdate.addArgument(rt);
}
}
}
}
}
private <T extends AModelElement<? extends ARobotSectionTable>> void applyArtifactalForLineContinue(
final List<RobotExecutableRow<T>> newExecutionContext, final int lastForIndex,
final int lastForExecutableIndex) {
for (int line = lastForIndex + 1; line <= lastForExecutableIndex; line++) {
newExecutionContext.get(line).getAction().getTypes().add(RobotTokenType.FOR_CONTINUE_ARTIFICIAL_TOKEN);
}
}
private Optional<RobotToken> getPreviouseLineContinueToken(final List<RobotToken> tokens) {
Optional<RobotToken> token = Optional.empty();
for (final RobotToken rt : tokens) {
String text = rt.getRaw();
if (text != null) {
text = text.trim();
}
if (rt.getTypes().contains(RobotTokenType.PREVIOUS_LINE_CONTINUE) && "...".equals(rt.getText().trim())) {
token = Optional.of(rt);
} else if (text != null) {
if (text.equals("\\") || text.isEmpty()) {
continue;
} else {
break;
}
}
}
return token;
}
private <T extends AModelElement<? extends ARobotSectionTable>> List<IExecutableRowDescriptor<T>> preBuildDescriptors(
final List<RobotExecutableRow<T>> executionContext) {
final List<IExecutableRowDescriptor<T>> descs = new ArrayList<>(0);
for (final RobotExecutableRow<T> p : executionContext) {
descs.add(p.buildLineDescription());
}
return descs;
}
private <T extends AModelElement<? extends ARobotSectionTable>> boolean containsArtifactalContinueAfectingForLoop(
final IExecutableRowDescriptor<T> execRow) {
boolean result = false;
if (execRow.getRowType() == ERowType.SIMPLE) {
final RobotAction action = execRow.getAction();
if (action.isPresent() || !action.getToken().getFilePosition().isNotSet()) {
final RobotToken actionToken = action.getToken();
final FilePosition actionTokenPos = actionToken.getFilePosition();
final List<RobotToken> elementTokens = execRow.getRow().getElementTokens();
if (!actionTokenPos.isNotSet()) {
for (final RobotToken rt : elementTokens) {
if (rt != actionToken) {
if (rt.getLineNumber() < actionToken.getLineNumber()) {
result = true;
break;
}
}
}
}
}
}
return result;
}
}