View Javadoc
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.eclipse.aether.internal.impl.collect.bf;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.Closeable;
26  import java.util.ArrayDeque;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Optional;
33  import java.util.Queue;
34  import java.util.Set;
35  import java.util.concurrent.Callable;
36  import java.util.concurrent.ConcurrentHashMap;
37  import java.util.concurrent.ExecutionException;
38  import java.util.concurrent.ExecutorService;
39  import java.util.concurrent.Future;
40  import java.util.concurrent.TimeUnit;
41  import java.util.concurrent.TimeoutException;
42  import java.util.stream.Collectors;
43  import java.util.stream.Stream;
44  
45  import org.eclipse.aether.RepositorySystemSession;
46  import org.eclipse.aether.RequestTrace;
47  import org.eclipse.aether.artifact.Artifact;
48  import org.eclipse.aether.artifact.ArtifactType;
49  import org.eclipse.aether.artifact.DefaultArtifact;
50  import org.eclipse.aether.collection.CollectRequest;
51  import org.eclipse.aether.collection.DependencyManager;
52  import org.eclipse.aether.collection.DependencySelector;
53  import org.eclipse.aether.collection.DependencyTraverser;
54  import org.eclipse.aether.collection.VersionFilter;
55  import org.eclipse.aether.graph.DefaultDependencyNode;
56  import org.eclipse.aether.graph.Dependency;
57  import org.eclipse.aether.graph.DependencyNode;
58  import org.eclipse.aether.impl.ArtifactDescriptorReader;
59  import org.eclipse.aether.impl.RemoteRepositoryManager;
60  import org.eclipse.aether.impl.VersionRangeResolver;
61  import org.eclipse.aether.internal.impl.collect.DataPool;
62  import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollectionContext;
63  import org.eclipse.aether.internal.impl.collect.DefaultVersionFilterContext;
64  import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate;
65  import org.eclipse.aether.internal.impl.collect.PremanagedDependency;
66  import org.eclipse.aether.repository.RemoteRepository;
67  import org.eclipse.aether.resolution.ArtifactDescriptorException;
68  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
69  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
70  import org.eclipse.aether.resolution.VersionRangeRequest;
71  import org.eclipse.aether.resolution.VersionRangeResult;
72  import org.eclipse.aether.util.ConfigUtils;
73  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
74  import org.eclipse.aether.util.concurrency.ExecutorUtils;
75  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
76  import org.eclipse.aether.version.Version;
77  
78  import static org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle.find;
79  
80  /**
81   * Breadth-first {@link org.eclipse.aether.impl.DependencyCollector}
82   *
83   * @since 1.8.0
84   */
85  @Singleton
86  @Named(BfDependencyCollector.NAME)
87  public class BfDependencyCollector extends DependencyCollectorDelegate {
88      public static final String NAME = "bf";
89  
90      /**
91       * The key in the repository session's {@link RepositorySystemSession#getConfigProperties()
92       * configuration properties} used to store a {@link Boolean} flag controlling the resolver's skip mode.
93       *
94       * @since 1.8.0
95       */
96      static final String CONFIG_PROP_SKIPPER = "aether.dependencyCollector.bf.skipper";
97  
98      /**
99       * The default value for {@link #CONFIG_PROP_SKIPPER}, {@code true}.
100      *
101      * @since 1.8.0
102      */
103     static final boolean CONFIG_PROP_SKIPPER_DEFAULT = true;
104 
105     /**
106      * The count of threads to be used when collecting POMs in parallel, default value 5.
107      *
108      * @since 1.9.0
109      */
110     static final String CONFIG_PROP_THREADS = "aether.dependencyCollector.bf.threads";
111 
112     @Inject
113     public BfDependencyCollector(
114             RemoteRepositoryManager remoteRepositoryManager,
115             ArtifactDescriptorReader artifactDescriptorReader,
116             VersionRangeResolver versionRangeResolver) {
117         super(remoteRepositoryManager, artifactDescriptorReader, versionRangeResolver);
118     }
119 
120     @SuppressWarnings("checkstyle:parameternumber")
121     @Override
122     protected void doCollectDependencies(
123             RepositorySystemSession session,
124             RequestTrace trace,
125             DataPool pool,
126             DefaultDependencyCollectionContext context,
127             DefaultVersionFilterContext versionContext,
128             CollectRequest request,
129             DependencyNode node,
130             List<RemoteRepository> repositories,
131             List<Dependency> dependencies,
132             List<Dependency> managedDependencies,
133             Results results) {
134         boolean useSkip = ConfigUtils.getBoolean(session, CONFIG_PROP_SKIPPER_DEFAULT, CONFIG_PROP_SKIPPER);
135         int nThreads = ExecutorUtils.threadCount(session, 5, CONFIG_PROP_THREADS);
136         logger.debug("Using thread pool with {} threads to resolve descriptors.", nThreads);
137 
138         if (useSkip) {
139             logger.debug("Collector skip mode enabled");
140         }
141 
142         try (DependencyResolutionSkipper skipper = useSkip
143                         ? DependencyResolutionSkipper.defaultSkipper()
144                         : DependencyResolutionSkipper.neverSkipper();
145                 ParallelDescriptorResolver parallelDescriptorResolver = new ParallelDescriptorResolver(nThreads)) {
146             Args args = new Args(session, pool, context, versionContext, request, skipper, parallelDescriptorResolver);
147 
148             DependencySelector rootDepSelector = session.getDependencySelector() != null
149                     ? session.getDependencySelector().deriveChildSelector(context)
150                     : null;
151             DependencyManager rootDepManager = session.getDependencyManager() != null
152                     ? session.getDependencyManager().deriveChildManager(context)
153                     : null;
154             DependencyTraverser rootDepTraverser = session.getDependencyTraverser() != null
155                     ? session.getDependencyTraverser().deriveChildTraverser(context)
156                     : null;
157             VersionFilter rootVerFilter = session.getVersionFilter() != null
158                     ? session.getVersionFilter().deriveChildFilter(context)
159                     : null;
160 
161             List<DependencyNode> parents = Collections.singletonList(node);
162             for (Dependency dependency : dependencies) {
163                 RequestTrace childTrace =
164                         collectStepTrace(trace, args.request.getRequestContext(), parents, dependency);
165                 DependencyProcessingContext processingContext = new DependencyProcessingContext(
166                         rootDepSelector,
167                         rootDepManager,
168                         rootDepTraverser,
169                         rootVerFilter,
170                         childTrace,
171                         repositories,
172                         managedDependencies,
173                         parents,
174                         dependency,
175                         PremanagedDependency.create(rootDepManager, dependency, false, args.premanagedState));
176                 if (!filter(processingContext)) {
177                     processingContext.withDependency(processingContext.premanagedDependency.getManagedDependency());
178                     resolveArtifactDescriptorAsync(args, processingContext, results);
179                     args.dependencyProcessingQueue.add(processingContext);
180                 }
181             }
182 
183             while (!args.dependencyProcessingQueue.isEmpty()) {
184                 processDependency(
185                         args, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(), false);
186             }
187         }
188     }
189 
190     @SuppressWarnings("checkstyle:parameternumber")
191     private void processDependency(
192             Args args,
193             Results results,
194             DependencyProcessingContext context,
195             List<Artifact> relocations,
196             boolean disableVersionManagement) {
197         Dependency dependency = context.dependency;
198         PremanagedDependency preManaged = context.premanagedDependency;
199 
200         boolean noDescriptor = isLackingDescriptor(dependency.getArtifact());
201         boolean traverse =
202                 !noDescriptor && (context.depTraverser == null || context.depTraverser.traverseDependency(dependency));
203 
204         Future<DescriptorResolutionResult> resolutionResultFuture = args.resolver.find(dependency.getArtifact());
205         DescriptorResolutionResult resolutionResult;
206         VersionRangeResult rangeResult;
207         try {
208             resolutionResult = resolutionResultFuture.get();
209             rangeResult = resolutionResult.rangeResult;
210         } catch (Exception e) {
211             results.addException(dependency, e, context.parents);
212             return;
213         }
214 
215         Set<Version> versions = resolutionResult.descriptors.keySet();
216         for (Version version : versions) {
217             Artifact originalArtifact = dependency.getArtifact().setVersion(version.toString());
218             Dependency d = dependency.setArtifact(originalArtifact);
219 
220             final ArtifactDescriptorResult descriptorResult = resolutionResult.descriptors.get(version);
221             if (descriptorResult != null) {
222                 d = d.setArtifact(descriptorResult.getArtifact());
223 
224                 int cycleEntry = find(context.parents, d.getArtifact());
225                 if (cycleEntry >= 0) {
226                     results.addCycle(context.parents, cycleEntry, d);
227                     DependencyNode cycleNode = context.parents.get(cycleEntry);
228                     if (cycleNode.getDependency() != null) {
229                         DefaultDependencyNode child = createDependencyNode(
230                                 relocations, preManaged, rangeResult, version, d, descriptorResult, cycleNode);
231                         context.getParent().getChildren().add(child);
232                         continue;
233                     }
234                 }
235 
236                 if (!descriptorResult.getRelocations().isEmpty()) {
237                     boolean disableVersionManagementSubsequently =
238                             originalArtifact.getGroupId().equals(d.getArtifact().getGroupId())
239                                     && originalArtifact
240                                             .getArtifactId()
241                                             .equals(d.getArtifact().getArtifactId());
242 
243                     PremanagedDependency premanagedDependency = PremanagedDependency.create(
244                             context.depManager, d, disableVersionManagementSubsequently, args.premanagedState);
245                     DependencyProcessingContext relocatedContext = new DependencyProcessingContext(
246                             context.depSelector,
247                             context.depManager,
248                             context.depTraverser,
249                             context.verFilter,
250                             context.trace,
251                             context.repositories,
252                             descriptorResult.getManagedDependencies(),
253                             context.parents,
254                             d,
255                             premanagedDependency);
256 
257                     if (!filter(relocatedContext)) {
258                         relocatedContext.withDependency(premanagedDependency.getManagedDependency());
259                         resolveArtifactDescriptorAsync(args, relocatedContext, results);
260                         processDependency(
261                                 args,
262                                 results,
263                                 relocatedContext,
264                                 descriptorResult.getRelocations(),
265                                 disableVersionManagementSubsequently);
266                     }
267 
268                     return;
269                 } else {
270                     d = args.pool.intern(d.setArtifact(args.pool.intern(d.getArtifact())));
271 
272                     List<RemoteRepository> repos =
273                             getRemoteRepositories(rangeResult.getRepository(version), context.repositories);
274 
275                     DefaultDependencyNode child = createDependencyNode(
276                             relocations,
277                             preManaged,
278                             rangeResult,
279                             version,
280                             d,
281                             descriptorResult.getAliases(),
282                             repos,
283                             args.request.getRequestContext());
284 
285                     context.getParent().getChildren().add(child);
286 
287                     boolean recurse =
288                             traverse && !descriptorResult.getDependencies().isEmpty();
289                     DependencyProcessingContext parentContext = context.withDependency(d);
290                     if (recurse) {
291                         doRecurse(args, parentContext, descriptorResult, child, results, disableVersionManagement);
292                     } else if (!args.skipper.skipResolution(child, parentContext.parents)) {
293                         List<DependencyNode> parents = new ArrayList<>(parentContext.parents.size() + 1);
294                         parents.addAll(parentContext.parents);
295                         parents.add(child);
296                         args.skipper.cache(child, parents);
297                     }
298                 }
299             } else {
300                 List<RemoteRepository> repos =
301                         getRemoteRepositories(rangeResult.getRepository(version), context.repositories);
302                 DefaultDependencyNode child = createDependencyNode(
303                         relocations,
304                         preManaged,
305                         rangeResult,
306                         version,
307                         d,
308                         null,
309                         repos,
310                         args.request.getRequestContext());
311                 context.getParent().getChildren().add(child);
312             }
313         }
314     }
315 
316     @SuppressWarnings("checkstyle:parameternumber")
317     private void doRecurse(
318             Args args,
319             DependencyProcessingContext parentContext,
320             ArtifactDescriptorResult descriptorResult,
321             DefaultDependencyNode child,
322             Results results,
323             boolean disableVersionManagement) {
324         DefaultDependencyCollectionContext context = args.collectionContext;
325         context.set(parentContext.dependency, descriptorResult.getManagedDependencies());
326 
327         DependencySelector childSelector =
328                 parentContext.depSelector != null ? parentContext.depSelector.deriveChildSelector(context) : null;
329         DependencyManager childManager =
330                 parentContext.depManager != null ? parentContext.depManager.deriveChildManager(context) : null;
331         DependencyTraverser childTraverser =
332                 parentContext.depTraverser != null ? parentContext.depTraverser.deriveChildTraverser(context) : null;
333         VersionFilter childFilter =
334                 parentContext.verFilter != null ? parentContext.verFilter.deriveChildFilter(context) : null;
335 
336         final List<RemoteRepository> childRepos = args.ignoreRepos
337                 ? parentContext.repositories
338                 : remoteRepositoryManager.aggregateRepositories(
339                         args.session, parentContext.repositories, descriptorResult.getRepositories(), true);
340 
341         Object key = args.pool.toKey(
342                 parentContext.dependency.getArtifact(),
343                 childRepos,
344                 childSelector,
345                 childManager,
346                 childTraverser,
347                 childFilter);
348 
349         List<DependencyNode> children = args.pool.getChildren(key);
350         if (children == null) {
351             boolean skipResolution = args.skipper.skipResolution(child, parentContext.parents);
352             if (!skipResolution) {
353                 List<DependencyNode> parents = new ArrayList<>(parentContext.parents.size() + 1);
354                 parents.addAll(parentContext.parents);
355                 parents.add(child);
356                 for (Dependency dependency : descriptorResult.getDependencies()) {
357                     RequestTrace childTrace = collectStepTrace(
358                             parentContext.trace, args.request.getRequestContext(), parents, dependency);
359                     PremanagedDependency premanagedDependency = PremanagedDependency.create(
360                             childManager, dependency, disableVersionManagement, args.premanagedState);
361                     DependencyProcessingContext processingContext = new DependencyProcessingContext(
362                             childSelector,
363                             childManager,
364                             childTraverser,
365                             childFilter,
366                             childTrace,
367                             childRepos,
368                             descriptorResult.getManagedDependencies(),
369                             parents,
370                             dependency,
371                             premanagedDependency);
372                     if (!filter(processingContext)) {
373                         // resolve descriptors ahead for managed dependency
374                         processingContext.withDependency(processingContext.premanagedDependency.getManagedDependency());
375                         resolveArtifactDescriptorAsync(args, processingContext, results);
376                         args.dependencyProcessingQueue.add(processingContext);
377                     }
378                 }
379                 args.pool.putChildren(key, child.getChildren());
380                 args.skipper.cache(child, parents);
381             }
382         } else {
383             child.setChildren(children);
384         }
385     }
386 
387     private boolean filter(DependencyProcessingContext context) {
388         return context.depSelector != null && !context.depSelector.selectDependency(context.dependency);
389     }
390 
391     private void resolveArtifactDescriptorAsync(Args args, DependencyProcessingContext context, Results results) {
392         Dependency dependency = context.dependency;
393         args.resolver.resolveDescriptors(dependency.getArtifact(), () -> {
394             VersionRangeRequest rangeRequest = createVersionRangeRequest(
395                     args.request.getRequestContext(), context.trace, context.repositories, dependency);
396             VersionRangeResult rangeResult = cachedResolveRangeResult(rangeRequest, args.pool, args.session);
397             List<? extends Version> versions =
398                     filterVersions(dependency, rangeResult, context.verFilter, args.versionContext);
399 
400             // resolve newer version first to maximize benefits of skipper
401             Collections.reverse(versions);
402 
403             Map<Version, ArtifactDescriptorResult> descriptors = new ConcurrentHashMap<>(versions.size());
404             Stream<? extends Version> stream = versions.size() > 1 ? versions.parallelStream() : versions.stream();
405             stream.forEach(version -> Optional.ofNullable(
406                             resolveDescriptorForVersion(args, context, results, dependency, version))
407                     .ifPresent(r -> descriptors.put(version, r)));
408 
409             DescriptorResolutionResult resolutionResult =
410                     new DescriptorResolutionResult(dependency.getArtifact(), rangeResult);
411             // keep original sequence
412             versions.forEach(version -> resolutionResult.descriptors.put(version, descriptors.get(version)));
413             // populate for versions in version range
414             resolutionResult.flatten().forEach(dr -> args.resolver.cacheVersionRangeDescriptor(dr.artifact, dr));
415 
416             return resolutionResult;
417         });
418     }
419 
420     private ArtifactDescriptorResult resolveDescriptorForVersion(
421             Args args, DependencyProcessingContext context, Results results, Dependency dependency, Version version) {
422         Artifact original = dependency.getArtifact();
423         Artifact newArtifact = new DefaultArtifact(
424                 original.getGroupId(),
425                 original.getArtifactId(),
426                 original.getClassifier(),
427                 original.getExtension(),
428                 version.toString(),
429                 original.getProperties(),
430                 (ArtifactType) null);
431         Dependency newDependency =
432                 new Dependency(newArtifact, dependency.getScope(), dependency.isOptional(), dependency.getExclusions());
433         DependencyProcessingContext newContext = context.copy();
434 
435         ArtifactDescriptorRequest descriptorRequest = createArtifactDescriptorRequest(
436                 args.request.getRequestContext(), context.trace, newContext.repositories, newDependency);
437         return isLackingDescriptor(newArtifact)
438                 ? new ArtifactDescriptorResult(descriptorRequest)
439                 : resolveCachedArtifactDescriptor(
440                         args.pool, descriptorRequest, args.session, newContext.withDependency(newDependency), results);
441     }
442 
443     private ArtifactDescriptorResult resolveCachedArtifactDescriptor(
444             DataPool pool,
445             ArtifactDescriptorRequest descriptorRequest,
446             RepositorySystemSession session,
447             DependencyProcessingContext context,
448             Results results) {
449         Object key = pool.toKey(descriptorRequest);
450         ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest);
451         if (descriptorResult == null) {
452             try {
453                 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest);
454                 pool.putDescriptor(key, descriptorResult);
455             } catch (ArtifactDescriptorException e) {
456                 results.addException(context.dependency, e, context.parents);
457                 pool.putDescriptor(key, e);
458                 return null;
459             }
460 
461         } else if (descriptorResult == DataPool.NO_DESCRIPTOR) {
462             return null;
463         }
464 
465         return descriptorResult;
466     }
467 
468     static class ParallelDescriptorResolver implements Closeable {
469         private final ExecutorService executorService;
470 
471         /**
472          * Artifact ID -> Future of DescriptorResolutionResult
473          */
474         private final Map<String, Future<DescriptorResolutionResult>> results = new ConcurrentHashMap<>(256);
475 
476         ParallelDescriptorResolver(int threads) {
477             this.executorService = ExecutorUtils.threadPool(threads, getClass().getSimpleName() + "-");
478         }
479 
480         void resolveDescriptors(Artifact artifact, Callable<DescriptorResolutionResult> callable) {
481             results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> this.executorService.submit(callable));
482         }
483 
484         void cacheVersionRangeDescriptor(Artifact artifact, DescriptorResolutionResult resolutionResult) {
485             results.computeIfAbsent(ArtifactIdUtils.toId(artifact), key -> new DoneFuture<>(resolutionResult));
486         }
487 
488         Future<DescriptorResolutionResult> find(Artifact artifact) {
489             return results.get(ArtifactIdUtils.toId(artifact));
490         }
491 
492         @Override
493         public void close() {
494             executorService.shutdown();
495         }
496     }
497 
498     static class DoneFuture<V> implements Future<V> {
499         private final V v;
500 
501         DoneFuture(V v) {
502             this.v = v;
503         }
504 
505         @Override
506         public boolean cancel(boolean mayInterruptIfRunning) {
507             return false;
508         }
509 
510         @Override
511         public boolean isCancelled() {
512             return false;
513         }
514 
515         @Override
516         public boolean isDone() {
517             return true;
518         }
519 
520         @Override
521         public V get() throws InterruptedException, ExecutionException {
522             return v;
523         }
524 
525         @Override
526         public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
527             return v;
528         }
529     }
530 
531     static class DescriptorResolutionResult {
532         Artifact artifact;
533 
534         VersionRangeResult rangeResult;
535 
536         Map<Version, ArtifactDescriptorResult> descriptors;
537 
538         DescriptorResolutionResult(Artifact artifact, VersionRangeResult rangeResult) {
539             this.artifact = artifact;
540             this.rangeResult = rangeResult;
541             this.descriptors = new LinkedHashMap<>(rangeResult.getVersions().size());
542         }
543 
544         DescriptorResolutionResult(
545                 VersionRangeResult rangeResult, Version version, ArtifactDescriptorResult descriptor) {
546             // NOTE: In case of A1 -> A2 relocation this happens:
547             // ArtifactDescriptorResult read by ArtifactDescriptorResultReader for A1
548             // will return instance that will have artifact = A2 (as RelocatedArtifact).
549             // So to properly "key" this instance, we need to use "originally requested" A1 instead!
550             // In short:
551             // ArtifactDescriptorRequest.artifact != ArtifactDescriptorResult.artifact WHEN relocation in play
552             // otherwise (no relocation), they are EQUAL.
553             this(descriptor.getRequest().getArtifact(), rangeResult);
554             this.descriptors.put(version, descriptor);
555         }
556 
557         List<DescriptorResolutionResult> flatten() {
558             if (descriptors.size() > 1) {
559                 return descriptors.entrySet().stream()
560                         .map(e -> new DescriptorResolutionResult(rangeResult, e.getKey(), e.getValue()))
561                         .collect(Collectors.toList());
562             } else {
563                 return Collections.emptyList();
564             }
565         }
566     }
567 
568     static class Args {
569 
570         final RepositorySystemSession session;
571 
572         final boolean ignoreRepos;
573 
574         final boolean premanagedState;
575 
576         final DataPool pool;
577 
578         final Queue<DependencyProcessingContext> dependencyProcessingQueue = new ArrayDeque<>(128);
579 
580         final DefaultDependencyCollectionContext collectionContext;
581 
582         final DefaultVersionFilterContext versionContext;
583 
584         final CollectRequest request;
585 
586         final DependencyResolutionSkipper skipper;
587 
588         final ParallelDescriptorResolver resolver;
589 
590         Args(
591                 RepositorySystemSession session,
592                 DataPool pool,
593                 DefaultDependencyCollectionContext collectionContext,
594                 DefaultVersionFilterContext versionContext,
595                 CollectRequest request,
596                 DependencyResolutionSkipper skipper,
597                 ParallelDescriptorResolver resolver) {
598             this.session = session;
599             this.request = request;
600             this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories();
601             this.premanagedState = ConfigUtils.getBoolean(session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE);
602             this.pool = pool;
603             this.collectionContext = collectionContext;
604             this.versionContext = versionContext;
605             this.skipper = skipper;
606             this.resolver = resolver;
607         }
608     }
609 }