1 /*
2 * Themis: Java Project Framework
3 * Copyright 2012-2026. Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17 package io.github.tonywasher.joceanus.themis.lethe.analysis;
18
19 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20 import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
21 import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisGeneric.ThemisAnalysisGenericBase;
22
23 import java.nio.CharBuffer;
24 import java.util.Deque;
25
26 /**
27 * Line buffer.
28 */
29 public class ThemisAnalysisLine
30 implements ThemisAnalysisElement {
31 /**
32 * The token terminators.
33 */
34 private static final char[] TERMINATORS = {
35 ThemisAnalysisChar.PARENTHESIS_OPEN,
36 ThemisAnalysisChar.PARENTHESIS_CLOSE,
37 ThemisAnalysisChar.GENERIC_OPEN,
38 ThemisAnalysisChar.COMMA,
39 ThemisAnalysisChar.SEMICOLON,
40 ThemisAnalysisChar.COLON,
41 ThemisAnalysisChar.ARRAY_OPEN
42 };
43
44 /**
45 * The line buffer.
46 */
47 private final CharBuffer theBuffer;
48
49 /**
50 * The properties.
51 */
52 private ThemisAnalysisProperties theProperties;
53
54 /**
55 * Constructor.
56 *
57 * @param pBuffer the buffer
58 * @param pOffset the offset to copy from
59 * @param pLen the length
60 */
61 ThemisAnalysisLine(final char[] pBuffer,
62 final int pOffset,
63 final int pLen) {
64 /* create the buffer */
65 final char[] myBuffer = new char[pLen];
66 System.arraycopy(pBuffer, pOffset, myBuffer, 0, pLen);
67 theBuffer = CharBuffer.wrap(myBuffer);
68
69 /* Strip any leading/trailing whiteSpace */
70 stripLeadingWhiteSpace();
71 stripTrailingWhiteSpace();
72
73 /* Set null properties */
74 theProperties = ThemisAnalysisProperties.NULL;
75 }
76
77 /**
78 * Constructor.
79 *
80 * @param pBuffer the buffer
81 */
82 ThemisAnalysisLine(final CharBuffer pBuffer) {
83 /* Store values */
84 theBuffer = pBuffer;
85
86 /* Set null properties */
87 theProperties = ThemisAnalysisProperties.NULL;
88 }
89
90 /**
91 * Constructor.
92 *
93 * @param pLine the line
94 */
95 ThemisAnalysisLine(final ThemisAnalysisLine pLine) {
96 /* Store values */
97 final CharBuffer myBuffer = pLine.theBuffer;
98 theBuffer = myBuffer.subSequence(0, myBuffer.length());
99
100 /* Set null properties */
101 theProperties = ThemisAnalysisProperties.NULL;
102 }
103
104 /**
105 * Constructor.
106 *
107 * @param pLines the lines
108 */
109 ThemisAnalysisLine(final Deque<ThemisAnalysisElement> pLines) {
110 /* Create a stringBuilder */
111 final StringBuilder myBuilder = new StringBuilder();
112
113 /* Loop through the lines */
114 for (ThemisAnalysisElement myLine : pLines) {
115 myBuilder.append(((ThemisAnalysisLine) myLine).theBuffer);
116 myBuilder.append(ThemisAnalysisChar.BLANK);
117 }
118
119 /* Create the buffer */
120 theBuffer = CharBuffer.wrap(myBuilder.toString());
121 stripLeadingWhiteSpace();
122 stripTrailingWhiteSpace();
123
124 /* Set null properties */
125 theProperties = ThemisAnalysisProperties.NULL;
126 }
127
128 /**
129 * Obtain the length.
130 *
131 * @return the length
132 */
133 public int getLength() {
134 return theBuffer.remaining();
135 }
136
137 /**
138 * Set the new length.
139 *
140 * @param pLen the length
141 */
142 private void setLength(final int pLen) {
143 theBuffer.limit(theBuffer.position() + pLen);
144 }
145
146 /**
147 * Adjust the position.
148 *
149 * @param pAdjust the adjustment
150 */
151 private void adjustPosition(final int pAdjust) {
152 theBuffer.position(theBuffer.position() + pAdjust);
153 }
154
155 /**
156 * Obtain the character at the given position.
157 *
158 * @param pIndex the position of the character
159 * @return the character
160 */
161 char charAt(final int pIndex) {
162 return theBuffer.charAt(pIndex);
163 }
164
165 /**
166 * Obtain the properties.
167 *
168 * @return the properties
169 */
170 public ThemisAnalysisProperties getProperties() {
171 return theProperties;
172 }
173
174 /**
175 * Strip trailing comments.
176 *
177 * @throws OceanusException on error
178 */
179 void stripTrailingComments() throws OceanusException {
180 /* Loop through the characters */
181 final int myLength = getLength();
182 int mySkipped = 0;
183 for (int i = 0; i < myLength - mySkipped - 1; i++) {
184 /* Access position and current character */
185 final int myPos = i + mySkipped;
186 final char myChar = theBuffer.charAt(myPos);
187
188 /* If this is a single/double quote */
189 if (myChar == ThemisAnalysisChar.SINGLEQUOTE
190 || myChar == ThemisAnalysisChar.DOUBLEQUOTE) {
191 final int myEnd = findEndOfQuotedSequence(myPos);
192 mySkipped += myEnd - myPos;
193
194 /* If we have a line comment */
195 } else if (myChar == ThemisAnalysisChar.COMMENT
196 && theBuffer.charAt(myPos + 1) == ThemisAnalysisChar.COMMENT) {
197 /* Reset the length */
198 setLength(myPos);
199 stripTrailingWhiteSpace();
200 break;
201 }
202 }
203 }
204
205 /**
206 * Trim leading whiteSpace.
207 */
208 void stripLeadingWhiteSpace() {
209 /* Loop through the characters */
210 int myWhiteSpace = 0;
211 final int myLength = getLength();
212 for (int i = 0; i < myLength; i++) {
213 /* Break loop if not whiteSpace */
214 if (!Character.isWhitespace(theBuffer.charAt(i))) {
215 break;
216 }
217
218 /* Increment count */
219 myWhiteSpace++;
220 }
221
222 /* Adjust position */
223 adjustPosition(myWhiteSpace);
224 }
225
226 /**
227 * Trim trailing whiteSpace.
228 */
229 private void stripTrailingWhiteSpace() {
230 /* Loop through the characters */
231 int myWhiteSpace = 0;
232 final int myLength = getLength();
233 for (int i = myLength - 1; i >= 0; i--) {
234 /* Break loop if not whiteSpace */
235 if (!Character.isWhitespace(theBuffer.charAt(i))) {
236 break;
237 }
238
239 /* Increment count */
240 myWhiteSpace++;
241 }
242
243 /* Adjust length */
244 setLength(myLength - myWhiteSpace);
245 }
246
247 /**
248 * Mark the line.
249 */
250 void mark() {
251 theBuffer.mark();
252 }
253
254 /**
255 * Reset the line.
256 */
257 void reset() {
258 theBuffer.reset();
259 }
260
261 /**
262 * Strip Modifiers.
263 *
264 * @throws OceanusException on error
265 */
266 void stripModifiers() throws OceanusException {
267 /* Loop while we find a modifier */
268 boolean bContinue = true;
269 while (bContinue) {
270 /* If we have a generic variable list */
271 if (ThemisAnalysisGeneric.isGeneric(this)) {
272 /* Declare them to the properties */
273 theProperties = theProperties.setGenericVariables(new ThemisAnalysisGenericBase(this));
274 }
275
276 /* Access the next token */
277 final String nextToken = peekNextToken();
278 bContinue = false;
279
280 /* If this is a modifier */
281 final ThemisAnalysisModifier myModifier = ThemisAnalysisModifier.findModifier(nextToken);
282 if (myModifier != null) {
283 /* Set modifier */
284 theProperties = theProperties.setModifier(myModifier);
285 stripStartSequence(nextToken);
286 bContinue = true;
287 }
288 }
289 }
290
291 /**
292 * Does line start with identifier?
293 *
294 * @param pIdentifier the identifier
295 * @return true/false
296 * @throws OceanusException on error
297 */
298 boolean isStartedBy(final String pIdentifier) throws OceanusException {
299 /* If the line is too short, just return */
300 final int myIdLen = pIdentifier.length();
301 final int myLength = getLength();
302 if (myIdLen > myLength) {
303 return false;
304 }
305
306 /* Loop through the identifier */
307 for (int i = 0; i < myIdLen; i++) {
308 if (theBuffer.charAt(i) != pIdentifier.charAt(i)) {
309 return false;
310 }
311 }
312
313 /* Catch any solo modifiers */
314 if (myIdLen == myLength) {
315 throw new ThemisDataException("Modifier found without object");
316 }
317
318 /* The next character must be whitespace */
319 if (Character.isWhitespace(theBuffer.charAt(myIdLen))) {
320 adjustPosition(myIdLen + 1);
321 stripLeadingWhiteSpace();
322 return true;
323 }
324
325 /* False alarm */
326 return false;
327 }
328
329 /**
330 * Strip NextToken.
331 *
332 * @return the next token
333 */
334 String stripNextToken() {
335 /* Access the next token */
336 final String myToken = peekNextToken();
337 stripStartSequence(myToken);
338 return myToken;
339 }
340
341 /**
342 * Peek NextToken.
343 *
344 * @return the next token
345 */
346 String peekNextToken() {
347 /* Loop through the buffer */
348 final int myLength = getLength();
349 for (int i = 0; i < myLength; i++) {
350 /* if we have hit whiteSpace or a terminator */
351 final char myChar = theBuffer.charAt(i);
352 if (isTerminator(myChar)) {
353 /* Strip out the characters */
354 final CharBuffer myToken = theBuffer.subSequence(0, i);
355 return myToken.toString();
356 }
357 }
358
359 /* Whole buffer is the token */
360 return toString();
361 }
362
363 /**
364 * Strip NextToken.
365 *
366 * @return the next token
367 */
368 String stripLastToken() {
369 /* Access the next token */
370 final String myToken = peekLastToken();
371 stripEndSequence(myToken);
372 return myToken;
373 }
374
375 /**
376 * Peek lastToken.
377 *
378 * @return the last token
379 */
380 String peekLastToken() {
381 /* Loop through the buffer */
382 final int myLength = getLength();
383 for (int i = myLength - 1; i >= 0; i--) {
384 /* if we have hit whiteSpace or a terminator */
385 final char myChar = theBuffer.charAt(i);
386 if (isTerminator(myChar)) {
387 /* Strip out the characters */
388 final CharBuffer myToken = theBuffer.subSequence(i + 1, myLength);
389 return myToken.toString();
390 }
391 }
392
393 /* Whole buffer is the token */
394 return toString();
395 }
396
397 /**
398 * Is the character a token terminator?
399 *
400 * @param pChar the character
401 * @return true/false
402 */
403 private static boolean isTerminator(final char pChar) {
404 /* WhiteSpace is a terminator */
405 if (Character.isWhitespace(pChar)) {
406 return true;
407 }
408
409 /* Check whether char is any of the terminators */
410 return isInList(pChar, TERMINATORS);
411 }
412
413 /**
414 * Is the character in the list?
415 *
416 * @param pChar the character
417 * @param pList the list of characters
418 * @return true/false
419 */
420 private static boolean isInList(final char pChar,
421 final char[] pList) {
422 /* Loop through the list */
423 for (char myChar : pList) {
424 /* if we have matched */
425 if (pChar == myChar) {
426 return true;
427 }
428 }
429
430 /* Not a terminator */
431 return false;
432 }
433
434 /**
435 * Strip data up to position.
436 *
437 * @param pPosition the position to strip to (inclusive)
438 * @return the stripped line
439 */
440 ThemisAnalysisLine stripUpToPosition(final int pPosition) {
441 /* Obtain the new buffer */
442 final CharBuffer myChars = theBuffer.subSequence(0, pPosition + 1);
443 adjustPosition(pPosition + 1);
444 stripLeadingWhiteSpace();
445 return new ThemisAnalysisLine(myChars);
446 }
447
448 /**
449 * Does line start with the sequence?
450 *
451 * @param pSequence the sequence
452 * @return true/false
453 */
454 boolean startsWithSequence(final CharSequence pSequence) {
455 /* If the line is too short, just return */
456 final int mySeqLen = pSequence.length();
457 final int myLength = getLength();
458 if (mySeqLen > myLength) {
459 return false;
460 }
461
462 /* Loop through the sequence */
463 for (int i = 0; i < mySeqLen; i++) {
464 if (theBuffer.charAt(i) != pSequence.charAt(i)) {
465 return false;
466 }
467 }
468
469 /* True */
470 return true;
471 }
472
473 /**
474 * Does line start with the character?
475 *
476 * @param pChar the character
477 * @return true/false
478 */
479 boolean startsWithChar(final char pChar) {
480 /* If the line is too short, just return false */
481 final int myLength = getLength();
482 if (myLength == 0) {
483 return false;
484 }
485
486 /* Test the character */
487 return theBuffer.charAt(0) == pChar;
488 }
489
490 /**
491 * Strip the starting sequence.
492 *
493 * @param pSequence the sequence
494 */
495 void stripStartSequence(final CharSequence pSequence) {
496 /* If the line starts with the sequence */
497 if (startsWithSequence(pSequence)) {
498 /* adjust the length */
499 adjustPosition(pSequence.length());
500 stripLeadingWhiteSpace();
501 }
502 }
503
504 /**
505 * Strip the starting character.
506 *
507 * @param pChar the character
508 */
509 void stripStartChar(final char pChar) {
510 /* If the line starts with the character */
511 if (startsWithChar(pChar)) {
512 /* adjust the length */
513 adjustPosition(1);
514 stripLeadingWhiteSpace();
515 }
516 }
517
518 /**
519 * Does line end with the sequence?
520 *
521 * @param pSequence the sequence
522 * @return true/false
523 */
524 boolean endsWithSequence(final CharSequence pSequence) {
525 /* If the line is too short, just return */
526 final int mySeqLen = pSequence.length();
527 final int myLength = getLength();
528 if (mySeqLen > myLength) {
529 return false;
530 }
531
532 /* Loop through the buffer */
533 final int myBase = myLength - mySeqLen;
534 for (int i = 0; i < mySeqLen; i++) {
535 /* Loop through the sequence */
536 if (theBuffer.charAt(i + myBase) != pSequence.charAt(i)) {
537 /* Not found */
538 return false;
539 }
540 }
541
542 /* found */
543 return true;
544 }
545
546 /**
547 * Does line end with the character?
548 *
549 * @param pChar the character
550 * @return true/false
551 */
552 boolean endsWithChar(final char pChar) {
553 /* If the line is too short, just return false */
554 final int myLength = getLength();
555 if (myLength == 0) {
556 return false;
557 }
558
559 /* Test the character */
560 return theBuffer.charAt(myLength - 1) == pChar;
561 }
562
563 /**
564 * Strip the starting sequence.
565 *
566 * @param pSequence the sequence
567 */
568 void stripEndSequence(final CharSequence pSequence) {
569 /* If the line ends with the sequence */
570 if (endsWithSequence(pSequence)) {
571 /* adjust the length */
572 setLength(getLength() - pSequence.length());
573 stripTrailingWhiteSpace();
574 }
575 }
576
577 /**
578 * Strip the ending character.
579 *
580 * @param pChar the character
581 */
582 void stripEndChar(final char pChar) {
583 /* If the line ends with the sequence */
584 if (endsWithChar(pChar)) {
585 /* adjust the length */
586 setLength(getLength() - 1);
587 stripTrailingWhiteSpace();
588 }
589 }
590
591 /**
592 * Find end of nested sequence, allowing for escaped quotes.
593 * <p>
594 * To enable distinction between finding the end of the sequence from still being nested, the nestLevel
595 * is negative. Hence a result that is negative indicates that the sequence is continuing.
596 * </p>
597 *
598 * @param pStart the start position
599 * @param pLevel the current nestLevel (negative value)
600 * @param pTerm the end nest character
601 * @param pNest the start nest character
602 * @return the position of the end of the nest if (non-negative), or nestLevel (negative) if not terminated.
603 * @throws OceanusException on error
604 */
605 int findEndOfNestedSequence(final int pStart,
606 final int pLevel,
607 final char pTerm,
608 final char pNest) throws OceanusException {
609 /* Access details of quote */
610 final int myLength = getLength();
611
612 /* Loop through the line */
613 boolean maybeComment = false;
614 int myNested = pLevel;
615 int mySkipped = 0;
616 for (int i = pStart; i < myLength - mySkipped; i++) {
617 /* Access position and current character */
618 final int myPos = i + mySkipped;
619 final char myChar = theBuffer.charAt(myPos);
620
621 /* If this is the comment character */
622 if (myChar == ThemisAnalysisChar.COMMENT) {
623 /* Flip flag */
624 maybeComment = !maybeComment;
625
626 /* If we have double comment, break loop */
627 if (!maybeComment) {
628 break;
629 }
630 } else {
631 maybeComment = false;
632 }
633
634 /* If this is a single/double quote */
635 if (myChar == ThemisAnalysisChar.SINGLEQUOTE
636 || myChar == ThemisAnalysisChar.DOUBLEQUOTE) {
637 /* Find the end of the sequence and skip the quotes */
638 final int myEnd = findEndOfQuotedSequence(myPos);
639 mySkipped += myEnd - myPos;
640
641 /* If we should be increasing the nest level */
642 } else if (myChar == pNest) {
643 myNested--;
644
645 /* If we should be decreasing the nest level */
646 } else if (myChar == pTerm) {
647 myNested++;
648
649 /* Return current position if we have finished */
650 if (myNested == 0) {
651 return myPos;
652 }
653 }
654 }
655
656 /* Return the new nest level */
657 return myNested;
658 }
659
660 /**
661 * Find end of single/double quoted sequence, allowing for escaped quote.
662 * <p>
663 * Note that a quoted sequence cannot span lines.
664 * </p>
665 *
666 * @param pStart the start position of the quote
667 * @return the end position of the sequence.
668 * @throws OceanusException on error
669 */
670 int findEndOfQuotedSequence(final int pStart) throws OceanusException {
671 /* Access details of single/double quote */
672 final int myLength = getLength();
673 final char myQuote = theBuffer.charAt(pStart);
674
675 /* Loop through the characters */
676 int mySkipped = 0;
677 for (int i = pStart + 1; i < myLength - mySkipped; i++) {
678 /* Access position and current character */
679 final int myPos = i + mySkipped;
680 final char myChar = theBuffer.charAt(myPos);
681
682 /* Skip escaped character */
683 if (myChar == ThemisAnalysisChar.ESCAPE) {
684 mySkipped++;
685
686 /* Return current position if we have finished */
687 } else if (myChar == myQuote) {
688 return myPos;
689 }
690 }
691
692 /* We should always be terminated */
693 throw new ThemisDataException("Unable to find end of quote in line");
694 }
695
696 @Override
697 public String toString() {
698 return theBuffer.toString();
699 }
700 }