/*
* Copyright 2016 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.rf.ide.core.testdata.mapping;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.rf.ide.core.testdata.mapping.collect.RobotTokensCollector;
import org.rf.ide.core.testdata.model.RobotFile;
import org.rf.ide.core.testdata.model.RobotFileOutput;
import org.rf.ide.core.testdata.text.read.RobotLine;
import org.rf.ide.core.testdata.text.read.recognizer.RobotToken;
import org.rf.ide.core.testdata.text.read.recognizer.RobotTokenType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ListMultimap;
/**
* @author wypych
*/
public class TwoModelReferencesLinker {
private final RobotTokensCollector tokenCollector;
public TwoModelReferencesLinker() {
this(new RobotTokensCollector());
}
@VisibleForTesting
protected TwoModelReferencesLinker(final RobotTokensCollector tokenCollector) {
this.tokenCollector = tokenCollector;
}
public void update(final RobotFileOutput oldModifiedOutput, final RobotFileOutput alreadyDumpedContent) {
update(oldModifiedOutput, alreadyDumpedContent, true);
}
public void update(final RobotFileOutput oldModifiedOutput, final RobotFileOutput alreadyDumpedContent,
final boolean fallbackAllowed) {
validateBasicThatOutputFromSameFile(oldModifiedOutput, alreadyDumpedContent);
final ListMultimap<RobotTokenType, RobotToken> oldViewAboutTokens = tokenCollector
.extractRobotTokens(oldModifiedOutput);
final ListMultimap<RobotTokenType, RobotToken> newViewAboutTokens = tokenCollector
.extractRobotTokens(alreadyDumpedContent);
if (fallbackAllowed) {
validateThatTheSameTokensInView(oldViewAboutTokens, newViewAboutTokens);
}
replaceNewReferenceByCorrespondingOld(oldModifiedOutput, oldViewAboutTokens, alreadyDumpedContent,
newViewAboutTokens);
}
@VisibleForTesting
protected void replaceNewReferenceByCorrespondingOld(final RobotFileOutput oldModifiedOutput,
final ListMultimap<RobotTokenType, RobotToken> oldViewAboutTokens,
final RobotFileOutput alreadyDumpedContent,
final ListMultimap<RobotTokenType, RobotToken> newViewAboutTokens) {
// general idea: 1. 'old tokens contains content as expected, so we only updating in them
// position with clear dirty flag'
// 2. next we are searching new token in new output line position and replacing it by old
// 3. last we removing old lines and adding new lines in old output object
final List<RobotLine> newContentLines = alreadyDumpedContent.getFileModel().getFileContent();
for (final RobotTokenType type : oldViewAboutTokens.keySet()) {
final List<RobotToken> oldToUpdate = oldViewAboutTokens.get(type);
final List<RobotToken> newToCopy = newViewAboutTokens.get(type);
final int tokSize = Math.min(oldToUpdate.size(), newToCopy.size());
for (int index = 0; index < tokSize; index++) {
final RobotToken oldToken = oldToUpdate.get(index);
final RobotToken newToken = newToCopy.get(index);
oldToken.setText(newToken.getText());
oldToken.setRaw(newToken.getRaw());
oldToken.setLineNumber(newToken.getLineNumber());
oldToken.setStartColumn(newToken.getStartColumn());
oldToken.setStartOffset(newToken.getStartOffset());
oldToken.clearDirtyFlag();
if (newToken.getLineNumber() >= 0) {
final RobotLine robotLine = newContentLines.get(newToken.getLineNumber() - 1);
final Optional<Integer> posToken = robotLine.getElementPositionInLine(newToken);
if (posToken.isPresent()) {
robotLine.setLineElementAt(posToken.get(), oldToken);
}
}
}
}
final RobotFile oldFileModel = oldModifiedOutput.getFileModel();
oldFileModel.removeLines();
for (final RobotLine line : newContentLines) {
oldFileModel.addNewLine(line);
}
}
@VisibleForTesting
protected void validateBasicThatOutputFromSameFile(final RobotFileOutput oldModifiedOutput,
final RobotFileOutput alreadyDumpedContent) {
try {
final Path oldPathNormalized = oldModifiedOutput.getProcessedFile().toPath().normalize();
final Path newContentFileNormalized = alreadyDumpedContent.getProcessedFile().toPath().normalize();
if (!Files.isSameFile(oldPathNormalized, newContentFileNormalized)) {
throw new DifferentOutputFile(
"File " + newContentFileNormalized + " is not expected, old path: " + oldPathNormalized);
}
} catch (final IOException e) {
throw new DifferentOutputFile(e);
}
}
@VisibleForTesting
protected void validateThatTheSameTokensInView(final ListMultimap<RobotTokenType, RobotToken> oldViewAboutTokens,
final ListMultimap<RobotTokenType, RobotToken> newViewAboutTokens) {
final Set<RobotTokenType> newKeySet = newViewAboutTokens.keySet();
for (final RobotTokenType t : newKeySet) {
final List<RobotToken> oldToks = new ArrayList<>(oldViewAboutTokens.get(t));
final List<RobotToken> newToks = newViewAboutTokens.get(t);
int toksSize = -1;
if (oldToks.size() == newToks.size()) {
toksSize = oldToks.size();
} else {
removePreviousLineContinue(oldToks);
final int oldNotEmpty = findTheFirstNotEmpty(oldToks);
final int newNotEmpty = findTheFirstNotEmpty(newToks);
if (oldNotEmpty == newNotEmpty) {
toksSize = oldNotEmpty;
} else {
throw new DifferentOutputFile("Type " + t + " has not the same number of elements in outputs.");
}
}
for (int i = 0; i < toksSize; i++) {
final RobotToken rtOld = oldToks.get(i);
final RobotToken rtNew = newToks.get(i);
if (!rtOld.getText().equals(rtNew.getText())) {
if (!isAcceptableContent(rtOld, rtNew)) {
throw new DifferentOutputFile("Token type " + t + " with index " + i
+ " doesn't contain the same content as old. Expected " + rtOld.getText() + " got "
+ rtNew.getText());
}
}
}
}
}
private boolean isAcceptableContent(final RobotToken rtOld, final RobotToken rtNew) {
final String oldTrimmed = rtOld.getText().trim();
final String newTrimmed = rtNew.getText().trim();
if ((oldTrimmed.isEmpty() && newTrimmed.equals("\\"))) {
return true;
}
if (oldTrimmed.equals("...") && newTrimmed.equals("...")) {
return true;
}
if (rtOld.getTypes().contains(RobotTokenType.VARIABLE_USAGE)) {
final String oldText = oldTrimmed;
final String newText = newTrimmed;
if (newText.endsWith("=")) {
if (oldText.equals(newText.substring(0, newText.length() - 1).trim())) {
return true;
}
}
}
if (rtNew.getTypes().contains(RobotTokenType.VARIABLE_USAGE)) {
final String oldText = oldTrimmed;
final String newText = newTrimmed;
if (oldText.endsWith("=")) {
if (newText.equals(oldText.substring(0, oldText.length() - 1).trim())) {
return true;
}
}
}
if (!newTrimmed.isEmpty() && newTrimmed.equals(oldTrimmed)) {
return true;
}
return false;
}
private void removePreviousLineContinue(final List<RobotToken> toks) {
for (int index = 0; index < toks.size(); index++) {
final RobotToken rTokCurrent = toks.get(index);
final String tokCurrent = rTokCurrent.getText();
if (tokCurrent.equals("\n...") || tokCurrent.equals("\r\n...") || tokCurrent.equals("\r...")) {
toks.remove(index);
index--;
}
}
}
private int findTheFirstNotEmpty(final List<RobotToken> t) {
final int index = -1;
for (int i = t.size() - 1; i >= 0; i--) {
if (!t.get(i).getText().isEmpty()) {
return i;
}
}
return index;
}
public static class DifferentOutputFile extends RuntimeException {
private static final long serialVersionUID = -4734971783082313050L;
public DifferentOutputFile(final String errorMsg) {
super(errorMsg);
}
public DifferentOutputFile(final Exception e) {
super(e);
}
}
}