/**
* 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.converters;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.roboconf.core.ErrorCode;
import net.roboconf.core.dsl.ParsingConstants;
import net.roboconf.core.dsl.ParsingModelValidator;
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.FileDefinition;
import net.roboconf.core.internal.dsl.parsing.FileDefinitionParser;
import net.roboconf.core.model.ParsingError;
import net.roboconf.core.model.SourceReference;
import net.roboconf.core.model.beans.AbstractType;
import net.roboconf.core.model.beans.Component;
import net.roboconf.core.model.beans.Facet;
import net.roboconf.core.model.beans.Graphs;
import net.roboconf.core.model.beans.ImportedVariable;
import net.roboconf.core.model.helpers.ComponentHelpers;
import net.roboconf.core.model.helpers.RoboconfErrorHelpers;
import net.roboconf.core.utils.ModelUtils;
import net.roboconf.core.utils.Utils;
/**
* To build a {@link Graphs} from a {@link FileDefinition}.
* @author Vincent Zurczak - Linagora
*/
public class FromGraphDefinition {
private final File rootDirectory;
private final boolean flexParsing;
private final Collection<ParsingError> errors = new ArrayList<> ();
private final Map<Object,SourceReference> objectToSource = new HashMap<> ();
private Map<String,ComponentData> componentNameToComponentData;
private Map<String,FacetData> facetNameToFacetData;
private Set<File> importsToProcess, processedImports;
private final Map<String,String> typeAnnotations = new HashMap<> ();
/**
* Constructor.
* @param rootDirectory the root directory that contains the definition (used to resolve imports)
*/
public FromGraphDefinition( File rootDirectory ) {
this( rootDirectory, false );
}
/**
* Constructor.
* @param rootDirectory the root directory that contains the definition (used to resolve imports)
* @param flexParsing true to ignore parsing errors and build most of the runtime model
*/
public FromGraphDefinition( File rootDirectory, boolean flexParsing ) {
this.rootDirectory = rootDirectory;
this.flexParsing = flexParsing;
}
/**
* @return the errors (never null)
*/
public Collection<ParsingError> getErrors() {
return this.errors;
}
/**
* @return the processedImports
*/
public Set<File> getProcessedImports() {
return this.processedImports;
}
/**
* @return the objectToSource (never null)
*/
public Map<Object,SourceReference> getObjectToSource() {
return this.objectToSource;
}
/**
* @return the type annotations (never null, key = type name, value = annotation)
*/
public Map<String,String> getTypeAnnotations() {
return this.typeAnnotations;
}
/**
* @param file the initial file to parse
* @return a graph(s)
* <p>
* The result is not significant if there are errors.<br>
* Conversion errors are available by using {@link #getErrors()}.
* </p>
*/
public Graphs buildGraphs( File file ) {
// Initialize collections
this.componentNameToComponentData = new HashMap<> ();
this.facetNameToFacetData = new HashMap<> ();
this.importsToProcess = new HashSet<> ();
this.processedImports = new HashSet<> ();
this.errors.clear();
this.typeAnnotations.clear();
// Process the file and its imports
this.importsToProcess.add( file );
while( ! this.importsToProcess.isEmpty()) {
File importedFile = this.importsToProcess.iterator().next();
this.importsToProcess.remove( importedFile );
this.processedImports.add( importedFile );
if( ! importedFile.exists()) {
ParsingError error = new ParsingError( ErrorCode.CO_UNREACHABLE_FILE, file, 0 );
error.setDetails( "File location: " + importedFile );
this.errors.add( error );
continue;
}
// Load the file
FileDefinition currentDefinition = new FileDefinitionParser( importedFile, false ).read();
Collection<ParsingError> currentErrors = new ArrayList<> ();
currentErrors.addAll( currentDefinition.getParsingErrors());
StringBuilder lastComment = new StringBuilder();
for( AbstractBlock block : currentDefinition.getBlocks()) {
// Validate
currentErrors.addAll( ParsingModelValidator.validate( block ));
// Load annotations
if( block instanceof BlockComment ) {
lastComment.append(((BlockComment) block).getContent().trim() + "\n" );
} else if( block instanceof BlockBlank ) {
lastComment.setLength( 0 );
} else if( lastComment.length() > 0
&& block instanceof AbstractBlockHolder ) {
String comment = lastComment.toString().replaceAll( "#\\s*", "" ).trim();
this.typeAnnotations.put(((AbstractBlockHolder) block).getName(), comment );
}
}
// Verify the file kind
if( currentDefinition.getFileType() != FileDefinition.AGGREGATOR
&& currentDefinition.getFileType() != FileDefinition.GRAPH
&& currentDefinition.getFileType() != FileDefinition.EMPTY ) {
ParsingError error = new ParsingError( ErrorCode.CO_NOT_A_GRAPH, file, 0 );
error.setDetails( "Imported file " + importedFile + " is of type " + FileDefinition.fileTypeAsString( currentDefinition.getFileType()) + "." );
currentErrors.add( error );
}
// Process the file
this.errors.addAll( currentErrors );
if( this.flexParsing || ! RoboconfErrorHelpers.containsCriticalErrors( currentErrors ))
processInstructions( currentDefinition );
}
// Check names collisions
if( this.flexParsing || this.errors.isEmpty())
checkNameCollisions();
// Check uniqueness
if( this.flexParsing || this.errors.isEmpty())
checkUnicity();
// Resolve all
if( this.flexParsing || this.errors.isEmpty())
resolveComponents();
if( this.flexParsing || this.errors.isEmpty())
resolveFacets();
// Build the result
return buildFinalGraphs();
}
private void processInstructions( FileDefinition definition ) {
for( AbstractBlock block : definition.getBlocks()) {
switch( block.getInstructionType()) {
case AbstractBlock.COMPONENT:
processComponent((BlockComponent) block );
break;
case AbstractBlock.FACET:
processFacet((BlockFacet) block);
break;
case AbstractBlock.IMPORT:
processImport((BlockImport) block );
break;
default:
// nothing
break;
}
}
}
private void processImport( BlockImport block ) {
String uri = block.getUri().trim();
File newDefFile = new File( this.rootDirectory, uri );
if( ! this.processedImports.contains( newDefFile ))
this.importsToProcess.add( newDefFile );
}
private void processFacet( BlockFacet block ) {
FacetData data = this.facetNameToFacetData.get( block.getName());
if( data != null ) {
data.blocks.add( block );
} else {
data = new FacetData();
data.object = new Facet( block.getName());
data.object.exportedVariables.putAll( ModelUtils.getExportedVariables( block ));
data.childrenNames.addAll( ModelUtils.getPropertyValues( block, ParsingConstants.PROPERTY_GRAPH_CHILDREN ));
data.extendedFacetNames.addAll( ModelUtils.getPropertyValues( block, ParsingConstants.PROPERTY_GRAPH_EXTENDS ));
data.blocks.add( block );
this.facetNameToFacetData.put( block.getName(), data );
}
}
private void processComponent( BlockComponent block ) {
ComponentData data = this.componentNameToComponentData.get( block.getName());
if( data != null ) {
data.blocks.add( block );
} else {
data = new ComponentData();
data.object = new Component( block.getName());
data.object.exportedVariables.putAll( ModelUtils.getExportedVariables( block ));
data.object.setInstallerName( ModelUtils.getPropertyValue( block, ParsingConstants.PROPERTY_COMPONENT_INSTALLER ));
for( String s : ModelUtils.getPropertyValues( block, ParsingConstants.PROPERTY_COMPONENT_IMPORTS )) {
Boolean optional = s.toLowerCase().endsWith( ParsingConstants.PROPERTY_COMPONENT_OPTIONAL_IMPORT );
if( optional )
s = s.substring( 0, s.length() - ParsingConstants.PROPERTY_COMPONENT_OPTIONAL_IMPORT.length()).trim();
Boolean external = s.toLowerCase().startsWith( ParsingConstants.PROPERTY_COMPONENT_EXTERNAL_IMPORT );
if( external )
s = s.substring( ParsingConstants.PROPERTY_COMPONENT_EXTERNAL_IMPORT.length()).trim();
data.object.addImportedVariable( new ImportedVariable( s, optional, external ));
}
data.extendedComponentName = ModelUtils.getPropertyValue( block, ParsingConstants.PROPERTY_GRAPH_EXTENDS );
data.childrenNames.addAll( ModelUtils.getPropertyValues( block, ParsingConstants.PROPERTY_GRAPH_CHILDREN ));
data.facetNames.addAll( ModelUtils.getPropertyValues( block, ParsingConstants.PROPERTY_COMPONENT_FACETS ));
data.blocks.add( block );
this.componentNameToComponentData.put( block.getName(), data );
}
}
private void checkNameCollisions() {
Collection<String> names = new HashSet<> ();
names.addAll( this.componentNameToComponentData.keySet());
names.retainAll( this.facetNameToFacetData.keySet());
for( String name : names ) {
ComponentData cd = this.componentNameToComponentData.get( name );
this.errors.addAll( cd.error( ErrorCode.CO_CONFLICTING_NAME, "Name: " + name ));
FacetData fd = this.facetNameToFacetData.get( name );
this.errors.addAll( fd.error( ErrorCode.CO_CONFLICTING_NAME, "Name: " + name ));
}
}
private void checkUnicity() {
// Components
for( Data<?> data : this.componentNameToComponentData.values()) {
if( data.blocks.size() > 1 )
this.errors.addAll( data.error( ErrorCode.CO_ALREADY_DEFINED_COMPONENT, "Component name: " + data.object.getName()));
}
// Facets
for( Data<?> data : this.facetNameToFacetData.values()) {
if( data.blocks.size() > 1 )
this.errors.addAll( data.error( ErrorCode.CO_ALREADY_DEFINED_FACET, "Facet name: " + data.object.getName()));
}
}
private void resolveComponents() {
for( ComponentData data : this.componentNameToComponentData.values()) {
// Being here means we did not find conflicting names
AbstractBlockHolder holder = data.blocks.get( 0 );
SourceReference sr = new SourceReference( data.object, holder.getFile(), holder.getLine());
this.objectToSource.put( data.object, sr );
// The extended component name
if( ! Utils.isEmptyOrWhitespaces( data.extendedComponentName )) {
ComponentData extendedComponentData = this.componentNameToComponentData.get( data.extendedComponentName );
if( extendedComponentData != null )
data.object.extendComponent( extendedComponentData.object );
else
this.errors.addAll( data.error( ErrorCode.CO_INEXISTING_COMPONENT, "Component name: " + data.extendedComponentName ));
}
// The facets
for( String s : data.facetNames ) {
FacetData facetData = this.facetNameToFacetData.get( s );
if( facetData != null )
data.object.associateFacet( facetData.object );
else
this.errors.addAll( data.error( ErrorCode.CO_INEXISTING_FACET, "Facet name: " + s ));
}
// The children
for( String s : data.childrenNames ) {
ComponentData componentData = this.componentNameToComponentData.get( s );
FacetData facetData = this.facetNameToFacetData.get( s );
if( componentData != null )
data.object.addChild( componentData.object );
else if( facetData != null )
data.object.addChild( facetData.object );
else
this.errors.addAll( data.error( ErrorCode.CO_INEXISTING_CHILD, "Name: " + s ));
}
}
}
private void resolveFacets() {
for( FacetData data : this.facetNameToFacetData.values()) {
// Being here means we did not find conflicting names
AbstractBlockHolder holder = data.blocks.get( 0 );
SourceReference sr = new SourceReference( data.object, holder.getFile(), holder.getLine());
this.objectToSource.put( data.object, sr );
// The extended facets
for( String s : data.extendedFacetNames ) {
FacetData facetData = this.facetNameToFacetData.get( s );
if( facetData != null )
data.object.extendFacet( facetData.object );
else
this.errors.addAll( data.error( ErrorCode.CO_INEXISTING_FACET, "Facet name: " + s ));
}
// The children
for( String s : data.childrenNames ) {
ComponentData componentData = this.componentNameToComponentData.get( s );
FacetData facetData = this.facetNameToFacetData.get( s );
if( componentData != null )
data.object.addChild( componentData.object );
else if( facetData != null )
data.object.addChild( facetData.object );
else
this.errors.addAll( data.error( ErrorCode.CO_INEXISTING_CHILD, "Name: " + s ));
}
}
}
private Graphs buildFinalGraphs() {
Graphs result = new Graphs();
for( ComponentData cd : this.componentNameToComponentData.values()) {
if( ComponentHelpers.findAllAncestors( cd.object ).isEmpty())
result.getRootComponents().add( cd.object );
}
for( FacetData data : this.facetNameToFacetData.values())
result.getFacetNameToFacet().put( data.object.getName(), data.object );
return result;
}
/**
* @author Vincent Zurczak - Linagora
* @param <T>
*/
private static class Data<T extends AbstractType> {
T object;
Collection<String> childrenNames = new HashSet<> ();
List<AbstractBlockHolder> blocks = new ArrayList<> ();
List<ParsingError> error( ErrorCode code, String cause ) {
List<ParsingError> errors = new ArrayList<> ();
for( AbstractBlockHolder block : this.blocks ) {
ParsingError error = new ParsingError( code, block.getDeclaringFile().getEditedFile(), block.getLine());
error.setDetails( cause );
errors.add( error );
}
return errors;
}
}
/**
* @author Vincent Zurczak - Linagora
*/
private static class ComponentData extends Data<Component> {
String extendedComponentName;
Collection<String> facetNames = new HashSet<> ();
}
/**
* @author Vincent Zurczak - Linagora
*/
private static class FacetData extends Data<Facet> {
Collection<String> extendedFacetNames = new HashSet<> ();
}
}