1 | /* |
---|---|
2 | * Copyright (C) 2007-2010 JĂșlio Vilmar Gesser. |
3 | * Copyright (C) 2011, 2013-2020 The JavaParser Team. |
4 | * |
5 | * This file is part of JavaParser. |
6 | * |
7 | * JavaParser can be used either under the terms of |
8 | * a) the GNU Lesser General Public License as published by |
9 | * the Free Software Foundation, either version 3 of the License, or |
10 | * (at your option) any later version. |
11 | * b) the terms of the Apache License |
12 | * |
13 | * You should have received a copy of both licenses in LICENCE.LGPL and |
14 | * LICENCE.APACHE. Please refer to those files for details. |
15 | * |
16 | * JavaParser is distributed in the hope that it will be useful, |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | * GNU Lesser General Public License for more details. |
20 | */ |
21 | package com.github.javaparser.ast.expr; |
22 | |
23 | import com.github.javaparser.TokenRange; |
24 | import com.github.javaparser.ast.AllFieldsConstructor; |
25 | import com.github.javaparser.ast.Generated; |
26 | import com.github.javaparser.ast.Node; |
27 | import com.github.javaparser.ast.visitor.CloneVisitor; |
28 | import com.github.javaparser.ast.visitor.GenericVisitor; |
29 | import com.github.javaparser.ast.visitor.VoidVisitor; |
30 | import com.github.javaparser.metamodel.JavaParserMetaModel; |
31 | import com.github.javaparser.metamodel.TextBlockLiteralExprMetaModel; |
32 | import com.github.javaparser.utils.Pair; |
33 | import java.util.Arrays; |
34 | import java.util.Optional; |
35 | import java.util.function.Consumer; |
36 | import java.util.stream.Stream; |
37 | import static com.github.javaparser.utils.StringEscapeUtils.unescapeJavaTextBlock; |
38 | import static java.util.stream.Collectors.joining; |
39 | import static java.util.stream.IntStream.range; |
40 | |
41 | /** |
42 | * <h1>A text block</h1> |
43 | * <h2>Java 13-</h2> |
44 | * A text block is a multi-line string. It was introduced in JEP 355. |
45 | * The content of "value" is byte-for-byte exactly what is in the source code. |
46 | */ |
47 | public class TextBlockLiteralExpr extends LiteralStringValueExpr { |
48 | |
49 | public TextBlockLiteralExpr() { |
50 | this(null, "empty"); |
51 | } |
52 | |
53 | /** |
54 | * Creates a text block literal expression from given string. |
55 | * |
56 | * @param value the value of the literal |
57 | */ |
58 | @AllFieldsConstructor |
59 | public TextBlockLiteralExpr(final String value) { |
60 | this(null, value); |
61 | } |
62 | |
63 | /** |
64 | * This constructor is used by the parser and is considered private. |
65 | */ |
66 | @Generated("com.github.javaparser.generator.core.node.MainConstructorGenerator") |
67 | public TextBlockLiteralExpr(TokenRange tokenRange, String value) { |
68 | super(tokenRange, value); |
69 | customInitialization(); |
70 | } |
71 | |
72 | @Override |
73 | @Generated("com.github.javaparser.generator.core.node.AcceptGenerator") |
74 | public <R, A> R accept(final GenericVisitor<R, A> v, final A arg) { |
75 | return v.visit(this, arg); |
76 | } |
77 | |
78 | @Override |
79 | @Generated("com.github.javaparser.generator.core.node.AcceptGenerator") |
80 | public <A> void accept(final VoidVisitor<A> v, final A arg) { |
81 | v.visit(this, arg); |
82 | } |
83 | |
84 | @Override |
85 | @Generated("com.github.javaparser.generator.core.node.RemoveMethodGenerator") |
86 | public boolean remove(Node node) { |
87 | if (node == null) |
88 | return false; |
89 | return super.remove(node); |
90 | } |
91 | |
92 | @Override |
93 | @Generated("com.github.javaparser.generator.core.node.TypeCastingGenerator") |
94 | public boolean isTextBlockLiteralExpr() { |
95 | return true; |
96 | } |
97 | |
98 | @Override |
99 | @Generated("com.github.javaparser.generator.core.node.TypeCastingGenerator") |
100 | public TextBlockLiteralExpr asTextBlockLiteralExpr() { |
101 | return this; |
102 | } |
103 | |
104 | @Override |
105 | @Generated("com.github.javaparser.generator.core.node.TypeCastingGenerator") |
106 | public Optional<TextBlockLiteralExpr> toTextBlockLiteralExpr() { |
107 | return Optional.of(this); |
108 | } |
109 | |
110 | @Generated("com.github.javaparser.generator.core.node.TypeCastingGenerator") |
111 | public void ifTextBlockLiteralExpr(Consumer<TextBlockLiteralExpr> action) { |
112 | action.accept(this); |
113 | } |
114 | |
115 | @Override |
116 | @Generated("com.github.javaparser.generator.core.node.ReplaceMethodGenerator") |
117 | public boolean replace(Node node, Node replacementNode) { |
118 | if (node == null) |
119 | return false; |
120 | return super.replace(node, replacementNode); |
121 | } |
122 | |
123 | @Override |
124 | @Generated("com.github.javaparser.generator.core.node.CloneGenerator") |
125 | public TextBlockLiteralExpr clone() { |
126 | return (TextBlockLiteralExpr) accept(new CloneVisitor(), null); |
127 | } |
128 | |
129 | @Override |
130 | @Generated("com.github.javaparser.generator.core.node.GetMetaModelGenerator") |
131 | public TextBlockLiteralExprMetaModel getMetaModel() { |
132 | return JavaParserMetaModel.textBlockLiteralExprMetaModel; |
133 | } |
134 | |
135 | /** |
136 | * Most of the algorithm for stripIndent, stopping just before concatenating all the lines into a single string. |
137 | * Useful for tools. |
138 | */ |
139 | public Stream<String> stripIndentOfLines() { |
140 | /* Split the content of the text block at every LF, producing a list of individual lines. |
141 | Note that any line in the content which was just an LF will become an empty line in the list of individual lines. */ |
142 | String[] rawLines = getValue().split("\\R", -1); |
143 | /* Add all non-blank lines from the list of individual lines into a set of determining lines. |
144 | (Blank lines -- lines that are empty or are composed wholly of white space -- have no visible influence on the indentation. |
145 | Excluding blank lines from the set of determining lines avoids throwing off step 4 of the algorithm.) */ |
146 | /* If the last line in the list of individual lines (i.e., the line with the closing delimiter) is blank, then add it to the set of determining lines. |
147 | (The indentation of the closing delimiter should influence the indentation of the content as a whole -- a "significant trailing line" policy.) */ |
148 | /* Compute the common white space prefix of the set of determining lines, by counting the number of leading white space characters on each line and taking the minimum count. */ |
149 | int commonWhiteSpacePrefixSize = range(0, rawLines.length).mapToObj(nr -> new Pair<>(nr, rawLines[nr])).filter(l -> !emptyOrWhitespace(l.b) || isLastLine(rawLines, l.a)).map(l -> indentSize(l.b)).min(Integer::compare).orElse(0); |
150 | /* Remove the common white space prefix from each non-blank line in the list of individual lines. */ |
151 | /* Remove all trailing white space from all lines in the modified list of individual lines from step 5. |
152 | This step collapses wholly-whitespace lines in the modified list so that they are empty, but does not discard them. */ |
153 | return Arrays.stream(rawLines).map(l -> l.substring(commonWhiteSpacePrefixSize)).map(this::trimTrailing); |
154 | } |
155 | |
156 | /** |
157 | * @return The algorithm from String::stripIndent in JDK 13. |
158 | */ |
159 | public String stripIndent() { |
160 | /* Construct the result string by joining all the lines in the modified list of individual lines from step 6, using LF as the separator between lines. |
161 | If the final line in the list from step 6 is empty, then the joining LF from the previous line will be the last character in the result string. */ |
162 | return stripIndentOfLines().collect(joining("\n")); |
163 | } |
164 | |
165 | /** |
166 | * @return The algorithm from String::translateEscapes in JDK 13. |
167 | */ |
168 | public String translateEscapes() { |
169 | return unescapeJavaTextBlock(stripIndent()); |
170 | } |
171 | |
172 | /** |
173 | * @return the final string value of this text block after all processing. |
174 | */ |
175 | public String asString() { |
176 | return translateEscapes(); |
177 | } |
178 | |
179 | /** |
180 | * @return is the line with index lineNr the last line in rawLines? |
181 | */ |
182 | private boolean isLastLine(String[] rawLines, Integer lineNr) { |
183 | return lineNr == rawLines.length - 1; |
184 | } |
185 | |
186 | /** |
187 | * @return is this string empty or filled only with whitespace? |
188 | */ |
189 | private boolean emptyOrWhitespace(String rawLine) { |
190 | return rawLine.trim().isEmpty(); |
191 | } |
192 | |
193 | /** |
194 | * @return the amount of leading whitespaces. |
195 | */ |
196 | private int indentSize(String s) { |
197 | String content = s.trim(); |
198 | if (content.isEmpty()) { |
199 | return s.length(); |
200 | } |
201 | return s.indexOf(content); |
202 | } |
203 | |
204 | /** |
205 | * Can be replaced when moving to JDK 11 |
206 | */ |
207 | private String trimTrailing(String source) { |
208 | int pos = source.length() - 1; |
209 | while ((pos >= 0) && Character.isWhitespace(source.charAt(pos))) { |
210 | pos--; |
211 | } |
212 | pos++; |
213 | return (pos < source.length()) ? source.substring(0, pos) : source; |
214 | } |
215 | } |
216 |
Members