]> source.dussan.org Git - archiva.git/blob
312615b7f0a2474b889adb4127f035f9131fad9b
[archiva.git] /
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  * 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
16  * under the License.
17  */
18
19 package org.apache.archiva.repository.storage;
20
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;
27 import java.util.Set;
28 import java.util.Spliterator;
29 import java.util.function.Consumer;
30 import java.util.stream.Collectors;
31
32 /**
33  *
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.
38  *
39  * The estimated size is not accurate, because the tree paths are scanned on demand.
40  *
41  * @since 3.0
42  * @author Martin Stockhammer <martin_s@apache.org>
43  */
44 public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable
45 {
46     public static final int DEFAULT_SPLIT_THRESHOLD = 2;
47
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;
54
55
56     AssetSpliterator( int splitThreshold, StorageAsset... assets) {
57         this.splitThreshold = splitThreshold;
58         Collections.addAll( this.workList, assets );
59     }
60
61     AssetSpliterator( StorageAsset... assets) {
62         this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
63         Collections.addAll( this.workList, assets );
64     }
65
66     AssetSpliterator() {
67         this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
68     }
69
70     AssetSpliterator( int splitThreshold) {
71         this.splitThreshold = splitThreshold;
72     }
73
74
75     AssetSpliterator( int splitThreshold, Set<StorageAsset> visitedContainers) {
76         this.visitedContainers.addAll( visitedContainers );
77         this.splitThreshold = splitThreshold;
78     }
79
80     AssetSpliterator( List<StorageAsset> baseList, Set<StorageAsset> visitedContainers) {
81         this.workList.addAll(baseList);
82         this.visitedContainers.addAll( visitedContainers );
83         this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
84     }
85
86     private void add( StorageAsset asset) {
87         workList.addLast( asset );
88     }
89
90
91     @Override
92     public void close( )
93     {
94         this.workList.clear();
95         this.workList=null;
96         this.visitedContainers.clear();
97         this.visitedContainers=null;
98     }
99
100     @Override
101     public boolean tryAdvance( Consumer<? super StorageAsset> action )
102     {
103         try
104         {
105             StorageAsset asset = workList.getLast( );
106             consumeAsset( action, asset );
107             return true;
108         } catch (NoSuchElementException e) {
109             return false;
110         }
111     }
112
113     private void consumeAsset( Consumer<? super StorageAsset> action, StorageAsset asset )
114     {
115         // Traverse the path to the deepest descent (depth-first)
116         while(retrieveNextPath( asset )) {
117             asset = workList.getLast( );
118         }
119         action.accept( workList.removeLast() );
120         visited++;
121     }
122
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 );
129             return true;
130         } else {
131             return false;
132         }
133     }
134
135     @Override
136     public void forEachRemaining( Consumer<? super StorageAsset> action )
137     {
138         try
139         {
140             //noinspection InfiniteLoopStatement
141             while ( true )
142             {
143                 consumeAsset( action, workList.getLast( ) );
144             }
145         } catch (NoSuchElementException e) {
146             // Should happen at the end.
147         }
148     }
149
150     List<StorageAsset> getChildContainers( StorageAsset parent) {
151         return parent.list( ).stream( ).filter( StorageAsset::isContainer ).collect( Collectors.toList( ) );
152     }
153
154     List<StorageAsset> getChildFiles(StorageAsset parent) {
155         return parent.list( ).stream( ).filter( StorageAsset::isLeaf ).collect( Collectors.toList( ) );
156     }
157
158
159     /**
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.
163      *
164      * @return the new spliterator if the work list size is greater than 2
165      */
166     @Override
167     public Spliterator<StorageAsset> trySplit( )
168     {
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 );
175             try {
176                 //noinspection InfiniteLoopStatement
177                 while (true)
178                 {
179                     newWorkList.add( workList.getFirst( ) );
180                     newSpliterator.add( workList.getFirst( ) );
181                 }
182             } catch (NoSuchElementException e) {
183                 //
184             }
185             // Swap the worklist
186             this.workList = newWorkList;
187             return newSpliterator;
188         } else {
189             return null;
190         }
191     }
192
193     @Override
194     public long estimateSize( )
195     {
196         return workList.size()+visited;
197     }
198
199     @Override
200     public int characteristics( )
201     {
202         return CHARACTERISTICS;
203     }
204
205
206 }