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.jet.cli.common.arguments.CommonCompilerArguments;
026    import org.jetbrains.jet.cli.common.messages.*;
027    import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException;
028    import org.jetbrains.jet.config.CompilerConfiguration;
029    
030    import java.io.PrintStream;
031    import java.util.List;
032    
033    import static org.jetbrains.jet.cli.common.ExitCode.*;
034    
035    public abstract class CLICompiler<A extends CommonCompilerArguments> {
036    
037        @NotNull
038        private List<CompilerPlugin> compilerPlugins = Lists.newArrayList();
039    
040        @NotNull
041        public List<CompilerPlugin> getCompilerPlugins() {
042            return compilerPlugins;
043        }
044    
045        public void setCompilerPlugins(@NotNull List<CompilerPlugin> compilerPlugins) {
046            this.compilerPlugins = compilerPlugins;
047        }
048    
049        @NotNull
050        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
051            A arguments = createArguments();
052            if (!parseArguments(errStream, arguments, args)) {
053                return INTERNAL_ERROR;
054            }
055            return exec(errStream, arguments);
056        }
057    
058        /**
059         * Returns true if the arguments can be parsed correctly
060         */
061        protected boolean parseArguments(@NotNull PrintStream errStream, @NotNull A arguments, @NotNull String[] args) {
062            try {
063                arguments.freeArgs = Args.parse(arguments, args);
064                checkArguments(arguments);
065                return true;
066            }
067            catch (IllegalArgumentException e) {
068                errStream.println(e.getMessage());
069                usage(errStream);
070            }
071            catch (Throwable t) {
072                // Always use tags
073                errStream.println(MessageRenderer.TAGS.renderException(t));
074            }
075            return false;
076        }
077    
078        protected void checkArguments(@NotNull A argument) {
079    
080        }
081    
082        /**
083         * Allow derived classes to add additional command line arguments
084         */
085        protected void usage(@NotNull PrintStream target) {
086            Usage.print(target, createArguments());
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        /**
101         * Executes the compiler on the parsed arguments
102         */
103        @NotNull
104        public ExitCode exec(@NotNull PrintStream errStream, @NotNull A arguments) {
105            if (arguments.help) {
106                usage(errStream);
107                return OK;
108            }
109    
110            MessageRenderer messageRenderer = getMessageRenderer(arguments);
111            errStream.print(messageRenderer.renderPreamble());
112    
113            printVersionIfNeeded(errStream, arguments, messageRenderer);
114    
115            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
116    
117            if (arguments.suppressAllWarnings()) {
118                collector = new FilteringMessageCollector(collector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
119            }
120    
121            try {
122                return exec(collector, arguments);
123            }
124            finally {
125                errStream.print(messageRenderer.renderConclusion());
126            }
127        }
128    
129        @NotNull
130        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
131            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
132            try {
133                Disposable rootDisposable = Disposer.newDisposable();
134                try {
135                    MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
136                    ExitCode code = doExecute(arguments, severityCollector, rootDisposable);
137                    return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
138                }
139                finally {
140                    Disposer.dispose(rootDisposable);
141                }
142            }
143            catch (Throwable t) {
144                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(t),
145                                         CompilerMessageLocation.NO_LOCATION);
146                return INTERNAL_ERROR;
147            }
148            finally {
149                groupingCollector.flush();
150            }
151        }
152    
153        @NotNull
154        protected abstract ExitCode doExecute(@NotNull A arguments, @NotNull MessageCollector messageCollector, @NotNull Disposable rootDisposable);
155    
156        //TODO: can we make it private?
157        @NotNull
158        protected MessageRenderer getMessageRenderer(@NotNull A arguments) {
159            return arguments.tags ? MessageRenderer.TAGS : MessageRenderer.PLAIN;
160        }
161    
162        protected void printVersionIfNeeded(
163                @NotNull PrintStream errStream,
164                @NotNull A arguments,
165                @NotNull MessageRenderer messageRenderer
166        ) {
167            if (arguments.version) {
168                String versionMessage = messageRenderer.render(CompilerMessageSeverity.INFO,
169                                                               "Kotlin Compiler version " + KotlinVersion.VERSION,
170                                                               CompilerMessageLocation.NO_LOCATION);
171                errStream.println(versionMessage);
172            }
173        }
174    
175        /**
176         * Useful main for derived command line tools
177         */
178        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
179            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
180            // to avoid accidentally starting the UI thread
181            System.setProperty("java.awt.headless", "true");
182            ExitCode exitCode = doMainNoExit(compiler, args);
183            if (exitCode != OK) {
184                System.exit(exitCode.getCode());
185            }
186        }
187    
188        @NotNull
189        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
190            try {
191                return compiler.exec(System.err, args);
192            }
193            catch (CompileEnvironmentException e) {
194                System.err.println(e.getMessage());
195                return INTERNAL_ERROR;
196            }
197        }
198    }