+++ /dev/null
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.archiva.repository.storage;
-
-import java.io.Closeable;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.Spliterator;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-/**
- *
- * Base Spliterator implementation for Storage Assets. The spliterator visits the tree by depth-first.
- * For the non-concurrent usage it is guaranteed that children are visited before their
- * parents. If the spliterator is used in a parallel stream, there is no guarantee for
- * the order of returned assets.
- *
- * The estimated size is not accurate, because the tree paths are scanned on demand.
- *
- * @since 3.0
- * @author Martin Stockhammer <martin_s@apache.org>
- */
-public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable
-{
- public static final int DEFAULT_SPLIT_THRESHOLD = 2;
-
- // the linked list is used as stack
- private LinkedList<StorageAsset> workList = new LinkedList<>( );
- private LinkedHashSet<StorageAsset> visitedContainers = new LinkedHashSet<>( );
- private long visited = 0;
- private final int splitThreshold;
- private static final int CHARACTERISTICS = Spliterator.DISTINCT|Spliterator.NONNULL;
-
-
- AssetSpliterator( int splitThreshold, StorageAsset... assets) {
- this.splitThreshold = splitThreshold;
- Collections.addAll( this.workList, assets );
- }
-
- AssetSpliterator( StorageAsset... assets) {
- this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
- Collections.addAll( this.workList, assets );
- }
-
- AssetSpliterator() {
- this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
- }
-
- AssetSpliterator( int splitThreshold) {
- this.splitThreshold = splitThreshold;
- }
-
-
- AssetSpliterator( int splitThreshold, Set<StorageAsset> visitedContainers) {
- this.visitedContainers.addAll( visitedContainers );
- this.splitThreshold = splitThreshold;
- }
-
- AssetSpliterator( List<StorageAsset> baseList, Set<StorageAsset> visitedContainers) {
- this.workList.addAll(baseList);
- this.visitedContainers.addAll( visitedContainers );
- this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
- }
-
- private void add( StorageAsset asset) {
- workList.addLast( asset );
- }
-
-
- @Override
- public void close( )
- {
- this.workList.clear();
- this.workList=null;
- this.visitedContainers.clear();
- this.visitedContainers=null;
- }
-
- @Override
- public boolean tryAdvance( Consumer<? super StorageAsset> action )
- {
- try
- {
- StorageAsset asset = workList.getLast( );
- consumeAsset( action, asset );
- return true;
- } catch (NoSuchElementException e) {
- return false;
- }
- }
-
- private void consumeAsset( Consumer<? super StorageAsset> action, StorageAsset asset )
- {
- // Traverse the path to the deepest descent (depth-first)
- while(retrieveNextPath( asset )) {
- asset = workList.getLast( );
- }
- action.accept( workList.removeLast() );
- visited++;
- }
-
- private boolean retrieveNextPath(StorageAsset parent) {
- if (parent.isContainer() && !visitedContainers.contains( parent )) {
- // Containers after files in stack guarantee the depth-first behaviour
- workList.addAll( getChildFiles( parent ) );
- workList.addAll( getChildContainers( parent ) );
- visitedContainers.add( parent );
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public void forEachRemaining( Consumer<? super StorageAsset> action )
- {
- try
- {
- //noinspection InfiniteLoopStatement
- while ( true )
- {
- consumeAsset( action, workList.getLast( ) );
- }
- } catch (NoSuchElementException e) {
- // Should happen at the end.
- }
- }
-
- // In reverse order
- List<StorageAsset> getChildContainers( StorageAsset parent) {
- final List<StorageAsset> children = parent.list( );
- final int len = children.size( );
- return IntStream.range( 0, children.size( ) ).mapToObj( i ->
- children.get(len - i - 1)).filter( StorageAsset::isContainer ).collect( Collectors.toList( ) );
- }
-
- // In reverse order
- List<StorageAsset> getChildFiles(StorageAsset parent) {
- final List<StorageAsset> children = parent.list( );
- final int len = children.size( );
- return IntStream.range( 0, children.size( ) ).mapToObj( i ->
- children.get(len - i - 1)).filter( StorageAsset::isLeaf ).collect( Collectors.toList( ) );
- }
-
-
- /**
- * Splits by moving every second asset to the new spliterator. This allows to start both at similar
- * tree depths. But it is not guaranteed that they start on the same depth.
- * The split happens only, if the number of elements in the worklist is greater than 2.
- *
- * @return the new spliterator if the work list size is greater than 2
- */
- @Override
- public Spliterator<StorageAsset> trySplit( )
- {
- if (workList.size()>splitThreshold) {
- // We use the elements alternately for the current and the new spliterator
- // For the parallel scenario we cannot guarantee that children are visited
- // before their parents
- final LinkedList<StorageAsset> newWorkList = new LinkedList<>( );
- final AssetSpliterator newSpliterator = new AssetSpliterator( this.splitThreshold, visitedContainers );
- try {
- //noinspection InfiniteLoopStatement
- while (true)
- {
- newWorkList.add( workList.getFirst( ) );
- newSpliterator.add( workList.getFirst( ) );
- }
- } catch (NoSuchElementException e) {
- //
- }
- // Swap the worklist
- this.workList = newWorkList;
- return newSpliterator;
- } else {
- return null;
- }
- }
-
- @Override
- public long estimateSize( )
- {
- return workList.size()+visited;
- }
-
- @Override
- public int characteristics( )
- {
- return CHARACTERISTICS;
- }
-
-
-}
--- /dev/null
+package org.apache.archiva.repository.storage.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.repository.storage.StorageAsset;
+
+import java.io.Closeable;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ *
+ * Base Spliterator implementation for Storage Assets. The spliterator visits the tree by depth-first.
+ * For the non-concurrent usage it is guaranteed that children are visited before their
+ * parents. If the spliterator is used in a parallel stream, there is no guarantee for
+ * the order of returned assets.
+ *
+ * The estimated size is not accurate, because the tree paths are scanned on demand (lazy loaded)
+ *
+ * The spliterator returns the status of the assets at the time of retrieval. If modifications occur
+ * during traversal the returned assets may not represent the latest state.
+ * There is no check for modifications during traversal and no <code>{@link java.util.ConcurrentModificationException}</code> are thrown.
+ *
+ *
+ *
+ * @since 3.0
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable
+{
+ public static final int DEFAULT_SPLIT_THRESHOLD = 2;
+
+ // the linked list is used as stack
+ private LinkedList<StorageAsset> workList = new LinkedList<>( );
+ private LinkedHashSet<StorageAsset> visitedContainers = new LinkedHashSet<>( );
+ private long visited = 0;
+ private final int splitThreshold;
+ private static final int CHARACTERISTICS = Spliterator.DISTINCT|Spliterator.NONNULL|Spliterator.CONCURRENT;
+
+
+ public AssetSpliterator( int splitThreshold, StorageAsset... assets) {
+ this.splitThreshold = splitThreshold;
+ init( assets );
+ }
+
+ private void init( StorageAsset[] assets )
+ {
+ if (assets.length==0 || assets[0] == null) {
+ throw new IllegalArgumentException( "There must be at least one non-null asset" );
+ }
+ Collections.addAll( this.workList, assets );
+ retrieveNextPath( this.workList.get( 0 ) );
+ }
+
+ public AssetSpliterator( StorageAsset... assets) {
+ this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
+ init( assets );
+ }
+
+ protected AssetSpliterator() {
+ this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
+ }
+
+ protected AssetSpliterator( int splitThreshold) {
+ this.splitThreshold = splitThreshold;
+ }
+
+
+ protected AssetSpliterator( int splitThreshold, Set<StorageAsset> visitedContainers) {
+ this.visitedContainers.addAll( visitedContainers );
+ this.splitThreshold = splitThreshold;
+ }
+
+ protected AssetSpliterator( List<StorageAsset> baseList, Set<StorageAsset> visitedContainers) {
+ this.workList.addAll(baseList);
+ retrieveNextPath( this.workList.get( 0 ) );
+ this.visitedContainers.addAll( visitedContainers );
+ this.splitThreshold = DEFAULT_SPLIT_THRESHOLD;
+ }
+
+ private void add( StorageAsset asset) {
+ workList.addLast( asset );
+ }
+
+
+ @Override
+ public void close( )
+ {
+ this.workList.clear();
+ this.workList=null;
+ this.visitedContainers.clear();
+ this.visitedContainers=null;
+ }
+
+ @Override
+ public boolean tryAdvance( Consumer<? super StorageAsset> action )
+ {
+ try
+ {
+ StorageAsset asset = workList.getLast( );
+ consumeAsset( action, asset );
+ return true;
+ } catch (NoSuchElementException e) {
+ return false;
+ }
+ }
+
+ private void consumeAsset( Consumer<? super StorageAsset> action, StorageAsset asset )
+ {
+ // Traverse the path to the deepest descent (depth-first)
+ while(retrieveNextPath( asset )) {
+ asset = workList.getLast( );
+ }
+ action.accept( workList.removeLast() );
+ visited++;
+ }
+
+ private boolean retrieveNextPath(StorageAsset parent) {
+ if (parent.isContainer() && !visitedContainers.contains( parent )) {
+ // Containers after files in stack guarantee the depth-first behaviour
+ workList.addAll( getChildFiles( parent ) );
+ workList.addAll( getChildContainers( parent ) );
+ visitedContainers.add( parent );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void forEachRemaining( Consumer<? super StorageAsset> action )
+ {
+ try
+ {
+ //noinspection InfiniteLoopStatement
+ while ( true )
+ {
+ consumeAsset( action, workList.getLast( ) );
+ }
+ } catch (NoSuchElementException e) {
+ // Should happen at the end.
+ }
+ }
+
+ // Assets are returned in reverse order
+ List<StorageAsset> getChildContainers( StorageAsset parent) {
+ final List<StorageAsset> children = parent.list( );
+ final int len = children.size( );
+ return IntStream.range( 0, children.size( ) ).mapToObj( i ->
+ children.get(len - i - 1)).filter( StorageAsset::isContainer ).collect( Collectors.toList( ) );
+ }
+
+ // Assets are returned in reverse order
+ List<StorageAsset> getChildFiles(StorageAsset parent) {
+ final List<StorageAsset> children = parent.list( );
+ final int len = children.size( );
+ return IntStream.range( 0, children.size( ) ).mapToObj( i ->
+ children.get(len - i - 1)).filter( StorageAsset::isLeaf ).collect( Collectors.toList( ) );
+ }
+
+
+ /**
+ * Splits by moving every second asset to the new spliterator. This allows to start both at similar
+ * tree depths. But it is not guaranteed that they start on the same depth.
+ * The split happens only, if the number of elements in the worklist is greater than 2.
+ *
+ * @return the new spliterator if the work list size is greater than 2
+ */
+ @Override
+ public Spliterator<StorageAsset> trySplit( )
+ {
+ if (workList.size()>splitThreshold) {
+ // We use the elements alternately for the current and the new spliterator
+ // For the parallel scenario we cannot guarantee that children are visited
+ // before their parents
+ final LinkedList<StorageAsset> newWorkList = new LinkedList<>( );
+ final AssetSpliterator newSpliterator = new AssetSpliterator( this.splitThreshold, visitedContainers );
+ try {
+ //noinspection InfiniteLoopStatement
+ while (true)
+ {
+ newWorkList.add( workList.removeFirst( ) );
+ newSpliterator.add( workList.removeFirst( ) );
+ }
+ } catch (NoSuchElementException e) {
+ //
+ }
+ // Swap the worklist
+ this.workList = newWorkList;
+ return newSpliterator;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public long estimateSize( )
+ {
+ return workList.size()+visited;
+ }
+
+ @Override
+ public int characteristics( )
+ {
+ return CHARACTERISTICS;
+ }
+
+
+}
--- /dev/null
+package org.apache.archiva.repository.storage.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.repository.storage.StorageAsset;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ *
+ * Utility class for traversing the asset tree recursively and stream based access to the assets.
+ *
+ * @since 3.0
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class StorageUtil
+{
+ /**
+ * Walk the tree starting at the given asset. The consumer is called for each asset found.
+ * It runs a depth-first search where children are consumed before their parents.
+ *
+ * @param start the starting asset
+ * @param consumer the consumer that is applied to each asset
+ */
+ public static void walk( StorageAsset start, Consumer<StorageAsset> consumer ) {
+ try(Stream<StorageAsset> assetStream = newAssetStream( start, false )) {
+ assetStream.forEach( consumer::accept );
+ }
+ }
+
+ /**
+ * Walk the tree starting at the given asset. The consumer function is called for each asset found
+ * as long as it returns <code>true</code> as result. If the function returns <code>false</code> the
+ * processing stops.
+ * It runs a depth-first search where children are consumed before their parents.
+ *
+ * @param start the starting asset
+ * @param consumer the consumer function that is applied to each asset and that has to return <code>true</code>,
+ * if the walk should continue.
+ */
+ public static void walk( StorageAsset start, Function<StorageAsset, Boolean> consumer ) {
+ try(Stream<StorageAsset> assetStream = newAssetStream( start, false )) {
+ assetStream.anyMatch( a -> !consumer.apply( a ) );
+ }
+ }
+
+
+ /**
+ * Returns a stream of assets starting at the given start node. The returned stream returns a closable
+ * stream and should always be used in a try-with-resources statement.
+ *
+ * @param start the starting asset
+ * @param parallel <code>true</code>, if a parallel stream should be created, otherwise <code>false</code>
+ * @return the newly created stream
+ */
+ public static Stream<StorageAsset> newAssetStream( StorageAsset start, boolean parallel )
+ {
+ return StreamSupport.stream( new AssetSpliterator( start ), parallel );
+ }
+
+
+ /**
+ * Returns a non-parallel stream.
+ * Calls {@link #newAssetStream(StorageAsset, boolean)} with <code>parallel=false</code>.
+ *
+ * @param start the starting asset
+ * @return the returned stream object
+ */
+ public static Stream<StorageAsset> newAssetStream( StorageAsset start) {
+ return newAssetStream( start, false );
+ }
+
+
+}
+++ /dev/null
-package org.apache.archiva.repository.storage;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.archiva.repository.storage.mock.MockAsset;
-import org.junit.jupiter.api.Test;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Test the AssetSpliterator class
- *
- * @author Martin Stockhammer <martin_s@apache.org>
- */
-class AssetSpliteratorTest
-{
-
- private StorageAsset createTree() {
- MockAsset root = new MockAsset( "" );
- for (int i=0; i<10; i++) {
- String name1 = "a" + String.format("%03d",i);
- MockAsset parent1 = new MockAsset( root, name1 );
- for (int k=0; k<15; k++) {
- String name2 = name1 + String.format("%03d", k);
- MockAsset parent2 = new MockAsset( parent1, name2 );
- for (int u=0; u<5; u++) {
- String name3 = name2 + String.format("%03d", u);
- MockAsset parent3 = new MockAsset( parent2, name3 );
- }
- }
- }
- return root;
- }
-
- private class Status {
- LinkedList<StorageAsset> visited = new LinkedList<>( );
-
- Status() {
-
- }
-
- public void add(StorageAsset asset) {
- visited.addLast( asset );
- }
-
- public StorageAsset getLast() {
- return visited.getLast( );
- }
-
- public List<StorageAsset> getVisited() {
- return visited;
- }
-
- public int size() {
- return visited.size( );
- }
- }
-
- @Test
- void tryAdvance( )
- {
- StorageAsset root = createTree( );
- AssetSpliterator spliterator = new AssetSpliterator( root );
- final StorageAsset expectedTarget = root.list( ).get( 0 ).list( ).get( 0 ).list( ).get( 0 );
- final Status status = new Status( );
- spliterator.tryAdvance( a -> status.add( a ) );
- assertEquals( expectedTarget, status.getLast( ) );
- }
-
- @Test
- void forEachRemaining( )
- {
- }
-
- @Test
- void trySplit( )
- {
- }
-}
\ No newline at end of file
public MockAsset( String name ) {
this.name = name;
- this.path = "";
+ this.path = "/";
}
public MockAsset( MockAsset parent, String name ) {
this.parent = parent;
- this.path = parent.getPath( ) + "/" + name;
+ this.path = (parent.hasParent()?parent.getPath( ):"") + "/" + name;
this.name = name;
parent.registerChild( this );
}
{
return getPath();
}
+
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o ) return true;
+ if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+ MockAsset mockAsset = (MockAsset) o;
+
+ return path.equals( mockAsset.path );
+ }
+
+ @Override
+ public int hashCode( )
+ {
+ return path.hashCode( );
+ }
}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.archiva.repository.storage.util;
+
+import org.apache.archiva.repository.storage.StorageAsset;
+import org.apache.archiva.repository.storage.mock.MockAsset;
+import org.junit.jupiter.api.Test;
+
+import java.util.Spliterator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Test the AssetSpliterator class
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+class AssetSpliteratorTest
+{
+
+ private static int LEVEL1 = 10;
+ private static int LEVEL2 = 15;
+ private static int LEVEL3 = 5;
+
+
+
+ private StorageAsset createTree() {
+ return createTree( LEVEL1, LEVEL2, LEVEL3 );
+ }
+
+ private StorageAsset createTree(int... levelElements) {
+ MockAsset root = new MockAsset( "" );
+ recurseSubTree( root, 0, levelElements );
+ return root;
+ }
+
+ private void recurseSubTree(MockAsset parent, int level, int[] levelElements) {
+ if (level < levelElements.length)
+ {
+ for ( int k = 0; k < levelElements[level]; k++ )
+ {
+ String name = parent.getName( ) + String.format( "%03d", k );
+ MockAsset asset = new MockAsset( parent, name );
+ recurseSubTree( asset, level + 1, levelElements );
+ }
+ }
+ }
+
+ @Test
+ void tryAdvance( )
+ {
+ StorageAsset root = createTree( );
+ AssetSpliterator spliterator = new AssetSpliterator( root );
+ final ConsumeVisitStatus status = new ConsumeVisitStatus( );
+ StorageAsset expectedTarget = root.list( ).get( 0 ).list( ).get( 0 ).list( ).get( 0 );
+ spliterator.tryAdvance( status );
+ assertEquals( 1, status.size( ) );
+ assertEquals( expectedTarget, status.getLast( ) );
+
+ spliterator.tryAdvance( status );
+ assertEquals( 2, status.size( ) );
+ expectedTarget = root.list( ).get( 0 ).list( ).get( 0 ).list( ).get( 1 );
+ assertEquals( expectedTarget, status.getLast( ) );
+
+ }
+
+ @Test
+ void forEachRemaining( )
+ {
+ StorageAsset root = createTree( );
+ AssetSpliterator spliterator = new AssetSpliterator( root );
+ final ConsumeVisitStatus status = new ConsumeVisitStatus( );
+ spliterator.forEachRemaining( status );
+ // 10 * 15 * 5 + 10 * 15 + 10 + 1
+ assertEquals( LEVEL1*LEVEL2*LEVEL3+LEVEL1*LEVEL2+LEVEL1+1
+ , status.size( ) );
+ assertEquals( root, status.getLast( ) );
+ }
+
+ @Test
+ void forEachRemaining2( )
+ {
+ StorageAsset root = createTree( );
+ AssetSpliterator spliterator = new AssetSpliterator( root );
+ final ConsumeVisitStatus status = new ConsumeVisitStatus( );
+ spliterator.tryAdvance( a -> {} );
+ spliterator.tryAdvance( a -> {} );
+ spliterator.tryAdvance( a -> {} );
+ spliterator.tryAdvance( a -> {} );
+
+ spliterator.forEachRemaining( status );
+ int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
+ expected = expected - 4;
+ assertEquals( expected
+ , status.size( ) );
+ assertEquals( root, status.getLast( ) );
+ }
+
+ @Test
+ void forEachRemaining3( )
+ {
+ StorageAsset root = createTree( );
+ StorageAsset testRoot = root.list( ).get( 1 );
+ AssetSpliterator spliterator = new AssetSpliterator( testRoot );
+ final ConsumeVisitStatus status = new ConsumeVisitStatus( );
+ spliterator.forEachRemaining( status );
+ int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
+ assertEquals( expected
+ , status.size( ) );
+ assertEquals( testRoot, status.getLast( ) );
+ }
+
+
+ @Test
+ void trySplit( )
+ {
+ StorageAsset root = createTree( );
+ AssetSpliterator spliterator = new AssetSpliterator( root );
+ final ConsumeVisitStatus status1 = new ConsumeVisitStatus( );
+ final ConsumeVisitStatus status2 = new ConsumeVisitStatus( );
+ Spliterator<StorageAsset> newSpliterator = spliterator.trySplit( );
+ assertNotNull( newSpliterator );
+ newSpliterator.forEachRemaining( status1 );
+ spliterator.forEachRemaining( status2 );
+
+ int sum = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
+ int expected1 = sum / 2;
+ int expected2 = sum / 2 + 1 ;
+ assertEquals( expected1, status1.size( ) );
+ assertEquals( expected2, status2.size( ) );
+
+ }
+
+ @Test
+ void checkCharacteristics() {
+ StorageAsset root = createTree( );
+ AssetSpliterator spliterator = new AssetSpliterator( root );
+ assertEquals( Spliterator.NONNULL, spliterator.characteristics( ) & Spliterator.NONNULL );
+ assertEquals( Spliterator.CONCURRENT, spliterator.characteristics( ) & Spliterator.CONCURRENT );
+ assertEquals( Spliterator.DISTINCT, spliterator.characteristics( ) & Spliterator.DISTINCT );
+
+
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.apache.archiva.repository.storage.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.repository.storage.StorageAsset;
+
+import java.util.function.Consumer;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class ConsumeVisitStatus extends VisitStatus implements Consumer<StorageAsset>
+{
+ @Override
+ public void accept( StorageAsset asset )
+ {
+ add( asset );
+ }
+}
--- /dev/null
+package org.apache.archiva.repository.storage.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.repository.storage.StorageAsset;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class StopVisitStatus extends VisitStatus implements Function<StorageAsset, Boolean>
+{
+ private Predicate<StorageAsset> stopCondition;
+
+ public void setStopCondition( Predicate<StorageAsset> predicate )
+ {
+ this.stopCondition = predicate;
+ }
+
+ @Override
+ public Boolean apply( StorageAsset asset )
+ {
+ add( asset );
+ return !stopCondition.test( asset );
+ }
+
+}
--- /dev/null
+package org.apache.archiva.repository.storage.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import org.apache.archiva.repository.storage.StorageAsset;
+import org.apache.archiva.repository.storage.mock.MockAsset;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+class StorageUtilTest
+{
+ private static int LEVEL1 = 12;
+ private static int LEVEL2 = 13;
+ private static int LEVEL3 = 6;
+
+
+
+ private StorageAsset createTree() {
+ return createTree( LEVEL1, LEVEL2, LEVEL3 );
+ }
+
+ private StorageAsset createTree(int... levelElements) {
+ MockAsset root = new MockAsset( "" );
+ recurseSubTree( root, 0, levelElements );
+ return root;
+ }
+
+ private void recurseSubTree(MockAsset parent, int level, int[] levelElements) {
+ if (level < levelElements.length)
+ {
+ for ( int k = 0; k < levelElements[level]; k++ )
+ {
+ String name = parent.getName( ) + String.format( "%03d", k );
+ MockAsset asset = new MockAsset( parent, name );
+ recurseSubTree( asset, level + 1, levelElements );
+ }
+ }
+ }
+
+ @Test
+ void testWalkFromRoot() {
+ StorageAsset root = createTree( );
+ ConsumeVisitStatus status = new ConsumeVisitStatus( );
+
+ StorageUtil.walk( root, status );
+ int expected = LEVEL1 * LEVEL2 * LEVEL3 + LEVEL1 * LEVEL2 + LEVEL1 + 1;
+ assertEquals( expected, status.size() );
+ StorageAsset first = root.list( ).get( 0 ).list( ).get( 0 ).list().get(0);
+ assertEquals( first, status.getFirst( ) );
+ assertEquals( root, status.getLast( ) );
+ }
+
+ @Test
+ void testWalkFromChild() {
+ StorageAsset root = createTree( );
+ ConsumeVisitStatus status = new ConsumeVisitStatus( );
+ StorageAsset testRoot = root.list( ).get( 3 );
+
+ StorageUtil.walk( testRoot, status );
+ int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
+ assertEquals( expected, status.size() );
+ StorageAsset first = root.list( ).get( 3 ).list( ).get( 0 ).list().get(0);
+ assertEquals( first, status.getFirst( ) );
+ assertEquals( testRoot, status.getLast( ) );
+ }
+
+
+ @Test
+ void testWalkFromRootWithCondition() {
+ StorageAsset root = createTree( );
+ StopVisitStatus status = new StopVisitStatus( );
+ status.setStopCondition( a -> a.getName().equals("001002003") );
+
+ StorageUtil.walk( root, status );
+ assertEquals( "001002003", status.getLast( ).getName() );
+ int expected = LEVEL2 * LEVEL3 + LEVEL2 + 2 * LEVEL3 + 1 + 1 + 1 + 4;
+ assertEquals( expected, status.size() );
+ }
+
+ @Test
+ void testStream() {
+ StorageAsset root = createTree( );
+ ConsumeVisitStatus status = new ConsumeVisitStatus( );
+
+ List<StorageAsset> result;
+ try ( Stream<StorageAsset> stream = StorageUtil.newAssetStream( root, false ) )
+ {
+ result = stream.filter( a -> a.getName( ).startsWith( "001" ) ).collect( Collectors.toList());
+ }
+ int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
+ assertEquals( expected, result.size( ) );
+ assertEquals( "001", result.get( result.size( ) - 1 ).getName() );
+ assertEquals( "001012", result.get( result.size( ) - 2 ).getName() );
+ }
+
+ @Test
+ void testStreamParallel() {
+ StorageAsset root = createTree( );
+ ConsumeVisitStatus status = new ConsumeVisitStatus( );
+
+ List<StorageAsset> result;
+ try ( Stream<StorageAsset> stream = StorageUtil.newAssetStream( root, true ) )
+ {
+ result = stream.filter( a -> a.getName( ).startsWith( "001" ) ).collect( Collectors.toList());
+ }
+ int expected = LEVEL2 * LEVEL3 + LEVEL2 + 1;
+ assertEquals( expected, result.size( ) );
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.apache.archiva.repository.storage.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.repository.storage.StorageAsset;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+class VisitStatus
+{
+ LinkedList<StorageAsset> visited = new LinkedList<>( );
+
+ VisitStatus( )
+ {
+
+ }
+
+ public void add( StorageAsset asset )
+ {
+ // System.out.println( "Adding " + asset.getPath( ) );
+ visited.addLast( asset );
+ }
+
+ public StorageAsset getLast( )
+ {
+ return visited.getLast( );
+ }
+
+ public StorageAsset getFirst() {
+ return visited.getFirst( );
+ }
+
+ public List<StorageAsset> getVisited( )
+ {
+ return visited;
+ }
+
+ public int size( )
+ {
+ return visited.size( );
+ }
+
+
+}