/*******************************************************************************
* Copyright (c) 2015 Bruno Medeiros and other Contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bruno Medeiros - initial API and implementation
*******************************************************************************/
package com.github.rustdt.tooling.cargo;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.CoreUtil.areEqual;
import static melnorme.utilbox.core.CoreUtil.nullToEmpty;
import static melnorme.utilbox.misc.StringUtil.nullAsEmpty;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.MessageFormat;
import java.util.EnumSet;
import melnorme.lang.tooling.bundle.DependencyRef;
import melnorme.lang.tooling.bundle.FileRef;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.collections.Collection2;
import melnorme.utilbox.collections.Indexable;
import melnorme.utilbox.collections.LinkedHashMap2;
import melnorme.utilbox.misc.HashcodeUtil;
import melnorme.utilbox.misc.Location;
/**
* A Cargo crate manifest
*/
public class CargoManifest {
/* ----------------- ----------------- */
public static class CrateDependencyRef extends DependencyRef {
public CrateDependencyRef(String bundleName, String version) {
super(bundleName, version);
}
public CrateDependencyRef(String bundleName, String version, boolean optional) {
super(bundleName, version, optional);
}
}
public static LinkedHashMap2<String, CrateDependencyRef> depsHashMap(Indexable<CrateDependencyRef> deps) {
LinkedHashMap2<String, CrateDependencyRef> depsMap = new LinkedHashMap2<>();
for(CrateDependencyRef dependencyRef : nullToEmpty(deps)) {
depsMap.put(dependencyRef.getBundleName(), dependencyRef);
}
return depsMap;
}
/* ----------------- ----------------- */
public static LinkedHashMap2<String, FileRef> fileRefHashMap(Indexable<FileRef> deps) {
LinkedHashMap2<String, FileRef> depsMap = new LinkedHashMap2<>();
for(FileRef dependencyRef : nullToEmpty(deps)) {
depsMap.put(dependencyRef.getBinaryPathString(), dependencyRef);
}
return depsMap;
}
/* ----------------- ----------------- */
protected final String name;
protected final String version;
protected final LinkedHashMap2<String, CrateDependencyRef> depsMap;
protected final FileRef libTarget;
protected final LinkedHashMap2<String, FileRef> binariesMap;
protected final LinkedHashMap2<String, FileRef> testsMap;
public CargoManifest(String name, String version, Indexable<CrateDependencyRef> deps,
FileRef libTarget, Indexable<FileRef> binaries, Indexable<FileRef> tests) {
this.name = assertNotNull(name);
this.version = version;
this.depsMap = depsHashMap(deps);
this.libTarget = libTarget;
this.binariesMap = fileRefHashMap(binaries);
this.testsMap = fileRefHashMap(tests);
}
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(!(obj instanceof CargoManifest)) return false;
CargoManifest other = (CargoManifest) obj;
return
areEqual(name, other.name) &&
areEqual(version, other.version) &&
areEqual(depsMap, other.depsMap) &&
areEqual(libTarget, other.libTarget) &&
areEqual(binariesMap, other.binariesMap) &&
areEqual(testsMap, other.testsMap)
;
}
@Override
public int hashCode() {
return HashcodeUtil.combinedHashCode(name, version);
}
@Override
public String toString() {
return MessageFormat.format("{0}[{1}]", name, nullAsEmpty(version));
}
/* ----------------- ----------------- */
public String getName() {
return name;
}
public String getVersion() {
return version;
}
public Collection2<CrateDependencyRef> getDependencies() {
return depsMap.getValuesView();
}
public FileRef getLibTarget() {
return libTarget;
}
public Collection2<FileRef> getBinaries() {
return binariesMap.getValuesView();
}
public Collection2<FileRef> getTests() {
return testsMap.getValuesView();
}
/* ----------------- ----------------- */
protected Location getSrcLocation(Location crateLocation) {
return crateLocation.resolve_fromValid("src");
}
/* ----------------- ----------------- */
public static class RustFileCollector {
protected final ArrayList2<FileRef> fileRefs = new ArrayList2<>();
protected ArrayList2<FileRef> find(Location rootLocation, Location loc) {
try {
find_do(rootLocation, loc);
} catch(IOException e) {
// Ignore
}
return fileRefs;
}
protected void find_do(Location rootLocation, Location loc) throws IOException {
Files.walkFileTree(loc.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), 1,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
String fileName = filePath.getFileName().toString();
String crateNameRef = RustNamingRules.getCrateNameRef(fileName);
if(crateNameRef != null) {
Path sourcePath = rootLocation.relativize(Location.create_fromValid(filePath));
fileRefs.add(new FileRef(crateNameRef, sourcePath.toString()));
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
});
}
}
public FileRef getEffectiveLibrary(Location crateLocation) {
if(libTarget != null) {
return libTarget;
}
Location srcLoc = getSrcLocation(crateLocation);
Location defaultLibrarySourceFile = srcLoc.resolve_valid("lib.rs");
if(defaultLibrarySourceFile.toFile().exists()) {
return new FileRef(name, null);
}
return null;
}
public Collection2<FileRef> getEffectiveBinaries(Location crateLocation) {
if(!binariesMap.isEmpty()) {
return getBinaries();
}
ArrayList2<FileRef> binaries = new ArrayList2<>();
Location srcLoc = getSrcLocation(crateLocation);
Location defaultBinarySourceFile= srcLoc.resolve_valid("main.rs");
if(defaultBinarySourceFile.toFile().exists()) {
binaries.add(new FileRef(name, null));
}
Location srcBinLoc = srcLoc.resolve_fromValid("bin");
binaries.addAll2(new RustFileCollector().find(crateLocation, srcBinLoc));
return binaries;
}
protected Location getIntegrationTestsLocation(Location crateLocaction) {
return crateLocaction.resolve_fromValid("tests");
}
public Collection2<FileRef> getEffectiveIntegrationTests(Location crateLocation) {
if(!testsMap.isEmpty()) {
return getTests();
}
ArrayList2<FileRef> binaries = new ArrayList2<>();
Location testsLoc = getIntegrationTestsLocation(crateLocation);
binaries.addAll2(new RustFileCollector().find(crateLocation, testsLoc));
return binaries;
}
public Collection2<String> getEffectiveTestTargets(Location crateLocation) {
ArrayList2<String> effectiveTestTargets = new ArrayList2<>();
for(FileRef integrationTest : getEffectiveIntegrationTests(crateLocation)) {
effectiveTestTargets.add(integrationTest.getBinaryPathString());
}
for(FileRef integrationTest : singletonIterable(getEffectiveLibrary(crateLocation))) {
effectiveTestTargets.add("lib." + integrationTest.getBinaryPathString());
}
for(FileRef bin : getEffectiveBinaries(crateLocation)) {
effectiveTestTargets.add("bin." + bin.getBinaryPathString());
}
return effectiveTestTargets;
}
protected static <T> Iterable<T> singletonIterable(T obj) {
return obj == null ? new ArrayList2<>() : ArrayList2.create(obj);
}
}