001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.taglet; 017 018import org.opengion.fukurou.system.LogWriter; 019import org.opengion.fukurou.util.StringUtil; 020 021import com.sun.javadoc.RootDoc; 022import com.sun.javadoc.ClassDoc; 023import com.sun.javadoc.MethodDoc; 024import com.sun.javadoc.Type; 025import com.sun.javadoc.Tag; 026 027import java.util.Map; 028import java.util.HashMap; 029import java.io.IOException; 030import java.util.stream.Stream; // 6.4.3.4 (2016/03/11) 031import java.util.stream.Collectors; // 6.4.3.4 (2016/03/11) 032 033/** 034 * ソースコメントから、タグ情報を取り出す Doclet クラスです。 035 * この Doclet は、":org.opengion.hayabusa.taglib" のみ対象として処理します。 036 * og.formSample , og.tag , og.group タグを切り出します。 037 * 038 * @version 4.0 039 * @author Kazuhiko Hasegawa 040 * @since JDK5.0, 041 */ 042@SuppressWarnings(value={"deprecation","removal"}) // Ver7.0.0.0 , 7.2.2.0 (2020/03/27) 043public final class DocletTaglib { 044 // 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。変数名も変えておきます。 045 046 private static final String OG_FOR_SMPL = "og.formSample"; 047 private static final String OG_TAG_NAME = "og.tag"; 048 private static final String OG_GROUP = "og.group"; 049 050 private static final String DOC_PARAM = "param"; // 6.1.1.0 (2015/01/17) 051 052 private static final String OG_TAG_CLASS = "org.opengion.hayabusa.taglib"; 053 private static final String ENCODE = "UTF-8"; 054 055 /** 056 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。 057 * 058 */ 059 private DocletTaglib() {} 060 061 /** 062 * Doclet のエントリポイントメソッドです。 063 * 064 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。 065 * 066 * @param root エントリポイントのRootDocオブジェクト 067 * 068 * @return 正常実行時 true 069 */ 070 public static boolean start( final RootDoc root ) { 071 final String version = DocletUtil.getOption( "-version" , root.options() ); 072 final String file = DocletUtil.getOption( "-outfile" , root.options() ); 073 074 DocletTagWriter writer = null; 075 try { 076 writer = new DocletTagWriter( file,ENCODE ); 077 078 // 5.7.1.1 (2013/12/13) タグのインデントを止める。 079 writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE, "\" ?>" ); 080 writer.printTag( "<javadoc>" ); 081 writer.printTag( " <version>",version,"</version>" ); 082 writer.printTag( " <description></description>" ); 083 writeContents( root.classes(),writer ); 084 writer.printTag( "</javadoc>" ); 085 } 086 catch( final IOException ex ) { 087 LogWriter.log( ex ); 088 } 089 finally { 090 if( writer != null ) { writer.close(); } 091 } 092 return true; 093 } 094 095 /** 096 * ClassDoc 配列よりコンテンツを作成します。 097 * 098 * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。 099 * @og.rev 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。 100 * @og.rev 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット 101 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。 102 * @og.rev 6.1.1.0 (2015/01/17) タグリブの param 属性取得(0:パラメータ、1:ラベル、2:コメント、3:コード) 103 * @og.rev 6.2.5.0 (2015/06/05) 2:コメント を、comment として出力します。 104 * @og.rev 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。変数名も変えておきます。 105 * 106 * @param classes ClassDoc配列 107 * @param writer DocletTagWriterオブジェクト 108 */ 109 private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) { 110 final Map<String,String> mtdClsMap = new HashMap<>(); // 6.4.3.1 (2016/02/12) 変数名も変えておきます。 111 for( int i=0; i<classes.length; i++ ) { 112 ClassDoc classDoc = classes[i] ; 113 final String classFullName = classDoc.qualifiedName() ; 114 115 if( ! classDoc.isPublic() || 116 classFullName.indexOf( OG_TAG_CLASS ) < 0 ) { continue; } 117 118 final Tag[] desc = classDoc.firstSentenceTags(); 119 Tag[] cmnt = classDoc.inlineTags(); // 5.5.4.1 (2012/07/06) 120 final Tag[] smplTags = classDoc.tags(OG_FOR_SMPL); 121 final Tag[] grpTags = classDoc.tags(OG_GROUP); 122 123 // 5.7.1.1 (2013/12/13) タグのインデントを止める。 124 writer.printTag( "<classDoc>" ); 125 writer.printTag( " <tagClass>" ,classFullName ,"</tagClass>" ); 126 // 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。 127 writer.printTag( " <tagGroup>" ,makeGroupTag( grpTags ) ,"</tagGroup>" ); 128 writer.printTag( " <description>" ,desc ,"</description>" ); 129 writer.printTag( " <contents>" ,cmnt ,"</contents>" ); 130 writer.printTag( " <formSample>" ,smplTags ,"</formSample>" ); 131 132 // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。 133 mtdClsMap.clear(); 134 String className = classDoc.name(); 135 while( ! "BodyTagSupport".equals( className ) && 136 ! "TagSupport".equals( className ) ) { 137 String extendFlag = "false"; 138 if( "HTMLTagSupport".equals( className ) ) { 139 extendFlag = "true" ; 140 } 141 final MethodDoc[] methods = classDoc.methods(); 142 for( int j=0; j<methods.length; j++ ) { 143 if( ! methods[j].isPublic() ) { continue; } 144 final Tag[] tags = methods[j].tags(OG_TAG_NAME); 145 if( tags.length > 0 ) { 146 final String methodName = DocletUtil.removeSetter( methods[j].name() ); 147 // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。変数名も変えておきます。 148 if( mtdClsMap.containsKey( methodName ) ) { continue; } 149 mtdClsMap.put( methodName,className ); 150 final Tag[] ftag = methods[j].firstSentenceTags(); 151 cmnt = methods[j].inlineTags(); // 5.5.4.1 (2012/07/06) 152 153 // 6.1.1.0 (2015/01/17) タグリブの param 属性取得(0:パラメータ、1:ラベル、2:コメント、3:コード) 154 final String[] keylblCd = methodLabelCode( methods[j] ); 155 156 // 5.7.1.1 (2013/12/13) タグのインデントを止める。 157 writer.printTag( " <method>" ); 158 writer.printTag( " <name>" ,methodName ,"</name>" ); 159 writer.printTag( " <label>" ,keylblCd[1],"</label>" ); // 6.1.1.0 (2015/01/17) 160 writer.printTag( " <comment>" ,keylblCd[2],"</comment>" ); // 6.2.5.0 (2015/06/05) 161 writer.printTag( " <code>" ,keylblCd[3],"</code>" ); // 6.1.1.0 (2015/01/17) 162 writer.printTag( " <htmlExtend>" ,extendFlag ,"</htmlExtend>" ); 163 writer.printTag( " <description>",ftag ,"</description>" ); 164 // 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット 165 writer.printTag( " <contents>" ,cmnt ,"" ); 166 writer.printTag( "" ,tags ,"</contents>" ); 167 writer.printTag( " </method>"); 168 } 169 } 170 final Type type = classDoc.superclassType(); 171 if( type == null ) { break; } 172 classDoc = type.asClassDoc() ; 173 className = classDoc.name(); 174 } 175 writer.printTag( " </classDoc>" ); 176 } 177 } 178 179 /** 180 * MethodDocを受け取り、0:パラメータ、1:ラベル、2:コメント、3:コードを文字列配列に入れて返します。 181 * 182 * これは、タグリブのラベルリソース登録時に使用する情報です。 183 * タグリブのローカルルールとして、0:パラメータ 1:ラベル 2:ラベル以降の解説 3:コード 184 * という記述を行う事を前提にしています。 185 * 186 * 0:パラメータは、引数です。メソッド名ではありませんので、ご注意ください。 187 * 1:ラベルは、パラメータの次の空白文字から、次の空白文字、または、"[" までの文字です。 188 * 2:コメントは、ラベル以降の文字列で、コードの記述部分も含みます。 189 * 3:コード は、特別な処理を行います。"[" と "]" 内に記述された内容を取り出します。 190 * 191 * @og.rev 6.1.1.0 (2015/01/17) タグリブの param 属性取得(0:パラメータ、1:ラベル、2:コメント、3:コード) 192 * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。 193 * 194 * @param method メソッドドック 195 * 196 * @return 0:パラメータ、1:ラベル、2:コメント、3:コードを文字列配列に詰めた値(長さ4の配列) 197 * @og.rtnNotNull 198 */ 199 private static String[] methodLabelCode( final MethodDoc method ) { 200 final String[] arys = new String[] { "","","","" } ; // 後で内容を更新する。 201 202 final Tag[] docParam = method.tags(DOC_PARAM); 203 if( docParam.length == 1 ) { // Taglibのsetter は、paramは一つだけのハズ 204 final String data = docParam[0].text().trim(); // @param の説明文 205 206 // 最大3つ と指定しているが、0:パラメータと1:ラベルの2つしか保証されていない。 207 final String[] temp = data.split( "[\\s]+",3 ); // 空白文字で3つに分解する。 208 // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。 209 System.arraycopy( temp,0,arys,0,temp.length ); // 6.3.6.0 (2015/08/16) 210 211 // 3:コード があれば、2:コメントから取り出す。 212 final int st1 = arys[2].indexOf( '[' ); 213 if( st1 >= 0 ) { 214 final int st2 = arys[2].indexOf( ']',st1 ); 215 if( st2 > 0 ) { 216 // 前後の [] は、取り除き、'/' があれば、' ' に置換する。(コード文字列化) 217 arys[3] = arys[2].substring( st1+1,st2 ).replace( '/' , ' ' ); 218 } 219 } 220 } 221 return arys ; 222 } 223 224 /** 225 * タグ配列を受け取り、タグ出力します。 226 * 複数のタグを出力する場合に、CSV形式で連結します。 227 * 228 * @og.rev 5.5.4.1 (2012/07/06) DocletUtil.htmlFilter → StringUtil.htmlFilter に変更 229 * @og.rev 5.6.6.1 (2013/07/12) og.group の表示方法を変更する。 230 * @og.rev 6.4.3.4 (2016/03/11) CSV形式の文字連結を、stream 経由で行います。 231 * 232 * @param tag タグ配列(可変長引数) 233 * 234 * @return タグ出力文字列 235 * @og.rtnNotNull 236 */ 237 private static String makeGroupTag( final Tag... tag ) { 238 239 return Stream.of( tag ) 240 .map( tg -> StringUtil.htmlFilter( tg.text() ) ) 241 .collect( Collectors.joining( "】,【" , "【" , "】" ) ); // 連結文字 , 最初 , 最後 242 } 243 244 /** 245 * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。 246 * 247 * ドックレットに認識させる各カスタムオプションに、 optionLength がその 248 * オプションを構成する要素 (トークン) の数を返さなければなりません。 249 * このカスタムオプションでは、 -tag オプションそのものと 250 * その値の 2 つの要素で構成されるので、作成するドックレットの 251 * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては 252 * なりません。また、認識できないオプションに対しては、0 を返します。 253 * 254 * @param option カスタムオプションのキーワード 255 * 256 * @return 要素 (トークン) の数 257 */ 258 public static int optionLength( final String option ) { 259 if( "-version".equalsIgnoreCase(option) ) { 260 return 2; 261 } 262 else if( "-outfile".equalsIgnoreCase(option) ) { 263 return 2; 264 } 265 return 0; 266 } 267}