/*
* 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.text.write;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.rf.ide.core.testdata.DumpContext;
import org.rf.ide.core.testdata.DumpedResultBuilder;
import org.rf.ide.core.testdata.DumpedResultBuilder.DumpedResult;
import org.rf.ide.core.testdata.IRobotFileDumper;
import org.rf.ide.core.testdata.model.AModelElement;
import org.rf.ide.core.testdata.model.FilePosition;
import org.rf.ide.core.testdata.model.ModelType;
import org.rf.ide.core.testdata.model.RobotFile;
import org.rf.ide.core.testdata.model.table.ARobotSectionTable;
import org.rf.ide.core.testdata.model.table.KeywordTable;
import org.rf.ide.core.testdata.model.table.SettingTable;
import org.rf.ide.core.testdata.model.table.SettingTableElementsComparator;
import org.rf.ide.core.testdata.model.table.TableHeader;
import org.rf.ide.core.testdata.model.table.TableHeaderComparator;
import org.rf.ide.core.testdata.model.table.TestCaseTable;
import org.rf.ide.core.testdata.model.table.VariableTable;
import org.rf.ide.core.testdata.model.table.keywords.UserKeyword;
import org.rf.ide.core.testdata.model.table.testcases.TestCase;
import org.rf.ide.core.testdata.model.table.variables.AVariable;
import org.rf.ide.core.testdata.text.read.IRobotLineElement;
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.separators.Separator;
import org.rf.ide.core.testdata.text.read.separators.Separator.SeparatorType;
import org.rf.ide.core.testdata.text.write.SectionBuilder.Section;
import org.rf.ide.core.testdata.text.write.SectionBuilder.SectionType;
import org.rf.ide.core.testdata.text.write.tables.ISectionTableDumper;
import org.rf.ide.core.testdata.text.write.tables.KeywordsSectionTableDumper;
import org.rf.ide.core.testdata.text.write.tables.SettingsSectionTableDumper;
import org.rf.ide.core.testdata.text.write.tables.TestCasesSectionTableDumper;
import org.rf.ide.core.testdata.text.write.tables.VariablesSectionTableDumper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.Files;
public abstract class ARobotFileDumper implements IRobotFileDumper {
private final List<ISectionTableDumper> tableDumpers;
private final DumperHelper aDumpHelper;
private DumpContext dumpContext;
public ARobotFileDumper() {
this.aDumpHelper = new DumperHelper(this);
this.tableDumpers = new ArrayList<ISectionTableDumper>(
Arrays.asList(new SettingsSectionTableDumper(aDumpHelper), new VariablesSectionTableDumper(aDumpHelper),
new TestCasesSectionTableDumper(aDumpHelper), new KeywordsSectionTableDumper(aDumpHelper)));
}
@Override
public void dump(final File robotFile, final RobotFile model) throws IOException {
Files.write(dump(model), robotFile, Charset.forName("utf-8"));
}
@Override
public DumpedResult dumpToResultObject(final RobotFile model) {
return newLines(model, new DumpedResultBuilder());
}
@Override
public String dump(final RobotFile model) {
return dumpToResultObject(model).newContent();
}
private DumpedResult newLines(final RobotFile model, final DumpedResultBuilder builder) {
final List<RobotLine> lines = new ArrayList<>(0);
builder.producedLines(lines);
this.aDumpHelper.setTokenDumpListener(builder);
final SectionBuilder sectionBuilder = new SectionBuilder();
final List<Section> sections = sectionBuilder.build(model);
dumpUntilRobotHeaderSection(model, sections, 0, lines);
final SettingTable settingTable = model.getSettingTable();
final List<AModelElement<SettingTable>> sortedSettings = sortSettings(settingTable);
final VariableTable variableTable = model.getVariableTable();
final List<AModelElement<VariableTable>> sortedVariables = sortVariables(variableTable);
final TestCaseTable testCaseTable = model.getTestCaseTable();
final List<AModelElement<TestCaseTable>> sortedTestCases = getTestCases(testCaseTable);
final KeywordTable keywordTable = model.getKeywordTable();
final List<AModelElement<KeywordTable>> sortedKeywords = getKeywords(keywordTable);
final List<TableHeader<? extends ARobotSectionTable>> headers = new ArrayList<>(0);
headers.addAll(settingTable.getHeaders());
headers.addAll(variableTable.getHeaders());
headers.addAll(testCaseTable.getHeaders());
headers.addAll(keywordTable.getHeaders());
Collections.sort(headers, new TableHeaderComparator<>());
for (final TableHeader<? extends ARobotSectionTable> th : headers) {
List<AModelElement<ARobotSectionTable>> sorted = null;
final int sectionWithHeader = getSectionWithHeader(sections, th);
ISectionTableDumper dumperToUse = null;
for (final ISectionTableDumper dumper : tableDumpers) {
if (dumper.isServedType(th)) {
dumperToUse = dumper;
break;
}
}
if (th.getModelType() == ModelType.SETTINGS_TABLE_HEADER) {
sorted = copySettings(sortedSettings);
} else if (th.getModelType() == ModelType.VARIABLES_TABLE_HEADER) {
sorted = copyVariables(sortedVariables);
} else if (th.getModelType() == ModelType.KEYWORDS_TABLE_HEADER) {
sorted = copyKeywords(sortedKeywords);
} else if (th.getModelType() == ModelType.TEST_CASE_TABLE_HEADER) {
sorted = copyTestCases(sortedTestCases);
}
dumperToUse.dump(model, sections, sectionWithHeader, th, sorted, lines);
if (th.getModelType() == ModelType.SETTINGS_TABLE_HEADER) {
sortedSettings.clear();
sortedSettings.addAll(copyUpSettings(sorted));
} else if (th.getModelType() == ModelType.VARIABLES_TABLE_HEADER) {
sortedVariables.clear();
sortedVariables.addAll(copyUpVariables(sorted));
} else if (th.getModelType() == ModelType.KEYWORDS_TABLE_HEADER) {
sortedKeywords.clear();
sortedKeywords.addAll(copyUpKeywords(sorted));
} else if (th.getModelType() == ModelType.TEST_CASE_TABLE_HEADER) {
sortedTestCases.clear();
sortedTestCases.addAll(copyUpTestCases(sorted));
}
if (sectionWithHeader > -1) {
dumpUntilRobotHeaderSection(model, sections, sectionWithHeader + 1, lines);
}
}
final List<Section> userSections = filterUserTableHeadersOnly(sections);
dumpUntilRobotHeaderSection(model, userSections, 0, lines);
aDumpHelper.addEOFinCaseIsMissing(model, lines);
return builder.build();
}
@SuppressWarnings("unchecked")
private List<AModelElement<ARobotSectionTable>> copySettings(final List<AModelElement<SettingTable>> elems) {
final List<AModelElement<ARobotSectionTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
copied.add(((AModelElement<ARobotSectionTable>) stE));
}
return copied;
}
@SuppressWarnings("unchecked")
private List<AModelElement<ARobotSectionTable>> copyVariables(final List<AModelElement<VariableTable>> elems) {
final List<AModelElement<ARobotSectionTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
fixVariableDeclarationToName((AVariable) stE);
copied.add(((AModelElement<ARobotSectionTable>) stE));
}
return copied;
}
private void fixVariableDeclarationToName(final AVariable var) {
final String varName = var.getName();
if (varName != null && !varName.isEmpty()) {
String varDeclaration = varName;
final String correctBeginOfVariable = var.getType().getIdentificator() + "{";
if (!varName.startsWith(correctBeginOfVariable)) {
varDeclaration = correctBeginOfVariable + varName;
}
if (!varDeclaration.endsWith("}")) {
varDeclaration += "}";
}
var.getDeclaration().setText(varDeclaration);
}
}
@SuppressWarnings("unchecked")
private List<AModelElement<ARobotSectionTable>> copyTestCases(final List<AModelElement<TestCaseTable>> elems) {
final List<AModelElement<ARobotSectionTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
copied.add(((AModelElement<ARobotSectionTable>) stE));
}
return copied;
}
@SuppressWarnings("unchecked")
private List<AModelElement<ARobotSectionTable>> copyKeywords(final List<AModelElement<KeywordTable>> elems) {
final List<AModelElement<ARobotSectionTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
copied.add(((AModelElement<ARobotSectionTable>) stE));
}
return copied;
}
@SuppressWarnings("unchecked")
private List<AModelElement<SettingTable>> copyUpSettings(final List<AModelElement<ARobotSectionTable>> elems) {
final List<AModelElement<SettingTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
copied.add(((AModelElement<SettingTable>) stE));
}
return copied;
}
@SuppressWarnings("unchecked")
private List<AModelElement<VariableTable>> copyUpVariables(final List<AModelElement<ARobotSectionTable>> elems) {
final List<AModelElement<VariableTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
copied.add(((AModelElement<VariableTable>) stE));
}
return copied;
}
@SuppressWarnings("unchecked")
private List<AModelElement<TestCaseTable>> copyUpTestCases(final List<AModelElement<ARobotSectionTable>> elems) {
final List<AModelElement<TestCaseTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
copied.add(((AModelElement<TestCaseTable>) stE));
}
return copied;
}
@SuppressWarnings("unchecked")
private List<AModelElement<KeywordTable>> copyUpKeywords(final List<AModelElement<ARobotSectionTable>> elems) {
final List<AModelElement<KeywordTable>> copied = new ArrayList<>();
for (final AModelElement<?> stE : elems) {
copied.add(((AModelElement<KeywordTable>) stE));
}
return copied;
}
private List<AModelElement<KeywordTable>> getKeywords(final KeywordTable keywordTable) {
final List<AModelElement<KeywordTable>> list = new ArrayList<>();
for (final UserKeyword uk : keywordTable.getKeywords()) {
list.add(uk);
}
return list;
}
private List<AModelElement<TestCaseTable>> getTestCases(final TestCaseTable testCaseTable) {
final List<AModelElement<TestCaseTable>> list = new ArrayList<>();
for (final TestCase tc : testCaseTable.getTestCases()) {
list.add(tc);
}
return list;
}
private List<AModelElement<SettingTable>> sortSettings(final SettingTable settingTable) {
final List<AModelElement<SettingTable>> list = new ArrayList<>();
list.addAll(settingTable.getDefaultTags());
list.addAll(settingTable.getDocumentation());
list.addAll(settingTable.getForceTags());
list.addAll(settingTable.getSuiteSetups());
list.addAll(settingTable.getSuiteTeardowns());
list.addAll(settingTable.getTestSetups());
list.addAll(settingTable.getTestTeardowns());
list.addAll(settingTable.getTestTemplates());
list.addAll(settingTable.getTestTimeouts());
list.addAll(settingTable.getUnknownSettings());
list.addAll(settingTable.getMetadatas());
list.addAll(settingTable.getImports());
Collections.sort(list, new SettingTableElementsComparator());
repositionElementsBaseOnList(list, settingTable.getImports());
repositionElementsBaseOnList(list, settingTable.getMetadatas());
return list;
}
@VisibleForTesting
protected void repositionElementsBaseOnList(final List<AModelElement<SettingTable>> src,
final List<? extends AModelElement<SettingTable>> correctors) {
if (correctors.size() >= 2) {
int hitCorrectors = 0;
AModelElement<SettingTable> currentCorrector = correctors.get(hitCorrectors);
for (int i = 0; i < src.size(); i++) {
final AModelElement<SettingTable> m = src.get(i);
if (correctors.contains(m)) {
if (currentCorrector == m) {
hitCorrectors++;
if (hitCorrectors < correctors.size()) {
currentCorrector = correctors.get(hitCorrectors);
} else {
if (isNextTheSameAsCurrent(src, m, i)) {
src.remove(i);
i--;
}
}
} else {
if (hitCorrectors < correctors.size()) {
if (currentCorrector.getBeginPosition().isNotSet()) {
src.add(i, currentCorrector);
} else {
src.set(i, currentCorrector);
}
} else {
src.remove(i);
}
i--;
}
}
}
if (hitCorrectors != correctors.size()) {
throw new IllegalStateException("Not all elements included in output before.");
}
}
}
@VisibleForTesting
protected boolean isNextTheSameAsCurrent(final List<AModelElement<SettingTable>> src,
final AModelElement<SettingTable> m, final int currentIndex) {
return (currentIndex + 1 < src.size() && m == src.get(currentIndex + 1));
}
private List<AModelElement<VariableTable>> sortVariables(final VariableTable variableTable) {
final List<AModelElement<VariableTable>> list = new ArrayList<>();
for (final AVariable var : variableTable.getVariables()) {
list.add(var);
}
return list;
}
private List<Section> filterUserTableHeadersOnly(final List<Section> sections) {
final List<Section> userSections = new ArrayList<>(0);
for (final Section section : sections) {
final SectionType type = section.getType();
if (type == SectionType.TRASH || type == SectionType.USER_TABLE) {
userSections.add(section);
}
}
return userSections;
}
private void dumpUntilRobotHeaderSection(final RobotFile model, final List<Section> sections,
final int currentSection, final List<RobotLine> outLines) {
int removedIndex = -1;
final int sectionSize = sections.size();
for (int sectionId = currentSection; sectionId < sectionSize; sectionId++) {
final Section section = sections.get(sectionId);
final SectionType type = section.getType();
if (type == SectionType.TRASH || type == SectionType.USER_TABLE) {
dumpFromTo(model, section.getStart(), section.getEnd(), outLines);
removedIndex++;
} else {
break;
}
}
for (int i = removedIndex; i > -1; i--) {
sections.remove(currentSection);
}
}
private void dumpFromTo(final RobotFile model, final FilePosition start, final FilePosition end,
final List<RobotLine> outLines) {
boolean meetEnd = false;
final List<RobotLine> fileContent = model.getFileContent();
for (final RobotLine line : fileContent) {
for (final IRobotLineElement elem : line.getLineElements()) {
final FilePosition elemPos = elem.getFilePosition();
if (elemPos.isBefore(start)) {
continue;
} else if (elemPos.isSamePlace(start) || elemPos.isSamePlace(end)
|| (elemPos.isAfter(start) && elemPos.isBefore(end))) {
aDumpHelper.getDumpLineUpdater().updateLine(model, outLines, elem);
} else {
meetEnd = true;
break;
}
}
if (meetEnd) {
break;
} else {
final IRobotLineElement endOfLine = line.getEndOfLine();
final FilePosition endOfLineFP = endOfLine.getFilePosition();
if (endOfLineFP.isSamePlace(start) || endOfLineFP.isSamePlace(end)
|| (endOfLineFP.isAfter(start) && endOfLineFP.isBefore(end))) {
aDumpHelper.getDumpLineUpdater().updateLine(model, outLines, endOfLine);
}
}
}
}
private int getSectionWithHeader(final List<Section> sections,
final TableHeader<? extends ARobotSectionTable> theader) {
int section = -1;
final int sectionsSize = sections.size();
for (int sectionId = 0; sectionId < sectionsSize; sectionId++) {
final Section s = sections.get(sectionId);
final FilePosition thPos = theader.getDeclaration().getFilePosition();
if (thPos.isSamePlace(s.getStart()) || (thPos.isAfter(s.getStart()) && thPos.isBefore(s.getEnd()))) {
section = sectionId;
break;
}
}
return section;
}
public Separator getSeparator(final RobotFile model, final List<RobotLine> lines, final IRobotLineElement lastToken,
final IRobotLineElement currentToken) {
Separator sep = null;
final FilePosition fp = lastToken.getFilePosition();
final FilePosition fpTok = currentToken.getFilePosition();
boolean wasLastToken = false;
IRobotLineElement tokenToSearch = null;
final int offset;
if (fpTok.isNotSet()) {
if (fp.isNotSet()) {
tokenToSearch = lastToken;
offset = -1;
} else {
wasLastToken = true;
tokenToSearch = lastToken;
offset = fp.getOffset();
}
} else {
tokenToSearch = currentToken;
offset = fpTok.getOffset();
}
final RobotLine line;
if (offset > -1) {
line = model.getFileContent().get(model.getRobotLineIndexBy(offset).get());
} else {
if (!lines.isEmpty()) {
line = lines.get(lines.size() - 1);
} else {
line = new RobotLine(0, model);
}
}
final List<IRobotLineElement> elems = line.getLineElements();
final Optional<Integer> tokenPos = line.getElementPositionInLine(tokenToSearch);
if (tokenPos.isPresent()) {
final Integer tokPos = tokenPos.get();
final int start = (wasLastToken) ? tokPos + 1 : tokPos - 1;
for (int index = start; index < elems.size() && index >= 0; index--) {
final IRobotLineElement elem = elems.get(index);
if (elem instanceof RobotToken) {
break;
} else if (elem instanceof Separator) {
sep = (Separator) elem;
break;
} else {
continue;
}
}
if (sep != null) {
final Optional<SeparatorType> separatorForLine = line.getSeparatorForLine();
if (separatorForLine.isPresent()) {
if (sep.getTypes().get(0) != separatorForLine.get()) {
if (separatorForLine.get() == SeparatorType.PIPE) {
final List<Separator> seps = new ArrayList<>(0);
for (final IRobotLineElement e : elems) {
if (e instanceof Separator) {
seps.add((Separator) e);
}
}
if (seps.size() > 1) {
sep = seps.get(seps.size() - 1);
} else {
sep = new Separator();
sep.setRaw(" | ");
sep.setText(" | ");
sep.setType(SeparatorType.PIPE);
}
}
}
}
}
}
if (sep == null) {
sep = Separator.matchSeparator(dumpContext.getPreferedSeparator());
}
if (sep == null || !isAcceptableForDefault(sep)) {
sep = getSeparatorDefault();
}
if (sep != null) {
final Optional<SeparatorType> sepInLine = line.getSeparatorForLine();
if (sepInLine.isPresent()) {
if (sepInLine.get() != sep.getTypes().get(0)) {
if (sepInLine.get() == SeparatorType.PIPE) {
sep = new Separator();
sep.setRaw(" | ");
sep.setText(" | ");
sep.setType(SeparatorType.PIPE);
} else {
sep = new Separator();
sep.setRaw("\t");
sep.setText("\t");
sep.setType(SeparatorType.TABULATOR_OR_DOUBLE_SPACE);
}
}
}
}
return sep;
}
@Override
public void setContext(final DumpContext ctx) {
this.dumpContext = ctx;
}
public boolean isFileDirty() {
return this.dumpContext.isDirty();
}
protected abstract Separator getSeparatorDefault();
protected abstract boolean isAcceptableForDefault(final Separator separator);
protected abstract boolean canBeSeparatorAddBeforeExecutableUnitName(final Separator currentSeparator);
}