]> source.dussan.org Git - archiva.git/commitdiff
Adding stream based asset utility
authorMartin Stockhammer <martin_s@apache.org>
Sat, 29 Feb 2020 15:18:48 +0000 (16:18 +0100)
committerMartin Stockhammer <martin_s@apache.org>
Sat, 29 Feb 2020 15:18:48 +0000 (16:18 +0100)
archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/AssetSpliterator.java [deleted file]
archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/AssetSpliterator.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/StorageUtil.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/AssetSpliteratorTest.java [deleted file]
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/mock/MockAsset.java
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/AssetSpliteratorTest.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/ConsumeVisitStatus.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StopVisitStatus.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/StorageUtilTest.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-storage-api/src/test/java/org/apache/archiva/repository/storage/util/VisitStatus.java [new file with mode: 0644]

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/AssetSpliterator.java
deleted file mode 100644 (file)
index 8195de7..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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;
-    }
-
-
-}
diff --git a/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/AssetSpliterator.java b/archiva-modules/archiva-base/archiva-storage-api/src/main/java/org/apache/archiva/repository/storage/util/AssetSpliterator.java
new file mode 100644 (file)
index 0000000..7b45e12
--- /dev/null
@@ -0,0 +1,233 @@
+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;
+    }
+
+
+}
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 (file)
index 0000000..b912ee3
--- /dev/null
@@ -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 <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 );
+    }
+
+
+}
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 (file)
index 7dea353..0000000
+++ /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 <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
index 0d1764f94d74210e1c089e35d7b48c0f19cc4c5e..3baad4f16634bc75f598322cea6efc23703b6f77 100644 (file)
@@ -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 (file)
index 0000000..ee16fe8
--- /dev/null
@@ -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 <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
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 (file)
index 0000000..dc03860
--- /dev/null
@@ -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 <martin_s@apache.org>
+ */
+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 (file)
index 0000000..a0b17df
--- /dev/null
@@ -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 <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 );
+    }
+
+}
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 (file)
index 0000000..7907754
--- /dev/null
@@ -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 <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
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 (file)
index 0000000..4df53ed
--- /dev/null
@@ -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 <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( );
+    }
+
+
+}