1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
30 private final Pattern pattern;
31 private final int count;
32 private final ArrayList<Object> parts = new ArrayList<Object>(16);
33 private final String[] strings;
34 private final int[] refs;
35
36 public Wildcard(String pattern, String result) {
37 if (pattern.equals("**"))
38 throw new IllegalArgumentException("'**' is not a valid pattern");
39 if (!checkIdentifierChars(pattern, "/*"))
40 throw new IllegalArgumentException("Not a valid package pattern: " + pattern);
41 if (pattern.indexOf("***") >= 0)
42 throw new IllegalArgumentException("The sequence '***' is invalid in a package pattern");
43
44 String regex = pattern;
45 regex = replaceAllLiteral(dstar, regex, "(.+?)");
46 regex = replaceAllLiteral(star, regex, "([^/]+)");
47 regex = replaceAllLiteral(estar, regex, "*)");
48 this.pattern = Pattern.compile("\\A" + regex + "\\Z");
49 this.count = this.pattern.matcher("foo").groupCount();
50
51
52 char[] chars = result.toCharArray();
53 int max = 0;
54 for (int i = 0, mark = 0, state = 0, len = chars.length; i < len + 1; i++) {
55 char ch = (i == len) ? '@' : chars[i];
56 if (state == 0) {
57 if (ch == '@') {
58 parts.add(new String(chars, mark, i - mark));
59 mark = i + 1;
60 state = 1;
61 }
62 } else {
63 switch (ch) {
64 case '0': case '1': case '2': case '3': case '4':
65 case '5': case '6': case '7': case '8': case '9':
66 break;
67 default:
68 if (i == mark)
69 throw new IllegalArgumentException("Backslash not followed by a digit");
70 int n = Integer.parseInt(new String(chars, mark, i - mark));
71 if (n > max)
72 max = n;
73 parts.add(new Integer(n));
74 mark = i--;
75 state = 0;
76 }
77 }
78 }
79 int size = parts.size();
80 strings = new String[size];
81 refs = new int[size];
82 Arrays.fill(refs, -1);
83 for (int i = 0; i < size; i++) {
84 Object v = parts.get(i);
85 if (v instanceof String) {
86 strings[i] = ((String)v).replace('.', '/');
87 } else {
88 refs[i] = ((Integer)v).intValue();
89 }
90 }
91 if (count < max)
92 throw new IllegalArgumentException("Result includes impossible placeholder \"@" + max + "\": " + result);
93
94 }
95
96 public boolean matches(String value) {
97 return getMatcher(value) != null;
98 }
99
100 public String replace(String value) {
101 Matcher matcher = getMatcher(value);
102 if (matcher != null) {
103 StringBuilder sb = new StringBuilder();
104 for (int i = 0; i < strings.length; i++)
105 sb.append((refs[i] >= 0) ? matcher.group(refs[i]) : strings[i]);
106 return sb.toString();
107 }
108 return null;
109 }
110
111 private Matcher getMatcher(String value) {
112 Matcher matcher = pattern.matcher(value);
113 if (matcher.matches() && checkIdentifierChars(value, "/"))
114 return matcher;
115 return null;
116 }
117
118 private static boolean checkIdentifierChars(String expr, String extra) {
119
120
121
122 if (expr.endsWith("package-info")) {
123 expr = expr.substring(0, expr.length() - "package-info".length());
124 }
125 for (int i = 0, len = expr.length(); i < len; i++) {
126 char c = expr.charAt(i);
127 if (extra.indexOf(c) >= 0)
128 continue;
129 if (!Character.isJavaIdentifierPart(c))
130 return false;
131 }
132 return true;
133 }
134
135 private static String replaceAllLiteral(Pattern pattern, String value, String replace) {
136 replace = replace.replaceAll("([$\\\\])", "\\\\$0");
137 return pattern.matcher(value).replaceAll(replace);
138 }
139
140 public String toString() {
141 return "Wildcard{pattern=" + pattern + ",parts=" + parts + "}";
142 }
143 }