/****************************************************************************** * Copyright (c) 2016 Oracle * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Konstantin Komissarchik - initial implementation and ongoing maintenance ******************************************************************************/ package org.eclipse.sapphire.modeling.util; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.sapphire.util.ListFactory; /** * Sorts objects in dependencies-first order. * * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public final class DependencySorter<K,T> { private final Map<K,T> keyToObject = new LinkedHashMap<K,T>(); private final Map<T,K> objectToKey = new LinkedHashMap<T,K>(); private final Map<K,Set<K>> dependencies = new LinkedHashMap<K,Set<K>>(); public void add( final K key, final T object ) { if( key == null ) { throw new IllegalArgumentException(); } if( object == null ) { throw new IllegalArgumentException(); } if( this.keyToObject.containsKey( key ) ) { if( this.keyToObject.get( key ) != object ) { throw new IllegalArgumentException(); } } else { this.keyToObject.put( key, object ); this.objectToKey.put( object, key ); if( this.dependencies.get( key ) == null ) { this.dependencies.put( key, new LinkedHashSet<K>() ); } } } /** * Determines if the sorter contains an object with the specified key. * * @param key the object key * @return true if and only if the sorter contains an object with the specified key */ public boolean contains( final K key ) { return ( this.keyToObject.get( key ) != null ); } public void dependency( final K from, final K to ) { if( from == null ) { throw new IllegalArgumentException(); } if( to == null ) { throw new IllegalArgumentException(); } Set<K> set = this.dependencies.get( from ); if( set == null ) { set = new LinkedHashSet<K>(); this.dependencies.put( from, set ); } set.add( to ); } /** * Returns the sorted list. The returned list is not modifiable. * * @return the sorted list */ public List<T> sort() { // Return early if no objects defined. if( this.keyToObject.isEmpty() ) { return Collections.emptyList(); } // Find all objects with no incoming dependencies. final List<T> roots = new ArrayList<T>(); for( final K key : this.dependencies.keySet() ) { boolean found = false; for( final Set<K> dependencies : this.dependencies.values() ) { if( dependencies.contains( key ) ) { found = true; break; } } if( ! found ) { final T object = this.keyToObject.get( key ); if( object != null ) { roots.add( object ); } } } // Start with the roots and try to visit all objects. The ones not visited // are involved in detached loops. Pick one of the not visited objects to // be another root and visit what can be reached from that object. Repeat // until all objects have been visited. final Set<T> visited = new HashSet<T>(); for( T root : roots ) { visit( root, visited ); } while( visited.size() != this.keyToObject.size() ) { for( T object : this.keyToObject.values() ) { if( ! visited.contains( object ) ) { roots.add( object ); visit( object, visited ); break; } } } // Finally, traverse the dependency trees of each root and add objects in depth-first order. The visited set // tracks objects already traversed so that we can break cycles. final ListFactory<T> result = ListFactory.start(); visited.clear(); for( T root : roots ) { traverse( root, visited, result ); } return result.result(); } private void visit( final T object, final Set<T> visited ) { if( visited.contains( object ) ) { return; } visited.add( object ); for( K key : this.dependencies.get( this.objectToKey.get( object ) ) ) { final T x = this.keyToObject.get( key ); if( x != null ) { visit( x, visited ); } } } private void traverse( final T object, final Set<T> visited, final ListFactory<T> result ) { if( visited.contains( object ) ) { return; } visited.add( object ); for( K key : this.dependencies.get( this.objectToKey.get( object ) ) ) { final T x = this.keyToObject.get( key ); if( x != null ) { traverse( x, visited, result ); } } result.add( object ); } }