对于这个问题,重点是我创建的用于解析日志条目的API。我已使用Java 8编写了此代码,我的意图是使API至少具有令人惊讶的效果,所有使您怀疑的内容都可能成为您的评论中的候选。
这个问题是紧随其后的-至可扩展抽象日志阅读器,与解析HearthStone的日志文件密切相关:API。
类摘要:
com.github.skiwi2。 hearthmonitor.logreader
LogReader
接口
用于读取日志条目。
CloseableLogReader
接口
用于从可关闭资源中读取日志条目。
DefaultLogReader
Class
用于通过迭代器从日志源读取日志条目。
建议扩展此类并通过传递迭代器子类。
LogReaderUtils
实用程序类,可从其他来源创建LogReader。
EntryParser
接口
用于解析日志源中的行
如果需要,它可以选择通过行读取器从日志源中读取更多行。
LineReader
接口
用于从输入源读取线。
MatchingIterator
接口
用于迭代元素,同时还可以检查下一个元素是否与谓词匹配。
IteratorUtils
实用程序类使用迭代器来做复杂的事情。 />
指示日志条目不可解析的异常。
NotReadableException
异常指示日志条目不可读。
com.github.skiwi2.hearthmonitor.logreader.logreaders
FileLogReader
用于从日志文件读取日志条目。
ListLogReader
用于从列表中读取日志条目。
MonitoringFileLogReader
用于从日志文件读取日志条目,直到还有更多输入可用。
/**
* Used to read log entries.
*
* @author Frank van Heeswijk
*/
public interface LogReader {
/**
* Returns the next log entry.
*
* The NotReadableException has more information available.
* You can recover the lines that could not be read by calling NotReadableException#getLines.
* You can see which exceptions were thrown internally by calling NotReadableException#getOccurredExceptions.
*
* @return The next log entry.
* @throws NotReadableException If the log entry could not be read.
* @throws java.util.NoSuchElementException If there is no more input.
*/
LogEntry readNextEntry() throws NotReadableException;
/**
* Returns whether there is a next log entry.
*
* @return Whether there is a next log entry.
*/
boolean hasNextEntry();
}
/**
* Used to read log entries from a closeable resource.
*
* @author Frank van Heeswijk
*/
public interface CloseableLogReader extends LogReader, AutoCloseable { }
/**
* Used to read log entries from a log source via an iterator.
*
* It is encouraged to extend this class and pass the iterator via the subclass.
*
* @author Frank van Heeswijk
*/
public class DefaultLogReader implements LogReader {
private final Set<? extends EntryParser> entryParsers;
private final MatchingIterator<String> matchingIterator;
private final List<String> linesInMemory = new ArrayList<>();
/**
* Constructs a new DefaultLogReader instance.
*
* @param entryParsers The set of entry parsers
* @param readIterator The iterator used to read lines from the log source
* @param filterPredicate The predicate to use to filter the lines read from the log source
* @throws java.lang.NullPointerException If entryParsers, readIterator or filterPredicate is null.
*/
protected DefaultLogReader(final Set<? extends EntryParser> entryParsers, final Iterator<String> readIterator, final Predicate<? super String> filterPredicate) {
Objects.requireNonNull(filterPredicate, "filterPredicate");
Objects.requireNonNull(readIterator, "readIterator");
this.entryParsers = Objects.requireNonNull(entryParsers, "entryParsers");
Iterator<String> filteredIterator = IteratorUtils.filteredIterator(readIterator, filterPredicate);
this.matchingIterator = MatchingIterator.fromIterator(filteredIterator);
}
/**
* Constructs a new DefaultLogReader instance.
*
* The filter predicate can be used to filter the lines you want to traverse.
*
* @param entryParsers The set of entry parsers
* @param readIterator The iterator used to read lines from the log source
* @throws java.lang.NullPointerException If entryParsers or readIterator is null.
*/
protected DefaultLogReader(final Set<? extends EntryParser> entryParsers, final Iterator<String> readIterator) {
this(entryParsers, readIterator, string -> true);
}
@Override
public LogEntry readNextEntry() throws NotReadableException {
List<Exception> occurredExceptions = new ArrayList<>();
String line = matchingIterator.next();
linesInMemory.add(line);
for (EntryParser entryParser : entryParsers) {
if (!entryParser.isParsable(line)) {
continue;
}
try {
LogEntry result = entryParser.parse(line, new LineReader() {
@Override
public String readNextLine() {
String nextLine = matchingIterator.next();
linesInMemory.add(nextLine);
return nextLine;
}
@Override
public boolean hasNextLine() {
return matchingIterator.hasNext();
}
@Override
public boolean nextLineMatches(final Predicate<? super String> condition) {
return matchingIterator.nextMatches(condition);
}
});
linesInMemory.clear();
return result;
} catch (NotParsableException | NoSuchElementException ex) {
occurredExceptions.add(ex);
//try next entry parser
}
}
List<String> notParsableLines = new ArrayList<>(linesInMemory);
linesInMemory.clear();
throw new NotReadableException(notParsableLines, occurredExceptions);
}
@Override
public boolean hasNextEntry() {
return matchingIterator.hasNext();
}
}
/**
* Utility class to create a LogReader from other sources.
*
* @author Frank van Heeswijk
*/
public final class LogReaderUtils {
private LogReaderUtils() {
throw new UnsupportedOperationException();
}
/**
* Creates a LogReader that can read log entries from an input string, a LineReader for the extra lines and a read condition.
*
* The LogReader will attempt to read extra lines from the LineReader as long as the read condition is met using the supplied entry readers.
*
* Note: The input will always be included and not checked against the extra read condition.
*
* @param input The input line
* @param extraLineReader The extra line reader
* @param extraReadCondition The extra read condition
* @param entryParsers The set of entry parsers
* @return A new LogReader that can read log entries from the input string, the LineReader for the extra lines and the read condition.
*/
public static LogReader fromInputAndExtraLineReader(final String input, final LineReader extraLineReader, final Predicate<? super String> extraReadCondition, final Set<? extends EntryParser> entryParsers) {
Objects.requireNonNull(extraLineReader, "extraLineReader");
Objects.requireNonNull(extraReadCondition, "extraReadCondition");
Objects.requireNonNull(entryParsers, "entryParsers");
return new DefaultLogReader(entryParsers, createReadIteratorForFromInputAndExtraLineReader(input, extraLineReader, extraReadCondition));
}
/**
* Returns an iterator for the given input and extra line reader.
*
* @param input The given input
* @param extraLineReader The given extra line reader
* @param extraReadCondition The given extra read condition
* @return The iterator for the given input and extra line reader.
*/
private static Iterator<String> createReadIteratorForFromInputAndExtraLineReader(final String input, final LineReader extraLineReader, final Predicate<? super String> extraReadCondition) {
LineReader conditionalLineReader = LineReader.readWhile(extraLineReader, extraReadCondition);
Iterator<String> lineReaderIterator = new Iterator<String>() {
@Override
public boolean hasNext() {
return conditionalLineReader.hasNextLine();
}
@Override
public String next() {
return conditionalLineReader.readNextLine();
}
};
return Stream.concat(
Stream.of(input),
StreamSupport.stream(Spliterators.spliteratorUnknownSize(lineReaderIterator, Spliterator.NONNULL), false)
).iterator();
}
}
/**
* Used to parse lines from a log source.
*
* It has the option to read more lines from the log source via a line reader if deemed necessary.
*
* @author Frank van Heeswijk
*/
public interface EntryParser {
/**
* Returns whether this entry parser can parse the input.
*
* @param input The input check parsability for
* @return Whether this entry parser can parse the input.
*/
boolean isParsable(final String input);
/**
* Parses the input String resulting in a LogEntry.
*
* If deemed necessary, extra lines may be obtained from the LineReader.
*
* @param input The input to parse
* @param lineReader The line reader from which extra lines can be obtained
* @return The LogEntry obtained after parsing the input.
* @throws NotParsableException If this entry reader cannot parse the input to return a LogEntry.
*/
LogEntry parse(final String input, final LineReader lineReader) throws NotParsableException;
}
/**
* Used to read lines from an input source.
*
* @author Frank van Heeswijk
*/
public interface LineReader {
/**
* Reads the next line.
*
* @return The next line.
* @throws java.util.NoSuchElementException If there are no lines left anymore
*/
String readNextLine() throws NoSuchElementException;
/**
* Returns whether there is a next line to read.
*
* @return Whether there is a next line to read.
*/
boolean hasNextLine();
/**
* Returns whether the next line matches the given condition.
*
* @param condition The condition that the next line should match
* @return Whether the next line matches the given condition.
*/
boolean nextLineMatches(final Predicate<? super String> condition);
/**
* Returns a LineReader that reads from another LineReader while the read condition is true.
*
* @param lineReader The LineReader to be read from
* @param readCondition The read condition
* @return A LineReader that reads from another LineReader while the read condition is true.
* @throws java.lang.NullPointerException If lineReader or readCondition is null.
*/
static LineReader readWhile(final LineReader lineReader, final Predicate<? super String> readCondition) {
Objects.requireNonNull(lineReader, "lineReader");
Objects.requireNonNull(readCondition, "readCondition");
return new LineReader() {
@Override
public String readNextLine() throws NoSuchElementException {
if (!lineReader.nextLineMatches(readCondition)) {
throw new NoSuchElementException();
}
return lineReader.readNextLine();
}
@Override
public boolean hasNextLine() {
return lineReader.nextLineMatches(readCondition);
}
@Override
public boolean nextLineMatches(final Predicate<? super String> condition) {
return lineReader.nextLineMatches(line -> (readCondition.test(line) && condition.test(line)));
}
};
}
}
/**
* Used to iterate over elements while also having the possibility to check if the next elements matches a predicate.
*
* @author Frank van Heeswijk
* @param <E> The type of elements
*/
public interface MatchingIterator<E> extends Iterator<E> {
/**
* Returns whether the next element matches the given condition.
*
* @param condition The condition predicate that the next element may match
* @return Whether the next element matches the given condition.
* @throws java.lang.NullPointerException If condition is null.
*/
boolean nextMatches(final Predicate<? super E> condition);
/**
* Returns a matching iterator constructed from an iterator.
*
* @param iterator The input iterator
* @param <E> The type of the elements in the iterator
* @return The matching iterator constructed from the iterator.
*/
static <E> MatchingIterator<E> fromIterator(final Iterator<? extends E> iterator) {
return new MatchingIterator<E>() {
private final List<E> peekedElements = new ArrayList<>();
@Override
public boolean hasNext() {
Optional<E> peekElement = peek();
return peekElement.isPresent();
}
@Override
public E next() {
if (!peekedElements.isEmpty()) {
return peekedElements.remove(0);
}
return iterator.next();
}
@Override
public boolean nextMatches(final Predicate<? super E> condition) {
Objects.requireNonNull(condition, "condition");
Optional<E> peekElement = peek();
return (peekElement.isPresent() && condition.test(peekElement.get()));
}
/**
* Returns an optional containing the next element, or an empty optional if there is none.
*
* @return The optional containing the next element, or the empty optional if there is none.
*/
private Optional<E> peek() {
if (!peekedElements.isEmpty()) {
return Optional.ofNullable(peekedElements.get(0));
}
if (!iterator.hasNext()) {
return Optional.empty();
}
E element = iterator.next();
peekedElements.add(element);
return Optional.ofNullable(element);
}
};
}
}
/**
* Utility class to do complex things with iterators.
*
* @author Frank van Heeswijk
*/
public final class IteratorUtils {
private IteratorUtils() {
throw new UnsupportedOperationException();
}
/**
* Returns an iterator that is a view on the given iterator only considering elements that match the condition predicate.
*
* @param iterator The input iterator
* @param condition The condition predicate that elements have to match
* @param <E> The type of elements
* @return The iterator that is a view on the given iterator only considering elements that match the condition predicate.
*/
public static <E> Iterator<E> filteredIterator(final Iterator<? extends E> iterator, final Predicate<? super E> condition) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)
.filter(condition)
.iterator();
}
}
/**
* Exception to indicate that a log entry is not parsable.
*
* @author Frank van Heeswijk
*/
public class NotParsableException extends Exception {
private static final long serialVersionUID = 3147294996191143729L;
public NotParsableException() {
}
public NotParsableException(final String message) {
super(message);
}
public NotParsableException(final String message, final Throwable cause) {
super(message, cause);
}
public NotParsableException(final Throwable cause) {
super(cause);
}
}
/**
* Exception to indicate that a log entry is not readable.
*
* @author Frank van Heeswijk
*/
public class NotReadableException extends Exception {
private static final long serialVersionUID = -117259271357929934L;
private final List<String> lines = new ArrayList<>();
private final List<Exception> occurredExceptions = new ArrayList<>();
/**
* Constructs a new NotReadableException instance.
*
* @param lines The lines that were not readable
* @param occurredExceptions The exceptions that occurred during reading
*/
public NotReadableException(final List<String> lines, final List<Exception> occurredExceptions) {
Objects.requireNonNull(lines, "lines");
Objects.requireNonNull(occurredExceptions, "occurredExceptions");
this.lines.addAll(lines);
this.occurredExceptions.addAll(occurredExceptions);
}
/**
* Returns the lines that were not readable.
*
* @return The lines that were not readable.
*/
public List<String> getLines() {
return new ArrayList<>(lines);
}
/**
* Returns the exceptions that occurred during reading.
*
* @return The exceptions that occurred during reading.
*/
public List<Exception> getOccurredExceptions() {
return new ArrayList<>(occurredExceptions);
}
}
/**
* Used to read log entries from a log file.
*
* @author Frank van Heeswijk
*/
public class FileLogReader extends DefaultLogReader implements CloseableLogReader {
private final BufferedReader bufferedReader;
/**
* Constructs a new FileLogReader instance.
*
* @param bufferedReader The buffered reader from which to read
* @param entryParsers The set of entry parsers
* @throws java.lang.NullPointerException If bufferedReader or entryParsers is null.
*/
public FileLogReader(final BufferedReader bufferedReader, final Set<? extends EntryParser> entryParsers) {
super(entryParsers, bufferedReader.lines().iterator());
this.bufferedReader = Objects.requireNonNull(bufferedReader, "bufferedReader");
}
/**
* Constructs a new FileLogReader instance.
*
* The filter predicate can be used to filter the lines you want to traverse.
*
* @param bufferedReader The buffered reader from which to read
* @param entryParsers The set of entry parsers
* @param filterPredicate The predicate to filter the lines with
* @throws java.lang.NullPointerException If bufferedReader, filterPredicate or entryParsers is null.
*/
public FileLogReader(final BufferedReader bufferedReader, final Set<? extends EntryParser> entryParsers, final Predicate<? super String> filterPredicate) {
super(entryParsers, bufferedReader.lines().iterator(), filterPredicate);
this.bufferedReader = Objects.requireNonNull(bufferedReader, "bufferedReader");
}
@Override
public void close() throws IOException {
bufferedReader.close();
}
}
/**
* Used to read log entries from a list.
*
* @author Frank van Heeswijk
*/
public class ListLogReader extends DefaultLogReader {
/**
* Constructs a new ListLogReader instance.
*
* This method saves a snapshot of the list at this time, and uses that to iterate over.
*
* @param inputList The input list to read from
* @param entryParsers The set of entry parsers
* @throws java.lang.NullPointerException If inputList or entryParsers is null.
*/
public ListLogReader(final List<String> inputList, final Set<? extends EntryParser> entryParsers) {
super(entryParsers, new ArrayList<>(inputList).iterator());
}
/**
* Constructs a new ListLogReader instance.
*
* This method saves a snapshot of the list at this time, and uses that to iterate over.
* The filter predicate can be used to filter the lines you want to traverse.
*
* @param inputList The input list to read from
* @param entryParsers The set of entry parsers
* @param filterPredicate The predicate to filter the lines with
* @throws java.lang.NullPointerException If inputList, filterPredicate or entryParsers is null.
*/
public ListLogReader(final List<String> inputList, final Set<? extends EntryParser> entryParsers, final Predicate<? super String> filterPredicate) {
super(entryParsers, new ArrayList<>(inputList).iterator(), filterPredicate);
}
}
/**
* Used to read log entries from a log file, blocking until more input is available.
*
* @author Frank van Heeswijk
*/
public class MonitoringFileLogReader extends DefaultLogReader implements CloseableLogReader {
private final BufferedReader bufferedReader;
/**
* Constructs a new MonitoringFileLogReader instance.
*
* @param bufferedReader The buffered reader from which to read
* @param entryParsers The set of entry parsers
* @throws java.lang.NullPointerException If bufferedReader or entryParsers is null.
*/
public MonitoringFileLogReader(final BufferedReader bufferedReader, final Set<? extends EntryParser> entryParsers) {
super(entryParsers, createReadIterator(bufferedReader));
this.bufferedReader = Objects.requireNonNull(bufferedReader, "bufferedReader");
}
/**
* Constructs a new MonitoringFileLogReader instance.
*
* The filter predicate can be used to filter the lines you want to traverse.
*
* @param bufferedReader The buffered reader from which to read
* @param entryParsers The set of entry parsers
* @param filterPredicate The predicate to filter the lines with
* @throws java.lang.NullPointerException If bufferedReader, filterPredicate or entryParsers is null.
*/
public MonitoringFileLogReader(final BufferedReader bufferedReader, final Set<? extends EntryParser> entryParsers, final Predicate<? super String> filterPredicate) {
super(entryParsers, createReadIterator(bufferedReader), filterPredicate);
this.bufferedReader = Objects.requireNonNull(bufferedReader, "bufferedReader");
}
/**
* Returns an iterator for the given buffered reader.
*
* @param bufferedReader The given buffered reader
* @return The iterator for the given buffered reader.
*/
private static Iterator<String> createReadIterator(final BufferedReader bufferedReader) {
Iterator<String> bufferedReaderIterator = bufferedReader.lines().iterator();
return new Iterator<String>() {
private boolean isInterrupted = false;
@Override
public boolean hasNext() {
return !isInterrupted;
}
@Override
public String next() {
if (isInterrupted) {
throw new NoSuchElementException();
}
try {
while (!bufferedReaderIterator.hasNext()) {
Thread.sleep(100);
}
return bufferedReaderIterator.next();
} catch (InterruptedException | UncheckedIOException ex) {
isInterrupted = true;
Thread.currentThread().isInterrupted();
throw new NoSuchElementException();
}
}
};
}
@Override
public void close() throws IOException {
bufferedReader.close();
}
}
>该项目(包括其单元测试)可以在Github上找到,示例用法也可以在单元测试中找到。
我想回顾一下此代码的各个方面,并特别关注公共API(我认为此版本稳定用于下一个版本)。
#1 楼
DesignLogReader
读取日志,过滤掉不需要的内容并将其转换为LogEntries
。根据您的设计。有点多。拆分它。
制作一个
LogReader
,它仅读取文件并为您提供无尽的字符串。哦,等等,这是一个BufferedReader
!我会把LogReader
基本上看作是BufferedReader
的迭代器包装器。制作一个
LogParser
,它将一个Iterable<String>
解析为LogEntries
。然后制作一个LogFilter
,它需要一堆LogEntries
并仅返回您想要的那些。由于
LogFilter
的输入和输出是同一类型,因此对于调用者而言是可选的。这简化了涉及简单任务的API。代码看起来不错。我只有一点要指出的地方:
isEmpty检查
DefaultLogReader
具有Set
的EntryParser
,和final
。您甚至可以通过Objects.requireNotNull
检查它是否不为null ...,但不检查它是否实际包含某些东西。对于
DefaultLogReader
,这意味着如果您提供一组空的EntryParsers
,您将首次尝试阅读内容时,请先获取NotReadableException
。我不确定那是您想要的东西。鉴于您已将其设置为异常而不是成员属性,我倾向于说您应该添加检查以查看传递给您的集合是否确实包含某些内容。数据结构
private final List<E> peekedElements = new ArrayList<>();
@Override
public boolean hasNext() {
Optional<E> peekElement = peek();
return peekElement.isPresent();
}
@Override
public E next() {
if (!peekedElements.isEmpty()) {
return peekedElements.remove(0);
}
return iterator.next();
}
@Override
public boolean nextMatches(final Predicate<? super E> condition) {
Objects.requireNonNull(condition, "condition");
Optional<E> peekElement = peek();
return (peekElement.isPresent() && condition.test(peekElement.get()));
}
/**
* Returns an optional containing the next element, or an empty optional if there is none.
*
* @return The optional containing the next element, or the empty optional if there is none.
*/
private Optional<E> peek() {
if (!peekedElements.isEmpty()) {
return Optional.ofNullable(peekedElements.get(0));
}
if (!iterator.hasNext()) {
return Optional.empty();
}
E element = iterator.next();
peekedElements.add(element);
return Optional.ofNullable(element);
}
让我们看看...
您添加到末尾并从头开始删除。列表没有其他交互。
为什么选择
List
?一个Queue
更好(为此目的而设计!)。不需要的成员变量
DefaultLogReader
具有linesInMemory
。这是不需要的。它可以是readNextEntry
中的最终局部变量。类名/类型不匹配
您有
FileLogReader
需要一个BufferedReader
。这不是BufferedReaderLogReader
吗?您怎么知道它是一个文件?我敢打赌,我可以为您的FileLogReader
评审标准输入。我建议改用一个InputStream
并使其成为一个StreamLogReader
,或者甚至取一个File
或一个Path
...但这可能不是您想要的。做出决定后,适当更改名称。我会输入一个常量POLL_INTERVAL
或其他内容。仅提供变量名称会有所帮助,但也许会出现一条正确的消息“ [entryParsers对于[class]为null]”?参数顺序让他们的第一个参数成为最重要的参数...恰好是迭代器提供程序。那么,为什么还要将
Objects.requireNotNull
作为DefaultLogReader
的第一个参数呢?您搞砸了参数的顺序,并使实现者进行了仔细检查(坚持下去,它是反向的吗?)。这只是一个小问题,但我没有更大的建议。但是公共API的一部分是附带的文档。因此,让我们专注于此。 />
,假设实现丢失。
while (!bufferedReaderIterator.hasNext()) {
Thread.sleep(100);
}
文档提供的信息以粗体显示。
此方法创建了一个新的DefaultLogReader。是的,那就是构造函数的工作。
entryParsers
是一组EntryParsers。代码就是这样,谢谢。 DefaultLogReader
是用于从日志源读取行的迭代器。知道了entryParsers
是用于过滤从日志源读取的行的谓词。嗯。哦,如果您为其中任何一个提供null,它将抛出NullPointerException。
您应该努力为所记录的每件事提供有意义的文档-或将其留空。在这种情况下,常规构造函数消息和
readIterator
无法提供有意义的信息。例如,有趣的是,在顶部的那个构造函数中,没有复制filterPredicate
。如果您以后修改该设置,它将对entryParsers
的内部工作产生影响。糟糕。方法说明-您有两个构造函数!为什么这是不同的?考虑编写类似“使用行过滤构造新的DefaultLogReader实例”之类的内容。
Javadoc链接
>我建议在此处放置一个
entryParsers
。 https://stackoverflow.com/questions/10097199/javadoc-see-or-link 像这样,您强调它可以使用
DefaultLogReader
而不是某些对象。双倍空格
/**
* Constructs a new DefaultLogReader instance.
*
* @param entryParsers The set of entry parsers
* @param readIterator The iterator used to read lines from the log source
* @param filterPredicate The predicate to use to filter the lines read from the log source
* @throws java.lang.NullPointerException If entryParsers, readIterator or filterPredicate is null.
*/
protected DefaultLogReader(final Set<? extends EntryParser> entryParsers, final Iterator<String> readIterator, final Predicate<? super String> filterPredicate) {
Objects.requireNonNull(filterPredicate, "filterPredicate");
Objects.requireNonNull(readIterator, "readIterator");
this.entryParsers = Objects.requireNonNull(entryParsers, "entryParsers");
Iterator<String> filteredIterator = IteratorUtils.filteredIterator(readIterator, filterPredicate);
this.matchingIterator = MatchingIterator.fromIterator(filteredIterator);
}
It has the option to read more lines from the log source via a line reader if deemed necessary.
检查自动格式设置。在发布有关CR /提交到存储库的问题之前自动格式化。
评论
\ $ \ begingroup \ $
那么您有什么建议呢?需要以javadoc的形式提供一些文档,否则,一旦生成javadoc,它将看起来非常难看。
\ $ \ endgroup \ $
–skiwi
15年1月27日在15:55
\ $ \ begingroup \ $
javadoc冗余不是很常见吗?
\ $ \ endgroup \ $
–西蒙·福斯伯格
15年1月27日在16:00
\ $ \ begingroup \ $
@SimonAndréForsberg只要写得不好=)
\ $ \ endgroup \ $
– Pimgd
15年1月27日在16:02
#2 楼
我将从头到尾进行遍历。 /**
* Returns the next log entry.
*
* The NotReadableException has more information available.
* You can recover the lines that could not be read by calling NotReadableException#getLines.
* You can see which exceptions were thrown internally by calling NotReadableException#getOccurredExceptions.
*
* @return The next log entry.
* @throws NotReadableException If the log entry could not be read.
* @throws java.util.NoSuchElementException If there is no more input.
*/
LogEntry readNextEntry() throws NotReadableException;
这里您做了一些奇怪的事情。您将Javadoc中有关
NotReadableException
的内容描述为引发该异常的方法。此javadoc的完整第二段应位于NotReadableException
的类级javadoc上。public interface CloseableLogReader extends LogReader, AutoCloseable { }
对我来说似乎是个坏主意。我希望每个日志读取器都实现
AutoCloseable
。主要是因为它们是阅读器。如果您的阅读器不是AutoCloseable(或需要关闭资源),则可以应用以下技巧:
public interface LogReader extends AutoCloseable {
// Your code here:
@Override
default void close() {
// cheat to not be forced to override
}
}
* It is encouraged to extend this class and pass the iterator via the subclass.
此处的Javadoc与代码有些矛盾。您明确鼓励扩展类,但仍将实例字段声明为私有。如果您真的鼓励扩展该类,那么我希望它们会受到保护!
entryParser.parse(line, new LineReader() {
@Override
public String readNextLine() {
String nextLine = matchingIterator.next();
linesInMemory.add(nextLine);
return nextLine;
}
@Override
public boolean hasNextLine() {
return matchingIterator.hasNext();
}
@Override
public boolean nextLineMatches(final Predicate<? super String> condition) {
return matchingIterator.nextMatches(condition);
}
});
EEEEEK!为什么在这里创建一个
new LineReader() { ... }
?这毫无意义,尤其是当您可以在全班级创建一个LineReader时。您在这里混合抽象级别。 首先,您通过将阅读器作为参数传递给
entryParser.parse()
来隐藏阅读器,然后使用(比较)低级代码将维护者先生打在脸上。这会施加很多精神压力,并使您的代码难以遵循。其余的吗?我只是快速阅读了一下,不能说太多,但是另一件事困扰了我:
您的行(特别是在javadocs中)很长!您在此处呈现的几乎每个代码块都有一个水平滚动条:(仅仅因为它是javadoc,这并不意味着它可以一直持续下去。甚至更是如此,当换行符被忽略时,html样式和更多行或在文档视图中查看javadoc时,less没什么区别。
评论
\ $ \ begingroup \ $
“ *鼓励扩展此类并通过子类传递迭代器。”似乎是指创建类似ListLogReader的东西,而不是MyCustomLogReaderThatAlsoPrintsWhatItReads并做一些时髦的事情。仅提供迭代器,就不需要访问实例字段...?
\ $ \ endgroup \ $
– Pimgd
2015年2月4日14:51
\ $ \ begingroup \ $
但是使用它确实需要它。还有其他装饰。而且装饰绝对不会扩展。
\ $ \ endgroup \ $
–Vogel612♦
2015年2月4日14:52
#3 楼
我不相信您的总体结构应该是有用的。我一直在试图弄清我们如何使用您的代码,以及发生了什么事件序列,以便代码按照应有的方式运行....在此过程中,我遇到了许多问题,它们都增加了提示您使用的是错误的方法。即使您当前的代码中,也要添加以下项目(除了其他评论者指出的内容之外)...
泛型
/>
EntryParser
类应被生成:public interface EntryParser {
boolean isParsable(final String input);
LogEntry parse(final String input, final LineReader lineReader) throws NotParsableException;
}
应为:
public interface EntryParser<T extends LogEntry> {
boolean isParsable(final String input);
T parse(final String input, final LineReader lineReader) throws NotParsableException;
}
然后应更新所有EntryParsers使其具有新的泛型类型。
抽象
您的代码中的抽象层太多了。每当您看到“标记接口”时,我都会怀疑,您不仅拥有标记接口,还继承了标记?
public interface LogObject { }
和
public interface LogEntry extends LogObject { }
真的吗?这些增加了什么价值?
哦,我明白了那些是什么……对。
您创建了这些接口是因为解析器上没有泛型。我知道了。
您根本不需要它们,您可以拥有:
public interface EntryParser<T> {
boolean isParsable(final String input);
T parse(final String input, final LineReader lineReader) throws NotParsableException;
}
然后它“就可以使用”。
呼叫方向
我不知道我不完全知道如何描述我在您的系统中遇到的问题,但是您正在做反向操作。
您有多个条目解析器,并且都对它们进行了轮询以识别它们是否可以处理一组特定的数据。当他们可以解析数据时,便会获得一个“读取器”,从中可以读取更多数据。这是一个问题,因为解析器不应该被赋予阅读器...而是应该使用推送模型。
考虑一堆要从日志中解析的条目。创建一个馈线的“联系”。它以“空闲”状态开始。然后,当它获取行时,它将行馈送到可能的解析器,直到其中之一可以处理该行。
如果一个人可以处理它(
canParse(line) == true
),它将使解析器“拥有”这些行,直到parseLine(line)
方法返回false为止。 />获取行源获取行,查找是否有任何解析器可以处理。
将行馈送到解析器,直到事件被解析
事件被解析后,通知侦听器完成。
将解析器返回到“钓鱼探险”中寻找解析器。
如果侦听器将事件传递到(阻塞)队列中,则可以在该队列上进行迭代,并根据需要提取事件。一个线程解析文件,其他线程等待所需的数据。
这就是我要做的方式...。
实际代码
我将从类似以下的接口开始:
public interface EntryParseListener<T> {
public void parsedEntry(T entry);
}
public abstract EntryParser<T> {
private final List<EventListener<T>> listeners = new ......
public abstract boolean canParse(String line);
public boolean parseLine(String line);
protected void notifyListeners(T event) {
for (EventListener<T> listener : listeners) {
listener.parsedEntry(event);
}
}
public void addListener(EventListener<T> listener) {
....
}
}
调用此方法时,日志文件中刚刚遇到了侦听器类型的事件。
由抽象类和查找某些事件的具体类管理。仅解析单行的具体类将类似于:
@FunctionalInterface
public interface LogEventListener<T> {
public void logEvent(T event);
}
,它基于底层抽象类:
import java.util.regex.Pattern;
import hearthstone.LogEntryParser;;
public class PowerLineParser extends LogEntryParser<String> {
private static final Pattern POWER = Pattern.compile("\s*\[Power\].*");
@Override
public boolean canParse(String line) {
return POWER.matcher(line).matches();
}
@Override
public String nextLine(String line) {
int pos = line.indexOf("[Power]");
// always return
return "This is power line " + line.substring(pos + 8);
}
@Override
public void close() {
}
}
该解析器可以标识感兴趣的行。如果可以解析该行,则将其反馈给parse方法,然后将所有后续行反馈到parse方法返回true之前。
一个简单的异常也很有用。将其设置为
RuntimeException
也可以使该tp轻松地插入流中:import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public abstract class LogEntryParser<T> {
private final Set<LogEventListener<T>> listeners = new LinkedHashSet<>();
public final void addListener(LogEventListener<T> listener) {
synchronized(listeners) {
listeners.add(listener);
}
}
public final void removeListener(LogEventListener<T> listener) {
synchronized(listeners) {
listeners.remove(listener);
}
}
/**
* Called to determine which parser, if any, will be used to parse the next lines.
*/
public abstract boolean canParse(String line);
/**
* Parse a line of data, Return a complete event, if it is complete, null otherwise
* @param line the next line in the event to parse
* @return the parsed event (if it is ready to be parsed).
*/
public abstract T nextLine(String line);
/**
* Called in the event that no additional data will be arriving.
*/
public abstract void close();
private void notifyListeners(final T event) {
List<LogEventListener<T>> tonotify = getListeners();
for (LogEventListener<T> listener : tonotify) {
listener.logEvent(event);
}
}
public final boolean parse(String line) throws LogParseException {
T event = nextLine(line);
if (event == null) {
return false;
}
notifyListeners(event);
return true;
}
private final List<LogEventListener<T>> getListeners() {
synchronized (listeners) {
return new ArrayList<>(listeners);
}
}
}
驱动该系统的引擎如下所示:加快步伐,如:
public class LogParseException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LogParseException(String message, Throwable cause) {
super(message, cause);
}
public LogParseException(String message) {
super(message);
}
}
评论
为什么会有一个com.github.skiwi2.hearthmonitor.logreaders和一个com.github.skiwi2.hearthmonitor.logreader包?它们之间有什么区别?它们听起来与IMO非常相似。@SimonAndréForsberg一个包含基本API,另一个包含具体的日志读取器实例化...这个问题也有错别字,前一个包称为com.github.skiwi2.hearthmonitor.logreader.logreaders
@skiwi,您可能想使用WatchService监视文件。
LogEntry是抽象的基类/接口,具有子类的各种特定类型的日志条目吗?
@CodesInChaos正确,公共类CreateGameLogEntry实现了LogEntry {作为示例。