1 /** 2 * Copyright 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.tonicsystems.jarjar; 18 19 import java.util.regex.Matcher; 20 import java.util.regex.Pattern; 21 import java.util.ArrayList; 22 import java.util.Arrays; 23 24 class Wildcard 25 { 26 private static Pattern dstar = Pattern.compile("\\*\\*"); 27 private static Pattern star = Pattern.compile("\\*"); 28 private static Pattern estar = Pattern.compile("\\+\\??\\)\\Z"); 29 private static Pattern dollar = Pattern.compile("\\$"); 30 31 private final Pattern pattern; 32 private final int count; 33 private final ArrayList<Object> parts = new ArrayList<Object>(16); // kept for debugging 34 private final String[] strings; 35 private final int[] refs; 36 Wildcard(String pattern, String result)37 public Wildcard(String pattern, String result) { 38 if (pattern.equals("**")) 39 throw new IllegalArgumentException("'**' is not a valid pattern"); 40 if (!checkIdentifierChars(pattern, "/*")) 41 throw new IllegalArgumentException("Not a valid package pattern: " + pattern); 42 if (pattern.indexOf("***") >= 0) 43 throw new IllegalArgumentException("The sequence '***' is invalid in a package pattern"); 44 45 String regex = pattern; 46 regex = replaceAllLiteral(dstar, regex, "(.+?)"); 47 regex = replaceAllLiteral(star, regex, "([^/]+)"); 48 regex = replaceAllLiteral(estar, regex, "*)"); 49 regex = replaceAllLiteral(dollar, regex, "\\$"); 50 this.pattern = Pattern.compile("\\A" + regex + "\\Z"); 51 this.count = this.pattern.matcher("foo").groupCount(); 52 53 // TODO: check for illegal characters 54 char[] chars = result.toCharArray(); 55 int max = 0; 56 for (int i = 0, mark = 0, state = 0, len = chars.length; i < len + 1; i++) { 57 char ch = (i == len) ? '@' : chars[i]; 58 if (state == 0) { 59 if (ch == '@') { 60 parts.add(new String(chars, mark, i - mark)); 61 mark = i + 1; 62 state = 1; 63 } 64 } else { 65 switch (ch) { 66 case '0': case '1': case '2': case '3': case '4': 67 case '5': case '6': case '7': case '8': case '9': 68 break; 69 default: 70 if (i == mark) 71 throw new IllegalArgumentException("Backslash not followed by a digit"); 72 int n = Integer.parseInt(new String(chars, mark, i - mark)); 73 if (n > max) 74 max = n; 75 parts.add(new Integer(n)); 76 mark = i--; 77 state = 0; 78 } 79 } 80 } 81 int size = parts.size(); 82 strings = new String[size]; 83 refs = new int[size]; 84 Arrays.fill(refs, -1); 85 for (int i = 0; i < size; i++) { 86 Object v = parts.get(i); 87 if (v instanceof String) { 88 strings[i] = ((String)v).replace('.', '/'); 89 } else { 90 refs[i] = ((Integer)v).intValue(); 91 } 92 } 93 if (count < max) 94 throw new IllegalArgumentException("Result includes impossible placeholder \"@" + max + "\": " + result); 95 // System.err.println(this); 96 } 97 matches(String value)98 public boolean matches(String value) { 99 return getMatcher(value) != null; 100 } 101 replace(String value)102 public String replace(String value) { 103 Matcher matcher = getMatcher(value); 104 if (matcher != null) { 105 StringBuilder sb = new StringBuilder(); 106 for (int i = 0; i < strings.length; i++) 107 sb.append((refs[i] >= 0) ? matcher.group(refs[i]) : strings[i]); 108 return sb.toString(); 109 } 110 return null; 111 } 112 getMatcher(String value)113 private Matcher getMatcher(String value) { 114 Matcher matcher = pattern.matcher(value); 115 if (matcher.matches() && checkIdentifierChars(value, "/")) 116 return matcher; 117 return null; 118 } 119 checkIdentifierChars(String expr, String extra)120 private static boolean checkIdentifierChars(String expr, String extra) { 121 // package-info violates the spec for Java Identifiers. 122 // Nevertheless, expressions that end with this string are still legal. 123 // See 7.4.1.1 of the Java language spec for discussion. 124 if (expr.endsWith("package-info")) { 125 expr = expr.substring(0, expr.length() - "package-info".length()); 126 } 127 for (int i = 0, len = expr.length(); i < len; i++) { 128 char c = expr.charAt(i); 129 if (extra.indexOf(c) >= 0) 130 continue; 131 if (!Character.isJavaIdentifierPart(c)) 132 return false; 133 } 134 return true; 135 } 136 replaceAllLiteral(Pattern pattern, String value, String replace)137 private static String replaceAllLiteral(Pattern pattern, String value, String replace) { 138 replace = replace.replaceAll("([$\\\\])", "\\\\$0"); 139 return pattern.matcher(value).replaceAll(replace); 140 } 141 toString()142 public String toString() { 143 return "Wildcard{pattern=" + pattern + ",parts=" + parts + "}"; 144 } 145 } 146