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}