001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 *
017 * $Id$
018 */
019
020package org.nuxeo.runtime.contribution.impl;
021
022import java.util.ArrayList;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Set;
027
028import org.nuxeo.runtime.contribution.Contribution;
029import org.nuxeo.runtime.contribution.ContributionRegistry;
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032
033/**
034 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
035 */
036public class ContributionImpl<K, T> implements Contribution<K, T> {
037
038    private static final Log log = LogFactory.getLog(ContributionImpl.class);
039
040    protected final AbstractContributionRegistry<K, T> registry;
041
042    protected final K primaryKey;
043
044    protected final List<T> mainFragments = new ArrayList<T>();
045
046    protected final List<T> fragments = new ArrayList<T>();
047
048    // the contributions I depend on
049    protected final Set<Contribution<K, T>> dependencies = new HashSet<Contribution<K, T>>();
050
051    // the contributions that are waiting for me
052    protected final Set<Contribution<K, T>> dependents = new HashSet<Contribution<K, T>>();
053
054    // the unresolved dependencies that are blocking my registration
055    // TODO: this member can be removed since we can obtain unresolved deps from dependencies set.
056    // protected Set<Contribution<K,T>> unresolvedDependencies = new HashSet<Contribution<K,T>>();
057
058    // last merged fragment
059    protected T value;
060
061    protected boolean isResolved = false;
062
063    public ContributionImpl(AbstractContributionRegistry<K, T> reg, K primaryKey) {
064        this.primaryKey = primaryKey;
065        registry = reg;
066    }
067
068    public ContributionRegistry<K, T> getRegistry() {
069        return registry;
070    }
071
072    /**
073     * @return the primaryKey.
074     */
075    public K getId() {
076        return primaryKey;
077    }
078
079    public Iterator<T> iterator() {
080        return fragments.iterator();
081    }
082
083    public Set<Contribution<K, T>> getDependencies() {
084        return dependencies;
085    }
086
087    public Set<Contribution<K, T>> getDependents() {
088        return dependents;
089    }
090
091    public Set<Contribution<K, T>> getUnresolvedDependencies() {
092        Set<Contribution<K, T>> set = new HashSet<Contribution<K, T>>();
093        for (Contribution<K, T> dep : dependencies) {
094            if (dep.isResolved()) {
095                set.add(dep);
096            }
097        }
098        return set;
099    }
100
101    protected boolean checkIsResolved() {
102        if (mainFragments.isEmpty()) {
103            return false;
104        }
105        for (Contribution<K, T> dep : dependencies) {
106            if (!dep.isResolved()) {
107                return false;
108            }
109        }
110        return true;
111    }
112
113    public int size() {
114        return fragments.size();
115    }
116
117    public boolean isEmpty() {
118        return fragments.isEmpty();
119    }
120
121    public T getFragment(int index) {
122        return fragments.get(index);
123    }
124
125    public boolean removeFragment(Object fragment) {
126        if (mainFragments.remove(fragment)) {
127            if (mainFragments.isEmpty()) {
128                if (fragments.isEmpty()) {
129                    unregister();
130                } else {
131                    unresolve();
132                }
133            } else {
134                update();
135            }
136            return true;
137        }
138        if (fragments.remove(fragment)) {
139            if (!mainFragments.isEmpty()) {
140                update();
141            }
142            return true;
143        }
144        return false;
145    }
146
147    public void addFragment(T fragment, K... superKeys) {
148        // check if it is the main fragment
149        if (registry.isMainFragment(fragment)) {
150            mainFragments.add(fragment);
151        } else { // update contribution fragments
152            fragments.add(fragment);
153        }
154        // when passing a null value as the superKey you get an array with a null element
155        if (superKeys != null && superKeys.length > 0 && superKeys[0] != null) {
156            for (K superKey : superKeys) {
157                Contribution<K, T> c = registry.getOrCreateDependency(superKey);
158                dependencies.add(c);
159                c.getDependents().add(this);
160            }
161        }
162        // recompute resolved state
163        update();
164    }
165
166    public T getValue() {
167        if (!isResolved) {
168            throw new IllegalStateException("Cannot compute merged values for not resolved contributions");
169        }
170        if (mainFragments.isEmpty() || value != null) {
171            return value;
172        }
173        // clone the last registered main fragment.
174        T result = registry.clone(mainFragments.get(mainFragments.size() - 1));
175        // first apply its super objects if any
176        for (Contribution<K, T> key : dependencies) {
177            T superObject = registry.getContribution(key.getId()).getValue();
178            registry.applySuperFragment(result, superObject);
179        }
180        // and now apply fragments
181        for (T fragment : this) {
182            registry.applyFragment(result, fragment);
183        }
184        value = result;
185        return result;
186    }
187
188    public boolean isPhantom() {
189        return mainFragments.isEmpty();
190    }
191
192    public boolean isResolved() {
193        return isResolved;
194    }
195
196    public boolean isRegistered() {
197        return !fragments.isEmpty();
198    }
199
200    /**
201     * Called each time a fragment is added or removed to update resolved state and to fire update notifications to the
202     * registry owning that contribution
203     */
204    protected void update() {
205        T oldValue = value;
206        value = null;
207        boolean canResolve = checkIsResolved();
208        if (isResolved != canResolve) { // resolved state changed
209            if (canResolve) {
210                resolve();
211            } else {
212                unresolve();
213            }
214        } else if (isResolved) {
215            registry.fireUpdated(oldValue, this);
216        }
217    }
218
219    public void unregister() {
220        if (isResolved) {
221            unresolve();
222        }
223        fragments.clear();
224        value = null;
225    }
226
227    public void unresolve() {
228        if (!isResolved) {
229            return;
230        }
231        isResolved = false;
232        for (Contribution<K, T> dep : dependents) {
233            dep.unresolve();
234        }
235        registry.fireUnresolved(this, value);
236        value = null;
237    }
238
239    public void resolve() {
240        if (isResolved || isPhantom()) {
241            throw new IllegalStateException("Cannot resolve. Invalid state. phantom: " + isPhantom() + "; resolved: "
242                    + isResolved);
243        }
244        if (checkIsResolved()) { // resolve dependents
245            isResolved = true;
246            registry.fireResolved(this);
247            for (Contribution<K, T> dep : dependents) {
248                if (!dep.isResolved()) {
249                    dep.resolve();
250                }
251            }
252        }
253    }
254
255    @Override
256    public boolean equals(Object obj) {
257        if (obj == this) {
258            return true;
259        }
260        if (obj instanceof Contribution) {
261            return primaryKey.equals(((Contribution) obj).getId());
262        }
263        return false;
264    }
265
266    @Override
267    public String toString() {
268        return primaryKey.toString() + " [ phantom: " + isPhantom() + "; resolved: " + isResolved + "]";
269    }
270
271}