Java tutorial
/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.idea.internal; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.psi.PsiElement; import com.intellij.ui.content.ContentFactory; import com.intellij.ui.content.ContentManager; import com.intellij.util.Alarm; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.backend.common.output.OutputFile; import org.jetbrains.kotlin.backend.common.output.OutputFileCollection; import org.jetbrains.kotlin.codegen.ClassBuilderFactories; import org.jetbrains.kotlin.codegen.CompilationErrorHandler; import org.jetbrains.kotlin.codegen.KotlinCodegenFacade; import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.codegen.state.Progress; import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.descriptors.ModuleDescriptor; import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.diagnostics.DiagnosticSink; import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade; import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde; import org.jetbrains.kotlin.idea.util.InfinitePeriodicalTask; import org.jetbrains.kotlin.idea.util.LongRunningReadTask; import org.jetbrains.kotlin.idea.util.ProjectRootsUtil; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor; import javax.swing.*; import java.awt.*; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; import java.util.List; public class KotlinBytecodeToolWindow extends JPanel implements Disposable { private static final int UPDATE_DELAY = 1000; private static final String DEFAULT_TEXT = "/*\n" + "Generated bytecode for Kotlin source file.\n" + "No Kotlin source file is opened.\n" + "*/"; public class UpdateBytecodeToolWindowTask extends LongRunningReadTask<Location, String> { @Override protected Location prepareRequestInfo() { if (!toolWindow.isVisible()) { return null; } Location location = Location .fromEditor(FileEditorManager.getInstance(myProject).getSelectedTextEditor(), myProject); if (location.getEditor() == null) { return null; } JetFile file = location.getJetFile(); if (file == null || !ProjectRootsUtil.isInProjectSource(file)) { return null; } return location; } @NotNull @Override protected Location cloneRequestInfo(@NotNull Location location) { Location newLocation = super.cloneRequestInfo(location); assert location.equals(newLocation) : "cloneRequestInfo should generate same location object"; return newLocation; } @Override protected void hideResultOnInvalidLocation() { setText(DEFAULT_TEXT); } @NotNull @Override protected String processRequest(@NotNull Location location) { JetFile jetFile = location.getJetFile(); assert jetFile != null; return getBytecodeForFile(jetFile, enableInline.isSelected(), enableAssertions.isSelected(), enableOptimization.isSelected()); } @Override protected void onResultReady(@NotNull Location requestInfo, String resultText) { Editor editor = requestInfo.getEditor(); assert editor != null; if (resultText == null) { return; } setText(resultText); int fileStartOffset = requestInfo.getStartOffset(); int fileEndOffset = requestInfo.getEndOffset(); Document document = editor.getDocument(); int startLine = document.getLineNumber(fileStartOffset); int endLine = document.getLineNumber(fileEndOffset); if (endLine > startLine && fileEndOffset > 0 && document.getCharsSequence().charAt(fileEndOffset - 1) == '\n') { endLine--; } Document byteCodeDocument = myEditor.getDocument(); Pair<Integer, Integer> linesRange = mapLines(byteCodeDocument.getText(), startLine, endLine); int endSelectionLineIndex = Math.min(linesRange.second + 1, byteCodeDocument.getLineCount()); int startOffset = byteCodeDocument.getLineStartOffset(linesRange.first); int endOffset = Math.min(byteCodeDocument.getLineStartOffset(endSelectionLineIndex), byteCodeDocument.getTextLength()); myEditor.getCaretModel().moveToOffset(endOffset); myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); myEditor.getCaretModel().moveToOffset(startOffset); myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); myEditor.getSelectionModel().setSelection(startOffset, endOffset); } } private final Editor myEditor; private final Project myProject; private final ToolWindow toolWindow; private final JCheckBox enableInline; private final JCheckBox enableOptimization; private final JCheckBox enableAssertions; public KotlinBytecodeToolWindow(Project project, ToolWindow toolWindow) { super(new BorderLayout()); myProject = project; this.toolWindow = toolWindow; myEditor = EditorFactory.getInstance().createEditor(EditorFactory.getInstance().createDocument(""), project, JavaFileType.INSTANCE, true); add(myEditor.getComponent()); JPanel optionPanel = new JPanel(new FlowLayout()); add(optionPanel, BorderLayout.NORTH); /*TODO: try to extract default parameter from compiler options*/ enableInline = new JCheckBox("Enable inline", true); enableOptimization = new JCheckBox("Enable optimization", true); enableAssertions = new JCheckBox("Enable assertions", true); optionPanel.add(enableInline); optionPanel.add(enableOptimization); optionPanel.add(enableAssertions); new InfinitePeriodicalTask(UPDATE_DELAY, Alarm.ThreadToUse.SWING_THREAD, this, new Computable<LongRunningReadTask>() { @Override public LongRunningReadTask compute() { return new UpdateBytecodeToolWindowTask(); } }).start(); setText(DEFAULT_TEXT); } @NotNull private static String getBytecodeForFile(final JetFile jetFile, boolean enableInline, boolean enableAssertions, boolean enableOptimization) { GenerationState state; try { ResolutionFacade resolutionFacade = ResolvePackage.getResolutionFacade(jetFile); Ref<Set<JetElement>> ref = Ref.create(); BindingContext bindingContext = processInlinedDeclarations(jetFile.getProject(), resolutionFacade, Collections.<JetElement>singleton(jetFile), 1, ref, !enableInline); //We processing another files just to annotate anonymous classes within their inline functions //Bytecode not produced for them cause of filtering via generateClassFilter Set<JetFile> toProcess = new LinkedHashSet<JetFile>(); toProcess.add(jetFile); if (ref.get() != null) { for (JetElement element : ref.get()) { JetFile file = element.getContainingJetFile(); toProcess.add(file); } } GenerationState.GenerateClassFilter generateClassFilter = new GenerationState.GenerateClassFilter() { @Override public boolean shouldGeneratePackagePart(JetFile file) { return file == jetFile; } @Override public boolean shouldAnnotateClass(JetClassOrObject classOrObject) { return true; } @Override public boolean shouldGenerateClass(JetClassOrObject classOrObject) { return classOrObject.getContainingJetFile() == jetFile; } @Override public boolean shouldGenerateScript(JetScript script) { return script.getContainingJetFile() == jetFile; } }; ModuleDescriptor moduleDescriptor = resolutionFacade.findModuleDescriptor(jetFile); state = new GenerationState(jetFile.getProject(), ClassBuilderFactories.TEST, Progress.DEAF, moduleDescriptor, bindingContext, new ArrayList<JetFile>(toProcess), !enableAssertions, !enableAssertions, generateClassFilter, !enableInline, !enableOptimization, null, null, DiagnosticSink.DO_NOTHING, null); KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION); } catch (ProcessCanceledException e) { throw e; } catch (Exception e) { return printStackTraceToString(e); } StringBuilder answer = new StringBuilder(); OutputFileCollection outputFiles = state.getFactory(); for (OutputFile outputFile : outputFiles.asList()) { answer.append("// ================"); answer.append(outputFile.getRelativePath()); answer.append(" =================\n"); answer.append(outputFile.asText()).append("\n\n"); } return answer.toString(); } private static BindingContext processInlinedDeclarations(@NotNull Project project, @NotNull ResolutionFacade resolutionFacade, @NotNull Set<JetElement> originalElements, int deep, @NotNull Ref<Set<JetElement>> resultElements, boolean processOnlyReifiedInline) { AnalysisResult newResult = resolutionFacade.analyzeFullyAndGetResult(originalElements); BindingContext bindingContext = newResult.getBindingContext(); Set<JetElement> collectedElements = new HashSet<JetElement>(); collectedElements.addAll(originalElements); Map<Call, ResolvedCall<?>> contents = bindingContext.getSliceContents(BindingContext.RESOLVED_CALL); for (ResolvedCall call : contents.values()) { CallableDescriptor descriptor = call.getResultingDescriptor(); if (!(descriptor instanceof DeserializedSimpleFunctionDescriptor) && InlineUtil.isInline(descriptor)) { if (!processOnlyReifiedInline || hasReifiedTypeParameters(descriptor)) { PsiElement declaration = DescriptorToSourceUtilsIde.INSTANCE$.getAnyDeclaration(project, descriptor); if (declaration != null && declaration instanceof JetElement) { collectedElements.add((JetElement) declaration); } } } } if (collectedElements.size() != originalElements.size()) { if (newResult.isError() || deep >= 10) { resultElements.set(collectedElements); return bindingContext; } return processInlinedDeclarations(project, resolutionFacade, collectedElements, deep + 1, resultElements, processOnlyReifiedInline); } resultElements.set(collectedElements); return bindingContext; } private static boolean hasReifiedTypeParameters(CallableDescriptor descriptor) { return Iterables.any(descriptor.getTypeParameters(), new Predicate<TypeParameterDescriptor>() { @Override public boolean apply(TypeParameterDescriptor input) { return input.isReified(); } }); } private static Pair<Integer, Integer> mapLines(String text, int startLine, int endLine) { int byteCodeLine = 0; int byteCodeStartLine = -1; int byteCodeEndLine = -1; List<Integer> lines = new ArrayList<Integer>(); for (String line : text.split("\n")) { line = line.trim(); if (line.startsWith("LINENUMBER")) { int ktLineNum = new Scanner(line.substring("LINENUMBER".length())).nextInt() - 1; lines.add(ktLineNum); } } Collections.sort(lines); for (Integer line : lines) { if (line >= startLine) { startLine = line; break; } } for (String line : text.split("\n")) { line = line.trim(); if (line.startsWith("LINENUMBER")) { int ktLineNum = new Scanner(line.substring("LINENUMBER".length())).nextInt() - 1; if (byteCodeStartLine < 0 && ktLineNum == startLine) { byteCodeStartLine = byteCodeLine; } if (byteCodeStartLine > 0 && ktLineNum > endLine) { byteCodeEndLine = byteCodeLine - 1; break; } } if (byteCodeStartLine >= 0 && (line.startsWith("MAXSTACK") || line.startsWith("LOCALVARIABLE") || line.isEmpty())) { byteCodeEndLine = byteCodeLine - 1; break; } byteCodeLine++; } if (byteCodeStartLine == -1 || byteCodeEndLine == -1) { return new Pair<Integer, Integer>(0, 0); } else { return new Pair<Integer, Integer>(byteCodeStartLine, byteCodeEndLine); } } private static String printStackTraceToString(Throwable e) { StringWriter out = new StringWriter(1024); PrintWriter printWriter = new PrintWriter(out); try { e.printStackTrace(printWriter); return out.toString().replace("\r", ""); } finally { printWriter.close(); } } private void setText(@NotNull final String resultText) { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { myEditor.getDocument().setText(StringUtil.convertLineSeparators(resultText)); } }); } @Override public void dispose() { EditorFactory.getInstance().releaseEditor(myEditor); } public static class Factory implements ToolWindowFactory { @Override public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { ContentManager contentManager = toolWindow.getContentManager(); ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); contentManager.addContent( contentFactory.createContent(new KotlinBytecodeToolWindow(project, toolWindow), "", false)); } } }