LexSimpleAnalyzerUtils.java
/*
* JFlex Maven3 plugin
* Copyright (c) 2007-2017 R��gis D��camps <decamps@users.sf.net>
* Credit goes to the authors of the ant task.
* SPDX-License-Identifier: BSD-3-Clause
*/
package jflex.maven.plugin.jflex;
import com.google.common.io.Files;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
* @author Rafal Mantiuk (Rafal.Mantiuk@bellstream.pl)
* @author Gerwin Klein (lsf@jflex.de)
* @author R��gis D��camps
* @author Chris Fraire (cfraire@me.com)
*/
class LexSimpleAnalyzerUtils {
static final String DEFAULT_NAME = "Yylex";
private static final Pattern INCLUDE_DIRECTIVE_MATCHER = Pattern.compile("^\\s*%include\\s+(.+)");
private static final int INCLUDE_DIRECTIVE_ARG_OFFSET = 1;
/**
* Guesses package and class name, and {@code %include} files, based on this grammar definition.
*
* @param lexFile the lex spec to process
* @return collected info about this lex spec.
* @throws FileNotFoundException if the lex file does not exist
* @throws IOException when an IO exception occurred while reading a file.
*/
static SpecInfo guessSpecInfo(File lexFile) throws IOException {
Reader lexFileReader = Files.newReader(lexFile, StandardCharsets.UTF_8);
return guessSpecInfo(lexFileReader, lexFile);
}
/**
* Guesses package and class name, and {@code %include} files, based on this grammar definition.
*
* @param lexFileReader reader for lex spec to process
* @param lexFile the lex spec to process, used for relative path name resolution of {@code
* %incude}s.
* @return collected info about this lex spec.
* @throws IOException when an IO exception occurred while processing the reader. Ignores IO
* errors for {@code %incude} files.
*/
static SpecInfo guessSpecInfo(Reader lexFileReader, File lexFile) throws IOException {
try (LineNumberReader reader = new LineNumberReader(lexFileReader)) {
String className = null;
String packageName = null;
while (className == null || packageName == null) {
String line = reader.readLine();
if (line == null) {
break;
}
if (packageName == null) {
packageName = guessPackage(line);
}
if (className == null) {
className = guessClass(line);
}
}
if (className == null) {
className = DEFAULT_NAME;
}
return new SpecInfo(className, packageName, guessIncludes(lexFile));
}
}
/**
* Processes a file for {@code %include} directives.
*
* @param file the lex file to process.
* @return the set of files (recursively) mentioned in {@code %include}s.
*/
private static Set<File> guessIncludes(File file) {
return nestedIncludes(new HashSet<>(), file);
}
/**
* Recursively processes a file for {@code %include} directives.
*
* @param file the file to process; itself assumed to be an {@code %include} or lex file. Path
* names in the file are relative to the file location.
* @param seen the set of files seen so far, to avoid following cycles.
* @return the set of files (recursively) mentioned in {@code %include}s.
*/
private static Set<File> nestedIncludes(Set<File> seen, File file) {
Set<File> includedFiles = new HashSet<>();
Set<File> newSeen = new HashSet<>(seen);
try {
Reader reader = Files.newReader(file, StandardCharsets.UTF_8);
Set<File> newFiles = mapFiles(parseIncludes(reader), file.getParentFile());
newFiles.removeAll(seen);
newSeen.addAll(newFiles);
includedFiles.addAll(newFiles);
Set<File> nested =
newFiles.stream()
.flatMap(f -> nestedIncludes(newSeen, f).stream())
.collect(Collectors.toSet());
includedFiles.addAll(nested);
} catch (IOException e) {
// silently ignore IO exceptions in include file processing
}
return includedFiles;
}
/**
* Resolves path names relative to parent.
*
* @param set a set of relative path names
* @param parent the parent file of these path names
* @return the set of files relative to {@code parent}
*/
static Set<File> mapFiles(Set<String> set, File parent) {
return set.stream().map(s -> new File(parent, s)).collect(Collectors.toSet());
}
/**
* Parses input for {@code %include} directives.
*
* @param fileReader the input
* @return the set of path names mentioned after {@code %include} directives in the input.
*/
static Set<String> parseIncludes(Reader fileReader) throws IOException {
Set<String> includedFiles = new HashSet<>();
try (LineNumberReader reader = new LineNumberReader(fileReader)) {
String line = reader.readLine();
while (line != null) {
String includedFile = guessIncluded(line);
if (includedFile != null) {
includedFiles.add(includedFile);
}
line = reader.readLine();
}
}
return includedFiles;
}
@Nullable
private static String guessClass(String line) {
int index = line.indexOf("%class");
if (index > -1) {
index += "%class".length();
return line.substring(index).trim();
}
return null;
}
@Nullable
private static String guessPackage(String line) {
int index = line.trim().indexOf("package");
if (index == 0) {
index += "package".length();
int end = line.indexOf(';', index);
if (end >= index) {
return line.substring(index, end).trim();
}
}
return null;
}
@Nullable
private static String guessIncluded(String line) {
Matcher matcher = INCLUDE_DIRECTIVE_MATCHER.matcher(line);
if (matcher.find()) {
return matcher.group(INCLUDE_DIRECTIVE_ARG_OFFSET).trim();
}
return null;
}
private LexSimpleAnalyzerUtils() {}
}