This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/master by this push:
new 5566bd5b [MRESOLVER-273] More compact filesystem friendly mapper (#194)
5566bd5b is described below
commit 5566bd5b3b0e59d124d820e6274da8e2da7804b0
Author: Tamas Cservenak <[email protected]>
AuthorDate: Tue Sep 27 22:02:54 2022 +0200
[MRESOLVER-273] More compact filesystem friendly mapper (#194)
Create more compact FS NameMapper. Also, clean up existing ones and reduce
clutter and mess.
High level changes:
* Introduce new HashingNameMapper (implements the "more compact" name
mapper)
* Introduce StringDigestUtil for String hashing (cleanup the mess of
digest/checksum mixup, drop SimpleDigest as it is not part of API and is
package private/internal stuff)
* Cleanup and simplify existing NameMapper implementations (they are not
components anymore)
* Introduce providers for "user facing" configuration names, as those are
usually combination of existing NameMappers (like one wrapping other, etc).
Hence, to keep things simple, no NameMapper is component anymore but dedicated
providers are constructing them. No user facing change happens by this, as
mapper names remains same.
---
.github/workflows/maven-verify.yml | 3 +
.../src/main/java/TestNioLock.java | 2 +-
.../eclipse/aether/impl/guice/AetherModule.java | 45 ++++---
.../eclipse/aether/internal/impl/SimpleDigest.java | 88 ------------
.../impl/SimpleLocalRepositoryManager.java | 9 +-
.../synccontext/DefaultSyncContextFactory.java | 20 +--
...taticNameMapper.java => BasedirNameMapper.java} | 64 ++++-----
.../named/DiscriminatingNameMapper.java | 43 +++---
.../impl/synccontext/named/FileGAVNameMapper.java | 131 ------------------
.../impl/synccontext/named/GAVNameMapper.java | 100 ++++++++++----
.../impl/synccontext/named/HashingNameMapper.java | 91 +++++++++++++
.../impl/synccontext/named/NameMapper.java | 20 ++-
.../synccontext/named/NamedLockFactoryAdapter.java | 8 +-
.../impl/synccontext/named/StaticNameMapper.java | 51 +++----
.../DiscriminatingNameMapperProvider.java | 53 ++++++++
.../named/providers/FileGAVNameMapperProvider.java | 53 ++++++++
.../FileHashingGAVNameMapperProvider.java | 54 ++++++++
.../named/providers/GAVNameMapperProvider.java} | 38 ++++--
.../named/providers/StaticNameMapperProvider.java} | 38 ++++--
.../synccontext/named/providers/package-info.java | 22 +--
.../src/site/markdown/synccontextfactory.md.vm | 10 +-
.../impl/synccontext/FileLockAdapterTest.java | 5 +-
.../NamedLockFactoryAdapterTestSupport.java | 2 +-
.../synccontext/named/BasedirNameMapperTest.java | 148 +++++++++++++++++++++
.../impl/synccontext/named/GAVNameMapperTest.java | 95 +++++++++++++
.../synccontext/named/HashingNameMapperTest.java | 146 ++++++++++++++++++++
.../synccontext/named/NameMapperTestSupport.java | 55 ++++++++
.../NamedLockFactoryAdapterTestSupport.java | 2 +-
.../named/providers/FileLockNamedLockFactory.java | 2 -
.../org/eclipse/aether/util/DirectoryUtils.java | 120 +++++++++++++++++
.../org/eclipse/aether/util/StringDigestUtil.java | 93 +++++++++++++
.../eclipse/aether/util/DirectoryUtilsTest.java | 128 ++++++++++++++++++
.../eclipse/aether/util/StringDigestUtilTest.java | 101 ++++++++++++++
src/site/markdown/local-repository.md | 2 +-
34 files changed, 1416 insertions(+), 426 deletions(-)
diff --git a/.github/workflows/maven-verify.yml
b/.github/workflows/maven-verify.yml
index 62709ed2..0d507e8c 100644
--- a/.github/workflows/maven-verify.yml
+++ b/.github/workflows/maven-verify.yml
@@ -25,4 +25,7 @@ jobs:
build:
name: Verify
uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v2
+ with:
+ ff-site-run: false
+
diff --git
a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/TestNioLock.java
b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/TestNioLock.java
index 5d906553..8ec810a2 100644
---
a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/TestNioLock.java
+++
b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/TestNioLock.java
@@ -32,7 +32,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * A simple tool to check file locking on your OS/FS/Java combo. To use this
tool, just copy it to baseDir on
+ * A simple tool to check file locking on your OS/FS/Java combo. To use this
tool, just copy it to basedir on
* the volume you plan to use as local repository and compile and run it:
* <ul>
* <li><pre>javac TestNioLock.java</pre></li>
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
index ce7ccc28..d003928e 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
@@ -56,11 +56,12 @@ import
org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate;
import org.eclipse.aether.internal.impl.collect.bf.BfDependencyCollector;
import org.eclipse.aether.internal.impl.collect.df.DfDependencyCollector;
import org.eclipse.aether.internal.impl.synccontext.DefaultSyncContextFactory;
-import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper;
-import
org.eclipse.aether.internal.impl.synccontext.named.DiscriminatingNameMapper;
import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
-import org.eclipse.aether.internal.impl.synccontext.named.StaticNameMapper;
-import org.eclipse.aether.internal.impl.synccontext.named.FileGAVNameMapper;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.DiscriminatingNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.FileGAVNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.FileHashingGAVNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.GAVNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.StaticNameMapperProvider;
import org.eclipse.aether.named.NamedLockFactory;
import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
import org.eclipse.aether.named.providers.LocalReadWriteLockNamedLockFactory;
@@ -206,14 +207,16 @@ public class AetherModule
.to(
org.eclipse.aether.internal.impl.synccontext.legacy.DefaultSyncContextFactory.class
)
.in( Singleton.class );
- bind( NameMapper.class ).annotatedWith( Names.named(
StaticNameMapper.NAME ) )
- .to( StaticNameMapper.class ).in( Singleton.class );
- bind( NameMapper.class ).annotatedWith( Names.named(
GAVNameMapper.NAME ) )
- .to( GAVNameMapper.class ).in( Singleton.class );
- bind( NameMapper.class ).annotatedWith( Names.named(
DiscriminatingNameMapper.NAME ) )
- .to( DiscriminatingNameMapper.class ).in( Singleton.class );
- bind( NameMapper.class ).annotatedWith( Names.named(
FileGAVNameMapper.NAME ) )
- .to( FileGAVNameMapper.class ).in( Singleton.class );
+ bind( NameMapper.class ).annotatedWith( Names.named(
StaticNameMapperProvider.NAME ) )
+ .toProvider( StaticNameMapperProvider.class ).in(
Singleton.class );
+ bind( NameMapper.class ).annotatedWith( Names.named(
GAVNameMapperProvider.NAME ) )
+ .toProvider( GAVNameMapperProvider.class ).in( Singleton.class
);
+ bind( NameMapper.class ).annotatedWith( Names.named(
DiscriminatingNameMapperProvider.NAME ) )
+ .toProvider( DiscriminatingNameMapperProvider.class ).in(
Singleton.class );
+ bind( NameMapper.class ).annotatedWith( Names.named(
FileGAVNameMapperProvider.NAME ) )
+ .toProvider( FileGAVNameMapperProvider.class ).in(
Singleton.class );
+ bind( NameMapper.class ).annotatedWith( Names.named(
FileHashingGAVNameMapperProvider.NAME ) )
+ .toProvider( FileHashingGAVNameMapperProvider.class ).in(
Singleton.class );
bind( NamedLockFactory.class ).annotatedWith( Names.named(
NoopNamedLockFactory.NAME ) )
.to( NoopNamedLockFactory.class ).in( Singleton.class );
@@ -271,16 +274,18 @@ public class AetherModule
@Provides
@Singleton
Map<String, NameMapper> provideNameMappers(
- @Named( StaticNameMapper.NAME ) NameMapper staticNameMapper,
- @Named( GAVNameMapper.NAME ) NameMapper gavNameMapper,
- @Named( DiscriminatingNameMapper.NAME ) NameMapper
discriminatingNameMapper,
- @Named( FileGAVNameMapper.NAME ) NameMapper fileGavNameMapper )
+ @Named( StaticNameMapperProvider.NAME ) NameMapper
staticNameMapper,
+ @Named( GAVNameMapperProvider.NAME ) NameMapper gavNameMapper,
+ @Named( DiscriminatingNameMapperProvider.NAME ) NameMapper
discriminatingNameMapper,
+ @Named( FileGAVNameMapperProvider.NAME ) NameMapper
fileGavNameMapper,
+ @Named( FileHashingGAVNameMapperProvider.NAME ) NameMapper
fileHashingGavNameMapper )
{
Map<String, NameMapper> nameMappers = new HashMap<>();
- nameMappers.put( StaticNameMapper.NAME, staticNameMapper );
- nameMappers.put( GAVNameMapper.NAME, gavNameMapper );
- nameMappers.put( DiscriminatingNameMapper.NAME,
discriminatingNameMapper );
- nameMappers.put( FileGAVNameMapper.NAME, fileGavNameMapper );
+ nameMappers.put( StaticNameMapperProvider.NAME, staticNameMapper );
+ nameMappers.put( GAVNameMapperProvider.NAME, gavNameMapper );
+ nameMappers.put( DiscriminatingNameMapperProvider.NAME,
discriminatingNameMapper );
+ nameMappers.put( FileGAVNameMapperProvider.NAME, fileGavNameMapper );
+ nameMappers.put( FileHashingGAVNameMapperProvider.NAME,
fileHashingGavNameMapper );
return Collections.unmodifiableMap( nameMappers );
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleDigest.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleDigest.java
deleted file mode 100644
index f6ea12dd..00000000
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleDigest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.eclipse.aether.internal.impl;
-
-/*
- * 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 java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-
-/**
- * A simple digester for strings. It will traverse through a list of digest
algorithms and pick the
- * strongest one available.
- */
-class SimpleDigest
-{
-
- private static final String[] HASH_ALGOS = new String[] { "SHA-1", "MD5" };
-
- private final MessageDigest digest;
-
- SimpleDigest()
- {
- MessageDigest md = null;
- for ( String hashAlgo : HASH_ALGOS )
- {
- try
- {
- md = MessageDigest.getInstance( hashAlgo );
- break;
- }
- catch ( NoSuchAlgorithmException ne )
- {
- }
- }
- if ( md == null )
- {
- throw new IllegalStateException( "Not supported digests: " +
Arrays.toString( HASH_ALGOS ) );
- }
- this.digest = md;
- }
-
- public void update( String data )
- {
- if ( data == null || data.isEmpty() )
- {
- return;
- }
- digest.update( data.getBytes( StandardCharsets.UTF_8 ) );
- }
-
- @SuppressWarnings( "checkstyle:magicnumber" )
- public String digest()
- {
- StringBuilder buffer = new StringBuilder( 64 );
-
- byte[] bytes = digest.digest();
- for ( byte aByte : bytes )
- {
- int b = aByte & 0xFF;
-
- if ( b < 0x10 )
- {
- buffer.append( '0' );
- }
-
- buffer.append( Integer.toHexString( b ) );
- }
-
- return buffer.toString();
- }
-}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java
index a16ede57..6758ca00 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java
@@ -39,6 +39,7 @@ import org.eclipse.aether.repository.LocalMetadataResult;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.util.StringDigestUtil;
/**
* A local repository manager that realizes the classical Maven 2.0 local
repository.
@@ -119,13 +120,13 @@ class SimpleLocalRepositoryManager
subKeys.add( mirroredRepo.getId() );
}
- SimpleDigest digest = new SimpleDigest();
- digest.update( context );
+ StringDigestUtil sha1 = StringDigestUtil.sha1();
+ sha1.update( context );
for ( String subKey : subKeys )
{
- digest.update( subKey );
+ sha1.update( subKey );
}
- buffer.append( digest.digest() );
+ buffer.append( sha1.digest() );
key = buffer.toString();
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
index 96996698..65ba3d73 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
@@ -30,12 +30,13 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.SyncContext;
-import
org.eclipse.aether.internal.impl.synccontext.named.DiscriminatingNameMapper;
-import org.eclipse.aether.internal.impl.synccontext.named.FileGAVNameMapper;
-import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper;
import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
import
org.eclipse.aether.internal.impl.synccontext.named.NamedLockFactoryAdapter;
-import org.eclipse.aether.internal.impl.synccontext.named.StaticNameMapper;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.DiscriminatingNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.FileGAVNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.FileHashingGAVNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.GAVNameMapperProvider;
+import
org.eclipse.aether.internal.impl.synccontext.named.providers.StaticNameMapperProvider;
import org.eclipse.aether.named.NamedLockFactory;
import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
import org.eclipse.aether.named.providers.LocalReadWriteLockNamedLockFactory;
@@ -64,7 +65,7 @@ public final class DefaultSyncContextFactory
private static final String NAME_MAPPER_KEY =
"aether.syncContext.named.nameMapper";
- private static final String DEFAULT_NAME_MAPPER_NAME = GAVNameMapper.NAME;
+ private static final String DEFAULT_NAME_MAPPER_NAME =
GAVNameMapperProvider.NAME;
private static final String FACTORY_KEY =
"aether.syncContext.named.factory";
@@ -102,10 +103,11 @@ public final class DefaultSyncContextFactory
public void initService( final ServiceLocator locator )
{
HashMap<String, NameMapper> mappers = new HashMap<>();
- mappers.put( StaticNameMapper.NAME, new StaticNameMapper() );
- mappers.put( GAVNameMapper.NAME, new GAVNameMapper() );
- mappers.put( DiscriminatingNameMapper.NAME, new
DiscriminatingNameMapper( new GAVNameMapper() ) );
- mappers.put( FileGAVNameMapper.NAME, new FileGAVNameMapper() );
+ mappers.put( StaticNameMapperProvider.NAME, new
StaticNameMapperProvider().get() );
+ mappers.put( GAVNameMapperProvider.NAME, new
GAVNameMapperProvider().get() );
+ mappers.put( DiscriminatingNameMapperProvider.NAME, new
DiscriminatingNameMapperProvider().get() );
+ mappers.put( FileGAVNameMapperProvider.NAME, new
FileGAVNameMapperProvider().get() );
+ mappers.put( FileHashingGAVNameMapperProvider.NAME, new
FileHashingGAVNameMapperProvider().get() );
this.nameMappers = mappers;
HashMap<String, NamedLockFactory> factories = new HashMap<>();
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/StaticNameMapper.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/BasedirNameMapper.java
similarity index 50%
copy from
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/StaticNameMapper.java
copy to
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/BasedirNameMapper.java
index 14fc7e79..f9d5bd6e 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/StaticNameMapper.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/BasedirNameMapper.java
@@ -19,49 +19,41 @@ package org.eclipse.aether.internal.impl.synccontext.named;
* under the License.
*/
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
-import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.DirectoryUtils;
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Objects;
+import static java.util.Objects.requireNonNull;
/**
- * Static {@link NameMapper}, always assigns one same name, effectively
becoming equivalent to "static" sync context.
+ * Wrapping {@link NameMapper} class that is file system friendly: it wraps
another
+ * {@link NameMapper} and resolves the resulting "file system friendly" names
against local
+ * repository basedir.
+ *
+ * @since TBD
*/
-@Singleton
-@Named( StaticNameMapper.NAME )
-public class StaticNameMapper implements NameMapper
+public class BasedirNameMapper implements NameMapper
{
- public static final String NAME = "static";
+ private static final String CONFIG_PROP_LOCKS_DIR =
"aether.syncContext.named.basedir.locksDir";
- /**
- * Configuration property to pass in static name
- */
- private static final String CONFIG_PROP_NAME =
"aether.syncContext.named.static.name";
+ private final NameMapper delegate;
- private final String name;
-
- /**
- * Uses string {@code "static"} for the static name
- */
- @Inject
- public StaticNameMapper()
+ public BasedirNameMapper( final NameMapper delegate )
{
- this( NAME );
+ this.delegate = requireNonNull( delegate );
}
- /**
- * Uses passed in non-{@code null} string for the static name
- */
- public StaticNameMapper( final String name )
+ @Override
+ public boolean isFileSystemFriendly()
{
- this.name = Objects.requireNonNull( name );
+ return delegate.isFileSystemFriendly();
}
@Override
@@ -69,6 +61,18 @@ public class StaticNameMapper implements NameMapper
final Collection<? extends Artifact>
artifacts,
final Collection<? extends Metadata>
metadatas )
{
- return Collections.singletonList( ConfigUtils.getString( session,
name, CONFIG_PROP_NAME ) );
+ try
+ {
+ final Path basedir = DirectoryUtils.resolveDirectory(
+ session, ".locks", CONFIG_PROP_LOCKS_DIR, false );
+
+ return delegate.nameLocks( session, artifacts, metadatas ).stream()
+ .map( name -> basedir.resolve( name
).toAbsolutePath().toString() )
+ .collect( Collectors.toList() );
+ }
+ catch ( IOException e )
+ {
+ throw new UncheckedIOException( e );
+ }
}
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/DiscriminatingNameMapper.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/DiscriminatingNameMapper.java
index 862403e1..0aa74aee 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/DiscriminatingNameMapper.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/DiscriminatingNameMapper.java
@@ -22,27 +22,21 @@ package org.eclipse.aether.internal.impl.synccontext.named;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
-import org.eclipse.aether.util.ChecksumUtils;
import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.StringDigestUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.nio.charset.StandardCharsets;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
import java.util.Objects;
import static java.util.stream.Collectors.toList;
/**
- * Discriminating {@link NameMapper}, that wraps another {@link NameMapper}
and adds a "discriminator" as prefix, that
+ * Wrapping {@link NameMapper}, that wraps another {@link NameMapper} and adds
a "discriminator" as prefix, that
* makes lock names unique including the hostname and local repository (by
default). The discriminator may be passed
* in via {@link RepositorySystemSession} or is automatically calculated based
on the local hostname and repository
* path. The implementation retains order of collection elements as it got it
from
@@ -50,12 +44,8 @@ import static java.util.stream.Collectors.toList;
* <p>
* The default setup wraps {@link GAVNameMapper}, but manually may be created
any instance needed.
*/
-@Singleton
-@Named( DiscriminatingNameMapper.NAME )
public class DiscriminatingNameMapper implements NameMapper
{
- public static final String NAME = "discriminating";
-
/**
* Configuration property to pass in discriminator
*/
@@ -72,25 +62,31 @@ public class DiscriminatingNameMapper implements NameMapper
private static final Logger LOGGER = LoggerFactory.getLogger(
DiscriminatingNameMapper.class );
- private final NameMapper nameMapper;
+ private final NameMapper delegate;
private final String hostname;
- @Inject
- public DiscriminatingNameMapper( @Named( GAVNameMapper.NAME ) final
NameMapper nameMapper )
+ public DiscriminatingNameMapper( final NameMapper delegate )
{
- this.nameMapper = Objects.requireNonNull( nameMapper );
+ this.delegate = Objects.requireNonNull( delegate );
this.hostname = getHostname();
}
+ @Override
+ public boolean isFileSystemFriendly()
+ {
+ return false; // uses ":" in produced lock names
+ }
+
@Override
public Collection<String> nameLocks( final RepositorySystemSession session,
final Collection<? extends Artifact>
artifacts,
final Collection<? extends Metadata>
metadatas )
{
String discriminator = createDiscriminator( session );
- return nameMapper.nameLocks( session, artifacts, metadatas
).stream().map( s -> discriminator + ":" + s )
- .collect( toList() );
+ return delegate.nameLocks( session, artifacts, metadatas ).stream()
+ .map( s -> discriminator + ":" + s )
+ .collect( toList() );
}
private String getHostname()
@@ -117,16 +113,7 @@ public class DiscriminatingNameMapper implements NameMapper
discriminator = hostname + ":" + basedir;
try
{
- Map<String, Object> checksums = ChecksumUtils
- .calc( discriminator.getBytes( StandardCharsets.UTF_8
), Collections.singletonList( "SHA-1" ) );
- Object checksum = checksums.get( "SHA-1" );
-
- if ( checksum instanceof Exception )
- {
- throw (Exception) checksum;
- }
-
- return String.valueOf( checksum );
+ return StringDigestUtil.sha1( discriminator );
}
catch ( Exception e )
{
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/FileGAVNameMapper.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/FileGAVNameMapper.java
deleted file mode 100644
index 500b3306..00000000
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/FileGAVNameMapper.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.eclipse.aether.internal.impl.synccontext.named;
-
-/*
- * 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.eclipse.aether.RepositorySystemSession;
-import org.eclipse.aether.artifact.Artifact;
-import org.eclipse.aether.metadata.Metadata;
-import org.eclipse.aether.named.support.FileSystemFriendly;
-
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-/**
- * A {@link NameMapper} that creates same name mapping as Takari Local
Repository does, with
- * {@code baseDir} (local repo). Part of code blatantly copies parts of the
Takari
- * {@code LockingSyncContext}.
- *
- * @see <a
href="https://github.com/takari/takari-local-repository/blob/24133e50a0478dccb5620ac2f2255187608f165b/src/main/java/io/takari/aether/concurrency/LockingSyncContext.java">Takari
- * LockingSyncContext.java</a>
- */
-@Singleton
-@Named( FileGAVNameMapper.NAME )
-public class FileGAVNameMapper
- implements NameMapper, FileSystemFriendly
-{
- public static final String NAME = "file-gav";
-
- private static final String LOCK_SUFFIX = ".resolverlock";
-
- private static final char SEPARATOR = '~';
-
- private final ConcurrentMap<String, Path> baseDirs;
-
- public FileGAVNameMapper()
- {
- this.baseDirs = new ConcurrentHashMap<>();
- }
-
- @Override
- public TreeSet<String> nameLocks( final RepositorySystemSession session,
- final Collection<? extends Artifact>
artifacts,
- final Collection<? extends Metadata>
metadatas )
- {
- File localRepositoryBasedir =
session.getLocalRepository().getBasedir();
- // here we abuse concurrent hash map to make sure costly
getCanonicalFile is invoked only once
- Path baseDir = baseDirs.computeIfAbsent(
- localRepositoryBasedir.getPath(), k ->
- {
- try
- {
- return new File( localRepositoryBasedir, ".locks"
).getCanonicalFile().toPath();
- }
- catch ( IOException e )
- {
- throw new UncheckedIOException( e );
- }
- }
- );
-
- TreeSet<String> paths = new TreeSet<>();
- if ( artifacts != null )
- {
- for ( Artifact artifact : artifacts )
- {
- paths.add( getPath( baseDir, artifact ) + LOCK_SUFFIX );
- }
- }
- if ( metadatas != null )
- {
- for ( Metadata metadata : metadatas )
- {
- paths.add( getPath( baseDir, metadata ) + LOCK_SUFFIX );
- }
- }
- return paths;
- }
-
- private String getPath( final Path baseDir, final Artifact artifact )
- {
- // NOTE: Don't use LRM.getPath*() as those paths could be different
across processes, e.g. due to staging LRMs.
- String path = artifact.getGroupId()
- + SEPARATOR + artifact.getArtifactId()
- + SEPARATOR + artifact.getBaseVersion();
- return baseDir.resolve( path ).toAbsolutePath().toString();
- }
-
- private String getPath( final Path baseDir, final Metadata metadata )
- {
- // NOTE: Don't use LRM.getPath*() as those paths could be different
across processes, e.g. due to staging.
- String path = "";
- if ( metadata.getGroupId().length() > 0 )
- {
- path += metadata.getGroupId();
- if ( metadata.getArtifactId().length() > 0 )
- {
- path += SEPARATOR + metadata.getArtifactId();
- if ( metadata.getVersion().length() > 0 )
- {
- path += SEPARATOR + metadata.getVersion();
- }
- }
- }
- return baseDir.resolve( path ).toAbsolutePath().toString();
- }
-}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapper.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapper.java
index ea0149c4..1492aa3d 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapper.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapper.java
@@ -19,24 +19,52 @@ package org.eclipse.aether.internal.impl.synccontext.named;
* under the License.
*/
+import java.util.Collection;
+import java.util.TreeSet;
+
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
-import javax.inject.Named;
-import javax.inject.Singleton;
-import java.util.Collection;
-import java.util.TreeSet;
+import static java.util.Objects.requireNonNull;
/**
* Artifact GAV {@link NameMapper}, uses artifact and metadata coordinates to
name their corresponding locks. Is not
- * considering local repository, only the artifact coordinates.
+ * considering local repository, only the artifact coordinates. May use custom
prefixes and sufixes and separators,
+ * hence this instance may or may not be filesystem friendly (depends on
strings used).
*/
-@Singleton
-@Named( GAVNameMapper.NAME )
public class GAVNameMapper implements NameMapper
{
- public static final String NAME = "gav";
+ private final boolean fileSystemFriendly;
+
+ private final String artifactPrefix;
+
+ private final String artifactSuffix;
+
+ private final String metadataPrefix;
+
+ private final String metadataSuffix;
+
+ private final String fieldSeparator;
+
+ public GAVNameMapper( boolean fileSystemFriendly,
+ String artifactPrefix, String artifactSuffix,
+ String metadataPrefix, String metadataSuffix,
+ String fieldSeparator )
+ {
+ this.fileSystemFriendly = fileSystemFriendly;
+ this.artifactPrefix = requireNonNull( artifactPrefix );
+ this.artifactSuffix = requireNonNull( artifactSuffix );
+ this.metadataPrefix = requireNonNull( metadataPrefix );
+ this.metadataSuffix = requireNonNull( metadataSuffix );
+ this.fieldSeparator = requireNonNull( fieldSeparator );
+ }
+
+ @Override
+ public boolean isFileSystemFriendly()
+ {
+ return fileSystemFriendly;
+ }
@Override
public Collection<String> nameLocks( final RepositorySystemSession session,
@@ -45,15 +73,12 @@ public class GAVNameMapper implements NameMapper
{
// Deadlock prevention: https://stackoverflow.com/a/16780988/696632
// We must acquire multiple locks always in the same order!
- Collection<String> keys = new TreeSet<>();
+ TreeSet<String> keys = new TreeSet<>();
if ( artifacts != null )
{
for ( Artifact artifact : artifacts )
{
- String key = "artifact:" + artifact.getGroupId()
- + ":" + artifact.getArtifactId()
- + ":" + artifact.getBaseVersion();
- keys.add( key );
+ keys.add( getArtifactName( artifact ) );
}
}
@@ -61,22 +86,45 @@ public class GAVNameMapper implements NameMapper
{
for ( Metadata metadata : metadatas )
{
- StringBuilder key = new StringBuilder( "metadata:" );
- if ( !metadata.getGroupId().isEmpty() )
+ keys.add( getMetadataName( metadata ) );
+ }
+ }
+ return keys;
+ }
+
+ private String getArtifactName( Artifact artifact )
+ {
+ return artifactPrefix + artifact.getGroupId()
+ + fieldSeparator + artifact.getArtifactId()
+ + fieldSeparator + artifact.getBaseVersion()
+ + artifactSuffix;
+ }
+
+ private String getMetadataName( Metadata metadata )
+ {
+ String name = metadataPrefix;
+ if ( !metadata.getGroupId().isEmpty() )
+ {
+ name += metadata.getGroupId();
+ if ( !metadata.getArtifactId().isEmpty() )
+ {
+ name += fieldSeparator + metadata.getArtifactId();
+ if ( !metadata.getVersion().isEmpty() )
{
- key.append( metadata.getGroupId() );
- if ( !metadata.getArtifactId().isEmpty() )
- {
- key.append( ':' ).append( metadata.getArtifactId() );
- if ( !metadata.getVersion().isEmpty() )
- {
- key.append( ':' ).append( metadata.getVersion() );
- }
- }
+ name += fieldSeparator + metadata.getVersion();
}
- keys.add( key.toString() );
}
}
- return keys;
+ return name + metadataSuffix;
+ }
+
+ public static NameMapper gav()
+ {
+ return new GAVNameMapper( false, "artifact:", "", "metadata:", "", ":"
);
+ }
+
+ public static NameMapper fileGav()
+ {
+ return new GAVNameMapper( true, "", ".lock", "", ".lock", "~" );
}
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/HashingNameMapper.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/HashingNameMapper.java
new file mode 100644
index 00000000..feadd2cb
--- /dev/null
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/HashingNameMapper.java
@@ -0,0 +1,91 @@
+package org.eclipse.aether.internal.impl.synccontext.named;
+
+/*
+ * 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 java.util.Collection;
+import java.util.stream.Collectors;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.StringDigestUtil;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Wrapping {@link NameMapper}, that wraps another {@link NameMapper} and
hashes resulting strings. It makes use of
+ * fact that (proper) Hash will create unique fixed length string for each
different input string (so injection still
+ * stands). This mapper produces file system friendly names. Supports
different "depths" (0-4 inclusive) where the
+ * name will contain 0 to 4 level deep directories.
+ * <p>
+ * This mapper is usable in any scenario, but intent was to produce more
"compact" name mapper for file locking.
+ *
+ * @since TBD
+ */
+public class HashingNameMapper implements NameMapper
+{
+ private static final String CONFIG_PROP_DEPTH =
"aether.syncContext.named.hashing.depth";
+
+ private final NameMapper delegate;
+
+ public HashingNameMapper( final NameMapper delegate )
+ {
+ this.delegate = requireNonNull( delegate );
+ }
+
+ @Override
+ public boolean isFileSystemFriendly()
+ {
+ return true; // hashes delegated strings, so whatever it wrapped, it
does not come through
+ }
+
+ @Override
+ public Collection<String> nameLocks( RepositorySystemSession session,
+ Collection<? extends Artifact>
artifacts,
+ Collection<? extends Metadata>
metadatas )
+ {
+ final int depth = ConfigUtils.getInteger( session, 2,
CONFIG_PROP_DEPTH );
+ if ( depth < 0 || depth > 4 )
+ {
+ throw new IllegalArgumentException( "allowed depth value is
between 0 and 4 (inclusive)" );
+ }
+ return delegate.nameLocks( session, artifacts, metadatas ).stream()
+ .map( n -> hashName( n, depth ) )
+ .collect( Collectors.toList() );
+ }
+
+ private String hashName( final String name, final int depth )
+ {
+ String hashedName = StringDigestUtil.sha1( name );
+ if ( depth == 0 )
+ {
+ return hashedName;
+ }
+ StringBuilder prefix = new StringBuilder( "" );
+ int i = 0;
+ while ( i < hashedName.length() && i / 2 < depth )
+ {
+ prefix.append( hashedName, i, i + 2 ).append( "/" );
+ i += 2;
+ }
+ return prefix.append( hashedName ).toString();
+ }
+}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NameMapper.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NameMapper.java
index b5fd2f0e..fa8e8f76 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NameMapper.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NameMapper.java
@@ -19,24 +19,38 @@ package org.eclipse.aether.internal.impl.synccontext.named;
* under the License.
*/
+import java.util.Collection;
+
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
-import java.util.Collection;
-
/**
* Component mapping lock names to passed in artifacts and metadata as
required.
*/
public interface NameMapper
{
+ /**
+ * Returns {@code true} if lock names returned by this lock name mapper
are file system friendly, can be used
+ * as file names and paths.
+ *
+ * @since TBD
+ */
+ boolean isFileSystemFriendly();
+
/**
* Creates (opaque) names for passed in artifacts and metadata. Returned
collection has max size of sum of the
* passed in artifacts and metadata collections, or less. If an empty
collection is returned, there will be no
* locking happening. Never returns {@code null}. The resulting collection
MUST BE "stable" (always sorted by
* same criteria) to avoid deadlocks by acquiring locks in same order,
essentially disregarding the order of
* the input collections.
+ * <p>
+ * There is no requirement of any kind of "parity" between input element
count (sum of two collections, that is)
+ * and output collection size, just the returned upper size limit is
defined (sum of the passed in two collections
+ * size). If returned collection is empty, no locking will happen, if
single element, one lock will be used, if two
+ * then two named locks will be used etc.
*/
- Collection<String> nameLocks( RepositorySystemSession session,
Collection<? extends Artifact> artifacts,
+ Collection<String> nameLocks( RepositorySystemSession session,
+ Collection<? extends Artifact> artifacts,
Collection<? extends Metadata> metadatas );
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java
index bc1a7420..07ee8e6d 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/NamedLockFactoryAdapter.java
@@ -25,7 +25,7 @@ import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.named.NamedLock;
import org.eclipse.aether.named.NamedLockFactory;
-import org.eclipse.aether.named.support.FileSystemFriendly;
+import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
import org.eclipse.aether.util.ConfigUtils;
import org.slf4j.Logger;
@@ -59,11 +59,11 @@ public final class NamedLockFactoryAdapter
this.nameMapper = Objects.requireNonNull( nameMapper );
this.namedLockFactory = Objects.requireNonNull( namedLockFactory );
// TODO: this is ad-hoc "validation", experimental and likely to change
- if ( this.namedLockFactory instanceof FileSystemFriendly
- && !( this.nameMapper instanceof FileSystemFriendly ) )
+ if ( this.namedLockFactory instanceof FileLockNamedLockFactory
+ && !this.nameMapper.isFileSystemFriendly() )
{
throw new IllegalArgumentException(
- "Misconfiguration: FS friendly lock factory requires FS
friendly name mapper"
+ "Misconfiguration: FileLockNamedLockFactory lock factory
requires FS friendly NameMapper"
);
}
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/StaticNameMapper.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/StaticNameMapper.java
index 14fc7e79..4dcfaac4 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/StaticNameMapper.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/StaticNameMapper.java
@@ -19,49 +19,23 @@ package org.eclipse.aether.internal.impl.synccontext.named;
* under the License.
*/
+import java.util.Collection;
+import java.util.Collections;
+
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
-import org.eclipse.aether.util.ConfigUtils;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Singleton;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Objects;
/**
- * Static {@link NameMapper}, always assigns one same name, effectively
becoming equivalent to "static" sync context.
+ * Static {@link NameMapper}, always assigns one same name, effectively
becoming equivalent to "static" sync context:
+ * always maps ANY input to same name.
*/
-@Singleton
-@Named( StaticNameMapper.NAME )
public class StaticNameMapper implements NameMapper
{
- public static final String NAME = "static";
-
- /**
- * Configuration property to pass in static name
- */
- private static final String CONFIG_PROP_NAME =
"aether.syncContext.named.static.name";
-
- private final String name;
-
- /**
- * Uses string {@code "static"} for the static name
- */
- @Inject
- public StaticNameMapper()
- {
- this( NAME );
- }
-
- /**
- * Uses passed in non-{@code null} string for the static name
- */
- public StaticNameMapper( final String name )
+ @Override
+ public boolean isFileSystemFriendly()
{
- this.name = Objects.requireNonNull( name );
+ return true;
}
@Override
@@ -69,6 +43,13 @@ public class StaticNameMapper implements NameMapper
final Collection<? extends Artifact>
artifacts,
final Collection<? extends Metadata>
metadatas )
{
- return Collections.singletonList( ConfigUtils.getString( session,
name, CONFIG_PROP_NAME ) );
+ if ( ( artifacts != null && !artifacts.isEmpty() ) || ( metadatas !=
null && !metadatas.isEmpty() ) )
+ {
+ return Collections.singletonList( "static" );
+ }
+ else
+ {
+ return Collections.emptyList();
+ }
}
}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/DiscriminatingNameMapperProvider.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/DiscriminatingNameMapperProvider.java
new file mode 100644
index 00000000..3a58772f
--- /dev/null
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/DiscriminatingNameMapperProvider.java
@@ -0,0 +1,53 @@
+package org.eclipse.aether.internal.impl.synccontext.named.providers;
+
+/*
+ * 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 javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import
org.eclipse.aether.internal.impl.synccontext.named.DiscriminatingNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
+
+/**
+ * The "discriminating" name mapper provider.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named( DiscriminatingNameMapperProvider.NAME )
+public class DiscriminatingNameMapperProvider implements Provider<NameMapper>
+{
+ public static final String NAME = "discriminating";
+
+ private final NameMapper mapper;
+
+ public DiscriminatingNameMapperProvider()
+ {
+ this.mapper = new DiscriminatingNameMapper( GAVNameMapper.gav() );
+ }
+
+ @Override
+ public NameMapper get()
+ {
+ return mapper;
+ }
+}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/FileGAVNameMapperProvider.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/FileGAVNameMapperProvider.java
new file mode 100644
index 00000000..041d458b
--- /dev/null
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/FileGAVNameMapperProvider.java
@@ -0,0 +1,53 @@
+package org.eclipse.aether.internal.impl.synccontext.named.providers;
+
+/*
+ * 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 javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.eclipse.aether.internal.impl.synccontext.named.BasedirNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
+
+/**
+ * The "file-gav" name mapper provider.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named( FileGAVNameMapperProvider.NAME )
+public class FileGAVNameMapperProvider implements Provider<NameMapper>
+{
+ public static final String NAME = "file-gav";
+
+ private final NameMapper mapper;
+
+ public FileGAVNameMapperProvider()
+ {
+ this.mapper = new BasedirNameMapper( GAVNameMapper.fileGav() );
+ }
+
+ @Override
+ public NameMapper get()
+ {
+ return mapper;
+ }
+}
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/FileHashingGAVNameMapperProvider.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/FileHashingGAVNameMapperProvider.java
new file mode 100644
index 00000000..a81580c8
--- /dev/null
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/FileHashingGAVNameMapperProvider.java
@@ -0,0 +1,54 @@
+package org.eclipse.aether.internal.impl.synccontext.named.providers;
+
+/*
+ * 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 javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.eclipse.aether.internal.impl.synccontext.named.BasedirNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.HashingNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
+
+/**
+ * The "file-hgav" name mapper provider.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named( FileHashingGAVNameMapperProvider.NAME )
+public class FileHashingGAVNameMapperProvider implements Provider<NameMapper>
+{
+ public static final String NAME = "file-hgav";
+
+ private final NameMapper mapper;
+
+ public FileHashingGAVNameMapperProvider()
+ {
+ this.mapper = new BasedirNameMapper( new HashingNameMapper(
GAVNameMapper.gav() ) );
+ }
+
+ @Override
+ public NameMapper get()
+ {
+ return mapper;
+ }
+}
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/GAVNameMapperProvider.java
similarity index 53%
copy from
maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
copy to
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/GAVNameMapperProvider.java
index bebd3674..e8b93ddb 100644
---
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/GAVNameMapperProvider.java
@@ -1,4 +1,4 @@
-package org.eclipse.aether.internal.impl.synccontext;
+package org.eclipse.aether.internal.impl.synccontext.named.providers;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -19,18 +19,34 @@ package org.eclipse.aether.internal.impl.synccontext;
* under the License.
*/
-import org.eclipse.aether.internal.impl.synccontext.named.FileGAVNameMapper;
-import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
-import org.junit.BeforeClass;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
-public class FileLockAdapterTest
- extends NamedLockFactoryAdapterTestSupport
+import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
+
+/**
+ * The "gav" name mapper provider.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named( GAVNameMapperProvider.NAME )
+public class GAVNameMapperProvider implements Provider<NameMapper>
{
- @BeforeClass
- public static void createNamedLockFactory()
+ public static final String NAME = "gav";
+
+ private final NameMapper mapper;
+
+ public GAVNameMapperProvider()
+ {
+ this.mapper = GAVNameMapper.gav();
+ }
+
+ @Override
+ public NameMapper get()
{
- nameMapper = new FileGAVNameMapper();
- namedLockFactory = new FileLockNamedLockFactory();
- createAdapter();
+ return mapper;
}
}
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/StaticNameMapperProvider.java
similarity index 52%
copy from
maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
copy to
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/StaticNameMapperProvider.java
index bebd3674..58edc12b 100644
---
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/StaticNameMapperProvider.java
@@ -1,4 +1,4 @@
-package org.eclipse.aether.internal.impl.synccontext;
+package org.eclipse.aether.internal.impl.synccontext.named.providers;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -19,18 +19,34 @@ package org.eclipse.aether.internal.impl.synccontext;
* under the License.
*/
-import org.eclipse.aether.internal.impl.synccontext.named.FileGAVNameMapper;
-import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
-import org.junit.BeforeClass;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
-public class FileLockAdapterTest
- extends NamedLockFactoryAdapterTestSupport
+import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.StaticNameMapper;
+
+/**
+ * The "static" name mapper provider.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named( StaticNameMapperProvider.NAME )
+public class StaticNameMapperProvider implements Provider<NameMapper>
{
- @BeforeClass
- public static void createNamedLockFactory()
+ public static final String NAME = "static";
+
+ private final NameMapper mapper;
+
+ public StaticNameMapperProvider()
+ {
+ this.mapper = new StaticNameMapper();
+ }
+
+ @Override
+ public NameMapper get()
{
- nameMapper = new FileGAVNameMapper();
- namedLockFactory = new FileLockNamedLockFactory();
- createAdapter();
+ return mapper;
}
}
diff --git
a/maven-resolver-named-locks/src/main/java/org/eclipse/aether/named/support/FileSystemFriendly.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/package-info.java
similarity index 55%
rename from
maven-resolver-named-locks/src/main/java/org/eclipse/aether/named/support/FileSystemFriendly.java
rename to
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/package-info.java
index d029e4e9..fe86e2d3 100644
---
a/maven-resolver-named-locks/src/main/java/org/eclipse/aether/named/support/FileSystemFriendly.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/named/providers/package-info.java
@@ -1,5 +1,4 @@
-package org.eclipse.aether.named.support;
-
+// CHECKSTYLE_OFF: RegexpHeader
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -8,9 +7,9 @@ package org.eclipse.aether.named.support;
* 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
@@ -18,16 +17,9 @@ package org.eclipse.aether.named.support;
* specific language governing permissions and limitations
* under the License.
*/
-
/**
- * A marker interface that mark component "file system friendly". In case of
lock factory, it
- * would mean that passed in lock names MUST ADHERE to file path naming
convention (and not use
- * some special, non FS friendly characters in it). Essentially, component
marked with this
- * interface expects (or uses) that "name" is an absolute and valid file path.
- *
- * <strong>Important note:</strong> Experimental interface, is not meant to be
used outside of
- * Maven Resolver codebase. May change or be removed completely without any
further notice.
+ * As end-user "mappers" are actually configurations, constructed from several
NameMapper implementations, this
+ * package is holding providers that are constructing them, as no NameMapper
is a component anymore.
*/
-public interface FileSystemFriendly
-{
-}
+package org.eclipse.aether.internal.impl.synccontext.named.providers;
+
diff --git a/maven-resolver-impl/src/site/markdown/synccontextfactory.md.vm
b/maven-resolver-impl/src/site/markdown/synccontextfactory.md.vm
index 9c1f492e..afe611fb 100644
--- a/maven-resolver-impl/src/site/markdown/synccontextfactory.md.vm
+++ b/maven-resolver-impl/src/site/markdown/synccontextfactory.md.vm
@@ -55,7 +55,7 @@ For the `aether.syncContext.named.factory` property following
values are allowed
- `rwlock-local` (default), uses JVM `ReentrantReadWriteLock` per lock name,
usable for MT builds.
- `semaphore-local`, uses JVM `Semaphore` per lock name, usable for MT builds.
-- `file-lock`, uses advisory file locking, usable for MP builds (must be used
with `file-gav` name mapping).
+- `file-lock`, uses advisory file locking, usable for MP builds (must be used
with any of `file-` prefixed name mapping).
- `noop`, implement no-op locking (no locking). For experimenting only. Has
same functionality as old "nolock"
SyncContextFactory implementation.
@@ -64,6 +64,7 @@ For the `aether.syncContext.named.nameMapper` property
following values are allo
- `discriminating` (default), uses hostname + local repo + GAV to create
unique lock names for artifacts.
- `gav` uses GAV to create unique lock names for artifacts and metadata. Is
not unique if multiple local repositories are involved.
- `file-gav` uses GAV and session to create absolute file paths (to be used
with `file-lock` factory)
+- `file-hgav` uses more compact layout than `file-gav` by SHA-1 digest,
similar to git (to be used with `file-lock` factory)
- `static` uses static (same) string as lock name for any input. Effectively
providing functionality same as old
"global" locking SyncContextFactory.
@@ -76,10 +77,13 @@ Extra values for factory (these need extra setup and will
work with Sisu DI only
Other configuration keys:
-- `aether.syncContext.named.static.name`, the value to use as static lock
name, if `static` name mapper is used.
- If not set, defaults to "static".
+- `aether.syncContext.named.basedir.locksDir`, the relative or absolute
directory path to to store the lock files.
+ If relative, is resolved against local repository, if absolute, is used as
is. If not set, defaults to ".locks".
- `aether.syncContext.named.discriminating.discriminator`, when
`discriminating` name mapper is used, sets the a
discriminator uniquely identifying a host and local repository pair. If not
set, discriminator is calculated by
applying `sha1(hostname + ":" + localRepoPath)` + GAV name mapper.
- `aether.syncContext.named.discriminating.hostname`, the hostname to be used
to calculate discriminator value,
if above value not set. If not set, the hostname is detected using Java API.
+- `aether.syncContext.named.hashing.depth`, the depth of sub-directories
(0=xxxx..., 1=xx/xxxx..., 2=xx/xx/xxxx..., etc)
+ to be created by HashingNameMapper (used with `file-hgav` mapper). Only
integer values from 0 to 4 are accepted (inclusive).
+ If not set, defaults to 2.
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
index bebd3674..218af4c1 100644
---
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/FileLockAdapterTest.java
@@ -19,7 +19,8 @@ package org.eclipse.aether.internal.impl.synccontext;
* under the License.
*/
-import org.eclipse.aether.internal.impl.synccontext.named.FileGAVNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.BasedirNameMapper;
+import org.eclipse.aether.internal.impl.synccontext.named.GAVNameMapper;
import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
import org.junit.BeforeClass;
@@ -29,7 +30,7 @@ public class FileLockAdapterTest
@BeforeClass
public static void createNamedLockFactory()
{
- nameMapper = new FileGAVNameMapper();
+ nameMapper = new BasedirNameMapper( GAVNameMapper.fileGav() );
namedLockFactory = new FileLockNamedLockFactory();
createAdapter();
}
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/NamedLockFactoryAdapterTestSupport.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/NamedLockFactoryAdapterTestSupport.java
index ce176a76..9deadba8 100644
---
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/NamedLockFactoryAdapterTestSupport.java
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/NamedLockFactoryAdapterTestSupport.java
@@ -57,7 +57,7 @@ public abstract class NamedLockFactoryAdapterTestSupport
/**
* Subclass MAY populate this field but subclass must take care of proper
cleanup as well, if needed!
*/
- protected static NameMapper nameMapper = new DiscriminatingNameMapper(new
GAVNameMapper());
+ protected static NameMapper nameMapper = new DiscriminatingNameMapper(
GAVNameMapper.gav() );
/**
* Subclass MUST populate this field but subclass must take care of proper
cleanup as well, if needed! Once set,
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/BasedirNameMapperTest.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/BasedirNameMapperTest.java
new file mode 100644
index 00000000..3f8dfc5d
--- /dev/null
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/BasedirNameMapperTest.java
@@ -0,0 +1,148 @@
+package org.eclipse.aether.internal.impl.synccontext.named;
+
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.metadata.DefaultMetadata;
+import org.eclipse.aether.metadata.Metadata;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+
+public class BasedirNameMapperTest extends NameMapperTestSupport
+{
+ private final String PS = File.separator;
+
+ BasedirNameMapper mapper = new BasedirNameMapper( new HashingNameMapper(
GAVNameMapper.gav() ) );
+
+ @Test
+ public void nullsAndEmptyInputs()
+ {
+ Collection<String> names;
+
+ names = mapper.nameLocks( session, null, null );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, null, emptyList() );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, emptyList(), null );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, emptyList(), emptyList() );
+ assertThat( names, Matchers.empty() );
+ }
+
+ @Test
+ public void defaultLocksDir()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+ configProperties.put( "aether.syncContext.named.basedir.locksDir",
null );
+ DefaultArtifact artifact = new DefaultArtifact( "group:artifact:1.0" );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), null );
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( basedir + PS + ".locks" + PS +
"46e98183d232f1e16f863025080c7f2b9797fd10" ) );
+ }
+
+ @Test
+ public void relativeLocksDir()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+ configProperties.put( "aether.syncContext.named.basedir.locksDir",
"my/locks" );
+ DefaultArtifact artifact = new DefaultArtifact( "group:artifact:1.0" );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), null );
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( basedir + PS + "my" + PS + "locks" + PS +
"46e98183d232f1e16f863025080c7f2b9797fd10" ) );
+ }
+
+ @Test
+ public void absoluteLocksDir() throws IOException
+ {
+ String absoluteLocksDir = "/my/locks";
+ String customBaseDir = new File( absoluteLocksDir ).getCanonicalPath();
+
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+ configProperties.put( "aether.syncContext.named.basedir.locksDir",
absoluteLocksDir );
+ DefaultArtifact artifact = new DefaultArtifact( "group:artifact:1.0" );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), null );
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(), // ends with as we do not test
drive letter on non-Win plaf
+ equalTo( customBaseDir + PS +
"46e98183d232f1e16f863025080c7f2b9797fd10" ) );
+ }
+
+ @Test
+ public void singleArtifact()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+
+ DefaultArtifact artifact = new DefaultArtifact( "group:artifact:1.0" );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), null );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( basedir + PS + ".locks" + PS +
"46e98183d232f1e16f863025080c7f2b9797fd10" ) );
+ }
+
+ @Test
+ public void singleMetadata()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+
+ DefaultMetadata metadata =
+ new DefaultMetadata( "group", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, null,
singletonList( metadata ) );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( basedir + PS + ".locks" + PS +
"293b3990971f4b4b02b220620d2538eaac5f221b" ) );
+ }
+
+ @Test
+ public void oneAndOne()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+
+ DefaultArtifact artifact = new DefaultArtifact( "agroup:artifact:1.0"
);
+ DefaultMetadata metadata =
+ new DefaultMetadata( "bgroup", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), singletonList( metadata ) );
+
+ assertThat( names, hasSize( 2 ) );
+ Iterator<String> namesIterator = names.iterator();
+
+ // they are sorted as well
+ assertThat( namesIterator.next(),
+ equalTo( basedir + PS + ".locks" + PS +
"d36504431d00d1c6e4d1c34258f2bf0a004de085" ) );
+ assertThat( namesIterator.next(),
+ equalTo( basedir + PS + ".locks" + PS +
"fbcebba60d7eb931eca634f6ca494a8a1701b638" ) );
+ }
+}
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapperTest.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapperTest.java
new file mode 100644
index 00000000..a943bca6
--- /dev/null
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/GAVNameMapperTest.java
@@ -0,0 +1,95 @@
+package org.eclipse.aether.internal.impl.synccontext.named;
+
+/*
+ * 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 java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.metadata.DefaultMetadata;
+import org.eclipse.aether.metadata.Metadata;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+
+public class GAVNameMapperTest extends NameMapperTestSupport
+{
+ NameMapper mapper = GAVNameMapper.fileGav();
+
+ @Test
+ public void nullsAndEmptyInputs()
+ {
+ Collection<String> names;
+
+ names = mapper.nameLocks( session, null, null );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, null, emptyList() );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, emptyList(), null );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, emptyList(), emptyList() );
+ assertThat( names, Matchers.empty() );
+ }
+
+ @Test
+ public void singleArtifact()
+ {
+ DefaultArtifact artifact = new DefaultArtifact( "group:artifact:1.0" );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), null );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(), equalTo(
"group~artifact~1.0.lock" ) );
+ }
+
+ @Test
+ public void singleMetadata()
+ {
+ DefaultMetadata metadata =
+ new DefaultMetadata( "group", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, null,
singletonList( metadata ) );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(), equalTo( "group~artifact.lock" )
);
+ }
+
+ @Test
+ public void oneAndOne()
+ {
+ DefaultArtifact artifact = new DefaultArtifact( "agroup:artifact:1.0"
);
+ DefaultMetadata metadata =
+ new DefaultMetadata( "bgroup", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), singletonList( metadata ) );
+
+ assertThat( names, hasSize( 2 ) );
+ Iterator<String> namesIterator = names.iterator();
+
+ // they are sorted as well
+ assertThat( namesIterator.next(), equalTo( "agroup~artifact~1.0.lock"
) );
+ assertThat( namesIterator.next(), equalTo( "bgroup~artifact.lock" ) );
+ }
+}
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/HashingNameMapperTest.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/HashingNameMapperTest.java
new file mode 100644
index 00000000..12356ec5
--- /dev/null
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/HashingNameMapperTest.java
@@ -0,0 +1,146 @@
+package org.eclipse.aether.internal.impl.synccontext.named;
+
+/*
+ * 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 java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.metadata.DefaultMetadata;
+import org.eclipse.aether.metadata.Metadata;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+
+public class HashingNameMapperTest extends NameMapperTestSupport
+{
+ HashingNameMapper mapper = new HashingNameMapper( GAVNameMapper.gav() );
+
+ @Test
+ public void nullsAndEmptyInputs()
+ {
+ Collection<String> names;
+
+ names = mapper.nameLocks( session, null, null );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, null, emptyList() );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, emptyList(), null );
+ assertThat( names, Matchers.empty() );
+
+ names = mapper.nameLocks( session, emptyList(), emptyList() );
+ assertThat( names, Matchers.empty() );
+ }
+
+ @Test
+ public void singleArtifact_depth0()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+ DefaultArtifact artifact = new DefaultArtifact( "group:artifact:1.0" );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), null );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( "46e98183d232f1e16f863025080c7f2b9797fd10" ) );
+ }
+
+ @Test
+ public void singleMetadata_depth0()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+ DefaultMetadata metadata =
+ new DefaultMetadata( "group", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, null,
singletonList( metadata ) );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( "293b3990971f4b4b02b220620d2538eaac5f221b" ) );
+ }
+
+ @Test
+ public void oneAndOne_depth0()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "0" );
+ DefaultArtifact artifact = new DefaultArtifact( "agroup:artifact:1.0"
);
+ DefaultMetadata metadata =
+ new DefaultMetadata( "bgroup", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), singletonList( metadata ) );
+
+ assertThat( names, hasSize( 2 ) );
+ Iterator<String> namesIterator = names.iterator();
+
+ // they are sorted as well
+ assertThat( namesIterator.next(),
+ equalTo( "d36504431d00d1c6e4d1c34258f2bf0a004de085" ) );
+ assertThat( namesIterator.next(),
+ equalTo( "fbcebba60d7eb931eca634f6ca494a8a1701b638" ) );
+ }
+
+ @Test
+ public void singleArtifact_depth2()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "2" );
+ DefaultArtifact artifact = new DefaultArtifact( "group:artifact:1.0" );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), null );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( "46/e9/46e98183d232f1e16f863025080c7f2b9797fd10" ) );
+ }
+
+ @Test
+ public void singleMetadata_depth2()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "2" );
+ DefaultMetadata metadata =
+ new DefaultMetadata( "group", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, null,
singletonList( metadata ) );
+
+ assertThat( names, hasSize( 1 ) );
+ assertThat( names.iterator().next(),
+ equalTo( "29/3b/293b3990971f4b4b02b220620d2538eaac5f221b" ) );
+ }
+
+ @Test
+ public void oneAndOne_depth2()
+ {
+ configProperties.put( "aether.syncContext.named.hashing.depth", "2" );
+ DefaultArtifact artifact = new DefaultArtifact( "agroup:artifact:1.0"
);
+ DefaultMetadata metadata =
+ new DefaultMetadata( "bgroup", "artifact",
"maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT );
+ Collection<String> names = mapper.nameLocks( session, singletonList(
artifact ), singletonList( metadata ) );
+
+ assertThat( names, hasSize( 2 ) );
+ Iterator<String> namesIterator = names.iterator();
+
+ // they are sorted as well
+ assertThat( namesIterator.next(),
+ equalTo( "d3/65/d36504431d00d1c6e4d1c34258f2bf0a004de085" ) );
+ assertThat( namesIterator.next(),
+ equalTo( "fb/ce/fbcebba60d7eb931eca634f6ca494a8a1701b638" ) );
+ }
+}
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/NameMapperTestSupport.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/NameMapperTestSupport.java
new file mode 100644
index 00000000..4a3a60e5
--- /dev/null
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/NameMapperTestSupport.java
@@ -0,0 +1,55 @@
+package org.eclipse.aether.internal.impl.synccontext.named;
+
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.LocalRepository;
+import org.junit.Before;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Simple support class for {@link NameMapper} implementation UTs.
+ */
+public abstract class NameMapperTestSupport
+{
+ protected String basedir;
+
+ protected HashMap<String, Object> configProperties;
+
+ protected RepositorySystemSession session;
+
+ @Before
+ public void before() throws IOException
+ {
+ basedir = new File("/home/maven/.m2/repository").getCanonicalPath();
+ configProperties = new HashMap<>();
+
+ LocalRepository localRepository = new LocalRepository( new File(
basedir ) );
+ session = mock( RepositorySystemSession.class );
+ when( session.getConfigProperties() ).thenReturn( configProperties );
+ when( session.getLocalRepository() ).thenReturn( localRepository );
+ }
+}
diff --git
a/maven-resolver-named-locks-hazelcast/src/test/java/org/eclipse/aether/named/hazelcast/NamedLockFactoryAdapterTestSupport.java
b/maven-resolver-named-locks-hazelcast/src/test/java/org/eclipse/aether/named/hazelcast/NamedLockFactoryAdapterTestSupport.java
index db5beb2c..86a55ae9 100644
---
a/maven-resolver-named-locks-hazelcast/src/test/java/org/eclipse/aether/named/hazelcast/NamedLockFactoryAdapterTestSupport.java
+++
b/maven-resolver-named-locks-hazelcast/src/test/java/org/eclipse/aether/named/hazelcast/NamedLockFactoryAdapterTestSupport.java
@@ -66,7 +66,7 @@ public abstract class NamedLockFactoryAdapterTestSupport
protected static void setNamedLockFactory( final NamedLockFactory
namedLockFactory )
{
adapter = new NamedLockFactoryAdapter(
- new DiscriminatingNameMapper( new GAVNameMapper() ),
namedLockFactory
+ new DiscriminatingNameMapper( GAVNameMapper.gav() ),
namedLockFactory
);
}
diff --git
a/maven-resolver-named-locks/src/main/java/org/eclipse/aether/named/providers/FileLockNamedLockFactory.java
b/maven-resolver-named-locks/src/main/java/org/eclipse/aether/named/providers/FileLockNamedLockFactory.java
index d15b1be5..cacabe8e 100644
---
a/maven-resolver-named-locks/src/main/java/org/eclipse/aether/named/providers/FileLockNamedLockFactory.java
+++
b/maven-resolver-named-locks/src/main/java/org/eclipse/aether/named/providers/FileLockNamedLockFactory.java
@@ -33,7 +33,6 @@ import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.aether.named.support.FileLockNamedLock;
-import org.eclipse.aether.named.support.FileSystemFriendly;
import org.eclipse.aether.named.support.NamedLockFactorySupport;
import org.eclipse.aether.named.support.NamedLockSupport;
@@ -47,7 +46,6 @@ import org.eclipse.aether.named.support.NamedLockSupport;
@Named( FileLockNamedLockFactory.NAME )
public class FileLockNamedLockFactory
extends NamedLockFactorySupport
- implements FileSystemFriendly
{
public static final String NAME = "file-lock";
diff --git
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/DirectoryUtils.java
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/DirectoryUtils.java
new file mode 100644
index 00000000..ee1739a5
--- /dev/null
+++
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/DirectoryUtils.java
@@ -0,0 +1,120 @@
+package org.eclipse.aether.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 java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.aether.RepositorySystemSession;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A utility class to calculate (and create if needed) paths backed by
directories using configuration properties from
+ * repository system session and others.
+ *
+ * @see RepositorySystemSession#getConfigProperties()
+ * @see RepositorySystemSession#getLocalRepository()
+ * @since TBD
+ */
+public final class DirectoryUtils
+{
+ private DirectoryUtils()
+ {
+ // hide constructor
+ }
+
+ /**
+ * Creates {@link Path} instance out of passed in {@code name} parameter.
May create a directory on resulting path,
+ * if not exists. Following outcomes may happen:
+ * <ul>
+ * <li>{@code name} is absolute path - results in {@link Path}
instance created directly from name.</li>
+ * <li>{@code name} is relative path - results in {@link Path}
instance resolved with {@code base} parameter.
+ * </li>
+ * </ul>
+ * Resulting path is being checked is a directory, and if not, it will be
created. If resulting path exists but
+ * is not a directory, this method will fail.
+ *
+ * @param name The name to create directory with, cannot be {@code
null}.
+ * @param base The base {@link Path} to resolve name, if it is
relative path, cannot be {@code null}.
+ * @param mayCreate If resulting path does not exist, should it create?
+ * @return The {@link Path} instance that is resolved and backed by
existing directory.
+ * @throws IOException If some IO related errors happens.
+ */
+ public static Path resolveDirectory( String name, Path base, boolean
mayCreate ) throws IOException
+ {
+ requireNonNull( name, "name is null" );
+ requireNonNull( base, "base is null" );
+ final Path namePath = Paths.get( name );
+ final Path result;
+ if ( namePath.isAbsolute() )
+ {
+ result = namePath.normalize();
+ }
+ else
+ {
+ result = base.resolve( namePath ).normalize();
+ }
+
+ if ( !Files.exists( result ) )
+ {
+ if ( mayCreate )
+ {
+ Files.createDirectories( result );
+ }
+ }
+ else if ( !Files.isDirectory( result ) )
+ {
+ throw new IOException( "Path exists, but is not a directory: " +
result );
+ }
+ return result;
+ }
+
+ /**
+ * Creates {@link Path} instance out of session configuration, and (if
relative) resolve it against local
+ * repository
+ * basedir. Pre-populates values and invokes {@link
#resolveDirectory(String, Path, boolean)}.
+ * <p>
+ * For this method to work, {@link
org.eclipse.aether.repository.LocalRepository#getBasedir()} must return
+ * non-{@code null} value, otherwise {@link NullPointerException} is
thrown.
+ *
+ * @param session The session, may not be {@code null}.
+ * @param defaultName The default value if not present in session
configuration, may not be {@code null}.
+ * @param nameKey The key to look up for in session configuration to
obtain user set value.
+ * @param mayCreate If resulting path does not exist, should it create?
+ * @return The {@link Path} instance that is resolved and backed by
existing directory.
+ * @throws IOException If some IO related errors happens.
+ */
+ public static Path resolveDirectory( RepositorySystemSession session,
+ String defaultName,
+ String nameKey,
+ boolean mayCreate )
+ throws IOException
+ {
+ requireNonNull( session, "session is null" );
+ requireNonNull( defaultName, "defaultName is null" );
+ requireNonNull( nameKey, "nameKey is null" );
+ requireNonNull( session.getLocalRepository().getBasedir(),
"session.localRepository.basedir is null" );
+ return resolveDirectory( ConfigUtils.getString( session, defaultName,
nameKey ),
+ session.getLocalRepository().getBasedir().toPath(), mayCreate
);
+ }
+}
diff --git
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/StringDigestUtil.java
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/StringDigestUtil.java
new file mode 100644
index 00000000..0f3fa8c2
--- /dev/null
+++
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/StringDigestUtil.java
@@ -0,0 +1,93 @@
+package org.eclipse.aether.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 java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * A simple digester utility for Strings. Uses {@link MessageDigest} for
requested algorithm. Supports one-pass or
+ * several rounds of updates, and as result emits hex encoded String.
+ *
+ * @since TBD
+ */
+public final class StringDigestUtil
+{
+ private final MessageDigest digest;
+
+ /**
+ * Constructs instance with given algorithm.
+ *
+ * @see #sha1()
+ * @see #sha1(String)
+ */
+ public StringDigestUtil( final String alg )
+ {
+ try
+ {
+ this.digest = MessageDigest.getInstance( alg );
+ }
+ catch ( NoSuchAlgorithmException e )
+ {
+ throw new IllegalStateException( "Not supported digest algorithm:
" + alg );
+ }
+ }
+
+ /**
+ * Updates instance with passed in string.
+ */
+ public StringDigestUtil update( String data )
+ {
+ if ( data != null && !data.isEmpty() )
+ {
+ digest.update( data.getBytes( StandardCharsets.UTF_8 ) );
+ }
+ return this;
+ }
+
+ /**
+ * Returns the digest of all strings passed via {@link #update(String)} as
hex string. There is no state preserved
+ * and due implementation of {@link MessageDigest#digest()}, same applies
here: this instance "resets" itself.
+ * Hence, the digest hex encoded string is returned only once.
+ *
+ * @see MessageDigest#digest()
+ */
+ public String digest()
+ {
+ return ChecksumUtils.toHexString( digest.digest() );
+ }
+
+ /**
+ * Helper method to create {@link StringDigestUtil} using SHA-1 digest
algorithm.
+ */
+ public static StringDigestUtil sha1()
+ {
+ return new StringDigestUtil( "SHA-1" );
+ }
+
+ /**
+ * Helper method to calculate SHA-1 digest and hex encode it.
+ */
+ public static String sha1( final String string )
+ {
+ return sha1().update( string ).digest();
+ }
+}
diff --git
a/maven-resolver-util/src/test/java/org/eclipse/aether/util/DirectoryUtilsTest.java
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/DirectoryUtilsTest.java
new file mode 100644
index 00000000..f47c69a7
--- /dev/null
+++
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/DirectoryUtilsTest.java
@@ -0,0 +1,128 @@
+package org.eclipse.aether.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 java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assume.assumeTrue;
+
+public class DirectoryUtilsTest
+{
+ @Rule
+ public TestName testName = new TestName();
+
+ @Test
+ public void expectedCasesRelative() throws IOException
+ {
+ // hack for surefire: sets the property but directory may not exist
+ Files.createDirectories( Paths.get ( System.getProperty(
"java.io.tmpdir" ) ) );
+
+ Path tmpDir = Files.createTempDirectory( testName.getMethodName() );
+ Path result;
+
+ result = DirectoryUtils.resolveDirectory( "foo", tmpDir, false );
+ assertThat( result, equalTo( tmpDir.resolve( "foo" ) ) );
+
+ result = DirectoryUtils.resolveDirectory( "foo/bar", tmpDir, false );
+ assertThat( result, equalTo( tmpDir.resolve( "foo/bar" ) ) );
+
+ result = DirectoryUtils.resolveDirectory( "foo/./bar/..", tmpDir,
false );
+ assertThat( result, equalTo( tmpDir.resolve( "foo" ) ) );
+ }
+
+ @Test
+ public void expectedCasesAbsolute() throws IOException
+ {
+ // TODO: this test is skipped on Windows, as it is not clear which
drive letter will `new File("/foo")`
+ // path get. According to Windows (and assuming Java Path does
separator change OK), "\foo" file should
+ // get resolved to CWD drive + "\foo" path, but seems Java 17 is
different from 11 and 8 in this respect.
+ // This below WORKS on win + Java8 abd win + Java11 but FAILS on win +
Java17
+ assumeTrue( !"WindowsFileSystem".equals(
FileSystems.getDefault().getClass().getSimpleName() ) );
+
+ // hack for surefire: sets the property but directory may not exist
+ Files.createDirectories( Paths.get ( System.getProperty(
"java.io.tmpdir" ) ) );
+
+ Path tmpDir = Files.createTempDirectory( testName.getMethodName() );
+ Path result;
+
+ result = DirectoryUtils.resolveDirectory( "/foo", tmpDir, false );
+ assertThat( result, equalTo( FileSystems.getDefault().getPath( "/foo"
).toAbsolutePath() ) );
+
+ result = DirectoryUtils.resolveDirectory( "/foo/bar", tmpDir, false );
+ assertThat( result, equalTo( FileSystems.getDefault().getPath(
"/foo/bar" ).toAbsolutePath() ) );
+
+ result = DirectoryUtils.resolveDirectory( "/foo/./bar/..", tmpDir,
false );
+ assertThat( result, equalTo( FileSystems.getDefault().getPath( "/foo"
).toAbsolutePath() ) );
+ }
+
+ @Test
+ public void existsButIsADirectory() throws IOException
+ {
+ // hack for surefire: sets the property but directory may not exist
+ Files.createDirectories( Paths.get ( System.getProperty(
"java.io.tmpdir" ) ) );
+
+ Path tmpDir = Files.createTempDirectory( testName.getMethodName() );
+ Files.createDirectories( tmpDir.resolve( "foo" ) );
+ Path result = DirectoryUtils.resolveDirectory( "foo", tmpDir, false );
+ assertThat( result, equalTo( tmpDir.resolve( "foo" ) ) );
+ }
+
+ @Test
+ public void existsButNotADirectory() throws IOException
+ {
+ // hack for surefire: sets the property but directory may not exist
+ Files.createDirectories( Paths.get ( System.getProperty(
"java.io.tmpdir" ) ) );
+
+ Path tmpDir = Files.createTempDirectory( testName.getMethodName() );
+ Files.createFile( tmpDir.resolve( "foo" ) );
+ try
+ {
+ DirectoryUtils.resolveDirectory( "foo", tmpDir, false );
+ }
+ catch ( IOException e )
+ {
+ assertThat( e.getMessage(), startsWith( "Path exists, but is not a
directory:" ) );
+ }
+ }
+
+ @Test
+ public void notExistsAndIsCreated() throws IOException
+ {
+ // hack for surefire: sets the property but directory may not exist
+ Files.createDirectories( Paths.get ( System.getProperty(
"java.io.tmpdir" ) ) );
+
+ Path tmpDir = Files.createTempDirectory( testName.getMethodName() );
+ Files.createDirectories( tmpDir.resolve( "foo" ) );
+ Path result = DirectoryUtils.resolveDirectory( "foo", tmpDir, true );
+ assertThat( result, equalTo( tmpDir.resolve( "foo" ) ) );
+ assertThat( Files.isDirectory( tmpDir.resolve( "foo" ) ), equalTo(
true ) );
+ }
+}
diff --git
a/maven-resolver-util/src/test/java/org/eclipse/aether/util/StringDigestUtilTest.java
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/StringDigestUtilTest.java
new file mode 100644
index 00000000..23d9839f
--- /dev/null
+++
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/StringDigestUtilTest.java
@@ -0,0 +1,101 @@
+package org.eclipse.aether.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.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.fail;
+
+public class StringDigestUtilTest
+{
+ @Test
+ public void sha1Simple()
+ {
+ assertThat( StringDigestUtil.sha1( null ),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( StringDigestUtil.sha1( "" ),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( StringDigestUtil.sha1( "something" ),
+ is( "1af17e73721dbe0c40011b82ed4bb1a7dbe3ce29" ) );
+ assertThat( StringDigestUtil.sha1().update( null ).digest(),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( StringDigestUtil.sha1().update( "" ).digest(),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( StringDigestUtil.sha1().update( "something" ).digest(),
+ is( "1af17e73721dbe0c40011b82ed4bb1a7dbe3ce29" ) );
+ assertThat( StringDigestUtil.sha1().update( "some" ).update( "thing"
).digest(),
+ is( "1af17e73721dbe0c40011b82ed4bb1a7dbe3ce29" ) );
+ }
+
+ @Test
+ public void sha1Manual()
+ {
+ assertThat( new StringDigestUtil( "SHA-1" ).digest(),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( new StringDigestUtil( "SHA-1" ).update( "" ).digest(),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( new StringDigestUtil( "SHA-1" ).update( "something"
).digest(),
+ is( "1af17e73721dbe0c40011b82ed4bb1a7dbe3ce29" ) );
+ assertThat( new StringDigestUtil( "SHA-1" ).update( null ).digest(),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( new StringDigestUtil( "SHA-1" ).update( "" ).digest(),
+ is( "da39a3ee5e6b4b0d3255bfef95601890afd80709" ) );
+ assertThat( new StringDigestUtil( "SHA-1" ).update( "something"
).digest(),
+ is( "1af17e73721dbe0c40011b82ed4bb1a7dbe3ce29" ) );
+ assertThat( new StringDigestUtil( "SHA-1" ).update( "some" ).update(
"thing" ).digest(),
+ is( "1af17e73721dbe0c40011b82ed4bb1a7dbe3ce29" ) );
+ }
+
+ @Test
+ public void md5Manual()
+ {
+ assertThat( new StringDigestUtil( "MD5" ).digest(),
+ is( "d41d8cd98f00b204e9800998ecf8427e" ) );
+ assertThat( new StringDigestUtil( "MD5" ).update( "" ).digest(),
+ is( "d41d8cd98f00b204e9800998ecf8427e" ) );
+ assertThat( new StringDigestUtil( "MD5" ).update( "something"
).digest(),
+ is( "437b930db84b8079c2dd804a71936b5f" ) );
+ assertThat( new StringDigestUtil( "MD5" ).update( null ).digest(),
+ is( "d41d8cd98f00b204e9800998ecf8427e" ) );
+ assertThat( new StringDigestUtil( "MD5" ).update( "" ).digest(),
+ is( "d41d8cd98f00b204e9800998ecf8427e" ) );
+ assertThat( new StringDigestUtil( "MD5" ).update( "something"
).digest(),
+ is( "437b930db84b8079c2dd804a71936b5f" ) );
+ assertThat( new StringDigestUtil( "MD5" ).update( "some" ).update(
"thing" ).digest(),
+ is( "437b930db84b8079c2dd804a71936b5f" ) );
+ }
+
+ @Test
+ public void unsupportedAlg()
+ {
+ try
+ {
+ new StringDigestUtil( "FOO-BAR" );
+ fail( "StringDigestUtil should throw" );
+ }
+ catch ( IllegalStateException e )
+ {
+ // good
+ }
+ }
+}
+
diff --git a/src/site/markdown/local-repository.md
b/src/site/markdown/local-repository.md
index 3554c396..44440660 100644
--- a/src/site/markdown/local-repository.md
+++ b/src/site/markdown/local-repository.md
@@ -156,7 +156,7 @@ To manually instantiate a simple LRM, one needs to invoke
following code:
```java
LocalRepositoryManager simple = new SimpleLocalRepositoryManagerFactory()
- .newInstance( session, new LocalRepository( baseDir ) );
+ .newInstance( session, new LocalRepository( basedir ) );
```
Note: This code snippet above instantiates a component, that is not