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