DependencyResolver.java

/* $Id$
 *****************************************************************************
 * Copyright (c) 2010-2012 Contributors - see below
 * 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:
 *    Luis Sergio Oliveira (euluis)
 *****************************************************************************
 */

package org.argouml.profile.internal;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A dependency resolver for items of type T. It implements a state-full
 * dependency resolution algorithm.
 *
 * @author Luis Sergio Oliveira (euluis)
 * @param <T> the type of items for which dependencies will be resolved.
 */
class DependencyResolver<T> {

    private static final Logger LOG =
        Logger.getLogger(DependencyResolver.class.getName());

    private DependencyChecker<T> checker;

    /**
     * WARNING: only to be used from outside classes by tests.
     * This is the state-full part of the algorithm, storing the unresolved
     * items between resolve methods calls.
     */
    Collection<T> unresolvedItems;

    /**
     * Create a dependency resolver and initialize it with the associated
     * dependency checker.
     *
     * @param checker the object that will be invoked to check if for a certain
     *                item all dependencies are resolved.
     */
    DependencyResolver(DependencyChecker<T> checker) {
        this.checker = checker;
        unresolvedItems = new HashSet<T>();
    }

    /**
     * Attempt to resolve the dependencies of the items already handed over to
     * the resolver instance.
     */
    void resolve() {
        if (unresolvedItems.isEmpty()) {
            return;
        }
        final Collection<T> items = Collections.emptyList();
        resolve(items);
    }

    /**
     * Attempt to resolve the dependencies of the items already handed over to
     * the resolver instance and the additional items handed over now.
     *
     * @param items additional items to resolve.
     */
    void resolve(Collection<T> items) {
        if (unresolvedItems.isEmpty() && items.isEmpty()) {
            return;
        }
        Collection<T> allUnresolvedItems = new HashSet<T>();
        allUnresolvedItems.addAll(items);
        allUnresolvedItems.addAll(unresolvedItems);
        
        if (LOG.isLoggable(Level.FINE)) {
            LOG.log(Level.FINE,
                    items2Msg("Attempt to resolve the following items:",
                              allUnresolvedItems));
        }
        Collection<T> resolved = internalResolve(allUnresolvedItems);
        allUnresolvedItems.removeAll(resolved);
        unresolvedItems.clear();
        unresolvedItems.addAll(allUnresolvedItems);
        if (!unresolvedItems.isEmpty()) {
            LOG.log(Level.WARNING,
                    items2Msg("The following items were left unresolved after "
                              + "attempt:\n",
                              unresolvedItems));
        }
    }

    private String items2Msg(String preface, Collection<T> items) {
        StringBuffer msg = new StringBuffer(preface);
        for (T item : items) {
            msg.append("\t");
            msg.append(item.toString());
            msg.append("\n");
        }
        return msg.toString();
    }

    /**
     * Recursively resolve all dependencies. Stops when an iteration through
     * all unresolved items didn't manage to resolve any.
     *
     * @param items items to resolve.
     * @return the items that were resolved.
     */
    private Collection<T> internalResolve(Collection<T> items) {
        Collection<T> resolved = new HashSet<T>();
        for (T item : items) {
            if (checker.check(item)) {
                resolved.add(item);
            }
        }
        HashSet<T> toResolveItems = new HashSet<T>(items);
        toResolveItems.removeAll(resolved);
        if (!resolved.isEmpty()) {
            resolved.addAll(internalResolve(toResolveItems));
        }
        return resolved;
    }
}