Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.hyracks.maven.license; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Pattern; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SequenceWriter; import freemarker.cache.FileTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.apache.commons.io.IOUtils; import org.apache.hyracks.maven.license.freemarker.IndentDirective; import org.apache.hyracks.maven.license.freemarker.LoadFileDirective; import org.apache.hyracks.maven.license.project.LicensedProjects; import org.apache.hyracks.maven.license.project.Project; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.ProjectBuildingException; @Mojo(name = "generate", requiresProject = true, requiresDependencyResolution = ResolutionScope.TEST) public class GenerateFileMojo extends LicenseMojo { public static final Pattern FOUNDATION_PATTERN = Pattern .compile("^\\s*This product includes software developed " + "(at|by) The Apache Software Foundation \\(http://www.apache.org/\\).\\s*$".replace(" ", "\\s+"), Pattern.DOTALL | Pattern.MULTILINE); public static final Comparator<String> WHITESPACE_NORMALIZED_COMPARATOR = (o1, o2) -> o1.replaceAll("\\s+", " ") .compareTo(o2.replaceAll("\\s+", " ")); @Parameter(required = true) private File templateRootDir; @Parameter(defaultValue = "${project.build.directory}/generated-sources") private File outputDir; @Parameter private List<GeneratedFile> generatedFiles = new ArrayList<>(); @Parameter(defaultValue = "${project.build.sourceEncoding}") private String encoding; @Parameter private File licenseMapOutputFile; @Parameter private List<ExtraLicenseFile> extraLicenseMaps = new ArrayList<>(); @Parameter protected Map<String, String> templateProperties = new HashMap<>(); @Parameter private boolean stripFoundationAssertionFromNotices = true; private SortedMap<String, SortedSet<Project>> noticeMap; @java.lang.Override public void execute() throws MojoExecutionException, MojoFailureException { try { init(); readExtraMaps(); addDependenciesToLicenseMap(); resolveLicenseContent(); resolveNoticeFiles(); resolveLicenseFiles(); rebuildLicenseContentProjectMap(); combineCommonGavs(); SourcePointerResolver.execute(this); persistLicenseMap(); buildNoticeProjectMap(); generateFiles(); } catch (IOException | TemplateException | ProjectBuildingException e) { throw new MojoExecutionException("Unexpected exception: " + e, e); } } private void resolveLicenseContent() throws IOException { Set<LicenseSpec> licenseSpecs = new HashSet<>(); for (LicensedProjects licensedProjects : licenseMap.values()) { licenseSpecs.add(licensedProjects.getLicense()); } licenseSpecs.addAll(urlToLicenseMap.values()); for (LicenseSpec license : licenseSpecs) { resolveLicenseContent(license, true); } } private String resolveLicenseContent(LicenseSpec license, boolean bestEffort) throws IOException { if (license.getContent() == null) { getLog().debug("Resolving content for " + license.getUrl() + " (" + license.getContentFile() + ")"); File cFile = new File(license.getContentFile()); if (!cFile.isAbsolute()) { cFile = new File(licenseDirectory, license.getContentFile()); } if (!cFile.exists()) { if (!bestEffort) { getLog().warn("MISSING: license content file (" + cFile + ") for url: " + license.getUrl()); license.setContent("MISSING: " + license.getContentFile() + " (" + license.getUrl() + ")"); } } else { getLog().info("Reading license content from file: " + cFile); StringWriter sw = new StringWriter(); LicenseUtil.readAndTrim(sw, cFile); license.setContent(sw.toString()); } } return license.getContent(); } private void combineCommonGavs() { for (LicensedProjects licensedProjects : licenseMap.values()) { Map<String, Project> projectMap = new HashMap<>(); for (Iterator<Project> iter = licensedProjects.getProjects().iterator(); iter.hasNext();) { Project project = iter.next(); if (projectMap.containsKey(project.gav())) { Project first = projectMap.get(project.gav()); first.setLocation(first.getLocation() + "," + project.getLocation()); iter.remove(); } else { projectMap.put(project.gav(), project); } } } } private void generateFiles() throws TemplateException, IOException { Map<String, Object> props = getProperties(); Configuration config = new Configuration(); config.setTemplateLoader(new FileTemplateLoader(templateRootDir)); for (GeneratedFile generation : generatedFiles) { Template template = config.getTemplate(generation.getTemplate(), StandardCharsets.UTF_8.name()); if (template == null) { throw new IOException("Could not load template " + generation.getTemplate()); } outputDir.mkdirs(); final File file = new File(outputDir, generation.getOutputFile()); getLog().info("Writing " + file + "..."); try (final FileOutputStream fos = new FileOutputStream(file); final Writer writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { template.process(props, writer); } } } protected Map<String, Object> getProperties() { Map<String, Object> props = new HashMap<>(); props.put("indent", new IndentDirective()); props.put("loadfile", new LoadFileDirective()); props.put("project", project); props.put("noticeMap", noticeMap.entrySet()); props.put("licenseMap", licenseMap.entrySet()); props.put("licenses", urlToLicenseMap.values()); props.putAll(templateProperties); return props; } private void readExtraMaps() throws IOException { final ObjectMapper objectMapper = new ObjectMapper(); for (ExtraLicenseFile extraLicenseFile : extraLicenseMaps) { for (LicensedProjects projects : objectMapper.readValue(extraLicenseFile.getFile(), LicensedProjects[].class)) { LicenseSpec spec = urlToLicenseMap.get(projects.getLicense().getUrl()); if (spec != null) { // TODO(mblow): probably we should always favor the extra map... // propagate any license content we may have with what already has been loaded if (projects.getLicense().getContent() != null && spec.getContent() == null) { spec.setContent(projects.getLicense().getContent()); } // propagate any license displayName we may have with what already has been loaded if (projects.getLicense().getDisplayName() != null && spec.getDisplayName() == null) { spec.setDisplayName(projects.getLicense().getDisplayName()); } } for (Project p : projects.getProjects()) { p.setLocation(extraLicenseFile.getLocation()); addProject(p, projects.getLicense(), extraLicenseFile.isAdditive()); } } } } private void persistLicenseMap() throws IOException { if (licenseMapOutputFile != null) { licenseMapOutputFile.getParentFile().mkdirs(); SequenceWriter sw = new ObjectMapper().writerWithDefaultPrettyPrinter() .writeValues(licenseMapOutputFile).init(true); for (LicensedProjects entry : licenseMap.values()) { sw.write(entry); } sw.close(); } } private void rebuildLicenseContentProjectMap() throws IOException { int counter = 0; Map<String, LicensedProjects> licenseMap2 = new TreeMap<>(WHITESPACE_NORMALIZED_COMPARATOR); for (LicensedProjects lps : licenseMap.values()) { for (Project p : lps.getProjects()) { String licenseText = p.getLicenseText(); if (licenseText == null) { getLog().warn("Using license other than from within artifact: " + p.gav()); licenseText = resolveLicenseContent(lps.getLicense(), false); } LicenseSpec spec = lps.getLicense(); if (spec.getDisplayName() == null) { LicenseSpec canonicalLicense = urlToLicenseMap.get(spec.getUrl()); if (canonicalLicense != null) { spec.setDisplayName(canonicalLicense.getDisplayName()); } } if (!licenseMap2.containsKey(licenseText)) { if (!licenseText.equals(lps.getLicense().getContent())) { spec = new LicenseSpec(new ArrayList<>(), licenseText, null, spec.getDisplayName(), spec.getMetric(), spec.getUrl() + (counter++)); } licenseMap2.put(licenseText, new LicensedProjects(spec)); } final LicensedProjects lp2 = licenseMap2.get(licenseText); if (lp2.getLicense().getDisplayName() == null) { lp2.getLicense().setDisplayName(lps.getLicense().getDisplayName()); } lp2.addProject(p); } } licenseMap = licenseMap2; } private Set<Project> getProjects() { Set<Project> projects = new HashSet<>(); licenseMap.values().forEach(p -> projects.addAll(p.getProjects())); return projects; } private void buildNoticeProjectMap() { noticeMap = new TreeMap<>(WHITESPACE_NORMALIZED_COMPARATOR); for (Project p : getProjects()) { prependSourcePointerToNotice(p); final String noticeText = p.getNoticeText(); if (noticeText == null) { continue; } if (!noticeMap.containsKey(noticeText)) { noticeMap.put(noticeText, new TreeSet<>(Project.PROJECT_COMPARATOR)); } noticeMap.get(noticeText).add(p); } } private void prependSourcePointerToNotice(Project project) { if (project.getSourcePointer() != null) { String notice = project.getSourcePointer().replace("\n", "\n "); if (project.getNoticeText() != null) { notice += "\n\n" + project.getNoticeText(); } project.setNoticeText(notice); } } private void resolveNoticeFiles() throws MojoExecutionException, IOException { resolveArtifactFiles("NOTICE", entry -> entry.getName().matches("(.*/|^)" + "NOTICE" + "(.txt)?"), Project::setNoticeText, text -> stripFoundationAssertionFromNotices ? FOUNDATION_PATTERN.matcher(text).replaceAll("") : text); } private void resolveLicenseFiles() throws MojoExecutionException, IOException { resolveArtifactFiles("LICENSE", entry -> entry.getName().matches("(.*/|^)" + "LICENSE" + "(.txt)?"), Project::setLicenseText, UnaryOperator.identity()); } private void resolveArtifactFiles(final String name, Predicate<JarEntry> filter, BiConsumer<Project, String> consumer, UnaryOperator<String> contentTransformer) throws MojoExecutionException, IOException { for (Project p : getProjects()) { File artifactFile = new File(p.getArtifactPath()); if (!artifactFile.exists()) { throw new MojoExecutionException("Artifact file " + artifactFile + " does not exist!"); } else if (!artifactFile.getName().endsWith(".jar")) { getLog().info("Skipping unknown artifact file type: " + artifactFile); continue; } try (JarFile jarFile = new JarFile(artifactFile)) { SortedMap<String, JarEntry> matches = gatherMatchingEntries(jarFile, filter); if (matches.isEmpty()) { getLog().warn("No " + name + " file found for " + p.gav()); } else { if (matches.size() > 1) { getLog().warn("Multiple " + name + " files found for " + p.gav() + ": " + matches.keySet() + "; taking first"); } else { getLog().info(p.gav() + " has " + name + " file: " + matches.keySet()); } resolveContent(p, jarFile, matches.values().iterator().next(), contentTransformer, consumer, name); } } } } private void resolveContent(Project project, JarFile jarFile, JarEntry entry, UnaryOperator<String> transformer, BiConsumer<Project, String> contentConsumer, final String name) throws IOException { String text = IOUtils.toString(jarFile.getInputStream(entry), StandardCharsets.UTF_8); text = transformer.apply(text); text = LicenseUtil.trim(text); if (text.length() == 0) { getLog().warn("Ignoring empty " + name + " file ( " + entry + ") for " + project.gav()); } else { contentConsumer.accept(project, text); getLog().debug("Resolved " + name + " text for " + project.gav() + ": \n" + text); } } private SortedMap<String, JarEntry> gatherMatchingEntries(JarFile jarFile, Predicate<JarEntry> filter) { SortedMap<String, JarEntry> matches = new TreeMap<>(); Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (filter.test(entry)) { matches.put(entry.getName(), entry); } } return matches; } }