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.File;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.PrintWriter;
025import java.net.URL;
026import java.nio.file.Files;
027import java.nio.file.Path;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031import java.util.jar.JarEntry;
032import java.util.jar.JarFile;
033import java.util.regex.Pattern;
034import javax.inject.Inject;
035import lombok.NoArgsConstructor;
036import lombok.ToString;
037import lombok.extern.slf4j.Slf4j;
038import org.apache.maven.artifact.Artifact;
039import org.apache.maven.artifact.ArtifactUtils;
040import org.apache.maven.plugin.MojoExecutionException;
041import org.apache.maven.plugin.MojoFailureException;
042import org.apache.maven.plugins.annotations.Mojo;
043import org.apache.maven.plugins.annotations.Parameter;
044import org.apache.maven.project.MavenProject;
045
046import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
047import static java.nio.file.StandardOpenOption.CREATE;
048import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
049import static java.nio.file.StandardOpenOption.WRITE;
050import static java.util.stream.Collectors.groupingBy;
051import static java.util.stream.Collectors.mapping;
052import static java.util.stream.Collectors.toList;
053import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES;
054import static org.apache.maven.plugins.annotations.ResolutionScope.RUNTIME;
055
056/**
057 * {@link org.apache.maven.plugin.Mojo} to generate javadoc options file for
058 * {@code maven-javadoc-plugin}.
059 *
060 * {@injected.fields}
061 *
062 * {@maven.plugin.fields}
063 *
064 * {@bean.info}
065 *
066 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
067 */
068@Mojo(name = "generate-options-file", requiresDependencyResolution = RUNTIME,
069      defaultPhase = GENERATE_SOURCES, requiresProject = true)
070@NoArgsConstructor @ToString @Slf4j
071public class GenerateOptionsFileMojo extends AbstractJavadocMojo {
072    private static final Pattern JAR_ENTRY_PATTERN = Pattern.compile("^(package|element)[^-]*-list$");
073
074    @Parameter(property = "outputDirectory", defaultValue = "${project.build.directory}/javadoc-options")
075    private File outputDirectory = null;
076
077    @Parameter(defaultValue = "false", property = "includeDependencyManagement")
078    private boolean includeDependencyManagement = false;
079
080    @Parameter(property = "doclet")
081    private String doclet = null;
082
083    @Inject private MavenProject project = null;
084
085    @Override
086    public void execute() throws MojoExecutionException, MojoFailureException {
087        super.execute();
088
089        try {
090            if (! isSkip()) {
091                Set<URL> set = getLinkSet(project, includeDependencyManagement);
092                Map<URL,List<Artifact>> map =
093                    getResolvedOfflinelinkMap(project, includeDependencyManagement).entrySet().stream()
094                    .collect(groupingBy(Map.Entry::getValue, mapping(Map.Entry::getKey, toList())));
095
096                set.removeAll(map.keySet());
097
098                generateOutput(set, map);
099            } else {
100                log.info("Skipping javadoc options file generation.");
101            }
102        } catch (Throwable throwable) {
103            log.error("{}", throwable.getMessage(), throwable);
104
105            if (throwable instanceof MojoExecutionException) {
106                throw (MojoExecutionException) throwable;
107            } else if (throwable instanceof MojoFailureException) {
108                throw (MojoFailureException) throwable;
109            } else {
110                throw new MojoExecutionException(throwable.getMessage(), throwable);
111            }
112        }
113    }
114
115    private void generateOutput(Set<URL> set, Map<URL,List<Artifact>> map) throws IOException {
116        Path parent = outputDirectory.toPath();
117
118        Files.createDirectories(parent);
119
120        Path options = parent.resolve("options");
121
122        try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(options, CREATE, WRITE, TRUNCATE_EXISTING))) {
123            if (doclet != null) {
124                out.println("-doclet");
125                out.println(doclet);
126            }
127
128            for (URL url : set) {
129                out.println("-link");
130                out.println(url);
131            }
132
133            for (Map.Entry<URL,List<Artifact>> entry : map.entrySet()) {
134                List<Artifact> artifacts = entry.getValue();
135
136                for (Artifact artifact : artifacts) {
137                    Path location = parent.resolve(ArtifactUtils.versionlessKey(artifact));
138
139                    Files.createDirectories(location);
140
141                    try (JarFile jar = new JarFile(artifact.getFile())) {
142                        List<JarEntry> entries =
143                            jar.stream()
144                            .filter(t -> JAR_ENTRY_PATTERN.matcher(t.getName()).matches())
145                            .collect(toList());
146
147                        if (! entries.isEmpty()) {
148                            for (JarEntry jarEntry : entries) {
149                                try (InputStream in = jar.getInputStream(jarEntry)) {
150                                    Files.copy(in, location.resolve(jarEntry.getName()), REPLACE_EXISTING);
151                                }
152                            }
153
154                            Path packageList = location.resolve("package-list");
155                            Path elementList = location.resolve("element-list");
156
157                            if (! Files.exists(packageList)) {
158                                Files.copy(elementList, packageList);
159                            } else if (! Files.exists(elementList)) {
160                                Files.copy(packageList, elementList);
161                            }
162
163                            out.println("-linkoffline");
164                            out.println(entry.getKey());
165                            out.println(location);
166                        } else {
167                            log.warn("{}: Location directory is empty; skipping...", location);
168                        }
169                    }
170                }
171            }
172        }
173    }
174}