/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.buildServer.tools.rules;
import jetbrains.buildServer.tools.fs.Naming;
import jetbrains.buildServer.tools.java.JavaVersion;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static jetbrains.buildServer.tools.rules.PathRules.Builder;
/**
* @author Eugene Petrenko (eugene.petrenko@gmail.com)
* Date: 08.11.11 18:07
*/
public class RulesParser {
private final Builder<VersionRule> myVersions = new Builder<VersionRule>();
private final Builder<StaticCheckRule> myStatics = new Builder<StaticCheckRule>();
private final StaticRuleSettings myAllowedStaticClasses = new StaticRuleSettings();
private final List<Parser> myParsers;
private final Queue<File> myConfigs = new ArrayDeque<File>();
public RulesParser() {
final List<Parser> parsers = new ArrayList<Parser>();
parsers.add(new RegexParser(Pattern.compile("include\\s+(.+)\\s*")) {
@Override
protected boolean parse(@NotNull Matcher matcher) throws IOException {
if (myConfigs.isEmpty()) return false;
parseConfig(new File(myConfigs.peek().getParentFile(), matcher.group(1).trim()));
return true;
}
});
parsers.add(
new RegexParser(Pattern.compile("allow\\s+static\\s+class\\s+(.+)\\s*")) {
@Override
protected boolean parse(@NotNull Matcher matcher) {
final String clazz = matcher.group(1).trim();
myAllowedStaticClasses.addRule(clazz);
return true;
}
});
parsers.add(new RegexParser(Pattern.compile("check\\s+static\\s*=>\\s*(.*)\\s*")) {
protected boolean parse(@NotNull Matcher line) throws IOException {
String path = line.group(1).trim();
myStatics.include(new StaticCheckRule(resolvePath(path), myAllowedStaticClasses));
return true;
}
});
parsers.add(new RegexParser(Pattern.compile("-\\s*check\\s+static\\s*=\\s*>\\s*(.+)\\s*")) {
protected boolean parse(@NotNull Matcher line) throws IOException {
String path = line.group(1).trim();
myStatics.exclude(new PathRule(resolvePath(path)));
return true;
}
});
for (final JavaVersion v : JavaVersion.values()) {
parsers.add(new Parser() {
public boolean parse(@NotNull String line) throws IOException {
final String prefix = v.getShortName();
if (!line.startsWith(prefix)) return false;
String part = line.substring(prefix.length()).trim();
if (part.startsWith("=>")) {
part = part.substring(2).trim();
} else {
throw new IOException("Failed to parse java rule: " + line);
}
myVersions.include(new VersionRule(resolvePath(part), v));
return true;
}
});
}
parsers.add(new Parser() {
public boolean parse(@NotNull String line) throws IOException {
if (!line.startsWith("-")) return false;
String path = line.substring(1).trim();
if (path.startsWith("=>")) {
path = path.substring(2).trim();
} else {
throw new IOException("Failed to parse - rule: " + line);
}
myVersions.exclude(new PathRule(resolvePath(path)));
return true;
}
});
myParsers = Collections.unmodifiableList(parsers);
}
@NotNull
public PathSettings build() {
return new PathSettings(myVersions.build(), myStatics.build());
}
@NotNull
public RulesParser parseConfig(@NotNull final File config) {
Reader rdr = null;
try {
myConfigs.add(config);
rdr = new InputStreamReader(new FileInputStream(config), "utf-8");
return parseConfig(rdr);
} catch(IOException e) {
System.err.println("Failed to parse settings from: " + config + ". " + e.getMessage());
throw new RuntimeException("No settings found in: " + config);
} finally {
myConfigs.poll();
try {
if (rdr != null) rdr.close();
} catch (IOException e) {
//NOP
}
}
}
@NotNull
public RulesParser parseConfig(@NotNull final Reader rdr) throws IOException {
try {
final Scanner sc = new Scanner(rdr);
while (sc.hasNextLine()) {
String line = sc.nextLine().trim();
if (line.length() == 0 || line.startsWith(";")) {
continue;
}
line = line.trim();
boolean handled = false;
for (Parser parser : myParsers) {
if (parser.parse(line)) {
handled = true;
break;
}
}
if (handled) continue;
throw new IOException("Unexpected string: '" + line + "' in config");
}
} finally {
rdr.close();
}
return this;
}
private static String resolvePath(@NotNull String path) throws IOException {
return Naming.normalizePath(path);
}
private interface Parser {
boolean parse(@NotNull String line) throws IOException;
}
private abstract static class RegexParser implements Parser {
private final Pattern myRegex;
protected RegexParser(@NotNull Pattern regex) {
myRegex = regex;
}
public boolean parse(@NotNull final String line) throws IOException {
final Matcher matcher = myRegex.matcher(line);
return matcher.matches() && parse(matcher);
}
protected abstract boolean parse(@NotNull Matcher matcher) throws IOException;
}
}