/******************************************************************************* * (c) Copyright 2016 Hewlett-Packard Development Company, L.P. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License v2.0 which accompany this distribution. * * The Apache License is available at * http://www.apache.org/licenses/LICENSE-2.0 * *******************************************************************************/ package io.cloudslang.lang.compiler.parser.utils; import io.cloudslang.lang.compiler.SlangSource; import io.cloudslang.lang.compiler.parser.MetadataParser; import io.cloudslang.lang.compiler.parser.model.ParsedDescriptionData; import io.cloudslang.lang.compiler.parser.model.ParsedDescriptionSection; import io.cloudslang.lang.compiler.utils.MetadataUtils; import io.cloudslang.lang.compiler.utils.SlangSourceUtils; import io.cloudslang.lang.compiler.validator.matcher.DescriptionPatternMatcher; import io.cloudslang.lang.entities.constants.Regex; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; public class MetadataValidatorImpl implements MetadataValidator { private MetadataParser metadataParser; private DescriptionPatternMatcher descriptionPatternMatcher; public MetadataValidatorImpl() { descriptionPatternMatcher = new DescriptionPatternMatcher(); } @Override public List<RuntimeException> validateCheckstyle(SlangSource source) { Validate.notNull(source.getContent(), "Source " + source.getName() + " cannot be null"); try { return extractCheckstyleData(source); } catch (Throwable e) { throw new RuntimeException( "There was a problem extracting checkstyle data for source [" + source.getName() + "] - " + e.getMessage(), e ); } } private List<RuntimeException> extractCheckstyleData(SlangSource source) { List<String> lines = SlangSourceUtils.readLines(source); ParsedDescriptionData parsedDescriptionData = metadataParser.parse(source); List<RuntimeException> errors = new ArrayList<>(); // process flow descriptions List<ParsedDescriptionSection> topLevelDescriptions = parsedDescriptionData.getTopLevelDescriptions(); for (ParsedDescriptionSection topLevelDescription : topLevelDescriptions) { errors.addAll(processCommonRules(lines, topLevelDescription, false)); } // process step descriptions Collection<ParsedDescriptionSection> stepDescriptions = parsedDescriptionData.getStepDescriptions().values(); for (ParsedDescriptionSection stepDescription : stepDescriptions) { errors.addAll(processCommonRules(lines, stepDescription, true)); } return errors; } private List<RuntimeException> processCommonRules( List<String> lines, ParsedDescriptionSection parsedDescriptionSection, boolean isStep) { List<RuntimeException> errors = new ArrayList<>(); int startLineNumberZeroBased = parsedDescriptionSection.getStartLineNumber() - 1; // validate begin wrapper line validateBeginWrapperLine(lines, isStep, errors, startLineNumberZeroBased); boolean finished = false; String previousTag = null; int previousItemEndLineNumber = -1; for (int lineNrZeroBased = startLineNumberZeroBased + 1; !finished && lineNrZeroBased < lines.size(); lineNrZeroBased++) { String currentLine = lines.get(lineNrZeroBased); // #! @tag var: content // #! @tag: content boolean variableLine = isVariableLine(currentLine); boolean isGeneralLine = isGeneralLine(currentLine); if (variableLine || isGeneralLine || isVariableLineDeclarationOnly(currentLine)) { // extract tag String currentTag; if (variableLine) { Pair<String, String> declaration = descriptionPatternMatcher.getDescriptionVariableLineData(currentLine); String[] declarationElements = descriptionPatternMatcher.splitDeclaration(declaration.getLeft()); currentTag = declarationElements[0]; } else if (isGeneralLine) { Pair<String, String> declaration = descriptionPatternMatcher.getDescriptionGeneralLineData(currentLine); String[] declarationElements = descriptionPatternMatcher.splitDeclaration(declaration.getLeft()); currentTag = declarationElements[0]; } else { Pair<String, String> declaration = descriptionPatternMatcher.getDescriptionVariableLineDataDeclarationOnly(currentLine); String[] declarationElements = descriptionPatternMatcher.splitDeclaration(declaration.getLeft()); currentTag = declarationElements[0]; } // validate empty line validateEmptyLine(lines, errors, previousTag, previousItemEndLineNumber, currentTag); previousTag = currentTag; previousItemEndLineNumber = lineNrZeroBased; } else { // #! continued from previous line if (isNonEmptyComplementaryLine(currentLine)) { previousItemEndLineNumber = lineNrZeroBased; } else { // #!!# if (descriptionPatternMatcher.matchesDescriptionEnd(currentLine)) { // validate ending wrapper line validateEndingWrapperLine(lines, isStep, errors, lineNrZeroBased); finished = true; } // otherwise ignore } } } return errors; } private void validateEndingWrapperLine( List<String> lines, boolean isStep, List<RuntimeException> errors, int lineNrZeroBased) { int targetedLineNumberZeroBased = lineNrZeroBased + 1; String nextLine = tryExtractLine(lines, targetedLineNumberZeroBased); if (isStep) { if (nextLine == null || !descriptionPatternMatcher.matchesStepDelimiterLine(nextLine)) { errors.add( new RuntimeException( generateErrorMessage(lineNrZeroBased, "Next line should be delimiter line (90 characters of `#`)" ) ) ); } } else { if (nextLine == null || !descriptionPatternMatcher.matchesExecutableDelimiterLine(nextLine)) { errors.add( new RuntimeException( generateErrorMessage(lineNrZeroBased, "Next line should be delimiter line (120 characters of `#`)" ) ) ); } } } private void validateEmptyLine( List<String> lines, List<RuntimeException> errors, String previousTag, int previousItemEndLineNumber, String currentTag) { if (previousTag != null && !previousTag.equals(currentTag)) { int targetedLineNumberZeroBased = previousItemEndLineNumber + 1; String targetedLine = lines.get(targetedLineNumberZeroBased); if (!descriptionPatternMatcher.matchesEmptyLine(targetedLine)) { errors.add( new RuntimeException( generateErrorMessage( targetedLineNumberZeroBased, "There should be an empty line between two sections of different tags" + " (" + previousTag + " and " + currentTag + ")" ) ) ); } } } private void validateBeginWrapperLine( List<String> lines, boolean isStep, List<RuntimeException> errors, int startLineNumberZeroBased) { int targetedLineNumberZeroBased = startLineNumberZeroBased - 1; String previousLine = tryExtractLine(lines, targetedLineNumberZeroBased); if (isStep) { if (previousLine == null || !descriptionPatternMatcher.matchesStepDelimiterLine(previousLine)) { errors.add( new RuntimeException( generateErrorMessage(startLineNumberZeroBased, "Previous line should be delimiter line (90 characters of `#`)" ) ) ); } } else { if (previousLine == null || !descriptionPatternMatcher.matchesExecutableDelimiterLine(previousLine)) { errors.add( new RuntimeException( generateErrorMessage(startLineNumberZeroBased, "Previous line should be delimiter line (120 characters of `#`)" ) ) ); } } } private String generateErrorMessage(int lineNumberZeroBased, String data) { return MetadataUtils.generateErrorMessage(lineNumberZeroBased, data); } private boolean isNonEmptyComplementaryLine(String currentLine) { return descriptionPatternMatcher.matchesDescriptionComplementaryLine(currentLine) && !currentLine.trim().equals(Regex.DESCRIPTION_TOKEN); } private boolean isGeneralLine(String currentLine) { return descriptionPatternMatcher.matchesDescriptionGeneralLine(currentLine); } private boolean isVariableLine(String currentLine) { return descriptionPatternMatcher.matchesDescriptionVariableLine(currentLine); } private boolean isVariableLineDeclarationOnly(String currentLine) { return descriptionPatternMatcher.matchesVariableLineDeclarationOnlyLine(currentLine); } private String tryExtractLine(List<String> lines, int lineNr) { if (lineNr >= 0 && lineNr < lines.size()) { return lines.get(lineNr); } return null; } public void setMetadataParser(MetadataParser metadataParser) { this.metadataParser = metadataParser; } }