blob: 57d859a35d58c85e90cb7d327384e56bbada6bbc [file] [log] [blame]
Richard S. Hall85bafab2009-07-13 13:25:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20package org.cauldron.bld.core.repository;
21
22import java.io.IOException;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.Comparator;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.LinkedList;
30import java.util.List;
31import java.util.Map;
32import java.util.Set;
33
34import org.cauldron.bld.core.BldCore;
35import org.eclipse.core.runtime.IProgressMonitor;
36import org.eclipse.core.runtime.SubMonitor;
37import org.cauldron.sigil.model.ICompoundModelElement;
38import org.cauldron.sigil.model.IModelElement;
39import org.cauldron.sigil.model.eclipse.ILibrary;
40import org.cauldron.sigil.model.eclipse.ILibraryImport;
41import org.cauldron.sigil.model.eclipse.ISigilBundle;
42import org.cauldron.sigil.model.osgi.IPackageExport;
43import org.cauldron.sigil.model.osgi.IPackageImport;
44import org.cauldron.sigil.model.osgi.IRequiredBundle;
45import org.cauldron.sigil.repository.IBundleRepository;
46import org.cauldron.sigil.repository.IBundleResolver;
47import org.cauldron.sigil.repository.IRepositoryManager;
48import org.cauldron.sigil.repository.IResolution;
49import org.cauldron.sigil.repository.IResolutionMonitor;
50import org.cauldron.sigil.repository.ResolutionConfig;
51import org.cauldron.sigil.repository.ResolutionException;
52import org.osgi.framework.Version;
53
54public class BundleResolver implements IBundleResolver {
55
56 private class BundleOrderComparator implements Comparator<ISigilBundle> {
57 private IModelElement requirement;
58
59 public BundleOrderComparator(IModelElement requirement) {
60 this.requirement = requirement;
61 }
62
63 public int compare(ISigilBundle o1, ISigilBundle o2) {
64 int c = compareVersions(o1, o2);
65
66 if ( c == 0 ) {
67 c = compareImports(o1, o2);
68 }
69
70 return c;
71 }
72
73 private int compareImports(ISigilBundle o1, ISigilBundle o2) {
74 int c1 = o1.getBundleInfo().getImports().size();
75 int c2 = o2.getBundleInfo().getImports().size();
76
77 if ( c1 < c2 ) {
78 return -1;
79 }
80 else if ( c2 > c1 ) {
81 return 1;
82 }
83 else {
84 return 0;
85 }
86 }
87
88 private int compareVersions(ISigilBundle o1, ISigilBundle o2) {
89 Version v1 = null;
90 Version v2 = null;
91 if ( requirement instanceof IPackageImport ) {
92 v1 = findExportVersion( (IPackageImport) requirement, o1 );
93 v2 = findExportVersion( (IPackageImport) requirement, o2 );
94 }
95 else if ( requirement instanceof IRequiredBundle ) {
96 v1 = o1.getBundleInfo().getVersion();
97 v2 = o1.getBundleInfo().getVersion();
98 }
99
100 if ( v1 == null ) {
101 if ( v2 == null ) {
102 return 0;
103 }
104 else {
105 return 1;
106 }
107 }
108 else {
109 if ( v2 == null ) {
110 return -1;
111 }
112 else {
113 return v2.compareTo(v1);
114 }
115 }
116 }
117
118 private Version findExportVersion(IPackageImport pi, ISigilBundle o1) {
119 for ( IPackageExport pe : o1.getBundleInfo().getExports() ) {
120 if ( pi.getPackageName().equals( pi.getPackageName() ) ) {
121 return pe.getVersion();
122 }
123 }
124
125 return null;
126 }
127
128 }
129
130 private class ResolutionContext {
131 private final IModelElement root;
132 private final ResolutionConfig config;
133 private final IResolutionMonitor monitor;
134
135 private final Resolution resolution = new Resolution();
136 private final Set<IModelElement> parsed = new HashSet<IModelElement>();
137 private final LinkedList<IModelElement> requirements = new LinkedList<IModelElement>();
138
139 public ResolutionContext(IModelElement root, ResolutionConfig config, IResolutionMonitor monitor) {
140 this.root = root;
141 this.config = config;
142 this.monitor = monitor;
143 }
144
145 public void enterModelElement(IModelElement element) {
146 parsed.add(element);
147 }
148
149 public void exitModelElement(IModelElement element) {
150 parsed.remove(element);
151 }
152
153 public boolean isNewModelElement(IModelElement element) {
154 return !parsed.contains(element);
155 }
156
157 public boolean isValid() {
158 return resolution.isSuccess();
159 }
160
161 public void setValid(boolean valid) {
162 resolution.setSuccess(valid);
163 }
164
165 public ResolutionException newResolutionException() {
166 return new ResolutionException(root, requirements.toArray( new IModelElement[requirements.size()]) );
167 }
168
169 public void startRequirement(IModelElement element) {
170 requirements.add(element);
171 monitor.startResolution(element);
172 }
173
174 public void endRequirement(IModelElement element) {
175 ISigilBundle provider = resolution.getProvider(element);
176
177 setValid( provider != null || isOptional(element) || config.isIgnoreErrors() );
178
179 if ( isValid() ) {
180 // only clear stack if valid
181 // else use it as an aid to trace errors
182 requirements.remove(element);
183 }
184
185 monitor.endResolution( element, provider );
186 }
187 }
188
189 private class Resolution implements IResolution {
190 private Map<ISigilBundle, List<IModelElement>> providees = new HashMap<ISigilBundle, List<IModelElement>>();
191 private Map<IModelElement, ISigilBundle> providers = new HashMap<IModelElement, ISigilBundle>();
192 private boolean success = true; // assume success
193
194 boolean addProvider(IModelElement element, ISigilBundle provider) {
195 providers.put( element, provider );
196
197 List<IModelElement> requirements = providees.get( provider );
198
199 boolean isNewProvider = requirements == null;
200
201 if ( isNewProvider ) {
202 requirements = new ArrayList<IModelElement>();
203 providees.put( provider, requirements );
204 }
205
206 requirements.add( element );
207
208 return isNewProvider;
209 }
210
211 void removeProvider(IModelElement element, ISigilBundle provider) {
212 providers.remove(element);
213 List<IModelElement> e = providees.get(provider);
214 e.remove(element);
215 if ( e.isEmpty() ) {
216 providees.remove(provider);
217 }
218 }
219
220 void setSuccess(boolean success) {
221 this.success = success;
222 }
223
224 public boolean isSuccess() {
225 return success;
226 }
227
228 public ISigilBundle getProvider(IModelElement requirement) {
229 return providers.get(requirement);
230 }
231
232 public Set<ISigilBundle> getBundles() {
233 return providees.keySet();
234 }
235
236 public List<IModelElement> getMatchedRequirements(ISigilBundle bundle) {
237 return providees.get(bundle);
238 }
239
240 public boolean isSynchronized() {
241 for ( ISigilBundle b : getBundles() ) {
242 if ( !b.isSynchronized() ) {
243 return false;
244 }
245 }
246
247 return true;
248 }
249
250 public void synchronize(IProgressMonitor monitor) {
251 Set<ISigilBundle> bundles = getBundles();
252 SubMonitor progress = SubMonitor.convert(monitor, bundles.size());
253
254 for ( ISigilBundle b : bundles ) {
255 if ( monitor.isCanceled() ) {
256 break;
257 }
258
259 try {
260 b.synchronize(progress.newChild(1));
261 } catch (IOException e) {
262 BldCore.error( "Failed to synchronize " + b, e );
263 }
264 }
265 }
266 }
267
268 private static final IResolutionMonitor NULL_MONITOR = new IResolutionMonitor() {
269 public void endResolution(IModelElement requirement,
270 ISigilBundle sigilBundle) {
271 }
272
273 public boolean isCanceled() {
274 return false;
275 }
276
277 public void startResolution(IModelElement requirement) {
278 }
279 };
280
281 private IRepositoryManager repositoryManager;
282
283 public BundleResolver(IRepositoryManager repositoryManager) {
284 this.repositoryManager = repositoryManager;
285 }
286
287 public IResolution resolve(IModelElement element, ResolutionConfig config, IResolutionMonitor monitor) throws ResolutionException {
288 if ( monitor == null ) {
289 monitor = NULL_MONITOR;
290 }
291 ResolutionContext ctx = new ResolutionContext(element, config, monitor);
292
293 resolveElement(element, ctx);
294
295 if ( !ctx.isValid() ) {
296 throw ctx.newResolutionException();
297 }
298
299 return ctx.resolution;
300 }
301
302 private void resolveElement(IModelElement element, ResolutionContext ctx) throws ResolutionException {
303 if ( isRequirement(element) ) {
304 resolveRequirement(element, ctx);
305 }
306
307 if ( ctx.isValid() && element instanceof ICompoundModelElement ) {
308 resolveCompound((ICompoundModelElement) element, ctx);
309 }
310 }
311
312 private void resolveCompound(ICompoundModelElement compound, ResolutionContext ctx) throws ResolutionException {
313 for ( IModelElement element : compound.children() ) {
314 if ( ctx.isNewModelElement(element) ) {
315 if ( isRequirement(element) ) {
316 resolveRequirement(element, ctx);
317 }
318 else if ( element instanceof ICompoundModelElement ) {
319 if ( !ctx.monitor.isCanceled() ) {
320 ctx.enterModelElement( element );
321 resolveElement((ICompoundModelElement) element, ctx);
322 ctx.exitModelElement(element);
323 }
324 }
325
326 if ( !ctx.isValid() ) {
327 break;
328 }
329 }
330 }
331 }
332
333 private void resolveRequirement(IModelElement requirement, ResolutionContext ctx) throws ResolutionException {
334 if ( ctx.config.isOptional() || !isOptional(requirement) ) {
335 ctx.startRequirement(requirement );
336
337 try {
338 int[] priorities = repositoryManager.getPriorityLevels();
339
340 outer: for ( int i = 0; i< priorities.length; i++ ) {
341 List<ISigilBundle> providers = findProvidersAtPriority(priorities[i], requirement, ctx);
342
343 if ( !providers.isEmpty() && !ctx.monitor.isCanceled() ) {
344 if ( providers.size() > 1 ) {
345 Collections.sort(providers, new BundleOrderComparator(requirement));
346 }
347
348 for ( ISigilBundle provider : providers ) {
349 // reset validity - if there's another provider it can still be solved
350 ctx.setValid(true);
351 if ( ctx.resolution.addProvider(requirement, provider) ) {
352 if ( ctx.config.isDependents() ) {
353 resolveElement(provider, ctx);
354 }
355
356 if ( ctx.isValid() ) {
357 break outer;
358 }
359 else {
360 ctx.resolution.removeProvider(requirement, provider);
361 }
362 }
363 else {
364 break outer;
365 }
366 }
367 }
368 }
369 }
370 finally {
371 ctx.endRequirement(requirement);
372 }
373 }
374 }
375
376 private List<ISigilBundle> findProvidersAtPriority(int i, IModelElement requirement, ResolutionContext ctx) throws ResolutionException {
377 ArrayList<ISigilBundle> providers = new ArrayList<ISigilBundle>();
378
379 for (IBundleRepository rep : repositoryManager.getRepositories(i)) {
380 if ( ctx.monitor.isCanceled() ) {
381 break;
382 }
383 providers.addAll( findProviders( requirement, ctx.config, rep ) );
384 }
385
386 return providers;
387 }
388
389 private Collection<ISigilBundle> findProviders(IModelElement requirement, ResolutionConfig config, IBundleRepository rep) throws ResolutionException {
390 ArrayList<ISigilBundle> found = new ArrayList<ISigilBundle>();
391
392 if ( requirement instanceof IPackageImport ) {
393 IPackageImport pi = (IPackageImport) requirement;
394 found.addAll( rep.findAllProviders( pi, config.getOptions() ) );
395 }
396 else if ( requirement instanceof IRequiredBundle ) {
397 IRequiredBundle rb = (IRequiredBundle) requirement;
398 found.addAll( rep.findAllProviders( rb, config.getOptions() ) );
399 }
400 else if ( requirement instanceof ILibraryImport ) {
401 ILibrary lib = repositoryManager.resolveLibrary((ILibraryImport) requirement);
402 if (lib != null) {
403 found.addAll( rep.findProviders(lib, config.getOptions()) );
404 }
405 }
406 else {
407 // shouldn't get here - developer error if do
408 // use isRequirement before getting anywhere near this logic...
409 throw new IllegalStateException( "Invalid requirement type " + requirement );
410 }
411
412 return found;
413 }
414
415
416 private boolean isOptional(IModelElement element) {
417 if ( element instanceof IPackageImport ) {
418 return ((IPackageImport) element).isOptional();
419 }
420 else if ( element instanceof IRequiredBundle ) {
421 return ((IRequiredBundle) element).isOptional();
422 }
423 else if ( element instanceof ILibraryImport ) {
424 ILibrary lib = repositoryManager.resolveLibrary((ILibraryImport) element);
425 for ( IPackageImport pi : lib.getImports() ) {
426 if ( !isOptional(pi) ) {
427 return false;
428 }
429 }
430 return true;
431 }
432 else {
433 // should never get this due to isRequirement test prior to calling this
434 // developer error if found
435 throw new IllegalStateException( "Invalid optional element test for " + element);
436 }
437 }
438
439 private boolean isRequirement(IModelElement element) {
440 return element instanceof IPackageImport || element instanceof IRequiredBundle || element instanceof ILibraryImport;
441 }
442
443
444}