/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* bstefanescu
*/
package org.eclipse.ecr.runtime.model;
import java.util.HashMap;
import java.util.Map;
/**
* This is a contribution registry that is managing contribution fragments and merge them as needed.
* The implementation will be notified through {@link #contributionUpdated(String, Object)} each time
* you need to store or remove a contribution. Note that contribution objects that are registered by
* your implementation <b>must</b> not be modified. You can see them as immutable objects - otherwise your local changes
* will be lost at the next update event.
* <p>
* To use it you should extends this abstract implementation and implement the abstract methods.
* <p>
* The implementation registry doesn't need to be thread safe since it will be called from synchronized methods.
* <p>
* Also, the contribution object
* <p>
* A simple implementation is:
* <pre>
* public class MyRegistry extends ContributionFragmentRegistry<MyContribution> {
* public Map<String, MyContribution> registry = new HAshMap<String,MyContribution>();
* public String getContributionId(MyContribution contrib) {
* return contrib.getId();
* }
* public void contributionUpdated(String id, MyContribution contrib, MyContribution origContrib) {
* registry.put(id, contrib);
* }
* public void contributionRemoved(String id, MyContribution origContrib) {
* registry.remove(id);
* }
* public MyContribution clone(MyContribution contrib) {
* MyContribution clone = new MyContribution(contrib.getId());
* clone.setSomeProperty(contrib.getSomeProperty());
* ...
* return clone;
* }
* public void merge(MyContribution src, MyContribution dst) {
* dst.setSomeProperty(src.getSomeProperty());
* ...
* }
* }
* </pre>
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*
*/
public abstract class ContributionFragmentRegistry<T> {
protected Map<String, FragmentList<T>> contribs = new HashMap<String, FragmentList<T>>();
/**
* Get the contribution ID given the contribution object
* @param contrib
* @return
*/
public abstract String getContributionId(T contrib);
/**
* Add or update a contribution.
* <p>
* If the contribution doesn't yet exists then it will be added, otherwise the value will be updated.
* If the given value is null the existing contribution must be removed
* <p>
* The second parameter is the actual contribution value which should be stored (updated).
* This usually represent a clone of the original contribution or a merge of multiple contribution fragments.
* Modifications on this object at application level will be lost on next {@link #contributionUpdated(String, Object, Object)}
* call on the same object id.
* The last parameter is the original contribution object which was either cloned or merged.
* You should never modify this object at application level. Because it will be used each time a subsequent
* merge is done.
* Also you should not store this object. Usually you will never need to touch this object.
*
* @param id - the id of the contribution that need to be updated
* @param contrib the updated contribution object that
* @param origContrib - the original contribution fragment that triggered the update.
*/
public abstract void contributionUpdated(String id, T contrib, T origContrib);
/**
* All the fragments in the contribution was removed. Contribution must be unregistered.
* <p>
* The first parameter is the contribution ID that should be remove and the second parameter
* the original contribution fragment that as unregistered causing the contribution to be removed.
*
* @param id
* @param origContrib
*/
public abstract void contributionRemoved(String id, T origContrib);
/**
* CLone the given contribution object
* @param object
* @return
*/
public abstract T clone(T object);
/**
* Merge 'src' into 'dst'. When merging only the 'dst' object is modified.
* @param src the object to copy over the 'dst' object
* @param dst this object is modified
*/
public abstract void merge(T src, T dst);
/**
* Add a new contribution. This will start install the new contribution and will notify the implementation
* about the value to add. (the final value to add may not be the same object as the one added -
* but a merge between multiple contributions)
* @param contrib
*/
public synchronized void addContribution(T contrib) {
String id = getContributionId(contrib);
FragmentList<T> head = addFragment(id, contrib);
contributionUpdated(id, head.merge(this), contrib);
}
/**
* Remove a contribution. This will uninstall the contribution and notify the implementation about the new value
* it should store (after re-merging contribution fragments).
*
* @param contrib
*/
public synchronized void removeContribution(T contrib) {
String id = getContributionId(contrib);
FragmentList<T> head = removeFragment(id, contrib);
if (head != null) {
T result = head.merge(this);
if (result != null) {
contributionUpdated(id, result, contrib);
} else {
contributionRemoved(id, contrib);
}
}
}
/**
* Get a merged contribution directly from the internal registry - and
* avoid passing by the implementation registry.
* Note that this operation will invoke a merge of existing fragments if needed.
* @param id
* @return
*/
public synchronized T getContribution(String id) {
FragmentList<T> head = contribs.get(id);
return head != null ? head.merge(this) : null;
}
/**
* Get an array of all contribution fragments
* @return
*/
@SuppressWarnings("unchecked")
public synchronized FragmentList<T>[] getFragments() {
return contribs.values().toArray(new FragmentList[contribs.size()]);
}
private FragmentList<T> addFragment(String id, T contrib) {
FragmentList<T> head = contribs.get(id);
if (head == null) {
// no merge needed
head = new FragmentList<T>();
this.contribs.put(id, head);
}
head.add(contrib);
return head;
}
private FragmentList<T> removeFragment(String id, T contrib) {
FragmentList<T> head = contribs.get(id);
if (head == null) {
return null;
}
if (head.remove(contrib)) {
if (head.isEmpty()) {
contribs.remove(id);
}
return head;
}
return null;
}
public static class FragmentList<T> extends Fragment<T> {
public FragmentList() {
super (null);
prev = this;
next = this;
}
public boolean isEmpty() {
return next == null;
}
public T merge(ContributionFragmentRegistry<T> reg) {
T mergedValue = object;
if (mergedValue != null) {
return mergedValue;
}
Fragment<T> p = next;
if (p == this) {
return null;
}
mergedValue = reg.clone(p.object);
p = p.next;
while (p != this) {
reg.merge(p.object, mergedValue);
p = p.next;
}
object = mergedValue;
return mergedValue;
}
public final void add(T contrib) {
insertBefore(new Fragment<T>(contrib));
object = null;
}
public final void add(Fragment<T> fragment) {
insertBefore(fragment);
object = null;
}
public boolean remove(T contrib) {
Fragment<T> p = next;
while (p != this) {
if (p.object == contrib) {
p.remove();
object = null;
return true;
}
p = p.next;
}
return false;
}
}
public static class Fragment<T> {
public T object;
public Fragment<T> next;
public Fragment<T> prev;
public Fragment(T object) {
this.object = object;
}
public final void insertBefore(Fragment<T> fragment) {
fragment.prev = prev;
fragment.next = this;
prev.next = fragment;
prev = fragment;
}
public final void insertAfter(Fragment<T> fragment) {
fragment.prev = this;
fragment.next = next;
next.prev = fragment;
next = fragment;
}
public final void remove() {
prev.next = next;
next.prev = prev;
next = prev = null;
}
public final boolean hasNext() {
return next != null;
}
public final boolean hasPrev() {
return prev != null;
}
}
}