Skip to content

Package: BazaarImpl$2

BazaarImpl$2

nameinstructionbranchcomplexitylinemethod
compare(Vendor, Vendor)
M: 0 C: 12
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
{...}
M: 0 C: 9
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
3: *
4: * All rights reserved. This program and the accompanying materials
5: * are made available under the terms of the Eclipse Public License 2.0
6: * which accompanies this distribution, and is available at
7: * https://www.eclipse.org/legal/epl-2.0/
8: *
9: * SPDX-License-Identifier: EPL-2.0
10: *
11: * Contributors:
12: * jonas - initial API and implementation
13: * Christian W. Damus - bug 548592
14: ******************************************************************************/
15: package org.eclipse.emfforms.bazaar.internal;
16:
17: import java.util.ArrayList;
18: import java.util.Arrays;
19: import java.util.Collections;
20: import java.util.Comparator;
21: import java.util.HashMap;
22: import java.util.LinkedHashSet;
23: import java.util.List;
24: import java.util.Map;
25: import java.util.Map.Entry;
26: import java.util.PriorityQueue;
27: import java.util.Queue;
28: import java.util.Set;
29:
30: import org.eclipse.e4.core.contexts.ContextInjectionFactory;
31: import org.eclipse.e4.core.contexts.EclipseContextFactory;
32: import org.eclipse.e4.core.contexts.IContextFunction;
33: import org.eclipse.e4.core.contexts.IEclipseContext;
34: import org.eclipse.e4.core.di.InjectionException;
35: import org.eclipse.emfforms.bazaar.Bazaar;
36: import org.eclipse.emfforms.bazaar.BazaarContext;
37: import org.eclipse.emfforms.bazaar.BazaarContextFunction;
38: import org.eclipse.emfforms.bazaar.Bid;
39: import org.eclipse.emfforms.bazaar.Create;
40: import org.eclipse.emfforms.bazaar.Exchange;
41: import org.eclipse.emfforms.bazaar.Precondition;
42: import org.eclipse.emfforms.bazaar.Preconditions;
43: import org.eclipse.emfforms.bazaar.StaticBid;
44: import org.eclipse.emfforms.bazaar.Vendor;
45:
46: /**
47: * Implementation of a {@link Bazaar} using the {@link IEclipseContext} for dependency injection.
48: *
49: * @param <T> the type of product produced by this {@link Bazaar}
50: * @author jonas
51: *
52: */
53: public class BazaarImpl<T> implements Bazaar<T> {
54:
55:         private final Set<Vendor<? extends T>> vendors = new LinkedHashSet<Vendor<? extends T>>();
56:
57:         private PriorityOverlapCallBack<? super T> priorityOverlapCallBack;
58:
59:         private final Map<String, BazaarContextFunction> contextFunctionMap = new HashMap<String, BazaarContextFunction>();
60:
61:         @Override
62:         public void addVendor(Vendor<? extends T> vendor) {
63:                 vendors.add(vendor);
64:         }
65:
66:         @Override
67:         public void removeVendor(Vendor<? extends T> vendor) {
68:                 vendors.remove(vendor);
69:         }
70:
71:         /**
72:          * Returns the product which is provided with the highest priority by any {@link Vendor}.
73:          *
74:          * @param vendor The {@link Vendor} creating the product
75:          * @param context The {@link IEclipseContext} to inject parameters for creating a product (see {@link Create})
76:          * @return T the created product
77:          */
78:         @SuppressWarnings("unchecked")
79:         public T createProduct(Vendor<? extends T> vendor, IEclipseContext context) {
80:                 try {
81:                         return (T) ContextInjectionFactory.invoke(vendor, Create.class, context);
82:                 } catch (final InjectionException e) {
83:                         return null;
84:                 }
85:         }
86:
87:         /**
88:          * Transforms all {@link BazaarContextFunction}s to {@link IContextFunction}s and attaches those to an
89:          * {@link IEclipseContext}.
90:          *
91:          * @param context the {@link IEclipseContext} to attache {@link IContextFunction}s to
92:          * @return the {@link IEclipseContext} with attached {@link IContextFunction}
93:          */
94:         public IEclipseContext addContextFunctions(IEclipseContext context) {
95:                 final Set<Entry<String, BazaarContextFunction>> entrySet = contextFunctionMap.entrySet();
96:                 for (final Entry<String, BazaarContextFunction> entry : entrySet) {
97:                         final IContextFunction iContextFunction = new IContextFunction() {
98:
99:                                 @Override
100:                                 public Object compute(IEclipseContext context, String contextKey) {
101:                                         try {
102:                                                 final Object transformed = ContextInjectionFactory.invoke(entry.getValue(), Exchange.class,
103:                                                         context);
104:                                                 if (transformed != null) {
105:                                                         context.set(entry.getKey(), transformed);
106:                                                 }
107:                                                 return transformed;
108:                                         } catch (final InjectionException e) {
109:                                                 return null;
110:                                         }
111:                                 }
112:                         };
113:                         context.set(entry.getKey(), iContextFunction);
114:                 }
115:                 return context;
116:         }
117:
118:         /**
119:          * Creates an {@link IEclipseContext} from a {@link BazaarContext}. If the
120:          * bazaar context contains an Eclipse context, then it is used and will have
121:          * all other values set into it from the bazaar context.
122:          *
123:          * @param bazaarContext the {@link BazaarContext} to get key / values from
124:          * @return a {@link IEclipseContext} containign all key / values provided by the {@link BazaarContext}
125:          */
126:         public IEclipseContext createEclipseContext(BazaarContext bazaarContext) {
127:                 final Map<String, Object> contextMap = bazaarContext.getContextMap();
128:
129:                 // Let the client inject its own e4 context
130:                 final String contextKey = IEclipseContext.class.getName();
131:                 final IEclipseContext context = (IEclipseContext) contextMap.getOrDefault(contextKey,
132:                         EclipseContextFactory.create());
133:
134:                 final Set<String> keySet = bazaarContext.getContextMap().keySet();
135:                 for (final String string : keySet) {
136:                         if (!contextKey.equals(string)) { // Don't put the context into itself
137:                                 context.set(string, contextMap.get(string));
138:                         }
139:                 }
140:
141:                 return context;
142:         }
143:
144:         /**
145:          * @param context the Eclipse injection context
146:          * @return obtains the best available vendor for the give {@code context}
147:          */
148:         public Vendor<? extends T> getBestVendor(IEclipseContext context) {
149:                 Double winningBid = null;
150:                 Vendor<? extends T> winner = null;
151:
152:                 for (final Vendor<? extends T> vendor : vendors) {
153:                         final Double bid = bid(vendor, context);
154:
155:                         if (bid == null) {
156:                                 // This vendor refuses to bid
157:                                 continue;
158:                         }
159:
160:                         if (winningBid == null || bid > winningBid) {
161:                                 // We have a new winner
162:                                 winningBid = bid;
163:                                 winner = vendor;
164:                         } else if (bid.equals(winningBid)) {
165:                                 // Report the tie and keep the current winner
166:                                 reportPriorityOverlap(winner, vendor);
167:                         }
168:                 }
169:
170:                 return winner;
171:         }
172:
173:         private Double bid(Vendor<? extends T> vendor, IEclipseContext context) {
174:                 if (!checkPreConditions(vendor, context)) {
175:                         return null;
176:                 }
177:
178:                 Double result = null; // Assume no bid
179:
180:                 result = staticBid(vendor);
181:                 if (result != null) {
182:                         return result;
183:                 }
184:
185:                 try {
186:                         result = (Double) ContextInjectionFactory.invoke(vendor, Bid.class, context);
187:                 } catch (final InjectionException e) {
188:                         // Do nothing. The vendor refuses the bid
189:                 } catch (final ClassCastException e) {
190:                         // Bid method did not return a double. Reject it
191:                 }
192:
193:                 return result;
194:         }
195:
196:         private Double staticBid(Vendor<? extends T> vendor) {
197:                 final StaticBid staticBid = vendor.getClass().getAnnotation(StaticBid.class);
198:                 if (staticBid != null) {
199:                         return staticBid.bid();
200:                 }
201:                 return null;
202:         }
203:
204:         private Queue<Vendor<? extends T>> createVendorPriorityQueue(IEclipseContext context) {
205:                 final Map<Vendor<? extends T>, Double> priorities = new HashMap<Vendor<? extends T>, Double>();
206:                 for (final Vendor<? extends T> vendor : vendors) {
207:                         final Double bid = bid(vendor, context);
208:
209:                         if (bid != null) {
210:                                 priorities.put(vendor, bid);
211:                         } // else the vendor refuses to bid
212:                 }
213:
214:                 if (priorities.isEmpty()) {
215:                         return new PriorityQueue<Vendor<? extends T>>();
216:                 }
217:
218:                 final Comparator<Vendor<? extends T>> ordering = new Comparator<Vendor<? extends T>>() {
219:                         @Override
220:                         public int compare(Vendor<? extends T> o1, Vendor<? extends T> o2) {
221:                                 // Sort highest bid to lowest
222:                                 return priorities.get(o2).compareTo(priorities.get(o1));
223:                         }
224:                 };
225:
226:                 final PriorityQueue<Vendor<? extends T>> result = new PriorityQueue<Vendor<? extends T>>(priorities.size(),
227:                         ordering);
228:                 result.addAll(priorities.keySet());
229:
230:                 return result;
231:         }
232:
233:         /**
234:          * @param existingVendorWithSamePriority
235:          * @param vendor
236:          */
237:         private void reportPriorityOverlap(Vendor<? extends T> existingVendorWithSamePriority, Vendor<? extends T> vendor) {
238:                 if (priorityOverlapCallBack == null) {
239:                         return;
240:                 }
241:                 priorityOverlapCallBack.priorityOverlap(existingVendorWithSamePriority, vendor);
242:
243:         }
244:
245:         @Override
246:         public void setPriorityOverlapCallBack(PriorityOverlapCallBack<? super T> priorityOverlapCallBack) {
247:                 this.priorityOverlapCallBack = priorityOverlapCallBack;
248:         }
249:
250:         /**
251:          * @param key
252:          * @param contextFunction
253:          */
254:         @Override
255:         public void addContextFunction(String key, BazaarContextFunction contextFunction) {
256:                 contextFunctionMap.put(key, contextFunction);
257:         }
258:
259:         /**
260:          *
261:          * @see org.eclipse.emfforms.bazaar.Bazaar#createProduct(org.eclipse.emfforms.bazaar.BazaarContext)
262:          */
263:         @Override
264:         public T createProduct(BazaarContext bazaarContext) {
265:                 final IEclipseContext eclipseContext = createEclipseContextWithFunctions(bazaarContext);
266:                 final Vendor<? extends T> bestVendor = getBestVendor(eclipseContext);
267:                 if (bestVendor == null) {
268:                         return null;
269:                 }
270:                 final T createProduct = createProduct(bestVendor, eclipseContext);
271:                 return createProduct;
272:         }
273:
274:         private IEclipseContext createEclipseContextWithFunctions(BazaarContext bazaarContext) {
275:                 IEclipseContext eclipseContext = createEclipseContext(bazaarContext);
276:                 eclipseContext = addContextFunctions(eclipseContext);
277:                 return eclipseContext;
278:         }
279:
280:         /**
281:          *
282:          * @see org.eclipse.emfforms.bazaar.Bazaar#createProducts(org.eclipse.emfforms.bazaar.BazaarContext)
283:          */
284:         @Override
285:         public List<T> createProducts(BazaarContext bazaarContext) {
286:                 final IEclipseContext eclipseContext = createEclipseContextWithFunctions(bazaarContext);
287:                 final Queue<Vendor<? extends T>> vendorQueue = createVendorPriorityQueue(eclipseContext);
288:                 if (vendorQueue.isEmpty()) {
289:                         return Collections.emptyList();
290:                 }
291:
292:                 final List<T> ret = new ArrayList<T>();
293:                 for (Vendor<? extends T> vendor = vendorQueue.poll(); vendor != null; vendor = vendorQueue.poll()) {
294:                         final T createProduct = createProduct(vendor, eclipseContext);
295:                         if (createProduct != null) {
296:                                 ret.add(createProduct);
297:                         }
298:                 }
299:
300:                 return ret;
301:         }
302:
303:         /**
304:          * Checks the {@link Precondition}s for a given vendor.
305:          *
306:          * @param vendor The {@link Vendor} to check the {@link Precondition} for.
307:          * @param context The {@link IEclipseContext} to provide the {@link Precondition}
308:          * @return whether the {@link Precondition}s are fulfilled
309:          */
310:         public boolean checkPreConditions(Vendor<? extends T> vendor, IEclipseContext context) {
311:                 final Set<Precondition> preConditionList = new LinkedHashSet<Precondition>();
312:                 final Precondition preConditionAnnotation = vendor.getClass().getAnnotation(Precondition.class);
313:                 if (preConditionAnnotation != null) {
314:                         preConditionList.add(preConditionAnnotation);
315:                 }
316:                 final Preconditions conditions = vendor.getClass().getAnnotation(Preconditions.class);
317:                 if (conditions != null) {
318:                         preConditionList.addAll(Arrays.asList(conditions.preconditions()));
319:                 }
320:                 for (final Precondition preCondition : preConditionList) {
321:                         if (!checkPreCondition(preCondition, vendor, context)) {
322:                                 return false;
323:                         }
324:                 }
325:                 return true;
326:         }
327:
328:         private boolean checkPreCondition(Precondition preCondition, Vendor<? extends T> vendor, IEclipseContext context) {
329:                 if (preCondition.value().equals(Precondition.UNASSIGNED)) {
330:                         return context.containsKey(preCondition.key());
331:                 }
332:                 final Object object = context.get(preCondition.key());
333:                 if (object == null) {
334:                         return false;
335:                 }
336:                 return object.equals(preCondition.value());
337:         }
338:
339: }