View Javadoc

1   package org.apache.maven.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.ByteArrayOutputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.UnsupportedEncodingException;
30  import java.lang.reflect.Modifier;
31  import java.net.Authenticator;
32  import java.net.PasswordAuthentication;
33  import java.net.URL;
34  import java.net.URLClassLoader;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Locale;
40  import java.util.Properties;
41  import java.util.Set;
42  import java.util.StringTokenizer;
43  import java.util.jar.JarEntry;
44  import java.util.jar.JarInputStream;
45  import java.util.regex.Matcher;
46  import java.util.regex.Pattern;
47  import java.util.regex.PatternSyntaxException;
48  
49  import org.apache.maven.artifact.Artifact;
50  import org.apache.maven.project.MavenProject;
51  import org.apache.maven.settings.Proxy;
52  import org.apache.maven.settings.Settings;
53  import org.codehaus.plexus.util.FileUtils;
54  import org.codehaus.plexus.util.IOUtil;
55  import org.codehaus.plexus.util.StringUtils;
56  import org.codehaus.plexus.util.cli.CommandLineException;
57  import org.codehaus.plexus.util.cli.CommandLineUtils;
58  import org.codehaus.plexus.util.cli.Commandline;
59  
60  /**
61   * Set of utilities methods for Javadoc.
62   *
63   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
64   * @version $Id: JavadocUtil.html 829389 2012-08-19 17:23:07Z hboutemy $
65   * @since 2.4
66   */
67  public class JavadocUtil
68  {
69      /**
70       * Method that removes the invalid directories in the specified directories.
71       * <b>Note</b>: All elements in <code>dirs</code> could be an absolute or relative against the project's base
72       * directory <code>String</code> path.
73       *
74       * @param project the current Maven project not null
75       * @param dirs the list of <code>String</code> directories path that will be validated.
76       * @return a List of valid <code>String</code> directories absolute paths.
77       */
78      protected static List pruneDirs( MavenProject project, List dirs )
79      {
80          List pruned = new ArrayList( dirs.size() );
81          for ( Iterator i = dirs.iterator(); i.hasNext(); )
82          {
83              String dir = (String) i.next();
84  
85              if ( dir == null )
86              {
87                  continue;
88              }
89  
90              File directory = new File( dir );
91              if ( !directory.isAbsolute() )
92              {
93                  directory = new File( project.getBasedir(), directory.getPath() );
94              }
95  
96              if ( directory.isDirectory() && !pruned.contains( dir ) )
97              {
98                  pruned.add( directory.getAbsolutePath() );
99              }
100         }
101 
102         return pruned;
103     }
104 
105     /**
106      * Method that removes the invalid files in the specified files.
107      * <b>Note</b>: All elements in <code>files</code> should be an absolute <code>String</code> path.
108      *
109      * @param files the list of <code>String</code> files paths that will be validated.
110      * @return a List of valid <code>File</code> objects.
111      */
112     protected static List pruneFiles( List files )
113     {
114         List pruned = new ArrayList( files.size() );
115         for ( Iterator i = files.iterator(); i.hasNext(); )
116         {
117             String f = (String) i.next();
118 
119             if ( f == null )
120             {
121                 continue;
122             }
123 
124             File file = new File( f );
125             if ( file.isFile() && !pruned.contains( f ) )
126             {
127                 pruned.add( f );
128             }
129         }
130 
131         return pruned;
132     }
133 
134     /**
135      * Method that gets all the source files to be excluded from the javadoc on the given
136      * source paths.
137      *
138      * @param sourcePaths      the path to the source files
139      * @param subpackagesList  list of subpackages to be included in the javadoc
140      * @param excludedPackages the package names to be excluded in the javadoc
141      * @return a List of the source files to be excluded in the generated javadoc
142      */
143     protected static List getExcludedNames( List sourcePaths, String[] subpackagesList, String[] excludedPackages )
144     {
145         List excludedNames = new ArrayList();
146         for ( Iterator i = sourcePaths.iterator(); i.hasNext(); )
147         {
148             String path = (String) i.next();
149             for ( int j = 0; j < subpackagesList.length; j++ )
150             {
151                 List excludes = getExcludedPackages( path, excludedPackages );
152                 excludedNames.addAll( excludes );
153             }
154         }
155 
156         return excludedNames;
157     }
158 
159     /**
160      * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()}
161      * @param artifacts not null
162      * @return list of compile artifacts with compile scope
163      * @deprecated since 2.5, using {@link #getCompileArtifacts(Set, boolean)} instead of.
164      */
165     protected static List getCompileArtifacts( Set artifacts )
166     {
167         return getCompileArtifacts( artifacts, false );
168     }
169 
170     /**
171      * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()}
172      * @param artifacts not null
173      * @param withTestScope flag to include or not the artifacts with test scope
174      * @return list of compile artifacts with or without test scope.
175      */
176     protected static List getCompileArtifacts( Set artifacts, boolean withTestScope )
177     {
178         List list = new ArrayList( artifacts.size() );
179 
180         for ( Iterator i = artifacts.iterator(); i.hasNext(); )
181         {
182             Artifact a = (Artifact) i.next();
183 
184             // TODO: classpath check doesn't belong here - that's the other method
185             if ( a.getArtifactHandler().isAddedToClasspath() )
186             {
187                 // TODO: let the scope handler deal with this
188                 if ( withTestScope )
189                 {
190                     if ( Artifact.SCOPE_COMPILE.equals( a.getScope() )
191                         || Artifact.SCOPE_PROVIDED.equals( a.getScope() )
192                         || Artifact.SCOPE_SYSTEM.equals( a.getScope() )
193                         || Artifact.SCOPE_TEST.equals( a.getScope() ) )
194                     {
195                         list.add( a );
196                     }
197                 }
198                 else
199                 {
200                     if ( Artifact.SCOPE_COMPILE.equals( a.getScope() ) || Artifact.SCOPE_PROVIDED.equals( a.getScope() )
201                         || Artifact.SCOPE_SYSTEM.equals( a.getScope() ) )
202                     {
203                         list.add( a );
204                     }
205                 }
206             }
207         }
208 
209         return list;
210     }
211 
212     /**
213      * Convenience method to wrap an argument value in single quotes (i.e. <code>'</code>). Intended for values
214      * which may contain whitespaces.
215      * <br/>
216      * To prevent javadoc error, the line separator (i.e. <code>\n</code>) are skipped.
217      *
218      * @param value the argument value.
219      * @return argument with quote
220      */
221     protected static String quotedArgument( String value )
222     {
223         String arg = value;
224 
225         if ( StringUtils.isNotEmpty( arg ) )
226         {
227             if ( arg.indexOf( "'" ) != -1 )
228             {
229                 arg = StringUtils.replace( arg, "'", "\\'" );
230             }
231             arg = "'" + arg + "'";
232 
233             // To prevent javadoc error
234             arg = StringUtils.replace( arg, "\n", " " );
235         }
236 
237         return arg;
238     }
239 
240     /**
241      * Convenience method to format a path argument so that it is properly interpreted by the javadoc tool. Intended
242      * for path values which may contain whitespaces.
243      *
244      * @param value the argument value.
245      * @return path argument with quote
246      */
247     protected static String quotedPathArgument( String value )
248     {
249         String path = value;
250 
251         if ( StringUtils.isNotEmpty( path ) )
252         {
253             path = path.replace( '\\', '/' );
254             if ( path.indexOf( "\'" ) != -1 )
255             {
256                 String split[] = path.split( "\'" );
257                 path = "";
258 
259                 for ( int i = 0; i < split.length; i++ )
260                 {
261                     if ( i != split.length - 1 )
262                     {
263                         path = path + split[i] + "\\'";
264                     }
265                     else
266                     {
267                         path = path + split[i];
268                     }
269                 }
270             }
271             path = "'" + path + "'";
272         }
273 
274         return path;
275     }
276 
277     /**
278      * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code>
279      * to the <code>outputDirectory</code>.
280      *
281      * @param outputDirectory the output directory
282      * @param javadocDir the javadoc directory
283      * @throws IOException if any
284      * @deprecated since 2.5, using {@link #copyJavadocResources(File, File, String)} instead of.
285      */
286     protected static void copyJavadocResources( File outputDirectory, File javadocDir )
287         throws IOException
288     {
289         copyJavadocResources( outputDirectory, javadocDir, null );
290     }
291 
292     /**
293      * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code>
294      * to the <code>outputDirectory</code>.
295      *
296      * @param outputDirectory the output directory
297      * @param javadocDir the javadoc directory
298      * @param excludedocfilessubdir the excludedocfilessubdir parameter
299      * @throws IOException if any
300      * @since 2.5
301      */
302     protected static void copyJavadocResources( File outputDirectory, File javadocDir, String excludedocfilessubdir )
303         throws IOException
304     {
305         List excludes = new ArrayList();
306         excludes.addAll( Arrays.asList( FileUtils.getDefaultExcludes() ) );
307 
308         if ( StringUtils.isNotEmpty( excludedocfilessubdir ) )
309         {
310             StringTokenizer st = new StringTokenizer( excludedocfilessubdir, ":" );
311             String current;
312             while ( st.hasMoreTokens() )
313             {
314                 current = st.nextToken();
315                 excludes.add( "**/" + current + "/*" );
316             }
317         }
318 
319         if ( javadocDir.exists() && javadocDir.isDirectory() )
320         {
321             List docFiles = FileUtils.getDirectoryNames( javadocDir, "**/doc-files", StringUtils.join( excludes
322                 .iterator(), "," ), false, true );
323             for ( Iterator it = docFiles.iterator(); it.hasNext(); )
324             {
325                 String docFile = (String) it.next();
326 
327                 File docFileOutput = new File( outputDirectory, docFile );
328                 FileUtils.mkdir( docFileOutput.getAbsolutePath() );
329                 FileUtils.copyDirectory( new File( javadocDir, docFile ), docFileOutput );
330             }
331         }
332     }
333 
334     /**
335      * Method that gets the files or classes that would be included in the javadocs using the subpackages
336      * parameter.
337      *
338      * @param sourceDirectory the directory where the source files are located
339      * @param fileList        the list of all files found in the sourceDirectory
340      * @param excludePackages package names to be excluded in the javadoc
341      * @return a StringBuffer that contains the appended file names of the files to be included in the javadoc
342      */
343     protected static List getIncludedFiles( File sourceDirectory, String[] fileList, String[] excludePackages )
344     {
345         List files = new ArrayList();
346 
347         for ( int j = 0; j < fileList.length; j++ )
348         {
349             boolean include = true;
350             for ( int k = 0; k < excludePackages.length && include; k++ )
351             {
352                 // handle wildcards (*) in the excludePackageNames
353                 String[] excludeName = excludePackages[k].split( "[*]" );
354 
355                 if ( excludeName.length > 1 )
356                 {
357                     int u = 0;
358                     while ( include && u < excludeName.length )
359                     {
360                         if ( !"".equals( excludeName[u].trim() ) && fileList[j].indexOf( excludeName[u] ) != -1 )
361                         {
362                             include = false;
363                         }
364                         u++;
365                     }
366                 }
367                 else
368                 {
369                     if ( fileList[j].startsWith( sourceDirectory.toString() + File.separatorChar + excludeName[0] ) )
370                     {
371                         if ( excludeName[0].endsWith( String.valueOf( File.separatorChar ) ) )
372                         {
373                             int i = fileList[j].lastIndexOf( File.separatorChar );
374                             String packageName = fileList[j].substring( 0, i + 1 );
375                             File currentPackage = new File( packageName );
376                             File excludedPackage = new File( sourceDirectory, excludeName[0] );
377                             if ( currentPackage.equals( excludedPackage )
378                                 && fileList[j].substring( i ).indexOf( ".java" ) != -1 )
379                             {
380                                 include = true;
381                             }
382                             else
383                             {
384                                 include = false;
385                             }
386                         }
387                         else
388                         {
389                             include = false;
390                         }
391                     }
392                 }
393             }
394 
395             if ( include )
396             {
397                 files.add( quotedPathArgument( fileList[j] ) );
398             }
399         }
400 
401         return files;
402     }
403 
404     /**
405      * Method that gets the complete package names (including subpackages) of the packages that were defined
406      * in the excludePackageNames parameter.
407      *
408      * @param sourceDirectory     the directory where the source files are located
409      * @param excludePackagenames package names to be excluded in the javadoc
410      * @return a List of the packagenames to be excluded
411      */
412     protected static List getExcludedPackages( String sourceDirectory, String[] excludePackagenames )
413     {
414         List files = new ArrayList();
415         for ( int i = 0; i < excludePackagenames.length; i++ )
416         {
417             String[] fileList = FileUtils.getFilesFromExtension( sourceDirectory, new String[] { "java" } );
418             for ( int j = 0; j < fileList.length; j++ )
419             {
420                 String[] excludeName = excludePackagenames[i].split( "[*]" );
421                 int u = 0;
422                 while ( u < excludeName.length )
423                 {
424                     if ( !"".equals( excludeName[u].trim() ) && fileList[j].indexOf( excludeName[u] ) != -1
425                         && sourceDirectory.indexOf( excludeName[u] ) == -1 )
426                     {
427                         files.add( fileList[j] );
428                     }
429                     u++;
430                 }
431             }
432         }
433 
434         List excluded = new ArrayList();
435         for ( Iterator it = files.iterator(); it.hasNext(); )
436         {
437             String file = (String) it.next();
438             int idx = file.lastIndexOf( File.separatorChar );
439             String tmpStr = file.substring( 0, idx );
440             tmpStr = tmpStr.replace( '\\', '/' );
441             String[] srcSplit = tmpStr.split( sourceDirectory.replace( '\\', '/' ) + '/' );
442             String excludedPackage = srcSplit[1].replace( '/', '.' );
443 
444             if ( !excluded.contains( excludedPackage ) )
445             {
446                 excluded.add( excludedPackage );
447             }
448         }
449 
450         return excluded;
451     }
452 
453     /**
454      * Convenience method that gets the files to be included in the javadoc.
455      *
456      * @param sourceDirectory the directory where the source files are located
457      * @param files the variable that contains the appended filenames of the files to be included in the javadoc
458      * @param excludePackages the packages to be excluded in the javadocs
459      */
460     protected static void addFilesFromSource( List files, File sourceDirectory, String[] excludePackages )
461     {
462         String[] fileList = FileUtils.getFilesFromExtension( sourceDirectory.getPath(), new String[] { "java" } );
463         if ( fileList != null && fileList.length != 0 )
464         {
465             List tmpFiles = getIncludedFiles( sourceDirectory, fileList, excludePackages );
466             files.addAll( tmpFiles );
467         }
468     }
469 
470     /**
471      * Call the Javadoc tool and parse its output to find its version, i.e.:
472      * <pre>
473      * javadoc.exe(or .sh) -J-version
474      * </pre>
475      *
476      * @param javadocExe not null file
477      * @return the javadoc version as float
478      * @throws IOException if javadocExe is null, doesn't exist or is not a file
479      * @throws CommandLineException if any
480      * @throws IllegalArgumentException if no output was found in the command line
481      * @throws PatternSyntaxException if the output contains a syntax error in the regular-expression pattern.
482      * @see #parseJavadocVersion(String)
483      */
484     protected static float getJavadocVersion( File javadocExe )
485         throws IOException, CommandLineException, IllegalArgumentException, PatternSyntaxException
486     {
487         if ( ( javadocExe == null ) || ( !javadocExe.exists() ) || ( !javadocExe.isFile() ) )
488         {
489             throw new IOException( "The javadoc executable '" + javadocExe + "' doesn't exist or is not a file. " );
490         }
491 
492         Commandline cmd = new Commandline();
493         cmd.setExecutable( javadocExe.getAbsolutePath() );
494         cmd.setWorkingDirectory( javadocExe.getParentFile() );
495         cmd.createArg().setValue( "-J-version" );
496 
497         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
498         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
499 
500         int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
501 
502         if ( exitCode != 0 )
503         {
504             StringBuffer msg = new StringBuffer( "Exit code: " + exitCode + " - " + err.getOutput() );
505             msg.append( '\n' );
506             msg.append( "Command line was:" + CommandLineUtils.toString( cmd.getCommandline() ) );
507             throw new CommandLineException( msg.toString() );
508         }
509 
510         if ( StringUtils.isNotEmpty( err.getOutput() ) )
511         {
512             return parseJavadocVersion( err.getOutput() );
513         }
514         else if ( StringUtils.isNotEmpty( out.getOutput() ) )
515         {
516             return parseJavadocVersion( out.getOutput() );
517         }
518 
519         throw new IllegalArgumentException( "No output found from the command line 'javadoc -J-version'" );
520     }
521 
522     /**
523      * Parse the output for 'javadoc -J-version' and return the javadoc version recognized.
524      * <br/>
525      * Here are some output for 'javadoc -J-version' depending the JDK used:
526      * <table>
527      * <tr>
528      *   <th>JDK</th>
529      *   <th>Output for 'javadoc -J-version'</th>
530      * </tr>
531      * <tr>
532      *   <td>Sun 1.4</td>
533      *   <td>java full version "1.4.2_12-b03"</td>
534      * </tr>
535      * <tr>
536      *   <td>Sun 1.5</td>
537      *   <td>java full version "1.5.0_07-164"</td>
538      * </tr>
539      * <tr>
540      *   <td>IBM 1.4</td>
541      *   <td>javadoc full version "J2RE 1.4.2 IBM Windows 32 build cn1420-20040626"</td>
542      * </tr>
543      * <tr>
544      *   <td>IBM 1.5 (French JVM)</td>
545      *   <td>javadoc version complète de "J2RE 1.5.0 IBM Windows 32 build pwi32pdev-20070426a"</td>
546      * </tr>
547      * <tr>
548      *   <td>FreeBSD 1.5</td>
549      *   <td>java full version "diablo-1.5.0-b01"</td>
550      * </tr>
551      * <tr>
552      *   <td>BEA jrockit 1.5</td>
553      *   <td>java full version "1.5.0_11-b03"</td>
554      * </tr>
555      * </table>
556      *
557      * @param output for 'javadoc -J-version'
558      * @return the version of the javadoc for the output.
559      * @throws PatternSyntaxException if the output doesn't match with the output pattern
560      * <tt>(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*</tt>.
561      * @throws IllegalArgumentException if the output is null
562      */
563     protected static float parseJavadocVersion( String output )
564         throws IllegalArgumentException, PatternSyntaxException
565     {
566         if ( StringUtils.isEmpty( output ) )
567         {
568             throw new IllegalArgumentException( "The output could not be null." );
569         }
570 
571         Pattern pattern = Pattern.compile( "(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*" );
572 
573         Matcher matcher = pattern.matcher( output );
574         if ( !matcher.matches() )
575         {
576             throw new PatternSyntaxException( "Unrecognized version of Javadoc: '" + output + "'", pattern.pattern(),
577                                               pattern.toString().length() - 1 );
578         }
579 
580         String version = matcher.group( 3 );
581         if ( version == null )
582         {
583             version = matcher.group( 1 );
584         }
585         else
586         {
587             version = matcher.group( 1 ) + version;
588         }
589 
590         return Float.parseFloat( version );
591     }
592 
593     /**
594      * Parse a memory string which be used in the JVM arguments <code>-Xms</code> or <code>-Xmx</code>.
595      * <br/>
596      * Here are some supported memory string depending the JDK used:
597      * <table>
598      * <tr>
599      *   <th>JDK</th>
600      *   <th>Memory argument support for <code>-Xms</code> or <code>-Xmx</code></th>
601      * </tr>
602      * <tr>
603      *   <td>SUN</td>
604      *   <td>1024k | 128m | 1g | 1t</td>
605      * </tr>
606      * <tr>
607      *   <td>IBM</td>
608      *   <td>1024k | 1024b | 128m | 128mb | 1g | 1gb</td>
609      * </tr>
610      * <tr>
611      *   <td>BEA</td>
612      *   <td>1024k | 1024kb | 128m | 128mb | 1g | 1gb</td>
613      * </tr>
614      * </table>
615      *
616      * @param memory the memory to be parsed, not null.
617      * @return the memory parsed with a supported unit. If no unit specified in the <code>memory</code> parameter,
618      * the default unit is <code>m</code>. The units <code>g | gb</code> or <code>t | tb</code> will be converted
619      * in <code>m</code>.
620      * @throws IllegalArgumentException if the <code>memory</code> parameter is null or doesn't match any pattern.
621      */
622     protected static String parseJavadocMemory( String memory )
623         throws IllegalArgumentException
624     {
625         if ( StringUtils.isEmpty( memory ) )
626         {
627             throw new IllegalArgumentException( "The memory could not be null." );
628         }
629 
630         Pattern p = Pattern.compile( "^\\s*(\\d+)\\s*?\\s*$" );
631         Matcher m = p.matcher( memory );
632         if ( m.matches() )
633         {
634             return m.group( 1 ) + "m";
635         }
636 
637         p = Pattern.compile( "^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE );
638         m = p.matcher( memory );
639         if ( m.matches() )
640         {
641             return m.group( 1 ) + "k";
642         }
643 
644         p = Pattern.compile( "^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE );
645         m = p.matcher( memory );
646         if ( m.matches() )
647         {
648             return m.group( 1 ) + "m";
649         }
650 
651         p = Pattern.compile( "^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE );
652         m = p.matcher( memory );
653         if ( m.matches() )
654         {
655             return ( Integer.parseInt( m.group( 1 ) ) * 1024 ) + "m";
656         }
657 
658         p = Pattern.compile( "^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE );
659         m = p.matcher( memory );
660         if ( m.matches() )
661         {
662             return ( Integer.parseInt( m.group( 1 ) ) * 1024 * 1024 ) + "m";
663         }
664 
665         throw new IllegalArgumentException( "Could convert not to a memory size: " + memory );
666     }
667 
668     /**
669      * Fetch an URL
670      *
671      * @param settings the user settings used to fetch the url with an active proxy, if defined.
672      * @param url the url to fetch
673      * @throws IOException if any
674      */
675     protected static void fetchURL( Settings settings, URL url )
676         throws IOException
677     {
678         if ( url == null )
679         {
680             throw new IOException( "The url is null" );
681         }
682 
683         Properties oldSystemProperties = new Properties();
684         oldSystemProperties.putAll( System.getProperties() );
685 
686         if ( settings != null )
687         {
688             String scheme = url.getProtocol();
689 
690             if ( !"file".equals( scheme ) )
691             {
692                 Proxy activeProxy = settings.getActiveProxy();
693                 if ( activeProxy != null )
694                 {
695                     if ( "http".equals( scheme ) || "https".equals( scheme ) || "ftp".equals( scheme ) )
696                     {
697                         scheme += ".";
698                     }
699                     else
700                     {
701                         scheme = "";
702                     }
703 
704                     if ( StringUtils.isNotEmpty( activeProxy.getHost() ) )
705                     {
706                         Properties systemProperties = System.getProperties();
707                         systemProperties.setProperty( scheme + "proxySet", "true" );
708                         systemProperties.setProperty( scheme + "proxyHost", activeProxy.getHost() );
709 
710                         if ( activeProxy.getPort() > 0 )
711                         {
712                             systemProperties
713                                 .setProperty( scheme + "proxyPort", String.valueOf( activeProxy.getPort() ) );
714                         }
715 
716                         if ( StringUtils.isNotEmpty( activeProxy.getNonProxyHosts() ) )
717                         {
718                             systemProperties.setProperty( scheme + "nonProxyHosts", activeProxy.getNonProxyHosts() );
719                         }
720 
721                         final String userName = activeProxy.getUsername();
722                         if ( StringUtils.isNotEmpty( userName ) )
723                         {
724                             final String pwd = StringUtils.isEmpty( activeProxy.getPassword() ) ? "" : activeProxy
725                                 .getPassword();
726                             Authenticator.setDefault( new Authenticator()
727                             {
728                                 protected PasswordAuthentication getPasswordAuthentication()
729                                 {
730                                     return new PasswordAuthentication( userName, pwd.toCharArray() );
731                                 }
732                             } );
733                         }
734                     }
735                 }
736             }
737         }
738 
739         InputStream in = null;
740         try
741         {
742             in = url.openStream();
743         }
744         finally
745         {
746             IOUtil.close( in );
747 
748             // Reset system properties
749             if ( ( settings != null ) && ( !"file".equals( url.getProtocol() ) )
750                 && ( settings.getActiveProxy() != null )
751                 && ( StringUtils.isNotEmpty( settings.getActiveProxy().getHost() ) ) )
752             {
753                 System.setProperties( oldSystemProperties );
754                 Authenticator.setDefault( null );
755             }
756         }
757     }
758 
759     /**
760      * Validate if a charset is supported on this platform.
761      *
762      * @param charsetName the charsetName to be check.
763      * @return <code>true</code> if the charset is supported by the JVM, <code>false</code> otherwise.
764      */
765     protected static boolean validateEncoding( String charsetName )
766     {
767         if ( StringUtils.isEmpty( charsetName ) )
768         {
769             return false;
770         }
771 
772         OutputStream ost = new ByteArrayOutputStream();
773         OutputStreamWriter osw = null;
774         try
775         {
776             osw = new OutputStreamWriter( ost, charsetName );
777         }
778         catch ( UnsupportedEncodingException exc )
779         {
780             return false;
781         }
782         finally
783         {
784             IOUtil.close( osw );
785         }
786         return true;
787     }
788 
789     /**
790      * For security reasons, if an active proxy is defined and needs an authentication by
791      * username/password, hide the proxy password in the command line.
792      *
793      * @param cmdLine a command line, not null
794      * @param settings the user settings
795      * @return the cmdline with '*' for the http.proxyPassword JVM property
796      */
797     protected static String hideProxyPassword( String cmdLine, Settings settings )
798     {
799         if ( cmdLine == null )
800         {
801             throw new IllegalArgumentException( "cmdLine could not be null" );
802         }
803 
804         if ( settings == null )
805         {
806             return cmdLine;
807         }
808 
809         Proxy activeProxy = settings.getActiveProxy();
810         if ( activeProxy != null && StringUtils.isNotEmpty( activeProxy.getHost() )
811             && StringUtils.isNotEmpty( activeProxy.getUsername() )
812             && StringUtils.isNotEmpty( activeProxy.getPassword() ) )
813         {
814             String pass = "-J-Dhttp.proxyPassword=\"" + activeProxy.getPassword() + "\"";
815             String hidepass =
816                 "-J-Dhttp.proxyPassword=\"" + StringUtils.repeat( "*", activeProxy.getPassword().length() ) + "\"";
817 
818             return StringUtils.replace( cmdLine, pass, hidepass );
819         }
820 
821         return cmdLine;
822     }
823 
824     /**
825      * Auto-detect the class names of the implementation of <code>com.sun.tools.doclets.Taglet</code> class from a
826      * given jar file.
827      * <br/>
828      * <b>Note</b>: <code>JAVA_HOME/lib/tools.jar</code> is a requirement to find
829      * <code>com.sun.tools.doclets.Taglet</code> class.
830      *
831      * @param jarFile not null
832      * @return the list of <code>com.sun.tools.doclets.Taglet</code> class names from a given jarFile.
833      * @throws IOException if jarFile is invalid or not found, or if the <code>JAVA_HOME/lib/tools.jar</code>
834      * is not found.
835      * @throws ClassNotFoundException if any
836      * @throws NoClassDefFoundError if any
837      */
838     protected static List getTagletClassNames( File jarFile )
839         throws IOException, ClassNotFoundException, NoClassDefFoundError
840     {
841         List classes = getClassNamesFromJar( jarFile );
842         ClassLoader cl;
843 
844         // Needed to find com.sun.tools.doclets.Taglet class
845         File tools = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
846         if ( tools.exists() && tools.isFile() )
847         {
848             cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL(), tools.toURI().toURL() }, null );
849         }
850         else
851         {
852             cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL() }, null );
853         }
854 
855         List tagletClasses = new ArrayList();
856 
857         Class tagletClass = cl.loadClass( "com.sun.tools.doclets.Taglet" );
858         for ( Iterator it = classes.iterator(); it.hasNext(); )
859         {
860             String s = (String) it.next();
861 
862             Class c = cl.loadClass( s );
863 
864             if ( tagletClass.isAssignableFrom( c ) && !Modifier.isAbstract( c.getModifiers() ) )
865             {
866                 tagletClasses.add( c.getName() );
867             }
868         }
869 
870         return tagletClasses;
871     }
872 
873     // ----------------------------------------------------------------------
874     // private methods
875     // ----------------------------------------------------------------------
876 
877     /**
878      * @param jarFile not null
879      * @return all class names from the given jar file.
880      * @throws IOException if any or if the jarFile is null or doesn't exist.
881      */
882     private static List getClassNamesFromJar( File jarFile )
883         throws IOException
884     {
885         if ( jarFile == null || !jarFile.exists() || !jarFile.isFile() )
886         {
887             throw new IOException( "The jar '" + jarFile + "' doesn't exist or is not a file." );
888         }
889 
890         List classes = new ArrayList();
891         JarInputStream jarStream = null;
892 
893         try
894         {
895             jarStream = new JarInputStream( new FileInputStream( jarFile ) );
896             JarEntry jarEntry = jarStream.getNextJarEntry();
897             while ( jarEntry != null )
898             {
899                 if ( jarEntry == null )
900                 {
901                     break;
902                 }
903 
904                 if ( jarEntry.getName().toLowerCase( Locale.ENGLISH ).endsWith( ".class" ) )
905                 {
906                     String name = jarEntry.getName().substring( 0, jarEntry.getName().indexOf( "." ) );
907 
908                     classes.add( name.replaceAll( "/", "\\." ) );
909                 }
910 
911                 jarStream.closeEntry();
912                 jarEntry = jarStream.getNextJarEntry();
913             }
914         }
915         finally
916         {
917             IOUtil.close( jarStream );
918         }
919 
920         return classes;
921     }
922 }