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

org.eclipse.xtend2.lib.StringConcatenation Maven / Gradle / Ivy

There is a newer version: 2.4.3
Show newest version
/*******************************************************************************
 * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.xtend2.lib;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.google.common.annotations.GwtCompatible;

/**
 * 

* A {@link StringConcatenation} allows for efficient, indentation aware concatenation of character sequences. *

*

* In addition to the methods that are specified by the implemented interface {@link CharSequence}, there are some other * public operations that allow to modify the contents of this sequence. The string representation of arbitrary objects * can be appended to an instance of {@link StringConcatenation}. There are overloaded variants of * {@link #append(Object, String)} that allow to pass an indentation that should be applied to each line of the appended * content. Each line break that is part of the new content will be replaced by the line delimiter that was configured * for this {@link StringConcatenation}. *

*

* The {@link #append(Object) append}-operation ignores null arguments. This is different to what * {@link StringBuffer} or {@link StringBuilder} do and that's the reason why the {@link Appendable} interface is not * fulfilled by a {@link StringConcatenation}. *

*

* The object uses an internal {@link List} of {@link String Strings} that is concatenated on demand to a complete * sequence. Use {@link #toString()} to get the joined version of a {@link StringConcatenation}. *

*

* {@link #equals(Object)} and {@link #hashCode()} are not specialized for a {@link StringConcatenation}, that is, the * semantics are based on identity similar to what {@link StringBuffer} and {@link StringBuilder} do. *

* * @author Sebastian Zarnekow - Initial contribution and API * @since 2.3 */ @GwtCompatible(emulated = true) public class StringConcatenation implements CharSequence { /** * The default line delimiter that is used by instances of {@link StringConcatenation}. It uses * System.getProperty("line.separator"). * @since 2.3 */ public static final String DEFAULT_LINE_DELIMITER = System.getProperty("line.separator"); /** * The complete content of this sequence. It may content insignificant trailing parts that are not part of the final * string representation that can be obtained by {@link #toString()}. Insignificant parts will not be considered by * {@link #length()}, {@link #charAt(int)} or {@link #subSequence(int, int)}. */ private final List segments; /** * A cached string representation. */ private String cachedToString; /** * The configured delimiter. It will be used to replace possibly existing delimiters of to-be-appended content. */ private final String lineDelimiter; /** * Create a new concatenation that uses the system line delimiter. * * @see System#getProperties() * @see System#getProperty(String) */ public StringConcatenation() { this(DEFAULT_LINE_DELIMITER); } /** * Create a new concatenation with the specified delimiter. * * @param lineDelimiter * the used delimiter. * @throws IllegalArgumentException * if the lineDelimiter is null or the empty String. * @since 2.3 */ public StringConcatenation(String lineDelimiter) { if (lineDelimiter == null || lineDelimiter.length() == 0) throw new IllegalArgumentException("lineDelimiter must not be null or empty"); this.lineDelimiter = lineDelimiter; segments = new ArrayList(50); } /** * Append the string representation of the given object to this sequence. Does nothing if the object is * null. * * @param object * the to-be-appended object. */ public void append(Object object) { append(object, segments.size()); } /** * Add the string representation of the given object to this sequence at the given index. Does nothing if the object * is null. * * @param object * the to-be-appended object. * @param index * the index in the list of segments. */ protected void append(Object object, int index) { if (object == null) return; if (object instanceof StringConcatenation) { segments.addAll(index, ((StringConcatenation) object).getSignificantContent()); cachedToString = null; return; } String value = object.toString(); List newSegments = splitLinesAndNewLines(value); for (String newSegment : newSegments) { segments.add(index++, newSegment); } if (!newSegments.isEmpty()) cachedToString = null; } /** * Add the string representation of the given object to this sequence. The given indentation will be prepended to * each line except the first one if the object has a multi-line string representation. * * @param object * the appended object. * @param indentation * the indentation string that should be prepended. May not be null. */ public void append(Object object, String indentation) { append(object, indentation, segments.size()); } /** * Add the string representation of the given object to this sequence at the given index. The given indentation will * be prepended to each line except the first one if the object has a multi-line string representation. * * @param object * the to-be-appended object. * @param indentation * the indentation string that should be prepended. May not be null. * @param index * the index in the list of segments. */ protected void append(Object object, String indentation, int index) { if (indentation.length() == 0) { append(object, index); return; } if (object == null) return; if (object instanceof StringConcatenation) { StringConcatenation other = (StringConcatenation) object; List otherSegments = other.getSignificantContent(); appendSegments(indentation, index, otherSegments, other.lineDelimiter); return; } String value = object.toString(); List newSegments = splitLinesAndNewLines(value); appendSegments(indentation, index, newSegments, lineDelimiter); } /** * Add the string representation of the given object to this sequence immediately. That is, all the trailing * whitespace of this sequence will be ignored and the string is appended directly after the last segment that * contains something besides whitespace. The given indentation will be prepended to each line except the first one * if the object has a multi-line string representation. * * @param object * the to-be-appended object. * @param indentation * the indentation string that should be prepended. May not be null. */ public void appendImmediate(Object object, String indentation) { for (int i = segments.size() - 1; i >= 0; i--) { String segment = segments.get(i); for (int j = 0; j < segment.length(); j++) { if (!Character.isWhitespace(segment.charAt(j))) { append(object, indentation, i + 1); return; } } } append(object, indentation, 0); } /** * Add the list of segments to this sequence at the given index. The given indentation will be prepended to each * line except the first one if the object has a multi-line string representation. * * @param indentation * the indentation string that should be prepended. May not be null. * @param index * the index in this instance's list of segments. * @param otherSegments * the to-be-appended segments. May not be null. * @param otherDelimiter * the line delimiter that was used in the otherSegments list. */ protected void appendSegments(String indentation, int index, List otherSegments, String otherDelimiter) { for (String otherSegment : otherSegments) { if (otherDelimiter.equals(otherSegment)) { segments.add(index++, lineDelimiter); segments.add(index++, indentation); } else { segments.add(index++, otherSegment); } } if (!otherSegments.isEmpty()) cachedToString = null; } /** * Add a newline to this sequence according to the configured lineDelimiter. */ public void newLine() { segments.add(lineDelimiter); cachedToString = null; } /** * Add a newline to this sequence according to the configured lineDelimiter if the last line contains * something besides whitespace. */ public void newLineIfNotEmpty() { for (int i = segments.size() - 1; i >= 0; i--) { String segment = segments.get(i); if (lineDelimiter.equals(segment)) { segments.subList(i + 1, segments.size()).clear(); cachedToString = null; return; } for (int j = 0; j < segment.length(); j++) { if (!Character.isWhitespace(segment.charAt(j))) { newLine(); return; } } } segments.clear(); cachedToString = null; } @Override public String toString() { if (cachedToString != null) { return cachedToString; } List significantContent = getSignificantContent(); StringBuilder builder = new StringBuilder(significantContent.size() * 4); for (String segment : significantContent) builder.append(segment); cachedToString = builder.toString(); return cachedToString; } /** * Compute the significant content of this sequence. That is, trailing whitespace after the last line-break will be * ignored if the last line contains only whitespace. The return value is unsafe, that is modification to this * {@link StringConcatenation} will cause changes in a previously obtained result and vice versa. * * @return the significant content of this instance. Never null. */ protected List getSignificantContent() { for (int i = segments.size() - 1; i >= 0; i--) { String segment = segments.get(i); if (lineDelimiter.equals(segment)) { return segments.subList(0, i + 1); } for (int j = 0; j < segment.length(); j++) { if (!Character.isWhitespace(segment.charAt(j))) { return segments; } } } return segments; } /** * {@inheritDoc} * *

* Only the significant content of this sequence is considered. *

*/ public int length() { return toString().length(); } /** * {@inheritDoc} * *

* Only the significant content of this sequence is considered. *

*/ public char charAt(int index) { return toString().charAt(index); } /** * {@inheritDoc} * *

* Only the significant content of this sequence is considered. *

*/ public CharSequence subSequence(int start, int end) { return toString().subSequence(start, end); } /** * Return a list of segments where each segment is either the content of a line in the given text or a line-break * according to the configured {@link #lineDelimiter}. Existing line-breaks in the text will be replaced by this's * instances delimiter. * * @param text * the to-be-splitted text. May be null. * @return a list of segments. Is never null. */ protected List splitLinesAndNewLines(String text) { if (text == null) return Collections.emptyList(); List result = new ArrayList(5); int length = text.length(); int nextLineOffset = 0; int idx = 0; while (idx < length) { char currentChar = text.charAt(idx); // check for \r or \r\n if (currentChar == '\r') { int delimiterLength = 1; if (idx + 1 < length && text.charAt(idx + 1) == '\n') { delimiterLength++; idx++; } int lineLength = idx - delimiterLength - nextLineOffset + 1; result.add(text.substring(nextLineOffset, nextLineOffset + lineLength)); result.add(lineDelimiter); nextLineOffset = idx + 1; } else if (currentChar == '\n') { int lineLength = idx - nextLineOffset; result.add(text.substring(nextLineOffset, nextLineOffset + lineLength)); result.add(lineDelimiter); nextLineOffset = idx + 1; } idx++; } if (nextLineOffset != length) { int lineLength = length - nextLineOffset; result.add(text.substring(nextLineOffset, nextLineOffset + lineLength)); } return result; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy