1/*
2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug     7093891
27 * @summary support multiple task listeners
28 * @modules jdk.compiler/com.sun.tools.javac.api
29 *          jdk.compiler/com.sun.tools.javac.file
30 */
31
32import java.io.File;
33import java.io.IOException;
34import java.io.PrintWriter;
35import java.io.StringWriter;
36import java.net.URI;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.EnumMap;
40import java.util.List;
41import java.util.Set;
42
43import javax.annotation.processing.AbstractProcessor;
44import javax.annotation.processing.RoundEnvironment;
45import javax.annotation.processing.SupportedAnnotationTypes;
46import javax.lang.model.element.TypeElement;
47import javax.tools.JavaFileObject;
48import javax.tools.SimpleJavaFileObject;
49import javax.tools.StandardJavaFileManager;
50import javax.tools.StandardLocation;
51import javax.tools.ToolProvider;
52
53import com.sun.source.util.JavacTask;
54import com.sun.source.util.TaskEvent;
55import com.sun.source.util.TaskListener;
56import com.sun.tools.javac.api.JavacTool;
57
58public class TestSimpleAddRemove {
59    enum AddKind {
60        SET_IN_TASK,
61        ADD_IN_TASK,
62        ADD_IN_PROCESSOR,
63        ADD_IN_LISTENER;
64    }
65
66    enum RemoveKind {
67        REMOVE_IN_TASK,
68        REMOVE_IN_PROCESSOR,
69        REMOVE_IN_LISTENER,
70    }
71
72    enum CompileKind {
73        CALL {
74            void run(JavacTask t) {
75                if (!t.call()) throw new Error("compilation failed");
76            }
77        },
78        GENERATE {
79            void run(JavacTask t) throws IOException {
80                t.generate();
81            }
82        };
83        abstract void run(JavacTask t) throws IOException;
84    }
85
86    static class EventKindCounter extends EnumMap<TaskEvent.Kind, EventKindCounter.Count> {
87        static class Count {
88            int started;
89            int finished;
90
91            @Override
92            public String toString() {
93                return started + ":" + finished;
94            }
95        }
96
97        EventKindCounter() {
98            super(TaskEvent.Kind.class);
99        }
100
101        void inc(TaskEvent.Kind k, boolean started) {
102            Count c = get(k);
103            if (c == null)
104                put(k, c = new Count());
105
106            if (started)
107                c.started++;
108            else
109                c.finished++;
110        }
111    }
112
113    static class TestListener implements TaskListener {
114        EventKindCounter counter;
115
116        TestListener(EventKindCounter c) {
117            counter = c;
118        }
119
120        public void started(TaskEvent e) {
121            counter.inc(e.getKind(), true);
122        }
123
124        public void finished(TaskEvent e) {
125            counter.inc(e.getKind(), false);
126        }
127    }
128
129    static void addInListener(final JavacTask task, final TaskEvent.Kind kind, final TaskListener listener) {
130        task.addTaskListener(new TaskListener() {
131            public void started(TaskEvent e) {
132                if (e.getKind() == kind) {
133                    task.addTaskListener(listener);
134                    task.removeTaskListener(this);
135                }
136            }
137
138            public void finished(TaskEvent e) { }
139        });
140    }
141
142    static void removeInListener(final JavacTask task, final TaskEvent.Kind kind, final TaskListener listener) {
143        task.addTaskListener(new TaskListener() {
144            public void started(TaskEvent e) {
145                if (e.getKind() == kind) {
146                    task.removeTaskListener(listener);
147                    task.removeTaskListener(this);
148                }
149            }
150
151            public void finished(TaskEvent e) { }
152        });
153    }
154
155    @SupportedAnnotationTypes("*")
156    class TestProcessor extends AbstractProcessor {
157        AddKind ak;
158        RemoveKind rk;
159        TaskListener listener;
160
161        TestProcessor(AddKind ak, RemoveKind rk, TaskListener listener) {
162            this.ak = ak;
163            this.rk = rk;
164            this.listener = listener;
165        }
166
167        int round = 0;
168
169        @Override
170        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
171//            System.err.println("TestProcessor.process " + roundEnv);
172            JavacTask task = JavacTask.instance(processingEnv);
173            if (++round == 1) {
174                switch (ak) {
175                    case ADD_IN_PROCESSOR:
176                        task.addTaskListener(listener);
177                        break;
178                    case ADD_IN_LISTENER:
179                        addInListener(task, TaskEvent.Kind.ANALYZE, listener);
180                        break;
181                }
182            } else if (roundEnv.processingOver()) {
183                switch (rk) {
184                    case REMOVE_IN_PROCESSOR:
185                        task.removeTaskListener(listener);
186                        break;
187                    case REMOVE_IN_LISTENER:
188                        removeInListener(task, TaskEvent.Kind.GENERATE, listener);
189                        break;
190                }
191            }
192            return true;
193        }
194    }
195
196    static class TestSource extends SimpleJavaFileObject {
197        public TestSource() {
198            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
199        }
200
201        @Override
202        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
203            return "class Test { }";
204        }
205    }
206
207    public static void main(String... args) throws Exception {
208        TestSimpleAddRemove t = new TestSimpleAddRemove();
209        try {
210            t.run();
211        } finally {
212            t.fm.close();
213        }
214    }
215
216    JavacTool tool = (JavacTool) ToolProvider.getSystemJavaCompiler();
217    StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null);
218
219    void run() throws Exception {
220        for (CompileKind ck: CompileKind.values()) {
221            for (AddKind ak: AddKind.values()) {
222                for (RemoveKind rk: RemoveKind.values()) {
223                    test(ck, ak, rk);
224                }
225            }
226        }
227        if (errors > 0)
228            throw new Exception(errors + " errors occurred");
229    }
230
231    void test(CompileKind ck, AddKind ak, RemoveKind rk) throws IOException {
232        System.err.println("Test: " + ck + " " + ak + " " + rk);
233
234        File tmpDir = new File(ck + "-" + ak + "-" + rk);
235        tmpDir.mkdirs();
236        fm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(tmpDir));
237
238        List<String> options = new ArrayList<String>();
239        Iterable<? extends JavaFileObject> files = Arrays.asList(new TestSource());
240        StringWriter sw = new StringWriter();
241        PrintWriter pw = new PrintWriter(sw);
242        JavacTask task = tool.getTask(pw, fm, null, options, null, files);
243
244        EventKindCounter ec = new EventKindCounter();
245        TaskListener listener = new TestListener(ec);
246        boolean needProcessor = false;
247
248        switch (ak) {
249            case SET_IN_TASK:
250                task.setTaskListener(listener);
251                break;
252            case ADD_IN_TASK:
253                task.addTaskListener(listener);
254                break;
255            case ADD_IN_PROCESSOR:
256            case ADD_IN_LISTENER:
257                needProcessor = true;
258        }
259
260        switch (rk) {
261            case REMOVE_IN_TASK:
262                task.removeTaskListener(listener);
263                break;
264            case REMOVE_IN_PROCESSOR:
265            case REMOVE_IN_LISTENER:
266                needProcessor = true;
267        }
268
269        if (needProcessor)
270            task.setProcessors(Arrays.asList(new TestProcessor(ak, rk, listener)));
271
272        ck.run(task);
273        System.err.println(ec);
274
275        check(ck, ak, rk, ec);
276
277        System.err.println();
278    }
279
280    void check(CompileKind ck, AddKind ak, RemoveKind rk, EventKindCounter ec) {
281        // All results should be independent of ck, so we can ignore that
282
283        // Quick way to compare expected values of ec, by comparing ec.toString()
284        String expect = ec.toString();
285        String found;
286
287        switch (ak) {
288            // Add/set in task should record all events until the listener is removed
289            case SET_IN_TASK:
290            case ADD_IN_TASK:
291                switch (rk) {
292                    case REMOVE_IN_TASK:
293                        // Remove will succeed, meaning no events will be recorded
294                        found = "{}";
295                        break;
296                    case REMOVE_IN_PROCESSOR:
297                        if (ck == CompileKind.CALL)
298                            found = "{PARSE=1:1, ENTER=2:2, ANNOTATION_PROCESSING=1:0, ANNOTATION_PROCESSING_ROUND=2:1, COMPILATION=1:0}";
299                        else
300                            found = "{PARSE=1:1, ENTER=2:2, ANNOTATION_PROCESSING=1:0, ANNOTATION_PROCESSING_ROUND=2:1}";
301                        break;
302                    case REMOVE_IN_LISTENER:
303                        if (ck == CompileKind.CALL)
304                            found = "{PARSE=1:1, ENTER=3:3, ANALYZE=1:1, GENERATE=1:0, ANNOTATION_PROCESSING=1:1, ANNOTATION_PROCESSING_ROUND=2:2, COMPILATION=1:0}";
305                        else
306                            found = "{PARSE=1:1, ENTER=3:3, ANALYZE=1:1, GENERATE=1:0, ANNOTATION_PROCESSING=1:1, ANNOTATION_PROCESSING_ROUND=2:2}";
307                        break;
308                    default:
309                        throw new IllegalStateException();
310                }
311                break;
312
313            // "Add in processor" should skip initial PARSE/ENTER events
314            case ADD_IN_PROCESSOR:
315                switch (rk) {
316                    // Remove will fail (too early), so events to end will be recorded
317                    case REMOVE_IN_TASK:
318                        if (ck == CompileKind.CALL)
319                            found = "{ENTER=2:2, ANALYZE=1:1, GENERATE=1:1, ANNOTATION_PROCESSING=0:1, ANNOTATION_PROCESSING_ROUND=1:2, COMPILATION=0:1}";
320                        else
321                            found = "{ENTER=2:2, ANALYZE=1:1, GENERATE=1:1, ANNOTATION_PROCESSING=0:1, ANNOTATION_PROCESSING_ROUND=1:2}";
322                        break;
323                    case REMOVE_IN_PROCESSOR:
324                        found = "{ENTER=1:1, ANNOTATION_PROCESSING_ROUND=1:1}";
325                        break;
326                    case REMOVE_IN_LISTENER:
327                        found = "{ENTER=2:2, ANALYZE=1:1, GENERATE=1:0, ANNOTATION_PROCESSING=0:1, ANNOTATION_PROCESSING_ROUND=1:2}";
328                        break;
329                    default:
330                        throw new IllegalStateException();
331                }
332                break;
333
334            // "Add in listener" will occur during "ANALYSE.started" event
335            case ADD_IN_LISTENER:
336                switch (rk) {
337                    // Remove will fail (too early, so events to end will be recorded
338                    case REMOVE_IN_TASK:
339                    case REMOVE_IN_PROCESSOR:
340                        if (ck == CompileKind.CALL)
341                            found = "{ANALYZE=0:1, GENERATE=1:1, COMPILATION=0:1}";
342                        else
343                            found = "{ANALYZE=0:1, GENERATE=1:1}";
344                        break;
345                    // Remove will succeed during "GENERATE.finished" event
346                    case REMOVE_IN_LISTENER:
347                        found = "{ANALYZE=0:1, GENERATE=1:0}";
348                        break;
349                    default:
350                        throw new IllegalStateException();
351                }
352                break;
353            default:
354                throw new IllegalStateException();
355        }
356
357        if (!found.equals(expect)) {
358            System.err.println("Expected: " + expect);
359            System.err.println("   Found: " + found);
360            error("unexpected value found");
361        }
362    }
363
364    int errors;
365
366    void error(String message) {
367        System.err.println("Error: " + message);
368        errors++;
369    }
370}
371