This is an automated email from the ASF dual-hosted git repository. martin_s pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/archiva.git
commit de22e846edafd2ba8b06ab135be43b4c596c3db0 Author: Martin Stockhammer <[email protected]> AuthorDate: Sat Feb 29 16:18:48 2020 +0100 Adding stream based asset utility --- .../storage/{ => util}/AssetSpliterator.java | 48 ++++-- .../repository/storage/util/StorageUtil.java | 93 ++++++++++++ .../repository/storage/AssetSpliteratorTest.java | 100 ------------- .../archiva/repository/storage/mock/MockAsset.java | 21 ++- .../storage/util/AssetSpliteratorTest.java | 161 +++++++++++++++++++++ .../storage/util/ConsumeVisitStatus.java | 35 +++++ .../repository/storage/util/StopVisitStatus.java | 45 ++++++ .../repository/storage/util/StorageUtilTest.java | 134 +++++++++++++++++ .../repository/storage/util/VisitStatus.java | 67 +++++++++ 9 files changed, 586 insertions(+), 118 deletions(-) diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/AssetSpliterator.java b/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/AssetSpliterator.java similarity index 81% rename from archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/AssetSpliterator.java rename to archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/AssetSpliterator.java index 8195de7..7b45e12 100644 --- a/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/AssetSpliterator.java +++ b/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/AssetSpliterator.java @@ -1,3 +1,5 @@ +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 @@ -16,7 +18,7 @@ * under the License. */ -package org.apache.archiva.repository.storage; +import org.apache.archiva.repository.storage.StorageAsset; import java.io.Closeable; import java.util.Collections; @@ -29,8 +31,6 @@ 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; /** * @@ -39,7 +39,13 @@ import java.util.stream.StreamSupport; * 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. + * 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 <[email protected]> @@ -53,35 +59,45 @@ public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable private LinkedHashSet<StorageAsset> visitedContainers = new LinkedHashSet<>( ); private long visited = 0; private final int splitThreshold; - private static final int CHARACTERISTICS = Spliterator.DISTINCT|Spliterator.NONNULL; + private static final int CHARACTERISTICS = Spliterator.DISTINCT|Spliterator.NONNULL|Spliterator.CONCURRENT; - AssetSpliterator( int splitThreshold, StorageAsset... assets) { + 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 ) ); } - AssetSpliterator( StorageAsset... assets) { + public AssetSpliterator( StorageAsset... assets) { this.splitThreshold = DEFAULT_SPLIT_THRESHOLD; - Collections.addAll( this.workList, assets ); + init( assets ); } - AssetSpliterator() { + protected AssetSpliterator() { this.splitThreshold = DEFAULT_SPLIT_THRESHOLD; } - AssetSpliterator( int splitThreshold) { + protected AssetSpliterator( int splitThreshold) { this.splitThreshold = splitThreshold; } - AssetSpliterator( int splitThreshold, Set<StorageAsset> visitedContainers) { + protected AssetSpliterator( int splitThreshold, Set<StorageAsset> visitedContainers) { this.visitedContainers.addAll( visitedContainers ); this.splitThreshold = splitThreshold; } - AssetSpliterator( List<StorageAsset> baseList, Set<StorageAsset> visitedContainers) { + 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; } @@ -150,7 +166,7 @@ public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable } } - // In reverse order + // Assets are returned in reverse order List<StorageAsset> getChildContainers( StorageAsset parent) { final List<StorageAsset> children = parent.list( ); final int len = children.size( ); @@ -158,7 +174,7 @@ public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable children.get(len - i - 1)).filter( StorageAsset::isContainer ).collect( Collectors.toList( ) ); } - // In reverse order + // Assets are returned in reverse order List<StorageAsset> getChildFiles(StorageAsset parent) { final List<StorageAsset> children = parent.list( ); final int len = children.size( ); @@ -187,8 +203,8 @@ public class AssetSpliterator implements Spliterator<StorageAsset>, Closeable //noinspection InfiniteLoopStatement while (true) { - newWorkList.add( workList.getFirst( ) ); - newSpliterator.add( workList.getFirst( ) ); + newWorkList.add( workList.removeFirst( ) ); + newSpliterator.add( workList.removeFirst( ) ); } } catch (NoSuchElementException e) { // diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/StorageUtil.java b/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/StorageUtil.java new file mode 100644 index 0000000..b912ee3 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/StorageUtil.java @@ -0,0 +1,93 @@ +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 <[email protected]> + */ +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 ); + } + + +} diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/AssetSpliteratorTest.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/AssetSpliteratorTest.java deleted file mode 100644 index 7dea353..0000000 --- a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/AssetSpliteratorTest.java +++ /dev/null @@ -1,100 +0,0 @@ -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 <[email protected]> - */ -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 diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/mock/MockAsset.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/mock/MockAsset.java index 0d1764f..3baad4f 100644 --- a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/mock/MockAsset.java +++ b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/mock/MockAsset.java @@ -45,12 +45,12 @@ public class MockAsset implements StorageAsset 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 ); } @@ -189,4 +189,21 @@ public class MockAsset implements StorageAsset { 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( ); + } } diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/AssetSpliteratorTest.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/AssetSpliteratorTest.java new file mode 100644 index 0000000..ee16fe8 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/AssetSpliteratorTest.java @@ -0,0 +1,161 @@ +/* + * 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 <[email protected]> + */ +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 diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/ConsumeVisitStatus.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/ConsumeVisitStatus.java new file mode 100644 index 0000000..dc03860 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/ConsumeVisitStatus.java @@ -0,0 +1,35 @@ +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 <[email protected]> + */ +public class ConsumeVisitStatus extends VisitStatus implements Consumer<StorageAsset> +{ + @Override + public void accept( StorageAsset asset ) + { + add( asset ); + } +} diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StopVisitStatus.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StopVisitStatus.java new file mode 100644 index 0000000..a0b17df --- /dev/null +++ b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StopVisitStatus.java @@ -0,0 +1,45 @@ +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 <[email protected]> + */ +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 ); + } + +} diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StorageUtilTest.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StorageUtilTest.java new file mode 100644 index 0000000..7907754 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StorageUtilTest.java @@ -0,0 +1,134 @@ +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 <[email protected]> + */ +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 diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/VisitStatus.java b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/VisitStatus.java new file mode 100644 index 0000000..4df53ed --- /dev/null +++ b/archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/VisitStatus.java @@ -0,0 +1,67 @@ +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 <[email protected]> + */ +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( ); + } + + +}
