/* * 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.tables; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import org.rf.ide.core.testdata.mapping.table.ElementsUtility; 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.IExecutableStepsHolder; import org.rf.ide.core.testdata.model.table.RobotElementsComparatorWithPositionChangedPresave; import org.rf.ide.core.testdata.model.table.TableHeader; import org.rf.ide.core.testdata.text.read.EndOfLineBuilder.EndOfLineTypes; import org.rf.ide.core.testdata.text.read.IRobotLineElement; import org.rf.ide.core.testdata.text.read.IRobotTokenType; 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 org.rf.ide.core.testdata.text.read.separators.Separator; import org.rf.ide.core.testdata.text.write.DumperHelper; import org.rf.ide.core.testdata.text.write.SectionBuilder.Section; public abstract class AExecutableTableElementDumper implements IExecutableSectionElementDumper { private final DumperHelper aDumpHelper; private final ElementsUtility anElementHelper; private final ModelType servedType; private final TableElementDumperHelper elemDumperHelper; private final List<IForceFixBeforeDumpTask> afterSortTasks = new ArrayList<>(0); public AExecutableTableElementDumper(final DumperHelper aDumpHelper, final ModelType servedType) { this.aDumpHelper = aDumpHelper; this.anElementHelper = new ElementsUtility(); this.servedType = servedType; this.elemDumperHelper = new TableElementDumperHelper(); } protected void addAfterSortTask(final IForceFixBeforeDumpTask fixTask) { this.afterSortTasks.add(fixTask); } protected DumperHelper getDumperHelper() { return this.aDumpHelper; } protected ElementsUtility getElementHelper() { return this.anElementHelper; } protected TableElementDumperHelper getElementDumperHelper() { return this.elemDumperHelper; } @Override public boolean isServedType(final AModelElement<? extends IExecutableStepsHolder<?>> element) { return (element.getModelType() == servedType); } public abstract RobotElementsComparatorWithPositionChangedPresave getSorter( final AModelElement<? extends IExecutableStepsHolder<?>> currentElement); @Override public void dump(final RobotFile model, final List<Section> sections, final int sectionWithHeaderPos, final TableHeader<? extends ARobotSectionTable> th, final List<AModelElement<? extends IExecutableStepsHolder<?>>> sortedSettings, final AModelElement<? extends IExecutableStepsHolder<?>> currentElement, final List<RobotLine> lines) { final RobotToken elemDeclaration = currentElement.getDeclaration(); final FilePosition filePosition = elemDeclaration.getFilePosition(); int fileOffset = -1; if (filePosition != null && !filePosition.isNotSet()) { fileOffset = filePosition.getOffset(); } final RobotLine currentLine = getLineForOffset(model, fileOffset); if (!lines.isEmpty()) { if (lines.get(lines.size() - 1).getEndOfLine().getTypes().contains(EndOfLineTypes.EOF)) { lines.get(lines.size() - 1).setEndOfLine(null, -1, -1); } } IRobotLineElement lastToken = elemDeclaration; if (currentLine != null) { getDumperHelper().getSeparatorDumpHelper().dumpSeparatorsBeforeToken(model, currentLine, elemDeclaration, lines); } final List<RobotToken> tokens = prepareTokens(currentElement); finishPreviousLineIfIsRequired(model, lines, lastToken, tokens); final int nrOfTokens = getElementDumperHelper().getLastIndexNotEmptyIndex(tokens) + 1; if (!elemDeclaration.isDirty() && currentLine != null) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, elemDeclaration); lastToken = addSuffixAfterDeclarationElement(model, lines, elemDeclaration, currentLine, lastToken); } else { final boolean shouldDumpDeclaration = checkIfShouldBeDumpDirectly(elemDeclaration, tokens); if (shouldDumpDeclaration) { final boolean wasPrettyAlign = handleEmptyAndPrettyAlignForDirectlyDump(model, lines, lastToken); addDoubleSeparatorInCaseOfPipeSeparations(model, lines, lastToken); if (!getDumperHelper().wasSeparatorBefore(lines) && !wasPrettyAlign) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, getDumperHelper().getSeparator(model, lines, lastToken, lastToken)); } getDumperHelper().getDumpLineUpdater().updateLine(model, lines, elemDeclaration); } lastToken = elemDeclaration; } // dump as it is if (canBeDumpedDirectly(lastToken, tokens)) { final boolean wasDumped = getElementDumperHelper().dumpAsItIs(getDumperHelper(), model, lastToken, tokens, lines); if (wasDumped) { return; } } final List<Integer> lineEndPos = new ArrayList<>(getElementDumperHelper().getLineEndPos(model, tokens)); // just dump now handleLineBreak(model, lines, currentLine, lastToken, tokens, lineEndPos); for (int tokenId = 0; tokenId < nrOfTokens; tokenId++) { final IRobotLineElement tokElem = tokens.get(tokenId); if (tokenId == 0 && (tokElem == lastToken || lastToken.getTypes().contains(RobotTokenType.ASSIGNMENT) || lastToken.getTypes().contains(RobotTokenType.PRETTY_ALIGN_SPACE))) { lastToken = tokElem; continue; } if (isNewLineWithPreviousLineContinueToken(model, lines, lastToken, tokElem)) { lastToken = tokElem; continue; } if (!getDumperHelper().wasSeparatorBefore(lines)) { final Separator sep = getDumperHelper().getSeparator(model, lines, lastToken, tokElem); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, sep); lastToken = sep; } getDumperHelper().getDumpLineUpdater().updateLine(model, lines, tokElem); lastToken = tokElem; RobotLine currentLineTok = null; if (!tokElem.getFilePosition().isNotSet()) { currentLineTok = null; if (fileOffset >= 0) { final Optional<Integer> lineIndex = model.getRobotLineIndexBy(tokElem.getFilePosition().getOffset()); if (lineIndex.isPresent()) { currentLineTok = model.getFileContent().get(lineIndex.get()); } } lastToken = prettyAlignForCurrentDumpedNotDeclarationToken(model, lines, lastToken, tokElem, currentLineTok); } final boolean dumpAfterSep = dumpAfterSeparator(tokens, nrOfTokens, tokenId, tokElem); if (dumpAfterSep && currentLine != null) { getDumperHelper().getSeparatorDumpHelper().dumpSeparatorsAfterToken(model, currentLine, lastToken, lines); } // check if is not end of line handleLastEndOfTheLineBreak(model, lines, currentLine, lastToken, tokens, nrOfTokens, lineEndPos, tokenId); } } private boolean isNewLineWithPreviousLineContinueToken(final RobotFile model, final List<RobotLine> lines, final IRobotLineElement lastToken, final IRobotLineElement tokElem) { if (tokElem.getText().equals("\n...")) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, getDumperHelper().getLineSeparator(model)); final RobotToken lineContinueToken = new RobotToken(); lineContinueToken.setRaw("..."); lineContinueToken.setText("..."); lineContinueToken.setType(RobotTokenType.PREVIOUS_LINE_CONTINUE); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, getDumperHelper().getSeparator(model, lines, lastToken, lineContinueToken)); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, lineContinueToken); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, getDumperHelper().getSeparator(model, lines, lastToken, lineContinueToken)); return true; } return false; } private RobotLine getLineForOffset(final RobotFile model, final int fileOffset) { RobotLine currentLine = null; if (fileOffset >= 0) { final Optional<Integer> lineIndex = model.getRobotLineIndexBy(fileOffset); if (lineIndex.isPresent()) { currentLine = model.getFileContent().get(lineIndex.get()); } } return currentLine; } private void handleLastEndOfTheLineBreak(final RobotFile model, final List<RobotLine> lines, final RobotLine currentLine, final IRobotLineElement lastToken, final List<RobotToken> tokens, final int nrOfTokens, final List<Integer> lineEndPos, final int tokenId) { if (lineEndPos.contains(tokenId) && tokenId + 1 < nrOfTokens) { if (currentLine != null) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, currentLine.getEndOfLine()); } else { // new end of line } if (!tokens.isEmpty()) { final Separator sepNew = getDumperHelper().getSeparator(model, lines, lastToken, tokens.get(tokenId + 1)); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, sepNew); final RobotToken lineContinueToken = new RobotToken(); lineContinueToken.setRaw("..."); lineContinueToken.setText("..."); lineContinueToken.setType(RobotTokenType.PREVIOUS_LINE_CONTINUE); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, lineContinueToken); } } } private boolean dumpAfterSeparator(final List<RobotToken> tokens, final int nrOfTokens, final int tokenId, final IRobotLineElement tokElem) { boolean dumpAfterSep = false; if (tokenId + 1 < nrOfTokens) { if (!tokElem.getTypes().contains(RobotTokenType.START_HASH_COMMENT) && !tokElem.getTypes().contains(RobotTokenType.COMMENT_CONTINUE)) { final IRobotLineElement nextElem = tokens.get(tokenId + 1); if (nextElem.getTypes().contains(RobotTokenType.START_HASH_COMMENT) || nextElem.getTypes().contains(RobotTokenType.COMMENT_CONTINUE)) { dumpAfterSep = true; } } } else { dumpAfterSep = true; } return dumpAfterSep; } private IRobotLineElement prettyAlignForCurrentDumpedNotDeclarationToken(final RobotFile model, final List<RobotLine> lines, final IRobotLineElement lastToken, final IRobotLineElement tokElem, final RobotLine currentLineTok) { if (currentLineTok != null && !tokElem.isDirty()) { final List<IRobotLineElement> lineElements = currentLineTok.getLineElements(); final int thisTokenPosIndex = lineElements.indexOf(tokElem); if (thisTokenPosIndex >= 0) { if (lineElements.size() - 1 > thisTokenPosIndex + 1) { final IRobotLineElement nextElem = lineElements.get(thisTokenPosIndex + 1); if (nextElem.getTypes().contains(RobotTokenType.PRETTY_ALIGN_SPACE) || nextElem.getTypes().contains(RobotTokenType.ASSIGNMENT)) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, nextElem); return nextElem; } } } } return lastToken; } private void handleLineBreak(final RobotFile model, final List<RobotLine> lines, final RobotLine currentLine, final IRobotLineElement lastToken, final List<RobotToken> tokens, final List<Integer> lineEndPos) { if (tokens.size() > 1 && lineEndPos.contains(0)) { if (currentLine != null) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, currentLine.getEndOfLine()); } if (getDumperHelper().isSeparatorForExecutableUnitName( getDumperHelper().getSeparator(model, lines, lastToken, lastToken))) { int countSeparatorsBefore = getDumperHelper().countSeparatorsBefore(lines); Separator beforeExecRowSep = null; if (countSeparatorsBefore == 0) { beforeExecRowSep = getDumperHelper().getSeparator(model, lines, lastToken, lastToken); if (beforeExecRowSep.getText().equals(" | ")) { beforeExecRowSep.setText("| "); beforeExecRowSep.setRaw("| "); } getDumperHelper().getDumpLineUpdater().updateLine(model, lines, beforeExecRowSep); ++countSeparatorsBefore; } if (countSeparatorsBefore == 1) { Separator separator; if (beforeExecRowSep == null) { separator = getDumperHelper().getSeparator(model, lines, lastToken, lastToken); } else { separator = getDumperHelper().getSeparator(model, lines, beforeExecRowSep, beforeExecRowSep); } getDumperHelper().getDumpLineUpdater().updateLine(model, lines, separator); } } else { final Separator sep = getDumperHelper().getSeparator(model, lines, lastToken, tokens.get(0)); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, sep); } final RobotToken lineContinueToken = new RobotToken(); lineContinueToken.setRaw("..."); lineContinueToken.setText("..."); lineContinueToken.setType(RobotTokenType.PREVIOUS_LINE_CONTINUE); getDumperHelper().getDumpLineUpdater().updateLine(model, lines, lineContinueToken); } } private boolean canBeDumpedDirectly(final IRobotLineElement lastToken, final List<RobotToken> tokens) { return !lastToken.getFilePosition().isNotSet() && !getElementDumperHelper().getFirstBrokenChainPosition(tokens, true).isPresent() && !tokens.isEmpty() && !getElementDumperHelper().isDirtyAnyDirtyInside(tokens); } private void addDoubleSeparatorInCaseOfPipeSeparations(final RobotFile model, final List<RobotLine> lines, final IRobotLineElement lastToken) { if (getDumperHelper() .isSeparatorForExecutableUnitName(getDumperHelper().getSeparator(model, lines, lastToken, lastToken))) { int countSeparatorsBefore = getDumperHelper().countSeparatorsBefore(lines); if (countSeparatorsBefore == 0) { final Separator beforeExecRowSep = getDumperHelper().getSeparator(model, lines, lastToken, lastToken); if (beforeExecRowSep.getText().equals(" | ")) { beforeExecRowSep.setText("| "); beforeExecRowSep.setRaw("| "); } getDumperHelper().getDumpLineUpdater().updateLine(model, lines, beforeExecRowSep); ++countSeparatorsBefore; } if (countSeparatorsBefore == 1) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, getDumperHelper().getSeparator(model, lines, lastToken, lastToken)); } } } private boolean handleEmptyAndPrettyAlignForDirectlyDump(final RobotFile model, final List<RobotLine> lines, final IRobotLineElement lastToken) { boolean wasPrettyAlign = false; if (!lines.isEmpty()) { final RobotLine lastLine = lines.get(lines.size() - 1); if (getDumperHelper().getEmptyLineDumper().isEmptyLine(lastLine)) { final List<IRobotLineElement> lineElements = lastLine.getLineElements(); if (!lineElements.isEmpty()) { final IRobotLineElement lastElement = lineElements.get(lineElements.size() - 1); if (lastElement.getTypes().contains(RobotTokenType.PRETTY_ALIGN_SPACE)) { wasPrettyAlign = (lastElement.getStartOffset() + lastElement.getText().length()) == lastToken .getStartOffset(); if (!wasPrettyAlign && lastElement.getLineNumber() != lastToken.getLineNumber()) { wasPrettyAlign = true; } } } } else { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, getDumperHelper().getLineSeparator(model)); } } return wasPrettyAlign; } private boolean checkIfShouldBeDumpDirectly(final RobotToken elemDeclaration, final List<RobotToken> tokens) { boolean shouldDumpDeclaration = true; if (!elemDeclaration.isNotEmpty()) { if (tokens.size() > 1) { final RobotToken theNextToken = tokens.get(1); final List<IRobotTokenType> types = theNextToken.getTypes(); if (types.contains(RobotTokenType.COMMENT_CONTINUE) || types.contains(RobotTokenType.START_HASH_COMMENT)) { shouldDumpDeclaration = false; } } } return shouldDumpDeclaration; } private List<RobotToken> prepareTokens(final AModelElement<? extends IExecutableStepsHolder<?>> currentElement) { final RobotElementsComparatorWithPositionChangedPresave sorter = getSorter(currentElement); final List<RobotToken> tokens = sorter.getTokensInElement(); Collections.sort(tokens, sorter); for (final IForceFixBeforeDumpTask task : afterSortTasks) { task.fixBeforeDump(currentElement, tokens); } return tokens; } private IRobotLineElement addSuffixAfterDeclarationElement(final RobotFile model, final List<RobotLine> lines, final RobotToken elemDeclaration, final RobotLine currentLine, final IRobotLineElement lastToken) { IRobotLineElement lastTokenToReturn = lastToken; final List<IRobotLineElement> lineElements = currentLine.getLineElements(); final int tokenPosIndex = lineElements.indexOf(elemDeclaration); if (lineElements.size() - 1 > tokenPosIndex + 1) { for (int index = tokenPosIndex + 1; index < lineElements.size(); index++) { final IRobotLineElement nextElem = lineElements.get(index); final List<IRobotTokenType> types = nextElem.getTypes(); if (types.contains(RobotTokenType.PRETTY_ALIGN_SPACE) || types.contains(RobotTokenType.ASSIGNMENT)) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, nextElem); lastTokenToReturn = nextElem; } else { break; } } } return lastTokenToReturn; } private void finishPreviousLineIfIsRequired(final RobotFile model, final List<RobotLine> lines, final IRobotLineElement lastToken, final List<RobotToken> tokens) { if (getDumperHelper().isCurrentFileDirty() && !lines.isEmpty() && !getDumperHelper().getEmptyLineDumper().isEmptyLine(lines.get(lines.size() - 1)) && canBeDumpedDirectly(lastToken, tokens)) { getDumperHelper().getDumpLineUpdater().updateLine(model, lines, getDumperHelper().getLineSeparator(model)); } } }