Home | History | Annotate | Download | only in jarjar
      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 
     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 
     98     public boolean matches(String value) {
     99         return getMatcher(value) != null;
    100     }
    101 
    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 
    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 
    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 
    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 
    142     public String toString() {
    143         return "Wildcard{pattern=" + pattern + ",parts=" + parts + "}";
    144     }
    145 }
    146