package com.intellij.flex.uiDesigner;
import com.intellij.flex.uiDesigner.abc.ClassPoolGenerator;
import com.intellij.flex.uiDesigner.io.AmfOutputStream;
import com.intellij.flex.uiDesigner.io.BlockDataOutputStream;
import com.intellij.flex.uiDesigner.io.PrimitiveAmfOutputStream;
import com.intellij.flex.uiDesigner.io.StringRegistry;
import com.intellij.flex.uiDesigner.libraries.*;
import com.intellij.flex.uiDesigner.mxml.MxmlWriter;
import com.intellij.flex.uiDesigner.mxml.ProjectComponentReferenceCounter;
import com.intellij.javascript.flex.mxml.FlexCommonTypeNames;
import com.intellij.javascript.flex.resolve.ActionScriptClassResolver;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.ecmal4.XmlBackedJSClassFactory;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.AsyncResult;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.Consumer;
import gnu.trove.THashMap;
import gnu.trove.TObjectObjectProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.io.InfoMap;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import static com.intellij.flex.uiDesigner.DocumentFactoryManager.DocumentInfo;
public class Client implements Disposable {
protected final BlockDataOutputStream blockOut = new BlockDataOutputStream();
protected final AmfOutputStream out = new AmfOutputStream(blockOut);
private final InfoMap<Module, ModuleInfo> registeredModules = new InfoMap<>(true);
private final InfoMap<Project, ProjectInfo> registeredProjects = new InfoMap<>();
private final ReentrantLock outLock = new ReentrantLock();
public static Client getInstance() {
return DesignerApplicationManager.getService(Client.class);
}
public void setOut(@NotNull OutputStream socketOut) {
blockOut.setOut(socketOut);
}
public boolean isModuleRegistered(Module module) {
return registeredModules.contains(module);
}
public InfoMap<Project, ProjectInfo> getRegisteredProjects() {
return registeredProjects;
}
public InfoMap<Module, ModuleInfo> getRegisteredModules() {
return registeredModules;
}
@NotNull
public Module getModule(int id) {
return registeredModules.getElement(id);
}
@NotNull
public Project getProject(int id) {
return registeredProjects.getElement(id);
}
@Override
public void dispose() {
registeredModules.dispose();
}
public boolean flush() {
try {
out.flush();
return true;
}
catch (IOException e) {
LogMessageUtil.processInternalError(e);
}
return false;
}
private void beginMessage(ClientMethod method) {
beginMessage(method, null, null, null);
}
private void beginMessage(ClientMethod method, ActionCallback callback) {
beginMessage(method, callback, null, null);
}
private void beginMessage(ClientMethod method,
@Nullable ActionCallback callback,
@Nullable ActionCallback rejectedCallback,
@Nullable Runnable doneRunnable) {
outLock.lock();
if (callback != null) {
if (rejectedCallback != null) {
callback.notifyWhenRejected(rejectedCallback);
}
if (doneRunnable != null) {
callback.doWhenDone(doneRunnable);
}
}
blockOut.assertStart();
out.write(ClientMethod.METHOD_CLASS);
out.write(callback == null ? 0 : SocketInputHandler.getInstance().addCallback(callback));
out.write(method);
}
public void openProject(Project project) {
boolean hasError = true;
try {
beginMessage(ClientMethod.openProject);
writeId(project);
out.writeAmfUtf(project.getName());
ProjectWindowBounds.write(project, out);
hasError = false;
}
finally {
finalizeMessageAndFlush(hasError);
}
}
public void closeProject(final Project project) {
boolean hasError = true;
try {
beginMessage(ClientMethod.closeProject);
writeId(project);
hasError = false;
}
finally {
try {
finalizeMessageAndFlush(hasError);
}
finally {
unregisterProject(project);
}
}
}
public void unregisterProject(final Project project) {
DocumentFactoryManager.getInstance().unregister(project);
registeredProjects.remove(project);
if (registeredProjects.isEmpty()) {
registeredModules.clear();
}
else {
registeredModules.remove(new TObjectObjectProcedure<Module, ModuleInfo>() {
@Override
public boolean execute(Module module, ModuleInfo info) {
return module.getProject() != project;
}
});
}
}
public ActionCallback unregisterModule(final Module module) {
boolean hasError = true;
final ActionCallback callback = new ActionCallback("renderDocumentAndDependents");
try {
hasError = false;
beginMessage(ClientMethod.unregisterModule, callback, null, () -> {
try {
SocketInputHandler.getInstance().unregisterDocumentFactories();
}
catch (IOException e) {
LogMessageUtil.LOG.error(e);
}
});
writeId(module);
}
finally {
try {
registeredModules.remove(module);
}
finally {
finalizeMessageAndFlush(hasError, callback);
}
}
return callback;
}
public void updateStringRegistry(StringRegistry.StringWriter stringWriter) throws IOException {
boolean hasError = true;
try {
beginMessage(ClientMethod.updateStringRegistry);
stringWriter.writeTo(out);
hasError = false;
}
finally {
finalizeMessage(hasError);
}
}
public void registerLibrarySet(LibrarySet librarySet) {
final List<Library> styleOwners = new ArrayList<>();
for (Library library : librarySet.getLibraries()) {
if (library.isStyleOwner()) {
styleOwners.add(library);
}
}
boolean hasError = true;
try {
beginMessage(ClientMethod.registerLibrarySet);
out.writeUInt29(librarySet.getId());
out.write(librarySet instanceof FlexLibrarySet);
LibrarySet parent = librarySet.getParent();
out.writeShort(parent == null ? -1 : parent.getId());
out.write(styleOwners.size());
final LibraryManager libraryManager = LibraryManager.getInstance();
for (Library library : styleOwners) {
final boolean registered = libraryManager.isRegistered(library);
out.write(registered);
if (registered) {
out.writeUInt29(library.getId());
}
else {
out.writeUInt29(libraryManager.add(library));
writeVirtualFile(library.getFile(), out);
if (library.inheritingStyles == null) {
out.writeShort(0);
}
else {
out.write(library.inheritingStyles);
}
if (library.defaultsStyle == null) {
out.write(0);
}
else {
out.write(1);
out.write(library.defaultsStyle);
}
}
}
hasError = false;
}
finally {
finalizeMessage(hasError);
}
}
public void registerModule(Project project, ModuleInfo moduleInfo, StringRegistry.StringWriter stringWriter) {
boolean hasError = true;
try {
beginMessage(ClientMethod.registerModule);
stringWriter.writeToIfStarted(out);
out.writeShort(registeredModules.add(moduleInfo));
writeId(project);
out.writeShort(moduleInfo.getLibrarySet().getId());
out.write(moduleInfo.isApp());
out.write(moduleInfo.getLocalStyleHolders(), "lsh", true, true);
hasError = false;
}
finally {
finalizeMessage(hasError);
if (hasError) {
stringWriter.rollback();
}
}
}
public void renderDocument(Module module, XmlFile psiFile) {
renderDocument(module, psiFile, new ProblemsHolder());
}
/**
* final, full render document - responsible for handle problemsHolder and assetCounter - you must not do it
*/
public AsyncResult<DocumentInfo> renderDocument(Module module, XmlFile psiFile, ProblemsHolder problemsHolder) {
VirtualFile virtualFile = psiFile.getVirtualFile();
final int factoryId = registerDocumentFactoryIfNeed(module, psiFile, virtualFile, false, problemsHolder);
final AsyncResult<DocumentInfo> result = new AsyncResult<>();
if (factoryId == -1) {
result.setRejected();
return result;
}
FlexLibrarySet flexLibrarySet = registeredModules.getInfo(module).getFlexLibrarySet();
if (flexLibrarySet != null) {
fillAssetClassPoolIfNeed(flexLibrarySet);
}
if (!problemsHolder.isEmpty()) {
DocumentProblemManager.getInstance().report(module.getProject(), problemsHolder);
}
final ActionCallback callback = new ActionCallback("renderDocument");
boolean hasError = true;
try {
beginMessage(ClientMethod.renderDocument, callback, result,
() -> result.setDone(DocumentFactoryManager.getInstance().getInfo(factoryId)));
out.writeShort(factoryId);
hasError = false;
}
finally {
finalizeMessageAndFlush(hasError);
}
return result;
}
public void fillAssetClassPoolIfNeed(FlexLibrarySet librarySet) {
final AssetCounterInfo assetCounterInfo = librarySet.assetCounterInfo;
int diff = assetCounterInfo.demanded.imageCount - assetCounterInfo.allocated.imageCount;
if (diff > 0) {
// reduce number of call fill asset class pool
diff *= 2;
fillAssetClassPool(librarySet, diff, ClassPoolGenerator.Kind.IMAGE);
assetCounterInfo.allocated.imageCount += diff;
}
diff = assetCounterInfo.demanded.swfCount - assetCounterInfo.allocated.swfCount;
if (diff > 0) {
// reduce number of call fill asset class pool
diff *= 2;
fillAssetClassPool(librarySet, diff, ClassPoolGenerator.Kind.SWF);
assetCounterInfo.allocated.swfCount += diff;
}
diff = assetCounterInfo.demanded.viewCount - assetCounterInfo.allocated.viewCount;
if (diff > 0) {
// reduce number of call fill asset class pool
diff *= 2;
fillAssetClassPool(librarySet, diff, ClassPoolGenerator.Kind.SPARK_VIEW);
assetCounterInfo.allocated.viewCount += diff;
}
}
private void fillAssetClassPool(FlexLibrarySet flexLibrarySet, int classCount, ClassPoolGenerator.Kind kind) {
boolean hasError = true;
try {
if (kind == ClassPoolGenerator.Kind.IMAGE) {
beginMessage(ClientMethod.fillImageClassPool);
}
else {
beginMessage(kind == ClassPoolGenerator.Kind.SWF ? ClientMethod.fillSwfClassPool : ClientMethod.fillViewClassPool);
}
writeId(flexLibrarySet.getId());
out.writeShort(classCount);
ClassPoolGenerator.generate(kind, classCount, flexLibrarySet.assetCounterInfo.allocated, blockOut);
hasError = false;
}
catch (Throwable e) {
LogMessageUtil.processInternalError(e, null);
}
finally {
finalizeMessage(hasError);
}
}
private void finalizeMessage(boolean hasError) {
try {
if (hasError) {
blockOut.rollback();
}
else {
try {
blockOut.end();
}
catch (IOException e) {
LogMessageUtil.processInternalError(e);
}
}
out.resetAfterMessage();
}
finally {
outLock.unlock();
}
}
private void finalizeMessageAndFlush(boolean hasError) {
finalizeMessageAndFlush(hasError, null);
}
private void finalizeMessageAndFlush(boolean hasError, @Nullable ActionCallback callback) {
if (hasError) {
try {
blockOut.rollback();
}
finally {
outLock.unlock();
if (callback != null) {
callback.setRejected();
}
}
}
else {
try {
out.flush();
}
catch (IOException e) {
if (callback != null) {
callback.setRejected();
}
LogMessageUtil.processInternalError(e);
}
finally {
outLock.unlock();
}
}
}
public void updateLocalStyleHolders(THashMap<ModuleInfo, List<LocalStyleHolder>> holders, StringRegistry.StringWriter stringWriter) {
boolean hasError = false;
try {
beginMessage(ClientMethod.updateLocalStyleHolders);
stringWriter.writeTo(out);
out.writeUInt29(holders.size());
holders.forEachKey(moduleInfo -> {
out.writeUInt29(moduleInfo.getId());
out.write(moduleInfo.getLocalStyleHolders(), "lsh", true, true);
return true;
});
}
catch (Throwable e) {
hasError = true;
stringWriter.rollback();
LogMessageUtil.processInternalError(e);
}
finally {
finalizeMessage(hasError);
}
}
public boolean updateDocumentFactory(int factoryId, Module module, XmlFile psiFile, boolean reportProblems) {
try {
beginMessage(ClientMethod.updateDocumentFactory);
out.writeShort(factoryId);
final ProblemsHolder problemsHolder = new ProblemsHolder();
boolean result = writeDocumentFactory(DocumentFactoryManager.getInstance().getInfo(factoryId), module, psiFile, problemsHolder);
if (!problemsHolder.isEmpty() && reportProblems) {
DocumentProblemManager.getInstance().report(module.getProject(), problemsHolder);
}
if (result) {
return true;
}
}
catch (Throwable e) {
LogMessageUtil.processInternalError(e, psiFile.getVirtualFile());
}
finally {
outLock.unlock();
}
blockOut.rollback();
return false;
}
public AsyncResult<List<DocumentInfo>> renderDocumentAndDependents(@Nullable List<DocumentInfo> infos,
THashMap<ModuleInfo, List<LocalStyleHolder>> outdatedLocalStyleHolders,
final AsyncResult<List<DocumentInfo>> result) {
if ((infos == null || infos.isEmpty()) && outdatedLocalStyleHolders.isEmpty()) {
result.setDone(infos);
return result;
}
final ActionCallback callback = new ActionCallback("renderDocumentAndDependents");
boolean hasError = true;
try {
beginMessage(ClientMethod.renderDocumentsAndDependents, callback, result, () -> {
final int[] ids;
try {
ids = SocketInputHandler.getInstance().getReader().readIntArray();
}
catch (IOException e) {
LogMessageUtil.processInternalError(e);
return;
}
DocumentFactoryManager documentFactoryManager = DocumentFactoryManager.getInstance();
List<DocumentInfo> rendered = new ArrayList<>(ids.length);
for (int id : ids) {
rendered.add(documentFactoryManager.getInfo(id));
}
result.setDone(rendered);
});
out.writeUInt29(outdatedLocalStyleHolders.size());
outdatedLocalStyleHolders.forEachKey(moduleInfo -> {
out.writeUInt29(moduleInfo.getId());
return true;
});
out.write(infos);
hasError = false;
}
finally {
finalizeMessageAndFlush(hasError, callback);
}
return result;
}
private int registerDocumentFactoryIfNeed(Module module, XmlFile psiFile, VirtualFile virtualFile, boolean force,
ProblemsHolder problemsHolder) {
final DocumentFactoryManager documentFactoryManager = DocumentFactoryManager.getInstance();
final boolean registered = !force && documentFactoryManager.isRegistered(virtualFile);
final DocumentInfo documentInfo = documentFactoryManager.get(virtualFile, null, null);
if (!registered) {
boolean hasError = true;
try {
beginMessage(ClientMethod.registerDocumentFactory);
writeId(module);
out.writeShort(documentInfo.getId());
writeVirtualFile(virtualFile, out);
hasError = !writeDocumentFactory(documentInfo, module, psiFile, problemsHolder);
}
catch (Throwable e) {
LogMessageUtil.processInternalError(e, virtualFile);
}
finally {
try {
if (hasError) {
blockOut.rollback();
//noinspection ReturnInsideFinallyBlock
return -1;
}
}
finally {
outLock.unlock();
}
}
}
return documentInfo.getId();
}
/**
* You must rollback block out if this method returns false
*/
private boolean writeDocumentFactory(DocumentInfo documentInfo,
Module module,
XmlFile psiFile,
ProblemsHolder problemsHolder) throws IOException {
final AccessToken token = ReadAction.start();
final int flags;
try {
final JSClass jsClass = XmlBackedJSClassFactory.getXmlBackedClass(psiFile);
assert jsClass != null;
out.writeAmfUtf(jsClass.getQualifiedName());
if (ActionScriptClassResolver.isParentClass(jsClass, FlexCommonTypeNames.SPARK_APPLICATION) ||
ActionScriptClassResolver.isParentClass(jsClass, FlexCommonTypeNames.MX_APPLICATION)) {
flags = 1;
}
else if (ActionScriptClassResolver.isParentClass(jsClass, FlexCommonTypeNames.IUI_COMPONENT)) {
flags = 0;
}
else {
flags = 2;
}
}
finally {
token.finish();
}
out.write(flags);
Pair<ProjectComponentReferenceCounter, List<RangeMarker>> result =
new MxmlWriter(out, problemsHolder, registeredModules.getInfo(module).getFlexLibrarySet().assetCounterInfo.demanded).write(psiFile);
if (result == null) {
return false;
}
blockOut.end();
documentInfo.setRangeMarkers(result.second);
return result.first.unregistered.isEmpty() || registerDocumentReferences(result.first.unregistered, module, problemsHolder);
}
public boolean registerDocumentReferences(List<XmlFile> files, @Nullable Module module, ProblemsHolder problemsHolder) {
for (XmlFile file : files) {
VirtualFile virtualFile = file.getViewProvider().getVirtualFile();
Module documentModule = ModuleUtilCore.findModuleForFile(virtualFile, file.getProject());
assert documentModule != null;
if (module != documentModule && !isModuleRegistered(documentModule)) {
try {
LibraryManager.getInstance().registerModule(documentModule, problemsHolder);
}
catch (InitException e) {
LogMessageUtil.LOG.error(e.getCause());
// todo unclear error message (module will not be specified in this error message (but must be))
problemsHolder.add(e.getMessage());
}
}
// force register, it is registered (id allocated) only on server side
if (registerDocumentFactoryIfNeed(documentModule, file, virtualFile, true, problemsHolder) == -1) {
return false;
}
}
return true;
}
public void selectComponent(int documentId, int componentId) {
boolean hasError = true;
try {
beginMessage(ClientMethod.selectComponent);
out.writeUInt29(documentId);
out.writeUInt29(componentId);
hasError = false;
}
finally {
finalizeMessageAndFlush(hasError);
}
}
public ActionCallback updatePropertyOrStyle(int documentId, int componentId, Consumer<AmfOutputStream> streamConsumer) {
final ActionCallback callback = new ActionCallback("updatePropertyOrStyle");
boolean hasError = true;
try {
beginMessage(ClientMethod.updatePropertyOrStyle, callback);
out.writeUInt29(documentId);
out.writeUInt29(componentId);
streamConsumer.consume(out);
hasError = false;
}
finally {
finalizeMessageAndFlush(hasError, callback);
}
return callback;
}
//public AsyncResult<BufferedImage> getDocumentImage(DocumentFactoryManager.DocumentInfo documentInfo) {
// final AsyncResult<BufferedImage> result = new AsyncResult<BufferedImage>();
// getDocumentImage(documentInfo, result);
// return result;
//}
public void getDocumentImage(DocumentInfo documentInfo, final AsyncResult<BufferedImage> result) {
final ActionCallback callback = new ActionCallback("getDocumentImage");
boolean hasError = true;
try {
beginMessage(ClientMethod.getDocumentImage, callback, result, () -> {
Reader reader = SocketInputHandler.getInstance().getReader();
try {
result.setDone(reader.readImage());
}
catch (IOException e) {
LogMessageUtil.LOG.error(e);
result.setRejected();
}
});
out.writeShort(documentInfo.getId());
hasError = false;
}
finally {
finalizeMessageAndFlush(hasError, callback);
}
}
public static void writeVirtualFile(VirtualFile file, PrimitiveAmfOutputStream out) {
out.writeAmfUtf(file.getUrl());
out.writeAmfUtf(file.getPresentableUrl());
}
public void initStringRegistry() throws IOException {
StringRegistry stringRegistry = StringRegistry.getInstance();
boolean hasError = true;
try {
beginMessage(ClientMethod.initStringRegistry);
out.write(stringRegistry.toArray());
hasError = false;
}
finally {
finalizeMessage(hasError);
}
}
public void writeId(Module module, PrimitiveAmfOutputStream out) {
out.writeShort(registeredModules.getId(module));
}
private void writeId(Module module) {
writeId(module, out);
}
private void writeId(Project project) {
writeId(registeredProjects.getId(project));
}
private void writeId(int id) {
out.writeShort(id);
}
private enum ClientMethod {
openProject, closeProject, registerLibrarySet, registerModule, unregisterModule, registerDocumentFactory, updateDocumentFactory, renderDocument, renderDocumentsAndDependents,
initStringRegistry, updateStringRegistry, fillImageClassPool, fillSwfClassPool, fillViewClassPool,
selectComponent, getDocumentImage, updatePropertyOrStyle, updateLocalStyleHolders;
public static final int METHOD_CLASS = 0;
}
}