001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.runtime.model;
013
014import java.util.HashMap;
015import java.util.Map;
016
017/**
018 * This is a contribution registry that is managing contribution fragments and merge them as needed. The implementation
019 * will be notified through {@link #contributionUpdated(String, Object)} each time you need to store or remove a
020 * contribution. Note that contribution objects that are registered by your implementation <b>must</b> not be modified.
021 * You can see them as immutable objects - otherwise your local changes will be lost at the next update event.
022 * <p>
023 * To use it you should extends this abstract implementation and implement the abstract methods.
024 * <p>
025 * The implementation registry doesn't need to be thread safe since it will be called from synchronized methods.
026 * <p>
027 * Also, the contribution object
028 * <p>
029 * A simple implementation is:
030 *
031 * <pre>
032 * public class MyRegistry extends ContributionFragmentRegistry&lt;MyContribution&gt; {
033 *     public Map&lt;String, MyContribution&gt; registry = new HAshMap&lt;String, MyContribution&gt;();
034 *
035 *     public String getContributionId(MyContribution contrib) {
036 *         return contrib.getId();
037 *     }
038 *
039 *     public void contributionUpdated(String id, MyContribution contrib, MyContribution origContrib) {
040 *         registry.put(id, contrib);
041 *     }
042 *
043 *     public void contributionRemoved(String id, MyContribution origContrib) {
044 *         registry.remove(id);
045 *     }
046 *
047 *     public MyContribution clone(MyContribution contrib) {
048 *          MyContribution clone = new MyContribution(contrib.getId());
049 *          clone.setSomeProperty(contrib.getSomeProperty());
050 *          ...
051 *          return clone;
052 *       }
053 *
054 *     public void merge(MyContribution src, MyContribution dst) {
055 *          dst.setSomeProperty(src.getSomeProperty());
056 *          ...
057 *       }
058 * }
059 * </pre>
060 *
061 * Since 5.5, if the registry does not support merging of resources, you can just override the method
062 * {@link #isSupportingMerge()} and return false, so that {@link #merge(Object, Object)} and {@link #clone()} are never
063 * called.
064 *
065 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
066 * @see SimpleContributionRegistry<T>
067 */
068public abstract class ContributionFragmentRegistry<T> {
069
070    protected Map<String, FragmentList<T>> contribs = new HashMap<String, FragmentList<T>>();
071
072    /**
073     * Returns the contribution ID given the contribution object
074     *
075     * @param contrib
076     * @return
077     */
078    public abstract String getContributionId(T contrib);
079
080    /**
081     * Adds or updates a contribution.
082     * <p>
083     * If the contribution doesn't yet exists then it will be added, otherwise the value will be updated. If the given
084     * value is null the existing contribution must be removed.
085     * <p>
086     * The second parameter is the contribution that should be updated when merging, as well as stored and used. This
087     * usually represents a clone of the original contribution or a merge of multiple contribution fragments.
088     * Modifications on this object at application level will be lost on next
089     * {@link #contributionUpdated(String, Object, Object)} call on the same object id: modifications should be done in
090     * the {@link #merge(Object, Object)} method.
091     * <p>
092     * The last parameter is the new contribution object, unchanged (original) which was neither cloned nor merged. This
093     * object should never be modified at application level, because it will be used each time a subsequent merge is
094     * done. Also, it never should be stored.
095     *
096     * @param id - the id of the contribution that needs to be updated
097     * @param contrib the updated contribution object that
098     * @param newOrigContrib - the new, unchanged (original) contribution fragment that triggered the update.
099     */
100    public abstract void contributionUpdated(String id, T contrib, T newOrigContrib);
101
102    /**
103     * All the fragments in the contribution was removed. Contribution must be unregistered.
104     * <p>
105     * The first parameter is the contribution ID that should be remove and the second parameter the original
106     * contribution fragment that as unregistered causing the contribution to be removed.
107     *
108     * @param id
109     * @param origContrib
110     */
111    public abstract void contributionRemoved(String id, T origContrib);
112
113    /**
114     * CLone the given contribution object
115     *
116     * @param object
117     * @return
118     */
119    public abstract T clone(T orig);
120
121    /**
122     * Merge 'src' into 'dst'. When merging only the 'dst' object is modified.
123     *
124     * @param src the object to copy over the 'dst' object
125     * @param dst this object is modified
126     */
127    public abstract void merge(T src, T dst);
128
129    /**
130     * Returns true if merge is supported.
131     * <p>
132     * Hook method to be overridden if merge logics behind {@link #clone()} and {@link #merge(Object, Object)} cannot be
133     * implemented.
134     *
135     * @since 5.5
136     */
137    public boolean isSupportingMerge() {
138        return true;
139    }
140
141    /**
142     * Add a new contribution. This will start install the new contribution and will notify the implementation about the
143     * value to add. (the final value to add may not be the same object as the one added - but a merge between multiple
144     * contributions)
145     *
146     * @param contrib
147     */
148    public synchronized void addContribution(T contrib) {
149        String id = getContributionId(contrib);
150        FragmentList<T> head = addFragment(id, contrib);
151        contributionUpdated(id, head.merge(this), contrib);
152    }
153
154    /**
155     * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it
156     * should store (after re-merging contribution fragments).
157     * <p>
158     * Uses standard equality to check for old objects (useEqualsMethod == false).
159     *
160     * @param contrib
161     * @see #removeContribution(Object, boolean)
162     */
163    public synchronized void removeContribution(T contrib) {
164        removeContribution(contrib, false);
165    }
166
167    /**
168     * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it
169     * should store (after re-merging contribution fragments).
170     * <p>
171     * Equality can be controlled from here.
172     * <p>
173     * Contributions come from the runtime that keeps exact instances, so using equality usually makes it possible to
174     * remove the exact instance that was contributed by this component (without needing to reference the component name
175     * for instance). But when unit-testing, or when registrating contributions that do not come directly from the
176     * runtime, regirties need to use the equals method defined on each contribution.
177     *
178     * @param contrib the contrib to remove
179     * @param useEqualsMethod a boolean stating that old contributions should be checked using the equals method instead
180     *            of
181     * @since 5.6
182     */
183    public synchronized void removeContribution(T contrib, boolean useEqualsMethod) {
184        String id = getContributionId(contrib);
185        FragmentList<T> head = removeFragment(id, contrib, useEqualsMethod);
186        if (head != null) {
187            T result = head.merge(this);
188            if (result != null) {
189                contributionUpdated(id, result, contrib);
190            } else {
191                contributionRemoved(id, contrib);
192            }
193        }
194    }
195
196    /**
197     * Get a merged contribution directly from the internal registry - and avoid passing by the implementation registry.
198     * Note that this operation will invoke a merge of existing fragments if needed.
199     * <p>
200     * Since 5.5, this method has made protected as it should not be used by the service retrieving merged resources
201     * (otherwise merge will be done again). If you'd really like to call it, add a public method on your registry
202     * implementation that will call it.
203     *
204     * @param id
205     * @return
206     */
207    protected synchronized T getContribution(String id) {
208        FragmentList<T> head = contribs.get(id);
209        return head != null ? head.merge(this) : null;
210    }
211
212    /**
213     * Get an array of all contribution fragments
214     *
215     * @return
216     */
217    @SuppressWarnings("unchecked")
218    public synchronized FragmentList<T>[] getFragments() {
219        return contribs.values().toArray(new FragmentList[contribs.size()]);
220    }
221
222    protected FragmentList<T> addFragment(String id, T contrib) {
223        FragmentList<T> head = contribs.get(id);
224        if (head == null) {
225            // no merge needed
226            head = new FragmentList<T>();
227            this.contribs.put(id, head);
228        }
229        head.add(contrib);
230        return head;
231    }
232
233    protected FragmentList<T> removeFragment(String id, T contrib, boolean useEqualsMethod) {
234        FragmentList<T> head = contribs.get(id);
235        if (head == null) {
236            return null;
237        }
238        if (head.remove(contrib, useEqualsMethod)) {
239            if (head.isEmpty()) {
240                contribs.remove(id);
241            }
242            return head;
243        }
244        return null;
245    }
246
247    public static class FragmentList<T> extends Fragment<T> {
248
249        public FragmentList() {
250            super(null);
251            prev = this;
252            next = this;
253        }
254
255        public boolean isEmpty() {
256            return next == null;
257        }
258
259        public T merge(ContributionFragmentRegistry<T> reg) {
260            T mergedValue = object;
261            if (mergedValue != null) {
262                return mergedValue;
263            }
264            Fragment<T> p = next;
265            if (p == this) {
266                return null;
267            }
268            mergedValue = reg.isSupportingMerge() ? reg.clone(p.object) : p.object;
269            p = p.next;
270            while (p != this) {
271                if (reg.isSupportingMerge()) {
272                    reg.merge(p.object, mergedValue);
273                } else {
274                    mergedValue = p.object;
275                }
276                p = p.next;
277            }
278            object = mergedValue;
279            return mergedValue;
280        }
281
282        public final void add(T contrib) {
283            insertBefore(new Fragment<T>(contrib));
284            object = null;
285        }
286
287        public final void add(Fragment<T> fragment) {
288            insertBefore(fragment);
289            object = null;
290        }
291
292        public boolean remove(T contrib) {
293            return remove(contrib, false);
294        }
295
296        /**
297         * @since 5.6
298         */
299        public boolean remove(T contrib, boolean useEqualsMethod) {
300            Fragment<T> p = next;
301            while (p != this) {
302                if ((useEqualsMethod && (p.object != null && p.object.equals(contrib)))
303                        || (!useEqualsMethod && p.object == contrib)) {
304                    p.remove();
305                    object = null;
306                    return true;
307                }
308                p = p.next;
309            }
310            return false;
311        }
312    }
313
314    public static class Fragment<T> {
315        public T object;
316
317        public Fragment<T> next;
318
319        public Fragment<T> prev;
320
321        public Fragment(T object) {
322            this.object = object;
323        }
324
325        public final void insertBefore(Fragment<T> fragment) {
326            fragment.prev = prev;
327            fragment.next = this;
328            prev.next = fragment;
329            prev = fragment;
330        }
331
332        public final void insertAfter(Fragment<T> fragment) {
333            fragment.prev = this;
334            fragment.next = next;
335            next.prev = fragment;
336            next = fragment;
337        }
338
339        public final void remove() {
340            prev.next = next;
341            next.prev = prev;
342            next = prev = null;
343        }
344
345        public final boolean hasNext() {
346            return next != null;
347        }
348
349        public final boolean hasPrev() {
350            return prev != null;
351        }
352    }
353
354}