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