44
55import java .util .ArrayList ;
66import java .util .List ;
7+ import java .util .Map .Entry ;
78import java .util .PrimitiveIterator ;
89
9- import static io .cucumber .gherkin .GherkinLanguageConstants .COMMENT_PREFIX ;
1010import static io .cucumber .gherkin .GherkinLanguageConstants .TAG_PREFIX ;
11- import static io .cucumber .gherkin .StringUtils .ltrim ;
12- import static io .cucumber .gherkin .StringUtils .ltrimKeepNewLines ;
11+ import static io .cucumber .gherkin .GherkinLanguageConstants .TITLE_KEYWORD_SEPARATOR ;
12+ import static io .cucumber .gherkin .Locations .COLUMN_OFFSET ;
13+ import static io .cucumber .gherkin .StringUtils .containsWhiteSpace ;
1314import static io .cucumber .gherkin .StringUtils .rtrim ;
14- import static io .cucumber .gherkin .StringUtils .rtrimKeepNewLines ;
15- import static io .cucumber .gherkin .StringUtils .symbolCount ;
16- import static io . cucumber . gherkin . StringUtils . trim ;
15+ import static io .cucumber .gherkin .StringUtils .trimAndIndent ;
16+ import static io .cucumber .gherkin .StringUtils .trimAndIndentKeepNewLines ;
17+ import static java . util . Collections . emptyList ;
1718import static java .util .Objects .requireNonNull ;
1819
1920class GherkinLine {
20- // TODO: set this to 0 when/if we change to 0-indexed columns
21- private static final int OFFSET = 1 ;
22- private final String lineText ;
23- private final String trimmedLineText ;
21+
22+ /**
23+ * The line text, including all leading and trailing whitespace characters.
24+ */
25+ private final String rawText ;
26+ private final Location location ;
27+ private final boolean empty ;
28+
29+ /**
30+ * The line text with any whitespace characters trimmed.
31+ */
32+ private final String text ;
33+
34+ /**
35+ * The offset in code-points of the first non-whitespace character in this
36+ * line.
37+ */
2438 private final int indent ;
25- private final Location line ;
2639
27- public GherkinLine (String lineText , Location line ) {
28- this .lineText = requireNonNull (lineText );
29- this .trimmedLineText = trim (lineText );
30- this .line = requireNonNull (line );
31- indent = symbolCount (lineText ) - symbolCount (ltrim (lineText ));
40+ GherkinLine (String rawText , Location location ) {
41+ this .rawText = requireNonNull (rawText );
42+ this .location = requireNonNull (location );
43+ Entry <String , Integer > trimmedIndent = trimAndIndent (rawText );
44+ this .text = trimmedIndent .getKey ();
45+ this .indent = trimmedIndent .getValue ();
46+ this .empty = text .isEmpty ();
3247 }
3348
34- public int indent () {
49+ int getIndent () {
3550 return indent ;
3651 }
3752
38- public String getLineText (int indentToRemove ) {
39- if (indentToRemove < 0 || indentToRemove > indent ())
40- return trimmedLineText ;
41- return lineText .substring (indentToRemove );
53+ String getText () {
54+ return text ;
55+ }
56+
57+ String getRawText () {
58+ return rawText ;
4259 }
4360
44- public boolean isEmpty ( ) {
45- return trimmedLineText . isEmpty ( );
61+ String getRawTextSubstring ( int beginIndex ) {
62+ return rawText . substring ( beginIndex );
4663 }
4764
48- public boolean startsWith ( String prefix ) {
49- return trimmedLineText . startsWith ( prefix ) ;
65+ boolean isEmpty ( ) {
66+ return empty ;
5067 }
5168
52- public String getRestTrimmed ( int length ) {
53- return trimmedLineText . substring ( length ). trim ( );
69+ boolean startsWith ( String prefix ) {
70+ return text . startsWith ( prefix );
5471 }
5572
56- public List <GherkinLineSpan > getTags () {
73+ String substringTrimmed (int beginIndex ) {
74+ return text .substring (beginIndex ).trim ();
75+ }
5776
58- String uncommentedLine = trimmedLineText .split ("\\ s" + COMMENT_PREFIX , 2 )[0 ];
59- List <GherkinLineSpan > tags = new ArrayList <>();
77+ List <GherkinLineSpan > parseTags () {
78+ // in most cases, the line contains no tag, so the code is optimized for this situation
79+ if (empty ) {
80+ return emptyList ();
81+ }
82+ String uncommentedLine = StringUtils .removeComments (text );
6083 int indexInUncommentedLine = 0 ;
6184
6285 String [] elements = uncommentedLine .split (TAG_PREFIX );
86+ if (elements .length == 0 ) {
87+ return emptyList ();
88+ }
89+ List <GherkinLineSpan > tags = new ArrayList <>(elements .length );
6390 for (String element : elements ) {
6491 String token = rtrim (element );
6592 if (token .isEmpty ()) {
6693 continue ;
6794 }
6895 int symbolLength = uncommentedLine .codePointCount (0 , indexInUncommentedLine );
69- int column = indent () + symbolLength + 1 ;
70- if (! token . matches ( "^ \\ S+$" )) {
71- throw new ParserException ("A tag may not contain whitespace" , Locations .atColumn (line , column ));
96+ int column = indent + symbolLength + COLUMN_OFFSET ;
97+ if (containsWhiteSpace ( token )) {
98+ throw new ParserException ("A tag may not contain whitespace" , Locations .atColumn (location , column ));
7299 }
73100 tags .add (new GherkinLineSpan (column , TAG_PREFIX + token ));
74101 indexInUncommentedLine += element .length () + 1 ;
75102 }
76103 return tags ;
77104 }
78105
79- public List <GherkinLineSpan > getTableCells () {
106+ List <GherkinLineSpan > parseTableCells () {
80107 List <GherkinLineSpan > lineSpans = new ArrayList <>();
81108 StringBuilder cellBuilder = new StringBuilder ();
82109 boolean beforeFirst = true ;
83110 int col = 0 ;
84111 int cellStart = 0 ;
85112 boolean escape = false ;
86- PrimitiveIterator .OfInt iterator = lineText .codePoints ().iterator ();
113+ PrimitiveIterator .OfInt iterator = text .codePoints ().iterator ();
87114 while (iterator .hasNext ()) {
88115 int c = iterator .next ();
89116 if (escape ) {
@@ -112,10 +139,9 @@ public List<GherkinLineSpan> getTableCells() {
112139 // Skip the first empty span
113140 beforeFirst = false ;
114141 } else {
115- String cell = cellBuilder .toString ();
116- String leftTrimmedCell = ltrimKeepNewLines (cell );
117- int cellIndent = symbolCount (cell ) - symbolCount (leftTrimmedCell );
118- lineSpans .add (new GherkinLineSpan (cellStart + cellIndent + OFFSET , rtrimKeepNewLines (leftTrimmedCell )));
142+ Entry <String , Integer > trimmedCellIndent = trimAndIndentKeepNewLines (cellBuilder .toString ());
143+ int column = indent + cellStart + trimmedCellIndent .getValue () + COLUMN_OFFSET ;
144+ lineSpans .add (new GherkinLineSpan (column , trimmedCellIndent .getKey ()));
119145 }
120146 cellBuilder = new StringBuilder ();
121147 cellStart = col + 1 ;
@@ -128,11 +154,11 @@ public List<GherkinLineSpan> getTableCells() {
128154 return lineSpans ;
129155 }
130156
131- public boolean startsWithTitleKeyword (String text ) {
132- int textLength = text .length ();
133- return trimmedLineText .length () > textLength &&
134- trimmedLineText .startsWith (text ) &&
135- trimmedLineText .startsWith (GherkinLanguageConstants . TITLE_KEYWORD_SEPARATOR , textLength );
157+ boolean startsWithTitleKeyword (String keyword ) {
158+ int keywordLength = keyword .length ();
159+ return text .length () > keywordLength &&
160+ text .startsWith (keyword ) &&
161+ text .startsWith (TITLE_KEYWORD_SEPARATOR , keywordLength );
136162 }
137163
138164}
0 commit comments