View Javadoc

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  
30      private final Pattern pattern;
31      private final int count;
32      private final ArrayList<Object> parts = new ArrayList<Object>(16); // kept for debugging
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          // TODO: check for illegal characters
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          // System.err.println(this);
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       // package-info violates the spec for Java Identifiers.
120       // Nevertheless, expressions that end with this string are still legal.
121       // See 7.4.1.1 of the Java language spec for discussion.
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 }