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:[email protected]">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}