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
10 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
19 package org.apache.archiva.repository.storage;
21 import java.io.Closeable;
22 import java.util.Collections;
23 import java.util.LinkedHashSet;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.NoSuchElementException;
28 import java.util.Spliterator;
29 import java.util.function.Consumer;
30 import java.util.stream.Collectors;
34 * Base Spliterator implementation for Storage Assets. The spliterator visits the tree by depth-first.
35 * For the non-concurrent usage it is guaranteed that children are visited before their
36 * parents. If the spliterator is used in a parallel stream, there is no guarantee for
37 * the order of returned assets.
39 * The estimated size is not accurate, because the tree paths are scanned on demand.
42 * @author Martin Stockhammer <martin_s@apache.org>
44 public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable
46 public static final int DEFAULT_SPLIT_THRESHOLD = 2;
48 // the linked list is used as stack
49 private LinkedList<StorageAsset> workList = new LinkedList<>( );
50 private LinkedHashSet<StorageAsset> visitedContainers = new LinkedHashSet<>( );
51 private long visited = 0;
52 private final int splitThreshold;
53 private static final int CHARACTERISTICS = Spliterator.DISTINCT|Spliterator.NONNULL;
56 AssetSpliterator( int splitThreshold, StorageAsset... assets) {
57 this.splitThreshold = splitThreshold;
58 Collections.addAll( this.workList, assets );
61 AssetSpliterator( StorageAsset... assets) {
62 this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
63 Collections.addAll( this.workList, assets );
67 this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
70 AssetSpliterator( int splitThreshold) {
71 this.splitThreshold = splitThreshold;
75 AssetSpliterator( int splitThreshold, Set<StorageAsset> visitedContainers) {
76 this.visitedContainers.addAll( visitedContainers );
77 this.splitThreshold = splitThreshold;
80 AssetSpliterator( List<StorageAsset> baseList, Set<StorageAsset> visitedContainers) {
81 this.workList.addAll(baseList);
82 this.visitedContainers.addAll( visitedContainers );
83 this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
86 private void add( StorageAsset asset) {
87 workList.addLast( asset );
94 this.workList.clear();
96 this.visitedContainers.clear();
97 this.visitedContainers=null;
101 public boolean tryAdvance( Consumer<? super StorageAsset> action )
105 StorageAsset asset = workList.getLast( );
106 consumeAsset( action, asset );
108 } catch (NoSuchElementException e) {
113 private void consumeAsset( Consumer<? super StorageAsset> action, StorageAsset asset )
115 // Traverse the path to the deepest descent (depth-first)
116 while(retrieveNextPath( asset )) {
117 asset = workList.getLast( );
119 action.accept( workList.removeLast() );
123 private boolean retrieveNextPath(StorageAsset parent) {
124 if (parent.isContainer() && !visitedContainers.contains( parent )) {
125 // Containers after files in stack guarantee the depth-first behaviour
126 workList.addAll( getChildFiles( parent ) );
127 workList.addAll( getChildContainers( parent ) );
128 visitedContainers.add( parent );
136 public void forEachRemaining( Consumer<? super StorageAsset> action )
140 //noinspection InfiniteLoopStatement
143 consumeAsset( action, workList.getLast( ) );
145 } catch (NoSuchElementException e) {
146 // Should happen at the end.
150 List<StorageAsset> getChildContainers( StorageAsset parent) {
151 return parent.list( ).stream( ).filter( StorageAsset::isContainer ).collect( Collectors.toList( ) );
154 List<StorageAsset> getChildFiles(StorageAsset parent) {
155 return parent.list( ).stream( ).filter( StorageAsset::isLeaf ).collect( Collectors.toList( ) );
160 * Splits by moving every second asset to the new spliterator. This allows to start both at similar
161 * tree depths. But it is not guaranteed that they start on the same depth.
162 * The split happens only, if the number of elements in the worklist is greater than 2.
164 * @return the new spliterator if the work list size is greater than 2
167 public Spliterator<StorageAsset> trySplit( )
169 if (workList.size()>splitThreshold) {
170 // We use the elements alternately for the current and the new spliterator
171 // For the parallel scenario we cannot guarantee that children are visited
172 // before their parents
173 final LinkedList<StorageAsset> newWorkList = new LinkedList<>( );
174 final AssetSpliterator newSpliterator = new AssetSpliterator( this.splitThreshold, visitedContainers );
176 //noinspection InfiniteLoopStatement
179 newWorkList.add( workList.getFirst( ) );
180 newSpliterator.add( workList.getFirst( ) );
182 } catch (NoSuchElementException e) {
186 this.workList = newWorkList;
187 return newSpliterator;
194 public long estimateSize( )
196 return workList.size()+visited;
200 public int characteristics( )
202 return CHARACTERISTICS;