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}