/* * Copyright 2011-present Greg Shrago * * 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.intellij.grammar.livePreview; import com.intellij.lang.Language; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import org.intellij.grammar.psi.BnfFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.org.objectweb.asm.ClassWriter; import org.jetbrains.org.objectweb.asm.Label; import org.jetbrains.org.objectweb.asm.MethodVisitor; import java.lang.ref.SoftReference; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.intellij.grammar.generator.BnfConstants.LP_DISPLAY_NAME; import static org.jetbrains.org.objectweb.asm.Opcodes.*; /** * @author gregsh */ public class LivePreviewLanguage extends Language { private final VirtualFilePointer myFilePointer; private final SoftReference<BnfFile> myBnfFile; public static final Language BASE_INSTANCE = new Language("BNF_LP") { @NotNull @Override public String getDisplayName() { return LP_DISPLAY_NAME; } }; private static final MyClassLoader ourClassLoader = new MyClassLoader(); protected LivePreviewLanguage(@NotNull BnfFile grammarFile) { super(BASE_INSTANCE, ObjectUtils.assertNotNull(grammarFile.getVirtualFile()).getPath()); VirtualFile virtualFile = ObjectUtils.assertNotNull(grammarFile.getVirtualFile()); Application app = ApplicationManager.getApplication(); if (app.isUnitTestMode()) { myBnfFile = new SoftReference<>(grammarFile); myFilePointer = null; } else { myFilePointer = VirtualFilePointerManager.getInstance().create(virtualFile, app, null); myBnfFile = null; } } @NotNull @Override public String getDisplayName() { VirtualFile file = getGrammarFile(); return file == null ? getID() : "'" + file.getName() + "' grammar"; } @Nullable public VirtualFile getGrammarFile() { if (myBnfFile != null) { BnfFile file = myBnfFile.get(); return file == null? null : file.getVirtualFile(); } else { return myFilePointer.getFile(); } } @Nullable public BnfFile getGrammar(@Nullable Project project) { if (myBnfFile != null) return myBnfFile.get(); VirtualFile file = project == null? null : getGrammarFile(); PsiFile psiFile = file == null? null : PsiManager.getInstance(project).findFile(file); return psiFile instanceof BnfFile? (BnfFile)psiFile : null; } @NotNull public static LivePreviewLanguage newInstance(PsiFile psiFile) { try { return (LivePreviewLanguage)ourClassLoader.createClass().getDeclaredConstructors()[0].newInstance(psiFile); } catch (Exception e) { throw new RuntimeException(e); } } @Nullable public static LivePreviewLanguage findInstance(PsiFile psiFile) { VirtualFile vFile = psiFile.getVirtualFile(); if (vFile == null) return null; for (Language language : Language.getRegisteredLanguages()) { if (language instanceof LivePreviewLanguage && vFile.equals(((LivePreviewLanguage)language).getGrammarFile())) { return (LivePreviewLanguage)language; } } return null; } @NotNull public List<Editor> getGrammarEditors(@NotNull Project project) { VirtualFile file = getGrammarFile(); if (file == null) return Collections.emptyList(); FileEditor[] editors = FileEditorManager.getInstance(project).getAllEditors(file); if (editors.length == 0) return Collections.emptyList(); List<Editor> result = ContainerUtil.newArrayList(); for (FileEditor editor : editors) { if (editor instanceof TextEditor) result.add(((TextEditor)editor).getEditor()); } return result; } @NotNull public List<Editor> getPreviewEditors(@NotNull Project project) { FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); VirtualFile[] files = fileEditorManager.getOpenFiles(); if (files.length == 0) return Collections.emptyList(); List<Editor> result = ContainerUtil.newArrayList(); PsiManager psiManager = PsiManager.getInstance(project); for (VirtualFile file : files) { PsiFile psiFile = psiManager.findFile(file); Language language = psiFile == null ? null : psiFile.getLanguage(); if (language == this) { for (FileEditor editor : fileEditorManager.getAllEditors(file)) { if (editor instanceof TextEditor) result.add(((TextEditor)editor).getEditor()); } } } return result; } private static class MyClassLoader extends ClassLoader { private final AtomicInteger myCounter = new AtomicInteger(); MyClassLoader() { super(LivePreviewHelper.class.getClassLoader()); } Class<LivePreviewLanguage> createClass() throws Exception { int index = myCounter.incrementAndGet(); String className = LivePreviewLanguage.class.getName() + "$$_" + index; byte[] b = dump(className); return (Class<LivePreviewLanguage>)defineClass(className, b, 0, b.length); } public static byte[] dump(String className) throws Exception { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, className.replace(".", "/"), null, "org/intellij/grammar/livePreview/LivePreviewLanguage", null); { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Lorg/intellij/grammar/psi/BnfFile;)V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, "org/intellij/grammar/livePreview/LivePreviewLanguage", "<init>", "(Lorg/intellij/grammar/psi/BnfFile;)V", false); mv.visitInsn(RETURN); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLocalVariable("this", "L" + className.replace(".", "/") + ";", null, l0, l1, 0); mv.visitLocalVariable("grammarFile", "Lorg/intellij/grammar/psi/BnfFile;", null, l0, l1, 1); mv.visitMaxs(3, 2); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } } }