001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.cli.common;
018    
019    import com.google.common.base.Predicates;
020    import com.google.common.collect.Lists;
021    import com.intellij.openapi.Disposable;
022    import com.intellij.openapi.util.Disposer;
023    import com.sampullara.cli.Args;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.cli.common.arguments.CommonCompilerArguments;
027    import org.jetbrains.jet.cli.common.messages.*;
028    import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException;
029    import org.jetbrains.jet.config.CompilerConfiguration;
030    import org.jetbrains.jet.config.Services;
031    
032    import java.io.PrintStream;
033    import java.util.List;
034    
035    import static org.jetbrains.jet.cli.common.ExitCode.*;
036    
037    public abstract class CLICompiler<A extends CommonCompilerArguments> {
038        @NotNull
039        private List<CompilerPlugin> compilerPlugins = Lists.newArrayList();
040    
041        @NotNull
042        public List<CompilerPlugin> getCompilerPlugins() {
043            return compilerPlugins;
044        }
045    
046        public void setCompilerPlugins(@NotNull List<CompilerPlugin> compilerPlugins) {
047            this.compilerPlugins = compilerPlugins;
048        }
049    
050        @NotNull
051        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
052            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_WITH_RELATIVE_PATH, args);
053        }
054    
055        @SuppressWarnings("UnusedDeclaration") // Used via reflection in CompilerRunnerUtil#invokeExecMethod
056        @NotNull
057        public ExitCode execAndOutputHtml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
058            return exec(errStream, services, MessageRenderer.TAGS, args);
059        }
060    
061        @Nullable
062        private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
063            try {
064                A arguments = createArguments();
065                arguments.freeArgs = Args.parse(arguments, args);
066                return arguments;
067            }
068            catch (IllegalArgumentException e) {
069                errStream.println(e.getMessage());
070                usage(errStream, false);
071            }
072            catch (Throwable t) {
073                errStream.println(messageRenderer.render(
074                        CompilerMessageSeverity.EXCEPTION,
075                        OutputMessageUtil.renderException(t),
076                        CompilerMessageLocation.NO_LOCATION)
077                );
078            }
079            return null;
080        }
081    
082        /**
083         * Allow derived classes to add additional command line arguments
084         */
085        protected void usage(@NotNull PrintStream target, boolean extraHelp) {
086            Usage.print(target, createArguments(), extraHelp);
087        }
088    
089        /**
090         * Strategy method to configure the environment, allowing compiler
091         * based tools to customise their own plugins
092         */
093        protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) {
094            configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, compilerPlugins);
095        }
096    
097        @NotNull
098        protected abstract A createArguments();
099    
100        @NotNull
101        private ExitCode exec(
102                @NotNull PrintStream errStream,
103                @NotNull Services services,
104                @NotNull MessageRenderer messageRenderer,
105                @NotNull String[] args
106        ) {
107            A arguments = parseArguments(errStream, messageRenderer, args);
108            if (arguments == null) {
109                return INTERNAL_ERROR;
110            }
111    
112            if (arguments.help || arguments.extraHelp) {
113                usage(errStream, arguments.extraHelp);
114                return OK;
115            }
116    
117            errStream.print(messageRenderer.renderPreamble());
118    
119            printVersionIfNeeded(errStream, arguments, messageRenderer);
120    
121            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
122    
123            if (arguments.suppressWarnings) {
124                collector = new FilteringMessageCollector(collector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
125            }
126    
127            try {
128                return exec(collector, services, arguments);
129            }
130            finally {
131                errStream.print(messageRenderer.renderConclusion());
132            }
133        }
134    
135        @NotNull
136        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
137            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
138            try {
139                Disposable rootDisposable = Disposer.newDisposable();
140                try {
141                    MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
142                    ExitCode code = doExecute(arguments, services, severityCollector, rootDisposable);
143                    return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
144                }
145                finally {
146                    Disposer.dispose(rootDisposable);
147                }
148            }
149            catch (Throwable t) {
150                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
151                                         CompilerMessageLocation.NO_LOCATION);
152                return INTERNAL_ERROR;
153            }
154            finally {
155                groupingCollector.flush();
156            }
157        }
158    
159        @NotNull
160        protected abstract ExitCode doExecute(
161                @NotNull A arguments,
162                @NotNull Services services,
163                @NotNull MessageCollector messageCollector,
164                @NotNull Disposable rootDisposable
165        );
166    
167        protected void printVersionIfNeeded(
168                @NotNull PrintStream errStream,
169                @NotNull A arguments,
170                @NotNull MessageRenderer messageRenderer
171        ) {
172            if (arguments.version) {
173                String versionMessage = messageRenderer.render(CompilerMessageSeverity.INFO,
174                                                               "Kotlin Compiler version " + KotlinVersion.VERSION,
175                                                               CompilerMessageLocation.NO_LOCATION);
176                errStream.println(versionMessage);
177            }
178        }
179    
180        /**
181         * Useful main for derived command line tools
182         */
183        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
184            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
185            // to avoid accidentally starting the UI thread
186            System.setProperty("java.awt.headless", "true");
187            ExitCode exitCode = doMainNoExit(compiler, args);
188            if (exitCode != OK) {
189                System.exit(exitCode.getCode());
190            }
191        }
192    
193        @NotNull
194        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
195            try {
196                return compiler.exec(System.err, args);
197            }
198            catch (CompileEnvironmentException e) {
199                System.err.println(e.getMessage());
200                return INTERNAL_ERROR;
201            }
202        }
203    }