001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.doclets; 021 022import java.io.FileNotFoundException; 023import java.io.FileOutputStream; 024import java.io.OutputStreamWriter; 025import java.io.PrintWriter; 026import java.io.Writer; 027import java.nio.charset.StandardCharsets; 028import java.util.Arrays; 029import java.util.List; 030import java.util.Locale; 031import java.util.stream.Collectors; 032 033import com.sun.javadoc.ClassDoc; 034import com.sun.javadoc.DocErrorReporter; 035import com.sun.javadoc.FieldDoc; 036import com.sun.javadoc.RootDoc; 037import com.sun.javadoc.Tag; 038 039/** 040 * Doclet which is used to write property file with short descriptions 041 * (first sentences) of TokenTypes' constants. 042 * Request: 724871 043 * For ide plugins (like the eclipse plugin) it would be useful to have 044 * programmatic access to the first sentence of the TokenType constants, 045 * so they can use them in their configuration gui. 046 * @author o_sukhodolsky 047 */ 048public final class TokenTypesDoclet { 049 050 /** Command line option to specify file to write output of the doclet. */ 051 private static final String DEST_FILE_OPT = "-destfile"; 052 053 /** Stop instances being created. */ 054 private TokenTypesDoclet() { 055 } 056 057 /** 058 * The doclet's starter method. 059 * @param root {@code RootDoc} given to the doclet 060 * @return true if the given {@code RootDoc} is processed. 061 * @exception FileNotFoundException will be thrown if the doclet 062 * will be unable to write to the specified file. 063 */ 064 public static boolean start(RootDoc root) 065 throws FileNotFoundException { 066 final String fileName = getDestFileName(root.options()); 067 final FileOutputStream fos = new FileOutputStream(fileName); 068 final Writer osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); 069 final PrintWriter writer = new PrintWriter(osw, false); 070 071 try { 072 final ClassDoc[] classes = root.classes(); 073 final FieldDoc[] fields = classes[0].fields(); 074 for (final FieldDoc field : fields) { 075 if (field.isStatic() && field.isPublic() && field.isFinal() 076 && "int".equals(field.type().qualifiedTypeName())) { 077 final String firstSentence; 078 079 if (field.firstSentenceTags().length == 1) { 080 firstSentence = field.firstSentenceTags()[0].text(); 081 } 082 else if (Arrays.stream(field.firstSentenceTags()) 083 .filter(tag -> !"Text".equals(tag.name())).count() == 1) { 084 // We have to filter "Text" tags because of jdk parsing bug 085 // till JDK-8186270 086 firstSentence = field.firstSentenceTags()[0].text() 087 + "<code>" 088 + field.firstSentenceTags()[1].text() 089 + "</code>" 090 + field.firstSentenceTags()[2].text(); 091 } 092 else { 093 final List<Tag> tags = Arrays.asList(field.firstSentenceTags()); 094 final String joinedTags = tags 095 .stream() 096 .map(Tag::toString) 097 .collect(Collectors.joining("\", \"", "[\"", "\"]")); 098 final String message = String.format(Locale.ROOT, 099 "Should be only one tag for %s. Tags %s.", 100 field.toString(), joinedTags); 101 throw new IllegalArgumentException(message); 102 } 103 writer.println(field.name() + "=" + firstSentence); 104 } 105 } 106 } 107 finally { 108 writer.close(); 109 } 110 111 return true; 112 } 113 114 /** 115 * Returns option length (how many parts are in option). 116 * @param option option name to process 117 * @return option length (how many parts are in option). 118 */ 119 public static int optionLength(String option) { 120 int length = 0; 121 if (DEST_FILE_OPT.equals(option)) { 122 length = 2; 123 } 124 return length; 125 } 126 127 /** 128 * Checks that only valid options was specified. 129 * @param options all parsed options 130 * @param reporter the reporter to report errors. 131 * @return true if only valid options was specified 132 */ 133 public static boolean checkOptions(String[][] options, DocErrorReporter reporter) { 134 boolean foundDestFileOption = false; 135 boolean onlyOneDestFileOption = true; 136 for (final String[] opt : options) { 137 if (DEST_FILE_OPT.equals(opt[0])) { 138 if (foundDestFileOption) { 139 reporter.printError("Only one -destfile option allowed."); 140 onlyOneDestFileOption = false; 141 break; 142 } 143 foundDestFileOption = true; 144 } 145 } 146 if (!foundDestFileOption) { 147 reporter.printError("Usage: javadoc -destfile file -doclet TokenTypesDoclet ..."); 148 } 149 return onlyOneDestFileOption && foundDestFileOption; 150 } 151 152 /** 153 * Reads destination file name. 154 * @param options all specified options. 155 * @return destination file name 156 */ 157 private static String getDestFileName(String[]... options) { 158 String fileName = null; 159 for (final String[] opt : options) { 160 if (DEST_FILE_OPT.equals(opt[0])) { 161 fileName = opt[1]; 162 } 163 } 164 return fileName; 165 } 166 167}