KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > javaguard > ScriptFile


1 /**
2  * JavaGuard -- an obfuscation package for Java classfiles.
3  *
4  * Copyright (c) 2002 Thorsten Heit (theit@gmx.de)
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  *
20  * The author may be contacted at theit@gmx.de.
21  *
22  *
23  * $Id: ScriptFile.java,v 1.5 2002/05/11 18:55:54 glurk Exp $
24  */

25 package net.sf.javaguard;
26
27 import java.io.*;
28 import java.util.*;
29
30
31 /** Parser for a JavaGuard script file.
32  *
33  * @author <a HREF="mailto:theit@gmx.de">Thorsten Heit</a>
34  */

35 public class ScriptFile implements ScriptConstants {
36   /** Holds all parsed entries from the script file. */
37   private Vector entries;
38   
39   /** Holds the script file name. */
40   private String JavaDoc name;
41   
42   
43   
44   
45   /** Constructor that reads and parses the given script file.
46    * @param aScript file object for a valid JavaGuard script
47    * @throws IOException if an error occurs during reading
48    */

49   public ScriptFile(File aScript)
50   throws IOException {
51     entries = new Vector();
52     setName(aScript.getName());
53     
54     InputStream is = null;
55     try {
56       is = new FileInputStream(aScript);
57       StreamTokenizer tk = new StreamTokenizer(new BufferedReader(new InputStreamReader(is)));
58       // use our own syntax schema
59
tk.resetSyntax();
60       tk.eolIsSignificant(true);
61       // everything below ' ' should be treated as a whitespace character...
62
tk.whitespaceChars(0x00, 0x20);
63       // ...and everything else as a word character
64
tk.wordChars('!', '~');
65       // enable C/C++-style and shell script-like comments:
66
tk.slashSlashComments(true);
67       tk.slashStarComments(true);
68       tk.commentChar('#');
69       readEntries(tk);
70     } finally {
71       if (null != is) {
72         is.close();
73       }
74     }
75   }
76   
77   
78   /** Reads all script file entries from the given input stream.
79    * @param tk the input stream for the script file
80    * @throws IOException if an error occurs during reading
81    */

82   private void readEntries(StreamTokenizer tk)
83   throws IOException {
84     ScriptEntry scriptEntry;
85     try {
86       while ( null != (scriptEntry = readNextEntry(tk))) {
87         entries.add(scriptEntry);
88       }
89     } catch (IOException ex) {
90       IOException ioex = new IOException("Parse error at line " + tk.lineno()
91       + ": " + ex.getMessage() + ".");
92       ioex.fillInStackTrace();
93       throw ioex;
94     } catch (IllegalArgumentException JavaDoc iaex) {
95       IOException ioex = new IOException("Parse error at line " + tk.lineno()
96       + ": " + iaex.getMessage() + ".");
97       ioex.fillInStackTrace();
98       throw ioex;
99     }
100   }
101   
102   
103   /** Read the next entry in the script file.
104    * @param tk the valid input stream for the script file
105    * @return next entry in the script file; null if nothing more is available
106    * @throws IOException if an error occurs during reading
107    * @throws IllegalArgumentException if an error occurs during parsing
108    */

109   private ScriptEntry readNextEntry(StreamTokenizer tk)
110   throws IOException, IllegalArgumentException JavaDoc {
111     // Reset the 'next error' state
112
ScriptEntry entry = null;
113     
114     // read the next token in the stream
115
readNextToken(tk);
116     if (tk.ttype == StreamTokenizer.TT_EOF) return null;
117     
118     // parse and validate the actual token (which must be a script file directive)
119
int type = parseDirective(tk);
120     if (type < 0) {
121       throw new IllegalArgumentException JavaDoc("Unknown script file directive");
122     }
123     
124     // each script directive requires at least one additional word following it
125
readNextTokenInLine(tk);
126     entry = new ScriptEntry(type, tk.sval);
127     
128     // depending on the script directive read additional parameters
129
switch (type) {
130       case TYPE_ATTRIBUTE:
131         if (!Tools.isInArrayIgnoreCase(entry.getName(), validAttrs)) {
132           throw new IllegalArgumentException JavaDoc("Unknown attribute option specified");
133         }
134         // optionally read an additional info string for the attribute
135
if (conditionalReadNextTokenInLine(tk)) {
136           entry.setInfo(tk.sval);
137         }
138         break;
139         
140       case TYPE_RENAME:
141         if (!Tools.isInArrayIgnoreCase(entry.getName(), renameAttrs)) {
142           throw new IllegalArgumentException JavaDoc("Unknown rename option specified");
143         }
144         // optionally read an additional info string for the attribute
145
if (conditionalReadNextTokenInLine(tk)) {
146           entry.setInfo(tk.sval);
147         }
148         break;
149         
150       case TYPE_PRESERVE:
151         if (!Tools.isInArrayIgnoreCase(entry.getName(), preserveAttrs)) {
152           throw new IllegalArgumentException JavaDoc("Unknown preserve option specified");
153         }
154         if (conditionalReadNextTokenInLine(tk)) {
155           entry.setInfo(tk.sval);
156         }
157         break;
158         
159       case TYPE_PACKAGE:
160         // read additional options, the same as for a ".class" directive
161
readClassOptions(tk, entry);
162         break;
163         
164       case TYPE_PACKAGE_MAP:
165         // read the mapping name for a ".package_map" directive
166
readNextTokenInLine(tk);
167         checkJavaIdentifier(tk.sval);
168         entry.setMappedName(tk.sval);
169         // read additional options, the same as for a ".class" directive
170
readClassOptions(tk, entry);
171         break;
172         
173       case TYPE_CLASS:
174         // read additional options for a ".class" directive
175
readClassOptions(tk, entry);
176         break;
177         
178       case TYPE_CLASS_MAP:
179         // read the mapping name and additional options a ".class_map" directive
180
readNextTokenInLine(tk);
181         checkJavaInnerIdentifier(tk.sval);
182         entry.setMappedName(tk.sval);
183         readClassOptions(tk, entry);
184         break;
185         
186       case TYPE_METHOD:
187         // optionally read the descriptor for the ".method" directive
188
if (conditionalReadNextTokenInLine(tk)) {
189           checkMethodDescriptor(tk.sval);
190           entry.setDescriptor(tk.sval);
191         }
192         break;
193         
194       case TYPE_METHOD_MAP:
195         // read the descriptor for the ".method_map" directive...
196
readNextTokenInLine(tk);
197         checkMethodDescriptor(tk.sval);
198         entry.setDescriptor(tk.sval);
199         // ...and read the mapping name
200
readNextTokenInLine(tk);
201         checkJavaIdentifier(tk.sval);
202         entry.setMappedName(tk.sval);
203         break;
204         
205       case TYPE_FIELD:
206         // optionally read the descriptor for the ".field" directive
207
if (conditionalReadNextTokenInLine(tk)) {
208           checkJavaType(tk.sval);
209           entry.setDescriptor(tk.sval);
210         }
211         break;
212         
213       case TYPE_FIELD_MAP: {
214         // If there are two more tokens in the current line available the first
215
// one specifies the descriptor and the second the mapped name;
216
// otherwise the token specifies the mapped name
217
readNextTokenInLine(tk);
218         String JavaDoc tmp = tk.sval;
219         if (conditionalReadNextTokenInLine(tk)) {
220           // two tokens available -> first = descriptor, second = mapped name
221
checkJavaType(tmp);
222           entry.setDescriptor(tmp);
223           entry.setMappedName(tk.sval);
224         } else {
225           entry.setMappedName(tmp);
226         }
227         checkJavaIdentifier(entry.getMappedName());
228         break;
229       }
230       
231       case TYPE_IGNORE_METHOD:
232         // try to read an optional method descriptor
233
if (conditionalReadNextTokenInLine(tk)) {
234           checkMethodDescriptor(tk.sval);
235           entry.setDescriptor(tk.sval);
236         }
237         break;
238         
239       case TYPE_IGNORE_FIELD:
240         // try to read an optional field descriptor
241
if (conditionalReadNextTokenInLine(tk)) {
242           checkJavaType(tk.sval);
243           entry.setDescriptor(tk.sval);
244         }
245         break;
246         
247       case TYPE_OBFUSCATE_METHOD:
248         // try to read an optional method descriptor
249
if (conditionalReadNextTokenInLine(tk)) {
250           checkMethodDescriptor(tk.sval);
251           entry.setDescriptor(tk.sval);
252         }
253         break;
254         
255         
256       case TYPE_OBFUSCATE_FIELD:
257         // try to read an optional field descriptor
258
if (conditionalReadNextTokenInLine(tk)) {
259           checkJavaType(tk.sval);
260           entry.setDescriptor(tk.sval);
261         }
262         break;
263     }
264     
265     return entry;
266   }
267   
268   
269   /** Reads the next word token from the given input stream.
270    * @param tk the valid input stream
271    * @throws IOException if an error occurs during reading
272    */

273   private void readNextToken(StreamTokenizer tk)
274   throws IOException {
275     int token = StreamTokenizer.TT_EOL;
276     
277     // read the next word token
278
while (token == StreamTokenizer.TT_EOL) {
279       token = tk.nextToken();
280     }
281   }
282   
283   
284   /** Reads the next word token in the current line in the given input stream.
285    * @param tk the valid input stream
286    * @throws IOException if an error occurs during reading
287    */

288   private void readNextTokenInLine(StreamTokenizer tk)
289   throws IOException {
290     int token = StreamTokenizer.TT_NUMBER;
291     
292     // read the next token
293
while (token == StreamTokenizer.TT_NUMBER) {
294       token = tk.nextToken();
295     }
296     if (token != StreamTokenizer.TT_WORD) {
297       throw new IOException("Unexpected end of file at line " + tk.lineno() + " of script file.");
298     }
299   }
300   
301   
302   /** Tries to read the next available word in the current line in the input
303    * stream.
304    * @return true if the current line contains one more word; false else
305    * @param tk the input stream
306    * @throws IOException if an error occurs during reading
307    */

308   private boolean conditionalReadNextTokenInLine(StreamTokenizer tk)
309   throws IOException {
310     int token = StreamTokenizer.TT_NUMBER;
311     
312     // read the next token
313
while (token == StreamTokenizer.TT_NUMBER) {
314       token = tk.nextToken();
315     }
316     // if the next token is not a word push it back to the input stream
317
if (token != StreamTokenizer.TT_WORD) {
318       tk.pushBack();
319       return false;
320     }
321     return true;
322   }
323   
324   
325   /** Read and parse optional parameters that can be specified in <code>.class</code>
326    * or <code>.class_map</code> directives.
327    * @param tk the valid input stream
328    * @param entry the current script file entry
329    * @throws IOException if an error occurs during reading
330    * @throws IllegalArgumentException if an illegal or an invalid class option
331    * is found
332    */

333   private void readClassOptions(StreamTokenizer tk, ScriptEntry entry)
334   throws IOException, IllegalArgumentException JavaDoc {
335     while (conditionalReadNextTokenInLine(tk)) {
336       int subtype;
337       switch (subtype = parseToken(tk.sval, options, optionTypes)) {
338         case OPTION_TYPE_PUBLIC:
339           entry.setRetainPublic(true);
340           break;
341           
342         case OPTION_TYPE_PROTECTED:
343           entry.setRetainProtected(true);
344           break;
345           
346         case OPTION_TYPE_METHOD:
347           entry.setRetainMethods(true);
348           break;
349           
350         case OPTION_TYPE_FIELD:
351           entry.setRetainFields(true);
352           break;
353           
354         default:
355           throw new IllegalArgumentException JavaDoc("Unknown class option");
356       }
357     }
358     // check whether the "field" or "method" option are specified together
359
// with the "public" or "protected" option
360
if ( (entry.canRetainFields() || entry.canRetainMethods()) &&
361     !entry.canRetainPublic() && !entry.canRetainProtected()) {
362       throw new IllegalArgumentException JavaDoc("Missing option \"public\" or \"protected\"");
363     }
364     // check whether the "public" or "protected" keywords were given
365
// without any "field" or "method" modifier
366
if ( (entry.canRetainPublic() || entry.canRetainProtected()) &&
367     !entry.canRetainFields() && !entry.canRetainMethods()) {
368       // no "method" and "field" modifier are specified
369
// -> all public/protected elements should be retained
370
entry.setRetainFields(true);
371       entry.setRetainMethods(true);
372     }
373   }
374   
375   
376   /** Checks whether the current token is a valid script file directive.
377    * @return the code of the directive if the token is a valid script file
378    * directive; -1 else
379    * @param tk the input stream
380    */

381   private int parseDirective(StreamTokenizer tk) {
382     if (tk.ttype == StreamTokenizer.TT_WORD) {
383       return parseToken(tk.sval, directives, directiveTypes);
384     }
385     return -1;
386   }
387   
388   
389   /** Checks whether a string exists in an array of strings. If yes return an
390    * <code>int</code> value representing the string code.
391    * @param str The string to check
392    * @param list An array of strings; may not be null
393    * @param codes An array of <code>int</code> values representing the string
394    * codes. Must have the same size as the <code>list</code> parameter.
395    * @return An <code>int</code> value from the <code>codes</code> array if
396    * the string exists in the list; -1 else
397    */

398   private int parseToken(String JavaDoc str, String JavaDoc[] list, int[] codes) {
399     for (int i=0; i<list.length; i++) {
400       if (str.equalsIgnoreCase(list[i])) {
401         return codes[i];
402       }
403     }
404     return -1;
405   }
406   
407   
408   
409   
410   /** Checks whether a given method descriptor is valid.
411    * @param s the string to check
412    * @throws IllegalArgumentException if the descriptor is invalid
413    */

414   private void checkMethodDescriptor(String JavaDoc s)
415   throws IllegalArgumentException JavaDoc {
416     if (s.length() == 0 || s.charAt(0) != '(') {
417       throw new IllegalArgumentException JavaDoc("Empty or invalid method descriptor.");
418     }
419     s = s.substring(1);
420     
421     // Check each type
422
while (s.length() > 0 && s.charAt(0) != ')') {
423       s = checkFirstJavaType(s);
424     }
425     checkJavaType(s.substring(1));
426   }
427   
428   
429   /** Checks whether the first Java type is valid.
430    * @param s the string to check
431    * @throws IllegalArgumentException if the first type is invalid.
432    * @return all but first type in the string
433    */

434   private String JavaDoc checkFirstJavaType(String JavaDoc s)
435   throws IllegalArgumentException JavaDoc {
436     // Pull off the array specifiers
437
while (s.charAt(0) == '[') {
438       s = s.substring(1);
439       if (s.length() == 0) {
440         throw new IllegalArgumentException JavaDoc("Invalid Java type");
441       }
442     }
443     
444     // Check a type
445
int pos = 0;
446     switch (s.charAt(0)) {
447       case 'B':
448       case 'C':
449       case 'D':
450       case 'F':
451       case 'I':
452       case 'J':
453       case 'S':
454       case 'V':
455       case 'Z':
456         break;
457         
458       case 'L':
459         pos = s.indexOf(';');
460         if (pos == -1) {
461           throw new IllegalArgumentException JavaDoc("Invalid class or interface type specification");
462         }
463         // Check the class type
464
checkClassSpec(s.substring(0, pos));
465         break;
466         
467       default:
468         throw new IllegalArgumentException JavaDoc("Unknown Java type");
469     }
470     return s.substring(pos + 1);
471   }
472   
473   
474   
475   /** Checks whether the Java type is valid.
476    * @param s the string to check
477    * @throws IllegalArgumentException if the string is invalid
478    */

479   private void checkJavaType(String JavaDoc s)
480   throws IllegalArgumentException JavaDoc {
481     if (!checkFirstJavaType(s).equals("")) {
482       throw new IllegalArgumentException JavaDoc("Invalid Java type");
483     }
484   }
485   
486   
487   
488   
489   /** Checks whether the given string specifies a correct class specification.
490    * @param s the string to check
491    * @throws IllegalArgumentException if the string is invalid
492    */

493   private void checkClassSpec(String JavaDoc s)
494   throws IllegalArgumentException JavaDoc {
495     if (s.length() == 0) {
496       throw new IllegalArgumentException JavaDoc("Class specification may not be empty");
497     }
498     
499     int pos = -1;
500     // check all possible inner classes first
501
while ((pos = s.lastIndexOf('$')) != -1) {
502       checkJavaInnerIdentifier(s.substring(pos + 1));
503       s = s.substring(0, pos);
504     }
505     // now check all class and package names
506
while ((pos = s.lastIndexOf('/')) != -1) {
507       checkJavaIdentifier(s.substring(pos + 1));
508       s = s.substring(0, pos);
509     }
510     checkJavaIdentifier(s);
511   }
512   
513   
514   /** Checks whether the given string is a valid Java identifier.
515    * @param s the string to check
516    * @throws IllegalArgumentException if the string is invalid
517    */

518   private void checkJavaIdentifier(String JavaDoc s)
519   throws IllegalArgumentException JavaDoc {
520     if (s.length() == 0 || !Character.isJavaIdentifierStart(s.charAt(0))) {
521       throw new IllegalArgumentException JavaDoc("Identifier empty or invalid start character");
522     }
523     for (int i = 1; i < s.length(); i++) {
524       if (!Character.isJavaIdentifierPart(s.charAt(i))) {
525         throw new IllegalArgumentException JavaDoc("Invalid character inside the identifier");
526       }
527     }
528   }
529   
530   
531   /** Checks whether the given string is a valid Java identifier. Allows
532    * anonymous inner class names like '4'.
533    * @param s the string to check
534    * @throws IllegalArgumentException if the string is invalid
535    */

536   private void checkJavaInnerIdentifier(String JavaDoc s)
537   throws IllegalArgumentException JavaDoc {
538     if (s.length() == 0) {
539       throw new IllegalArgumentException JavaDoc("Identifier may not be empty");
540     }
541     for (int i = 0; i < s.length(); i++) {
542       if (!Character.isJavaIdentifierPart(s.charAt(i))) {
543         throw new IllegalArgumentException JavaDoc("Invalid character inside the identifier");
544       }
545     }
546   }
547   
548   
549   
550   
551   /** Returns an iterator over the elements in the script file in proper
552    * sequence.
553    * @return an iterator over the elements in this list in proper sequence.
554    */

555   public Iterator iterator() {
556     return entries.iterator();
557   }
558   
559   
560   
561   
562   /** Stores the script file name.
563    * @param aName the script file name
564    * @see #getName
565    */

566   public void setName(String JavaDoc aName) {
567     this.name = aName;
568   }
569   
570   
571   /** Returns the current script file name.
572    * @return script file name
573    * @see #setName
574    */

575   public String JavaDoc getName() {
576     return name;
577   }
578 }
579
Popular Tags