All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.lib.commons.string.LineBreakReplacer Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * 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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy