/** * Copyright 2014-2017 Linagora, Université Joseph Fourier, Floralis * * The present code is developed in the scope of the joint LINAGORA - * Université Joseph Fourier - Floralis research program and is designated * as a "Result" pursuant to the terms and conditions of the LINAGORA * - Université Joseph Fourier - Floralis research program. Each copyright * holder of Results enumerated here above fully & independently holds complete * ownership of the complete Intellectual Property rights applicable to the whole * of said Results, and may freely exploit it in any manner which does not infringe * the moral rights of the other copyright holders. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.roboconf.core.dsl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import net.roboconf.core.ErrorCode; import net.roboconf.core.dsl.parsing.AbstractBlock; import net.roboconf.core.dsl.parsing.AbstractBlockHolder; import net.roboconf.core.dsl.parsing.BlockBlank; import net.roboconf.core.dsl.parsing.BlockComment; import net.roboconf.core.dsl.parsing.BlockComponent; import net.roboconf.core.dsl.parsing.BlockFacet; import net.roboconf.core.dsl.parsing.BlockImport; import net.roboconf.core.dsl.parsing.BlockInstanceOf; import net.roboconf.core.dsl.parsing.BlockProperty; import net.roboconf.core.dsl.parsing.FileDefinition; import net.roboconf.core.internal.dsl.parsing.ExportedVariablesParser; import net.roboconf.core.model.ParsingError; import net.roboconf.core.model.beans.ExportedVariable; import net.roboconf.core.utils.ModelUtils; import net.roboconf.core.utils.Utils; /** * A set of methods to validate parsing model objects. * @author Vincent Zurczak - Linagora */ public final class ParsingModelValidator { /** * Constructor. */ private ParsingModelValidator() { // nothing } /** * @param definitionFile a definition file * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( FileDefinition definitionFile ) { Collection<ParsingError> result = new ArrayList<> (); result.addAll( definitionFile.getParsingErrors()); for( AbstractBlock block : definitionFile.getBlocks()) result.addAll( validate( block )); return result; } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( AbstractBlock block ) { Collection<ParsingError> result; switch( block.getInstructionType()) { case AbstractBlock.COMPONENT: result = validate((BlockComponent) block); break; case AbstractBlock.FACET: result = validate((BlockFacet) block); break; case AbstractBlock.IMPORT: result = validate((BlockImport) block); break; case AbstractBlock.COMMENT: result = validate((BlockComment) block); break; case AbstractBlock.PROPERTY: result = validate((BlockProperty) block); break; case AbstractBlock.BLANK: result = validate((BlockBlank) block); break; case AbstractBlock.INSTANCEOF: result = validate((BlockInstanceOf) block); break; default: result = new ArrayList<>( 1 ); ParsingError error = parsingError( ErrorCode.PM_INVALID_BLOCK_TYPE, block ); error.setDetails( "Instruction type: " + block.getInstructionType()); result.add( error ); break; } return result; } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( BlockImport block ) { String uri = block.getUri(); Collection<ParsingError> result = new ArrayList<> (); if( Utils.isEmptyOrWhitespaces( uri )) result.add( parsingError( ErrorCode.PM_EMPTY_IMPORT_LOCATION, block )); return result; } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( BlockComponent block ) { return validatePropertiesHolder( block, true ); } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( BlockInstanceOf block ) { // Basic checks Collection<ParsingError> result = validatePropertiesHolder( block, false ); BlockProperty prop = block.findPropertyBlockByName( ParsingConstants.PROPERTY_INSTANCE_NAME ); if( prop == null ) result.add( parsingError( ErrorCode.PM_MISSING_INSTANCE_NAME, block )); else if( ! prop.getValue().matches( ParsingConstants.PATTERN_FLEX_ID )) result.add( parsingError( ErrorCode.PM_INVALID_INSTANCE_NAME, block )); // Check internal regions are supported for( AbstractBlock region : block.getInnerBlocks()) { if( region.getInstructionType() != AbstractBlock.BLANK && region.getInstructionType() != AbstractBlock.COMMENT && region.getInstructionType() != AbstractBlock.PROPERTY && region.getInstructionType() != AbstractBlock.INSTANCEOF ) { ParsingError error = parsingError( ErrorCode.PM_INVALID_INSTANCE_ELEMENT, region ); error.setDetails( "Invalid type found: " + region.getClass().getSimpleName()); result.add( error ); } } return result; } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( BlockFacet block ) { return validatePropertiesHolder( block, true ); } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( BlockProperty block ) { Collection<ParsingError> result = new ArrayList<> (); String value = block.getValue(); String name = block.getName(); int line = block.getLine(); if( Utils.isEmptyOrWhitespaces( value )) { result.add( new ParsingError( ErrorCode.PM_EMPTY_PROPERTY_VALUE, block.getFile(), line )); } else if( ParsingConstants.PROPERTY_GRAPH_CHILDREN.equals( name )) { for( String s : Utils.splitNicely( value, ParsingConstants.PROPERTY_SEPARATOR )) { if( s.matches( ParsingConstants.PATTERN_ID )) continue; ParsingError error = new ParsingError( ErrorCode.PM_INVALID_CHILD_NAME, block.getFile(), line ); error.setDetails( "Child name: " + s ); result.add( error ); } } else if( ParsingConstants.PROPERTY_COMPONENT_FACETS.equals( name ) || ParsingConstants.PROPERTY_GRAPH_EXTENDS.equals( name )) { for( String s : Utils.splitNicely( value, ParsingConstants.PROPERTY_SEPARATOR )) { if( Utils.isEmptyOrWhitespaces( s )) { result.add( new ParsingError( ErrorCode.PM_EMPTY_REFERENCED_NAME, block.getFile(), line )); } else if( ! s.matches( ParsingConstants.PATTERN_ID )) { ParsingError error = new ParsingError( ErrorCode.PM_INVALID_NAME, block.getFile(), line ); error.setDetails( "Invalid name: " + s ); result.add( error ); } } } else if( ParsingConstants.PROPERTY_COMPONENT_IMPORTS.equals( name )) { for( String s : Utils.splitNicely( value, ParsingConstants.PROPERTY_SEPARATOR )) { if( s.toLowerCase().endsWith( ParsingConstants.PROPERTY_COMPONENT_OPTIONAL_IMPORT )) s = s.substring( 0, s.length() - ParsingConstants.PROPERTY_COMPONENT_OPTIONAL_IMPORT.length()); if( s.toLowerCase().startsWith( ParsingConstants.PROPERTY_COMPONENT_EXTERNAL_IMPORT )) s = s.substring( ParsingConstants.PROPERTY_COMPONENT_EXTERNAL_IMPORT.length()); String patternForImports = ParsingConstants.PATTERN_ID; patternForImports += "(\\.\\*)?"; s = s.trim(); if( Utils.isEmptyOrWhitespaces( s )) result.add( new ParsingError( ErrorCode.PM_EMPTY_VARIABLE_NAME, block.getFile(), line )); else if( ! s.matches( patternForImports )) result.add( new ParsingError( ErrorCode.PM_INVALID_IMPORTED_VAR_NAME, block.getFile(), line, s )); else if( ! s.contains( "." ) || s.indexOf( '.' ) == s.length() -1 ) result.add( new ParsingError( ErrorCode.PM_INCOMPLETE_IMPORTED_VAR_NAME, block.getFile(), line, s )); } } else if( ParsingConstants.PROPERTY_GRAPH_EXPORTS.equals( name )) { ExportedVariablesParser exportsParser = new ExportedVariablesParser(); exportsParser.parse( value, block.getFile(), line ); result.addAll( exportsParser.errors ); for( ExportedVariable var : ModelUtils.findExportedVariables( value, block.getFile(), line ).values()) { String exportKey = var.getName(); if( Utils.isEmptyOrWhitespaces( exportKey )) result.add( new ParsingError( ErrorCode.PM_EMPTY_VARIABLE_NAME, block.getFile(), line )); else if( ! exportKey.matches( ParsingConstants.PATTERN_ID )) { result.add( new ParsingError( ErrorCode.PM_INVALID_EXPORTED_VAR_NAME, block.getFile(), line, "Variable name: " + exportKey )); if( exportKey.toLowerCase().startsWith( ParsingConstants.PROPERTY_COMPONENT_EXTERNAL_IMPORT + " " )) result.add( new ParsingError( ErrorCode.PM_EXTERNAL_IS_KEYWORD_FOR_IMPORTS, block.getFile(), line, "Variable name: " + exportKey )); } } } else if( ParsingConstants.PROPERTY_COMPONENT_INSTALLER.equals( name )) { if( ! value.matches( ParsingConstants.PATTERN_FLEX_ID )) result.add( new ParsingError( ErrorCode.PM_INVALID_INSTALLER_NAME, block.getFile(), line, value )); } else if( ParsingConstants.PROPERTY_INSTANCE_NAME.equals( name )) { // nothing } else if( ParsingConstants.PROPERTY_INSTANCE_CHANNELS.equals( name )) { // nothing for the moment } else if( ParsingConstants.PROPERTY_INSTANCE_DATA.equals( name )) { // nothing } else if( ParsingConstants.PROPERTY_INSTANCE_STATE.equals( name )) { // nothing } else if( ParsingConstants.PROPERTY_INSTANCE_COUNT.equals( name )) { int count = -1; try { count = Integer.parseInt( value ); } catch( NumberFormatException e ) { // nothing } if( count < 1 ) result.add( new ParsingError( ErrorCode.PM_INVALID_INSTANCE_COUNT, block.getFile(), line )); else if( count == 1 ) result.add( new ParsingError( ErrorCode.PM_USELESS_INSTANCE_COUNT, block.getFile(), line )); } else { ParsingError error = new ParsingError( ErrorCode.PM_UNKNOWN_PROPERTY_NAME, block.getFile(), line ); error.setDetails( "Property name: " + name ); result.add( error ); } return result; } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( BlockComment block ) { // Only makes sense when a comment section was created programmatically. Collection<ParsingError> result = new ArrayList<> (); int cpt = 0; for( String s : block.getContent().split( "\n" )) { if( ! s.trim().startsWith( ParsingConstants.COMMENT_DELIMITER )) result.add( new ParsingError( ErrorCode.PM_MALFORMED_COMMENT, block.getFile(), block.getLine() + cpt )); cpt ++; } return result; } /** * @param block a block * @return a non-null list of {@link ParsingError}s */ public static Collection<ParsingError> validate( BlockBlank block ) { // Only makes sense when a BlockBlank section was created programmatically. Collection<ParsingError> result = new ArrayList<> (); if( ! Utils.isEmptyOrWhitespaces( block.getContent())) result.add( parsingError( ErrorCode.PM_MALFORMED_BLANK, block )); return result; } /** * @param holder * @param strict true if only supported properties are allowed, false otherwise * @return a non-null list of {@link ParsingError}s */ private static Collection<ParsingError> validatePropertiesHolder( AbstractBlockHolder holder, boolean strict ) { // Check the name Collection<ParsingError> result = new ArrayList<> (); String name = holder.getName(); if( Utils.isEmptyOrWhitespaces( name )) result.add( parsingError( holder.getInstructionType() == AbstractBlock.FACET ? ErrorCode.PM_EMPTY_FACET_NAME : ErrorCode.PM_EMPTY_COMPONENT_NAME, holder )); else if( ! name.matches( ParsingConstants.PATTERN_ID )) result.add( parsingError( ErrorCode.PM_INVALID_NAME, holder )); else if( name.contains( "." )) result.add( parsingError( ErrorCode.PM_DOT_IS_NOT_ALLOWED, holder )); // Check all the properties have a value List<String> supportedProperties = Arrays.asList( holder.getSupportedPropertyNames()); Set<String> foundProperties = new HashSet<> (); for( AbstractBlock region : holder.getInnerBlocks()) { if( region.getInstructionType() == AbstractBlock.PROPERTY ) { BlockProperty p = (BlockProperty) region; // Exports and imports can now appear more than once if( ! ParsingConstants.PROPERTY_COMPONENT_IMPORTS.equalsIgnoreCase( p.getName()) && ! ParsingConstants.PROPERTY_GRAPH_EXPORTS.equalsIgnoreCase( p.getName()) && foundProperties.contains( p.getName())) { ParsingError error = parsingError( ErrorCode.PM_DUPLICATE_PROPERTY, p ); error.setDetails( "Property name: " + p.getName()); result.add( error ); } foundProperties.add( p.getName()); if( supportedProperties.contains( p.getName())) { result.addAll( validate( p )); } else if( strict ) { ParsingError error = parsingError( ErrorCode.PM_PROPERTY_NOT_APPLIABLE, p ); error.setDetails( "Property name: " + p.getName()); result.add( error ); } } else { result.addAll( validate( region )); } } return result; } private static ParsingError parsingError( ErrorCode errorCode, AbstractBlock block ) { return new ParsingError( errorCode, block.getDeclaringFile().getEditedFile(), block.getLine()); } }