1 /*
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 package org.apache.felix.resolver;
20 
21 import java.security.*;
22 import java.util.*;
23 import java.util.Map.Entry;
24 import java.util.concurrent.*;
25 import java.util.concurrent.atomic.AtomicReference;
26 
27 import org.apache.felix.resolver.reason.ReasonException;
28 import org.apache.felix.resolver.util.ArrayMap;
29 import org.apache.felix.resolver.util.CandidateSelector;
30 import org.apache.felix.resolver.util.OpenHashMap;
31 import org.osgi.framework.namespace.*;
32 import org.osgi.resource.*;
33 import org.osgi.service.resolver.*;
34 
35 public class ResolverImpl implements Resolver
36 {
37     private final AccessControlContext m_acc =
38         System.getSecurityManager() != null ?
39             AccessController.getContext() :
40             null;
41 
42     private final Logger m_logger;
43 
44     private final int m_parallelism;
45 
46     private final Executor m_executor;
47 
48     enum PermutationType {
49         USES,
50         IMPORT,
51         SUBSTITUTE
52     }
53 
54     // Note this class is not thread safe.
55     // Only use in the context of a single thread.
56     static class ResolveSession implements Runnable
57     {
58         // Holds the resolve context for this session
59         private final ResolveContext m_resolveContext;
60         private final Collection<Resource> m_mandatoryResources;
61         private final Collection<Resource> m_optionalResources;
62         private final Resource m_dynamicHost;
63         private final Requirement m_dynamicReq;
64         private final List<Capability> m_dynamicCandidates;
65         // keeps track of valid related resources that we have seen.
66         // a null value or TRUE indicate it is valid
67         private Map<Resource, Boolean> m_validRelatedResources = new HashMap<Resource, Boolean>(0);
68         // keeps track of related resources for each resource
69         private Map<Resource, Collection<Resource>> m_relatedResources = new HashMap<Resource, Collection<Resource>>(0);
70         // Holds candidate permutations based on permutating "uses" chains.
71         // These permutations are given higher priority.
72         private final List<Candidates> m_usesPermutations = new LinkedList<Candidates>();
73         private int m_usesIndex = 0;
74         // Holds candidate permutations based on permutating requirement candidates.
75         // These permutations represent backtracking on previous decisions.
76         private final List<Candidates> m_importPermutations = new LinkedList<Candidates>();
77         private int m_importIndex = 0;
78         // Holds candidate permutations based on substituted packages
79         private final List<Candidates> m_substPermutations = new LinkedList<Candidates>();
80         private int m_substituteIndex = 0;
81         // Holds candidate permutations based on removing candidates that satisfy
82         // multiple cardinality requirements.
83         // This permutation represents a permutation that is consistent because we have
84         // removed the offending capabilities
85         private Candidates m_multipleCardCandidates = null;
86         // The delta is used to detect that we have already processed this particular permutation
87         private final Set<Object> m_processedDeltas = new HashSet<Object>();
88         private final Executor m_executor;
89         private final Set<Requirement> m_mutated = new HashSet<Requirement>();
90         private final Set<Requirement> m_sub_mutated = new HashSet<Requirement>();
91         private final ConcurrentMap<String, List<String>> m_usesCache = new ConcurrentHashMap<String, List<String>>();
92         private ResolutionError m_currentError;
93         volatile private CancellationException m_isCancelled = null;
94 
createSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates)95         static ResolveSession createSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates)
96         {
97             ResolveSession session = new ResolveSession(resolveContext, executor, dynamicHost, dynamicReq, dynamicCandidates);
98             // call onCancel first
99             session.getContext().onCancel(session);
100             // now gather the mandatory and optional resources
101             session.initMandatoryAndOptionalResources();
102             return session;
103         }
104 
ResolveSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates)105         private ResolveSession(ResolveContext resolveContext, Executor executor, Resource dynamicHost, Requirement dynamicReq, List<Capability> dynamicCandidates)
106         {
107             m_resolveContext = resolveContext;
108             m_executor = executor;
109             m_dynamicHost = dynamicHost;
110             m_dynamicReq = dynamicReq;
111             m_dynamicCandidates = dynamicCandidates;
112             if (m_dynamicHost != null) {
113                 m_mandatoryResources = Collections.singletonList(dynamicHost);
114                 m_optionalResources = Collections.emptyList();
115             } else {
116                 // Do not call resolve context yet, onCancel must be called first
117                 m_mandatoryResources = new ArrayList<Resource>();
118                 m_optionalResources = new ArrayList<Resource>();
119             }
120         }
121 
initMandatoryAndOptionalResources()122         private void initMandatoryAndOptionalResources() {
123             if (!isDynamic()) {
124                 m_mandatoryResources.addAll(getContext().getMandatoryResources());
125                 m_optionalResources.addAll(getContext().getOptionalResources());
126             }
127         }
getMultipleCardCandidates()128         Candidates getMultipleCardCandidates()
129         {
130             return m_multipleCardCandidates;
131         }
132 
getContext()133         ResolveContext getContext()
134         {
135             return m_resolveContext;
136         }
137 
getUsesCache()138         ConcurrentMap<String, List<String>> getUsesCache() {
139             return m_usesCache;
140         }
141 
permutateIfNeeded(PermutationType type, Requirement req, Candidates permutation)142         void permutateIfNeeded(PermutationType type, Requirement req, Candidates permutation) {
143             List<Capability> candidates = permutation.getCandidates(req);
144             if ((candidates != null) && (candidates.size() > 1))
145             {
146                 if ((type == PermutationType.SUBSTITUTE)) {
147                     if (!m_sub_mutated.add(req)) {
148                         return;
149                     }
150                 } else if (!m_mutated.add(req)) {
151                     return;
152                 }
153                 // If we haven't already permutated the existing
154                 // import, do so now.
155                 addPermutation(type, permutation.permutate(req));
156             }
157         }
158 
clearMutateIndexes()159         private void clearMutateIndexes() {
160             m_usesIndex = 0;
161             m_importIndex = 0;
162             m_substituteIndex = 0;
163             m_mutated.clear();
164             // NOTE: m_sub_mutated is never cleared.
165             // It is unclear if even more permutations based on a substitutions will ever help.
166             // Being safe and reducing extra permutations until we get a scenario that proves
167             // more permutations would really help.
168         }
169 
addPermutation(PermutationType type, Candidates permutation)170         void addPermutation(PermutationType type, Candidates permutation) {
171             if (permutation != null)
172             {
173                 List<Candidates> typeToAddTo = null;
174                 try {
175                     switch (type) {
176                         case USES :
177                             typeToAddTo = m_usesPermutations;
178                             m_usesPermutations.add(m_usesIndex++, permutation);
179                             break;
180                         case IMPORT :
181                             typeToAddTo = m_importPermutations;
182                             m_importPermutations.add(m_importIndex++, permutation);
183                             break;
184                         case SUBSTITUTE :
185                             typeToAddTo = m_substPermutations;
186                             m_substPermutations.add(m_substituteIndex++, permutation);
187                             break;
188                         default :
189                             throw new IllegalArgumentException("Unknown permitation type: " + type);
190                     }
191                 } catch (IndexOutOfBoundsException e) {
192                     // just a safeguard, this really should never happen
193                     typeToAddTo.add(permutation);
194                 }
195             }
196         }
197 
getNextPermutation()198         Candidates getNextPermutation() {
199             Candidates next = null;
200             do {
201                 if (!m_usesPermutations.isEmpty())
202                 {
203                     next = m_usesPermutations.remove(0);
204                 }
205                 else if (!m_importPermutations.isEmpty())
206                 {
207                     next = m_importPermutations.remove(0);
208                 }
209                 else if (!m_substPermutations.isEmpty())
210                 {
211                     next = m_substPermutations.remove(0);
212                 }
213                 else {
214                     return null;
215                 }
216             }
217             while(!m_processedDeltas.add(next.getDelta()));
218             // Null out each time a new permutation is attempted.
219             // We only use this to store a valid permutation which is a
220             // delta of the current permutation.
221             m_multipleCardCandidates = null;
222             // clear mutateIndexes also so we insert new permutations
223             // based of this permutation as a higher priority
224             clearMutateIndexes();
225             return next;
226         }
227 
clearPermutations()228         void clearPermutations() {
229             m_usesPermutations.clear();
230             m_importPermutations.clear();
231             m_substPermutations.clear();
232             m_multipleCardCandidates = null;
233             m_processedDeltas.clear();
234             m_currentError = null;
235         }
236 
checkMultiple( UsedBlames usedBlames, Blame usedBlame, Candidates permutation)237         boolean checkMultiple(
238                 UsedBlames usedBlames,
239                 Blame usedBlame,
240                 Candidates permutation)
241         {
242             // Check the root requirement to see if it is a multiple cardinality
243             // requirement.
244             CandidateSelector candidates = null;
245             Requirement req = usedBlame.m_reqs.get(0);
246             if (Util.isMultiple(req))
247             {
248                 // Create a copy of the current permutation so we can remove the
249                 // candidates causing the blame.
250                 if (m_multipleCardCandidates == null)
251                 {
252                     m_multipleCardCandidates = permutation.copy();
253                 }
254                 // Get the current candidate list and remove all the offending root
255                 // cause candidates from a copy of the current permutation.
256                 candidates = m_multipleCardCandidates.clearMultipleCardinalityCandidates(req, usedBlames.getRootCauses(req));
257             }
258             // We only are successful if there is at least one candidate left
259             // for the requirement
260             return (candidates != null) && !candidates.isEmpty();
261         }
262 
getPermutationCount()263         long getPermutationCount() {
264             return m_usesPermutations.size() + m_importPermutations.size() + m_substPermutations.size();
265         }
266 
getExecutor()267         Executor getExecutor() {
268             return m_executor;
269         }
270 
getCurrentError()271         ResolutionError getCurrentError() {
272             return m_currentError;
273         }
274 
setCurrentError(ResolutionError error)275         void setCurrentError(ResolutionError error) {
276             this.m_currentError = error;
277         }
278 
isDynamic()279         boolean isDynamic() {
280             return m_dynamicHost != null;
281         }
282 
getMandatoryResources()283         Collection<Resource> getMandatoryResources() {
284             return m_mandatoryResources;
285         }
286 
getOptionalResources()287         Collection<Resource> getOptionalResources() {
288             return m_optionalResources;
289         }
290 
getDynamicHost()291         Resource getDynamicHost() {
292             return m_dynamicHost;
293         }
294 
getDynamicRequirement()295         Requirement getDynamicRequirement() {
296             return m_dynamicReq;
297         }
298 
getDynamicCandidates()299         List<Capability> getDynamicCandidates() {
300             return m_dynamicCandidates;
301         }
302 
isValidRelatedResource(Resource resource)303         public boolean isValidRelatedResource(Resource resource) {
304             Boolean valid = m_validRelatedResources.get(resource);
305             if (valid == null)
306             {
307                 // Mark this resource as a valid related resource
308                 m_validRelatedResources.put(resource, Boolean.TRUE);
309                 valid = Boolean.TRUE;
310             }
311             return valid;
312         }
313 
invalidateRelatedResource(Resource faultyResource)314         public boolean invalidateRelatedResource(Resource faultyResource) {
315             Boolean valid = m_validRelatedResources.get(faultyResource);
316             if (valid != null && valid)
317             {
318                 // This was related resource.
319                 // Invalidate it and try again.
320                 m_validRelatedResources.put(faultyResource, Boolean.FALSE);
321                 return true;
322             }
323             return false;
324         }
325 
getRelatedResources(Resource resource)326         public Collection<Resource> getRelatedResources(Resource resource) {
327             Collection<Resource> related =  m_relatedResources.get(resource);
328             return related == null ? Collections.<Resource> emptyList() : related;
329         }
330 
setRelatedResources(Resource resource, Collection<Resource> related)331         public void setRelatedResources(Resource resource, Collection<Resource> related) {
332             m_relatedResources.put(resource, related);
333         }
334 
335         @Override
run()336         public void run() {
337             m_isCancelled = new CancellationException();
338         }
339 
isCancelled()340         boolean isCancelled() {
341             return m_isCancelled != null;
342         }
343 
checkForCancel()344         void checkForCancel() throws ResolutionException {
345             if (isCancelled()) {
346                 throw new ResolutionException("Resolver operation has been cancelled.", m_isCancelled, null);
347             }
348         }
349     }
350 
ResolverImpl(Logger logger)351     public ResolverImpl(Logger logger)
352     {
353         this(logger, Runtime.getRuntime().availableProcessors());
354     }
355 
ResolverImpl(Logger logger, int parallelism)356     public ResolverImpl(Logger logger, int parallelism)
357     {
358         this.m_logger = logger;
359         this.m_parallelism = parallelism;
360         this.m_executor = null;
361     }
362 
ResolverImpl(Logger logger, Executor executor)363     public ResolverImpl(Logger logger, Executor executor)
364     {
365         this.m_logger = logger;
366         this.m_parallelism = -1;
367         this.m_executor = executor;
368     }
369 
resolve(ResolveContext rc)370     public Map<Resource, List<Wire>> resolve(ResolveContext rc) throws ResolutionException
371     {
372         if (m_executor != null)
373         {
374             return resolve(rc, m_executor);
375         }
376         else if (m_parallelism > 1)
377         {
378             final ExecutorService executor =
379                 System.getSecurityManager() != null ?
380                     AccessController.doPrivileged(
381                         new PrivilegedAction<ExecutorService>()
382                         {
383                             public ExecutorService run()
384                             {
385                                 return Executors.newFixedThreadPool(m_parallelism);
386                             }
387                         }, m_acc)
388                 :
389                     Executors.newFixedThreadPool(m_parallelism);
390             try
391             {
392                 return resolve(rc, executor);
393             }
394             finally
395             {
396                 if (System.getSecurityManager() != null)
397                 {
398                     AccessController.doPrivileged(new PrivilegedAction<Void>(){
399                         public Void run() {
400                             executor.shutdownNow();
401                             return null;
402                         }
403                     }, m_acc);
404                 }
405                 else
406                 {
407                     executor.shutdownNow();
408                 }
409             }
410         }
411         else
412         {
413             return resolve(rc, new DumbExecutor());
414         }
415     }
416 
resolve(ResolveContext rc, Executor executor)417     public Map<Resource, List<Wire>> resolve(ResolveContext rc, Executor executor) throws ResolutionException
418     {
419         ResolveSession session = ResolveSession.createSession(rc, executor, null, null, null);
420         return doResolve(session);
421     }
422 
doResolve(ResolveSession session)423     private Map<Resource, List<Wire>> doResolve(ResolveSession session) throws ResolutionException {
424         Map<Resource, List<Wire>> wireMap = new HashMap<Resource, List<Wire>>();
425         boolean retry;
426         do
427         {
428             retry = false;
429             try
430             {
431                 getInitialCandidates(session);
432                 if (session.getCurrentError() != null) {
433                     throw session.getCurrentError().toException();
434                 }
435 
436                 Map<Resource, ResolutionError> faultyResources = new HashMap<Resource, ResolutionError>();
437                 Candidates allCandidates = findValidCandidates(session, faultyResources);
438                 session.checkForCancel();
439 
440                 // If there is a resolve exception, then determine if an
441                 // optionally resolved resource is to blame (typically a fragment).
442                 // If so, then remove the optionally resolved resolved and try
443                 // again; otherwise, m_currentError the resolve exception.
444                 if (session.getCurrentError() != null)
445                 {
446                     Set<Resource> resourceKeys = faultyResources.keySet();
447                     retry = (session.getOptionalResources().removeAll(resourceKeys));
448                     for (Resource faultyResource : resourceKeys)
449                     {
450                         if (session.invalidateRelatedResource(faultyResource))
451                         {
452                             retry = true;
453                         }
454                     }
455                     // log all the resolution exceptions for the uses constraint violations
456                     for (Map.Entry<Resource, ResolutionError> usesError : faultyResources.entrySet())
457                     {
458                         m_logger.logUsesConstraintViolation(usesError.getKey(), usesError.getValue());
459                     }
460                     if (!retry)
461                     {
462                         throw session.getCurrentError().toException();
463                     }
464                 }
465                 // If there is no exception to m_currentError, then this was a clean
466                 // resolve, so populate the wire map.
467                 else
468                 {
469                     if (session.getMultipleCardCandidates() != null)
470                     {
471                         // Candidates for multiple cardinality requirements were
472                         // removed in order to provide a consistent class space.
473                         // Use the consistent permutation
474                         allCandidates = session.getMultipleCardCandidates();
475                     }
476                     if (session.isDynamic() )
477                     {
478                         wireMap = populateDynamicWireMap(session,
479                             wireMap, allCandidates);
480                     }
481                     else
482                     {
483                         for (Resource resource : allCandidates.getRootHosts().keySet())
484                         {
485                             if (allCandidates.isPopulated(resource))
486                             {
487                                 wireMap =
488                                     populateWireMap(
489                                         session, allCandidates.getWrappedHost(resource),
490                                         wireMap, allCandidates);
491                             }
492                         }
493                     }
494                 }
495             }
496             finally
497             {
498                 // Always clear the state.
499                 session.clearPermutations();
500             }
501         }
502         while (retry);
503 
504         return wireMap;
505     }
506 
getInitialCandidates(ResolveSession session)507     private void getInitialCandidates(ResolveSession session) {
508         // Create object to hold all candidates.
509         Candidates initialCandidates;
510         if (session.isDynamic()) {
511             // Create all candidates pre-populated with the single candidate set
512             // for the resolving dynamic import of the host.
513             initialCandidates = new Candidates(session);
514             ResolutionError prepareError = initialCandidates.populateDynamic();
515             if (prepareError != null) {
516                 session.setCurrentError(prepareError);
517                 return;
518             }
519         } else {
520             List<Resource> toPopulate = new ArrayList<Resource>();
521 
522             // Populate mandatory resources; since these are mandatory
523             // resources, failure throws a resolve exception.
524             for (Resource resource : session.getMandatoryResources())
525             {
526                 if (Util.isFragment(resource) || (session.getContext().getWirings().get(resource) == null))
527                 {
528                     toPopulate.add(resource);
529                 }
530             }
531             // Populate optional resources; since these are optional
532             // resources, failure does not throw a resolve exception.
533             for (Resource resource : session.getOptionalResources())
534             {
535                 if (Util.isFragment(resource) || (session.getContext().getWirings().get(resource) == null))
536                 {
537                     toPopulate.add(resource);
538                 }
539             }
540 
541             initialCandidates = new Candidates(session);
542             initialCandidates.populate(toPopulate);
543         }
544 
545         // Merge any fragments into hosts.
546         ResolutionError prepareError = initialCandidates.prepare();
547         if (prepareError != null)
548         {
549             session.setCurrentError(prepareError);
550         }
551         else
552         {
553             // Record the initial candidate permutation.
554             session.addPermutation(PermutationType.USES, initialCandidates);
555         }
556     }
557 
findValidCandidates(ResolveSession session, Map<Resource, ResolutionError> faultyResources)558     private Candidates findValidCandidates(ResolveSession session, Map<Resource, ResolutionError> faultyResources) {
559         Candidates allCandidates = null;
560         boolean foundFaultyResources = false;
561         do
562         {
563             allCandidates = session.getNextPermutation();
564             if (allCandidates == null)
565             {
566                 break;
567             }
568 
569 //allCandidates.dump();
570 
571             Map<Resource, ResolutionError> currentFaultyResources = new HashMap<Resource, ResolutionError>();
572 
573             session.setCurrentError(
574                     checkConsistency(
575                             session,
576                             allCandidates,
577                             currentFaultyResources
578                     )
579             );
580 
581             if (!currentFaultyResources.isEmpty())
582             {
583                 if (!foundFaultyResources)
584                 {
585                     foundFaultyResources = true;
586                     faultyResources.putAll(currentFaultyResources);
587                 }
588                 else if (faultyResources.size() > currentFaultyResources.size())
589                 {
590                     // save the optimal faultyResources which has less
591                     faultyResources.clear();
592                     faultyResources.putAll(currentFaultyResources);
593                 }
594             }
595         }
596         while (!session.isCancelled() && session.getCurrentError() != null);
597 
598         return allCandidates;
599     }
600 
checkConsistency( ResolveSession session, Candidates allCandidates, Map<Resource, ResolutionError> currentFaultyResources)601     private ResolutionError checkConsistency(
602         ResolveSession session,
603         Candidates allCandidates,
604         Map<Resource, ResolutionError> currentFaultyResources)
605     {
606         ResolutionError rethrow = allCandidates.checkSubstitutes();
607         if (rethrow != null)
608         {
609             return rethrow;
610         }
611         Map<Resource, Resource> allhosts = allCandidates.getRootHosts();
612         // Calculate package spaces
613         Map<Resource, Packages> resourcePkgMap =
614             calculatePackageSpaces(session, allCandidates, allhosts.values());
615         ResolutionError error = null;
616         // Check package consistency
617         Map<Resource, Object> resultCache =
618                 new OpenHashMap<Resource, Object>(resourcePkgMap.size());
619         for (Entry<Resource, Resource> entry : allhosts.entrySet())
620         {
621             rethrow = checkPackageSpaceConsistency(
622                     session, entry.getValue(),
623                     allCandidates, session.isDynamic(), resourcePkgMap, resultCache);
624             if (session.isCancelled()) {
625                 return null;
626             }
627             if (rethrow != null)
628             {
629                 Resource faultyResource = entry.getKey();
630                 // check that the faulty requirement is not from a fragment
631                 for (Requirement faultyReq : rethrow.getUnresolvedRequirements())
632                 {
633                     if (faultyReq instanceof WrappedRequirement)
634                     {
635                         faultyResource =
636                                 ((WrappedRequirement) faultyReq)
637                                         .getDeclaredRequirement().getResource();
638                         break;
639                     }
640                 }
641                 currentFaultyResources.put(faultyResource, rethrow);
642                 error = rethrow;
643             }
644         }
645         return error;
646     }
647 
resolveDynamic(ResolveContext context, Wiring hostWiring, Requirement dynamicRequirement)648     public Map<Resource,List<Wire>> resolveDynamic(ResolveContext context,
649             Wiring hostWiring, Requirement dynamicRequirement)
650             throws ResolutionException
651     {
652         Resource host = hostWiring.getResource();
653         List<Capability> matches = context.findProviders(dynamicRequirement);
654         // We can only create a dynamic import if the following
655         // conditions are met:
656         // 1. The package in question is not already imported.
657         // 2. The package in question is not accessible via require-bundle.
658         // 3. The package in question is not exported by the resource.
659         if (!matches.isEmpty())
660         {
661             // Make sure all matching candidates are packages.
662             for (Capability cap : matches)
663             {
664                 if (!cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
665                 {
666                     throw new IllegalArgumentException(
667                         "Matching candidate does not provide a package name.");
668                 }
669             }
670             ResolveSession session = ResolveSession.createSession(context,  new DumbExecutor(), host, dynamicRequirement, matches);
671             return doResolve(session);
672         }
673 
674         throw new Candidates.MissingRequirementError(dynamicRequirement).toException();
675     }
676 
getWireCandidates(ResolveSession session, Candidates allCandidates, Resource resource)677     private static List<WireCandidate> getWireCandidates(ResolveSession session, Candidates allCandidates, Resource resource)
678     {
679         // Create a list for requirement and proposed candidate
680         // capability or actual capability if resource is resolved or not.
681         List<WireCandidate> wireCandidates = new ArrayList<WireCandidate>(256);
682         Wiring wiring = session.getContext().getWirings().get(resource);
683         if (wiring != null)
684         {
685             // Use wires to get actual requirements and satisfying capabilities.
686             for (Wire wire : wiring.getRequiredResourceWires(null))
687             {
688                 // Wrap the requirement as a hosted requirement if it comes
689                 // from a fragment, since we will need to know the host. We
690                 // also need to wrap if the requirement is a dynamic import,
691                 // since that requirement will be shared with any other
692                 // matching dynamic imports.
693                 Requirement r = wire.getRequirement();
694                 if (!r.getResource().equals(wire.getRequirer())
695                     || Util.isDynamic(r))
696                 {
697                     r = new WrappedRequirement(wire.getRequirer(), r);
698                 }
699                 // Wrap the capability as a hosted capability if it comes
700                 // from a fragment, since we will need to know the host.
701                 Capability c = wire.getCapability();
702                 if (!c.getResource().equals(wire.getProvider()))
703                 {
704                     c = new WrappedCapability(wire.getProvider(), c);
705                 }
706                 wireCandidates.add(new WireCandidate(r, c));
707             }
708 
709             // Since the resource is resolved, it could be dynamically importing,
710             // so check to see if there are candidates for any of its dynamic
711             // imports.
712             //
713             // NOTE: If the resource is dynamically importing, the fact that
714             // the dynamic import is added here last to the
715             // list is used later when checking to see if the package being
716             // dynamically imported shadows an existing provider.
717             Requirement dynamicReq = session.getDynamicRequirement();
718             if (dynamicReq != null && resource.equals(session.getDynamicHost()))
719             {
720                 // Grab first (i.e., highest priority) candidate.
721                 Capability cap = allCandidates.getFirstCandidate(dynamicReq);
722                 wireCandidates.add(new WireCandidate(dynamicReq, cap));
723             }
724         }
725         else
726         {
727             for (Requirement req : resource.getRequirements(null))
728             {
729                 if (!Util.isDynamic(req))
730                 {
731                     // Get the candidates for the current requirement.
732                     List<Capability> candCaps = allCandidates.getCandidates(req);
733                     // Optional requirements may not have any candidates.
734                     if (candCaps == null)
735                     {
736                         continue;
737                     }
738 
739                     // For multiple cardinality requirements, we need to grab
740                     // all candidates.
741                     if (Util.isMultiple(req))
742                     {
743                         // Use the same requirement, but list each capability separately
744                         for (Capability cap : candCaps)
745                         {
746                             wireCandidates.add(new WireCandidate(req, cap));
747                         }
748                     }
749                     // Grab first (i.e., highest priority) candidate
750                     else
751                     {
752                         Capability cap = candCaps.get(0);
753                         wireCandidates.add(new WireCandidate(req, cap));
754                     }
755                 }
756             }
757         }
758         return wireCandidates;
759     }
760 
getPackages( ResolveSession session, Candidates allCandidates, Map<Resource, List<WireCandidate>> allWireCandidates, Map<Resource, Packages> allPackages, Resource resource, Packages resourcePkgs)761     private static Packages getPackages(
762             ResolveSession session,
763             Candidates allCandidates,
764             Map<Resource, List<WireCandidate>> allWireCandidates,
765             Map<Resource, Packages> allPackages,
766             Resource resource,
767             Packages resourcePkgs)
768     {
769         // First, all all exported packages
770         // This has been done previously
771 
772         // Second, add all imported packages to the target resource's package space.
773         for (WireCandidate wire : allWireCandidates.get(resource))
774         {
775             // If this resource is dynamically importing, then the last requirement
776             // is the dynamic import being resolved, since it is added last to the
777             // parallel lists above. For the dynamically imported package, make
778             // sure that the resource doesn't already have a provider for that
779             // package, which would be illegal and shouldn't be allowed.
780             if (Util.isDynamic(wire.requirement))
781             {
782                 String pkgName = (String) wire.capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
783                 if (resourcePkgs.m_exportedPkgs.containsKey(pkgName)
784                     || resourcePkgs.m_importedPkgs.containsKey(pkgName)
785                     || resourcePkgs.m_requiredPkgs.containsKey(pkgName))
786                 {
787                     throw new IllegalArgumentException(
788                         "Resource "
789                         + resource
790                         + " cannot dynamically import package '"
791                         + pkgName
792                         + "' since it already has access to it.");
793                 }
794             }
795 
796             mergeCandidatePackages(
797                 session,
798                 allPackages,
799                 allCandidates,
800                 resourcePkgs,
801                 wire.requirement,
802                 wire.capability,
803                 new HashSet<Capability>(),
804                 new HashSet<Resource>());
805         }
806 
807         return resourcePkgs;
808     }
809 
computeUses( ResolveSession session, Map<Resource, List<WireCandidate>> allWireCandidates, Map<Resource, Packages> resourcePkgMap, Resource resource)810     private void computeUses(
811             ResolveSession session,
812             Map<Resource, List<WireCandidate>> allWireCandidates,
813             Map<Resource, Packages> resourcePkgMap,
814             Resource resource)
815     {
816         List<WireCandidate> wireCandidates = allWireCandidates.get(resource);
817         Packages resourcePkgs = resourcePkgMap.get(resource);
818         // Fourth, if the target resource is unresolved or is dynamically importing,
819         // then add all the uses constraints implied by its imported and required
820         // packages to its package space.
821         // NOTE: We do not need to do this for resolved resources because their
822         // package space is consistent by definition and these uses constraints
823         // are only needed to verify the consistency of a resolving resource. The
824         // only exception is if a resolved resource is dynamically importing, then
825         // we need to calculate its uses constraints again to make sure the new
826         // import is consistent with the existing package space.
827         Wiring wiring = session.getContext().getWirings().get(resource);
828         Set<Capability> usesCycleMap = new HashSet<Capability>();
829 
830         int size = wireCandidates.size();
831         boolean isDynamicImporting = size > 0
832                 && Util.isDynamic(wireCandidates.get(size - 1).requirement);
833 
834         if ((wiring == null) || isDynamicImporting)
835         {
836             // Merge uses constraints from required capabilities.
837             for (WireCandidate w : wireCandidates)
838             {
839                 Requirement req = w.requirement;
840                 Capability cap = w.capability;
841                 // Ignore bundle/package requirements, since they are
842                 // considered below.
843                 if (!req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)
844                     && !req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
845                 {
846                     List<Requirement> blameReqs =
847                             Collections.singletonList(req);
848 
849                     mergeUses(
850                         session,
851                         resource,
852                         resourcePkgs,
853                         cap,
854                         blameReqs,
855                         cap,
856                         resourcePkgMap,
857                         usesCycleMap);
858                 }
859             }
860             // Merge uses constraints from imported packages.
861             for (List<Blame> blames : resourcePkgs.m_importedPkgs.values())
862             {
863                 for (Blame blame : blames)
864                 {
865                     List<Requirement> blameReqs =
866                         Collections.singletonList(blame.m_reqs.get(0));
867 
868                     mergeUses(
869                         session,
870                         resource,
871                         resourcePkgs,
872                         blame.m_cap,
873                         blameReqs,
874                         null,
875                         resourcePkgMap,
876                         usesCycleMap);
877                 }
878             }
879             // Merge uses constraints from required bundles.
880             for (List<Blame> blames : resourcePkgs.m_requiredPkgs.values())
881             {
882                 for (Blame blame : blames)
883                 {
884                     List<Requirement> blameReqs =
885                         Collections.singletonList(blame.m_reqs.get(0));
886 
887                     mergeUses(
888                         session,
889                         resource,
890                         resourcePkgs,
891                         blame.m_cap,
892                         blameReqs,
893                         null,
894                         resourcePkgMap,
895                         usesCycleMap);
896                 }
897             }
898         }
899     }
900 
mergeCandidatePackages( ResolveSession session, Map<Resource, Packages> resourcePkgMap, Candidates allCandidates, Packages packages, Requirement currentReq, Capability candCap, Set<Capability> capabilityCycles, Set<Resource> visitedRequiredBundles)901     private static void mergeCandidatePackages(
902             ResolveSession session,
903             Map<Resource, Packages> resourcePkgMap,
904             Candidates allCandidates,
905             Packages packages,
906             Requirement currentReq,
907             Capability candCap,
908             Set<Capability> capabilityCycles,
909             Set<Resource> visitedRequiredBundles)
910     {
911         if (!capabilityCycles.add(candCap))
912         {
913             return;
914         }
915 
916         if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
917         {
918             mergeCandidatePackage(
919                 packages.m_importedPkgs,
920                 currentReq, candCap);
921         }
922         else if (candCap.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
923         {
924             // Get the candidate's package space to determine which packages
925             // will be visible to the current resource.
926             if (visitedRequiredBundles.add(candCap.getResource()))
927             {
928                 // We have to merge all exported packages from the candidate,
929                 // since the current resource requires it.
930                 for (Blame blame : resourcePkgMap.get(candCap.getResource()).m_exportedPkgs.values())
931                 {
932                     mergeCandidatePackage(
933                         packages.m_requiredPkgs,
934                         currentReq,
935                         blame.m_cap);
936                 }
937                 // now merge in substitutes
938                 for (Blame blame : resourcePkgMap.get(
939                     candCap.getResource()).m_substitePkgs.values())
940                 {
941                     mergeCandidatePackage(packages.m_requiredPkgs, currentReq,
942                         blame.m_cap);
943                 }
944             }
945 
946             // If the candidate requires any other bundles with reexport visibility,
947             // then we also need to merge their packages too.
948             Wiring candWiring = session.getContext().getWirings().get(candCap.getResource());
949             if (candWiring != null)
950             {
951                 for (Wire w : candWiring.getRequiredResourceWires(null))
952                 {
953                     if (w.getRequirement().getNamespace()
954                         .equals(BundleNamespace.BUNDLE_NAMESPACE))
955                     {
956                         if (Util.isReexport(w.getRequirement()))
957                         {
958                             mergeCandidatePackages(
959                                 session,
960                                 resourcePkgMap,
961                                 allCandidates,
962                                 packages,
963                                 currentReq,
964                                 w.getCapability(),
965                                 capabilityCycles,
966                                 visitedRequiredBundles);
967                         }
968                     }
969                 }
970             }
971             else
972             {
973                 for (Requirement req : candCap.getResource().getRequirements(null))
974                 {
975                     if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
976                     {
977                         if (Util.isReexport(req))
978                         {
979                             Capability cap = allCandidates.getFirstCandidate(req);
980                             if (cap != null)
981                             {
982                                 mergeCandidatePackages(
983                                         session,
984                                         resourcePkgMap,
985                                         allCandidates,
986                                         packages,
987                                         currentReq,
988                                         cap,
989                                         capabilityCycles,
990                                         visitedRequiredBundles);
991                             }
992                         }
993                     }
994                 }
995             }
996         }
997     }
998 
mergeCandidatePackage( OpenHashMap<String, List<Blame>> packages, Requirement currentReq, Capability candCap)999     private static void mergeCandidatePackage(
1000         OpenHashMap<String, List<Blame>> packages,
1001         Requirement currentReq, Capability candCap)
1002     {
1003         if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
1004         {
1005             // Merge the candidate capability into the resource's package space
1006             // for imported or required packages, appropriately.
1007 
1008             String pkgName = (String) candCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
1009 
1010             List<Requirement> blameReqs = Collections.singletonList(currentReq);
1011 
1012             List<Blame> blames = packages.getOrCompute(pkgName);
1013             blames.add(new Blame(candCap, blameReqs));
1014 
1015 //dumpResourcePkgs(current, currentPkgs);
1016         }
1017     }
1018 
mergeUses( ResolveSession session, Resource current, Packages currentPkgs, Capability mergeCap, List<Requirement> blameReqs, Capability matchingCap, Map<Resource, Packages> resourcePkgMap, Set<Capability> cycleMap)1019     private void mergeUses(
1020         ResolveSession session, Resource current, Packages currentPkgs,
1021         Capability mergeCap, List<Requirement> blameReqs, Capability matchingCap,
1022         Map<Resource, Packages> resourcePkgMap,
1023         Set<Capability> cycleMap)
1024     {
1025         // If there are no uses, then just return.
1026         // If the candidate resource is the same as the current resource,
1027         // then we don't need to verify and merge the uses constraints
1028         // since this will happen as we build up the package space.
1029         if (current.equals(mergeCap.getResource()))
1030         {
1031             return;
1032         }
1033 
1034         // Check for cycles.
1035         if (!cycleMap.add(mergeCap))
1036         {
1037             return;
1038         }
1039 
1040         for (Capability candSourceCap : getPackageSources(mergeCap, resourcePkgMap))
1041         {
1042             List<String> uses;
1043 // TODO: RFC-112 - Need impl-specific type
1044 //            if (candSourceCap instanceof FelixCapability)
1045 //            {
1046 //                uses = ((FelixCapability) candSourceCap).getUses();
1047 //            }
1048 //            else
1049             {
1050                 String s = candSourceCap.getDirectives().get(Namespace.CAPABILITY_USES_DIRECTIVE);
1051                 if (s != null && s.length() > 0)
1052                 {
1053                     // Parse these uses directive.
1054                     uses = session.getUsesCache().get(s);
1055                     if (uses == null)
1056                     {
1057                         uses = parseUses(s);
1058                         session.getUsesCache().put(s, uses);
1059                     }
1060                 }
1061                 else
1062                 {
1063                     continue;
1064                 }
1065             }
1066             Packages candSourcePkgs = resourcePkgMap.get(candSourceCap.getResource());
1067             for (String usedPkgName : uses)
1068             {
1069                 List<Blame> candSourceBlames;
1070                 // Check to see if the used package is exported.
1071                 Blame candExportedBlame = candSourcePkgs.m_exportedPkgs.get(usedPkgName);
1072                 if (candExportedBlame != null)
1073                 {
1074                     candSourceBlames = Collections.singletonList(candExportedBlame);
1075                 }
1076                 else
1077                 {
1078                     // If the used package is not exported, check to see if it
1079                     // is required.
1080                     candSourceBlames = candSourcePkgs.m_requiredPkgs.get(usedPkgName);
1081                     // Lastly, if the used package is not required, check to see if it
1082                     // is imported.
1083                     if (candSourceBlames == null)
1084                     {
1085                         candSourceBlames = candSourcePkgs.m_importedPkgs.get(usedPkgName);
1086                     }
1087                 }
1088 
1089                 // If the used package cannot be found, then just ignore it
1090                 // since it has no impact.
1091                 if (candSourceBlames == null)
1092                 {
1093                     continue;
1094                 }
1095 
1096                 ArrayMap<Set<Capability>, UsedBlames> usedPkgBlames = currentPkgs.m_usedPkgs.getOrCompute(usedPkgName);
1097                 List<Blame> newBlames = new ArrayList<Blame>();
1098                 for (Blame blame : candSourceBlames)
1099                 {
1100                     List<Requirement> newBlameReqs;
1101                     if (blame.m_reqs != null)
1102                     {
1103                         newBlameReqs = new ArrayList<Requirement>(blameReqs.size() + 1);
1104                         newBlameReqs.addAll(blameReqs);
1105                         // Only add the last requirement in blame chain because
1106                         // that is the requirement wired to the blamed capability
1107                         newBlameReqs.add(blame.m_reqs.get(blame.m_reqs.size() - 1));
1108                     }
1109                     else
1110                     {
1111                         newBlameReqs = blameReqs;
1112                     }
1113                     newBlames.add(new Blame(blame.m_cap, newBlameReqs));
1114                 }
1115                 addUsedBlames(usedPkgBlames, newBlames, matchingCap, resourcePkgMap);
1116                 for (Blame newBlame : newBlames)
1117                 {
1118                     mergeUses(session, current, currentPkgs, newBlame.m_cap, newBlame.m_reqs, matchingCap,
1119                         resourcePkgMap, cycleMap);
1120                 }
1121             }
1122         }
1123     }
1124 
calculatePackageSpaces( final ResolveSession session, final Candidates allCandidates, Collection<Resource> hosts)1125     private Map<Resource, Packages> calculatePackageSpaces(
1126             final ResolveSession session,
1127             final Candidates allCandidates,
1128             Collection<Resource> hosts)
1129     {
1130         final EnhancedExecutor executor = new EnhancedExecutor(session.getExecutor());
1131 
1132         // Parallel compute wire candidates
1133         final Map<Resource, List<WireCandidate>> allWireCandidates = new ConcurrentHashMap<Resource, List<WireCandidate>>();
1134         {
1135             final ConcurrentMap<Resource, Runnable> tasks = new ConcurrentHashMap<Resource, Runnable>(allCandidates.getNbResources());
1136             class Computer implements Runnable
1137             {
1138                 final Resource resource;
1139                 public Computer(Resource resource)
1140                 {
1141                     this.resource = resource;
1142                 }
1143                 public void run()
1144                 {
1145                     List<WireCandidate> wireCandidates = getWireCandidates(session, allCandidates, resource);
1146                     allWireCandidates.put(resource, wireCandidates);
1147                     for (WireCandidate w : wireCandidates)
1148                     {
1149                         Resource u = w.capability.getResource();
1150                         if (!tasks.containsKey(u))
1151                         {
1152                             Computer c = new Computer(u);
1153                             if (tasks.putIfAbsent(u, c) == null)
1154                             {
1155                                 executor.execute(c);
1156                             }
1157                         }
1158                     }
1159                 }
1160             }
1161             for (Resource resource : hosts)
1162             {
1163                 executor.execute(new Computer(resource));
1164             }
1165             executor.await();
1166         }
1167 
1168         // Parallel get all exported packages
1169         final OpenHashMap<Resource, Packages> allPackages = new OpenHashMap<Resource, Packages>(allCandidates.getNbResources());
1170         for (final Resource resource : allWireCandidates.keySet())
1171         {
1172             final Packages packages = new Packages(resource);
1173             allPackages.put(resource, packages);
1174             executor.execute(new Runnable()
1175             {
1176                 public void run()
1177                 {
1178                     calculateExportedPackages(session, allCandidates, resource,
1179                         packages.m_exportedPkgs, packages.m_substitePkgs);
1180                 }
1181             });
1182         }
1183         executor.await();
1184 
1185         // Parallel compute package lists
1186         for (final Resource resource : allWireCandidates.keySet())
1187         {
1188             executor.execute(new Runnable()
1189             {
1190                 public void run()
1191                 {
1192                     getPackages(session, allCandidates, allWireCandidates, allPackages, resource, allPackages.get(resource));
1193                 }
1194             });
1195         }
1196         executor.await();
1197 
1198         // Compute package sources
1199         // First, sequentially compute packages for resources
1200         // that have required packages, so that all recursive
1201         // calls can be done without threading problems
1202         for (Map.Entry<Resource, Packages> entry : allPackages.fast())
1203         {
1204             final Resource resource = entry.getKey();
1205             final Packages packages = entry.getValue();
1206             if (!packages.m_requiredPkgs.isEmpty())
1207             {
1208                 getPackageSourcesInternal(session, allPackages, resource, packages);
1209             }
1210         }
1211         // Next, for all remaining resources, we can compute them
1212         // in parallel, as they won't refer to other resource packages
1213         for (Map.Entry<Resource, Packages> entry : allPackages.fast())
1214         {
1215             final Resource resource = entry.getKey();
1216             final Packages packages = entry.getValue();
1217             if (packages.m_sources.isEmpty())
1218             {
1219                 executor.execute(new Runnable()
1220                 {
1221                     public void run()
1222                     {
1223                         getPackageSourcesInternal(session, allPackages, resource, packages);
1224                     }
1225                 });
1226             }
1227         }
1228         executor.await();
1229 
1230         // Parallel compute uses
1231         for (final Resource resource : allWireCandidates.keySet())
1232         {
1233             executor.execute(new Runnable()
1234             {
1235                 public void run()
1236                 {
1237                     computeUses(session, allWireCandidates, allPackages, resource);
1238                 }
1239             });
1240         }
1241         executor.await();
1242 
1243         return allPackages;
1244     }
1245 
parseUses(String s)1246     private static List<String> parseUses(String s) {
1247         int nb = 1;
1248         int l = s.length();
1249         for (int i = 0; i < l; i++) {
1250             if (s.charAt(i) == ',') {
1251                 nb++;
1252             }
1253         }
1254         List<String> uses = new ArrayList<String>(nb);
1255         int start = 0;
1256         while (true) {
1257             while (start < l) {
1258                 char c = s.charAt(start);
1259                 if (c != ' ' && c != ',') {
1260                     break;
1261                 }
1262                 start++;
1263             }
1264             int end = start + 1;
1265             while (end < l) {
1266                 char c = s.charAt(end);
1267                 if (c == ' ' || c == ',') {
1268                     break;
1269                 }
1270                 end++;
1271             }
1272             if (start < l) {
1273                 uses.add(s.substring(start, end));
1274                 start = end + 1;
1275             } else {
1276                 break;
1277             }
1278         }
1279         return uses;
1280     }
1281 
addUsedBlames( ArrayMap<Set<Capability>, UsedBlames> usedBlames, Collection<Blame> blames, Capability matchingCap, Map<Resource, Packages> resourcePkgMap)1282     private void addUsedBlames(
1283         ArrayMap<Set<Capability>, UsedBlames> usedBlames, Collection<Blame> blames, Capability matchingCap, Map<Resource, Packages> resourcePkgMap)
1284     {
1285         Set<Capability> usedCaps;
1286         if (blames.size() == 1)
1287         {
1288             usedCaps = getPackageSources(blames.iterator().next().m_cap, resourcePkgMap);
1289         }
1290         else
1291         {
1292             usedCaps = new HashSet<Capability>();
1293             for (Blame blame : blames)
1294             {
1295                 usedCaps.addAll(getPackageSources(blame.m_cap, resourcePkgMap));
1296             }
1297         }
1298         if (usedCaps.isEmpty())
1299         {
1300             // This most likely is an issue with the resolve context.
1301             // To avoid total failure we do not add blames if there is
1302             // no source capabilities
1303             m_logger.log(Logger.LOG_INFO,
1304                 "Package sources are empty for used capability: " + blames);
1305             return;
1306         }
1307         // Find UsedBlame that uses the same capability as the new blame.
1308         UsedBlames addToBlame = usedBlames.getOrCompute(usedCaps);
1309         // Add the new Blames and record the matching capability cause
1310         // in case the root requirement has multiple cardinality.
1311         for (Blame blame : blames)
1312         {
1313             addToBlame.addBlame(blame, matchingCap);
1314         }
1315     }
1316 
checkPackageSpaceConsistency( ResolveSession session, Resource resource, Candidates allCandidates, boolean dynamic, Map<Resource, Packages> resourcePkgMap, Map<Resource, Object> resultCache)1317     private ResolutionError checkPackageSpaceConsistency(
1318         ResolveSession session,
1319         Resource resource,
1320         Candidates allCandidates,
1321         boolean dynamic,
1322         Map<Resource, Packages> resourcePkgMap,
1323         Map<Resource, Object> resultCache)
1324     {
1325         if (!dynamic && session.getContext().getWirings().containsKey(resource))
1326         {
1327             return null;
1328         }
1329         Object cache = resultCache.get(resource);
1330         if (cache != null)
1331         {
1332             return cache instanceof ResolutionError ? (ResolutionError) cache : null;
1333         }
1334 
1335         Packages pkgs = resourcePkgMap.get(resource);
1336 
1337         ResolutionError rethrow = null;
1338 
1339         // Check for conflicting imports from fragments.
1340         // TODO: Is this only needed for imports or are generic and bundle requirements also needed?
1341         //       I think this is only a special case for fragment imports because they can overlap
1342         //       host imports, which is not allowed in normal metadata.
1343         for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.fast())
1344         {
1345             String pkgName = entry.getKey();
1346             List<Blame> blames = entry.getValue();
1347             if (blames.size() > 1)
1348             {
1349                 Blame sourceBlame = null;
1350                 for (Blame blame : blames)
1351                 {
1352                     if (sourceBlame == null)
1353                     {
1354                         sourceBlame = blame;
1355                     }
1356                     else if (!sourceBlame.m_cap.getResource().equals(blame.m_cap.getResource()))
1357                     {
1358                         // Try to permutate the conflicting requirement.
1359                         session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(blame.m_reqs.get(0)));
1360                         // Try to permutate the source requirement.
1361                         session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(sourceBlame.m_reqs.get(0)));
1362                         // Report conflict.
1363                         rethrow = new UseConstraintError(
1364                                 session.getContext(), allCandidates,
1365                                 resource, pkgName,
1366                                 sourceBlame, blame);
1367                         if (m_logger.isDebugEnabled())
1368                         {
1369                             m_logger.debug(
1370                                     "Candidate permutation failed due to a conflict with a "
1371                                             + "fragment import; will try another if possible."
1372                                             + " (" + rethrow.getMessage() + ")");
1373                         }
1374                         return rethrow;
1375                     }
1376                 }
1377             }
1378         }
1379         // IMPLEMENTATION NOTE:
1380         // Below we track the mutated reqs that have been permuted
1381         // in a single candidates permutation.  This permutation may contain a
1382         // delta of several reqs which conflict with a directly imported/required candidates.
1383         // When several reqs are permuted at the same time this reduces the number of solutions tried.
1384         // See the method Candidates::canRemoveCandidate for a case where substitutions must be checked
1385         // because of this code that may permute multiple reqs in on candidates permutation.
1386         AtomicReference<Candidates> permRef1 = new AtomicReference<Candidates>();
1387         AtomicReference<Candidates> permRef2 = new AtomicReference<Candidates>();
1388         Set<Requirement> mutated = null;
1389 
1390         // Check if there are any uses conflicts with exported packages.
1391         for (Entry<String, Blame> entry : pkgs.m_exportedPkgs.fast())
1392         {
1393             String pkgName = entry.getKey();
1394             Blame exportBlame = entry.getValue();
1395             ArrayMap<Set<Capability>, UsedBlames> pkgBlames = pkgs.m_usedPkgs.get(pkgName);
1396             if (pkgBlames == null)
1397             {
1398                 continue;
1399             }
1400             for (UsedBlames usedBlames : pkgBlames.values())
1401             {
1402                 if (!isCompatible(exportBlame, usedBlames.m_caps, resourcePkgMap))
1403                 {
1404                     mutated = (mutated != null)
1405                             ? mutated
1406                             : new HashSet<Requirement>();
1407                     rethrow = permuteUsedBlames(session, rethrow, allCandidates, resource,
1408                             pkgName, null, usedBlames, permRef1, permRef2, mutated);
1409                 }
1410             }
1411 
1412             if (rethrow != null)
1413             {
1414                 if (!mutated.isEmpty())
1415                 {
1416                     session.addPermutation(PermutationType.USES, permRef1.get());
1417                     session.addPermutation(PermutationType.USES, permRef2.get());
1418                 }
1419                 if (m_logger.isDebugEnabled())
1420                 {
1421                     m_logger.debug("Candidate permutation failed due to a conflict between "
1422                             + "an export and import; will try another if possible."
1423                             + " (" + rethrow.getMessage() + ")");
1424                 }
1425                 return rethrow;
1426             }
1427         }
1428 
1429         // Check if there are any uses conflicts with imported and required packages.
1430         // We combine the imported and required packages here into one map.
1431         // Imported packages are added after required packages because they shadow or override
1432         // the packages from required bundles.
1433         OpenHashMap<String, List<Blame>> allImportRequirePkgs;
1434         if (pkgs.m_requiredPkgs.isEmpty())
1435         {
1436             allImportRequirePkgs = pkgs.m_importedPkgs;
1437         }
1438         else
1439         {
1440             allImportRequirePkgs = new OpenHashMap<String, List<Blame>>(pkgs.m_requiredPkgs.size() + pkgs.m_importedPkgs.size());
1441             allImportRequirePkgs.putAll(pkgs.m_requiredPkgs);
1442             allImportRequirePkgs.putAll(pkgs.m_importedPkgs);
1443         }
1444 
1445         for (Entry<String, List<Blame>> entry : allImportRequirePkgs.fast())
1446         {
1447             String pkgName = entry.getKey();
1448             ArrayMap<Set<Capability>, UsedBlames> pkgBlames = pkgs.m_usedPkgs.get(pkgName);
1449             if (pkgBlames == null)
1450             {
1451                 continue;
1452             }
1453             List<Blame> requirementBlames = entry.getValue();
1454 
1455             for (UsedBlames usedBlames : pkgBlames.values())
1456             {
1457                 if (!isCompatible(requirementBlames, usedBlames.m_caps, resourcePkgMap))
1458                 {
1459                     mutated = (mutated != null)
1460                             ? mutated
1461                             : new HashSet<Requirement>();// Split packages, need to think how to get a good message for split packages (sigh)
1462                     // For now we just use the first requirement that brings in the package that conflicts
1463                     Blame requirementBlame = requirementBlames.get(0);
1464                     rethrow = permuteUsedBlames(session, rethrow, allCandidates, resource, pkgName, requirementBlame, usedBlames, permRef1, permRef2, mutated);
1465                 }
1466 
1467                 // If there was a uses conflict, then we should add a uses
1468                 // permutation if we were able to permutate any candidates.
1469                 // Additionally, we should try to push an import permutation
1470                 // for the original import to force a backtracking on the
1471                 // original candidate decision if no viable candidate is found
1472                 // for the conflicting uses constraint.
1473                 if (rethrow != null)
1474                 {
1475                     // Add uses permutation if we m_mutated any candidates.
1476                     if (!mutated.isEmpty())
1477                     {
1478                         session.addPermutation(PermutationType.USES, permRef1.get());
1479                         session.addPermutation(PermutationType.USES, permRef2.get());
1480                     }
1481 
1482                     // Try to permutate the candidate for the original
1483                     // import requirement; only permutate it if we haven't
1484                     // done so already.
1485                     for (Blame requirementBlame : requirementBlames)
1486                     {
1487                         Requirement req = requirementBlame.m_reqs.get(0);
1488                         if (!mutated.contains(req))
1489                         {
1490                             // Since there may be lots of uses constraint violations
1491                             // with existing import decisions, we may end up trying
1492                             // to permutate the same import a lot of times, so we should
1493                             // try to check if that the case and only permutate it once.
1494                             session.permutateIfNeeded(PermutationType.IMPORT, req, allCandidates);
1495                         }
1496                     }
1497 
1498                     if (m_logger.isDebugEnabled())
1499                     {
1500                         m_logger.debug("Candidate permutation failed due to a conflict between "
1501                                         + "imports; will try another if possible."
1502                                         + " (" + rethrow.getMessage() + ")"
1503                         );
1504                     }
1505                     return rethrow;
1506                 }
1507             }
1508         }
1509 
1510         resultCache.put(resource, Boolean.TRUE);
1511 
1512         // Now check the consistency of all resources on which the
1513         // current resource depends. Keep track of the current number
1514         // of permutations so we know if the lower level check was
1515         // able to create a permutation or not in the case of failure.
1516         long permCount = session.getPermutationCount();
1517         for (Requirement req : resource.getRequirements(null))
1518         {
1519             Capability cap = allCandidates.getFirstCandidate(req);
1520             if (cap != null)
1521             {
1522                 if (!resource.equals(cap.getResource()))
1523                 {
1524                     rethrow = checkPackageSpaceConsistency(
1525                             session, cap.getResource(),
1526                             allCandidates, false, resourcePkgMap, resultCache);
1527                     if (session.isCancelled()) {
1528                         return null;
1529                     }
1530                     if (rethrow != null)
1531                     {
1532                         // If the lower level check didn't create any permutations,
1533                         // then we should create an import permutation for the
1534                         // requirement with the dependency on the failing resource
1535                         // to backtrack on our current candidate selection.
1536                         if (permCount == session.getPermutationCount())
1537                         {
1538                             session.addPermutation(PermutationType.IMPORT, allCandidates.permutate(req));
1539                         }
1540                         return rethrow;
1541                     }
1542                 }
1543             }
1544         }
1545         return null;
1546     }
1547 
permuteUsedBlames(ResolveSession session, ResolutionError rethrow, Candidates allCandidates, Resource resource, String pkgName, Blame requirementBlame, UsedBlames usedBlames, AtomicReference<Candidates> permRef1, AtomicReference<Candidates> permRef2, Set<Requirement> mutated)1548     private ResolutionError permuteUsedBlames(ResolveSession session,
1549           ResolutionError rethrow, Candidates allCandidates, Resource resource,
1550           String pkgName, Blame requirementBlame, UsedBlames usedBlames,
1551           AtomicReference<Candidates> permRef1, AtomicReference<Candidates> permRef2,
1552           Set<Requirement> mutated)
1553     {
1554         for (Blame usedBlame : usedBlames.m_blames)
1555         {
1556             if (session.checkMultiple(usedBlames, usedBlame, allCandidates))
1557             {
1558                 // Continue to the next usedBlame, if possible we
1559                 // removed the conflicting candidates.
1560                 continue;
1561             }
1562 
1563             if (rethrow == null)
1564             {
1565                 if (requirementBlame == null)
1566                 {
1567                     rethrow = new UseConstraintError(session.getContext(), allCandidates,
1568                             resource, pkgName, usedBlame);
1569                 }
1570                 else
1571                 {
1572                     rethrow = new UseConstraintError(session.getContext(), allCandidates,
1573                             resource, pkgName, requirementBlame, usedBlame);
1574                 }
1575             }
1576 
1577             // Create a candidate permutation that eliminates all candidates
1578             // that conflict with existing selected candidates going from direct requirement -> root
1579             Candidates perm1 = permRef1.get();
1580             if (perm1 == null)
1581             {
1582                 perm1 = allCandidates.copy();
1583                 permRef1.set(perm1);
1584             }
1585             for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
1586             {
1587                 Requirement req = usedBlame.m_reqs.get(reqIdx);
1588                 if (permuteUsedBlameRequirement(req, mutated, perm1))
1589                 {
1590                     break;
1591                 }
1592             }
1593             // Create a candidate permutation that eliminates all candidates
1594             // that conflict with existing selected candidates going from root -> direct requirement
1595             Candidates perm2 = permRef2.get();
1596             if (perm2 == null)
1597             {
1598                 perm2 = allCandidates.copy();
1599                 permRef2.set(perm2);
1600             }
1601             for (int reqIdx = 0; reqIdx < usedBlame.m_reqs.size(); reqIdx++)
1602             {
1603                 Requirement req = usedBlame.m_reqs.get(reqIdx);
1604                 if (permuteUsedBlameRequirement(req, mutated, perm2))
1605                 {
1606                     break;
1607                 }
1608             }
1609         }
1610         return rethrow;
1611     }
1612 
permuteUsedBlameRequirement(Requirement req, Set<Requirement> mutated, Candidates permutation)1613     private boolean permuteUsedBlameRequirement(Requirement req, Set<Requirement> mutated, Candidates permutation)
1614     {
1615         // Sanity check for multiple.
1616         if (Util.isMultiple(req))
1617         {
1618             return false;
1619         }
1620         // If we've already permutated this requirement in another
1621         // uses constraint, don't permutate it again just continue
1622         // with the next uses constraint.
1623         if (mutated.contains(req))
1624         {
1625             return true;
1626         }
1627 
1628         // See if we can permutate the candidates for blamed
1629         // requirement; there may be no candidates if the resource
1630         // associated with the requirement is already resolved.
1631         if (permutation.canRemoveCandidate(req))
1632         {
1633             permutation.removeFirstCandidate(req);
1634             mutated.add(req);
1635             return true;
1636         }
1637         return false;
1638     }
1639 
calculateExportedPackages( ResolveSession session, Candidates allCandidates, Resource resource, OpenHashMap<String, Blame> exports, OpenHashMap<String, Blame> substitutes)1640     private static OpenHashMap<String, Blame> calculateExportedPackages(
1641             ResolveSession session,
1642             Candidates allCandidates,
1643             Resource resource,
1644         OpenHashMap<String, Blame> exports, OpenHashMap<String, Blame> substitutes)
1645     {
1646         // Get all exported packages.
1647         Wiring wiring = session.getContext().getWirings().get(resource);
1648         List<Capability> caps = (wiring != null)
1649             ? wiring.getResourceCapabilities(null)
1650             : resource.getCapabilities(null);
1651         for (Capability cap : caps)
1652         {
1653             if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
1654             {
1655                 if (!cap.getResource().equals(resource))
1656                 {
1657                     cap = new WrappedCapability(resource, cap);
1658                 }
1659                 exports.put(
1660                     (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE),
1661                     new Blame(cap, null));
1662             }
1663         }
1664         // Remove substitutable exports that were imported.
1665         // For resolved resources Wiring.getCapabilities()
1666         // already excludes imported substitutable exports, but
1667         // for resolving resources we must look in the candidate
1668         // map to determine which exports are substitutable.
1669         if (wiring != null)
1670         {
1671             for (Wire wire : session.getContext().getSubstitutionWires(wiring))
1672             {
1673                 Capability cap = wire.getCapability();
1674                 if (!cap.getResource().equals(wire.getProvider()))
1675                 {
1676                     cap = new WrappedCapability(wire.getProvider(), cap);
1677                 }
1678                 substitutes.put(
1679                     // Using a null on requirement instead of the wire requirement here.
1680                     // It is unclear if we want to treat the substitution requirement as a permutation req here.
1681                     (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE),
1682                     new Blame(cap, null));
1683             }
1684         }
1685         else
1686         {
1687             if (!exports.isEmpty())
1688             {
1689                 for (Requirement req : resource.getRequirements(null))
1690                 {
1691                     if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
1692                     {
1693                         Capability cand = allCandidates.getFirstCandidate(req);
1694                         if (cand != null)
1695                         {
1696                             String pkgName = (String) cand.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
1697                             Blame blame = exports.remove(pkgName);
1698                             if (blame != null)
1699                             {
1700                                 // Using a null on requirement instead of the wire requirement here.
1701                                 // It is unclear if we want to treat the substitution requirement as a permutation req here.
1702                                 substitutes.put(pkgName, new Blame(cand, null));
1703                             }
1704                         }
1705                     }
1706                 }
1707             }
1708         }
1709         return exports;
1710     }
1711 
isCompatible( Blame currentBlame, Set<Capability> candSources, Map<Resource, Packages> resourcePkgMap)1712     private static boolean isCompatible(
1713         Blame currentBlame, Set<Capability> candSources,
1714         Map<Resource, Packages> resourcePkgMap)
1715     {
1716         if (candSources.contains(currentBlame.m_cap))
1717         {
1718             return true;
1719         }
1720         Set<Capability> currentSources = getPackageSources(currentBlame.m_cap, resourcePkgMap);
1721         return currentSources.containsAll(candSources)
1722                 || candSources.containsAll(currentSources);
1723     }
1724 
isCompatible( List<Blame> currentBlames, Set<Capability> candSources, Map<Resource, Packages> resourcePkgMap)1725     private static boolean isCompatible(
1726         List<Blame> currentBlames, Set<Capability> candSources,
1727         Map<Resource, Packages> resourcePkgMap)
1728     {
1729         int size = currentBlames.size();
1730         switch (size)
1731         {
1732         case 0:
1733             return true;
1734         case 1:
1735             return isCompatible(currentBlames.get(0), candSources, resourcePkgMap);
1736         default:
1737             Set<Capability> currentSources = new HashSet<Capability>(currentBlames.size());
1738             for (Blame currentBlame : currentBlames)
1739             {
1740                 Set<Capability> blameSources = getPackageSources(currentBlame.m_cap, resourcePkgMap);
1741                 currentSources.addAll(blameSources);
1742             }
1743             return currentSources.containsAll(candSources)
1744                 || candSources.containsAll(currentSources);
1745         }
1746     }
1747 
getPackageSources( Capability cap, Map<Resource, Packages> resourcePkgMap)1748     private static Set<Capability> getPackageSources(
1749             Capability cap, Map<Resource, Packages> resourcePkgMap)
1750     {
1751         Resource resource = cap.getResource();
1752         if(resource == null)
1753         {
1754             return new HashSet<Capability>();
1755         }
1756 
1757         OpenHashMap<Capability, Set<Capability>> sources = resourcePkgMap.get(resource).m_sources;
1758         if(sources == null)
1759         {
1760             return new HashSet<Capability>();
1761         }
1762 
1763         Set<Capability> packageSources = sources.get(cap);
1764         if(packageSources == null)
1765         {
1766             return new HashSet<Capability>();
1767         }
1768 
1769         return packageSources;
1770     }
1771 
getPackageSourcesInternal( ResolveSession session, Map<Resource, Packages> resourcePkgMap, Resource resource, Packages packages)1772     private static void getPackageSourcesInternal(
1773         ResolveSession session, Map<Resource, Packages> resourcePkgMap,
1774         Resource resource, Packages packages)
1775     {
1776         Wiring wiring = session.getContext().getWirings().get(resource);
1777         List<Capability> caps = (wiring != null)
1778                 ? wiring.getResourceCapabilities(null)
1779                 : resource.getCapabilities(null);
1780         @SuppressWarnings("serial")
1781         OpenHashMap<String, Set<Capability>> pkgs = new OpenHashMap<String, Set<Capability>>(caps.size()) {
1782             public Set<Capability> compute(String pkgName) {
1783                 return new HashSet<Capability>();
1784             }
1785         };
1786         Map<Capability, Set<Capability>> sources = packages.m_sources;
1787         for (Capability sourceCap : caps)
1788         {
1789             if (sourceCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
1790             {
1791                 String pkgName = (String) sourceCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
1792                 Set<Capability> pkgCaps = pkgs.getOrCompute(pkgName);
1793                 // Since capabilities may come from fragments, we need to check
1794                 // for that case and wrap them.
1795                 if (!resource.equals(sourceCap.getResource()))
1796                 {
1797                     sourceCap = new WrappedCapability(resource, sourceCap);
1798                 }
1799                 sources.put(sourceCap, pkgCaps);
1800                 pkgCaps.add(sourceCap);
1801             }
1802             else
1803             {
1804                 // Otherwise, need to return generic capabilities that have
1805                 // uses constraints so they are included for consistency
1806                 // checking.
1807                 String uses = sourceCap.getDirectives().get(Namespace.CAPABILITY_USES_DIRECTIVE);
1808                 if ((uses != null) && uses.length() > 0)
1809                 {
1810                     sources.put(sourceCap, Collections.singleton(sourceCap));
1811                 }
1812                 else
1813                 {
1814                     sources.put(sourceCap, Collections.<Capability>emptySet());
1815                 }
1816             }
1817         }
1818         for (Map.Entry<String, Set<Capability>> pkg : pkgs.fast())
1819         {
1820             String pkgName = pkg.getKey();
1821             List<Blame> required = packages.m_requiredPkgs.get(pkgName);
1822             if (required != null)
1823             {
1824                 Set<Capability> srcs = pkg.getValue();
1825                 for (Blame blame : required)
1826                 {
1827                     Capability bcap = blame.m_cap;
1828                     if (srcs.add(bcap))
1829                     {
1830                         Resource capResource = bcap.getResource();
1831                         Packages capPackages = resourcePkgMap.get(capResource);
1832                         Set<Capability> additional = capPackages.m_sources.get(bcap);
1833                         if (additional == null)
1834                         {
1835                             getPackageSourcesInternal(session, resourcePkgMap, capResource, capPackages);
1836                             additional = capPackages.m_sources.get(bcap);
1837                         }
1838                         srcs.addAll(additional);
1839                     }
1840                 }
1841             }
1842         }
1843     }
1844 
getDeclaredResource(Resource resource)1845     private static Resource getDeclaredResource(Resource resource)
1846     {
1847         if (resource instanceof WrappedResource)
1848         {
1849             return ((WrappedResource) resource).getDeclaredResource();
1850         }
1851         return resource;
1852     }
1853 
getDeclaredCapability(Capability c)1854     private static Capability getDeclaredCapability(Capability c)
1855     {
1856         if (c instanceof HostedCapability)
1857         {
1858             return ((HostedCapability) c).getDeclaredCapability();
1859         }
1860         return c;
1861     }
1862 
getDeclaredRequirement(Requirement r)1863     private static Requirement getDeclaredRequirement(Requirement r)
1864     {
1865         if (r instanceof WrappedRequirement)
1866         {
1867             return ((WrappedRequirement) r).getDeclaredRequirement();
1868         }
1869         return r;
1870     }
1871 
populateWireMap( ResolveSession session, Resource resource, Map<Resource, List<Wire>> wireMap, Candidates allCandidates)1872     private static Map<Resource, List<Wire>> populateWireMap(
1873         ResolveSession session, Resource resource,
1874         Map<Resource, List<Wire>> wireMap, Candidates allCandidates)
1875     {
1876         Resource unwrappedResource = getDeclaredResource(resource);
1877         if (!session.getContext().getWirings().containsKey(unwrappedResource)
1878             && !wireMap.containsKey(unwrappedResource))
1879         {
1880             wireMap.put(unwrappedResource, Collections.<Wire>emptyList());
1881 
1882             List<Wire> packageWires = new ArrayList<Wire>();
1883             List<Wire> bundleWires = new ArrayList<Wire>();
1884             List<Wire> capabilityWires = new ArrayList<Wire>();
1885 
1886             for (Requirement req : resource.getRequirements(null))
1887             {
1888                 List<Capability> cands = allCandidates.getCandidates(req);
1889                 if ((cands != null) && (cands.size() > 0))
1890                 {
1891                     for (Capability cand : cands)
1892                     {
1893                         // Do not create wires for the osgi.wiring.* namespaces
1894                         // if the provider and requirer are the same resource;
1895                         // allow such wires for non-OSGi wiring namespaces.
1896                         if (!cand.getNamespace().startsWith("osgi.wiring.")
1897                             || !resource.equals(cand.getResource()))
1898                         {
1899                             // Populate wires for the candidate
1900                             populateWireMap(session, cand.getResource(),
1901                                     wireMap, allCandidates);
1902 
1903                             Resource provider;
1904                             if (req.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE)) {
1905                                 provider = getDeclaredCapability(cand).getResource();
1906                             } else {
1907                                 provider = getDeclaredResource(cand.getResource());
1908                             }
1909                             Wire wire = new WireImpl(
1910                                 unwrappedResource,
1911                                 getDeclaredRequirement(req),
1912                                 provider,
1913                                 getDeclaredCapability(cand));
1914                             if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
1915                             {
1916                                 packageWires.add(wire);
1917                             }
1918                             else if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
1919                             {
1920                                 bundleWires.add(wire);
1921                             }
1922                             else
1923                             {
1924                                 capabilityWires.add(wire);
1925                             }
1926                         }
1927                         if (!Util.isMultiple(req))
1928                         {
1929                             // If not multiple just create a wire for the first candidate.
1930                             break;
1931                         }
1932                     }
1933                 }
1934             }
1935 
1936             // Combine package wires with require wires last.
1937             packageWires.addAll(bundleWires);
1938             packageWires.addAll(capabilityWires);
1939             wireMap.put(unwrappedResource, packageWires);
1940 
1941             // Add host wire for any fragments.
1942             if (resource instanceof WrappedResource)
1943             {
1944                 List<Resource> fragments = ((WrappedResource) resource).getFragments();
1945                 for (Resource fragment : fragments)
1946                 {
1947                     // Get wire list for the fragment from the wire map.
1948                     // If there isn't one, then create one. Note that we won't
1949                     // add the wire list to the wire map until the end, so
1950                     // we can determine below if this is the first time we've
1951                     // seen the fragment while populating wires to avoid
1952                     // creating duplicate non-payload wires if the fragment
1953                     // is attached to more than one host.
1954                     List<Wire> fragmentWires = wireMap.get(fragment);
1955                     fragmentWires = (fragmentWires == null)
1956                         ? new ArrayList<Wire>() : fragmentWires;
1957 
1958                     // Loop through all of the fragment's requirements and create
1959                     // any necessary wires for non-payload requirements.
1960                     for (Requirement req : fragment.getRequirements(null))
1961                     {
1962                         // Only look at non-payload requirements.
1963                         if (!isPayload(req))
1964                         {
1965                             // If this is the host requirement, then always create
1966                             // a wire for it to the current resource.
1967                             if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
1968                             {
1969                                 fragmentWires.add(
1970                                     new WireImpl(
1971                                         getDeclaredResource(fragment),
1972                                         req,
1973                                         unwrappedResource,
1974                                         unwrappedResource.getCapabilities(
1975                                             HostNamespace.HOST_NAMESPACE).get(0)));
1976                             }
1977                             // Otherwise, if the fragment isn't already resolved and
1978                             // this is the first time we are seeing it, then create
1979                             // a wire for the non-payload requirement.
1980                             else if (!session.getContext().getWirings().containsKey(fragment)
1981                                 && !wireMap.containsKey(fragment))
1982                             {
1983                                 Wire wire = createWire(req, allCandidates);
1984                                 if (wire != null)
1985                                 {
1986                                     fragmentWires.add(wire);
1987                                 }
1988                             }
1989                         }
1990                     }
1991 
1992                     // Finally, add the fragment's wire list to the wire map.
1993                     wireMap.put(fragment, fragmentWires);
1994                 }
1995             }
1996             // now make sure any related resources are populated
1997             for (Resource related : session.getRelatedResources(unwrappedResource)) {
1998                 if (allCandidates.isPopulated(related)) {
1999                     populateWireMap(session, related, wireMap, allCandidates);
2000                 }
2001             }
2002         }
2003 
2004         return wireMap;
2005     }
2006 
createWire(Requirement requirement, Candidates allCandidates)2007     private static Wire createWire(Requirement requirement, Candidates allCandidates)
2008     {
2009         Capability cand = allCandidates.getFirstCandidate(requirement);
2010         if (cand == null) {
2011             return null;
2012         }
2013         return new WireImpl(
2014             getDeclaredResource(requirement.getResource()),
2015             getDeclaredRequirement(requirement),
2016             getDeclaredResource(cand.getResource()),
2017             getDeclaredCapability(cand));
2018     }
2019 
isPayload(Requirement fragmentReq)2020     private static boolean isPayload(Requirement fragmentReq)
2021     {
2022         // this is where we would add other non-payload namespaces
2023         if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE
2024             .equals(fragmentReq.getNamespace()))
2025         {
2026             return false;
2027         }
2028         if (HostNamespace.HOST_NAMESPACE.equals(fragmentReq.getNamespace()))
2029         {
2030             return false;
2031         }
2032         return true;
2033     }
2034 
populateDynamicWireMap( ResolveSession session, Map<Resource, List<Wire>> wireMap, Candidates allCandidates)2035     private static Map<Resource, List<Wire>> populateDynamicWireMap(
2036         ResolveSession session, Map<Resource,
2037         List<Wire>> wireMap, Candidates allCandidates)
2038     {
2039         wireMap.put(session.getDynamicHost(), Collections.<Wire>emptyList());
2040 
2041         List<Wire> packageWires = new ArrayList<Wire>();
2042 
2043         // Get the candidates for the current dynamic requirement.
2044         // Record the dynamic candidate.
2045         Capability dynCand = allCandidates.getFirstCandidate(session.getDynamicRequirement());
2046 
2047         if (!session.getContext().getWirings().containsKey(dynCand.getResource()))
2048         {
2049             populateWireMap(session, dynCand.getResource(),
2050                 wireMap, allCandidates);
2051         }
2052 
2053         packageWires.add(
2054             new WireImpl(
2055                 session.getDynamicHost(),
2056                 session.getDynamicRequirement(),
2057                 getDeclaredResource(dynCand.getResource()),
2058                 getDeclaredCapability(dynCand)));
2059 
2060         wireMap.put(session.getDynamicHost(), packageWires);
2061 
2062         return wireMap;
2063     }
2064 
2065     @SuppressWarnings("unused")
dumpResourcePkgMap( ResolveContext rc, Map<Resource, Packages> resourcePkgMap)2066     private static void dumpResourcePkgMap(
2067         ResolveContext rc, Map<Resource, Packages> resourcePkgMap)
2068     {
2069         System.out.println("+++RESOURCE PKG MAP+++");
2070         for (Entry<Resource, Packages> entry : resourcePkgMap.entrySet())
2071         {
2072             dumpResourcePkgs(rc, entry.getKey(), entry.getValue());
2073         }
2074     }
2075 
dumpResourcePkgs( ResolveContext rc, Resource resource, Packages packages)2076     private static void dumpResourcePkgs(
2077         ResolveContext rc, Resource resource, Packages packages)
2078     {
2079         Wiring wiring = rc.getWirings().get(resource);
2080         System.out.println(resource
2081             + " (" + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)"));
2082         System.out.println("  EXPORTED");
2083         for (Entry<String, Blame> entry : packages.m_exportedPkgs.entrySet())
2084         {
2085             System.out.println("    " + entry.getKey() + " - " + entry.getValue());
2086         }
2087         System.out.println("  IMPORTED");
2088         for (Entry<String, List<Blame>> entry : packages.m_importedPkgs.entrySet())
2089         {
2090             System.out.println("    " + entry.getKey() + " - " + entry.getValue());
2091         }
2092         System.out.println("  REQUIRED");
2093         for (Entry<String, List<Blame>> entry : packages.m_requiredPkgs.entrySet())
2094         {
2095             System.out.println("    " + entry.getKey() + " - " + entry.getValue());
2096         }
2097         System.out.println("  USED");
2098         for (Entry<String, ArrayMap<Set<Capability>, UsedBlames>> entry : packages.m_usedPkgs.entrySet())
2099         {
2100             System.out.println("    " + entry.getKey() + " - " + entry.getValue().values());
2101         }
2102     }
2103 
2104     private static final class WireCandidate
2105     {
2106         public final Requirement requirement;
2107         public final Capability capability;
2108 
WireCandidate(Requirement requirement, Capability capability)2109         public WireCandidate(Requirement requirement, Capability capability)
2110         {
2111             this.requirement = requirement;
2112             this.capability = capability;
2113         }
2114     }
2115 
2116     public static class Packages
2117     {
2118         public final OpenHashMap<String, Blame> m_exportedPkgs;
2119         public final OpenHashMap<String, Blame> m_substitePkgs;
2120         public final OpenHashMap<String, List<Blame>> m_importedPkgs;
2121         public final OpenHashMap<String, List<Blame>> m_requiredPkgs;
2122         public final OpenHashMap<String, ArrayMap<Set<Capability>, UsedBlames>> m_usedPkgs;
2123         public final OpenHashMap<Capability, Set<Capability>> m_sources;
2124 
2125         @SuppressWarnings("serial")
Packages(Resource resource)2126         public Packages(Resource resource)
2127         {
2128             int nbCaps = resource.getCapabilities(null).size();
2129             int nbReqs = resource.getRequirements(null).size();
2130 
2131             m_exportedPkgs = new OpenHashMap<String, Blame>(nbCaps);
2132             m_substitePkgs = new OpenHashMap<String, Blame>(nbCaps);
2133             m_importedPkgs = new OpenHashMap<String, List<Blame>>(nbReqs) {
2134                 public List<Blame> compute(String s) {
2135                     return new ArrayList<Blame>();
2136                 }
2137             };
2138             m_requiredPkgs = new OpenHashMap<String, List<Blame>>(nbReqs) {
2139                 public List<Blame> compute(String s) {
2140                     return new ArrayList<Blame>();
2141                 }
2142             };
2143             m_usedPkgs = new OpenHashMap<String, ArrayMap<Set<Capability>, UsedBlames>>(128) {
2144                 @Override
2145                 protected ArrayMap<Set<Capability>, UsedBlames> compute(String s) {
2146                     return new ArrayMap<Set<Capability>, UsedBlames>() {
2147                         @Override
2148                         protected UsedBlames compute(Set<Capability> key) {
2149                             return new UsedBlames(key);
2150                         }
2151                     };
2152                 }
2153             };
2154             m_sources = new OpenHashMap<Capability, Set<Capability>>(nbCaps);
2155         }
2156     }
2157 
2158     private static class Blame
2159     {
2160         public final Capability m_cap;
2161         public final List<Requirement> m_reqs;
2162 
Blame(Capability cap, List<Requirement> reqs)2163         public Blame(Capability cap, List<Requirement> reqs)
2164         {
2165             m_cap = cap;
2166             m_reqs = reqs;
2167         }
2168 
2169         @Override
toString()2170         public String toString()
2171         {
2172             return m_cap.getResource()
2173                 + "." + m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
2174                 + (((m_reqs == null) || m_reqs.isEmpty())
2175                 ? " NO BLAME"
2176                 : " BLAMED ON " + m_reqs);
2177         }
2178 
2179         @Override
equals(Object o)2180         public boolean equals(Object o)
2181         {
2182             return (o instanceof Blame) && m_reqs.equals(((Blame) o).m_reqs)
2183                 && m_cap.equals(((Blame) o).m_cap);
2184         }
2185     }
2186 
2187     /*
2188      * UsedBlames hold a list of Blame that have a common used capability.
2189      * The UsedBlames stores sets of capabilities (root causes) that match a
2190      * root requirement with multiple cardinality.  These causes are the
2191      * capabilities that pulled in the common used capability.
2192      * It is assumed that multiple cardinality requirements can only be
2193      * root requirements of a Blame.
2194      *
2195      * This is only true because capabilities can only use a package
2196      * capability.  They cannot use any other kind of capability so we
2197      * do not have to worry about transitivity of the uses directive
2198      * from other capability types.
2199      */
2200     private static class UsedBlames
2201     {
2202         public final Set<Capability> m_caps;
2203         public final List<Blame> m_blames = new ArrayList<ResolverImpl.Blame>();
2204         private Map<Requirement, Set<Capability>> m_rootCauses;
2205 
UsedBlames(Set<Capability> caps)2206         public UsedBlames(Set<Capability> caps)
2207         {
2208             m_caps = caps;
2209         }
2210 
addBlame(Blame blame, Capability matchingRootCause)2211         public void addBlame(Blame blame, Capability matchingRootCause)
2212         {
2213             if (!m_caps.contains(blame.m_cap))
2214             {
2215                 throw new IllegalArgumentException(
2216                     "Attempt to add a blame with a different used capability: "
2217                     + blame.m_cap);
2218             }
2219             m_blames.add(blame);
2220             if (matchingRootCause != null)
2221             {
2222                 Requirement req = blame.m_reqs.get(0);
2223                 // Assumption made that the root requirement of the chain is the only
2224                 // possible multiple cardinality requirement and that the matching root cause
2225                 // capability is passed down from the beginning of the chain creation.
2226                 if (Util.isMultiple(req))
2227                 {
2228                     // The root requirement is multiple. Need to store the root cause
2229                     // so that we can find it later in case the used capability which the cause
2230                     // capability pulled in is a conflict.
2231                     if (m_rootCauses == null)
2232                     {
2233                         m_rootCauses = new HashMap<Requirement, Set<Capability>>();
2234                     }
2235                     Set<Capability> rootCauses = m_rootCauses.get(req);
2236                     if (rootCauses == null)
2237                     {
2238                         rootCauses = new HashSet<Capability>();
2239                         m_rootCauses.put(req, rootCauses);
2240                     }
2241                     rootCauses.add(matchingRootCause);
2242                 }
2243             }
2244         }
2245 
getRootCauses(Requirement req)2246         public Set<Capability> getRootCauses(Requirement req)
2247         {
2248             if (m_rootCauses == null)
2249             {
2250                 return Collections.emptySet();
2251             }
2252             Set<Capability> result = m_rootCauses.get(req);
2253             return result == null ? Collections.<Capability>emptySet() : result;
2254         }
2255 
2256         @Override
toString()2257         public String toString()
2258         {
2259             return m_blames.toString();
2260         }
2261     }
2262 
2263     private static final class UseConstraintError extends ResolutionError {
2264 
2265         private final ResolveContext m_context;
2266         private final Candidates m_allCandidates;
2267         private final Resource m_resource;
2268         private final String m_pkgName;
2269         private final Blame m_blame1;
2270         private final Blame m_blame2;
2271 
UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame)2272         public UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame) {
2273             this(context, allCandidates, resource, pkgName, blame, null);
2274         }
2275 
UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame1, Blame blame2)2276         public UseConstraintError(ResolveContext context, Candidates allCandidates, Resource resource, String pkgName, Blame blame1, Blame blame2) {
2277             this.m_context = context;
2278             this.m_allCandidates = allCandidates;
2279             this.m_resource = resource;
2280             this.m_pkgName = pkgName;
2281             if (blame1 == null)
2282             {
2283                 throw new NullPointerException("First blame cannot be null.");
2284             }
2285             this.m_blame1 = blame1;
2286             this.m_blame2 = blame2;
2287         }
2288 
getMessage()2289         public String getMessage() {
2290             if (m_blame2 == null)
2291             {
2292                 return "Uses constraint violation. Unable to resolve resource "
2293                         + Util.getSymbolicName(m_resource)
2294                         + " [" + m_resource
2295                         + "] because it exports package '"
2296                         + m_pkgName
2297                         + "' and is also exposed to it from resource "
2298                         + Util.getSymbolicName(m_blame1.m_cap.getResource())
2299                         + " [" + m_blame1.m_cap.getResource()
2300                         + "] via the following dependency chain:\n\n"
2301                         + toStringBlame(m_blame1);
2302             }
2303             else
2304             {
2305                 return  "Uses constraint violation. Unable to resolve resource "
2306                         + Util.getSymbolicName(m_resource)
2307                         + " [" + m_resource
2308                         + "] because it is exposed to package '"
2309                         + m_pkgName
2310                         + "' from resources "
2311                         + Util.getSymbolicName(m_blame1.m_cap.getResource())
2312                         + " [" + m_blame1.m_cap.getResource()
2313                         + "] and "
2314                         + Util.getSymbolicName(m_blame2.m_cap.getResource())
2315                         + " [" + m_blame2.m_cap.getResource()
2316                         + "] via two dependency chains.\n\nChain 1:\n"
2317                         + toStringBlame(m_blame1)
2318                         + "\n\nChain 2:\n"
2319                         + toStringBlame(m_blame2);
2320             }
2321         }
2322 
getUnresolvedRequirements()2323         public Collection<Requirement> getUnresolvedRequirements() {
2324             if (m_blame2 == null)
2325             {
2326                 // This is an export conflict so there is only the first blame;
2327                 // use its requirement.
2328                 return Collections.singleton(m_blame1.m_reqs.get(0));
2329             }
2330             else
2331             {
2332                 return Collections.singleton(m_blame2.m_reqs.get(0));
2333             }
2334         }
2335 
toStringBlame(Blame blame)2336         private String toStringBlame(Blame blame)
2337         {
2338             StringBuilder sb = new StringBuilder();
2339             if ((blame.m_reqs != null) && !blame.m_reqs.isEmpty())
2340             {
2341                 for (int i = 0; i < blame.m_reqs.size(); i++)
2342                 {
2343                     Requirement req = blame.m_reqs.get(i);
2344                     sb.append("  ");
2345                     sb.append(Util.getSymbolicName(req.getResource()));
2346                     sb.append(" [");
2347                     sb.append(req.getResource().toString());
2348                     sb.append("]\n");
2349                     if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
2350                     {
2351                         sb.append("    import: ");
2352                     }
2353                     else
2354                     {
2355                         sb.append("    require: ");
2356                     }
2357                     sb.append(req.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE));
2358                     sb.append("\n     |");
2359                     if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
2360                     {
2361                         sb.append("\n    export: ");
2362                     }
2363                     else
2364                     {
2365                         sb.append("\n    provide: ");
2366                     }
2367                     if ((i + 1) < blame.m_reqs.size())
2368                     {
2369                         Capability cap = getSatisfyingCapability(blame.m_reqs.get(i));
2370                         if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
2371                         {
2372                             sb.append(PackageNamespace.PACKAGE_NAMESPACE);
2373                             sb.append("=");
2374                             sb.append(cap.getAttributes()
2375                                     .get(PackageNamespace.PACKAGE_NAMESPACE));
2376                             Capability usedCap =
2377                                     getSatisfyingCapability(blame.m_reqs.get(i + 1));
2378                             sb.append("; uses:=");
2379                             sb.append(usedCap.getAttributes()
2380                                     .get(PackageNamespace.PACKAGE_NAMESPACE));
2381                         }
2382                         else
2383                         {
2384                             sb.append(cap);
2385                         }
2386                         sb.append("\n");
2387                     }
2388                     else
2389                     {
2390                         Capability export = getSatisfyingCapability(blame.m_reqs.get(i));
2391                         sb.append(export.getNamespace());
2392                         sb.append(": ");
2393                         Object namespaceVal = export.getAttributes().get(export.getNamespace());
2394                         if (namespaceVal != null)
2395                         {
2396                             sb.append(namespaceVal.toString());
2397                         }
2398                         else
2399                         {
2400                             for (Entry<String, Object> attrEntry : export.getAttributes().entrySet())
2401                             {
2402                                 sb.append(attrEntry.getKey()).append('=')
2403                                         .append(attrEntry.getValue()).append(';');
2404                             }
2405                         }
2406                         if (export.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
2407                                 && !export.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
2408                                 .equals(blame.m_cap.getAttributes().get(
2409                                         PackageNamespace.PACKAGE_NAMESPACE)))
2410                         {
2411                             sb.append("; uses:=");
2412                             sb.append(blame.m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
2413                             sb.append("\n    export: ");
2414                             sb.append(PackageNamespace.PACKAGE_NAMESPACE);
2415                             sb.append("=");
2416                             sb.append(blame.m_cap.getAttributes()
2417                                     .get(PackageNamespace.PACKAGE_NAMESPACE));
2418                         }
2419                         sb.append("\n  ");
2420                         sb.append(Util.getSymbolicName(blame.m_cap.getResource()));
2421                         sb.append(" [");
2422                         sb.append(blame.m_cap.getResource().toString());
2423                         sb.append("]");
2424                     }
2425                 }
2426             }
2427             else
2428             {
2429                 sb.append(blame.m_cap.getResource().toString());
2430             }
2431             return sb.toString();
2432         }
2433 
getSatisfyingCapability(Requirement req)2434         private Capability getSatisfyingCapability(Requirement req)
2435         {
2436             // If the requiring revision is not resolved, then check in the
2437             // candidate map for its matching candidate.
2438             Capability cap = m_allCandidates.getFirstCandidate(req);
2439             // Otherwise, if the requiring revision is resolved then check
2440             // in its wires for the capability satisfying the requirement.
2441             if (cap == null && m_context.getWirings().containsKey(req.getResource()))
2442             {
2443                 List<Wire> wires =
2444                         m_context.getWirings().get(req.getResource()).getRequiredResourceWires(null);
2445                 req = getDeclaredRequirement(req);
2446                 for (Wire w : wires)
2447                 {
2448                     if (w.getRequirement().equals(req))
2449                     {
2450                         // TODO: RESOLVER - This is not 100% correct, since requirements for
2451                         //       dynamic imports with wildcards will reside on many wires and
2452                         //       this code only finds the first one, not necessarily the correct
2453                         //       one. This is only used for the diagnostic message, but it still
2454                         //       could confuse the user.
2455                         cap = w.getCapability();
2456                         break;
2457                     }
2458                 }
2459             }
2460 
2461             return cap;
2462         }
2463 
2464         @Override
toException()2465         public ResolutionException toException()
2466         {
2467             return new ReasonException(ReasonException.Reason.UseConstraint, getMessage(), null, getUnresolvedRequirements());
2468         }
2469     }
2470 
2471     private static class EnhancedExecutor
2472     {
2473         private final Executor executor;
2474         private final Queue<Future<Void>> awaiting = new ConcurrentLinkedQueue<Future<Void>>();
2475         private final AtomicReference<Throwable> throwable = new AtomicReference<Throwable>();
2476 
EnhancedExecutor(Executor executor)2477         public EnhancedExecutor(Executor executor)
2478         {
2479             this.executor = executor;
2480         }
2481 
execute(final Runnable runnable)2482         public void execute(final Runnable runnable)
2483         {
2484             FutureTask<Void> task = new FutureTask<Void>(new Runnable()
2485             {
2486                 public void run()
2487                 {
2488                     try
2489                     {
2490                         runnable.run();
2491                     }
2492                     catch (Throwable t)
2493                     {
2494                         throwable.compareAndSet(null, t);
2495                     }
2496                 }
2497             }, (Void) null);
2498             // must have a happens-first to add the task to awaiting
2499             awaiting.add(task);
2500             try
2501             {
2502                 executor.execute(task);
2503             }
2504             catch (Throwable t)
2505             {
2506                 // if the task did not get added successfully to the executor we must cancel
2507                 // the task so we don't await on it
2508                 task.cancel(false);
2509                 throwable.compareAndSet(null, t);
2510             }
2511         }
2512 
await()2513         public void await()
2514         {
2515             Future<Void> awaitTask;
2516             while (throwable.get() == null && (awaitTask = awaiting.poll()) != null)
2517             {
2518                 if (!awaitTask.isDone() && !awaitTask.isCancelled())
2519                 {
2520                     try
2521                     {
2522                         awaitTask.get();
2523                     }
2524                     catch (CancellationException e)
2525                     {
2526                         // ignore; will have throwable set
2527                     }
2528                     catch (InterruptedException e)
2529                     {
2530                         throw new IllegalStateException(e);
2531                     }
2532                     catch (ExecutionException e)
2533                     {
2534                         throw new RuntimeException(e.getCause());
2535                     }
2536                 }
2537             }
2538             Throwable t = throwable.get();
2539             if (t != null)
2540             {
2541                 if (t instanceof Runnable)
2542                 {
2543                     throw (RuntimeException) t;
2544                 }
2545                 else if (t instanceof Error)
2546                 {
2547                     throw (Error) t;
2548                 }
2549                 throw new RuntimeException(t);
2550             }
2551         }
2552     }
2553 
2554     static class DumbExecutor implements Executor
2555     {
execute(Runnable command)2556         public void execute(Runnable command)
2557         {
2558             command.run();
2559         }
2560     }
2561 
2562 }
2563