View Javadoc
1   package org.apache.maven.tools.plugin.javadoc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedReader;
23  
24  import java.io.IOException;
25  import java.net.URI;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Optional;
33  import java.util.regex.Pattern;
34  
35  import org.apache.maven.settings.Settings;
36  import org.codehaus.plexus.languages.java.version.JavaVersion;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * Generates links for elements (packages, classes, fields, constructors, methods) in external
42   * and/or an internal (potentially not yet existing) javadoc site.
43   * The external site must be accessible for it to be considered due to the different fragment formats.
44   */
45  public class JavadocLinkGenerator
46  {
47      /**
48       * Javadoc tool version ranges whose generated sites expose different link formats.
49       *
50       */
51      public enum JavadocToolVersionRange
52      {
53          JDK7_OR_LOWER( null, JavaVersion.parse( "1.8" ) ),
54          JDK8_OR_9( JavaVersion.parse( "1.8" ), JavaVersion.parse( "10" ) ),
55          JDK10_OR_HIGHER( JavaVersion.parse( "10" ), null );
56          
57          // upper bound is exclusive, lower bound inclusive (null means unlimited)
58          private final JavaVersion lowerBound;
59          private final JavaVersion upperBound;
60          JavadocToolVersionRange( JavaVersion lowerBound, JavaVersion upperBound )
61          {
62              this.lowerBound = lowerBound;
63              this.upperBound = upperBound;
64          }
65  
66          static JavadocToolVersionRange findMatch( JavaVersion javadocVersion )
67          {
68              for ( JavadocToolVersionRange range : values() )
69              {
70                  if ( ( range.lowerBound == null || javadocVersion.isAtLeast( range.lowerBound ) )
71                       && ( range.upperBound == null || javadocVersion.isBefore( range.upperBound ) ) )
72                  {
73                      return range;
74                  }
75              }
76              throw new IllegalArgumentException( "Found no matching javadoc tool version range for " + javadocVersion );
77          }
78      }
79  
80      private static final Logger LOG = LoggerFactory.getLogger( JavadocLinkGenerator.class );
81      private final List<JavadocSite> externalJavadocSites;
82      private final JavadocSite internalJavadocSite; // may be null
83  
84      /**
85       * Constructor for an offline internal site only.
86       * 
87       * @param internalJavadocSiteUrl the url of the javadoc generated website
88       * @param internalJavadocVersion the version of javadoc with which the internal site from
89       *                               {@code internalJavadocSiteUrl} has been generated
90       */
91      public JavadocLinkGenerator( URI internalJavadocSiteUrl,
92                                   String internalJavadocVersion )
93      {
94          this( internalJavadocSiteUrl, internalJavadocVersion, Collections.emptyList(), null );
95      }
96  
97      /**
98       * Constructor for online external sites only.
99       * 
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 }