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.exc.ThemisIOException;
22  import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisDataMap.ThemisAnalysisDataType;
23  import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisDataMap.ThemisAnalysisIntermediate;
24  
25  import java.io.BufferedReader;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.InputStreamReader;
31  import java.nio.charset.StandardCharsets;
32  import java.util.ArrayDeque;
33  import java.util.Deque;
34  import java.util.List;
35  
36  /**
37   * Analysis representation of a java file.
38   */
39  public class ThemisAnalysisFile
40          implements ThemisAnalysisContainer, ThemisAnalysisIntermediate {
41      /**
42       * Object class.
43       */
44      public interface ThemisAnalysisObject
45              extends ThemisAnalysisDataType, ThemisAnalysisContainer {
46          /**
47           * Obtain the short name.
48           *
49           * @return the name
50           */
51          String getShortName();
52  
53          /**
54           * Obtain the full name of the object.
55           *
56           * @return the fullName
57           */
58          String getFullName();
59  
60          /**
61           * Obtain ancestors.
62           *
63           * @return the list of ancestors
64           */
65          List<ThemisAnalysisReference> getAncestors();
66  
67          /**
68           * Obtain properties.
69           *
70           * @return the properties
71           */
72          ThemisAnalysisProperties getProperties();
73      }
74  
75      /**
76       * The buffer length (must be longer than longest line).
77       */
78      private static final int BUFLEN = 1024;
79  
80      /**
81       * The location of the file.
82       */
83      private final File theLocation;
84  
85      /**
86       * The name of the file.
87       */
88      private final String theName;
89  
90      /**
91       * The package file.
92       */
93      private final ThemisAnalysisPackage thePackage;
94  
95      /**
96       * The dataMap.
97       */
98      private final ThemisAnalysisDataMap theDataMap;
99  
100     /**
101      * The contents.
102      */
103     private final Deque<ThemisAnalysisElement> theContents;
104 
105     /**
106      * The number of lines in the file.
107      */
108     private int theNumLines;
109 
110     /**
111      * Constructor.
112      *
113      * @param pPackage the package
114      * @param pFile    the file to analyse
115      */
116     ThemisAnalysisFile(final ThemisAnalysisPackage pPackage,
117                        final File pFile) {
118         /* Store the parameters */
119         thePackage = pPackage;
120         theLocation = pFile;
121         theName = pFile.getName().replace(ThemisAnalysisPackage.SFX_JAVA, "");
122         theDataMap = new ThemisAnalysisDataMap(thePackage.getDataMap());
123 
124         /* Create the list */
125         theContents = new ArrayDeque<>();
126     }
127 
128     /**
129      * Obtain the name of the fileClass.
130      *
131      * @return the name
132      */
133     public String getName() {
134         return theName;
135     }
136 
137     /**
138      * Obtain the location of the fileClass.
139      *
140      * @return the location
141      */
142     public String getLocation() {
143         return theLocation.getAbsolutePath();
144     }
145 
146     /**
147      * Process the file.
148      *
149      * @throws OceanusException on error
150      */
151     void processFile() throws OceanusException {
152         /* Create the queue */
153         final Deque<ThemisAnalysisElement> myLines = new ArrayDeque<>();
154 
155         /* create a read buffer */
156         final char[] myBuffer = new char[BUFLEN];
157         int myOffset = 0;
158 
159         /* Protect against exceptions */
160         try (InputStream myStream = new FileInputStream(theLocation);
161              InputStreamReader myInputReader = new InputStreamReader(myStream, StandardCharsets.UTF_8);
162              BufferedReader myReader = new BufferedReader(myInputReader)) {
163 
164             /* Read the header entry */
165             while (true) {
166                 /* Read some characters into the buffer */
167                 final int myChars = myReader.read(myBuffer, myOffset, BUFLEN - myOffset);
168                 if (myChars == -1 && myOffset == 0) {
169                     break;
170                 }
171 
172                 /* Process lines in the buffer */
173                 myOffset = processLines(myLines, myBuffer, myChars + myOffset);
174             }
175 
176             /* Record the number of lines */
177             theNumLines = myLines.size();
178 
179             /* Perform initial processing pass */
180             initialProcessingPass(myLines);
181 
182             /* Catch exceptions */
183         } catch (IOException e) {
184             /* Throw an exception */
185             throw new ThemisIOException("Failed to load file "
186                     + theLocation.getAbsolutePath(), e);
187         }
188     }
189 
190     @Override
191     public ThemisAnalysisDataMap getDataMap() {
192         return theDataMap;
193     }
194 
195     @Override
196     public Deque<ThemisAnalysisElement> getContents() {
197         return theContents;
198     }
199 
200     @Override
201     public ThemisAnalysisContainer getParent() {
202         return this;
203     }
204 
205     /**
206      * Obtain the package.
207      *
208      * @return the package
209      */
210     String getPackageName() {
211         return thePackage.getPackage();
212     }
213 
214     @Override
215     public int getNumLines() {
216         return theNumLines;
217     }
218 
219     /**
220      * Process lines.
221      *
222      * @param pLines    the list of lines to build
223      * @param pBuffer   the character buffer
224      * @param pNumChars the number of characters in the buffer
225      * @return the remaining characters in the buffer
226      * @throws OceanusException on error
227      */
228     private static int processLines(final Deque<ThemisAnalysisElement> pLines,
229                                     final char[] pBuffer,
230                                     final int pNumChars) throws OceanusException {
231         /* The start of the current line */
232         int myOffset = 0;
233 
234         /* Look for line feed in the buffer */
235         int myLF = findLineFeedInBuffer(pBuffer, 0, pNumChars);
236         while (myLF != -1) {
237             /* Build the line */
238             final ThemisAnalysisLine myLine = buildLine(pBuffer, myOffset, myLF);
239             pLines.add(myLine);
240 
241             /* Look for next lineFeed */
242             myOffset = myLF + 1;
243             myLF = findLineFeedInBuffer(pBuffer, myOffset, pNumChars);
244         }
245 
246         /* Copy remaining characters down */
247         final int myRemaining = pNumChars - myOffset;
248         if (myRemaining > 0) {
249             System.arraycopy(pBuffer, myOffset, pBuffer, 0, myRemaining);
250         }
251 
252         /* Return the number remaining */
253         return myRemaining;
254     }
255 
256     /**
257      * Find lineFeed in buffer.
258      *
259      * @param pBuffer   the character buffer
260      * @param pOffset   the starting offset
261      * @param pNumChars the number of characters in the buffer
262      * @return the remaining characters in the buffer
263      * @throws OceanusException on error
264      */
265     private static int findLineFeedInBuffer(final char[] pBuffer,
266                                             final int pOffset,
267                                             final int pNumChars) throws OceanusException {
268         /* Loop through the buffer */
269         for (int i = pOffset; i < pNumChars; i++) {
270             /* Check for LF and NULL */
271             switch (pBuffer[i]) {
272                 case ThemisAnalysisChar.LF:
273                     return i;
274                 case ThemisAnalysisChar.NULL:
275                     throw new ThemisDataException("Null character in file");
276                 default:
277                     break;
278             }
279         }
280 
281         /* Look for line feed in the buffer */
282         return -1;
283     }
284 
285     /**
286      * Build line from buffer.
287      *
288      * @param pBuffer   the character buffer
289      * @param pOffset   the starting offset
290      * @param pLineFeed the location of the lineFeed
291      * @return the new line
292      */
293     private static ThemisAnalysisLine buildLine(final char[] pBuffer,
294                                                 final int pOffset,
295                                                 final int pLineFeed) {
296         /* Strip any trailing cr */
297         int myLen = pLineFeed - pOffset;
298         if (myLen > 0 && pBuffer[pOffset + myLen - 1] == ThemisAnalysisChar.CR) {
299             myLen--;
300         }
301 
302         /* Build the line */
303         return new ThemisAnalysisLine(pBuffer, pOffset, myLen);
304     }
305 
306     /**
307      * Post-process the lines as first Pass.
308      *
309      * @param pLines the lines to process
310      * @throws OceanusException on error
311      */
312     private void initialProcessingPass(final Deque<ThemisAnalysisElement> pLines) throws OceanusException {
313         /* Create the parser */
314         final ThemisAnalysisParser myParser = new ThemisAnalysisParser(pLines, theContents, this);
315 
316         /* Loop through the lines */
317         while (myParser.hasLines()) {
318             /* Access next line */
319             ThemisAnalysisLine myLine = (ThemisAnalysisLine) myParser.popNextLine();
320 
321             /* Process comments/blanks/package/imports */
322             boolean processed = myParser.processCommentsAndBlanks(myLine)
323                     || processPackage(myLine)
324                     || myParser.processImports(myLine);
325 
326             /* If we haven't processed yet */
327             if (!processed) {
328                 /* Process the class */
329                 processed = myParser.processClass(myLine);
330                 if (!processed) {
331                     throw new ThemisDataException("Unexpected construct in file");
332                 }
333 
334                 /* Process any trailing blanks/comments */
335                 while (myParser.hasLines()) {
336                     myLine = (ThemisAnalysisLine) myParser.popNextLine();
337                     if (!myParser.processCommentsAndBlanks(myLine)) {
338                         throw new ThemisDataException("Trailing data in file");
339                     }
340                 }
341             }
342         }
343     }
344 
345     /**
346      * perform consolidation processing pass.
347      *
348      * @throws OceanusException on error
349      */
350     void consolidationProcessingPass() throws OceanusException {
351         /* Consolidate classMap */
352         theDataMap.consolidateMap();
353     }
354 
355     /**
356      * Perform final processing pass.
357      *
358      * @throws OceanusException on error
359      */
360     void finalProcessingPass() throws OceanusException {
361         /* Resolve references */
362         theDataMap.resolveReferences();
363 
364         /* Loop through the lines */
365         for (ThemisAnalysisElement myElement : theContents) {
366             /* If the element is a container */
367             if (myElement instanceof ThemisAnalysisContainer myContainer) {
368                 /* Access and process the container */
369                 myContainer.postProcessLines();
370             }
371         }
372 
373         /* Report unknown items */
374         theDataMap.reportUnknown();
375     }
376 
377     /**
378      * Process a potential import line.
379      *
380      * @param pLine the line
381      * @return have we processed the line?
382      * @throws OceanusException on error
383      */
384     private boolean processPackage(final ThemisAnalysisLine pLine) throws OceanusException {
385         /* If this is a package line */
386         if (ThemisAnalysisPackage.isPackage(pLine)) {
387             /* Check that the package is correct named */
388             if (!thePackage.getPackage().equals(pLine.toString())) {
389                 throw new ThemisDataException("Bad package");
390             }
391 
392             /* Setup the file resources */
393             theDataMap.setUpFileResources();
394 
395             /* Declare all the files in this package to the dataMap */
396             for (ThemisAnalysisFile myFile : thePackage.getFiles()) {
397                 theDataMap.declareFile(myFile);
398             }
399 
400             /* Process the package line */
401             theContents.add(thePackage);
402 
403             /* Processed */
404             return true;
405         }
406 
407         /* return false */
408         return false;
409     }
410 
411     @Override
412     public String toString() {
413         return getName();
414     }
415 }