blob: f6f19c0abd868f8ef5ea42be7b641b81094fcd0b [file] [log] [blame]
/*
* Copyright 2006 Sascha Weinreuter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.intellij.plugins.intelliLang.util;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.StringPattern;
import com.intellij.util.Function;
import com.intellij.util.containers.WeakHashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.List;
/**
* Simple abstraction of a String matcher that can be based on simple and vastly more
* efficient String comparisons than doing a full pattern match.
*/
public abstract class StringMatcher<T> {
protected final T myTarget;
public static final StringMatcher NONE = new Any("<none>", false);
public static final StringMatcher ANY = new Any("", true);
public static final StringMatcher ANY_PATTERN = new Any(".*", true);
protected StringMatcher(T target) {
myTarget = target;
}
private abstract static class Simple extends StringMatcher<String> {
Simple(String target) {
super(target);
}
public String getPattern() {
return myTarget;
}
}
private static final class Pattern extends StringMatcher<java.util.regex.Pattern> {
Pattern(String target) {
super(java.util.regex.Pattern.compile(target));
}
public boolean matches(String what) {
return myTarget.matcher(StringPattern.newBombedCharSequence(what)).matches();
}
public String getPattern() {
return myTarget.pattern();
}
}
private final static class Equals extends Simple {
Equals(String target) {
super(target);
}
public boolean matches(String what) {
return myTarget.equals(what);
}
}
private final static class StartsWith extends Simple {
StartsWith(String target) {
super(target);
}
public boolean matches(String what) {
return what.startsWith(myTarget);
}
public String getPattern() {
return super.getPattern() + ".*";
}
}
private final static class EndsWith extends Simple {
EndsWith(String target) {
super(target);
}
public boolean matches(String what) {
return what.endsWith(myTarget);
}
public String getPattern() {
return ".*" + super.getPattern();
}
}
private final static class Contains extends Simple {
Contains(String target) {
super(target);
}
public boolean matches(String what) {
return what.contains(myTarget);
}
public String getPattern() {
return ".*" + super.getPattern() + ".*";
}
}
private static final class Any extends Simple {
private final boolean myMatches;
Any(String target, boolean matches) {
super(target);
myMatches = matches;
}
public boolean matches(String what) {
return myMatches;
}
}
private static final class IgnoreCase extends StringMatcher<StringMatcher> {
IgnoreCase(StringMatcher target) {
super(target);
}
public boolean matches(String what) {
return myTarget.matches(what.toLowerCase());
}
public String getPattern() {
return "(?i)" + myTarget.getPattern();
}
}
private static final class Cache extends StringMatcher<StringMatcher> {
private final WeakHashMap<String, Boolean> myCache = new WeakHashMap<String, Boolean>();
Cache(StringMatcher target) {
super(target);
}
public String getPattern() {
return myTarget.getPattern();
}
public synchronized boolean matches(String what) {
final Boolean o = myCache.get(what);
if (o != null) {
return o;
}
final boolean b = myTarget.matches(what);
myCache.put(what, b);
return b;
}
}
public static final class MatcherSet extends StringMatcher<Set<StringMatcher>> {
private final String myPattern;
protected MatcherSet(Set<StringMatcher> target) {
super(target);
myPattern = StringUtil.join(target, new Function<StringMatcher, String>() {
public String fun(StringMatcher s) {
return s.getPattern();
}
}, "|");
}
public static StringMatcher create(Set<StringMatcher> matchers) {
final MatcherSet m = new MatcherSet(matchers);
return matchers.size() > 3 ? new Cache(m) : m;
}
public boolean matches(String what) {
for (StringMatcher matcher : myTarget) {
if (matcher.matches(what)) {
return true;
}
}
return false;
}
public String getPattern() {
return myPattern;
}
}
public abstract boolean matches(String what);
public abstract String getPattern();
public static StringMatcher create(String target) {
if (target.length() == 0) return ANY;
if (target.equals(".*")) return ANY_PATTERN;
if (target.equals(NONE.getPattern())) return NONE;
final List<String> branches = StringUtil.split(target,"|");
final Set<StringMatcher> matchers = new LinkedHashSet<StringMatcher>();
for (String branch : branches) {
boolean startsWith = false;
boolean endsWith = false;
boolean ignoreCase = false;
// this assumes the regex is syntactically correct
if (branch.startsWith("(?i)")) {
ignoreCase = true;
branch = branch.substring(2).toLowerCase();
}
if (branch.endsWith(".*")) {
startsWith = true;
branch = branch.substring(0, branch.length() - 2);
}
if (branch.startsWith(".*")) {
endsWith = true;
branch = branch.substring(2);
}
final boolean m = analyseBranch(branch);
if (!m) {
try {
return new Cache(new Pattern(target));
}
catch (Exception e) {
return new Any(target, false);
}
}
final StringMatcher matcher;
if (startsWith && endsWith) {
matcher = new Contains(branch);
}
else if (startsWith) {
matcher = new StartsWith(branch);
}
else if (endsWith) {
matcher = new EndsWith(branch);
}
else {
matcher = new Equals(branch);
}
matchers.add(ignoreCase ? new IgnoreCase(matcher) : matcher);
}
return matchers.size() == 1 ? matchers.iterator().next() : MatcherSet.create(matchers);
}
private static boolean analyseBranch(String target) {
for (int i = 0; i < target.length(); i++) {
final char c = target.charAt(i);
if (c != '_' && c != '-' && !Character.isLetterOrDigit(c)) {
return false;
}
}
return true;
}
public int hashCode() {
return myTarget.hashCode();
}
@SuppressWarnings({"SimplifiableIfStatement"})
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj.getClass() != getClass()) return false;
return ((StringMatcher)obj).myTarget.equals(myTarget);
}
}