001/* 002 * Copyright (c) 2017 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.fileexec; 017 018import java.nio.file.Path; 019import java.nio.file.PathMatcher; 020 021import java.util.Set; 022import java.util.Locale; 023import java.util.Arrays; 024import java.util.concurrent.CopyOnWriteArraySet; 025 026/** 027 * PathMatcherSet は、ファイル監視を行うクラスで利用する、ファイルの選別(PathMatcher)を管理するクラスです。 028 * 029 *<pre> 030 * PathMatcherオブジェクトを複数持っており(Set)それらが、その、判定によって、 031 * イベントを起こすかどうか、フィルタリングします。 032 * 033 *</pre> 034 * @og.rev 7.0.0.0 (2017/07/07) 新規作成 035 * 036 * @version 7.0 037 * @author Kazuhiko Hasegawa 038 * @since JDK1.8, 039 */ 040public class PathMatcherSet implements PathMatcher { 041 private static final XLogger LOGGER= XLogger.getLogger( PathMatcherSet.class.getName() ); // ログ出力 042 043 // パスの照合操作を行うPathMatcher のSetオブジェクト 044 private final Set<PathMatcher> pathMchSet = new CopyOnWriteArraySet<>(); // 未設定のときは、すべてにマッチングさせる。(startメソッドで) 045 046 /** 047 * デフォルトコンストラクター 048 * 049 */ 050 public PathMatcherSet() { super(); } 051 052 /** 053 * すべてのPathMatcherSet を、追加登録します。 054 * 055 * 引数が、null の場合は、登録しません。 056 * 057 * @param pmSet パスの照合操作のパターン 058 * @return このセットが変更された場合はtrue 059 */ 060 public boolean addAll( final PathMatcherSet pmSet ) { 061 return pmSet != null && pathMchSet.addAll( pmSet.pathMchSet ); 062 } 063 064 /** 065 * 内部の PathMatcherに、要素が含まれてい無い場合に、true を返します。 066 * 067 * @return このセットに要素が1つも含まれていない場合はtrue 068 */ 069 public boolean isEmpty() { 070 return pathMchSet.isEmpty(); 071 } 072 073 /** 074 * すべての要素をセットから削除します。 075 * 076 */ 077 public void clear() { 078 pathMchSet.clear(); 079 } 080 081 /** 082 * PathMatcher を、追加登録します。 083 * 084 * 引数が、null の場合は、登録しません。 085 * 086 * @param pathMch パスの照合操作のパターン 087 * @return 自分自身 088 * @see java.nio.file.PathMatcher 089 * @see #addStartsWith(String...) 090 * @see #addEndsWith(String...) 091 */ 092 public PathMatcherSet addPathMatcher( final PathMatcher pathMch ) { 093 // LOGGER.debug( () -> "addPathMatcher : PathMatcher=" + pathMch ); 094 095 if( pathMch != null ) { 096 pathMchSet.add( pathMch ); 097 } 098 099 return this; 100 } 101 102 /** 103 * 指定のパスが、指定の文字列と、先頭一致(startsWith) したパスのみ、有効とします。 104 * 105 * これは、#addPathMatcher(PathMatcher) の簡易指定版です。 106 * 指定の先頭一致(一般にはファイル名の先頭)のうち、ひとつでも一致すれば、true となります。 107 * 先頭文字列の判定には、大文字小文字の区別を行いません。 108 * 109 * @param startKey パスの先頭一致のパターン 110 * @return 自分自身 111 * @see #addPathMatcher(PathMatcher) 112 * @see #addEndsWith(String...) 113 */ 114 public PathMatcherSet addStartsWith( final String... startKey ) { 115 if( startKey != null ) { 116 LOGGER.debug( () -> "addStartsWith : String[]=" + Arrays.toString ( startKey ) ); 117 118 pathMchSet.add( 119 path -> { 120 // 大文字小文字の区別を行いません。 121 final String fname = path.getFileName().toString().toUpperCase(Locale.JAPAN); 122 for( final String key : startKey ) { 123 if( key == null || key.isEmpty() || fname.startsWith( key.toUpperCase(Locale.JAPAN) ) ) { return true; } 124 } 125 return false; 126 } 127 ); 128 } 129 130 return this; 131 } 132 133 /** 134 * 指定のパスが、指定の文字列と、終端一致(endsWith) したパスのみ、有効とします。 135 * 136 * これは、#addPathMatcher(PathMatcher) の簡易指定版です。 137 * 指定の終端文字列(一般には拡張子)のうち、ひとつでも一致すれば、true となります。 138 * 指定しない場合(null)は、すべて許可されたことになります。 139 * 終端文字列の判定には、大文字小文字の区別を行いません。 140 * 141 * @param endKey パスの終端一致のパターン 142 * @return 自分自身 143 * @see #addPathMatcher(PathMatcher) 144 * @see #addStartsWith(String...) 145 */ 146 public PathMatcherSet addEndsWith( final String... endKey ) { 147 if( endKey != null ) { 148 LOGGER.debug( () -> "addEndsWith : String[]=" + Arrays.toString ( endKey ) ); 149 150 pathMchSet.add( 151 path -> { 152 // 大文字小文字の区別を行いません。 153 final String fname = path.getFileName().toString().toUpperCase(Locale.JAPAN); 154 for( final String key : endKey ) { 155 if( key == null || key.isEmpty() || fname.endsWith( key.toUpperCase(Locale.JAPAN) ) ) { return true; } 156 } 157 return false; 158 } 159 ); 160 } 161 162 return this; 163 } 164 165 /** 166 * 指定のパスが、指定の文字列と、あいまい条件で一致したパスのみ、有効とします。 167 * 168 * PREFIX*SUFIX 形式で、'*' を前後に、StartsWithとEndsWithに登録します。 169 * '*'は、一つしか使用できません。正規表現ではなく、簡易的なあいまい検索です。 170 * そのため、ファイル名の指定は、一つのみとします。 171 * '*' が存在しない場合は、先頭一致とします。 172 * 指定しない場合(null)は、すべて許可されたことになります。 173 * 終端文字列の判定には、大文字小文字の区別を行いません。 174 * 175 * @og.rev 6.8.1.5 (2017/09/08) ファイル名の'*'の処理の見直し 176 * 177 * @param filename パスの一致のパターン 178 * @return 自分自身 179 * @see #addStartsWith(String...) 180 * @see #addEndsWith(String...) 181 */ 182 public PathMatcherSet addFileName( final String filename ) { 183 if( filename != null && !filename.isEmpty() ) { 184 LOGGER.debug( () -> "addFileName : filename=" + filename ); 185 186 final int ad = filename.indexOf( '*' ); // 分割するためのキーワード 187 188 // 暫定的なチェック:'*' は1箇所しか指定できません。 189 if( ad != filename.lastIndexOf( '*' ) ) { // つまり、2個以上ある。 190 // MSG2005 = 検索条件のファイル名の指定に、'*'を複数使用することは出来ません。 File=[{0}] 191 MsgUtil.errPrintln( "MSG2005" , filename ); 192 return this; 193 } 194 195 if( ad < 0 ) { 196 addStartsWith( filename ); // '*'が無い場合は、先頭一致で判定します。 197 } 198 else if( ad == 0 ) { 199 addEndsWith( filename.substring( 1 ) ); // 先頭が、'*' の場合は、後方一致で判定します。 200 } 201 else if( ad == filename.length()-1 ) { 202 addStartsWith( filename.substring( 0,filename.length()-1 ) ); // 最後が '*'の場合は、先頭一致で判定します。 203 } 204 else { 205 final String prefix = filename.substring( 0,ad ).toUpperCase(Locale.JAPAN); 206 final String sufix = filename.substring( ad+1 ).toUpperCase(Locale.JAPAN); 207 208 pathMchSet.add( 209 path -> { 210 // 大文字小文字の区別を行いません。 211 final String fname = path.getFileName().toString().toUpperCase(Locale.JAPAN); 212 213 if( fname.startsWith( prefix ) && fname.endsWith( sufix ) ) { return true; } // 両方成立が条件 214 return false; 215 } 216 ); 217 218 // addStartsWith( prefix ); // ゼロ文字列の場合は、true になります。 219 // addEndsWith( sufix ); // ゼロ文字列の場合は、true になります。 220 } 221 } 222 223 return this; 224 } 225 226 /** 227 * 指定されたパスがこのマッチャのパターンに一致するかどうかを示します。 228 * 229 * 内部の PathMatcher が、すべて true を返す場合のみ、true を返します。 230 * 未登録の場合は、true が返され、評価されません。 231 * これは、#allMatch( Path ) と同じ結果を返します。 232 * 233 * @param path 照合するパス 234 * @return パスがこのマッチャのパターンに一致した場合にのみtrue 235 * @see #allMatch( Path ) 236 */ 237 @Override 238 public boolean matches( final Path path ) { 239 return allMatch( path ); 240 } 241 242 /** 243 * すべての要素が、条件を満たす場合にのみ、有効となります。 244 * 245 * 内部の PathMatcher が、すべて true を返す場合のみ、true を返します。 246 * 未登録の場合は、true が返され、評価されません。 247 * これは、#matches( Path ) と同じ結果を返します。 248 * 249 * @param path 判定対象の Pathオブジェクト 250 * @return 内部の PathMatcher が、すべて true を返す場合のみ、true 251 * @see #matches( Path ) 252 */ 253 public boolean allMatch( final Path path ) { 254 // stream().allMatch で、Collectionが未登録時も、true ですが、明示的に示しておきます。 255 final boolean flag = pathMchSet.isEmpty() || pathMchSet.stream().allMatch( pMch -> pMch.matches( path ) ); 256 257 if( flag ) { 258 LOGGER.debug( () -> "allMatch : Path=" + path ); 259 } 260 261 return flag; 262 } 263 264 /** 265 * いずれかの要素が、条件を満たす場合に、有効となります。 266 * 267 * 内部の PathMatcher の、いずれかが、 true を返す場合に、true を返します。 268 * 未登録の場合は、true が返され、評価されません。 269 * この動きは、Set#anyMatch(java.util.function.Predicate)とは異なりますので、ご注意ください。 270 * 271 * @param path 判定対象の Pathオブジェクト 272 * @return 内部の PathMatcher の、いずれかが、 true を返す場合に、true 273 */ 274 public boolean anyMatch( final Path path ) { 275 // stream().anyMatch の場合は、Collectionが未登録時は、false が返る為、明示的に処理が必要です。 276 final boolean flag = pathMchSet.isEmpty() || pathMchSet.stream().anyMatch( pMch -> pMch.matches( path ) ); 277 278 if( flag ) { 279 LOGGER.debug( () -> "anyMatch : Path=" + path ); 280 } 281 282 return flag; 283 } 284 285 /** 286 * 一致する要素が、ひとつも存在しない場合に、有効となります。 287 * 288 * 内部の PathMatcher の要素のすべてに、false を返す場合に、true を返します。 289 * 未登録の場合は、true が返され、評価されません。 290 * 291 * @param path 判定対象の Pathオブジェクト 292 * @return 内部の PathMatcher の要素のすべてに、false を返す場合に、true 293 */ 294 public boolean noneMatch( final Path path ) { 295 // stream().noneMatch で、Collectionが未登録時も、true ですが、明示的に示しておきます。 296 final boolean flag = pathMchSet.isEmpty() || pathMchSet.stream().noneMatch( pMch -> pMch.matches( path ) ); 297 298 if( flag ) { 299 LOGGER.debug( () -> "noneMatch : Path=" + path ); 300 } 301 302 return flag; 303 } 304 305// /** main メソッドから呼ばれる ヘルプメッセージです。 {@value} */ 306// public static final String USAGE = "Usage: java jp.euromap.eu63.util.PathMatcherSet [dir] [-start ・・・・] [-end ・・・・]" ; 307// 308// /** 309// * 引数に監視対象のフォルダをフィルターします。 310// * 311// * {@value #USAGE} 312// * 313// * @param args コマンド引数配列 314// */ 315// public static void main( final String[] args ) { 316// // ********** 【整合性チェック】 ********** 317// if( args.length < 1 ) { 318// System.out.println( USAGE ); 319// return; 320// } 321// 322// // ********** 【引数定義】 ********** 323// Path sPath = new java.io.File( "." ).toPath(); // スキャンパス の初期値 324// 325// String startsWith = null; // ファイル先頭文字列の一致キー 326// String endsWith = null; // ファイル終端文字列の一致キー 327// 328// // ********** 【引数処理】 ********** 329// for( int i=0; i<args.length; i++ ) { 330// final String arg = args[i]; 331// 332// if( "-help" .equalsIgnoreCase( arg ) ) { System.out.println( USAGE ); return ; } 333// else if( "-start".equalsIgnoreCase( arg ) ) { startsWith = args[++i]; } // 記号を見つけたら、次の引数をセットします。 334// else if( "-end" .equalsIgnoreCase( arg ) ) { endsWith = args[++i]; } // 記号を見つけたら、次の引数をセットします。 335// else { 336// sPath = new java.io.File( arg ).toPath(); 337// } 338// } 339// 340// // ********** 【本体処理】 ********** 341// final PathMatcherSet pmSet = new PathMatcherSet(); 342// pmSet.addStartsWith( startsWith ); 343// pmSet.addEndsWith( endsWith ); 344// 345// try { 346// java.nio.file.Files.walk( sPath ) 347// .filter( path -> pmSet.allMatch( path ) ) 348// .forEach( path -> System.out.println( path ) ); 349// } 350// catch( final java.io.IOException ex ) { 351// ex.printStackTrace(); 352// } 353// } 354}