1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package sample.preproc;
17
18import java.io.IOException;
19import java.io.BufferedReader;
20import java.io.FileReader;
21import java.io.BufferedWriter;
22import java.io.FileWriter;
23import java.util.Vector;
24import javassist.CannotCompileException;
25import javassist.CtClass;
26import javassist.ClassPool;
27
28/**
29 * This is a preprocessor for Java source programs using annotated
30 * import declarations.
31 *
32 * <ul><pre>
33 * import <i>class-name</i> by <i>assistant-name</i> [(<i>arg1, arg2, ...</i>)]
34 * </pre></ul>
35 *
36 * <p>To process this annotation, run this class as follows:
37 *
38 * <ul><pre>
39 * java sample.preproc.Compiler sample.j
40 * </pre></ul>
41 *
42 * <p>This command produces <code>sample.java</code>, which only includes
43 * regular import declarations.  Also, the Javassist program
44 * specified by <i>assistant-name</i> is executed so that it produces
45 * class files under the <code>./tmpjvst</code> directory.  The class
46 * specified by <i>assistant-name</i> must implement
47 * <code>sample.preproc.Assistant</code>.
48 *
49 * @see sample.preproc.Assistant
50 */
51
52public class Compiler {
53    protected BufferedReader input;
54    protected BufferedWriter output;
55    protected ClassPool classPool;
56
57    /**
58     * Constructs a <code>Compiler</code> with a source file.
59     *
60     * @param inputname         the name of the source file.
61     */
62    public Compiler(String inputname) throws CannotCompileException {
63        try {
64            input = new BufferedReader(new FileReader(inputname));
65        }
66        catch (IOException e) {
67            throw new CannotCompileException("cannot open: " + inputname);
68        }
69
70        String outputname = getOutputFilename(inputname);
71        if (outputname.equals(inputname))
72            throw new CannotCompileException("invalid source name: "
73                                             + inputname);
74
75        try {
76            output = new BufferedWriter(new FileWriter(outputname));
77        }
78        catch (IOException e) {
79            throw new CannotCompileException("cannot open: " + outputname);
80        }
81
82        classPool = ClassPool.getDefault();
83    }
84
85    /**
86     * Starts preprocessing.
87     */
88    public void process() throws IOException, CannotCompileException {
89        int c;
90        CommentSkipper reader = new CommentSkipper(input, output);
91        while ((c = reader.read()) != -1) {
92            output.write(c);
93            if (c == 'p') {
94                if (skipPackage(reader))
95                    break;
96            }
97            else if (c == 'i')
98                readImport(reader);
99            else if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
100                break;
101        }
102
103        while ((c = input.read()) != -1)
104            output.write(c);
105
106        input.close();
107        output.close();
108    }
109
110    private boolean skipPackage(CommentSkipper reader) throws IOException {
111        int c;
112        c = reader.read();
113        output.write(c);
114        if (c != 'a')
115            return true;
116
117        while ((c = reader.read()) != -1) {
118            output.write(c);
119            if (c == ';')
120                break;
121        }
122
123        return false;
124    }
125
126    private void readImport(CommentSkipper reader)
127                                throws IOException, CannotCompileException
128    {
129        int word[] = new int[5];
130        int c;
131        for (int i = 0; i < 5; ++i) {
132            word[i] = reader.read();
133            output.write(word[i]);
134        }
135
136        if (word[0] != 'm' || word[1] != 'p' || word[2] != 'o'
137            || word[3] != 'r' || word[4] != 't')
138            return;     // syntax error?
139
140        c = skipSpaces(reader, ' ');
141        StringBuffer classbuf = new StringBuffer();
142        while (c != ' ' && c != '\t' && c != '\n' && c != '\r'
143               && c != ';' && c != -1) {
144            classbuf.append((char)c);
145            c = reader.read();
146        }
147
148        String importclass = classbuf.toString();
149        c = skipSpaces(reader, c);
150        if (c == ';') {
151            output.write(importclass);
152            output.write(';');
153            return;
154        }
155        if (c != 'b')
156            syntaxError(importclass);
157
158        reader.read();  // skip 'y'
159
160        StringBuffer assistant = new StringBuffer();
161        Vector args = new Vector();
162        c = readAssistant(reader, importclass, assistant, args);
163        c = skipSpaces(reader, c);
164        if (c != ';')
165            syntaxError(importclass);
166
167        runAssistant(importclass, assistant.toString(), args);
168    }
169
170    void syntaxError(String importclass) throws CannotCompileException {
171        throw new CannotCompileException("Syntax error.  Cannot import "
172                                         + importclass);
173    }
174
175    int readAssistant(CommentSkipper reader, String importclass,
176                      StringBuffer assistant, Vector args)
177        throws IOException, CannotCompileException
178    {
179        int c = readArgument(reader, assistant);
180        c = skipSpaces(reader, c);
181        if (c == '(') {
182            do {
183                StringBuffer arg = new StringBuffer();
184                c = readArgument(reader, arg);
185                args.addElement(arg.toString());
186                c = skipSpaces(reader, c);
187            } while (c == ',');
188
189            if (c != ')')
190                syntaxError(importclass);
191
192            return reader.read();
193        }
194
195        return c;
196    }
197
198    int readArgument(CommentSkipper reader, StringBuffer buf)
199        throws IOException
200    {
201        int c = skipSpaces(reader, ' ');
202        while ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'
203               || '0' <= c && c <= '9' || c == '.' || c == '_') {
204            buf.append((char)c);
205            c = reader.read();
206        }
207
208        return c;
209    }
210
211    int skipSpaces(CommentSkipper reader, int c) throws IOException {
212        while (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
213            if (c == '\n' || c == '\r')
214                output.write(c);
215
216            c = reader.read();
217        }
218
219        return c;
220    }
221
222    /**
223     * Is invoked if this compiler encoutenrs:
224     *
225     * <ul><pre>
226     * import <i>class name</i> by <i>assistant</i> (<i>args1</i>, <i>args2</i>, ...);
227     * </pre></ul>
228     *
229     * @param   classname       class name
230     * @param   assistantname   assistant
231     * @param   argv            args1, args2, ...
232     */
233    private void runAssistant(String importname, String assistantname,
234                              Vector argv)
235        throws IOException, CannotCompileException
236    {
237        Class assistant;
238        Assistant a;
239        int s = argv.size();
240        String[] args = new String[s];
241        for (int i = 0; i < s; ++i)
242            args[i] = (String)argv.elementAt(i);
243
244        try {
245            assistant = Class.forName(assistantname);
246        }
247        catch (ClassNotFoundException e) {
248            throw new CannotCompileException("Cannot find " + assistantname);
249        }
250
251        try {
252            a = (Assistant)assistant.newInstance();
253        }
254        catch (Exception e) {
255            throw new CannotCompileException(e);
256        }
257
258        CtClass[] imports = a.assist(classPool, importname, args);
259        s = imports.length;
260        if (s < 1)
261            output.write(" java.lang.Object;");
262        else {
263            output.write(' ');
264            output.write(imports[0].getName());
265            output.write(';');
266            for (int i = 1; i < s; ++i) {
267                output.write(" import ");
268                output.write(imports[1].getName());
269                output.write(';');
270            }
271        }
272    }
273
274    private String getOutputFilename(String input) {
275        int i = input.lastIndexOf('.');
276        if (i < 0)
277            i = input.length();
278
279        return input.substring(0, i) + ".java";
280    }
281
282    public static void main(String[] args) {
283        if (args.length > 0)
284            try {
285                Compiler c = new Compiler(args[0]);
286                c.process();
287            }
288            catch (IOException e) {
289                System.err.println(e);
290            }
291            catch (CannotCompileException e) {
292                System.err.println(e);
293            }
294        else {
295            System.err.println("Javassist version " + CtClass.version);
296            System.err.println("No source file is specified.");
297        }
298    }
299}
300
301class CommentSkipper {
302    private BufferedReader input;
303    private BufferedWriter output;
304
305    public CommentSkipper(BufferedReader reader, BufferedWriter writer) {
306        input = reader;
307        output = writer;
308    }
309
310    public int read() throws IOException {
311        int c;
312        while ((c = input.read()) != -1)
313            if (c != '/')
314                return c;
315            else {
316                c = input.read();
317                if (c == '/')
318                    skipCxxComments();
319                else if (c == '*')
320                    skipCComments();
321                else
322                    output.write('/');
323            }
324
325        return c;
326    }
327
328    private void skipCxxComments() throws IOException {
329        int c;
330        output.write("//");
331        while ((c = input.read()) != -1) {
332            output.write(c);
333            if (c == '\n' || c == '\r')
334                break;
335        }
336    }
337
338    private void skipCComments() throws IOException {
339        int c;
340        boolean star = false;
341        output.write("/*");
342        while ((c = input.read()) != -1) {
343            output.write(c);
344            if (c == '*')
345                star = true;
346            else if(star && c == '/')
347                break;
348            else
349                star = false;
350        }
351    }
352}
353