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.util;
017
018import java.io.IOException;
019import java.io.File;
020
021import java.util.List;
022import java.util.ArrayList;
023import java.util.Enumeration;
024import java.util.jar.JarFile;
025import java.util.jar.JarEntry;
026import java.net.URL;
027
028// import org.opengion.fukurou.system.Closer;                                           // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
029import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
030
031/**
032 * このクラスは、指定のディレクトリパスから .class ファイルを検索するクラスです。
033 * 検索パスは、実ファイルと、zipファイルの内部、jar ファイルの内部も含みます。
034 * 検索結果は、.class を取り除き、ファイルパスを、すべてドット(.)に変換した形式にします。
035 * これは、ほとんどクラスのフルパス文字列に相当します。
036 * ここで取得されたファイル名より、実クラスオブジェクトの作成が可能になります。
037 *
038 * このクラスの main メソッドは、クラスパスから指定の名前を持つクラス以下のディレクトリより
039 * ファイルを検索します。通常、このクラスの使い方として、取得したクラスファイル名(文字列)
040 * から、引数なしコンストラクタを呼び出して、実オブジェクトを生成させるので、通常のフォルダ
041 * から検索するより、クラスパス内から検索するペースが多いため、サンプルをそのように設定
042 * しています。
043 *
044 * @og.rev 4.0.0.0 (2004/12/31) 新規作成
045 * @og.group 初期化
046 *
047 * @version  4.0
048 * @author   Kazuhiko Hasegawa
049 * @since    JDK5.0,
050 */
051public final class FindClassFiles {
052        private final List<String> list = new ArrayList<>();
053        private final int    baseLen;
054
055        private static final String SUFIX     = ".class" ;
056        private static final int    SUFIX_LEN = SUFIX.length();
057
058        /**
059         * 検索パスを指定して構築する、コンストラクタです。
060         * ここで見つかったパス以下の classファイル(拡張子は小文字で、.class )を検索します。
061         * このファイル名は ファイルパスを ドット(.)に置き換え、.class を取り除いた格納しておきます。
062         *
063         * ※ Tomcat8.0.3 では、ClassLoader の getResources(String)で取得するURL名が、
064         *    /C:/opengionV6/uap/webapps/gf/WEB-INF/classes/org/opengion/plugin/
065         *    の形式で、最後の "/" を取る為、filepath.length() - 1 処理していましたが、
066         *    Tomcat8.0.5 では、/C:/opengionV6/uap/webapps/gf/WEB-INF/classes/org/opengion/plugin
067         *    の形式で、最後の "/" がなくなっています。
068         *    最後の "/" があってもなくても、new File(String) でディレクトリのオブジェクトを
069         *    作成できるため、filepath.length() に変更します。
070         *
071         * @og.rev 4.0.3.0 (2007/01/07) UNIXパス検索時の、ファイルパスの取得方法の不具合対応
072         * @og.rev 5.0.0.0 (2009/08/03) UNIXパス検索時の、ファイルパスの取得方法の不具合対応
073         * @og.rev 5.0.0.0 (2009/08/03) UNIXパス検索時の、ファイルパスの取得方法の不具合対応
074         * @og.rev 5.7.5.0 (2014/04/04) ファイル名の取得方法の修正
075         *
076         * @param       filepath        対象となるファイル群を検索する、ファイルパス
077         * @param       keyword         検索対象ファイルのキーワード
078         */
079        public FindClassFiles( final String filepath,final String keyword ) {
080                // jar:file:/実ディレクトリ!先頭クラス または、file:/実ディレクトリ または、/実ディレクトリ
081
082                String dir = filepath;                                                  // 5.5.2.6 (2012/05/25) findbugs対応
083                if( filepath.startsWith( "jar:" )
084                                        || filepath.startsWith( "file:" )
085                                        || filepath.charAt(0) == '/' ) {                // 4.4.0.0 (2009/08/02)
086                        int stAdrs = filepath.indexOf( '/' );
087                        if( filepath.charAt(stAdrs+2) == ':' ) {        // 4.0.3.0 (2007/01/07)
088                                stAdrs++;
089                        }
090                        int edAdrs = filepath.lastIndexOf( '!' );
091                        if( edAdrs < 0) {
092                                edAdrs = filepath.length();                     // 5.7.5.0 (2014/04/04) 最後の "/" はあってもなくてもよい。
093                        }
094                        dir = filepath.substring( stAdrs,edAdrs );
095                }
096
097                final File basefile = new File( dir );
098                final String baseFilename = basefile.getAbsolutePath() ;
099                baseLen = baseFilename.length() - keyword.length();
100                findFilename( basefile );
101        }
102
103        /**
104         * ファイルパスを ドット(.)に置き換え、.class を取り除いた形式(クラスの完全系)の文字列配列。
105         *
106         * @return      ファイルパスの文字列配列
107         * @og.rtnNotNull
108         */
109        public String[] getFilenames() {
110//              return list.toArray( new String[list.size()] );
111                return list.toArray( new String[0] );   // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応
112        }
113
114        /**
115         * ファイルを再帰的に検索します。
116         * 指定のファイルオブジェクトが ファイルの場合は、.class であればListに追加し、
117         * .zip か .jar では、findJarFiles を呼び出します。
118         * ファイルでない場合は、配列を取り出し、自分自身を再帰的に呼び出します。
119         *
120         * @og.rev 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
121         *
122         * @param       file    ファイル
123         */
124        private void findFilename( final File file ) {
125                if( file.isFile() ) {
126                        final String name = file.getAbsolutePath();
127                        if( name.endsWith( SUFIX ) ) {
128                                list.add( name.substring( baseLen,name.length()-SUFIX_LEN ).replace( File.separatorChar,'.' ) );
129                        }
130                        else if( name.endsWith( ".jar" ) || name.endsWith( ".zip" ) ) {
131                                findJarFiles( name );
132                        }
133                }
134                else {
135                        final File[] filelist = file.listFiles();
136                        // 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。
137                        if( filelist != null ) {
138                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
139//                              for( int i=0; i<filelist.length; i++ ) {
140//                                      findFilename( filelist[i] );
141//                              }
142                                for( final File tmp : filelist ) {
143                                        findFilename( tmp );
144                                }
145                        }
146                }
147        }
148
149        /**
150         * jar/zipファイルを検索します。
151         * 圧縮ファイルでは、階層ではなく、Enumeration としてファイルを取り出します。
152         * 拡張子で判断して、Listに追加していきます。
153         *
154         * @og.rev 5.5.2.6 (2012/05/25) JarFile を、Closer#zipClose( ZipFile ) メソッドを利用して、close します。
155                 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
156         *
157         * @param       filename        ファイル名
158         */
159        private void findJarFiles( final String filename ) {
160                // 5.5.2.6 (2012/05/25) findbugs対応
161                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
162//              JarFile jarFile = null;
163//              try {
164//                      jarFile = new JarFile( filename );
165                try ( JarFile jarFile = new JarFile( filename ) ) {
166                        final Enumeration<JarEntry> en = jarFile.entries() ;
167                        while( en.hasMoreElements() ) {
168                                final JarEntry ent = en.nextElement();
169                                if( ! ent.isDirectory() ) {
170                                        final String name = ent.getName();
171                                        if( name.endsWith( SUFIX ) ) {
172                                                list.add( name.substring( 0,name.length()-SUFIX_LEN ).replace( '/','.' ) );
173                                        }
174                                }
175                        }
176                }
177                catch( final IOException ex ) {
178                        final String errMsg = "ファイル読み取りストリームに失敗しました。"
179                                        + " File=" + filename
180                                        + ex.getMessage();                              // 5.1.8.0 (2010/07/01) errMsg 修正
181                        throw new OgRuntimeException( errMsg,ex );
182                }
183                // 5.5.2.6 (2012/05/25) findbugs対応
184//              finally {
185//                      Closer.zipClose( jarFile );
186//              }
187        }
188
189        /**
190         * サンプルメイン
191         * ここでは、引数に通常のファイルではなく、クラスパスより取得します。
192         * 通常、取得されたファイル名は、クラスの完全系の文字列なので、クラスパスより取得
193         * している限り、そのまま オブジェクトを構築できることを意味します。
194         *
195         * @og.rev 6.8.5.1 (2018/01/15) ファイル名は、##バージョン番号を変換しておく必要がある。
196         *
197         * @param       args    引数
198         */
199        public static void main( final String[] args ) {
200                try {
201                        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
202                        final Enumeration<URL> enume = loader.getResources( args[0] );  // 4.3.3.6 (2008/11/15) Generics警告対応
203                        while( enume.hasMoreElements() ) {
204                                final URL url = enume.nextElement();            // 4.3.3.6 (2008/11/15) Generics警告対応
205                                // jar:file:/実ディレクトリ!先頭クラス または、file:/実ディレクトリ
206                                final String dir = url.getFile().replaceAll( "%23%23","##" );           // 6.8.5.1 (2018/01/15)
207//                              System.out.println( "url=" + url.getFile() );
208                                System.out.println( "url=" + dir );                                                                     // 6.8.5.1 (2018/01/15)
209
210//                              final FindClassFiles filenames = new FindClassFiles( url.getFile(),args[0] );
211                                final FindClassFiles filenames = new FindClassFiles( dir,args[0] );     // 6.8.5.1 (2018/01/15)
212                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
213//                              final String[] names = filenames.getFilenames();
214//                              for( int i=0; i<names.length; i++ ) {
215//                                      System.out.println( names[i] );
216//                              }
217                                for( final String name : filenames.getFilenames() ) {
218                                        System.out.println( name );
219                                }
220                        }
221                }
222                catch( final IOException ex ) {
223                        final String errMsg = "ファイル読み取りストリームに失敗しました。"
224                                        + ex.getMessage();
225                        throw new OgRuntimeException( errMsg,ex );
226                }
227        }
228}