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 }