/*
* (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.runtime.contribution.Contribution;
import org.nuxeo.runtime.contribution.ContributionRegistry;
/**
* @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 synchronized 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() {
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;
}
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 + "]";
}
}