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<>();
047
048    protected final List<T> fragments = new ArrayList<>();
049
050    // the contributions I depend on
051    protected final Set<Contribution<K, T>> dependencies = new HashSet<>();
052
053    // the contributions that are waiting for me
054    protected final Set<Contribution<K, T>> dependents = new HashSet<>();
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    @Override
071    public ContributionRegistry<K, T> getRegistry() {
072        return registry;
073    }
074
075    /**
076     * @return the primaryKey.
077     */
078    @Override
079    public K getId() {
080        return primaryKey;
081    }
082
083    @Override
084    public Iterator<T> iterator() {
085        return fragments.iterator();
086    }
087
088    @Override
089    public Set<Contribution<K, T>> getDependencies() {
090        return dependencies;
091    }
092
093    @Override
094    public Set<Contribution<K, T>> getDependents() {
095        return dependents;
096    }
097
098    @Override
099    public Set<Contribution<K, T>> getUnresolvedDependencies() {
100        Set<Contribution<K, T>> set = new HashSet<>();
101        for (Contribution<K, T> dep : dependencies) {
102            if (dep.isResolved()) {
103                set.add(dep);
104            }
105        }
106        return set;
107    }
108
109    protected boolean checkIsResolved() {
110        if (mainFragments.isEmpty()) {
111            return false;
112        }
113        for (Contribution<K, T> dep : dependencies) {
114            if (!dep.isResolved()) {
115                return false;
116            }
117        }
118        return true;
119    }
120
121    @Override
122    public int size() {
123        return fragments.size();
124    }
125
126    @Override
127    public boolean isEmpty() {
128        return fragments.isEmpty();
129    }
130
131    @Override
132    public T getFragment(int index) {
133        return fragments.get(index);
134    }
135
136    @Override
137    public boolean removeFragment(Object fragment) {
138        if (mainFragments.remove(fragment)) {
139            if (mainFragments.isEmpty()) {
140                if (fragments.isEmpty()) {
141                    unregister();
142                } else {
143                    unresolve();
144                }
145            } else {
146                update();
147            }
148            return true;
149        }
150        if (fragments.remove(fragment)) {
151            if (!mainFragments.isEmpty()) {
152                update();
153            }
154            return true;
155        }
156        return false;
157    }
158
159    @Override
160    public synchronized void addFragment(T fragment, K... superKeys) {
161        // check if it is the main fragment
162        if (registry.isMainFragment(fragment)) {
163            mainFragments.add(fragment);
164        } else { // update contribution fragments
165            fragments.add(fragment);
166        }
167        // when passing a null value as the superKey you get an array with a null element
168        if (superKeys != null && superKeys.length > 0 && superKeys[0] != null) {
169            for (K superKey : superKeys) {
170                Contribution<K, T> c = registry.getOrCreateDependency(superKey);
171                dependencies.add(c);
172                c.getDependents().add(this);
173            }
174        }
175        // recompute resolved state
176        update();
177    }
178
179    @Override
180    public T getValue() {
181        if (!isResolved) {
182            throw new IllegalStateException("Cannot compute merged values for not resolved contributions");
183        }
184        if (mainFragments.isEmpty() || value != null) {
185            return value;
186        }
187        // clone the last registered main fragment.
188        T result = registry.clone(mainFragments.get(mainFragments.size() - 1));
189        // first apply its super objects if any
190        for (Contribution<K, T> key : dependencies) {
191            T superObject = registry.getContribution(key.getId()).getValue();
192            registry.applySuperFragment(result, superObject);
193        }
194        // and now apply fragments
195        for (T fragment : this) {
196            registry.applyFragment(result, fragment);
197        }
198        value = result;
199        return result;
200    }
201
202    @Override
203    public boolean isPhantom() {
204        return mainFragments.isEmpty();
205    }
206
207    @Override
208    public boolean isResolved() {
209        return isResolved;
210    }
211
212    @Override
213    public boolean isRegistered() {
214        return !fragments.isEmpty();
215    }
216
217    /**
218     * Called each time a fragment is added or removed to update resolved state and to fire update notifications to the
219     * registry owning that contribution
220     */
221    protected void update() {
222        T oldValue = value;
223        value = null;
224        boolean canResolve = checkIsResolved();
225        if (isResolved != canResolve) { // resolved state changed
226            if (canResolve) {
227                resolve();
228            } else {
229                unresolve();
230            }
231        } else if (isResolved) {
232            registry.fireUpdated(oldValue, this);
233        }
234    }
235
236    @Override
237    public void unregister() {
238        if (isResolved) {
239            unresolve();
240        }
241        fragments.clear();
242        value = null;
243    }
244
245    @Override
246    public void unresolve() {
247        if (!isResolved) {
248            return;
249        }
250        isResolved = false;
251        for (Contribution<K, T> dep : dependents) {
252            dep.unresolve();
253        }
254        registry.fireUnresolved(this, value);
255        value = null;
256    }
257
258    @Override
259    public void resolve() {
260        if (isResolved || isPhantom()) {
261            throw new IllegalStateException("Cannot resolve. Invalid state. phantom: " + isPhantom() + "; resolved: "
262                    + isResolved);
263        }
264        if (checkIsResolved()) { // resolve dependents
265            isResolved = true;
266            registry.fireResolved(this);
267            for (Contribution<K, T> dep : dependents) {
268                if (!dep.isResolved()) {
269                    dep.resolve();
270                }
271            }
272        }
273    }
274
275    @Override
276    public int hashCode() {
277        final int prime = 31;
278        int result = 1;
279        result = prime * result + ((primaryKey == null) ? 0 : primaryKey.hashCode());
280        return result;
281    }
282
283    @Override
284    public boolean equals(Object obj) {
285        if (obj == this) {
286            return true;
287        }
288        if (obj instanceof ContributionImpl) {
289            @SuppressWarnings("rawtypes")
290            ContributionImpl other = (ContributionImpl) obj;
291            return primaryKey.equals(other.primaryKey);
292        }
293        return false;
294    }
295
296    @Override
297    public String toString() {
298        return primaryKey.toString() + " [ phantom: " + isPhantom() + "; resolved: " + isResolved + "]";
299    }
300
301}