1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.plugin
.multivm
;
12 import java
.io
.IOException
;
13 import java
.io
.InputStream
;
14 import java
.io
.StreamTokenizer
;
15 import java
.util
.Collections
;
16 import java
.util
.Deque
;
17 import java
.util
.HashMap
;
18 import java
.util
.Iterator
;
19 import java
.util
.LinkedHashSet
;
20 import java
.util
.LinkedList
;
25 * Information on a source code file.
29 final class __SourceInfo__
31 /** The package this source is in. */
32 public final String inPackage
;
34 /** The name of the current class. */
35 public final String thisClass
;
37 /** The super-class of this one, if one is set. */
38 public final String superClass
;
40 /** The classes this implements. */
41 public final Set
<String
> implementsClasses
;
44 * Initializes the source information.
46 * @param __inPackage The package this source is in.
47 * @param __thisClass The current class.
48 * @param __superClass The super class, may be {@code null}.
49 * @param __implementsClasses The classes this implements.
50 * @throws NullPointerException On null arguments.
53 __SourceInfo__(String __inPackage
, String __thisClass
, String __superClass
,
54 Set
<String
> __implementsClasses
)
55 throws NullPointerException
57 if (__inPackage
== null || __thisClass
== null)
58 throw new NullPointerException("NARG");
60 this.inPackage
= __inPackage
;
61 this.thisClass
= __thisClass
;
62 this.superClass
= __superClass
;
63 this.implementsClasses
= (__implementsClasses
== null ||
64 __implementsClasses
.isEmpty() ? Collections
.emptySet() :
65 Collections
.unmodifiableSet(
66 new LinkedHashSet
<>(__implementsClasses
)));
74 public String
toString()
77 "__SourceInfo__{inPackage='%s', thisClass='%s'," +
78 "superClass='%s', implementsClasses=%s}",
79 this.inPackage
, this.thisClass
, this.superClass
,
80 this.implementsClasses
);
84 * Loads class information from a class file.
86 * @param __in The stream to from.
87 * @return The implementing class information.
88 * @throws IOException On read errors.
89 * @throws NullPointerException On null arguments.
92 public static __SourceInfo__
loadClass(InputStream __in
)
93 throws IOException
, NullPointerException
96 throw new NullPointerException("NARG");
98 throw new Error("TODO");
102 * Loads information gleaned from source code in Jasmin.
104 * @param __in The file to read from.
105 * @return The information on the class.
106 * @throws IOException On read errors.
107 * @throws NullPointerException On null arguments.
110 public static __SourceInfo__
loadJasmin(InputStream __in
)
111 throws IOException
, NullPointerException
114 throw new NullPointerException("NARG");
116 StreamTokenizer stream
= new StreamTokenizer(__in
);
118 // Jasmin files, do not use standard syntax so clear everything
119 stream
.resetSyntax();
121 // Set these specifically
122 stream
.commentChar(';');
123 stream
.wordChars('.', '.');
124 stream
.wordChars('/', '/');
125 stream
.wordChars('$', '$');
126 stream
.wordChars('a', 'z');
127 stream
.wordChars('A', 'Z');
128 stream
.wordChars('0', '9');
129 stream
.wordChars('_', '_');
130 stream
.eolIsSignificant(true);
131 stream
.quoteChar('"');
132 stream
.quoteChar('\'');
133 stream
.whitespaceChars(0, ' ');
136 String thisClass
= null;
137 String superClass
= null;
138 Set
<String
> implementsClasses
= new LinkedHashSet
<>();
141 Deque
<String
> queue
= new LinkedList
<>();
144 int type
= stream
.nextToken();
146 // End of file or line, will be a statement
147 if (type
== StreamTokenizer
.TT_EOL
||
148 type
== StreamTokenizer
.TT_EOF
)
150 String first
= queue
.pollFirst();
152 // Declares current class or interface?
153 if (".class".equals(first
) || ".interface".equals(first
))
155 // Ignore any access specifiers before the class name
159 second
= queue
.pollFirst();
160 } while (second
!= null &&
161 __SourceInfo__
.__isAccessSpec(second
));
163 // The class name just follows these
165 thisClass
= second
.replace('/', '.');
168 // Declares super class?
169 else if (".super".equals(first
))
171 // The super class just follows this
172 String second
= queue
.pollFirst();
174 superClass
= second
.replace('/', '.');
177 // Declares implementing interface?
178 else if (".implements".equals(first
))
180 // The super class just follows this
181 String second
= queue
.pollFirst();
183 implementsClasses
.add(
184 second
.replace('/', '.'));
187 // Stop on EOF, or if we hit a field or method as we know
188 // there will be nothing following this
189 if (type
== StreamTokenizer
.TT_EOF
||
190 ".method".equals(first
) ||
191 ".field".equals(first
))
194 // Clear the queue for the next run
198 // Push these to the queue
199 else if (type
== StreamTokenizer
.TT_NUMBER
)
200 queue
.addLast(Double
.toString(stream
.nval
));
201 else if (type
== StreamTokenizer
.TT_WORD
)
202 queue
.addLast(stream
.sval
);
204 // .class public foo/bar
206 // .implements foo/bar
209 // This should not happen, unless the source is malformed
210 if (thisClass
== null)
211 throw new IOException("Jasmin class has no name?");
213 // Determine the package we are in, which is just the package of our
215 int ls
= thisClass
.lastIndexOf('.');
216 String inPackage
= (ls
< 0 ?
"" : thisClass
.substring(0, ls
));
218 // Normalize to Java forms, as these all use binary names
219 return new __SourceInfo__(inPackage
,
220 thisClass
, superClass
, implementsClasses
);
224 * Loads information gleaned from source code in Java.
226 * @param __in The file to read from.
227 * @return The information on the class.
228 * @throws IOException On read errors.
229 * @throws NullPointerException On null arguments.
232 public static __SourceInfo__
loadJava(InputStream __in
)
233 throws IOException
, NullPointerException
236 throw new NullPointerException("NARG");
238 StreamTokenizer stream
= new StreamTokenizer(__in
);
240 // Initialize tokenizer parameters
241 stream
.slashStarComments(true);
242 stream
.slashSlashComments(true);
243 stream
.whitespaceChars(0, ' ');
244 stream
.parseNumbers();
245 stream
.wordChars('.', '.');
246 stream
.wordChars('_', '_');
247 stream
.wordChars('$', '$');
248 stream
.ordinaryChar('<');
249 stream
.ordinaryChar('>');
250 stream
.ordinaryChar('@');
251 stream
.ordinaryChar(';');
252 stream
.ordinaryChar('{');
253 stream
.ordinaryChar('}');
254 stream
.ordinaryChar('?');
257 String inPackage
= null;
258 String thisClass
= null;
259 String superClass
= null;
260 Set
<String
> implementsClasses
= new LinkedHashSet
<>();
261 Map
<String
, String
> imports
= new HashMap
<>();
262 boolean foundClassName
= false;
265 Deque
<String
> queue
= new LinkedList
<>();
268 int type
= stream
.nextToken();
270 // End of line or statement, curly braces are included since they
271 // open the class file
272 if (type
== ';' || type
== '{' || type
== StreamTokenizer
.TT_EOF
)
274 boolean isPackage
= false;
275 boolean isImport
= false;
277 // Is this a package or import statement?
278 String first
= queue
.pollFirst();
291 // Package or import statement
292 if (isPackage
|| isImport
)
294 String second
= __SourceInfo__
.__glue(queue
);
300 // Import statement that is not a static import
301 else if (!queue
.contains("static"))
303 // Determine the identifier this links to
304 int ld
= second
.lastIndexOf('.');
305 String ident
= (ld
< 0 ? second
:
306 second
.substring(ld
+ 1));
308 // Declare import, used for full name getting
309 imports
.put(ident
, second
);
313 // Potential class statement?
314 else if (type
== '{' && (queue
.contains("class") ||
315 queue
.contains("interface")))
317 // If no package was found, then this will be the default
319 if (inPackage
== null)
322 // It may or may not be here on this line
323 String maybeClass
= __SourceInfo__
324 .__follow(queue
, "class", "interface");
325 String maybeExtend
= __SourceInfo__
326 .__follow(queue
, "extends");
328 // We did find the class name?
329 if (maybeClass
!= null)
331 // Determine the name of the current class
332 thisClass
= (inPackage
.isEmpty() ? maybeClass
:
333 inPackage
+ "." + maybeClass
);
335 // We found this, so we should be able to stop now
336 foundClassName
= true;
339 // We did find the super class?
340 if (maybeExtend
!= null)
342 // Super class is from an import statement?
343 String foundImport
= imports
.get(maybeExtend
);
344 if (foundImport
!= null)
345 superClass
= foundImport
;
347 // Has dot, so is fully qualified
348 else if (maybeExtend
.indexOf('.') >= 0)
349 superClass
= maybeExtend
;
351 // Otherwise, assume it is in the same package as our
354 superClass
= (inPackage
.isEmpty() ? maybeExtend
:
355 inPackage
+ "." + maybeExtend
);
358 // Handle implements? There may be multiple ones
359 for (int count
= 0;; count
++)
361 // Try to read the next interface, if any
362 String maybeImplements
= __SourceInfo__
.__follow(queue
,
363 (count
== 0 ?
"implements" : ","));
364 if (maybeImplements
== null)
367 // Implements class is from an import statement?
368 String foundImport
= imports
.get(maybeImplements
);
369 if (foundImport
!= null)
370 implementsClasses
.add(foundImport
);
372 // Has dot, so is fully qualified
373 else if (maybeImplements
.indexOf('.') >= 0)
374 implementsClasses
.add(maybeImplements
);
376 // Otherwise, assume it is in the same package as our
379 implementsClasses
.add((inPackage
.isEmpty() ?
381 inPackage
+ "." + maybeImplements
));
385 // Stop on EOF or if we found the class we want
386 if (foundClassName
|| type
== StreamTokenizer
.TT_EOF
)
389 // Clear the queue for the next run
393 // Push these to the queue
394 else if (type
== StreamTokenizer
.TT_NUMBER
)
395 queue
.addLast(Double
.toString(stream
.nval
));
396 else if (type
== StreamTokenizer
.TT_WORD
)
397 queue
.addLast(stream
.sval
);
399 queue
.addLast(Character
.toString((char)type
));
402 // This should not happen, unless the source is malformed
403 if (inPackage
== null || thisClass
== null)
404 throw new IOException("Java class has no package or name?");
406 return new __SourceInfo__(inPackage
, thisClass
, superClass
,
411 * Attempts to locate the token that follows the given identifying token.
413 * For example {@code __follow(["public", "class", "foo"], "class")} will
414 * return {@code "foo"}.
416 * @param __seq The token sequence.
417 * @param __tokens The tokens to follow from.
418 * @return The following word.
419 * @throws NullPointerException On null arguments.
422 private static String
__follow(Deque
<String
> __seq
, String
... __tokens
)
423 throws NullPointerException
425 if (__seq
== null || __tokens
== null || __tokens
.length
<= 0)
426 throw new NullPointerException("NARG");
428 // Try to discover a match
431 // Try to find first valid token matching the word
432 String at
= __seq
.pollFirst();
436 // Find match for our token
437 String matching
= null;
438 for (String maybe
: __tokens
)
439 if (maybe
.equals(at
))
445 // Did we match our token?
446 if (matching
== null)
449 // Return the follower if it is a valid identifier
450 String follower
= __seq
.pollFirst();
451 if (follower
!= null && __SourceInfo__
.__isJavaWord(follower
))
453 // Skip any < and > since we do not care for generics at all
454 String skip
= __seq
.peekFirst();
455 if ("<".equals(skip
))
460 String token
= __seq
.pollFirst();
462 if ("<".equals(token
))
464 else if (">".equals(token
))
478 * Glues the given strings together.
480 * @param __strings The strings to glue together.
481 * @return The joined together strings.
482 * @throws NullPointerException On null arguments.
485 private static String
__glue(Iterable
<String
> __strings
)
486 throws NullPointerException
488 if (__strings
== null)
489 throw new NullPointerException("NARG");
491 StringBuilder sb
= new StringBuilder();
492 for (String s
: __strings
)
495 return sb
.toString();
499 * Is this an access specifier? This is used by Jasmin to declare a class
500 * or method with special flag types.
502 * @param __word The word to check.
503 * @return If this is an access specifier.
504 * @throws NullPointerException On null arguments.
507 private static boolean __isAccessSpec(String __word
)
508 throws NullPointerException
511 throw new NullPointerException("NARG");
533 * Checks if this is a Java word or not.
535 * @param __word The word to check.
536 * @return If this is a Java word or not.
537 * @throws NullPointerException On null arguments.
540 private static boolean __isJavaWord(String __word
)
541 throws NullPointerException
544 throw new NullPointerException("NARG");
546 // Blank words are never words
547 int n
= __word
.length();
551 // Check for invalid characters
552 for (int i
= 0; i
< n
; i
++)
554 char c
= __word
.charAt(i
);
556 // Consider dots valid for fully qualified names
560 // Otherwise, must be a valid character here
561 if ((i
== 0 && !Character
.isJavaIdentifierStart(c
)) ||
562 (i
> 0 && !Character
.isJavaIdentifierPart(c
)))
566 // Did not fail, so must be a Java word