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