您的位置:首页 > 编程语言 > Java开发

17 FileNameMatcher

2016-08-07 21:20 232 查看

前言

这个工具主要是用于校验给定的字符串, 是否匹配给定的pattern[不是正则, 比正则简单一点], 这个工具 最初我是准备使用在另外的一个工具上面的, 这个工具主要的作用在于将给定的项目打成jar包, 每次 配置一下路径, 然后 run 一下就能够生成给定的包了, 在该工具中FileNameMatcher主要充当的角色在于匹配各级路径

然后 最近的时候, 有用到了这个FileNameMatcher, 主要是最近的和一个朋友做的项目中又刚好需要用到这个

就把这个工具分享一下吧, 因为个人的水平有限,必然可能出现一些bug, 所以请大家指出!

问题描述

现在给定一个fileName, 和一个pattern, fileName为需要匹配的文件名, pattern为匹配的规则, pattern中允许使用通配符, ‘?’ 可以表示一个任意字符, ‘*’ 表示多个任意字符

然后 现在的程序需要做的事情, 就是 实现具体的逻辑

思路

思路 : 请详见代码吧, 注释 写的算是比较详细

难点 : 哈哈哈 难点便是思路

参考代码

/**
* file name : FileNameMatcher.java
* created at : 5:02:38 PM Nov 29, 2015
* created by 970655147
*/

package com.hx.log.util;

// FileNameMatcher
public class FileNameMatcher {

// 所支持的通配符, '?' 可以表示一个任意字符, '*' 表示任意个任意字符[具体的匹配由isGreedy进行约束]
// 因为普通场景下面, 没有'?', '*' 的文件名, 因此这里便没有写'?', '*'本身的表示[如果 要写的话, 模拟转义吧][在搜索wildCard的时候, 不能搜索转义的'?', '*', 在匹配的时候, 将'\?', '\*'替换为真实的字符串表示的'?', '*' ]
// 一个pattern中各个子pattern的分隔符, 各个子pattern之间的关系为 "短路或"
// 这里 并没有约定"短路与"的场景[优先级问题 会导出很多问题], 请自行使用match进行实现吧
public static final Character MATCH_ONE = '?';
public static final Character MATCH_MULTI = '*';
public static final char[] WILDCARDS = new char[]{MATCH_ONE, MATCH_MULTI };
public static final int MATCH_ONE_IDX = 0;
public static final int MATCH_MULTI_IDX = 1;

public static final String PATTERN_SEP = "\\|";

/**
* @Name: match
* @Description: 判断给定的fileName是否匹配给定的pattern
* @param fileName
* @param pattern
* @param isGreedy 表示是否采用贪婪匹配的模式[也就是通配符'*'是否贪婪]
* @return
* @Create at 2016年8月6日 下午3:15:28 by '970655147'
*/
public static boolean match(String fileName, String pattern, boolean isGreedy) {
// add params' verify at 2016.08.28
if (fileName == null && pattern == null) {
return true;
}
if (fileName == null || pattern == null) {
return false;
}

String[] subPatterns = pattern.split(PATTERN_SEP);
for(int i=0; i<subPatterns.length; i++) {
if(match0(fileName, subPatterns[i], isGreedy) ) {
return true;
}
}

return false;
}

/**
* @Name: match
* @Description: isGreedy 默认为false
* @param fileName
* @param pattern
* @return
* @Create at 2016年8月6日 下午3:16:51 by '970655147'
*/
public static boolean match(String fileName, String pattern) {
return match(fileName, pattern, false);
}

/**
* @Name: equalsIgnoreCase
* @Description: 判定str01, str02是否相等
* @param str01
* @param str02
* @return
* @Create at 2016年8月6日 下午3:20:51 by '970655147'
*/
public static boolean equalsInRange(String str01, int start01, String str02, int start02, int len) {
//      return str01.equalsIgnoreCase(str02);
for(int i=0; i<len; i++) {
if(str01.charAt(start01+i) != str02.charAt(start02+i) ) {
return false;
}
}
return true;
}

/**
* @Name: isEmpty
* @Description: 判定给定的字符串是否为空字符串
* @param str
* @return
* @Create at 2016年8月6日 下午3:21:01 by '970655147'
*/
public static boolean isEmpty(String str) {
return (str == null) || "".equals(str.trim());
}

/**
* @Name: match0
* @Description: match的核心业务方法
* @param fileName
* @param pattern
* @param isGreedy
* @return
* @Create at 2016年8月6日 下午3:28:45 by '970655147'
*/
private static boolean match0(String fileName, String pattern, boolean isGreedy) {
pattern = preparePattern(pattern);

int[] nextWildCards = newWildCards();
initWildCards(pattern, nextWildCards);
WildCardAndIdx wildCardAndIdx = new WildCardAndIdx();

int fileNameIdx = 0, patternIdx = 0;
while(hasNextWildCards(nextWildCards) ) {
// 如果没有匹配的pattern的字符串, 直接返回false
if((fileNameIdx < 0) || (fileNameIdx >= fileName.length()) ) {
return false;
}

nextWildCard(pattern, nextWildCards, wildCardAndIdx);
int len = wildCardAndIdx.pos - patternIdx;
if(len != 0) {
// 如果fileName不够长, 或者到下一个通配符之间的字符串和pattern不匹配, 直接返回false
if(fileNameIdx+len >= fileName.length()) {
return false;
}
if(! equalsInRange(fileName, fileNameIdx, pattern, patternIdx, len) ) {
return false;
}
}

// 处理各个通配符的场景
switch(wildCardAndIdx.wildCardIdx) {
// 对于'?', 索引增加 : 精确匹配的字符串的长度+1
case MATCH_ONE_IDX :
{
fileNameIdx += (len + 1);
patternIdx += (len + 1);
break ;
}
// 对于'*', 分为贪婪 和非贪婪进行处理
case MATCH_MULTI_IDX :
{
int curPos = wildCardAndIdx.pos;
peekNextWildCard(fileName, nextWildCards, wildCardAndIdx);
String strBetweenNextWildCard = null;
// 如果下一个字符也为通配符, 则strBetweenNextWildCard为"", isEmpty(strBetweenNextWildCard), 直接返回了true, 以及后面的fileNameIdx的更新造成影响  构成错误
// 所以 需要预处理pattern, 防止类似的情况发生 "**", "*?"
if(wildCardAndIdx.pos != -1) {
strBetweenNextWildCard = pattern.substring(curPos+1, wildCardAndIdx.pos);
} else {
strBetweenNextWildCard = pattern.substring(curPos+1);
}
// 处理pattern的最后一个字符为*的场景
if(isEmpty(strBetweenNextWildCard) ) {
return true;
}

if(isGreedy) {
int prevFileNameIdx = fileNameIdx;
fileNameIdx = fileName.lastIndexOf(strBetweenNextWildCard);
// have no match with 'strBetweenNextWildCard' after fileNameIdx, cut off for next loop
// updated at 2016.08.29
if(fileNameIdx <= prevFileNameIdx) {
fileNameIdx = -1;
}
} else {
// if have no match with 'strBetweenNextWildCard' after fileNameIdx, 'fileNameIdx' will be '-1'
fileNameIdx = fileName.indexOf(strBetweenNextWildCard, fileNameIdx+1);
}
patternIdx += (len + 1);
break ;
}
// Other ??  can't be there in normal case
default :
throw new RuntimeException("unsupported wildcard !");
}
}

return equalsInRange(fileName, fileNameIdx, pattern, patternIdx, Math.min(fileName.length()-fileNameIdx, pattern.length()-patternIdx) );
}

// 预处理pattern
// 1. 防止类似的情况发生 "**", "*?"
private static String preparePattern(String pattern) {
StringBuilder sb = new StringBuilder();
for(int i=0, len=pattern.length(); i<len; i++) {
char ch = pattern.charAt(i);
sb.append(ch);
// '*XX'
if(ch == MATCH_MULTI) {
int nextI = i+1;
while((nextI < len) && (Tools.contains(WILDCARDS, pattern.charAt(nextI))) ) {
nextI ++;
}
i = nextI - 1;
}
}

return sb.toString();
}
// 创建一个通配符的索引的数组
private static int[] newWildCards() {
return new int[WILDCARDS.length];
}
// 初始化通配符的位置
private static void initWildCards(String pattern, int[] nextWildCards) {
for(int i=0; i<nextWildCards.length; i++) {
nextWildCards[i] = pattern.indexOf(WILDCARDS[i] );
}
}
// 判断是否还有下一个通配符
private static boolean hasNextWildCards(int[] nextWildCards) {
for(int i=0; i<nextWildCards.length; i++) {
if(nextWildCards[i] >= 0) {
return true;
}
}

return false;
}
// 获取pattern中下一个通配符的位置, 并更新该通配符的下一个位置
private static void nextWildCard(String pattern, int[] nextWildCards, WildCardAndIdx wildCardAndIdx) {
peekNextWildCard(pattern, nextWildCards, wildCardAndIdx);

nextWildCards[wildCardAndIdx.wildCardIdx] = pattern.indexOf(WILDCARDS[wildCardAndIdx.wildCardIdx], wildCardAndIdx.pos+1);
}
// 获取pattern中下一个通配符的数据, 放到wildCardAndIdx中
private static void peekNextWildCard(String fileName, int[] nextWildCards, WildCardAndIdx wildCardAndIdx) {
int next = getMinIdx(nextWildCards);
wildCardAndIdx.wildCardIdx = next;

if(next != -1) {
wildCardAndIdx.pos = nextWildCards[next];
} else {
wildCardAndIdx.pos = -1;
}
}
// 获取pattern中下一个的通配符的索引
private static int getMinIdx(int[] nextWildCards) {
int min = Integer.MAX_VALUE, idx = -1;
for(int i=0; i<nextWildCards.length; i++) {
// '>= 0' for check valid
if((nextWildCards[i] >= 0) && (nextWildCards[i] < min) ) {
idx = i;
min = nextWildCards[i];
}
}

return idx;
}

// 通配符的索引, 以及其当前位置
static class WildCardAndIdx {
public int wildCardIdx;
public int pos;

public String toString() {
return WILDCARDS[wildCardIdx] + " -> " + pos;
}
}

}


======================= add at 2016.08.28 =======================

今天 无意间在一篇帖子中看到了原来apache的commons.io包下面有一个FilenameUtils[org.apache.commons.io.FilenameUtils]实现了这样的功能[还有很多其他的功能]

然后 对比了一下, 发现对于这样的工具类的相关工具方法, 我又忘记了对于参数的校验逻辑…[加上]

其次简单的比较了一下逻辑[因为没有详细看, 因此 也不敢说一定怎么怎么样]

1 对于”wildCard”的处理逻辑, FilenameUtils是首先根据给定的通配符进行分词[token], 而我这里是在校验过程中去寻找下一个wildCard

2 对于”*”的处理, FilenameUtils是直接让fileName的索引跳到末尾[匹配了fileName剩余的所有字符], 而我这里是尽量匹配”*”之后的部分 和fileName之后的数据[greedy or not]

其他的部分还有待于仔细阅读, 这里就不说了

======================= add at 2016.08.29 =======================

1 发现了一个更新fileNameIdx的问题, 并解决

if(isGreedy) {
fileNameIdx = fileName.lastIndexOf(strBetweenNextWildCard);
} else {
fileNameIdx = fileName.indexOf(strBetweenNextWildCard, fileNameIdx+1);
}


||

\ /

if(isGreedy) {
int prevFileNameIdx = fileNameIdx;
fileNameIdx = fileName.lastIndexOf(strBetweenNextWildCard);
// have no match with 'strBetweenNextWildCard' after fileNameIdx, cut off for next loop
// updated at 2016.08.29
if(fileNameIdx <= prevFileNameIdx) {
fileNameIdx = -1;
}
} else {
// if have no match with 'strBetweenNextWildCard' after fileNameIdx, 'fileNameIdx' will be '-1'
fileNameIdx = fileName.indexOf(strBetweenNextWildCard, fileNameIdx+1);
}


2 更新了关于一个区间的字符串的比较

public static boolean equalsIgnoreCase(String str01,  String str02) {
//      return str01.equalsIgnoreCase(str02);
for(int i=0; i<len; i++) {
if(str01.charAt(start01+i) != str02.charAt(start02+i) ) {
return false;
}
}
return true;
}


||

\ /

public static boolean equalsInRange(String str01, int start01, String str02, int start02, int len) {
for(int i=0; i<len; i++) {
if(str01.charAt(start01+i) != str02.charAt(start02+i) ) {
return false;
}
}
return true;
}


======================= add at 2016.09.09 =======================

今天 在看”Files. newDirectoryStream(Path dir, String glob)” 的时候无意间看到了一个glob表达式, 然后之后 了解了一下, 我这里的FileNameMatcher, 也算是glob表达式的功能的一个子集吧

glob表达式参考 : http://blog.csdn.net/chszs/article/details/46482571

效果截图



总结

小工具, 扯扯淡, 正则比这个功能强大多啦 !

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 校验