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 static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 020 021import java.util.Map; 022import java.util.TreeMap; 023import java.util.Set; // 6.2.0.0 (2015/02/27) 024import java.util.TreeSet; // 6.2.0.0 (2015/02/27) 025import java.io.IOException; 026 027import com.sun.javadoc.RootDoc; 028import com.sun.javadoc.ClassDoc; 029import com.sun.javadoc.ExecutableMemberDoc; 030import com.sun.javadoc.Type; 031import com.sun.javadoc.Parameter; 032import com.sun.javadoc.Tag; 033 034/** 035 * ソースコメントから、タグ情報を取り出す Doclet クラスです。 036 * パラメータの version に一致する og.rev タグの 書かれたメソッドを、 037 * ピックアップします。 038 * og.rev タグ で、まとめて表示します。 039 * 040 * ルールとしては、X.X.X.X だけが引数で渡されますので、 X.X.X.X (YYYY/MM/DD) が 041 * コメントとして記載されているとして、処理します。 042 * そして、それ以降の記述の、「。」までを、キーワードとして、まとめます。 043 * キーワード以下は、それぞれのコメントとして、各メソッドの直前に表示します。 044 * このクラスは、RELEASE-NOTES.txt を作成する場合に、変更箇所をピックアップするのに使用します。 045 * 046 * @version 6.0.2.3 (2014/10/10) 047 * @author Kazuhiko Hasegawa 048 * @since JDK5.0, 049 */ 050@SuppressWarnings(value={"deprecation","removal"}) // Ver7.0.0.0 , 7.2.2.0 (2020/03/27) 051public final class DocletVerCheck { 052 private static final String SELECT_PACKAGE = "org.opengion" ; 053 private static final String ENCODE = "UTF-8"; 054 055 private static final String OG_REV = "og.rev"; 056 057 // 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。 058 // 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。 059 060 private static int debugLevel ; // 6.2.0.0 (2015/02/27) 0:なし 1:ソース行出力 061 062 private static int omitPackage = SELECT_PACKAGE.length()+1 ; // 6.8.2.2 (2017/11/02) 汎用的に対応させます。 063 064 /** 065 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。 066 * 067 */ 068 private DocletVerCheck() {} 069 070 /** 071 * Doclet のエントリポイントメソッドです。 072 * 073 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 074 * @og.rev 6.8.2.2 (2017/11/02) パッケージ名の簡略化を、汎用的に対応させます。 075 * 076 * @param root エントリポイントのRootDocオブジェクト 077 * 078 * @return 正常実行時 true 079 */ 080 public static boolean start( final RootDoc root ) { 081 final String version = DocletUtil.getOption( "-version" , root.options() ); 082 final String file = DocletUtil.getOption( "-outfile" , root.options() ); 083 final String dbgLvl = DocletUtil.getOption( "-debugLevel" , root.options() ); // 6.2.0.0 (2015/02/27) 084 if( dbgLvl != null ) { debugLevel = Integer.parseInt( dbgLvl ); } 085 final String omitPkg = DocletUtil.getOption( "-omitPackage" , root.options() ); // 6.8.2.2 (2017/11/02) 086 if( omitPkg != null && !omitPkg.isEmpty() ) { omitPackage = omitPkg.length()+1; } // 6.8.2.2 (2017/11/02) 087 088 DocletTagWriter writer = null; 089 try { 090 writer = new DocletTagWriter( file,ENCODE ); 091 092 writeContents( root.classes(),writer,version ); 093 } 094 catch( final IOException ex ) { 095 LogWriter.log( ex ); 096 } 097 finally { 098 if( writer != null ) { writer.close(); } 099 } 100 return true; 101 } 102 103 /** 104 * TreeSet用の Comparator 実装クラス 105 * 106 * これは、String[] 配列を TreeSet でソートする為のComparatorです。 107 * ソートは、同一Set内で、配列の3番目の要素(ポジション)です。 108 * つまり、クラス名、メソッド名、行番号順になります。 109 * 110 * @og.rev 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。 111 * @og.rev 6.3.9.0 (2015/11/06) Comparator を実装する場合、Serializable も実装するべきかどうか検討するべきです(findbugs)。 112 * 113 */ 114 private static final class ObjComp implements java.util.Comparator<String[]> , java.io.Serializable { 115 private static final long serialVersionUID = 639020151030L ; 116 117 /** 118 * 比較メソッド 119 * 120 * これは、String[] 配列を TreeSet でソートする為のComparatorです。 121 * ソートは、同一Set内で、配列の3番目の要素(ポジション)です。 122 * つまり、クラス名、メソッド名、行番号順になります。 123 * 124 * @og.rev 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。 125 * 126 * @param o1 文字列配列(比較対象の最初のオブジェクト) 127 * @param o2 文字列配列(比較対象の2番目のオブジェクト) 128 * @return 最初の引数が2番目の引数より小さい場合は負の整数、両方が等しい場合は0、最初の引数が2番目の引数より大きい場合は正の整数 129 */ 130 public int compare( final String[] o1, final String[] o2 ) { 131 return o1[2].compareTo( o2[2] ); 132 } 133 } 134 135 /** 136 * ClassDoc 配列よりコンテンツを作成します。 137 * 138 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 139 * @og.rev 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。 140 * @og.rev 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。 141 * @og.rev 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。 142 * @og.rev 6.8.2.2 (2017/11/02) パッケージ名の簡略化を、汎用的に対応させます。 143 * 144 * @param classes ClassDoc配列 145 * @param writer Tagを書き出すWriterオブジェクト 146 * @param version チェックする対象のバージョン 147 */ 148 private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer,final String version ) { 149 150 // 6.4.3.1 (2016/02/12) インスタンス変数をローカル変数に変更。 151 final Map<String,Set<String[]>> VER_INFO_MAP = new TreeMap<>(); // 6.4.1.1 (2016/01/16) verInfo → VER_INFO_MAP refactoring 152 153 ExecutableMemberDoc[][] docs = new ExecutableMemberDoc[2][]; // 6.1.0.0 (2014/12/26) refactoring 154 for( int x=0; x<classes.length; x++ ) { 155 final ClassDoc classDoc = classes[x] ; 156 String fullName = classDoc.qualifiedName(); 157 158 fullName = fullName.substring( omitPackage ); // パッケージ名を簡略化しておきます。 6.8.2.2 (2017/11/02) 汎用的に対応させます。 159 160 // コンストラクタ、メソッドから、対象の @og.rev の値を取得します。 161 docs[0] = classDoc.constructors( false ) ; 162 docs[1] = classDoc.methods( false ) ; 163 164 for( int i=0; i<docs.length; i++ ) { 165 for( int j=0; j<docs[i].length; j++ ) { 166 final ExecutableMemberDoc doc = docs[i][j]; 167 final Tag[] revTags = doc.tags(OG_REV); 168 for( int k=0 ; k<revTags.length; k++ ) { 169 final String rev = revTags[k].text(); 170 if( rev.startsWith( version ) ) { 171 // 6.2.0.0 (2015/02/27) List → Set に変更して、TreeSet を使います。 172 Set<String[]> set = VER_INFO_MAP.get( rev ); // rev コメントでまとめます。 173 if( set == null ) { set = new TreeSet<>( new ObjComp() ); } 174 set.add( menberCheck( doc,fullName ) ); 175 VER_INFO_MAP.put( rev,set ); 176 177 break; 178 } 179 } 180 } 181 } 182 } 183 184 String revYMD = null; // 初めてのRev.日付をセットする。 185 String title = ""; // キーブレイクのマーカー 186 // 6.3.9.0 (2015/11/06) keySet() ではなく、entrySet() を使う様に変更 187 for( final Map.Entry<String,Set<String[]>> entry : VER_INFO_MAP.entrySet() ) { // 6.3.9.0 (2015/11/06) 188 final String key = entry.getKey(); // 6.3.9.0 (2015/11/06) 189 if( revYMD == null ) { 190 final int idx = key.indexOf( ')' ) ; 191 revYMD = key.substring( 0,idx+1 ); 192 writer.printTag( revYMD ); // 一番先頭の Rev-日付出力 193 } 194 195 // バージョン、日付以降のメッセージを取得。 196 final String msg = key.substring( revYMD.length() ).trim(); // 日付以降のコメント 197 final int idx = msg.indexOf( '。' ); // '。' の区切り文字の前半部分が、タイトル。 198 199 if( idx >= 0 ) { // 分割する場合 200 final String newTitle = msg.substring( 0,idx ).trim() ; 201 if( !title.equals( newTitle ) ) { 202 writer.printTag( "\n\t[" , newTitle , "]" ); 203 title = newTitle ; 204 } 205 if( idx != msg.length()-1 ) { // 分割する場合、最後の「。」は、分割対象外 206 writer.printTag( "\t" , msg.substring( idx+1 ).trim() ); 207 } 208 } 209 else { 210 writer.printTag( "\n\t[" , msg , "]" ); 211 title = msg ; 212 } 213 214 for( final String[] vals : entry.getValue() ) { // 6.3.9.0 (2015/11/06) keySet() ではなく、entrySet() を使う様に変更 215 if( debugLevel > 0 ) { // 6.2.0.0 (2015/02/27) 216 writer.printTag( vals[2] ); 217 } 218 writer.printTag( "\t\t" , vals[0] , "#" , vals[1] ); 219 } 220 } 221 } 222 223 /** 224 * メンバークラス(コンストラクタ、メソッド)から、メソッド名、シグネチャを取得します。 225 * 226 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 227 * 228 * @param menber ExecutableMemberDocオブジェクト 229 * @param fullName パッケージを含めたクラスのフル名称 230 * @return 配列にセットされた情報( fullName,signature,position ) 231 * @og.rtnNotNull 232 */ 233 private static String[] menberCheck( final ExecutableMemberDoc menber,final String fullName ) { 234 235 final String menberName = menber.name(); 236 237 // 6.0.2.5 (2014/10/31) char を append する。 238 final StringBuilder sigBuf = new StringBuilder( BUFFER_MIDDLE ); 239 sigBuf.append( menberName ).append( '(' ) ; 240 final Parameter[] prm = menber.parameters(); 241 242 for( int k=0; k<prm.length; k++ ) { 243 final Type ptyp = prm[k].type(); 244 final String prmNm =prm[k].name(); 245 246 sigBuf.append( ptyp.typeName() ).append( ptyp.dimension() ).append( ' ' ) 247 .append( prmNm ).append( ',' ); 248 } 249 250 if( prm.length > 0 ) { sigBuf.deleteCharAt( sigBuf.length()-1 ); } 251 sigBuf.append( ')' ); 252 final String signature = sigBuf.toString(); 253 254 final String position = menber.position().toString(); 255 System.out.println( position ); // デバッグ情報 256 257 return new String[] { fullName , signature , position }; 258 } 259 260 /** 261 * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。 262 * 263 * ドックレットに認識させる各カスタムオプションに、 optionLength がその 264 * オプションを構成する要素 (トークン) の数を返さなければなりません。 265 * このカスタムオプションでは、 -tag オプションそのものと 266 * その値の 2 つの要素で構成されるので、作成するドックレットの 267 * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては 268 * なりません。また、認識できないオプションに対しては、0 を返します。 269 * 270 * @og.rev 6.8.2.2 (2017/11/02) パッケージ名の簡略化を、汎用的に対応させます。 271 * 272 * @param option オプション文字列 273 * 274 * @return 要素(トークン) の数 275 */ 276 public static int optionLength( final String option ) { 277 if( "-version".equalsIgnoreCase(option) ) { 278 return 2; 279 } 280 else if( "-outfile".equalsIgnoreCase(option) ) { 281 return 2; 282 } 283 // 6.2.0.0 (2015/02/27) ソース行番号出力 284 else if( "-debugLevel".equalsIgnoreCase(option) ) { 285 return 2; 286 } 287 // 6.8.2.2 (2017/11/02) 汎用的に対応させます。 288 else if( "-omitPackage".equalsIgnoreCase(option) ) { 289 return 2; 290 } 291 return 0; 292 } 293}