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}