001package ball.maven.plugins.javadoc;
002/*-
003 * ##########################################################################
004 * Javadoc Maven Plugin
005 * %%
006 * Copyright (C) 2021 - 2023 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.net.URL;
022import java.util.Collection;
023import java.util.Comparator;
024import java.util.LinkedHashSet;
025import java.util.Map;
026import java.util.Objects;
027import java.util.Set;
028import java.util.TreeMap;
029import java.util.TreeSet;
030import java.util.stream.Stream;
031import javax.inject.Inject;
032import lombok.Getter;
033import lombok.NoArgsConstructor;
034import lombok.ToString;
035import lombok.extern.slf4j.Slf4j;
036import org.apache.maven.artifact.Artifact;
037import org.apache.maven.artifact.ArtifactUtils;
038import org.apache.maven.artifact.DefaultArtifact;
039import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
040import org.apache.maven.execution.MavenSession;
041import org.apache.maven.model.Dependency;
042import org.apache.maven.plugin.AbstractMojo;
043import org.apache.maven.plugin.MojoExecutionException;
044import org.apache.maven.plugin.MojoFailureException;
045import org.apache.maven.plugins.annotations.Parameter;
046import org.apache.maven.project.DefaultProjectBuildingRequest;
047import org.apache.maven.project.MavenProject;
048import org.apache.maven.project.ProjectBuildingRequest;
049import org.apache.maven.repository.RepositorySystem;
050import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
051
052import static java.util.stream.Collectors.toCollection;
053import static lombok.AccessLevel.PROTECTED;
054import static org.apache.commons.lang3.StringUtils.EMPTY;
055import static org.apache.commons.lang3.StringUtils.isBlank;
056import static org.apache.commons.lang3.StringUtils.isNotBlank;
057
058/**
059 * Abstract base class for javadoc {@link org.apache.maven.plugin.Mojo}s.
060 *
061 * {@injected.fields}
062 *
063 * {@bean.info}
064 *
065 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
066 */
067@NoArgsConstructor(access = PROTECTED) @Getter @ToString @Slf4j
068public abstract class AbstractJavadocMojo extends AbstractMojo {
069    @Parameter(required = false)
070    private Link[] links = new Link[] { };
071
072    @Parameter(required = false)
073    private Offlinelink[] offlinelinks = new Offlinelink[] { };
074
075    @Parameter(defaultValue = "false", property = "maven.javadoc.skip")
076    private boolean skip = false;
077
078    @Inject private MavenSession session = null;
079    @Inject private ArtifactHandlerManager manager = null;
080    @Inject private RepositorySystem system = null;
081    @Inject private ArtifactResolver resolver = null;
082
083    /**
084     * Method to produce a {@link Stream} of
085     * {@link MavenProject#getDependencies()} and
086     * {@link MavenProject#getDependencyManagement()}
087     * {@link Dependency Dependencies}.
088     *
089     * @param   project         The {@link MavenProject}.
090     *
091     * @return  The {@link Dependency} {@link Stream}.
092     */
093    protected Stream<Dependency> getDependencyManagementStream(MavenProject project) {
094        Stream<Dependency> stream =
095            Stream.of(project.getDependencies(), project.getDependencyManagement().getDependencies())
096            .filter(Objects::nonNull)
097            .flatMap(Collection::stream);
098
099        return stream;
100    }
101
102    /**
103     * Method to get the {@link Set} of {@link Link} {@link URL}s.
104     *
105     * @param   project         The {@link MavenProject}.
106     * @param   includeDependencyManagement
107     *                          Whether or not to include dependency
108     *                          management in the analysis.
109     *
110     * @return  The {@link Set} of {@link Link} {@link URL}s.
111     */
112    protected Set<URL> getLinkSet(MavenProject project, boolean includeDependencyManagement) {
113        Set<URL> set = new LinkedHashSet<>();
114
115        for (Link link : links) {
116            if (link.getArtifact() != null) {
117                Stream<Artifact> stream = project.getArtifacts().stream();
118
119                if (includeDependencyManagement) {
120                    stream =
121                        Stream.concat(stream,
122                                      getDependencyManagementStream(project)
123                                      .filter(t -> isNotBlank(t.getVersion()))
124                                      .map(JavadocArtifact::new));
125                }
126
127                stream
128                    .filter(link::include)
129                    .map(link::getUrl)
130                    .forEach(set::add);
131            } else {
132                set.add(link.getUrl());
133            }
134        }
135
136        return set;
137    }
138
139    /**
140     * Method to get the {@link Map} of {@link Offlinelink}
141     * {@link Artifact}s to {@link URL}s.
142     *
143     * @param   project         The {@link MavenProject}.
144     * @param   includeDependencyManagement
145     *                          Whether or not to include dependency
146     *                          management in the analysis.
147     *
148     * @return  The {@link Map} of {@link Offlinelink} {@link Artifact}s to
149     *          {@link URL}s.
150     */
151    protected Map<Artifact,URL> getResolvedOfflinelinkMap(MavenProject project, boolean includeDependencyManagement) {
152        TreeMap<Artifact,URL> map = new TreeMap<>(Comparator.comparing(ArtifactUtils::versionlessKey));
153
154        for (Offlinelink offlinelink : offlinelinks) {
155            project.getArtifacts().stream()
156                .filter(t -> Objects.equals(t.getType(), "jar"))
157                .filter(t -> Objects.equals(t.getClassifier(), "javadoc"))
158                .filter(offlinelink::include)
159                .forEach(t -> map.putIfAbsent(t, offlinelink.getUrl()));
160        }
161
162        Set<Artifact> artifacts =
163            project.getArtifacts().stream()
164            .filter(t -> Objects.equals(t.getType(), "jar"))
165            .filter(t -> isBlank(t.getClassifier()))
166            .map(JavadocArtifact::new)
167            .collect(toCollection(() -> new TreeSet<>(map.comparator())));
168
169        if (includeDependencyManagement) {
170            getDependencyManagementStream(project)
171                .filter(t -> Objects.equals(t.getType(), "jar"))
172                .filter(t -> isBlank(t.getClassifier()))
173                .filter(t -> isNotBlank(t.getVersion()))
174                .map(JavadocArtifact::new)
175                .forEach(artifacts::add);
176        }
177
178        artifacts.removeAll(map.keySet());
179
180        ProjectBuildingRequest request = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
181
182        for (Offlinelink offlinelink : offlinelinks) {
183            Set<Artifact> set =
184                artifacts.stream()
185                .filter(offlinelink::include)
186                .collect(toCollection(LinkedHashSet::new));
187
188            for (Artifact artifact : set) {
189                URL url = map.get(artifact);
190
191                if (url == null) {
192                    log.info("Resolving {}...", artifact);
193
194                    try {
195                        Artifact key = resolver.resolveArtifact(request, artifact).getArtifact();
196
197                        map.putIfAbsent(key, offlinelink.getUrl(artifact));
198                    } catch (Exception exception) {
199                        log.warn("{}: {}", artifact, exception.getMessage());
200                        log.debug("{}", exception);
201                    }
202                } else {
203                    if (! Objects.equals(url, offlinelink.getUrl(artifact))) {
204                        log.warn("{} matches {} but was previously resolved with {}", artifact, offlinelink, url);
205                    }
206                }
207            }
208        }
209
210        return map;
211    }
212
213    @Override
214    public void execute() throws MojoExecutionException, MojoFailureException {
215    }
216
217    private class JavadocArtifact extends DefaultArtifact {
218        public JavadocArtifact(Artifact artifact) {
219            this(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion());
220        }
221
222        public JavadocArtifact(Dependency dependency) {
223            this(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
224        }
225
226        public JavadocArtifact(String gav) { this(gav.split("[:]")); }
227
228        public JavadocArtifact(String... gav) {
229            super(gav.length > 0 ? gav[0] : EMPTY,
230                  gav.length > 1 ? gav[1] : EMPTY,
231                  gav.length > 2 ? gav[gav.length - 1] : EMPTY,
232                  EMPTY, "jar", "javadoc", manager.getArtifactHandler("jar"));
233        }
234    }
235}