| /* |
| * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package com.sun.classanalyzer; |
| |
| import java.io.BufferedReader; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.regex.Pattern; |
| |
| /** |
| * |
| * @author Mandy Chung |
| */ |
| public class ModuleConfig { |
| |
| private static String baseModuleName = "base"; |
| private final Set<String> roots; |
| private final Set<String> includes; |
| private final Filter filter; |
| private List<String> members; |
| final String module; |
| final boolean isBase; |
| |
| private ModuleConfig(String name) throws IOException { |
| this.roots = new TreeSet<String>(); |
| this.includes = new TreeSet<String>(); |
| this.module = name; |
| this.isBase = name.equals(baseModuleName); |
| this.filter = new Filter(this); |
| } |
| |
| List<String> members() { |
| if (members == null) { |
| members = new LinkedList<String>(); |
| |
| for (String s : includes) { |
| if (!s.contains("*") && Module.findModule(s) != null) { |
| // module member |
| members.add(s); |
| } |
| } |
| } |
| return members; |
| } |
| |
| boolean matchesRoot(String name) { |
| for (String pattern : roots) { |
| if (matches(name, pattern)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| boolean matchesIncludes(String name) { |
| for (String pattern : includes) { |
| if (matches(name, pattern)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| boolean isExcluded(String name) { |
| return filter.isExcluded(name); |
| } |
| |
| boolean matchesPackage(String packageName, String pattern) { |
| int pos = pattern.lastIndexOf('.'); |
| String pkg = pos > 0 ? pattern.substring(0, pos) : "<unnamed>"; |
| return packageName.equals(pkg); |
| } |
| |
| |
| boolean matches(String name, String pattern) { |
| if (pattern.contains("**") && !pattern.endsWith("**")) { |
| throw new UnsupportedOperationException("Not yet implemented"); |
| } |
| |
| String javaName = name; |
| |
| boolean isResourceFile = name.indexOf('/') >= 0; |
| if (isResourceFile) { |
| // it's a resource file; convert the name as a java |
| javaName = name.replace('/', '.'); |
| } |
| if (pattern.indexOf('/') < 0) { |
| // if the pattern doesn't contain '/ |
| return matchesJavaName(javaName, pattern); |
| } else { |
| if (isResourceFile) { |
| // the pattern is for matching resource file |
| return matchesNameWithSlash(name, pattern); |
| } else { |
| return false; |
| } |
| } |
| } |
| |
| boolean matchesJavaName(String name, String pattern) { |
| int pos = name.lastIndexOf('.'); |
| String packageName = pos > 0 ? name.substring(0, pos) : "<unnamed>"; |
| if (pattern.endsWith("**")) { |
| String p = pattern.substring(0, pattern.length() - 2); |
| return name.startsWith(p); |
| } else if (pattern.endsWith("*") && pattern.indexOf('*') == pattern.lastIndexOf('*')) { |
| if (matchesPackage(packageName, pattern)) { |
| // package name has to be exact match |
| String p = pattern.substring(0, pattern.length() - 1); |
| return name.startsWith(p); |
| } else { |
| return false; |
| } |
| } else if (pattern.contains("*")) { |
| String basename = pos > 0 ? name.substring(pos + 1, name.length()) : name; |
| pos = pattern.indexOf('*'); |
| String prefix = pattern.substring(0, pos); |
| String suffix = pattern.substring(pos + 1, pattern.length()); |
| if (name.startsWith(prefix) && matchesPackage(packageName, prefix)) { |
| // package name has to be exact match |
| if (suffix.contains("*")) { |
| return name.matches(convertToRegex(pattern)); |
| } else { |
| return basename.endsWith(suffix); |
| } |
| } else { |
| // we don't support wildcard be used in the package name |
| return false; |
| } |
| } else { |
| // exact match or inner class |
| return name.equals(pattern) || name.startsWith(pattern + "$"); |
| } |
| } |
| |
| boolean matchesNameWithSlash(String name, String pattern) { |
| if (pattern.endsWith("**")) { |
| String p = pattern.substring(0, pattern.length() - 2); |
| return name.startsWith(p); |
| } else if (pattern.contains("*")) { |
| int pos = pattern.indexOf('*'); |
| String prefix = pattern.substring(0, pos); |
| String suffix = pattern.substring(pos + 1, pattern.length()); |
| String tail = name.substring(pos, name.length()); |
| |
| if (!name.startsWith(prefix)) { |
| // prefix has to exact match |
| return false; |
| } |
| |
| if (pattern.indexOf('*') == pattern.lastIndexOf('*')) { |
| // exact match prefix with no '/' in the tail string |
| String wildcard = tail.substring(0, tail.length() - suffix.length()); |
| return tail.indexOf('/') < 0 && tail.endsWith(suffix); |
| } |
| |
| if (suffix.contains("*")) { |
| return matchesNameWithSlash(tail, suffix); |
| } else { |
| // tail ends with the suffix while no '/' in the wildcard matched string |
| String any = tail.substring(0, tail.length() - suffix.length()); |
| return tail.endsWith(suffix) && any.indexOf('/') < 0; |
| } |
| } else { |
| // exact match |
| return name.equals(pattern); |
| } |
| } |
| |
| private String convertToRegex(String pattern) { |
| StringBuilder sb = new StringBuilder(); |
| int i = 0; |
| int index = 0; |
| int plen = pattern.length(); |
| while (i < plen) { |
| char p = pattern.charAt(i); |
| if (p == '*') { |
| sb.append("(").append(pattern.substring(index, i)).append(")"); |
| if (i + 1 < plen && pattern.charAt(i + 1) == '*') { |
| sb.append(".*"); |
| index = i + 2; |
| } else { |
| sb.append("[^\\.]*"); |
| index = i + 1; |
| } |
| } |
| i++; |
| } |
| if (index < plen) { |
| sb.append("(").append(pattern.substring(index, plen)).append(")"); |
| } |
| return sb.toString(); |
| } |
| |
| static class Filter { |
| |
| final ModuleConfig config; |
| final Set<String> exclude = new TreeSet<String>(); |
| final Set<String> allow = new TreeSet<String>(); |
| |
| Filter(ModuleConfig config) { |
| this.config = config; |
| } |
| |
| Filter exclude(String pattern) { |
| exclude.add(pattern); |
| return this; |
| } |
| |
| Filter allow(String pattern) { |
| allow.add(pattern); |
| return this; |
| } |
| |
| String allowedBy(String name) { |
| String allowedBy = null; |
| for (String pattern : allow) { |
| if (config.matches(name, pattern)) { |
| if (name.equals(pattern)) { |
| return pattern; // exact match |
| } |
| if (allowedBy == null) { |
| allowedBy = pattern; |
| } else { |
| if (pattern.length() > allowedBy.length()) { |
| allowedBy = pattern; |
| } |
| } |
| } |
| } |
| return allowedBy; |
| } |
| |
| String excludedBy(String name) { |
| String allowedBy = allowedBy(name); |
| String excludedBy = null; |
| |
| if (allowedBy != null && name.equals(allowedBy)) { |
| return null; // exact match |
| } |
| for (String pattern : exclude) { |
| if (config.matches(name, pattern)) { |
| // not matched by allowed rule or exact match |
| if (allowedBy == null || name.equals(pattern)) { |
| return pattern; |
| } |
| if (excludedBy == null) { |
| excludedBy = pattern; |
| } else { |
| if (pattern.length() > excludedBy.length()) { |
| excludedBy = pattern; |
| } |
| } |
| } |
| } |
| return excludedBy; |
| } |
| |
| boolean isExcluded(String name) { |
| String allowedBy = allowedBy(name); |
| String excludedBy = excludedBy(name); |
| |
| if (excludedBy == null) { |
| return false; |
| } |
| // not matched by allowed rule or exact match |
| if (allowedBy == null || name.equals(excludedBy)) { |
| return true; |
| } |
| |
| if (allowedBy == null) { |
| return true; |
| } |
| if (allowedBy != null && |
| excludedBy.length() > allowedBy.length()) { |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| private static String trimComment(String line) { |
| StringBuilder sb = new StringBuilder(); |
| |
| int pos = 0; |
| while (pos >= 0 && pos < line.length()) { |
| int c1 = line.indexOf("//", pos); |
| if (c1 > 0 && !Character.isWhitespace(line.charAt(c1-1))) { |
| // not a comment |
| c1 = -1; |
| } |
| |
| int c2 = line.indexOf("/*", pos); |
| if (c2 > 0 && !Character.isWhitespace(line.charAt(c2-1))) { |
| // not a comment |
| c2 = -1; |
| } |
| |
| int c = line.length(); |
| int n = line.length(); |
| if (c1 >= 0 || c2 >= 0) { |
| if (c1 >= 0) { |
| c = c1; |
| } |
| if (c2 >= 0 && c2 < c) { |
| c = c2; |
| } |
| int c3 = line.indexOf("*/", c2 + 2); |
| if (c == c2 && c3 > c2) { |
| n = c3 + 2; |
| } |
| } |
| if (c > 0) { |
| if (sb.length() > 0) { |
| // add a whitespace if multiple comments on one line |
| sb.append(" "); |
| } |
| sb.append(line.substring(pos, c)); |
| } |
| pos = n; |
| } |
| return sb.toString(); |
| } |
| |
| private static boolean beginBlockComment(String line) { |
| int pos = 0; |
| while (pos >= 0 && pos < line.length()) { |
| int c = line.indexOf("/*", pos); |
| if (c < 0) { |
| return false; |
| } |
| |
| if (c > 0 && !Character.isWhitespace(line.charAt(c-1))) { |
| return false; |
| } |
| |
| int c1 = line.indexOf("//", pos); |
| if (c1 >= 0 && c1 < c) { |
| return false; |
| } |
| |
| int c2 = line.indexOf("*/", c + 2); |
| if (c2 < 0) { |
| return true; |
| } |
| pos = c + 2; |
| } |
| return false; |
| } |
| |
| static void setBaseModule(String name) { |
| baseModuleName = name; |
| } |
| // TODO: we shall remove "-" from the regex once we define |
| // the naming convention for the module names without dashes |
| static final Pattern classNamePattern = Pattern.compile("[\\w\\.\\*_$-/]+"); |
| |
| static List<ModuleConfig> readConfigurationFile(String file) throws IOException { |
| List<ModuleConfig> result = new ArrayList<ModuleConfig>(); |
| // parse configuration file |
| FileInputStream in = new FileInputStream(file); |
| try { |
| BufferedReader reader = new BufferedReader(new InputStreamReader(in)); |
| String line; |
| |
| int lineNumber = 0; |
| boolean inRoots = false; |
| boolean inIncludes = false; |
| boolean inAllows = false; |
| boolean inExcludes = false; |
| boolean inBlockComment = false; |
| ModuleConfig config = null; |
| |
| while ((line = reader.readLine()) != null) { |
| lineNumber++; |
| |
| if (inBlockComment) { |
| int c = line.indexOf("*/"); |
| if (c >= 0) { |
| line = line.substring(c + 2, line.length()); |
| inBlockComment = false; |
| } else { |
| // skip lines until end of comment block |
| continue; |
| } |
| } |
| |
| inBlockComment = beginBlockComment(line); |
| |
| line = trimComment(line).trim(); |
| // ignore empty lines |
| if (line.length() == 0) { |
| continue; |
| } |
| |
| String values; |
| if (inRoots || inIncludes || inExcludes || inAllows) { |
| values = line; |
| } else { |
| String[] s = line.split("\\s+"); |
| String keyword = s[0].trim(); |
| if (keyword.equals("module")) { |
| if (s.length != 3 || !s[2].trim().equals("{")) { |
| throw new RuntimeException(file + ", line " + |
| lineNumber + ", is malformed"); |
| } |
| config = new ModuleConfig(s[1].trim()); |
| result.add(config); |
| // switch to a new module; so reset the flags |
| inRoots = false; |
| inIncludes = false; |
| inExcludes = false; |
| inAllows = false; |
| continue; |
| } else if (keyword.equals("roots")) { |
| inRoots = true; |
| } else if (keyword.equals("include")) { |
| inIncludes = true; |
| } else if (keyword.equals("exclude")) { |
| inExcludes = true; |
| } else if (keyword.equals("allow")) { |
| inAllows = true; |
| } else if (keyword.equals("}")) { |
| if (config == null || s.length != 1) { |
| throw new RuntimeException(file + ", line " + |
| lineNumber + ", is malformed"); |
| } else { |
| // end of a module |
| config = null; |
| continue; |
| } |
| } else { |
| throw new RuntimeException(file + ", \"" + keyword + "\" on line " + |
| lineNumber + ", is not recognized"); |
| } |
| values = line.substring(keyword.length(), line.length()).trim(); |
| } |
| |
| if (config == null) { |
| throw new RuntimeException(file + ", module not specified"); |
| } |
| |
| int len = values.length(); |
| if (len == 0) { |
| continue; |
| } |
| char lastchar = values.charAt(len - 1); |
| if (lastchar != ',' && lastchar != ';') { |
| throw new RuntimeException(file + ", line " + |
| lineNumber + ", is malformed:" + |
| " ',' or ';' is missing."); |
| } |
| |
| values = values.substring(0, len - 1); |
| // parse the values specified for a keyword specified |
| for (String s : values.split(",")) { |
| s = s.trim(); |
| if (s.length() > 0) { |
| if (!classNamePattern.matcher(s).matches()) { |
| throw new RuntimeException(file + ", line " + |
| lineNumber + ", is malformed: \"" + s + "\""); |
| } |
| if (inRoots) { |
| config.roots.add(s); |
| } else if (inIncludes) { |
| config.includes.add(s); |
| } else if (inExcludes) { |
| config.filter.exclude(s); |
| } else if (inAllows) { |
| config.filter.allow(s); |
| } |
| |
| } |
| } |
| if (lastchar == ';') { |
| inRoots = false; |
| inIncludes = false; |
| inExcludes = false; |
| inAllows = false; |
| } |
| } |
| |
| if (inBlockComment) { |
| throw new RuntimeException(file + ", line " + |
| lineNumber + ", missing \"*/\" to end a block comment"); |
| } |
| if (config != null) { |
| throw new RuntimeException(file + ", line " + |
| lineNumber + ", missing \"}\" to end module definition" + |
| " for \"" + config.module + "\""); |
| } |
| |
| } finally { |
| in.close(); |
| } |
| |
| return result; |
| } |
| |
| private String format(String keyword, Collection<String> values) { |
| if (values.size() == 0) { |
| return ""; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| String format = "%4s%-9s"; |
| String spaces = String.format(format, "", ""); |
| sb.append(String.format(format, "", keyword)); |
| int count = 0; |
| for (String s : values) { |
| if (count > 0) { |
| sb.append(",\n").append(spaces); |
| } else if (count++ > 0) { |
| sb.append(", "); |
| } |
| sb.append(s); |
| } |
| if (count > 0) { |
| sb.append(";\n"); |
| } |
| return sb.toString(); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("module " + module).append(" {\n"); |
| sb.append(format("include", includes)); |
| sb.append(format("root", roots)); |
| sb.append(format("allow", filter.allow)); |
| sb.append(format("exclude", filter.exclude)); |
| sb.append("}\n"); |
| return sb.toString(); |
| } |
| } |