Java tutorial
/* * Copyright 2018 the original author or authors. * * 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 org.gradle.integtests.fixtures.executer; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import org.apache.commons.lang.StringUtils; import org.fusesource.jansi.AnsiOutputStream; import org.gradle.api.Action; import org.gradle.api.UncheckedIOException; import org.gradle.internal.Pair; import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; public class LogContent { private final static Pattern DEBUG_PREFIX = Pattern.compile("\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[\\w+] \\[.+?] "); private final static String PROGRESS_BAR_PATTERN = "<[-=(\u001b\\[\\d+[a-zA-Z;])]*> \\d+% (INITIALIZ|CONFIGUR|EXECUT|WAIT)ING( \\[((\\d+h )? \\d+m )?\\d+s\\])?"; private final static String WORK_IN_PROGRESS_PATTERN = "\u001b\\[\\d+[a-zA-Z]> (IDLE|[:a-z][\\w\\s\\d:>/\\\\\\.]+)\u001b\\[\\d*[a-zA-Z]"; private final static String DOWN_MOVEMENT_WITH_NEW_LINE_PATTERN = "\u001b\\[\\d+B\\n"; private final static Pattern WORK_IN_PROGRESS_AREA_PATTERN = Pattern.compile( PROGRESS_BAR_PATTERN + "|" + WORK_IN_PROGRESS_PATTERN + "|" + DOWN_MOVEMENT_WITH_NEW_LINE_PATTERN); private final static Pattern JAVA_ILLEGAL_ACCESS_WARNING_PATTERN = Pattern .compile("(?ms)WARNING: An illegal reflective access operation has occurred$.+?" + "^WARNING: All illegal access operations will be denied in a future release\r?\n"); private final ImmutableList<String> lines; private final boolean definitelyNoDebugPrefix; private final LogContent rawContent; private LogContent(ImmutableList<String> lines, boolean definitelyNoDebugPrefix, LogContent rawContent) { this.lines = lines; this.rawContent = rawContent == null ? this : rawContent; this.definitelyNoDebugPrefix = definitelyNoDebugPrefix || lines.isEmpty(); } /** * Creates a new instance, from raw characters. */ public static LogContent of(String chars) { String stripped = stripWorkInProgressArea(chars); LogContent raw = new LogContent(toLines(stripped), false, null); return new LogContent(toLines(stripJavaIllegalAccessWarnings(stripped)), false, raw); } private static ImmutableList<String> toLines(String chars) { List<String> lines = new ArrayList<String>(); int pos = 0; while (pos < chars.length()) { int next = chars.indexOf('\n', pos); if (next < 0) { lines.add(chars.substring(pos)); pos = chars.length(); continue; } if (next > pos && chars.charAt(next - 1) == '\r') { lines.add(chars.substring(pos, next - 1)); pos = next + 1; } else { lines.add(chars.substring(pos, next)); pos = next + 1; } if (pos == chars.length()) { // trailing EOL lines.add(""); } } return ImmutableList.copyOf(lines); } /** * Creates a new instance from a sequence of lines (without the line separators). */ public static LogContent of(List<String> lines) { return new LogContent(ImmutableList.copyOf(lines), false, null); } public static LogContent empty() { return new LogContent(ImmutableList.<String>of(), true, null); } /** * Returns the original content that this content was built from, after transforms such as {@link #removeDebugPrefix()} or {@link #splitOnFirstMatchingLine(Pattern)}. */ public LogContent getRawContent() { return rawContent; } /** * Does not return the text of this content. */ @Override public String toString() { // Intentionally not the text return lines.toString(); } /** * Returns this content formatted using a new line char to separate lines. */ public String withNormalizedEol() { if (lines.isEmpty()) { return ""; } return Joiner.on('\n').join(lines); } /** * Returns this content separated into lines. The line does not include the line separator. */ public ImmutableList<String> getLines() { return lines; } private LogContent lines(int startLine, int endLine) { if (rawContent != this) { throw new UnsupportedOperationException("not implemented"); } return new LogContent(lines.subList(startLine, endLine), definitelyNoDebugPrefix, null); } /** * Visits each line in this content. The line does not include the line separator. */ public void eachLine(Action<? super String> action) { for (String line : lines) { action.execute(line); } } /** * Locates the log content starting with the first line that matches the given pattern, or null if no such line. * * @return a pair containing (content-before-matching-line, content-from-matching-line) */ public @Nullable Pair<LogContent, LogContent> splitOnFirstMatchingLine(Pattern pattern) { for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); if (pattern.matcher(line).matches()) { LogContent before = new LogContent(lines.subList(0, i), definitelyNoDebugPrefix, rawContent.lines(0, i)); LogContent after = new LogContent(lines.subList(i, lines.size()), definitelyNoDebugPrefix, rawContent.lines(i, lines.size())); return Pair.of(before, after); } } return null; } /** * Returns the number of lines that match the given pattern. */ public int countMatches(Pattern pattern) { int count = 0; for (String line : lines) { if (pattern.matcher(line).matches()) { count++; } } return count; } /** * Drops the first n lines. */ public LogContent drop(int i) { return new LogContent(lines.subList(i, lines.size()), definitelyNoDebugPrefix, rawContent.lines(i, lines.size())); } /** * Returns a copy of this log content with the debug prefix removed. */ public LogContent removeDebugPrefix() { if (definitelyNoDebugPrefix) { return this; } List<String> result = new ArrayList<String>(lines.size()); for (String line : lines) { java.util.regex.Matcher matcher = DEBUG_PREFIX.matcher(line); if (matcher.lookingAt()) { result.add(line.substring(matcher.end())); } else { result.add(line); } } return new LogContent(ImmutableList.copyOf(result), true, rawContent); } /** * Returns a copy of this log content with ANSI control characters removed. */ public LogContent removeAnsiChars() { if (lines.isEmpty()) { return this; } try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer writer = new OutputStreamWriter(new AnsiOutputStream(baos)); for (int i = 0; i < lines.size(); i++) { if (i > 0) { writer.write("\n"); } writer.write(lines.get(i)); } writer.flush(); return new LogContent(toLines(baos.toString()), definitelyNoDebugPrefix, rawContent); } catch (IOException e) { throw new UncheckedIOException(e); } } /** * Remove all blank lines. */ public LogContent removeBlankLines() { List<String> nonBlankLines = lines.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList()); return new LogContent(ImmutableList.copyOf(nonBlankLines), definitelyNoDebugPrefix, rawContent); } public static String stripWorkInProgressArea(String output) { String result = output; for (int i = 1; i <= 10; ++i) { result = result.replaceAll(workInProgressAreaScrollingPattern(i), ""); } return WORK_IN_PROGRESS_AREA_PATTERN.matcher(result).replaceAll(""); } public static String stripJavaIllegalAccessWarnings(String result) { return JAVA_ILLEGAL_ACCESS_WARNING_PATTERN.matcher(result).replaceAll(""); } private static String workInProgressAreaScrollingPattern(int scroll) { return "(\u001b\\[0K\\n){" + scroll + "}\u001b\\[" + scroll + "A"; } }