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; // 7.4.4.0 (2021/06/30) openGionV8事前準備(taglet2→taglet) 017 018import jdk.javadoc.doclet.DocletEnvironment ; 019// import jdk.javadoc.doclet.Doclet ; 020// import jdk.javadoc.doclet.Reporter ; 021// import javax.lang.model.element.Element ; 022import javax.lang.model.element.Modifier ; 023import javax.lang.model.element.TypeElement; 024// import javax.lang.model.element.ElementKind ; 025// import javax.lang.model.element.VariableElement; 026import javax.lang.model.element.ExecutableElement; 027// import javax.lang.model.SourceVersion ; 028import javax.lang.model.type.TypeMirror; 029import javax.lang.model.type.TypeKind; 030import javax.lang.model.util.ElementFilter ; 031import javax.lang.model.util.Elements ; 032import javax.tools.Diagnostic.Kind ; 033import com.sun.source.doctree.DocCommentTree ; 034import com.sun.source.util.DocTrees ; 035// import com.sun.source.util.DocSourcePositions ; 036// import com.sun.source.doctree.DocTree ; 037 038// import java.util.Locale ; 039import java.util.Set; 040import java.util.Map; 041// import java.util.HashMap; 042import java.util.List; 043import java.util.HashSet; 044import java.util.Arrays; 045// import java.util.stream.Stream; // 6.4.3.4 (2016/03/11) 046// import java.util.stream.Collectors; // 6.4.3.4 (2016/03/11) 047 048// import java.io.IOException; 049// import java.io.File; 050// import java.io.PrintWriter; 051 052// import org.opengion.fukurou.util.FileUtil; 053// import org.opengion.fukurou.util.StringUtil; 054 055/** 056 * ソースコメントから、パラメータ情報を取り出す Doclet クラスです。 057 * og.paramLevel タグと og.cryptography タグを切り出します。 058 * これらは、システムパラメータとしてGE12テーブルに設定される値をクラスより抽出する 059 * のに使用します。 060 * 061 * @version 7.3 062 * @author Kazuhiko Hasegawa 063 * @since JDK11.0, 064 */ 065public class DocTreeTaglib extends AbstractDocTree { 066 private static final String OG_FOR_SMPL = "og.formSample"; 067 private static final String OG_TAG_NAME = "og.tag"; 068 private static final String OG_GROUP = "og.group"; 069 070 private static final String DOC_PARAM = "param"; // 6.1.1.0 (2015/01/17) 071 072 private static final String OG_TAG_CLASS = "org.opengion.hayabusa.taglib"; 073 074 private String version ; 075 private String outfile ; 076 077// private DocTrees docUtil; 078// private Elements eleUtil ; 079 080 /** 081 * デフォルトコンストラクター 082 * 083 * @og.rev 7.3.0.0 (2021/01/06) PMD refactoring. Each class should declare at least one constructor. 084 */ 085 public DocTreeTaglib() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 086 087 /** 088 * Doclet のエントリポイントメソッドです(昔の startメソッド)。 089 * 090 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 091 * 092 * @param docEnv ドックレットを1回呼び出す操作環境 093 * 094 * @return 正常実行時 true 095 */ 096 @Override 097 public boolean run( final DocletEnvironment docEnv ) { 098 try( DocTreeWriter writer = new DocTreeWriter( outfile,ENCODE ) ) { 099 writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE , "\" ?>" ); 100 writer.printTag( "<javadoc>" ); 101 writer.printTag( " <version>",version,"</version>" ); 102 writer.printTag( " <description></description>" ); 103 writeContents( docEnv,writer ); 104 writer.printTag( "</javadoc>" ); 105 } 106 catch( final Throwable th ) { 107 reporter.print(Kind.ERROR, th.getMessage()); 108 } 109 110 return true; 111 } 112 113 /** 114 * DocletEnvironmentよりコンテンツを作成します。 115 * 116 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 117 * @og.rev 8.0.2.1 (2021/12/10) tagGroupがdescriptionと合わせて出力されるため、【】でくくります。 118 * @og.rev 8.0.2.1 (2021/12/10) コメント分割で『。』と半角の『。』の両方対応しておく。 119 * 120 * @param docEnv ドックレットの最上位 121 * @param writer DocTreeWriterオブジェクト 122 */ 123 private void writeContents( final DocletEnvironment docEnv, final DocTreeWriter writer ) { 124// docUtil = docEnv.getDocTrees(); 125// eleUtil = docEnv.getElementUtils(); 126 127// // get the DocTrees utility class to access document comments 128 final DocTrees docUtil = docEnv.getDocTrees(); 129 final Elements eleUtil = docEnv.getElementUtils(); 130 131 final Set<String> mtdClsSet = new HashSet<>(); // 6.4.3.1 (2016/02/12) 変数名も変えておきます。 132 133 // クラス単位にループする。 134 for( final TypeElement typEle : ElementFilter.typesIn(docEnv.getIncludedElements())) { 135 final String fullName = String.valueOf( typEle.getQualifiedName() ) ; 136// final String fullName = String.valueOf( typEle ) ; 137 writer.setClassName( fullName ); 138 139 if( !typEle.getModifiers().contains( Modifier.PUBLIC ) || 140 !fullName.contains( OG_TAG_CLASS ) ) { continue; } // public かつ taglib に絞る 141 142 final DocCommentTree docTree = docUtil.getDocCommentTree(typEle); // ドキュメンテーション・コメントが見つからない場合、null が返る。 143 144// final List<? extends DocTree> desc = docTree == null ? EMPTY_LIST : docTree.getFirstSentence(); 145// final List<? extends DocTree> cmnt = docTree == null ? EMPTY_LIST : docTree.getFullBody(); 146 final String[] cmnts = getTitleCmnt( docTree ); // 8.0.2.1 (2021/12/10) 147 148 final Map<String,List<String>> blkTagMap = blockTagsMap(docTree); 149 final String smplTags = getBlockTag( OG_FOR_SMPL, blkTagMap, "" ); 150 final String grpTags = getBlockTag( OG_GROUP , blkTagMap, "," ); 151 152// String smplTags = ""; 153// final StringBuilder grpBuf = new StringBuilder(); 154// if( docTree != null ) { 155// for( final DocTree dt : docTree.getBlockTags() ) { 156// final String tag = String.valueOf(dt); 157// if( tag.contains( OG_FOR_SMPL ) ) { smplTags = cutTag( tag,OG_FOR_SMPL ); } 158// else if( tag.contains( OG_GROUP ) ) { grpBuf.append( '【' ).append( cutTag( tag,OG_GROUP ) ).append( "】," ); } 159// } 160// } 161// final String grpTags = grpBuf.length() == 0 ? "" : grpBuf.substring(0,grpBuf.length()-1); // 最後のカンマを削除 162 163 // 5.7.1.1 (2013/12/13) タグのインデントを止める。 164 writer.printTag( "<classDoc>" ); 165 writer.printTag( " <tagClass>" ,fullName ,"</tagClass>" ); 166 writer.printTag( " <tagGroup>【" ,grpTags ,"】</tagGroup>" ); // 8.0.2.1 (2021/12/10) 167// writer.printTag( " <description>" ,desc ,"</description>" ); 168 writer.printTag( " <description>" ,cmnts[0] ,"</description>" ); // 8.0.2.1 (2021/12/10) 169// writer.printTag( " <contents>" ,cmnt ,"</contents>" ); 170 writer.printTag( " <contents>" ,cmnts[1] ,"</contents>" ); // 8.0.2.1 (2021/12/10) 171 writer.printTag( " <formSample>" ,smplTags ,"</formSample>" ); 172 173 mtdClsSet.clear(); 174 String className = String.valueOf( typEle ); 175 TypeElement loopEle = typEle; 176 while( loopEle != null && 177 className.contains( OG_TAG_CLASS ) ) { 178 179 writer.setClassName( className ); 180 final String extendFlag = String.valueOf( className.contains( "HTMLTagSupport" ) ); 181 182 // メソッドのみフィルタリングして取得する 183 for( final ExecutableElement ele : ElementFilter.methodsIn(loopEle.getEnclosedElements())) { // メソッドだけに絞る 184 if( !ele.getModifiers().contains( Modifier.PUBLIC ) ) { continue; } // public だけに絞る 185 186 final DocCommentTree mdoc = docUtil.getDocCommentTree(ele); // ドキュメンテーション・コメントが見つからない場合、null が返る。 187 if( mdoc == null ) { continue; } 188 189 final Map<String,List<String>> blkTagMap2 = blockTagsMap(mdoc); 190 final String tags = getBlockTag( OG_TAG_NAME , blkTagMap2, "" ); 191 192// String tags = ""; 193// for( final DocTree dt : mdoc.getBlockTags() ) { 194// final String tag = String.valueOf(dt); 195// if( tag.contains( OG_TAG_NAME ) ) { tags = cutTag( tag,OG_TAG_NAME ); } 196// } 197 198 if( !tags.isEmpty() ) { 199 final String fname = String.valueOf( ele ); 200 if( !mtdClsSet.add( fname ) ) { continue; } // 継承もとに同じメソッド名がある場合は、無視する。 201 202 final String methodName = removeSetter( String.valueOf( ele.getSimpleName() ) ); 203 204// final List<? extends DocTree> ftag = mdoc.getFirstSentence(); 205// final List<? extends DocTree> mcmnt = mdoc.getFullBody(); 206 final String[] fcmnts = getTitleCmnt( mdoc ); // 8.0.2.1 (2021/12/10) 207 208 final String[] keylblCd = methodLabelCode( mdoc ); 209 210// final List<? extends DocTree> doc1 = mdoc.getPostamble(); 211// final List<? extends DocTree> doc2 = mdoc.getPreamble(); 212 213 // 5.7.1.1 (2013/12/13) タグのインデントを止める。 214 writer.printTag( " <method>" ); 215 writer.printTag( " <name>" ,methodName ,"</name>" ); 216 writer.printTag( " <label>" ,keylblCd[1],"</label>" ); // 6.1.1.0 (2015/01/17) 217 writer.printTag( " <comment>" ,keylblCd[2],"</comment>" ); // 6.2.5.0 (2015/06/05) 218 writer.printTag( " <code>" ,keylblCd[3],"</code>" ); // 6.1.1.0 (2015/01/17) 219 writer.printTag( " <htmlExtend>" ,extendFlag ,"</htmlExtend>" ); 220// writer.printTag( " <description>",ftag ,"</description>" ); // 8.0.2.1 (2021/12/10) 221 writer.printTag( " <description>",fcmnts[0] ,"</description>" ); 222// writer.printTag( " <contents>" ,mcmnt ,"" ); 223 writer.printTag( " <contents>" ,fcmnts[0] ,"" ); // 8.0.2.1 (2021/12/10) 224 writer.printTag( "" ,tags ,"</contents>" ); 225 writer.printTag( " </method>"); 226 } 227 } 228 229 final TypeMirror superType = loopEle.getSuperclass(); 230 className = String.valueOf( superType ); 231 loopEle = null; // while ループの終了 232 if( !TypeKind.NONE.equals( superType.getKind() ) ) { 233 for( final TypeElement tp : eleUtil.getAllTypeElements(className) ) { 234 if( className.equals( String.valueOf(tp) ) ) { 235 loopEle = tp ; break; 236 } 237 } 238 } 239 } 240 writer.printTag( "</classDoc>" ); 241 } 242 } 243 244 /** 245 * セッターメソッドの setXXXX の set を削除し、次の文字を小文字化します。 246 * つまり、セッターメソッドから属性値を推測します。 247 * (超特殊処理)セッターメソッドのset以下2文字目が大文字の場合は、 248 * 1文字目も大文字と考えて小文字化を行いません。 249 * 例えば、setSYS や setUSER など、RequestValueTag.javaに使用するケースです。 250 * 251 * @param target 処理対象となる文字列 252 * 253 * @return オプション文字列 254 */ 255 private String removeSetter( final String target ) { 256 if( target != null && target.startsWith( "set" ) ) { 257 char[] chs = target.toCharArray(); 258 if( chs.length > 4 && !( chs[4] >= 'A' && chs[4] <= 'Z' ) ) { 259 chs[3] = Character.toLowerCase( chs[3] ) ; 260 } 261 return new String( chs,3,chs.length-3 ); 262 } 263 return target; 264 } 265 266 /** 267 * MethodDocを受け取り、0:パラメータ、1:ラベル、2:コメント、3:コードを文字列配列に入れて返します。 268 * 269 * これは、タグリブのラベルリソース登録時に使用する情報です。 270 * タグリブのローカルルールとして、0:パラメータ 1:ラベル 2:ラベル以降の解説 3:コード 271 * という記述を行う事を前提にしています。 272 * 273 * 0:パラメータは、引数です。メソッド名ではありませんので、ご注意ください。 274 * 1:ラベルは、パラメータの次の空白文字から、次の空白文字、または、"[" までの文字です。 275 * 2:コメントは、ラベル以降の文字列で、コードの記述部分も含みます。 276 * 3:コード は、特別な処理を行います。"[" と "]" 内に記述された内容を取り出します。 277 * 278 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応 279 * 280 * @param mdoc DocCommentTreeオブジェクト 281 * 282 * @return 0:パラメータ、1:ラベル、2:コメント、3:コードを文字列配列に詰めた値(長さ4の配列) 283 * @og.rtnNotNull 284 */ 285 private String[] methodLabelCode( final DocCommentTree mdoc ) { 286 final String[] arys = new String[] { "","","","" } ; // 後で内容を更新する。 287 288 final Map<String,List<String>> blkTagMap = blockTagsMap(mdoc); 289 final String prmTag = getBlockTag( DOC_PARAM, blkTagMap, "" ); 290 291// String prmTag = ""; 292// for( final DocTree dt : mdoc.getBlockTags() ) { 293// final String tag = String.valueOf(dt); 294// if( tag.contains( DOC_PARAM ) ) { 295// prmTag = cutTag( tag,DOC_PARAM ); break; // Taglibのsetter は、paramは一つだけのハズ 296// } 297// } 298 299 // 最大3つ と指定しているが、0:パラメータと1:ラベルの2つしか保証されていない。 300 final String[] temp = prmTag.split( "[\\s]+",3 ); // 空白文字で3つに分解する。 301 System.arraycopy( temp,0,arys,0,temp.length ); // 6.3.6.0 (2015/08/16) 302 303 // 3:コード があれば、2:コメントから取り出す。 304 final int st1 = arys[2].indexOf( '[' ); 305 if( st1 >= 0 ) { 306 final int st2 = arys[2].indexOf( ']',st1 ); 307 if( st2 > 0 ) { 308 // 前後の [] は、取り除き、'/' があれば、' ' に置換する。(コード文字列化) 309 arys[3] = arys[2].substring( st1+1,st2 ).replace( '/' , ' ' ); 310 } 311 } 312 313 return arys ; 314 } 315 316 /** 317 * サポートされているすべてのオプションを返します。 318 * 319 * @return サポートされているすべてのオプションを含むセット、存在しない場合は空のセット 320 */ 321 @Override 322 public Set<? extends Option> getSupportedOptions() { 323 final Option[] options = { 324 new AbstractOption( "-outfile", "-version" ) { 325 326 /** 327 * 必要に応じてオプションと引数を処理します。 328 * 329 * @param opt オプション名 330 * @param arguments 引数をカプセル化したリスト 331 * @return 操作が成功した場合はtrue、そうでない場合はfalse 332 */ 333 @Override 334 public boolean process(final String opt, final List<String> arguments) { 335 if( "-outfile".equalsIgnoreCase(opt) ) { 336 outfile = arguments.get(0); 337 } 338 else if( "-version".equalsIgnoreCase(opt) ) { 339 version = arguments.get(0); 340 } 341 return true; 342 } 343 } 344 }; 345 return new HashSet<>(Arrays.asList(options)); 346 } 347}