/**
* Copyright 2007 Google Inc.
*
* 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 com.tonicsystems.jarjar.transform.config;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
public class PatternUtils {
private PatternUtils() {
}
private static final Pattern dstar = Pattern.compile("\\*\\*");
private static final Pattern star = Pattern.compile("\\*");
private static final Pattern estar = Pattern.compile("\\+\\??\\)\\Z");
@Nonnull
private static String replaceAllLiteral(@Nonnull String value, @Nonnull Pattern pattern, @Nonnull String replace) {
return pattern.matcher(value).replaceAll(Matcher.quoteReplacement(replace));
}
@Nonnull
public static Pattern newPattern(@Nonnull String pattern) {
if (pattern.equals("**"))
throw new IllegalArgumentException("'**' is not a valid pattern");
if (!isPossibleQualifiedName(pattern, "/*"))
throw new IllegalArgumentException("Not a valid package pattern: " + pattern);
if (pattern.indexOf("***") >= 0)
throw new IllegalArgumentException("The sequence '***' is invalid in a package pattern");
String regex = pattern;
regex = replaceAllLiteral(regex, dstar, "(.+?)"); // One wildcard test requires the argument to be allowably empty.
regex = replaceAllLiteral(regex, star, "([^/]+)");
regex = replaceAllLiteral(regex, estar, "*\\??)"); // Although we replaced with + above, we mean *
return Pattern.compile("\\A" + regex + "\\Z");
// this.count = this.pattern.matcher("foo").groupCount();
}
private static enum State {
NORMAL, ESCAPE;
}
@Nonnull
public static List<Object> newReplace(@Nonnull Pattern pattern, @Nonnull String result) {
List<Object> parts = new ArrayList<Object>(16);
// TODO: check for illegal characters
int max = 0;
State state = State.NORMAL;
for (int i = 0, mark = 0, len = result.length(); i < len + 1; i++) {
char ch = (i == len) ? '@' : result.charAt(i);
switch (state) {
case NORMAL:
if (ch == '@') {
parts.add(result.substring(mark, i).replace('.', '/'));
mark = i + 1;
state = State.ESCAPE;
}
break;
case ESCAPE:
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
default:
if (i == mark)
throw new IllegalArgumentException("Backslash not followed by a digit");
int n = Integer.parseInt(result.substring(mark, i));
if (n > max)
max = n;
parts.add(Integer.valueOf(n));
mark = i--;
state = State.NORMAL;
break;
}
break;
}
}
int count = pattern.matcher("foo").groupCount();
if (count < max)
throw new IllegalArgumentException("Result includes impossible placeholder \"@" + max + "\": " + result);
// System.err.println(this);
return parts;
}
public static String replace(@Nonnull AbstractPattern pattern, @Nonnull List<Object> replace, String value) {
Matcher matcher = pattern.getMatcher(value);
if (matcher == null)
return null;
StringBuilder sb = new StringBuilder();
for (Object part : replace) {
if (part instanceof String)
sb.append((String) part);
else
sb.append(matcher.group((Integer) part));
}
return sb.toString();
}
public static final String PACKAGE_INFO = "package-info";
/* pp */ static boolean isPossibleQualifiedName(@Nonnull String value, @Nonnull String extraAllowedCharacters) {
// package-info violates the spec for Java Identifiers.
// Nevertheless, expressions that end with this string are still legal.
// See 7.4.1.1 of the Java language spec for discussion.
if (value.endsWith(PACKAGE_INFO)) {
value = value.substring(0, value.length() - PACKAGE_INFO.length());
}
for (int i = 0, len = value.length(); i < len; i++) {
char c = value.charAt(i);
if (Character.isJavaIdentifierPart(c))
continue;
if (extraAllowedCharacters.indexOf(c) >= 0)
continue;
return false;
}
return true;
}
/**
* Copies the given {@link Iterable} into a new {@link List}.
*
* @param <T> The free parameter for the element type.
* @param in The Iterable to copy.
* @return A new, mutable {@link ArrayList}.
*/
@Nonnull
public static <T extends AbstractPattern> List<T> toList(@Nonnull Iterable<? extends T> in) {
List<T> out = new ArrayList<T>();
for (T i : in)
out.add(i);
return out;
}
// Adapted from http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns
@Nonnull
public static String convertGlobToRegEx(@Nonnull String line) {
line = line.trim();
int strLen = line.length();
StringBuilder sb = new StringBuilder(strLen);
// Remove beginning and ending * globs because they're useless
if (line.startsWith("*")) {
line = line.substring(1);
strLen--;
}
if (line.endsWith("*")) {
line = line.substring(0, strLen - 1);
strLen--;
}
boolean escaping = false;
int inCurlies = 0;
CHAR:
for (char currentChar : line.toCharArray()) {
switch (currentChar) {
case '*':
if (escaping)
sb.append("\\*");
else
sb.append(".*");
break;
case '?':
if (escaping)
sb.append("\\?");
else
sb.append('.');
break;
case '.':
case '(':
case ')':
case '+':
case '|':
case '^':
case '$':
case '@':
case '%':
sb.append('\\');
sb.append(currentChar);
break;
case '\\':
if (escaping)
sb.append("\\\\");
else {
escaping = true;
continue CHAR;
}
break;
case '{':
if (escaping)
sb.append("\\{");
else {
sb.append('(');
inCurlies++;
}
break;
case '}':
if (escaping)
sb.append("\\}");
else if (inCurlies > 0) {
sb.append(')');
inCurlies--;
} else
sb.append("}");
break;
case ',':
if (escaping)
sb.append("\\,");
else if (inCurlies > 0)
sb.append('|');
else
sb.append(",");
break;
default:
sb.append(currentChar);
break;
}
escaping = false;
}
return sb.toString();
}
}