org.conqat.lib.commons.string.LineBreakReplacer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-lib-commons Show documentation
Show all versions of teamscale-lib-commons Show documentation
Provides common utility functions
/*
* Copyright (c) CQSE GmbH
*
* 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.conqat.lib.commons.string;
import static org.conqat.lib.commons.string.LineSplitter.UNICODE_NEL;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* This class is used to replace line breaks in strings. It is written in a way that it will do zero
* String allocations, if nothing needs to be replaced (e.g. we want to replace all line breaks by
* '\n' but they are already all '\n').
*
* @implNote This code has been written with performance in mind and makes a few sacrifices in
* readability towards that goal. Do not change it without profiling the outcome (both CPU
* + memory consumption).
*/
public class LineBreakReplacer {
/** The string content to split. */
private final String content;
private final int contentLength;
private final char replacementCharacter;
private final String replacementCharacterAsString;
/**
* This is lazily initialized to avoid allocating a StringBuilder unless we really need it.
*/
private StringBuilder resultBuilder = null;
/** Constructor */
private LineBreakReplacer(@Nullable String content, char replacementCharacter) {
if (content != null) {
this.content = content;
} else {
this.content = "";
}
contentLength = this.content.length();
this.replacementCharacter = replacementCharacter;
// We do this conversion once, because it becomes expensive if done often
replacementCharacterAsString = String.valueOf(replacementCharacter);
}
private @NonNull String executeReplacement() {
if (content.isEmpty()) {
return content;
}
int lastReplacement = 0;
int currentPosition = 0;
while (currentPosition < contentLength) {
char c = content.charAt(currentPosition);
// Switch-case because it creates a superfast lookup
switch (c) {
case '\r':
if (peek(currentPosition + 1) == '\n') {
getResultBuilder().append(content, lastReplacement, currentPosition)
.append(replacementCharacterAsString);
lastReplacement = currentPosition + 2;
// Need to fast-forward, because we need to skip both characters: "\r\n"
currentPosition++;
break;
}
// fallthrough-intended
case UNICODE_NEL:
case '\n':
if (c != replacementCharacter) {
getResultBuilder().append(content, lastReplacement, currentPosition)
.append(replacementCharacterAsString);
lastReplacement = currentPosition + 1;
}
break;
}
currentPosition++;
}
if (resultBuilder != null) {
// We made some replacements, so finally add the last part to the end of the
// string and the return.
resultBuilder.append(content, lastReplacement, contentLength);
return resultBuilder.toString();
}
return content;
}
private char peek(int offset) {
if (offset < contentLength) {
return content.charAt(offset);
}
return Character.MIN_VALUE;
}
/**
* Replaces all lines breaks ('\r', '\r\n', '\n' and Unicode NEL) with the given replacement
* character.
*
* @return Returns the input with the given replacements applied. If @{code null} is given, an empty
* string is returned. If no replacements are made, the input string is returned.
*/
public static @NonNull String replaceLineBreaks(@Nullable String content, char replacementCharacter) {
return new LineBreakReplacer(content, replacementCharacter).executeReplacement();
}
private StringBuilder getResultBuilder() {
if (resultBuilder == null) {
resultBuilder = new StringBuilder();
}
return resultBuilder;
}
}