001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.pojotools; 014 015import static org.apache.juneau.internal.StringUtils.*; 016 017import java.util.*; 018import java.util.regex.*; 019 020import org.apache.juneau.*; 021import org.apache.juneau.internal.*; 022 023/** 024 * TODO 025 * 026 */ 027public class StringMatcherFactory extends MatcherFactory { 028 029 /** 030 * Default reusable matcher. 031 */ 032 public static final StringMatcherFactory DEFAULT = new StringMatcherFactory(); 033 034 @Override 035 public boolean canMatch(ClassMeta<?> cm) { 036 return true; 037 } 038 039 @Override 040 public Matcher create(String pattern) { 041 return new StringMatcher(pattern); 042 } 043 044 /** 045 * A construct representing a single search pattern. 046 */ 047 private static class StringMatcher extends Matcher { 048 private String pattern; 049 private static final AsciiSet 050 META_CHARS = AsciiSet.create("*?'\""), 051 SQ_CHAR = AsciiSet.create("'"), 052 DQ_CHAR = AsciiSet.create("\""), 053 REGEX_CHARS = AsciiSet.create("+\\[]{}()^$."); 054 055 Pattern[] orPatterns, andPatterns, notPatterns; 056 057 public StringMatcher(String searchPattern) { 058 059 this.pattern = searchPattern.trim(); 060 List<Pattern> ors = new LinkedList<>(); 061 List<Pattern> ands = new LinkedList<>(); 062 List<Pattern> nots = new LinkedList<>(); 063 064 for (String s : splitQuoted(pattern, true)) { 065 char c0 = s.charAt(0), c9 = s.charAt(s.length()-1); 066 067 if (c0 == '/' && c9 == '/' && s.length() > 1) { 068 ands.add(Pattern.compile(strip(s))); 069 } else { 070 char prefix = '^'; 071 boolean ignoreCase = false; 072 if (s.length() > 1 && (c0 == '^' || c0 == '+' || c0 == '-')) { 073 prefix = c0; 074 s = s.substring(1); 075 c0 = s.charAt(0); 076 } 077 078 if (c0 == '\'') { 079 s = unEscapeChars(strip(s), SQ_CHAR); 080 ignoreCase = true; 081 } else if (c0 == '"') { 082 s = unEscapeChars(strip(s), DQ_CHAR); 083 } 084 085 if (REGEX_CHARS.contains(s) || META_CHARS.contains(s)) { 086 StringBuilder sb = new StringBuilder(); 087 boolean isInEscape = false; 088 for (int i = 0; i < s.length(); i++) { 089 char c = s.charAt(i); 090 if (isInEscape) { 091 if (c == '?' || c == '*' || c == '\\') 092 sb.append('\\').append(c); 093 else 094 sb.append(c); 095 isInEscape = false; 096 } else { 097 if (c == '\\') 098 isInEscape = true; 099 else if (c == '?') 100 sb.append(".?"); 101 else if (c == '*') 102 sb.append(".*"); 103 else if (REGEX_CHARS.contains(c)) 104 sb.append("\\").append(c); 105 else 106 sb.append(c); 107 } 108 } 109 s = sb.toString(); 110 } 111 112 113 int flags = Pattern.DOTALL; 114 if (ignoreCase) 115 flags |= Pattern.CASE_INSENSITIVE; 116 117 Pattern p = Pattern.compile(s, flags); 118 119 if (prefix == '-') 120 nots.add(p); 121 else if (prefix == '+') 122 ands.add(p); 123 else 124 ors.add(p); 125 } 126 } 127 orPatterns = ors.toArray(new Pattern[ors.size()]); 128 andPatterns = ands.toArray(new Pattern[ands.size()]); 129 notPatterns = nots.toArray(new Pattern[nots.size()]); 130 } 131 132 @Override 133 public boolean matches(ClassMeta<?> cm, Object o) { 134 String s = (String)o; 135 for (int i = 0; i < andPatterns.length; i++) 136 if (! andPatterns[i].matcher(s).matches()) 137 return false; 138 for (int i = 0; i < notPatterns.length; i++) 139 if (notPatterns[i].matcher(s).matches()) 140 return false; 141 for (int i = 0; i < orPatterns.length; i++) 142 if (orPatterns[i].matcher(s).matches()) 143 return true; 144 return orPatterns.length == 0; 145 } 146 147 @Override 148 public String toString() { 149 return pattern; 150 } 151 } 152}