package com.intellij.flex.uiDesigner; import com.intellij.flex.uiDesigner.abc.EntireMovieTranscoder; import com.intellij.flex.uiDesigner.abc.FxgTranscoder; import com.intellij.flex.uiDesigner.abc.MovieSymbolTranscoder; import com.intellij.flex.uiDesigner.io.*; import com.intellij.flex.uiDesigner.libraries.LibraryManager; import com.intellij.ide.impl.ProjectUtil; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.properties.IProperty; import com.intellij.lang.properties.PropertiesFileType; import com.intellij.lang.properties.psi.PropertiesFile; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.FileTypeIndex; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.ui.AppIcon; import com.intellij.util.ExceptionUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.io.IdPool; import javax.swing.*; import java.io.*; import java.util.ArrayList; import java.util.List; @SuppressWarnings("StaticFieldReferencedViaSubclass") public class SocketInputHandlerImpl extends SocketInputHandler { protected static final Logger LOG = Logger.getInstance(SocketInputHandlerImpl.class.getName()); @Override public Reader getReader() { return reader; } protected Reader reader; private File resultFile; private File appDir; private final List<ActionCallback> callbacks = new ArrayList<>(); private final IdPool callbackIdPool = new IdPool(); @Override public int addCallback(final @NotNull ActionCallback actionCallback) { final int callbackIndex = callbackIdPool.allocate(); if (callbackIndex == callbacks.size()) { callbacks.add(actionCallback); } else { callbacks.set(callbackIndex, actionCallback); } actionCallback.doWhenProcessed(() -> { callbacks.set(callbackIndex, null); callbackIdPool.dispose(callbackIndex); }); return callbackIndex + 1; } protected void createReader(InputStream inputStream) { //noinspection IOResourceOpenedButNotSafelyClosed reader = new Reader(new BufferedInputStream(inputStream)); } protected void init(@NotNull InputStream inputStream, @NotNull File appDir) { createReader(inputStream); this.appDir = appDir; } @Override public void read(@NotNull InputStream inputStream, @NotNull File appDir) throws IOException { init(inputStream, appDir); if (processOnRead()) { process(); } } protected boolean processOnRead() { return true; } public void process() throws IOException { while (true) { final int command = reader.read(); if (command == -1) { break; } processCommandAndNotifyFileBased(command); } } protected static boolean isFileBased(int command) { return command >= ServerMethod.GET_RESOURCE_BUNDLE && command < 100 /* test methods */; } protected void processCommandAndNotifyFileBased(int command) throws IOException { final String resultReadyFilename = isFileBased(command) ? reader.readUTF() : null; try { processCommand(command); } catch (RuntimeException e) { if (!ApplicationManager.getApplication().isUnitTestMode()) { LOG.error(e); } else { throw e; } } catch (AssertionError e) { if (!ApplicationManager.getApplication().isUnitTestMode()) { LOG.error(e); } else { throw e; } } finally { if (resultReadyFilename != null) { //noinspection ResultOfMethodCallIgnored new File(appDir, resultReadyFilename).createNewFile(); } } } protected void processCommand(int command) throws IOException { switch (command) { case ServerMethod.GO_TO_CLASS: goToClass(); break; case ServerMethod.GET_RESOURCE_BUNDLE: getResourceBundle(); break; case ServerMethod.GET_BITMAP_DATA: getBitmapData(); break; case ServerMethod.GET_SWF_DATA: getSwfData(); break; case ServerMethod.GET_EMBED_SWF_ASSET_INFO: case ServerMethod.GET_EMBED_IMAGE_ASSET_INFO: getAssetInfo(command == ServerMethod.GET_EMBED_SWF_ASSET_INFO); break; case ServerMethod.OPEN_FILE: openFile(); break; case ServerMethod.OPEN_FILE_AND_FIND_XML_ATTRIBUTE_OR_TAG: openFileAndFindXmlAttributeOrTag(); break; case ServerMethod.OPEN_DOCUMENT: openDocument(); break; case ServerMethod.RESOLVE_EXTERNAL_INLINE_STYLE_DECLARATION_SOURCE: ApplicationManager.getApplication().invokeLater(new ResolveExternalInlineStyleSourceAction(reader, readModule())); break; case ServerMethod.UNREGISTER_DOCUMENT_FACTORIES: unregisterDocumentFactories(); break; case ServerMethod.UNREGISTER_LIBRARY_SETS: LibraryManager.getInstance().unregister(reader.readIntArray()); break; case ServerMethod.SHOW_ERROR: showError(); break; case ServerMethod.LOG_WARNING: logWarning(); break; case ServerMethod.CLOSE_PROJECT: Client.getInstance().unregisterProject(readProject()); break; case ServerMethod.SAVE_PROJECT_WINDOW_BOUNDS: ProjectWindowBounds.save(readProject(), reader); break; case ServerMethod.SET_PROPERTY: setProperty(); break; case ServerMethod.CALLBACK: executeCallback(); break; default: throw new IllegalArgumentException("unknown client command: " + command); } } @Override public void unregisterDocumentFactories() throws IOException { DocumentFactoryManager.getInstance().unregister(reader.readIntArray()); } protected void executeCallback() throws IOException { int callbackIndex = reader.readUnsignedByte() - 1; boolean success = reader.readBoolean(); ActionCallback callback = callbacks.get(callbackIndex); if (callback == null) { LOG.error("Callback #" + callbackIndex + " doesn't exists"); return; } if (success) { callback.setDone(); } else { callback.setRejected(); } } @NotNull private static XmlFile virtualFileToXmlFile(Project project, VirtualFile virtualFile) { final AccessToken token = ReadAction.start(); try { XmlFile psiFile = (XmlFile)PsiManager.getInstance(project).findFile(virtualFile); assert psiFile != null; return psiFile; } finally { token.finish(); } } private void setProperty() throws IOException { final Project project = readProject(); final DocumentFactoryManager.DocumentInfo info = DocumentFactoryManager.getInstance().getInfo(reader.readUnsignedShort()); final XmlFile psiFile = virtualFileToXmlFile(project, info.getElement()); final XmlTag rootTag = psiFile.getRootTag(); assert rootTag != null; final int offset = info.getRangeMarker(reader.readInt()).getStartOffset() - rootTag.getStartOffsetInParent(); final Document document = FileDocumentManager.getInstance().getDocument(info.getElement()); assert document != null; final String name = reader.readUTF(); final String value; switch (reader.read()) { case Amf3Types.TRUE: value = "true"; break; case Amf3Types.FALSE: value = "false"; break; case Amf3Types.STRING: value = reader.readUTF(); break; default: throw new IllegalArgumentException("unknown value type"); } final XmlTag tag; final AccessToken token = ReadAction.start(); try { tag = PsiTreeUtil.getParentOfType(rootTag.findElementAt(offset), XmlTag.class); assert tag != null; } finally { token.finish(); } ApplicationManager.getApplication().invokeLater(() -> { WriteAction.run(() -> tag.setAttribute(name, value)); info.documentModificationStamp = document.getModificationStamp(); }); } private void openFile() throws IOException { navigateToFile(new OpenFileDescriptor(readProject(), reader.readFile(), reader.readInt())); } private void openFileAndFindXmlAttributeOrTag() throws IOException { Project project = readProject(); VirtualFile file = reader.readFile(); int parentTextOffset = reader.readInt(); final String name = reader.readUTF(); final XmlFile psiFile = virtualFileToXmlFile(project, file); final XmlTag rootTag = psiFile.getRootTag(); assert rootTag != null; final XmlTag parentTag = PsiTreeUtil.getParentOfType(rootTag.findElementAt(parentTextOffset - rootTag.getStartOffsetInParent()), XmlTag.class); assert parentTag != null; XmlAttribute attribute = parentTag.getAttribute(name); final int offset; if (attribute != null) { offset = attribute.getTextOffset(); } else { XmlTag tag = parentTag.findFirstSubTag(name); assert tag != null; offset = tag.getTextOffset(); } navigateToFile(new OpenFileDescriptor(project, file, offset)); } private void openDocument() throws IOException { Project project = readProject(); int documentFactoryId = reader.readUnsignedShort(); int textOffset = reader.readInt(); boolean activateApp = reader.readBoolean(); DocumentFactoryManager.DocumentInfo info = DocumentFactoryManager.getInstance().getInfo(documentFactoryId); navigateToFile(new OpenFileDescriptor(project, info.getElement(), info.getRangeMarker(textOffset).getStartOffset()), activateApp); } private static void navigateToFile(final OpenFileDescriptor openFileDescriptor) { navigateToFile(openFileDescriptor, true); } private static void navigateToFile(final OpenFileDescriptor openFileDescriptor, final boolean activateApp) { ApplicationManager.getApplication().invokeLater(() -> { openFileDescriptor.navigate(true); focusProjectWindow(openFileDescriptor.getProject(), activateApp); }); } public static void focusProjectWindow(final Project p, final boolean activateApp) { final JFrame projectFrame = WindowManager.getInstance().getFrame(p); if (projectFrame == null) { return; } // IdeFocusManager is not working correctly. If we open 2 projects, select another project, select some component in ADL and navigate to project, first project frame must be focused projectFrame.toFront(); IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { IdeFocusManager.getGlobalInstance().requestFocus(projectFrame, true); }); if (activateApp) { AppIcon.getInstance().requestFocus((IdeFrame)WindowManager.getInstance().getFrame(p)); } } private void initResultFile() { if (resultFile == null) { resultFile = new File(appDir, "r"); } } private void getResourceBundle() throws IOException { initResultFile(); final boolean fromModuleSource = reader.readBoolean(); final int moduleId = readEntityId(); final String locale = reader.readUTF(); final String bundleName = reader.readUTF(); final ModuleInfo moduleInfo = Client.getInstance().getRegisteredModules().getNullableInfo(moduleId); PropertiesFile resourceBundle = null; int sourceId = -1; if (moduleInfo == null) { // project may be closed, but client is not closed yet (AppTest#testCloseAndOpenProject) LOG.warn("Skip getResourceBundle(" + locale + ", " + bundleName + ") due to cannot find module with id " + moduleId); } else { if (fromModuleSource) { resourceBundle = getResourceBundleFromModuleSource(moduleInfo.getModule(), bundleName); sourceId = moduleId; } else { Pair<PropertiesFile, Integer> bundleInfo = LibraryManager.getInstance().getResourceBundleFile(locale, bundleName, moduleInfo); if (bundleInfo != null) { resourceBundle = bundleInfo.first; sourceId = bundleInfo.second; } } } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") final FileOutputStream fileOut = new FileOutputStream(resultFile); final AccessToken token = ReadAction.start(); try { writeResourceBundle(resourceBundle, fileOut, sourceId); } finally { token.finish(); fileOut.close(); } } private static PropertiesFile getResourceBundleFromModuleSource(Module module, final String bundleName) { final AccessToken token = ReadAction.start(); try { final PsiManager psiManager = PsiManager.getInstance(module.getProject()); final List<VirtualFile> result = new ArrayList<>(); FileTypeIndex.processFiles(PropertiesFileType.INSTANCE, file -> { if (file.getNameWithoutExtension().equals(bundleName)) { result.add(file); // todo IDEA-74868 if (file.getParent().getName().equals("en_US")) { return false; } } return true; }, module.getModuleScope(false)); PropertiesFile defaultResourceBundle = null; for (VirtualFile file : result) { PsiFile psiFile = psiManager.findFile(file); if (psiFile != null) { if (file.getParent().getName().equals("en_US")) { defaultResourceBundle = (PropertiesFile)psiFile; } else { return (PropertiesFile)psiFile; } } } return defaultResourceBundle; } finally { token.finish(); } } private static void writeResourceBundle(PropertiesFile file, OutputStream out, int sourceId) throws IOException { if (file == null) { out.write(0); out.write(0); return; } //noinspection IOResourceOpenedButNotSafelyClosed final AmfOutputStream amfOut = new AmfOutputStream(new ByteArrayOutputStreamEx(4 * 1024)); amfOut.writeShort(sourceId + 1); // todo Embed, ClassReference, but idea doesn't support it too final List<IProperty> properties = file.getProperties(); amfOut.write(Amf3Types.DICTIONARY); amfOut.writeUInt29((properties.size() << 1) | 1); amfOut.write(0); for (IProperty property : properties) { amfOut.write(property.getUnescapedKey()); amfOut.write(property.getUnescapedValue()); } amfOut.getByteArrayOut().writeTo(out); } private void getBitmapData() throws IOException { initResultFile(); final ImageAssetInfo assetInfo = EmbedImageManager.getInstance().getInfo(reader.readUnsignedShort()); final FileOutputStream fileOut = new FileOutputStream(resultFile); try { ImageUtil.write(assetInfo.file, assetInfo.mimeType, fileOut); } catch (IOException e) { final String userMessage = FlashUIDesignerBundle.message("problem.opening.0", assetInfo.file.getName()); LOG.error(LogMessageUtil.createEvent(userMessage, ExceptionUtil.getThrowableText(e), assetInfo.file)); fileOut.getChannel().truncate(0); } finally { fileOut.close(); } } private void getSwfData() throws IOException { initResultFile(); SwfAssetInfo assetInfo = EmbedSwfManager.getInstance().getInfo(reader.readUnsignedShort()); if (assetInfo.symbolName == null) { new EntireMovieTranscoder().transcode(assetInfo.file, resultFile); } else if (assetInfo.symbolName == EmbedSwfManager.FXG_MARKER) { new FxgTranscoder().transcode(assetInfo.file, resultFile); } else { new MovieSymbolTranscoder().transcode(assetInfo.file, resultFile, assetInfo.symbolName); } } private void getAssetInfo(boolean isSwf) throws IOException { initResultFile(); int assetId = reader.readUnsignedShort(); EmbedAssetInfo assetInfo = isSwf ? EmbedSwfManager.getInstance().getInfo(assetId) : EmbedImageManager.getInstance().getInfo(assetId); ByteArrayOutputStreamEx byteOut = new ByteArrayOutputStreamEx(1024); //noinspection IOResourceOpenedButNotSafelyClosed PrimitiveAmfOutputStream out = new PrimitiveAmfOutputStream(byteOut); Client.writeVirtualFile(assetInfo.file, out); out.writeNullableString(assetInfo instanceof SwfAssetInfo ? ((SwfAssetInfo)assetInfo).symbolName : null); FileOutputStream fileOut = new FileOutputStream(resultFile); try { byteOut.writeTo(fileOut); } catch (IOException e) { LOG.error(LogMessageUtil.createEvent(FlashUIDesignerBundle.message("problem.opening.0", assetInfo.file.getName()), ExceptionUtil.getThrowableText(e), assetInfo.file)); fileOut.getChannel().truncate(0); } finally { out.close(); fileOut.close(); } } private void goToClass() throws IOException { final Module module = readModule(); final String className = reader.readUTF(); ApplicationManager.getApplication().invokeLater(() -> { JSClass classElement = ((JSClass)ActionScriptClassResolver.findClassByQNameStatic(className, module.getModuleWithDependenciesAndLibrariesScope(false))); classElement.navigate(true); ProjectUtil.focusProjectWindow(classElement.getProject(), true); }); } private void showError() throws IOException { try { ApplicationManager.getApplication().getMessageBus().syncPublisher(DesignerApplicationManager.MESSAGE_TOPIC).errorOccurred(); } finally { String userMessage = reader.readUTF(); String technicalMessage = reader.readUTF(); VirtualFile file = reader.readBoolean() ? DocumentFactoryManager.getInstance().getFile(reader.readUnsignedShort()) : null; if (StringUtil.isEmpty(userMessage)) { userMessage = technicalMessage; } LOG.error(LogMessageUtil.createEvent(userMessage, technicalMessage, file)); } } private void logWarning() throws IOException { LOG.warn(reader.readUTF()); } private Module readModule() throws IOException { return Client.getInstance().getModule(readEntityId()); } private Project readProject() throws IOException { return Client.getInstance().getProject(readEntityId()); } private int readEntityId() throws IOException { return reader.readUnsignedShort(); } @Override public void dispose() { if (reader != null) { try { reader.close(); } catch (IOException ignored) { } } } }