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}