aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.gradle1
-rw-r--r--src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java8
-rw-r--r--src/api/java/com/amadornes/artifactural/api/artifact/Internal.java16
-rw-r--r--src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java1
-rw-r--r--src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java2
-rw-r--r--src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java411
-rw-r--r--src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java49
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/artifact/ArtifactBase.java9
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactIdentifier.java9
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactMetadata.java5
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/artifact/StreamableArtifact.java5
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/cache/ArtifactCacheBase.java9
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/cache/LocatedArtifactCache.java43
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/util/HashFunction.java112
-rw-r--r--src/shared/java/com/amadornes/artifactural/base/util/PatternReplace.java140
15 files changed, 549 insertions, 271 deletions
diff --git a/build.gradle b/build.gradle
index 4c40c97..ba4e0be 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,6 +25,7 @@ configurations {
dependencies {
sharedImplementation sourceSets.api.output
+ sharedImplementation 'commons-io:commons-io:2.4'
gradlecompImplementation sourceSets.shared.output
gradlecompImplementation gradleApi()
diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java b/src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java
index 7e797fb..4271d70 100644
--- a/src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java
+++ b/src/api/java/com/amadornes/artifactural/api/artifact/Artifact.java
@@ -23,10 +23,10 @@ public interface Artifact {
Artifact apply(ArtifactTransformer transformer);
- Artifact.Cached cache(ArtifactCache cache, String specifier);
+ Artifact.Cached cache(ArtifactCache cache);
- default Artifact.Cached optionallyCache(ArtifactCache cache, String specifier) {
- return this instanceof Artifact.Cached ? (Artifact.Cached) this : cache(cache, specifier);
+ default Artifact.Cached optionallyCache(ArtifactCache cache) {
+ return this instanceof Artifact.Cached ? (Artifact.Cached) this : cache(cache);
}
boolean isPresent();
@@ -35,8 +35,10 @@ public interface Artifact {
interface Cached extends Artifact {
+ // Gets the file location, AND writes the file to disc if it hasn't already.
File asFile() throws IOException, MissingArtifactException;
+ // Gets the file location, but doesn't guarantee that it exists. As the wrapped Artifact may not of been written. What's the point of this?
File getFileLocation() throws IOException, MissingArtifactException;
}
diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/Internal.java b/src/api/java/com/amadornes/artifactural/api/artifact/Internal.java
index c7fd883..321b4fc 100644
--- a/src/api/java/com/amadornes/artifactural/api/artifact/Internal.java
+++ b/src/api/java/com/amadornes/artifactural/api/artifact/Internal.java
@@ -35,9 +35,18 @@ final class Internal {
return "missing";
}
+ @Override
+ public String toString() {
+ return "NO_IDENTIFIER";
+ }
+
};
static final Artifact NO_ARTIFACT = new Artifact.Cached() {
+ @Override
+ public String toString() {
+ return "NO_ARTIFACT";
+ }
@Override
public ArtifactIdentifier getIdentifier() {
@@ -56,6 +65,11 @@ final class Internal {
public String getHash() {
return "ERROR";
}
+
+ @Override
+ public String toString() {
+ return "NO_METADATA";
+ }
};
}
@@ -75,7 +89,7 @@ final class Internal {
}
@Override
- public Artifact.Cached cache(ArtifactCache cache, String specifier) {
+ public Artifact.Cached cache(ArtifactCache cache) {
return this;
}
diff --git a/src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java b/src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java
index 479909c..8ceef19 100644
--- a/src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java
+++ b/src/api/java/com/amadornes/artifactural/api/artifact/MissingArtifactException.java
@@ -1,6 +1,7 @@
package com.amadornes.artifactural.api.artifact;
public class MissingArtifactException extends RuntimeException {
+ private static final long serialVersionUID = 4902516963452435653L;
public MissingArtifactException(ArtifactIdentifier identifier) {
super("Could not find artifact: " + identifier);
diff --git a/src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java b/src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java
index 3209656..8099d30 100644
--- a/src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java
+++ b/src/api/java/com/amadornes/artifactural/api/cache/ArtifactCache.java
@@ -4,6 +4,6 @@ import com.amadornes.artifactural.api.artifact.Artifact;
public interface ArtifactCache {
- Artifact.Cached store(Artifact artifact, String specifier);
+ Artifact.Cached store(Artifact artifact);
}
diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java
index 4bac887..2418ea9 100644
--- a/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java
+++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/GradleRepositoryAdapter.java
@@ -2,108 +2,181 @@ package com.amadornes.artifactural.gradle;
import com.amadornes.artifactural.api.artifact.Artifact;
import com.amadornes.artifactural.api.artifact.ArtifactIdentifier;
+import com.amadornes.artifactural.api.artifact.MissingArtifactException;
import com.amadornes.artifactural.api.repository.Repository;
import com.amadornes.artifactural.base.artifact.SimpleArtifactIdentifier;
-import com.google.common.io.CountingInputStream;
-import org.apache.commons.io.IOUtils;
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectCollection;
-import org.gradle.api.Transformer;
+import com.amadornes.artifactural.base.cache.LocatedArtifactCache;
+
+import org.gradle.api.artifacts.ComponentMetadataSupplierDetails;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
+import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.attributes.AttributeContainer;
+import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleComponentRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleComponentRepositoryAccess;
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvableArtifact;
import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.DefaultMavenArtifactRepository;
+import org.gradle.api.internal.artifacts.repositories.DefaultMavenLocalArtifactRepository;
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
-import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceArtifactResolver;
-import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
-import org.gradle.api.resources.ResourceException;
-import org.gradle.internal.resource.AbstractExternalResource;
-import org.gradle.internal.resource.ExternalResource;
+import org.gradle.api.internal.artifacts.repositories.resolver.MetadataFetchingCost;
+import org.gradle.api.internal.attributes.AttributesSchemaInternal;
+import org.gradle.api.internal.attributes.ImmutableAttributesFactory;
+import org.gradle.api.internal.component.ArtifactType;
+import org.gradle.internal.action.InstantiatingAction;
+import org.gradle.internal.component.external.model.ComponentVariant;
+import org.gradle.internal.component.external.model.ModuleComponentArtifactMetadata;
+import org.gradle.internal.component.external.model.ModuleComponentResolveMetadata;
+import org.gradle.internal.component.external.model.ModuleDependencyMetadata;
+import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetadata;
+import org.gradle.internal.component.model.ComponentArtifactMetadata;
+import org.gradle.internal.component.model.ComponentOverrideMetadata;
+import org.gradle.internal.component.model.ComponentResolveMetadata;
+import org.gradle.internal.component.model.ConfigurationMetadata;
+import org.gradle.internal.component.model.ModuleSource;
+import org.gradle.internal.hash.HashValue;
+import org.gradle.internal.impldep.com.google.common.base.Optional;
+import org.gradle.internal.impldep.com.google.common.collect.ImmutableList;
+import org.gradle.internal.nativeintegration.filesystem.FileSystem;
+import org.gradle.internal.nativeintegration.services.FileSystems;
+import org.gradle.internal.resolve.result.BuildableArtifactResolveResult;
+import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult;
+import org.gradle.internal.resolve.result.BuildableComponentArtifactsResolveResult;
+import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
+import org.gradle.internal.resolve.result.BuildableModuleVersionListingResolveResult;
import org.gradle.internal.resource.ExternalResourceName;
-import org.gradle.internal.resource.ExternalResourceReadResult;
import org.gradle.internal.resource.ExternalResourceRepository;
-import org.gradle.internal.resource.ExternalResourceWriteResult;
-import org.gradle.internal.resource.ReadableContent;
-import org.gradle.internal.resource.ResourceExceptions;
-import org.gradle.internal.resource.metadata.DefaultExternalResourceMetaData;
+import org.gradle.internal.resource.LocalBinaryResource;
+import org.gradle.internal.resource.local.FileResourceRepository;
+import org.gradle.internal.resource.local.LocalFileStandInExternalResource;
+import org.gradle.internal.resource.local.LocallyAvailableExternalResource;
import org.gradle.internal.resource.metadata.ExternalResourceMetaData;
+import org.gradle.internal.resource.transfer.DefaultCacheAwareExternalResourceAccessor;
-import javax.annotation.Nullable;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.net.URI;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GradleRepositoryAdapter extends AbstractArtifactRepository implements ResolutionAwareRepository {
private static final Pattern URL_PATTERN = Pattern.compile(
- "^/(?<group>\\S+(?:/\\S+)*)/(?<name>\\S+)/(?<version>\\S+)/" +
+ "^(?<group>\\S+(?:/\\S+)*)/(?<name>\\S+)/(?<version>\\S+)/" +
"\\2-\\3(?:-(?<classifier>[^.\\s]+))?\\.(?<extension>\\S+)$");
- public static GradleRepositoryAdapter add(RepositoryHandler handler, String name, Object url, Repository repository) {
- // Create the real maven test we'll be using and remove it
- MavenArtifactRepository maven = handler.maven($ -> {
- $.setName(name);
- $.setUrl(url);
- });
- handler.remove(maven);
+ public static GradleRepositoryAdapter add(RepositoryHandler handler, String name, File local, Repository repository) {
+ BaseRepositoryFactory factory = ReflectionUtils.get(handler, "repositoryFactory"); // We reflect here and create it manually so it DOESN'T get attached.
+ DefaultMavenLocalArtifactRepository maven = (DefaultMavenLocalArtifactRepository)factory.createMavenLocalRepository(); // We use maven local because it bypasses the caching and coping to .m2
+ maven.setUrl(local);
+ maven.setName(name);
- // Add our own custom test instead, using the real one in the background
- GradleRepositoryAdapter repo = new GradleRepositoryAdapter((DefaultMavenArtifactRepository) maven, repository);
+ GradleRepositoryAdapter repo = new GradleRepositoryAdapter(repository, maven);
+ repo.setName(name);
handler.add(repo);
return repo;
}
- private final DefaultMavenArtifactRepository maven;
private final Repository repository;
+ private final DefaultMavenLocalArtifactRepository local;
- private GradleRepositoryAdapter(DefaultMavenArtifactRepository maven, Repository repository) {
- this.maven = maven;
+ private GradleRepositoryAdapter(Repository repository, DefaultMavenLocalArtifactRepository local) {
this.repository = repository;
- }
-
- @Override
- public String getName() {
- return maven.getName(); // Proxy to the real repo
- }
-
- @Override
- public void setName(String name) {
- maven.setName(name); // Proxy to the real repo
+ this.local = local;
}
@Override
public String getDisplayName() {
- return maven.getDisplayName(); // Proxy to the real repo
- }
-
- @Override
- public void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container) {
- // No-op. The real repo will get this already
+ return local.getDisplayName();
}
@Override
public ConfiguredModuleComponentRepository createResolver() {
- MavenResolver resolver = (MavenResolver) maven.createResolver();
- ExternalResourceRepository repo = new StreamingRepo();
-
- ExternalResourceArtifactResolver artifactResolver = ReflectionUtils.invoke(resolver, ExternalResourceResolver.class, "createArtifactResolver");
- ReflectionUtils.alter(resolver, "repository", prev -> repo);
- ReflectionUtils.alter(resolver, "mavenMetaDataLoader.cacheAwareExternalResourceAccessor.delegate", prev -> repo);
- ReflectionUtils.alter(artifactResolver, "repository", prev -> repo);
+ MavenResolver resolver = (MavenResolver)local.createResolver();
+
+ GeneratingFileResourceRepository repo = new GeneratingFileResourceRepository();
+ ReflectionUtils.alter(resolver, "repository", prev -> repo); // ExternalResourceResolver.repository
+ //ReflectionUtils.alter(resolver, "metadataSources", ); //ExternalResourceResolver.metadataSources We need to fix these from returning 'missing'
+ // MavenResolver -> MavenMetadataLoader -> FileCacheAwareExternalResourceAccessor -> DefaultCacheAwareExternalResourceAccessor
+ DefaultCacheAwareExternalResourceAccessor accessor = ReflectionUtils.get(resolver, "mavenMetaDataLoader.cacheAwareExternalResourceAccessor.delegate");
+ ReflectionUtils.alter(accessor, "delegate", prev -> repo); // DefaultCacheAwareExternalResourceAccessor.delegate
+ ReflectionUtils.alter(accessor, "fileResourceRepository", prev -> repo); // DefaultCacheAwareExternalResourceAccessor.fileResourceRepository
+ //ReflectionUtils.alter(resolver, "localAccess", AccessWrapper<MavenLocalRepositoryAccess>::new);
+
+ //return resolver;
+ //return new Resolver(resolver);
+ return new ConfiguredModuleComponentRepository() {
+ private final ModuleComponentRepositoryAccess local = wrap(resolver.getLocalAccess());
+ private final ModuleComponentRepositoryAccess remote = wrap(resolver.getRemoteAccess());
+ @Override public String getId() { return resolver.getId(); }
+ @Override public String getName() { return resolver.getName(); }
+ @Override public ModuleComponentRepositoryAccess getLocalAccess() { return local; }
+ @Override public ModuleComponentRepositoryAccess getRemoteAccess() { return remote; }
+ @Override public Map<ComponentArtifactIdentifier, ResolvableArtifact> getArtifactCache() { return resolver.getArtifactCache(); }
+ @Override public InstantiatingAction<ComponentMetadataSupplierDetails> getComponentMetadataSupplier() { return resolver.getComponentMetadataSupplier(); }
+ @Override public boolean isDynamicResolveMode() { return resolver.isDynamicResolveMode(); }
+ @Override public boolean isLocal() { return resolver.isLocal(); }
+
+ private ModuleComponentRepositoryAccess wrap(ModuleComponentRepositoryAccess delegate) {
+ return new ModuleComponentRepositoryAccess() {
+ @Override
+ public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
+ delegate.resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
+ if (result.getState() == BuildableModuleComponentMetaDataResolveResult.State.Resolved) {
+ ModuleComponentResolveMetadata meta = result.getMetaData();
+ if (meta.isMissing()) {
+ MutableModuleComponentResolveMetadata mutable = meta.asMutable();
+ mutable.setChanging(true);
+ mutable.setMissing(false);
+ result.resolved(mutable.asImmutable());
+ }
+ }
+ }
+
+ @Override
+ public void listModuleVersions(ModuleDependencyMetadata dependency, BuildableModuleVersionListingResolveResult result) {
+ delegate.listModuleVersions(dependency, result);
+ }
+
+ @Override
+ public void resolveArtifacts(ComponentResolveMetadata component, BuildableComponentArtifactsResolveResult result) {
+ delegate.resolveArtifacts(component, result);
+ }
+
+ @Override
+ public void resolveArtifactsWithType(ComponentResolveMetadata component, ArtifactType artifactType, BuildableArtifactSetResolveResult result) {
+ delegate.resolveArtifactsWithType(component, artifactType, result);
+ }
+
+ @Override
+ public void resolveArtifact(ComponentArtifactMetadata artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
+ delegate.resolveArtifact(artifact, moduleSource, result);
+ }
+
+ @Override
+ public MetadataFetchingCost estimateMetadataFetchingCost(ModuleComponentIdentifier moduleComponentIdentifier) {
+ return delegate.estimateMetadataFetchingCost(moduleComponentIdentifier);
+ }
+ };
+ }
+ };
+ }
- return resolver;
+ private static String cleanRoot(URI uri) {
+ String ret = uri.normalize().getPath().replace('\\', '/');
+ if (!ret.endsWith("/")) ret += '/';
+ return ret;
}
- private class StreamingRepo implements ExternalResourceRepository {
+ private class GeneratingFileResourceRepository implements FileResourceRepository {
+ private final FileSystem fileSystem = FileSystems.getDefault();
+ private final String root = cleanRoot(GradleRepositoryAdapter.this.local.getUrl());
+ private final LocatedArtifactCache cache = new LocatedArtifactCache(new File(root));
@Override
public ExternalResourceRepository withProgressLogging() {
@@ -111,204 +184,66 @@ public class GradleRepositoryAdapter extends AbstractArtifactRepository implemen
}
@Override
- public ExternalResource resource(ExternalResourceName name, boolean revalidate) {
- URI uri = name.getUri();
- Matcher matcher = URL_PATTERN.matcher(uri.getPath());
- if (!matcher.matches()) return new NullExternalResource(uri);
- ArtifactIdentifier identifier = new SimpleArtifactIdentifier(
- matcher.group("group").replace('/', '.'),
- matcher.group("name"),
- matcher.group("version"),
- matcher.group("classifier"),
- matcher.group("extension"));
- Artifact artifact = repository.getArtifact(identifier);
- if (!artifact.isPresent()) return new NullExternalResource(uri);
- return new CustomArtifactExternalResource(uri, artifact);
+ public LocalBinaryResource localResource(File file) {
+ System.out.println("localResource: " + file);
+ return null;
}
@Override
- public ExternalResource resource(ExternalResourceName name) {
- return resource(name, false);
- }
-
- }
-
- private class CustomArtifactExternalResource extends AbstractExternalResource {
-
- private final URI uri;
- private final Artifact artifact;
-
- private CustomArtifactExternalResource(URI uri, Artifact artifact) {
- this.uri = uri;
- this.artifact = artifact;
+ public LocallyAvailableExternalResource resource(File file) {
+ System.out.println("resource(File): " + file);
+ return findArtifact(file.getAbsolutePath().replace('\\', '/'));
}
@Override
- public String getDisplayName() {
- return uri.toString();
+ public LocallyAvailableExternalResource resource(ExternalResourceName location) {
+ return resource(location, false);
}
@Override
- public URI getURI() {
- return uri;
+ public LocallyAvailableExternalResource resource(ExternalResourceName location, boolean revalidate) {
+ System.out.println("resource(ExternalResourceName,boolean): " + location + ", " + revalidate);
+ return findArtifact(location.getUri().getPath().replace('\\', '/'));
}
- @Nullable
@Override
- public ExternalResourceReadResult<Void> writeToIfPresent(File file) {
- try {
- if (!artifact.isPresent()) return null;
- FileOutputStream out = new FileOutputStream(file);
- ExternalResourceReadResult<Void> result = writeTo(out);
- out.close();
- return result;
- } catch (IOException ex) {
- return null;
- }
+ public LocallyAvailableExternalResource resource(File file, URI originUri, ExternalResourceMetaData originMetadata) {
+ System.out.println("resource(File,URI,ExternalResourceMetaData): " + file + ", " + originUri + ", " + originMetadata);
+ return findArtifact(file.getAbsolutePath().replace('\\', '/'));
}
- @Override
- public ExternalResourceReadResult<Void> writeTo(OutputStream out) throws ResourceException {
- return withContent(in -> {
- try {
- IOUtils.copy(in, out);
- } catch (IOException ex) {
- throw ResourceExceptions.failure(uri, "Failed to write resource!", ex);
+ private LocallyAvailableExternalResource findArtifact(String path) {
+ if (path.startsWith(root)) {
+ String relative = path.substring(root.length());
+ System.out.println(" Relative: " + relative);
+ Matcher matcher = URL_PATTERN.matcher(relative);
+ if (!matcher.matches()) {
+ System.out.println(" Matcher Failed: " + relative);
+ } else {
+ ArtifactIdentifier identifier = new SimpleArtifactIdentifier(
+ matcher.group("group").replace('/', '.'),
+ matcher.group("name"),
+ matcher.group("version"),
+ matcher.group("classifier"),
+ matcher.group("extension"));
+ Artifact artifact = repository.getArtifact(identifier);
+ return wrap(artifact, identifier);
}
- });
- }
-
- @Override
- public ExternalResourceReadResult<Void> withContent(Action<? super InputStream> action) throws ResourceException {
- try {
- if (!artifact.isPresent()) throw ResourceExceptions.getMissing(uri);
- CountingInputStream in = new CountingInputStream(artifact.openStream());
- action.execute(in);
- in.close();
- return ExternalResourceReadResult.of(in.getCount());
- } catch (IOException ex) {
- throw ResourceExceptions.failure(uri, "Failed to write resource!", ex);
- }
- }
-
- @Nullable
- @Override
- public <T> ExternalResourceReadResult<T> withContentIfPresent(Transformer<? extends T, ? super InputStream> transformer) {
- try {
- if (!artifact.isPresent()) return null;
- CountingInputStream in = new CountingInputStream(artifact.openStream());
- T result = transformer.transform(in);
- in.close();
- return ExternalResourceReadResult.of(in.getCount(), result);
- } catch (IOException ex) {
- return null;
- }
- }
-
- @Nullable
- @Override
- public <T> ExternalResourceReadResult<T> withContentIfPresent(ContentAction<? extends T> contentAction) {
- try {
- if (!artifact.isPresent()) return null;
- CountingInputStream in = new CountingInputStream(artifact.openStream());
- T result = contentAction.execute(in, getMetaData());
- in.close();
- return ExternalResourceReadResult.of(in.getCount(), result);
- } catch (IOException ex) {
- return null;
+ } else {
+ System.out.println("Unknown root: " + path);
}
+ return new LocalFileStandInExternalResource(new File(path), fileSystem);
}
- @Override
- public ExternalResourceWriteResult put(ReadableContent readableContent) throws ResourceException {
- throw ResourceExceptions.putFailed(uri, null);
- }
-
- @Nullable
- @Override
- public List<String> list() throws ResourceException {
- return null;
- }
-
- @Nullable
- @Override
- public ExternalResourceMetaData getMetaData() {
+ private LocallyAvailableExternalResource wrap(Artifact artifact, ArtifactIdentifier id) {
+ if (!artifact.isPresent())
+ return new LocalFileStandInExternalResource(cache.getPath(artifact), fileSystem);
+ Artifact.Cached cached = artifact.optionallyCache(cache);
try {
- if (!artifact.isPresent()) return null;
- InputStream stream = artifact.openStream();
- int length = stream.available();
- stream.close();
- return new DefaultExternalResourceMetaData(uri, 0, length);
- } catch (IOException ex) {
- return null;
+ return new LocalFileStandInExternalResource(cached.asFile(), fileSystem);
+ } catch (MissingArtifactException | IOException e) {
+ throw new RuntimeException(e);
}
}
-
- }
-
- private class NullExternalResource extends AbstractExternalResource {
-
- private final URI uri;
-
- private NullExternalResource(URI uri) {
- this.uri = uri;
- }
-
- @Override
- public String getDisplayName() {
- return uri.toString();
- }
-
- @Override
- public URI getURI() {
- return uri;
- }
-
- @Nullable
- @Override
- public ExternalResourceReadResult<Void> writeToIfPresent(File destination) throws ResourceException {
- return null;
- }
-
- @Override
- public ExternalResourceReadResult<Void> writeTo(OutputStream destination) throws ResourceException {
- throw ResourceExceptions.getMissing(uri);
- }
-
- @Override
- public ExternalResourceReadResult<Void> withContent(Action<? super InputStream> readAction) throws ResourceException {
- throw ResourceExceptions.getMissing(uri);
- }
-
- @Nullable
- @Override
- public <T> ExternalResourceReadResult<T> withContentIfPresent(Transformer<? extends T, ? super InputStream> readAction) {
- return null;
- }
-
- @Nullable
- @Override
- public <T> ExternalResourceReadResult<T> withContentIfPresent(ContentAction<? extends T> readAction) {
- return null;
- }
-
- @Override
- public ExternalResourceWriteResult put(ReadableContent source) throws ResourceException {
- throw ResourceExceptions.getMissing(uri);
- }
-
- @Nullable
- @Override
- public List<String> list() {
- return null;
- }
-
- @Nullable
- @Override
- public ExternalResourceMetaData getMetaData() {
- return null;
- }
-
}
-
}
diff --git a/src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java b/src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java
index 5dcb9a3..f43b33c 100644
--- a/src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java
+++ b/src/gradlecomp/java/com/amadornes/artifactural/gradle/ReflectionUtils.java
@@ -6,28 +6,42 @@ import java.util.function.UnaryOperator;
public class ReflectionUtils {
+ @SuppressWarnings("unchecked")
public static <T> void alter(Object target, String name, UnaryOperator<T> operator) {
try {
- Object prev = target;
- Field f = null;
- for (String n : name.split("\\.")) {
- f = findField(target.getClass(), n);
- if (f == null) throw new IllegalStateException("Could not find '" + name + "'");
- f.setAccessible(true);
- prev = target;
- target = f.get(target);
+ int idx = name.lastIndexOf('.');
+ if (idx != -1) {
+ target = drillField(target, name.substring(0, idx));
+ if (target == null) throw new IllegalStateException("Could not find field '" + name + "'");
+ name = name.substring(idx + 1);
}
+ Field f = findField(target.getClass(), name);
if (f == null) throw new IllegalStateException("Could not find '" + name + "'");
- f.set(prev, operator.apply((T) target));
+ f.set(target, operator.apply((T)f.get(target)));
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
+ private static Object drillField(Object obj, String path) {
+ for (String name : path.split("\\.")) {
+ if (obj == null) return null;
+ Field f = findField(obj.getClass(), name);
+ if (f == null) return null;
+ try {
+ obj = f.get(obj);
+ } catch (IllegalAccessException e) {
+ return null;
+ }
+ }
+ return obj;
+ }
+
private static Field findField(Class<?> clazz, String name) {
while (clazz != Object.class) {
for (Field f : clazz.getDeclaredFields()) {
if (f.getName().equals(name)) {
+ f.setAccessible(true);
return f;
}
}
@@ -39,6 +53,7 @@ public class ReflectionUtils {
/**
* Invokes a method (can be private).
*/
+ @SuppressWarnings("unchecked")
public static <T> T invoke(Object target, Class<?> type, String name, Object... args) {
try {
Method method = type.getDeclaredMethod(name);
@@ -49,4 +64,20 @@ public class ReflectionUtils {
}
}
+ @SuppressWarnings("unchecked")
+ public static <T> T get(Object target, String name) {
+ try {
+ int idx = name.lastIndexOf('.');
+ if (idx != -1) {
+ target = drillField(target, name.substring(0, idx));
+ if (target == null) throw new IllegalStateException("Could not find field '" + name + "'");
+ name = name.substring(idx + 1);
+ }
+ Field f = findField(target.getClass(), name);
+ if (f == null) throw new IllegalStateException("Could not find '" + name + "'");
+ return (T)f.get(target);
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
}
diff --git a/src/shared/java/com/amadornes/artifactural/base/artifact/ArtifactBase.java b/src/shared/java/com/amadornes/artifactural/base/artifact/ArtifactBase.java
index e7dd0c2..b164feb 100644
--- a/src/shared/java/com/amadornes/artifactural/base/artifact/ArtifactBase.java
+++ b/src/shared/java/com/amadornes/artifactural/base/artifact/ArtifactBase.java
@@ -41,8 +41,13 @@ public abstract class ArtifactBase implements Artifact {
}
@Override
- public Artifact.Cached cache(ArtifactCache cache, String specifier) {
- return cache.store(this, specifier);
+ public Artifact.Cached cache(ArtifactCache cache) {
+ return cache.store(this);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(" + identifier + ", " + type +", " + metadata;
}
}
diff --git a/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactIdentifier.java b/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactIdentifier.java
index 231f68b..71f8cc6 100644
--- a/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactIdentifier.java
+++ b/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactIdentifier.java
@@ -39,4 +39,13 @@ public class SimpleArtifactIdentifier implements ArtifactIdentifier {
return extension;
}
+ @Override
+ public String toString() {
+ String ret = getGroup() + ':' + getName() + ':' + getVersion();
+ if (classifier != null)
+ ret += ':' + getClassifier();
+ if ("jar".equals(extension))
+ ret += '@' + getExtension();
+ return ret;
+ }
}
diff --git a/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactMetadata.java b/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactMetadata.java
index 860a655..6dd6195 100644
--- a/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactMetadata.java
+++ b/src/shared/java/com/amadornes/artifactural/base/artifact/SimpleArtifactMetadata.java
@@ -43,6 +43,11 @@ public class SimpleArtifactMetadata implements ArtifactMetadata {
}
}
+ @Override
+ public String toString() {
+ return "SimpleArtifactMetadata(" + entries.toString() + ", " + getHash() + ")";
+ }
+
private static class Entry {
private final String key, value;
diff --git a/src/shared/java/com/amadornes/artifactural/base/artifact/StreamableArtifact.java b/src/shared/java/com/amadornes/artifactural/base/artifact/StreamableArtifact.java
index 1b4377a..c0a9f57 100644
--- a/src/shared/java/com/amadornes/artifactural/base/artifact/StreamableArtifact.java
+++ b/src/shared/java/com/amadornes/artifactural/base/artifact/StreamableArtifact.java
@@ -82,6 +82,11 @@ public class StreamableArtifact extends ArtifactBase {
return file;
}
+ @Override
+ public String toString() {
+ return "StreamableFileArtifact(" + file + ")";
+ }
+
}
}
diff --git a/src/shared/java/com/amadornes/artifactural/base/cache/ArtifactCacheBase.java b/src/shared/java/com/amadornes/artifactural/base/cache/ArtifactCacheBase.java
index 93ca358..37c9d97 100644
--- a/src/shared/java/com/amadornes/artifactural/base/cache/ArtifactCacheBase.java
+++ b/src/shared/java/com/amadornes/artifactural/base/cache/ArtifactCacheBase.java
@@ -74,8 +74,8 @@ public abstract class ArtifactCacheBase implements ArtifactCache {
}
@Override
- public Artifact.Cached cache(ArtifactCache cache, String specifier) {
- return artifact.cache(cache, specifier);
+ public Artifact.Cached cache(ArtifactCache cache) {
+ return artifact.cache(cache);
}
@Override
@@ -100,7 +100,10 @@ public abstract class ArtifactCacheBase implements ArtifactCache {
public File getFileLocation() throws MissingArtifactException {
return file;
}
-
+ @Override
+ public String toString() {
+ return "wrapped(" + artifact + ", " + file + ")";
+ }
};
}
diff --git a/src/shared/java/com/amadornes/artifactural/base/cache/LocatedArtifactCache.java b/src/shared/java/com/amadornes/artifactural/base/cache/LocatedArtifactCache.java
index 198f240..71fbc9d 100644
--- a/src/shared/java/com/amadornes/artifactural/base/cache/LocatedArtifactCache.java
+++ b/src/shared/java/com/amadornes/artifactural/base/cache/LocatedArtifactCache.java
@@ -2,11 +2,17 @@ package com.amadornes.artifactural.base.cache;
import com.amadornes.artifactural.api.artifact.Artifact;
import com.amadornes.artifactural.api.artifact.ArtifactIdentifier;
+import com.amadornes.artifactural.base.util.PatternReplace;
import java.io.File;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class LocatedArtifactCache extends ArtifactCacheBase {
-
+ private static final String PATTERN = "[group]/[name](/[meta_hash])/[version]/[name]-[version](-[classifier])(-[specifier]).[extension]";
private final File path;
public LocatedArtifactCache(File path) {
@@ -14,22 +20,31 @@ public class LocatedArtifactCache extends ArtifactCacheBase {
}
@Override
- public Artifact.Cached store(Artifact artifact, String specifier) {
+ public Artifact.Cached store(Artifact artifact) {
+ return doStore(getPath(artifact), artifact);
+ }
+
+ public File getPath(Artifact artifact) {
ArtifactIdentifier identifier = artifact.getIdentifier();
- File cachePath = new File(path.getAbsolutePath()
- .replace("${GROUP}", identifier.getGroup())
- .replace("${NAME}", identifier.getName())
- .replace("${VERSION}", identifier.getVersion())
- .replace("${CLASSIFIER}", identifier.getClassifier())
- .replace("${EXTENSION}", identifier.getExtension())
- .replace("${SPECIFIER}", specifier)
- .replace("${META_HASH}", artifact.getMetadata().getHash())
- );
- return doStore(cachePath, artifact);
+ Map<String, String> names = Stream.of(
+ entry("group", identifier.getGroup()),
+ entry("name", identifier.getName()),
+ entry("version", identifier.getVersion()),
+ entry("classifier", identifier.getClassifier()),
+ entry("extension", identifier.getExtension()),
+ //entry("specifier", specifier), /?
+ entry("meta_hash", artifact.getMetadata().getHash())
+ ).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+ return new File(path, PatternReplace.replace(PATTERN, names));
}
- public static File expand(File path) {
- return new File(path, "${GROUP}/${NAME}/${META_HASH}/${NAME}-${VERSION}-${CLASSIFIER}-${SPECIFIER}.${EXTENSION}");
+ private static <K,V> Entry<K,V> entry(K key, V value) {
+ return new AbstractMap.SimpleEntry<>(key, value);
+ }
+
+ @Override
+ public String toString() {
+ return "LocatedArtifactCache(" + path + ")";
}
}
diff --git a/src/shared/java/com/amadornes/artifactural/base/util/HashFunction.java b/src/shared/java/com/amadornes/artifactural/base/util/HashFunction.java
new file mode 100644
index 0000000..275b53c
--- /dev/null
+++ b/src/shared/java/com/amadornes/artifactural/base/util/HashFunction.java
@@ -0,0 +1,112 @@
+package com.amadornes.artifactural.base.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+
+import org.apache.commons.io.IOUtils;
+
+//These are all standard hashing functions the JRE is REQUIRED to have, so add a nice factory that doesnt require catching annoying exceptions;
+public enum HashFunction {
+ MD5("md5", 32),
+ SHA1("SHA-1", 40),
+ SHA256("SHA-256", 64);
+
+ private String algo;
+ private String pad;
+
+ private HashFunction(String algo, int length) {
+ this.algo = algo;
+ this.pad = String.format("%0" + length + "d", 0);
+ }
+
+ public String getExtension() {
+ return this.name().toLowerCase(Locale.ENGLISH);
+ }
+
+ public Instance create() {
+ return new Instance();
+ }
+
+ public MessageDigest get() {
+ try {
+ return MessageDigest.getInstance(algo);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e); //Never happens
+ }
+ }
+
+ public String hash(File file) throws IOException {
+ try (FileInputStream fin = new FileInputStream(file)) {
+ return hash(fin);
+ }
+ }
+
+ public String hash(Iterable<File> files) throws IOException {
+ MessageDigest hash = get();
+ byte[] buf = new byte[1024];
+
+ for (File file : files) {
+ if (!file.exists())
+ continue;
+
+ try (FileInputStream fin = new FileInputStream(file)) {
+ int count = -1;
+ while ((count = fin.read(buf)) != -1)
+ hash.update(buf, 0, count);
+ }
+ }
+ return pad(new BigInteger(1, hash.digest()).toString(16));
+ }
+
+ public String hash(String data) {
+ return hash(data.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public String hash(InputStream stream) throws IOException {
+ return hash(IOUtils.toByteArray(stream));
+ }
+
+ public String hash(byte[] data) {
+ return pad(new BigInteger(1, get().digest(data)).toString(16));
+ }
+
+ public String pad(String hash) {
+ return (pad + hash).substring(hash.length());
+ }
+
+ public class Instance {
+ private MessageDigest digest = HashFunction.this.get();
+ public void update(byte input) {
+ digest.update(input);
+ }
+ public void update(byte[] input) {
+ digest.update(input);
+ }
+ public void update(byte[] input, int offset, int length) {
+ digest.update(input, offset, length);
+ }
+ public void update(ByteBuffer input) {
+ digest.update(input);
+ }
+ public void update(String input) {
+ update(input.getBytes(StandardCharsets.UTF_8));
+ }
+ public void update(int input) {
+ update(new byte[] { (byte)((input & 0xFF000000) >> 24), (byte)((input & 0xFF000000) >> 16), (byte)((input & 0xFF000000) >> 8), (byte)((input & 0xFF000000))});
+ }
+ public byte[] digest() {
+ return digest.digest();
+ }
+ public String finish() {
+ return pad(new BigInteger(1, digest()).toString(16));
+ }
+ }
+}
diff --git a/src/shared/java/com/amadornes/artifactural/base/util/PatternReplace.java b/src/shared/java/com/amadornes/artifactural/base/util/PatternReplace.java
new file mode 100644
index 0000000..80216d6
--- /dev/null
+++ b/src/shared/java/com/amadornes/artifactural/base/util/PatternReplace.java
@@ -0,0 +1,140 @@
+package com.amadornes.artifactural.base.util;
+
+import java.util.Map;
+
+public class PatternReplace {
+ /*
+ * Replaces a patterened string, with support for optional groups.
+ * Example:
+ * Values:
+ * group: net/minecraftforge
+ * name: forge
+ * version: 1.0
+ * ext: jar
+ *
+ * Example: [group]/[name]/[version]/[name]-[version](-[classifier]).[ext]
+ * {classifier: test} net/minecraftforge/forge/1.0/forge-1.0-test.jar
+ * {classifier: null} net/minecraftforge/forge/1.0/forge-1.0.jar
+ *
+ * Nested Optionals are supported:
+ * Example: [group]/[name]/[version]/[name]-[version](-[classifier](-[suffix])).[ext]
+ * {classifier: test, suffix: foo} net/minecraftforge/forge/1.0/forge-1.0-test-foo.jar
+ * {classifier: test, suffix: foo} net/minecraftforge/forge/1.0/forge-1.0-test.jar
+ * {classifier: null, suffix: foo} net/minecraftforge/forge/1.0/forge-1.0.jar
+ *
+ * Compound optionals are supported:
+ * Example: [group]/[name]/[version]/[name]-[version](-[classifier]-[suffix]).[ext]
+ * {classifier: test, suffix: foo} net/minecraftforge/forge/1.0/forge-1.0-test-foo.jar
+ * {classifier: test, suffix: null} net/minecraftforge/forge/1.0/forge-1.0.jar
+ * {classifier: null, suffix: foo} net/minecraftforge/forge/1.0/forge-1.0.jar
+ *
+ *
+ * TODO: Support nested names?
+ * Example: [group]/[name]/[version]/[name]-[version](-[classifier[suffix]]).[ext]
+ * {classifierFoo: test, suffix: Foo} net/minecraftforge/forge/1.0/forge-1.0-test.jar
+ * {classifierFoo: null, suffix: Foo} net/minecraftforge/forge/1.0/forge-1.0.jar
+ */
+ public static String replace(String pattern, Map<String, String> values) {
+ if (pattern == null) return null;
+ if (pattern.isEmpty()) return "";
+
+ Optional optional = null;
+ StringBuffer name = null;
+ StringBuffer ret = new StringBuffer();
+
+ char[] chars = pattern.toCharArray();
+ for (int x = 0; x < chars.length; x++) {
+ char c = chars[x];
+ if (c == '\\') {
+ if (x == chars.length -1)
+ throw new IllegalArgumentException("Escape character can not be end of pattern: " + pattern);
+ x++;
+ ret.append(chars[x]);
+ continue;
+ }
+ switch (c) {
+ case '[':
+ if (name != null)
+ throw new IllegalArgumentException("Nested names are not supported @ " + x + " : " + pattern);
+ name = new StringBuffer();
+ break;
+ case ']':
+ if (name == null)
+ throw new IllegalArgumentException("Name closing found without opening @ " + x + " : " + pattern);
+ String key = name.toString();
+ if (key.isEmpty())
+ throw new IllegalArgumentException("Name can not be empty @ " + x + ": " + pattern);
+ if (optional != null)
+ optional.setKey(key, values);
+ else
+ ret.append(values.get(key)); // appends 'null' if missing, if you want "" then use ([name])
+ // Should we have this default to not replacing at all if value is not set to allow chaining?
+ // Meaning: '[key]' == '[key]' if 'key' is not set.
+ // Current: '[key]' == 'null'
+ name = null;
+ break;
+ case '(':
+ optional = new Optional(optional);
+ break;
+ case ')':
+ if (optional == null)
+ throw new IllegalArgumentException("Optional closing found without opening @ " + x + ": " + pattern);
+ optional = optional.finish(x, pattern, ret);
+ break;
+ default:
+ if (name != null)
+ name.append(c);
+ else if (optional != null)
+ optional.append(c);
+ else
+ ret.append(c);
+ }
+ }
+ if (optional != null)
+ throw new IllegalArgumentException("Missing closing of optional value: " + pattern);
+ if (name != null)
+ throw new IllegalArgumentException("Missing closing of name entry: " + pattern);
+ return ret.toString();
+ }
+
+ public static String quote(String value) {
+ return value.replaceAll("\\", "\\\\")
+ .replaceAll("(", "\\(")
+ .replaceAll(")", "\\)")
+ .replaceAll("[", "\\[")
+ .replaceAll("]", "\\]");
+ }
+
+ private static class Optional {
+ private final Optional parent;
+ private final StringBuffer buf = new StringBuffer();
+ private boolean hadAll = true;
+ private boolean hadValue = false;
+
+ private Optional(Optional parent) {
+ this.parent = parent;
+ }
+
+ public void append(char c) {
+ buf.append(c);
+ }
+
+ private void setKey(String key, Map<String, String> values) {
+ hadValue = true;
+ String value = values.get(key);
+ if (value != null && !value.isEmpty()) {
+ hadAll &= true;
+ buf.append(value);
+ } else
+ hadAll = false;
+ }
+
+ public Optional finish(int position, String pattern, StringBuffer ret) {
+ if (!hadValue)
+ throw new IllegalArgumentException("Invalid optional, missing inner name @ " + position +": " + pattern);
+ if (hadAll)
+ (parent == null ? ret : parent.buf).append(buf);
+ return parent;
+ }
+ }
+}