Java tutorial
/* * Copyright (c) 2014, Inversoft Inc., All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ package org.savantbuild.dep.maven; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import org.apache.commons.lang.StringUtils; import org.savantbuild.dep.DefaultDependencyService; import org.savantbuild.dep.domain.ArtifactID; import org.savantbuild.dep.domain.ArtifactMetaData; import org.savantbuild.dep.domain.License; import org.savantbuild.dep.domain.Publication; import org.savantbuild.dep.domain.ReifiedArtifact; import org.savantbuild.dep.domain.Version; import org.savantbuild.dep.domain.VersionException; import org.savantbuild.dep.workflow.PublishWorkflow; import org.savantbuild.dep.workflow.process.CacheProcess; import org.savantbuild.dep.xml.ArtifactTools; import org.savantbuild.net.NetTools; import org.savantbuild.output.Output; import org.savantbuild.output.SystemOutOutput; import org.savantbuild.security.MD5; import static java.util.Arrays.asList; /** * The bridge between Maven artifacts and Savant artifacts. * * @author Brian Pontarelli */ public class SavantBridge { private final CacheProcess cacheProcess; private final boolean debug; private final GroupMappings groupMappings; private final BufferedReader input; private final Map<String, Map<License, String>> licenseMappings = new HashMap<>(); private final PublishWorkflow publishWorkflow; private final DefaultDependencyService service; private boolean includeTestDependencies; public SavantBridge(Path directory, GroupMappings groupMappings, boolean debug) { this.input = new BufferedReader(new InputStreamReader(System.in, Charset.forName("UTF-8"))); this.groupMappings = groupMappings; Output output = new SystemOutOutput(true); this.service = new DefaultDependencyService(output); // Cache only workflow that will check if the artifact coming from Maven already exists in our repository this.cacheProcess = new CacheProcess(output, directory.toString()); this.publishWorkflow = new PublishWorkflow(this.cacheProcess); this.debug = debug; } public void run() { includeTestDependencies = askYN("Include test dependencies"); MavenArtifact mavenArtifact = new MavenArtifact(); mavenArtifact.group = ask("Maven group (i.e. commons-collections)", null, "Invalid input. Please re-enter", StringUtils::isNotBlank); mavenArtifact.id = ask("Maven artifact id (i.e. commons-collections)", null, "Invalid input. Please re-enter", StringUtils::isNotBlank); mavenArtifact.version = ask("Maven artifact version (i.e. 3.0.GA.1)", null, "Invalid input. Please re-enter", StringUtils::isNotBlank); buildMavenGraph(mavenArtifact, new HashSet<>(), new HashSet<>()); downloadAndProcess(mavenArtifact); } private String ask(String message, String defaultValue, String errorMessage, Predicate<String> predicate) { String answer; boolean valid; do { System.out.printf(message + (defaultValue != null ? " [" + defaultValue + "]" : "") + "?\n"); try { answer = input.readLine(); } catch (IOException e) { throw new RuntimeException(e); } if (StringUtils.isBlank(answer) && defaultValue != null) { return defaultValue; } if (predicate != null) { valid = predicate.test(answer); if (!valid) { System.out.printf(errorMessage); } } else { valid = true; } } while (!valid); return answer; } private String askMultiLine(String message) { System.out.printf(message); String text = null; while (text == null) { StringBuilder build = new StringBuilder(); String line; try { while ((line = input.readLine()) != null) { build.append(line).append("\n"); } } catch (IOException e) { throw new RuntimeException(e); } text = build.toString().trim(); if (text.length() == 0) { System.out.printf("Invalid license text. Please re-enter\n\n"); text = null; } } return text; } private boolean askYN(String message) { String answer; boolean valid; do { System.out.printf(message + "?\n"); try { answer = input.readLine(); } catch (IOException e) { throw new RuntimeException(e); } valid = answer != null && (answer.equals("y") || answer.equals("n")); } while (!valid); return answer.equals("y"); } /** * Recursively populates the Maven dependency graph. * * @param mavenArtifact The maven artifact to fetch the graph for. */ private void buildMavenGraph(MavenArtifact mavenArtifact, Set<MavenArtifact> cycleCheck, Set<MavenArtifact> visitedArtifacts) { if (cycleCheck.contains(mavenArtifact)) { throw new RuntimeException( "The Maven artifact you are trying to convert contains a cycle in its dependencies. The cycle is for the artifact [" + mavenArtifact + "]. Cycles are impossible in the real world, so it seems as though someone has jimmied the POM."); } MavenArtifact existing = visitedArtifacts.stream().filter(mavenArtifact::equals).findFirst().orElse(null); if (existing != null) { mavenArtifact.savantArtifact = existing.savantArtifact; return; } makeSavantArtifact(mavenArtifact); // If the artifact has already been fetched, skip it if (cacheProcess.fetch(mavenArtifact.savantArtifact, mavenArtifact.savantArtifact.getArtifactFile(), null) != null) { return; } Path pomFile = downloadItem(mavenArtifact, mavenArtifact.getPOM()); if (pomFile == null) { throw new RuntimeException("Invalid Maven artifact [" + mavenArtifact + "]. It doesn't appear to exist in the Maven repository. Is it correct?"); } if (debug) { try { System.out.println("POM is " + new String(Files.readAllBytes(pomFile))); } catch (IOException e) { e.printStackTrace(); } } final POM pom = new POM(pomFile); final Map<String, String> properties = pom.properties; mavenArtifact.dependencies.addAll(pom.dependencies); // Load the parent POM's dependencies and properties POM current = pom; while (current.parentId != null) { MavenArtifact parentArtifact = new MavenArtifact(current.parentGroup, current.parentId, current.parentVersion); pomFile = downloadItem(parentArtifact, parentArtifact.getPOM()); current.parent = new POM(pomFile); current.parent.properties.forEach(properties::putIfAbsent); mavenArtifact.dependencies.addAll(current.dependencies); current = current.parent; } // Replace the properties and resolve artifact versions from parent POMs mavenArtifact.dependencies.forEach((dependency) -> { dependency.group = replaceProperties(dependency.group, properties); dependency.id = replaceProperties(dependency.id, properties); dependency.type = replaceProperties(dependency.type, properties); dependency.scope = replaceProperties(dependency.scope, properties); if (dependency.version == null) { dependency.version = pom.resolveDependencyVersion(dependency); if (dependency.version == null) { dependency.version = ask("Unable to determine version for dependency [" + dependency + "]. Maven allows this, " + "Savant does not. You must provide the correct version of the Maven artifact to use.", null, "You must supply a version", StringUtils::isNotBlank); } } dependency.version = replaceProperties(dependency.version, properties); }); // Remove dups Set<MavenArtifact> dependencies = new HashSet<>(mavenArtifact.dependencies); mavenArtifact.dependencies.clear(); mavenArtifact.dependencies.addAll(dependencies); // Ask which dependencies to include in the AMD mavenArtifact.dependencies.removeIf((dependency) -> { // Remove test dependencies if (!includeTestDependencies && dependency.scope.equalsIgnoreCase("test")) { return true; } // Ask if they want to keep it String includeString = ask( "Include dependency [" + dependency + "] in scope [" + dependency.scope + "] in the Savant AMD file ([y]es/[n]o) ", "y", "Invalid response", (response) -> StringUtils.isNotBlank(response) && (response.equals("y") || response.equals("n"))); boolean include = includeString.equals("y"); if (include) { dependency.scope = ask( "Enter scope for dependency (provided, compile, compile-optional, runtime, runtime-optional, test-compile, test-runtime)", dependency.scope, "Invalid response", (response) -> StringUtils.isNotBlank(response) && (response.equals("provided") || response.equals("compile") || response.equals("compile-optional") || response.equals("runtime") || response.equals("runtime-optional") || response.equals("test-compile") || response.equals("test-runtime"))); } return !include; }); // Mark the maven artifact as visited and then traverse graph. After the graph has been traversed, remove the artifact // from the visited list because that list is only used to check for cycles cycleCheck.add(mavenArtifact); visitedArtifacts.add(mavenArtifact); mavenArtifact.dependencies .forEach((dependency) -> buildMavenGraph(dependency, cycleCheck, visitedArtifacts)); cycleCheck.remove(mavenArtifact); } private void downloadAndProcess(MavenArtifact mavenArtifact) { // Check if the file already exists and if not, fetch it if (cacheProcess.fetch(mavenArtifact.savantArtifact, mavenArtifact.savantArtifact.getArtifactFile(), null) == null) { Path file = downloadItem(mavenArtifact, mavenArtifact.getMainFile()); if (file == null) { throw new RuntimeException("Unable to download Maven artifact " + mavenArtifact); } Path sourceFile = downloadItem(mavenArtifact, mavenArtifact.getSourceFile()); ArtifactMetaData amd = new ArtifactMetaData(mavenArtifact.getSavantDependencies(), mavenArtifact.savantArtifact.licenses); if (debug) { try { Path temp = ArtifactTools.generateXML(amd); System.out.println("Writing out AMD file"); System.out.println(new String(Files.readAllBytes(temp))); } catch (IOException e) { // never } } Publication publication = new Publication(mavenArtifact.savantArtifact, amd, file, sourceFile); service.publish(publication, publishWorkflow); } mavenArtifact.dependencies.forEach(this::downloadAndProcess); } private Path downloadItem(MavenArtifact mavenArtifact, String item) { try { URI md5URI = NetTools.build("http://repo1.maven.org/maven2", mavenArtifact.group.replace('.', '/'), mavenArtifact.id, mavenArtifact.version, item + ".md5"); Path md5File = NetTools.downloadToPath(md5URI, null, null, null); MD5 md5 = MD5.load(md5File); URI uri = NetTools.build("http://repo1.maven.org/maven2", mavenArtifact.group.replace('.', '/'), mavenArtifact.id, mavenArtifact.version, item); return NetTools.downloadToPath(uri, null, null, md5); } catch (URISyntaxException | IOException e) { throw new RuntimeException("ERROR", e); } } private Map<License, String> getLicenses(MavenArtifact mavenArtifact) { Map<License, String> licenses = licenseMappings.get(mavenArtifact.group + ":" + mavenArtifact.id); if (licenses == null) { String licenseNames = ask( "License(s) for this artifact - comma-separated list (" + asList(License.values()) + ")", License.ApacheV2_0.toString(), "Invalid license. Please re-enter.\n", (answer) -> { String[] parts = answer.split("\\W*,\\W*"); for (String part : parts) { try { License.valueOf(part); } catch (IllegalArgumentException e) { // Bad license return false; } } return true; }); licenses = new HashMap<>(); String[] parts = licenseNames.split("\\W*,\\W*"); for (String part : parts) { License license = License.valueOf(part); String text = null; if (license.requiresText) { text = askMultiLine("The license type [" + license + "] requires license text. Enter it here and terminate your entry with the ctrl-d.\n"); } licenses.put(license, text); } licenses.keySet().forEach((license) -> { }); licenseMappings.put(mavenArtifact.group + ":" + mavenArtifact.id, licenses); } return licenses; } private Version getSemanticVersion(String version) { boolean keep = false; if (isSemantic(version)) { System.out.printf("The version [%s] appears to be semantic. Does you want to keep it [y]?\n", version); String answer; try { answer = input.readLine(); } catch (IOException e) { throw new RuntimeException(e); } if (answer.equals("") || answer.equals("y")) { keep = true; } } else { System.out.printf( "The version [%s] is not semantic. You need to give the project a valid semantic version.\n", version); } if (!keep) { do { System.out.println("Enter the new version to use"); try { version = input.readLine(); } catch (IOException e) { throw new RuntimeException(e); } if (version != null && version.length() > 0 && isSemantic(version)) { keep = true; } else { System.out.println("Invalid semantic version. Please re-enter."); } } while (!keep); } return new Version(version); } private boolean isSemantic(String version) { try { new Version(version); } catch (VersionException e) { return false; } return true; } private void makeSavantArtifact(MavenArtifact mavenArtifact) { System.out.println(); System.out.println( "---------------------------------------------------------------------------------------------------------"); System.out.println("Converting Maven artifact [" + mavenArtifact + "] to a Savant Artifact"); System.out.println( "---------------------------------------------------------------------------------------------------------"); String savantGroup = groupMappings.map(mavenArtifact.group); if (!savantGroup.equals(mavenArtifact.group)) { System.out.println( "Mapped Maven group [" + mavenArtifact.group + "] to Savant group [" + savantGroup + "]"); } else if (!savantGroup.contains(".")) { savantGroup = ask("That group looks weaksauce. Enter the group to use with Savant", mavenArtifact.group, null, null); // Store the mapping if they changed the group if (!mavenArtifact.group.equals(savantGroup)) { groupMappings.add(mavenArtifact.group, savantGroup); } } Version savantVersion = getSemanticVersion(mavenArtifact.version); Map<License, String> licenses = getLicenses(mavenArtifact); mavenArtifact.savantArtifact = new ReifiedArtifact(new ArtifactID(savantGroup, mavenArtifact.id, mavenArtifact.id, (mavenArtifact.type == null ? "jar" : mavenArtifact.type)), savantVersion, licenses); } private String replaceProperties(String value, Map<String, String> properties) { if (value == null) { return null; } for (String key : properties.keySet()) { value = value.replace("${" + key + "}", properties.get(key)); } return value; } }