001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.tools.plugin.javadoc; 020 021import java.io.BufferedReader; 022import java.io.IOException; 023import java.net.URI; 024import java.nio.file.Files; 025import java.nio.file.Path; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.List; 029import java.util.Map; 030import java.util.Optional; 031import java.util.regex.Pattern; 032 033import org.apache.maven.settings.Settings; 034import org.codehaus.plexus.languages.java.version.JavaVersion; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * Generates links for elements (packages, classes, fields, constructors, methods) in external 040 * and/or an internal (potentially not yet existing) javadoc site. 041 * The external site must be accessible for it to be considered due to the different fragment formats. 042 */ 043public class JavadocLinkGenerator { 044 /** 045 * Javadoc tool version ranges whose generated sites expose different link formats. 046 * 047 */ 048 public enum JavadocToolVersionRange { 049 JDK7_OR_LOWER(null, JavaVersion.parse("1.8")), 050 JDK8_OR_9(JavaVersion.parse("1.8"), JavaVersion.parse("10")), 051 JDK10_OR_HIGHER(JavaVersion.parse("10"), null); 052 053 // upper bound is exclusive, lower bound inclusive (null means unlimited) 054 private final JavaVersion lowerBound; 055 private final JavaVersion upperBound; 056 057 JavadocToolVersionRange(JavaVersion lowerBound, JavaVersion upperBound) { 058 this.lowerBound = lowerBound; 059 this.upperBound = upperBound; 060 } 061 062 static JavadocToolVersionRange findMatch(JavaVersion javadocVersion) { 063 for (JavadocToolVersionRange range : values()) { 064 if ((range.lowerBound == null || javadocVersion.isAtLeast(range.lowerBound)) 065 && (range.upperBound == null || javadocVersion.isBefore(range.upperBound))) { 066 return range; 067 } 068 } 069 throw new IllegalArgumentException("Found no matching javadoc tool version range for " + javadocVersion); 070 } 071 } 072 073 private static final Logger LOG = LoggerFactory.getLogger(JavadocLinkGenerator.class); 074 private final List<JavadocSite> externalJavadocSites; 075 private final JavadocSite internalJavadocSite; // may be null 076 077 /** 078 * Constructor for an offline internal site only. 079 * 080 * @param internalJavadocSiteUrl the url of the javadoc generated website 081 * @param internalJavadocVersion the version of javadoc with which the internal site from 082 * {@code internalJavadocSiteUrl} has been generated 083 */ 084 public JavadocLinkGenerator(URI internalJavadocSiteUrl, String internalJavadocVersion) { 085 this(internalJavadocSiteUrl, internalJavadocVersion, Collections.emptyList(), null); 086 } 087 088 /** 089 * Constructor for online external sites only. 090 * 091 * @param externalJavadocSiteUrls 092 * @param settings 093 */ 094 public JavadocLinkGenerator(List<URI> externalJavadocSiteUrls, Settings settings) { 095 this(null, null, externalJavadocSiteUrls, settings); 096 } 097 098 /** 099 * Constructor for both an internal (offline) and external (online) sites. 100 * 101 * @param internalJavadocSiteUrl 102 * @param internalJavadocVersion 103 * @param externalJavadocSiteUrls 104 * @param settings 105 */ 106 public JavadocLinkGenerator( 107 URI internalJavadocSiteUrl, 108 String internalJavadocVersion, 109 List<URI> externalJavadocSiteUrls, 110 Settings settings) { 111 if (internalJavadocSiteUrl != null) { 112 // resolve version 113 JavaVersion javadocVersion = JavaVersion.parse(internalJavadocVersion); 114 internalJavadocSite = 115 new JavadocSite(internalJavadocSiteUrl, JavadocToolVersionRange.findMatch(javadocVersion), false); 116 } else { 117 internalJavadocSite = null; 118 } 119 if (externalJavadocSiteUrls != null) { 120 externalJavadocSites = new ArrayList<>(externalJavadocSiteUrls.size()); 121 for (URI siteUrl : externalJavadocSiteUrls) { 122 try { 123 externalJavadocSites.add(new JavadocSite(siteUrl, settings)); 124 } catch (IOException e) { 125 LOG.warn("Could not use {} as base URL: {}", siteUrl, e.getMessage(), e); 126 } 127 } 128 } else { 129 externalJavadocSites = Collections.emptyList(); 130 } 131 if (internalJavadocSite == null && externalJavadocSites.isEmpty()) { 132 throw new IllegalArgumentException( 133 "Either internal or at least one accessible external javadoc " + "URLs must be given!"); 134 } 135 } 136 137 /** 138 * Generates a (deep-)link to a HTML page in any of the sites given to the constructor. 139 * The link is not validated (i.e. might point to a non-existing page). 140 * Only uses the offline site for references returning {@code false} for 141 * {@link FullyQualifiedJavadocReference#isExternal()}. 142 * @param javadocReference 143 * @return the (deep-) link towards a javadoc page 144 * @throws IllegalArgumentException in case no javadoc link could be generated for the given reference 145 * @throws IllegalStateException in case no javadoc source sites have been configured 146 */ 147 public URI createLink(FullyQualifiedJavadocReference javadocReference) { 148 if (!javadocReference.isExternal() && internalJavadocSite != null) { 149 return internalJavadocSite.createLink(javadocReference); 150 } else { 151 JavadocSite javadocSite = externalJavadocSites.stream() 152 .filter(base -> 153 base.hasEntryFor(javadocReference.getModuleName(), javadocReference.getPackageName())) 154 .findFirst() 155 .orElseThrow(() -> new IllegalArgumentException("Found no javadoc site for " + javadocReference)); 156 return javadocSite.createLink(javadocReference); 157 } 158 } 159 160 /** 161 * Generates a (deep-)link to a HTML page in any of the sites given to the constructor. 162 * The link is not validated (i.e. might point to a non-existing page). 163 * Preferably resolves from the online sites if they provide the given package. 164 * @param binaryName a binary name according to 165 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.1">JLS 13.1</a> 166 * @return the (deep-) link towards a javadoc page 167 * @throws IllegalArgumentException in case no javadoc link could be generated for the given name 168 */ 169 public URI createLink(String binaryName) { 170 Map.Entry<String, String> packageAndClassName = JavadocSite.getPackageAndClassName(binaryName); 171 // first check external links, otherwise assume internal link 172 JavadocSite javadocSite = externalJavadocSites.stream() 173 .filter(base -> base.hasEntryFor(Optional.empty(), Optional.of(packageAndClassName.getKey()))) 174 .findFirst() 175 .orElse(null); 176 if (javadocSite == null) { 177 if (internalJavadocSite != null) { 178 javadocSite = internalJavadocSite; 179 } else { 180 throw new IllegalArgumentException("Found no javadoc site for " + binaryName); 181 } 182 } 183 return javadocSite.createLink(packageAndClassName.getKey(), packageAndClassName.getValue()); 184 } 185 186 public URI getInternalJavadocSiteBaseUrl() { 187 if (internalJavadocSite == null) { 188 throw new IllegalStateException("Could not get docroot of internal javadoc as it hasn't been set"); 189 } 190 return internalJavadocSite.getBaseUri(); 191 } 192 193 /** 194 * Checks if a given link is valid. For absolute links uses the underling {@link java.net.HttpURLConnection}, 195 * otherwise checks for existence of the file on the filesystem. 196 * 197 * @param url the url to check 198 * @param baseDirectory the base directory to which relative file URLs refer 199 * @return {@code true} in case the given link is valid otherwise {@code false} 200 */ 201 public static boolean isLinkValid(URI url, Path baseDirectory) { 202 if (url.isAbsolute()) { 203 try (BufferedReader reader = JavadocSite.getReader(url.toURL(), null)) { 204 if (url.getFragment() != null) { 205 Pattern pattern = JavadocSite.getAnchorPattern(url.getFragment()); 206 if (reader.lines().noneMatch(pattern.asPredicate())) { 207 return false; 208 } 209 } 210 } catch (IOException e) { 211 return false; 212 } 213 return true; 214 } else { 215 Path file = baseDirectory.resolve(url.getPath()); 216 boolean exists = Files.exists(file); 217 if (!exists) { 218 LOG.debug("Could not find file given through '{}' in resolved path '{}'", url, file); 219 } 220 return exists; 221 } 222 } 223}