@SafeVarargs
        private final Deque<List<Path>> sortPluginDirs( Iterable<Path>... dirs )
        {
            // Map all plugins to they parent plugin (lower-cased), using a null key for parent-less plugins;
            final Map<String, Set<Path>> byParent = new HashMap<>();
            for ( final Iterable<Path> iterable : dirs )
            {
                for ( final Path dir : iterable )
                {
                    final String parent = PluginMetadataHelper.getParentPlugin( dir );
                    if ( !byParent.containsKey( parent ) )
                    {
                        byParent.put( parent, new HashSet<>() );
                    }
                    byParent.get( parent ).add( dir );
                }
            }

            // Transform the map into a tree structure (where the root node is a placeholder without data).
            final Node root = new Node();
            populateTree( root, byParent );

            // byParent should be consumed. Remaining entries are depending on a non-existing parent.
            for ( Map.Entry<String, Set<Path>> entry : byParent.entrySet() )
            {
                if ( !entry.getValue().isEmpty() )
                {
                    for ( final Path path : entry.getValue() )
                    {
                        final String name = PluginMetadataHelper.getCanonicalName( path );
                        Log.warn( "Unable to load plugin '{}' as its defined parent plugin '{}' is not installed.", name, entry.getKey() );
                    }
                }
            }

            // Return a deque of lists, where each list is parent-child chain of plugins (the parents preceding its children).
            final Set<PluginToLoad> pluginsToLoad = new HashSet<>();
            final Deque<List<Path>> result = new ArrayDeque<>();
            for ( final Node noParentPlugin : root.children )
            {
                final List<Path> hierarchy = new ArrayList<>();
                walkTree( noParentPlugin, hierarchy );
                // Strip out duplicates
                final Iterator<Path> iterator = hierarchy.iterator();
                while (iterator.hasNext()) {
                    final PluginToLoad pluginToLoad = new PluginToLoad(iterator.next());
                    if (!pluginsToLoad.add(pluginToLoad)) {
                        Log.warn("Unable to load plugin at '{}' as a different plugin with the same name is present", pluginToLoad.path);
                        iterator.remove();
                    }
                }

                // The admin plugin should go first
                if ( noParentPlugin.getName().equals( "admin" ) )
                {
                    result.addFirst( hierarchy );
                }
                else
                {
                    result.addLast( hierarchy );
                }
            }

            return result;
        }
Returns all plugin directories, in a deque of lists with these characteristics:
<ol>
<li>Every list is a hierarchy of parent/child plugins (or is a list of one element).</li>
<li>Every list is ordered to ensure that all parent plugins have a lower index than their children.</li>
<li>The first element of every list will be a plugin that has no 'parent' plugin.</li>
<li>the first element of the first list will be the 'admin' plugin.</li>
</ol>

Plugins within the provided argument that refer to non-existing parent plugins will not be part of the returned
collection.

@param dirs Collections of paths that refer every plugin directory (but not the corresponding .jar/.war files).
@return An ordered collection of paths.
xmppserver/src/main/java/org/jivesoftware/openfire/container/PluginMonitor.java