/* * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * bstefanescu * * $Id$ */ package org.nuxeo.runtime.contribution.impl; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.nuxeo.runtime.contribution.Contribution; import org.nuxeo.runtime.contribution.ContributionRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> * */ public class ContributionImpl<K, T> implements Contribution<K, T> { private static final Log log = LogFactory.getLog(ContributionImpl.class); protected final AbstractContributionRegistry<K, T> registry; protected final K primaryKey; protected final List<T> mainFragments = new ArrayList<T>(); protected final List<T> fragments = new ArrayList<T>(); // the contributions I depend on protected final Set<Contribution<K, T>> dependencies = new HashSet<Contribution<K, T>>(); // the contributions that are waiting for me protected final Set<Contribution<K, T>> dependents = new HashSet<Contribution<K, T>>(); // the unresolved dependencies that are blocking my registration //TODO: this member can be removed since we can obtain unresolved deps from dependencies set. //protected Set<Contribution<K,T>> unresolvedDependencies = new HashSet<Contribution<K,T>>(); // last merged fragment protected T value; protected boolean isResolved = false; public ContributionImpl(AbstractContributionRegistry<K,T> reg, K primaryKey) { this.primaryKey = primaryKey; registry = reg; } public ContributionRegistry<K,T> getRegistry() { return registry; } /** * @return the primaryKey. */ public K getId() { return primaryKey; } public Iterator<T> iterator() { return fragments.iterator(); } public Set<Contribution<K,T>> getDependencies() { return dependencies; } public Set<Contribution<K,T>> getDependents() { return dependents; } public Set<Contribution<K,T>> getUnresolvedDependencies() { Set<Contribution<K,T>> set = new HashSet<Contribution<K,T>>(); for (Contribution<K,T> dep : dependencies) { if (dep.isResolved()) { set.add(dep); } } return set; } protected boolean checkIsResolved() { if (mainFragments.isEmpty()) { return false; } for (Contribution<K,T> dep : dependencies) { if (!dep.isResolved()) { return false; } } return true; } public int size() { return fragments.size(); } public boolean isEmpty() { return fragments.isEmpty(); } public T getFragment(int index) { return fragments.get(index); } public boolean removeFragment(Object fragment) { if (mainFragments.remove(fragment)) { if (mainFragments.isEmpty()) { if (fragments.isEmpty()) { unregister(); } else { unresolve(); } } else { update(); } return true; } if (fragments.remove(fragment)) { if (!mainFragments.isEmpty()) { update(); } return true; } return false; } public void addFragment(T fragment, K ... superKeys) { // check if it is the main fragment if (registry.isMainFragment(fragment)) { mainFragments.add(fragment); } else { // update contribution fragments fragments.add(fragment); } // when passing a null value as the superKey you get an array with a null element if (superKeys != null && superKeys.length > 0 && superKeys[0] != null) { for (K superKey : superKeys) { Contribution<K, T> c = registry.getOrCreateDependency(superKey); dependencies.add(c); c.getDependents().add(this); } } // recompute resolved state update(); } public T getValue() { try { if (!isResolved) { throw new IllegalStateException( "Cannot compute merged values for not resolved contributions"); } if (mainFragments.isEmpty() || value != null) { return value; } // clone the last registered main fragment. T result = registry.clone(mainFragments.get(mainFragments.size()-1)); // first apply its super objects if any for (Contribution<K,T> key : dependencies) { T superObject = registry.getContribution(key.getId()).getValue(); registry.applySuperFragment(result, superObject); } // and now apply fragments for (T fragment : this) { registry.applyFragment(result, fragment); } value = result; return result; } catch (Exception e) { log.error(e); return null; //TODO } } public boolean isPhantom() { return mainFragments.isEmpty(); } public boolean isResolved() { return isResolved; } public boolean isRegistered() { return !fragments.isEmpty(); } /** * Called each time a fragment is added or removed * to update resolved state and to fire update notifications to * the registry owning that contribution */ protected void update() { T oldValue = value; value = null; boolean canResolve = checkIsResolved(); if (isResolved != canResolve) { // resolved state changed if (canResolve) { resolve(); } else { unresolve(); } } else if (isResolved) { registry.fireUpdated(oldValue, this); } } public void unregister() { if (isResolved) { unresolve(); } fragments.clear(); value = null; } public void unresolve() { if (!isResolved) { return; } isResolved = false; for (Contribution<K,T> dep : dependents) { dep.unresolve(); } registry.fireUnresolved(this, value); value = null; } public void resolve() { if (isResolved || isPhantom()) { throw new IllegalStateException( "Cannot resolve. Invalid state. phantom: "+isPhantom()+"; resolved: "+isResolved); } if (checkIsResolved()) { // resolve dependents isResolved = true; registry.fireResolved(this); for (Contribution<K,T> dep : dependents) { if (!dep.isResolved()) { dep.resolve(); } } } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Contribution) { return primaryKey.equals(((Contribution) obj).getId()); } return false; } @Override public String toString() { return primaryKey.toString()+" [ phantom: "+isPhantom()+"; resolved: "+isResolved+"]"; } }