View Javadoc
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 }