001package ball.maven.plugins.javadoc; 002/*- 003 * ########################################################################## 004 * Javadoc Maven Plugin 005 * %% 006 * Copyright (C) 2021, 2022 Allen D. Ball 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * ########################################################################## 020 */ 021import java.io.BufferedReader; 022import java.io.File; 023import java.io.InputStream; 024import java.io.InputStreamReader; 025import java.io.OutputStream; 026import java.net.MalformedURLException; 027import java.net.URI; 028import java.net.URISyntaxException; 029import java.net.URL; 030import java.nio.file.Files; 031import java.nio.file.Path; 032import java.util.Arrays; 033import java.util.List; 034import java.util.Map; 035import java.util.Properties; 036import java.util.Set; 037import javax.inject.Inject; 038import lombok.NoArgsConstructor; 039import lombok.ToString; 040import lombok.extern.slf4j.Slf4j; 041import org.apache.maven.artifact.Artifact; 042import org.apache.maven.artifact.ArtifactUtils; 043import org.apache.maven.plugin.MojoExecutionException; 044import org.apache.maven.plugin.MojoFailureException; 045import org.apache.maven.plugins.annotations.Mojo; 046import org.apache.maven.plugins.annotations.Parameter; 047import org.apache.maven.project.MavenProject; 048 049import static java.nio.charset.StandardCharsets.UTF_8; 050import static java.util.stream.Collectors.toList; 051import static org.apache.maven.plugins.annotations.ResolutionScope.RUNTIME; 052 053/** 054 * {@link org.apache.maven.plugin.Mojo} to generate offline javadoc map. 055 * For each documented package, generate the following key/value pairs: 056 * <i>package</i>/Javadoc Root URL, <i>package</i>-module / Module (e.g., 057 * java.base), and <i>package</i>-artifact / groupId:artifactId. 058 * 059 * The <i>package</i>-module and <i>package</i>-artifact key/values are 060 * absent if no module or artifact respectively are specified. 061 * 062 * {@injected.fields} 063 * 064 * {@maven.plugin.fields} 065 * 066 * {@bean.info} 067 * 068 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 069 */ 070@Mojo(name = "generate-javadoc-map", requiresDependencyResolution = RUNTIME, requiresProject = true) 071@NoArgsConstructor @ToString @Slf4j 072public class GenerateJavadocMapMojo extends AbstractJavadocMojo { 073 private static final String ELEMENT_LIST = "element-list"; 074 private static final String PACKAGE_LIST = "package-list"; 075 076 private static final List<String> NAMES = Arrays.asList(ELEMENT_LIST, PACKAGE_LIST); 077 078 private static final String MODULE_PREFIX = "module:"; 079 080 @Parameter(property = "outputDirectory", defaultValue = "${project.build.directory}") 081 private File outputDirectory = null; 082 083 @Parameter(property = "outputFileName", defaultValue = "javadoc-map.properties") 084 private String outputFileName = null; 085 086 @Parameter(defaultValue = "true", property = "includeDependencyManagement") 087 private boolean includeDependencyManagement = true; 088 089 @Inject private MavenProject project = null; 090 091 @Override 092 public void execute() throws MojoExecutionException, MojoFailureException { 093 super.execute(); 094 095 try { 096 if (! isSkip()) { 097 Properties properties = new Properties(); 098 Map<Artifact,URL> map = getResolvedOfflinelinkMap(project, includeDependencyManagement); 099 100 map.forEach((k, v) -> load(properties, k, v)); 101 102 Set<URL> set = getLinkSet(project, includeDependencyManagement); 103 104 set.removeAll(map.values()); 105 set.forEach(t -> load(properties, null, t)); 106 107 Path path = outputDirectory.toPath().resolve(outputFileName); 108 109 Files.createDirectories(path.getParent()); 110 111 try (OutputStream out = Files.newOutputStream(path)) { 112 String name = path.getFileName().toString(); 113 114 if (name.toLowerCase().endsWith(".xml")) { 115 properties.storeToXML(out, name); 116 } else { 117 properties.store(out, name); 118 } 119 } 120 } else { 121 log.info("Skipping javadoc map generation."); 122 } 123 } catch (Throwable throwable) { 124 log.error("{}", throwable.getMessage(), throwable); 125 126 if (throwable instanceof MojoExecutionException) { 127 throw (MojoExecutionException) throwable; 128 } else if (throwable instanceof MojoFailureException) { 129 throw (MojoFailureException) throwable; 130 } else { 131 throw new MojoExecutionException(throwable.getMessage(), throwable); 132 } 133 } 134 } 135 136 private void load(Properties properties, Artifact artifact, URL javadoc) { 137 URL location = (artifact != null) ? toURL(artifact) : javadoc; 138 List<String> lines = null; 139 140 for (String name : NAMES) { 141 try { 142 URL url = new URL(location + name); 143 144 try (InputStream in = url.openStream()) { 145 lines = 146 new BufferedReader(new InputStreamReader(in, UTF_8)).lines() 147 .collect(toList()); 148 } 149 150 break; 151 } catch (Exception exception) { 152 continue; 153 } 154 } 155 156 if (lines != null) { 157 String module = null; 158 159 for (String line : lines) { 160 if (line.startsWith(MODULE_PREFIX)) { 161 module = line.substring(MODULE_PREFIX.length()); 162 } else { 163 if (! properties.containsKey(line)) { 164 properties.put(line, javadoc.toString()); 165 166 if (module != null) { 167 properties.put(line + "-module", module); 168 } 169 170 if (artifact != null) { 171 properties.put(line + "-artifact", ArtifactUtils.versionlessKey(artifact)); 172 } 173 } 174 } 175 } 176 } else { 177 log.warn("Could not read any of {} from {}", NAMES, location); 178 } 179 } 180 181 private URL toURL(Artifact artifact) { 182 URL url = null; 183 184 try { 185 url = new URI("jar", artifact.getFile().toURI().toASCIIString() + "!/", null).toURL(); 186 } catch(URISyntaxException | MalformedURLException exception) { 187 log.debug("{}: {}", artifact, exception.getMessage(), exception); 188 throw new IllegalStateException(exception); 189 } 190 191 return url; 192 } 193}