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

org.eclipse.jdt.internal.compiler.ast.TextBlock Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2019, 2023 IBM Corporation 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
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;

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

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.jdt.internal.compiler.util.Util;

public class TextBlock extends StringLiteral {

	public int endLineNumber;

	private TextBlock(char[] token, int start, int end, int lineNumber, int endLineNumber) {
		super(token, start,end, lineNumber);
		this.endLineNumber= endLineNumber - 1; // line number is 1 based
	}
	public static TextBlock createTextBlock(char[] token, int start, int end, int lineNumber, int endLineNumber) {
		return new TextBlock(token, start,end, lineNumber, endLineNumber);
	}
	public static char[][] convertTextBlockToLines(char[] all) {
		// 1. Normalize, i.e. convert all CR CRLF to LF
		all = normalize(all);
		// 2. Split into lines. Consider both \n and \r as line separators
		char[][] lines = CharOperation.splitOn('\n', all);
		int size = lines.length;
		List list = new ArrayList<>(lines.length);
		for(int i = 0; i < lines.length; i++) {
			char[] line = lines[i];
			if (i + 1 == size && line.length == 0) {
				list.add(line);
				break;
			}
			char[][] sub = CharOperation.splitOn('\r', line);
			if (sub.length == 0) {
				list.add(line);
			} else {
				for (char[] cs : sub) {
					list.add(cs);
				}
			}
		}
		size = list.size();
		lines = list.toArray(new char[size][]);
		return lines;
	}
	public static int getTextBlockIndent(char[][] lines) {
		int prefix = -1;
		int size = lines.length;
		for(int i = 0; i < size; i++) {
			char[] line = lines[i];
			boolean blank = true;
			int whitespaces = 0;
	 		for (char c : line) {
				if (blank) {
					if (ScannerHelper.isWhitespace(c)) {
						whitespaces++;
					} else {
						blank = false;
					}
				}
			}
	 		// The last line with closing delimiter is part of the
	 		// determining line list even if empty
			if (!blank || (i+1 == size)) {
				if (prefix < 0 || whitespaces < prefix) {
	 				prefix = whitespaces;
				}
			}
		}
		return prefix == -1 ? 0 : prefix;
	}
	public static int getIndentForFragments(char[][] fragments) {
		int prefix = -1;
		for(int index = 0; index < fragments.length; index++) {
			char[][] lines = convertTextBlockToLines(fragments[index]);
			int size = lines.length;
			for(int i = 0; i < size; i++) {
				if (index > 0 && i == 0) {
					continue;
				}
				char[] line = lines[i];
				boolean blank = true;
				int whitespaces = 0;
		 		for (char c : line) {
					if (blank) {
						if (ScannerHelper.isWhitespace(c)) {
							whitespaces++;
						} else {
							blank = false;
						}
					}
				}
		 		// The last line with closing delimiter is part of the
		 		// determining line list even if empty
				if (!blank || (i+1 == size)) {
					if (prefix < 0 || whitespaces < prefix) {
		 				prefix = whitespaces;
					}
				}
			}
		}
		return prefix == -1 ? 0 : prefix;
	}
	public static char[] formatTextBlock(char[] all, int indent, boolean followsExp, boolean precedesExp) {
		char[][] lines = convertTextBlockToLines(all);
		return formatTextBlock(lines, indent, followsExp, precedesExp);
	}
	public static char[] formatTextBlock(char[][] lines, int indent) {
		return formatTextBlock(lines, indent, false, false);
	}
	public static char[] formatTextBlock(char[][] lines, int indent, boolean followsExp, boolean precedesExp) {
		// Handle incidental white space
		// Split into lines and identify determining lines
		// Remove the common white space prefix
		// Handle escape sequences  that are not already done in getNextToken0()
		int size = lines.length;
		StringBuilder result = new StringBuilder();
		boolean newLine = false;
		for(int i = 0; i < size; i++) {
			if (i > 0)
				followsExp = false;
			char[] l  = lines[i];
			int length = l.length;
			int prefix = followsExp ? 0 : indent;
			// Remove the common prefix from each line
			// And remove all trailing whitespace
			// Finally append the \n at the end of the line (except the last line)
			int trail = length;
			// Only the last line is really prefixed to the embedded
			// expression in a string template
			if (!precedesExp || i < (size -1)) {
				for(;trail > 0;) {
					if (!ScannerHelper.isWhitespace(l[trail-1])) {
						break;
					}
					trail--;
				}
			}
			if (i >= (size -1)) {
				if (newLine) result.append('\n');
				if (trail < prefix)
					continue;
				newLine = getLineContent(result, l, prefix, trail-1, false, true);
			} else {
				if (i > 0 && newLine)
					result.append('\n');
				if (trail <= prefix) {
					newLine = true;
				} else {
					boolean merge = length > 0 && l[length - 1] == '\\';
					newLine = getLineContent(result, l, prefix, trail-1, merge, false);
				}
			}
		}
		return result.toString().toCharArray();
	}
	private static char[] normalize(char[] content) {
		StringBuilder result = new StringBuilder();
		boolean isCR = false;
		for (char c : content) {
			switch (c) {
				case '\r':
					result.append(c);
					isCR = true;
					break;
				case '\n':
					if (!isCR) {
						result.append(c);
					}
					isCR = false;
					break;
				default:
					result.append(c);
					isCR = false;
					break;
			}
		}
		return result.toString().toCharArray();
	}
	// This method is for handling the left over escaped characters during the first
	// scanning (scanForStringLiteral). Admittedly this goes over the text block
	// content again char by char, but this is required in order to correctly
	// treat all the white space and line endings
	private static boolean getLineContent(StringBuilder result, char[] line, int start, int end, boolean merge, boolean lastLine) {
		int lastPointer = 0;
		for(int i = start; i < end;) {
			char c = line[i];
			if (c != '\\') {
				i++;
				continue;
			}
			if (i < end) {
				if (lastPointer + 1 <= i) {
					result.append(CharOperation.subarray(line, lastPointer == 0 ? start : lastPointer, i));
				}
				char next = line[++i];
				switch (next) {
					case '\\' :
						result.append('\\');
						if (i == end)
							merge = false;
						break;
					case 's' :
						result.append(' ');
						break;
					case '"':
						result.append('"');
						break;
					case 'b' :
						result.append('\b');
						break;
					case 'n' :
						result.append('\n');
						break;
					case 'r' :
						result.append('\r');
						break;
					case 't' :
						result.append('\t');
						break;
					case 'f' :
						result.append('\f');
						break;
					default :
						// Direct copy from Scanner#scanEscapeCharacter
						int pos = i + 1;
						int number = ScannerHelper.getHexadecimalValue(next);
						if (number >= 0 && number <= 7) {
							boolean zeroToThreeNot = number > 3;
							try {
								if (pos <= end && ScannerHelper.isDigit(next = line[pos])) {
									pos++;
									int digit = ScannerHelper.getHexadecimalValue(next);
									if (digit >= 0 && digit <= 7) {
										number = (number * 8) + digit;
										if (pos <= end && ScannerHelper.isDigit(next = line[pos])) {
											pos++;
											if (zeroToThreeNot) {
												// has read \NotZeroToThree OctalDigit Digit --> ignore last character
											} else {
												digit = ScannerHelper.getHexadecimalValue(next);
												if (digit >= 0 && digit <= 7){ // has read \ZeroToThree OctalDigit OctalDigit
													number = (number * 8) + digit;
												} else {
													// has read \ZeroToThree OctalDigit NonOctalDigit --> ignore last character
												}
											}
										} else {
											// has read \OctalDigit NonDigit--> ignore last character
										}
									} else {
										// has read \OctalDigit NonOctalDigit--> ignore last character
									}
								} else {
									// has read \OctalDigit --> ignore last character
								}
							} catch (InvalidInputException e) {
								// Unlikely as this has already been processed in scanForStringLiteral()
							}
							if (number < 255) {
								next = (char) number;
							}
							result.append(next);
							lastPointer = i = pos;
							continue;
						} else {
							// Dealing with just '\'
							result.append(c);
							lastPointer = i;
							continue;
						}
				}
				lastPointer = ++i;
			}
		}
		end = merge ? end : end >= line.length ? end : end + 1;
		char[] chars = lastPointer == 0 ?
				CharOperation.subarray(line, start, end) :
					CharOperation.subarray(line, lastPointer, end);
		// The below check is because CharOperation.subarray tend to return null when the
		// boundaries produce a zero sized char[]
		if (chars != null && chars.length > 0)
			result.append(chars);
		return (!merge && !lastLine);
	}
	@Override
	public StringBuilder printExpression(int indent, StringBuilder output) {
		output.append("\"\"\"\n"); //$NON-NLS-1$
		for (char c:this.source()) {
			Util.appendEscapedChar(output, c, true);
		}
		output.append("\"\"\""); //$NON-NLS-1$
		return output;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy