/**
* Copyright 2013-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.model.helpers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import net.roboconf.core.Constants;
import net.roboconf.core.model.beans.AbstractApplication;
import net.roboconf.core.model.beans.AbstractType;
import net.roboconf.core.model.beans.ApplicationTemplate;
import net.roboconf.core.model.beans.Component;
import net.roboconf.core.model.beans.ExportedVariable;
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.comparators.AbstractTypeComparator;
/**
* Helpers related to components.
* @author Vincent Zurczak - Linagora
*/
public final class ComponentHelpers {
/**
* Private empty constructor.
*/
private ComponentHelpers() {
// nothing
}
/**
* Finds a component by name.
* @param graphs the graph(s) (can be null)
* @param name the component name (not null)
* @return a component (can be null)
*/
public static Component findComponent( Graphs graphs, String name ) {
Component result = null;
for( Iterator<Component> it = findAllComponents( graphs ).iterator(); it.hasNext() && result == null; ) {
Component c = it.next();
if( name.equals( c.getName()))
result = c;
}
return result;
}
/**
* Finds a component by name.
* @param app an application (not null)
* @param name the component name (not null)
* @return a component (can be null)
*/
public static Component findComponent( AbstractApplication app, String name ) {
Graphs graphs = app.getGraphs();
return findComponent( graphs, name );
}
/**
* Finds a component by name from another component.
* @param component the component used to build a partial graph (should not be null)
* @param componentName the component name (not null)
* @return a component (can be null)
*/
public static Component findComponentFrom( Component component, String componentName ) {
Graphs partialGraph = new Graphs();
if( component != null )
partialGraph.getRootComponents().add( component );
return findComponent( partialGraph, componentName );
}
/**
* Extracts the names of abstract types.
* @param types a non-null list of types
* @return a non-null list of string
*/
public static List<String> extractNames( Collection<? extends AbstractType> types ) {
List<String> result = new ArrayList<> ();
for( AbstractType t : types )
result.add( t.getName());
return result;
}
/**
* Determines whether a component is associated with the "target" installer or not.
* @param component a component (not null)
* @return true if it is associated with the "target" installer, false otherwise
*/
public static boolean isTarget( Component component ) {
return Constants.TARGET_INSTALLER.equalsIgnoreCase( component.getInstallerName());
}
/**
* Finds all the possible children of a component.
* @param component a non-null component
* @return a non-null list of components
*/
public static Collection<Component> findAllChildren( Component component ) {
return findAncestorsOrChildren( component, true );
}
/**
* Finds all the possible ancestors of a component.
* @param component a non-null component
* @return a non-null list of components
*/
public static Collection<Component> findAllAncestors( Component component ) {
return findAncestorsOrChildren( component, false );
}
/**
* Finds the installer name for a given component.
* @param component a component
* @return the installer name (potentially retrieved from an extended component)
*/
public static String findComponentInstaller( Component component ) {
String installer = null;
for( Component c : findAllExtendedComponents( component )) {
installer = c.getInstallerName();
if( installer != null )
break;
}
return installer;
}
/**
* Finds all the exported variables for a given component.
* <p>
* This method also fixes the name of the exported variables (set the right prefixes, if required).
* </p>
* <p>
* A component can override variables values of the components its extends.
* It can also override variable values from inherited and associated facets.
* </p>
* <p>
* To solve conflicts between facets and inherited components, facets variables are
* resolved first. Then, component values are injected and may thus override facet
* values.
* </p>
*
* @param component a non-null component
* @return a non-null map of exported variables (key = variable name, value = variable value).
*/
public static Map<String,String> findAllExportedVariables( Component component ) {
return findAllExportedVariables( component, new HashSet<Component>( 0 ));
}
/**
* Finds all the exported variables for a given Roboconf type.
* <p>
* This method also fixes the name of the exported variables (set the right prefixes, if required).
* </p>
*
* @param type a type (facet or component)
* @return a non-null map of exported variables (key = variable name, value = variable value).
*/
public static Map<String,String> findAllExportedVariables( AbstractType type ) {
Map<String,String> result;
if( type instanceof Facet )
result = findAllExportedVariables((Facet) type);
else
result = findAllExportedVariables((Component) type);
return result;
}
/**
* Finds all the exported variables for a given application / graph(s).
* @param graphs a non-null graph(s)
* @return a non-null map of exported variables (key = variable name, value = variable value).
*/
public static Map<String,String> findAllExportedVariables( Graphs graphs ) {
Map<String,String> result = new HashMap<> ();
for( Component c : findAllComponents( graphs ))
result.putAll( findAllExportedVariables( c ));
return result;
}
/**
* Finds all the exported variables of a facet.
* <p>
* This method also fixes the name of the exported variables (set the right prefixes, if required).
* </p>
* <p>
* A facet can also override variable values from the facets its extends.
* </p>
*
* @param facet a facet
* @return a non-null map (key = variable name, value = variable value).
*/
public static Map<String,String> findAllExportedVariables( Facet facet ) {
Map<Facet,Boolean> facetToResolved = new HashMap<> ();
Map<Facet,Map<String,String>> facetToResolvedExports = new HashMap<> ();
Collection<Facet> facets = new HashSet<>( findAllExtendedFacets( facet ));
facets.add( facet );
for( Facet f : facets ) {
facetToResolvedExports.put( f, new HashMap<String,String> ());
facetToResolved.put( f, Boolean.FALSE );
}
// The implementation prevents cycles from blocking the resolution.
while( facetToResolved.containsValue( Boolean.FALSE )) {
entries: for( Facet f : facetToResolvedExports.keySet()) {
// A facet can be processed only if all the facets it extends were already processed.
if( facetToResolved.get( f ))
continue entries;
// In case of cycle... A extends B which extends A...
if( searchForInheritanceCycle( f ) != null ) {
facetToResolved.put( f, Boolean.TRUE );
continue;
}
// Get the resolved variables from all the extended facets.
Map<String,String> localExportedVariables = new HashMap<> ();
for( Facet ff : f.getExtendedFacets()) {
if( ! facetToResolved.get( ff ))
continue entries;
localExportedVariables.putAll( facetToResolvedExports.get( ff ));
}
// If a variable name already exists from the inherited one, we can directly override it.
// Otherwise, we may have to update the variable prefix.
for( Map.Entry<String,ExportedVariable> var : f.exportedVariables.entrySet()) {
String value = var.getValue().getValue();
if( localExportedVariables.containsKey( var.getKey()))
localExportedVariables.put( var.getKey(), value );
else
localExportedVariables.put( fixVariableName( f, var.getKey()), value );
}
facetToResolvedExports.put( f, localExportedVariables );
facetToResolved.put( f, Boolean.TRUE );
}
}
// Now, the result is easy to get.
return facetToResolvedExports.get( facet );
}
/**
* Finds all the imported variables for a given component.
* @param component a non-null component
* @return a non-null map of imported variables (key = variable name, value = imported variable bean)
*/
public static Map<String,ImportedVariable> findAllImportedVariables( Component component ) {
// Process components from the ancestors to the children... => override
Map<String,ImportedVariable> result = new HashMap<> ();
List<Component> extendedComponents = findAllExtendedComponents( component );
Collections.reverse( extendedComponents );
for( Component c : extendedComponents )
result.putAll( c.importedVariables );
return result;
}
/**
* Finds all the facets of a component.
* <p>
* Inheritance cycles are ignored.
* </p>
*
* @param component a non-null component
* @return a non-null list of facets
*/
public static Collection<Facet> findAllFacets( Component component ) {
Set<Facet> result = new HashSet<> ();
List<Facet> toProcess = new ArrayList<> ();
for( Component c : findAllExtendedComponents( component ))
toProcess.addAll( c.getFacets());
while( ! toProcess.isEmpty()) {
Facet f = toProcess.remove( 0 );
result.add( f );
toProcess.addAll( f.getExtendedFacets());
// Prevent loops
toProcess.removeAll( result );
}
return result;
}
/**
* Finds all the components that this component inherits from.
* <p>
* For commodity reasons, the result always contains the current component.
* </p>
* <p>
* Inheritance cycles are ignored.
* </p>
*
* @param component a non-null component
* @return a non-null list (it always contains the <code>component</code> parameter)
*/
public static List<Component> findAllExtendedComponents( Component component ) {
List<Component> result = new ArrayList<> ();
for( Component c = component; c != null; c = c.getExtendedComponent()) {
if( result.contains( c ))
break;
result.add( c );
}
return result;
}
/**
* Finds all the facets that this facet inherits from.
* <p>
* Inheritance cycles are ignored.
* </p>
*
* @param facet a non-null facet
* @return a non-null collection
*/
public static Collection<Facet> findAllExtendedFacets( Facet facet ) {
Set<Facet> result = new HashSet<> ();
Set<Facet> toProcess = new HashSet<>( facet.getExtendedFacets());
while( ! toProcess.isEmpty()) {
Facet f = toProcess.iterator().next();
result.add( f );
toProcess.addAll( f.getExtendedFacets());
toProcess.removeAll( result );
}
return result;
}
/**
* Finds all the facets that extend this facet.
* <p>
* Inheritance cycles are ignored.
* </p>
*
* @param facet a non-null facet
* @return a non-null collection
*/
public static Collection<Facet> findAllExtendingFacets( Facet facet ) {
Set<Facet> result = new HashSet<> ();
Set<Facet> toProcess = new HashSet<>( facet.getExtendingFacets());
while( ! toProcess.isEmpty()) {
Facet f = toProcess.iterator().next();
result.add( f );
toProcess.addAll( f.getExtendingFacets());
toProcess.removeAll( result );
}
return result;
}
/**
* Finds all the components that extend a given component.
* <p>
* Inheritance cycles are ignored.
* </p>
*
* @param component a component
* @return a non-null collection
*/
public static Collection<Component> findAllExtendingComponents( Component component ) {
Collection<Component> result = new HashSet<> ();
Set<Component> toProcess = new HashSet<>( component.getExtendingComponents());
while( ! toProcess.isEmpty()) {
Component c = toProcess.iterator().next();
result.add( c );
toProcess.addAll( c.getExtendingComponents());
toProcess.removeAll( result );
}
// In case of circular dependencies...
result.remove( component );
return result;
}
/**
* Finds all the components of a graph.
* <p>
* Inheritance cycles are ignored.
* </p>
*
* @param graphs a set of graphs
* @return a non-null list of components
*/
public static List<Component> findAllComponents( Graphs graphs ) {
Set<Component> result = new HashSet<> ();
Set<Component> toProcess = new HashSet<> ();
toProcess.addAll( graphs.getRootComponents());
while( ! toProcess.isEmpty()) {
Component current = toProcess.iterator().next();
result.add( current );
toProcess.addAll( findAllExtendedComponents( current ));
toProcess.addAll( findAllExtendingComponents( current ));
toProcess.addAll( findAllChildren( current ));
toProcess.addAll( findAllAncestors( current ));
// Prevent loops
toProcess.removeAll( result );
}
return new ArrayList<>( result );
}
/**
* Finds all the components of an application or a template.
* <p>
* Inheritance cycles are ignored.
* </p>
*
* @param app an application or a template (not null)
* @return a non-null list of components
*/
public static List<Component> findAllComponents( AbstractApplication app ) {
List<Component> result = new ArrayList<> ();
if( app.getGraphs() != null )
result.addAll( findAllComponents( app.getGraphs()));
return result;
}
/**
* Searches for a loop in the graph starting from rootComponent.
* @param component the component from which we introspect (not null)
* @return null if no cycle was found, a string describing the cycle otherwise
*/
public static String searchForLoop( Component component ) {
return searchForLoop( component, new ArrayList<Component> ());
}
/**
* Searches for a loop in the graph starting from rootComponent.
* <p>
* We keep a list of all the ancestors. We then go through all the children.
* If a child appears to be also in the ancestors, then we have a cycle in
* the container-contained perspective.
* </p>
*
* @param component the component from which we introspect (not null)
* @param ancestors the list of ancestor components (not null)
* @return null if no cycle was found, a string describing the cycle otherwise
*/
private static String searchForLoop( Component component, List<Component> ancestors ) {
String result = null;
int index = ancestors.indexOf( component );
if( index >= 0 ) {
StringBuilder sb = new StringBuilder();
for( int i=index; i<ancestors.size(); i++ ) {
sb.append( ancestors.get( i ).getName());
sb.append( " -> " );
}
sb.append( component.getName());
result = sb.toString();
} else {
ancestors.add( component );
for( Component childComponent : findAllChildren( component )) {
List<Component> updatedAncestors = new ArrayList<>( ancestors );
String s = searchForLoop( childComponent, updatedAncestors );
if( s != null ) {
result = s;
break;
}
}
}
return result;
}
/**
* Searches for a cycle in the facets inheritance.
* @param facet the facet from which we introspect (not null)
* @return null if no cycle was found, a string describing the cycle otherwise
*/
public static String searchForInheritanceCycle( Facet facet ) {
String result = null;
if( findAllExtendedFacets( facet ).contains( facet ))
result = facet + " -> ... -> " + facet;
return result;
}
/**
* Searches for a cycle in the facets inheritance.
* @param component the component from which we introspect (not null)
* @return null if no cycle was found, a string describing the cycle otherwise
*/
public static String searchForInheritanceCycle( Component component ) {
String result = null;
for( Component c = component.getExtendedComponent(); c != null && result == null; c = c.getExtendedComponent()) {
if( c.equals( component ))
result = component + " -> ... -> " + component;
}
return result;
}
/**
* Finds the component dependencies for a given component.
* @param component a component
* @return a non-null map (key = component name, value = true if the dependency is optional, false otherwise)
*/
public static Map<String,Boolean> findComponentDependenciesFor( Component component ) {
Map<String,Boolean> map = new HashMap<> ();
for( ImportedVariable var : findAllImportedVariables( component ).values()) {
String componentOrFacet = VariableHelpers.parseVariableName( var.getName()).getKey();
Boolean b = map.get( componentOrFacet );
if( b == null || b )
map.put( componentOrFacet, var.isOptional());
}
return map;
}
/**
* Finds all the components a component depends on.
* @param component a component
* @param app the application
* @return a non-null map (key = component name, value = true if the dependency is optional, false otherwise)
*/
public static Map<Component,Boolean> findComponentDependenciesFor( Component component, ApplicationTemplate app ) {
// Determine which components or facets are required.
Map<String,Boolean> map = findComponentDependenciesFor( component );
// Resolve names to components
Map<Component,Boolean> result = new HashMap<> ();
for( Component c : findAllComponents( app )) {
// Check component names first.
Boolean required = map.get( c.getName());
if( required != null ) {
result.put( c, required );
}
// Or maybe it is a facet name.
else for( Facet f : findAllFacets( c )) {
required = map.get( f.getName());
// If this component owns the facet, stop here.
if( required != null ) {
result.put( c, required );
break;
}
}
}
return result;
}
/**
* Finds all the components that depend on a given component.
* @param component a component
* @param app the application
* @return a non-null map (key = component name, value = true if the dependency is optional, false otherwise)
*/
public static Map<Component,Boolean> findComponentsThatDependOn( Component component, ApplicationTemplate app ) {
// Determine the matching prefixes.
Set<String> prefixes = new HashSet<> ();
prefixes.add( component.getName());
for( Facet f : findAllFacets( component ))
prefixes.add( f.getName());
// Resolve names to components
Map<Component,Boolean> result = new HashMap<> ();
for( Component c : findAllComponents( app )) {
Map<String,Boolean> map = findComponentDependenciesFor( c );
map.keySet().retainAll( prefixes );
// Empty map => 'c' does not depend on 'component'
if( map.isEmpty())
continue;
// Otherwise, determine the dependency
boolean value = true;
for( Boolean b : map.values())
value = value && b;
result.put( c, value );
}
return result;
}
/**
* Fixes the name of an exported variable name.
* <p>
* An exported variable must ALWAYS be prefixed with the name of the "type"
* that exports it.
* </p>
*
* @param type a facet or a component
* @param exportedVariableName the name of an exported variable
* @return a non-null string
*/
static String fixVariableName( AbstractType type, String exportedVariableName ) {
String prefix = type.getName() + ".";
String result = exportedVariableName;
if( ! result.startsWith( prefix ))
result = prefix + exportedVariableName;
return result;
}
/**
* Finds the ancestors or the children of a given component.
* <p>
* The algorithm to find them is the same. The only difference
* lies in the list we go through.
* </p>
*
* @param component the component (not null)
* @param children true to search children, false for ancestors
* @return a non-null list
*/
private static Collection<Component> findAncestorsOrChildren( final Component component, final boolean children ) {
// The algorithm of death...
Set<Component> result = new TreeSet<>( new AbstractTypeComparator());
for( Component c : findAllExtendedComponents( component )) {
// A component may have zero child or ancestor.
// But its facets may define ones.
Collection<AbstractType> list = new HashSet<> ();
list.addAll( children ? c.getChildren() : c.getAncestors());
for( Facet facet : findAllFacets( c ))
list.addAll( children ? facet.getChildren() : facet.getAncestors());
// Now, take a look at the list's content.
for( AbstractType type : list ) {
if( type instanceof Component ) {
Component cType = (Component) type;
// Add the component but also those that extend it!
result.add( cType );
result.addAll( findAllExtendingComponents( cType ));
}
else {
Facet fType = (Facet) type;
// Find all the "super" facets
Collection<Facet> allFacets = findAllExtendingFacets( fType );
allFacets.add( fType );
for( Facet facet : allFacets ) {
// Add all the components associated with the facet.
// Add their extending components too!
for( Component cc : facet.getAssociatedComponents()) {
result.add( cc );
result.addAll( findAllExtendingComponents( cc ));
}
}
}
}
}
return result;
}
/**
* Finds all the exported variables for a given component.
* <p>
* This method also fixes the name of the exported variables (set the right prefixes, if required).
* </p>
* <p>
* A component can override variables values of the components its extends.
* It can also override variable values from inherited and associated facets.
* </p>
* <p>
* To solve conflicts between facets and inherited components, facets variables are
* resolved first. Then, component values are injected and may thus override facet
* values.
* </p>
*
* @param component a non-null component
* @param alreadyChecked a non-null list of already checked components (prevents cycles)
* @return a non-null map of exported variables (key = variable name, value = variable value).
*/
private static Map<String,String> findAllExportedVariables( Component component, Set<Component> alreadyChecked ) {
Map<String,String> result = new HashMap<> ();
// Get all the inherited variables from facets
for( Facet f : component.getFacets())
result.putAll( findAllExportedVariables( f ));
// Now, get all the exported variables by super components.
// Recursive call here...
List<Component> extendedComponents = findAllExtendedComponents( component );
Collections.reverse( extendedComponents );
extendedComponents.remove( component );
extendedComponents.removeAll( alreadyChecked );
Set<Component> ac = new HashSet<>( alreadyChecked );
ac.add( component );
for( Component c : extendedComponents )
result.putAll( findAllExportedVariables( c, ac ));
// Handle current variables
Map<String,String> newVariables = new HashMap<> ();
for( Map.Entry<String,ExportedVariable> entry : component.exportedVariables.entrySet()) {
// If a variable name already exists from the inherited one, we can directly override it.
String variableValue = entry.getValue().getValue();
if( result.containsKey( entry.getKey()))
newVariables.put( entry.getKey(), variableValue );
// Or it is a new value
else
newVariables.put( fixVariableName( component, entry.getKey()), variableValue );
// Override inherited variables.
// If the component exports a and that its inherits Alpha.a, then we should
// override the value of Alpha.a too. In some way, we skip prefixes...
for( String inheritedName : result.keySet()) {
String nameSuffix = VariableHelpers.parseVariableName( inheritedName ).getValue();
if( entry.getKey().equals( nameSuffix ))
newVariables.put( inheritedName, variableValue );
}
}
result.putAll( newVariables );
// Now, we must replicate variables that do not have the right prefix.
// Let's assume that A extends B, that A exports a1, a2, and that B exports b1.
// Then...
// A exports A.a1 and A.a2. So far, so good.
// Now, B exports B.b1.
//
// Besides, B is also a "A". So, its exports A.a1 and A.a2 (if someone wants to know all the A instances, B should respond).
// Now, if someone only wants to know B instances, B should propagate inherited information with its name prefix.
// So, B will also export B.a1 and B.a2
newVariables.clear();;
for( Map.Entry<String,String> entry : result.entrySet()) {
Map.Entry<String,String> var = VariableHelpers.parseVariableName( entry.getKey());
String newKey = component.getName() + "." + var.getValue();
if( ! component.getName().equals( var.getKey())
&& ! result.containsKey( newKey ))
newVariables.put( newKey, entry.getValue());
}
result.putAll( newVariables );
return result;
}
}