package com.intellij.flex.uiDesigner;
import com.intellij.flex.uiDesigner.io.StringRegistry;
import com.intellij.flex.uiDesigner.libraries.FlexLibrarySet;
import com.intellij.flex.uiDesigner.mxml.ProjectComponentReferenceCounter;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.AsyncResult;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.css.CssFileType;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.Consumer;
import com.intellij.util.messages.MessageBus;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectObjectProcedure;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.intellij.flex.uiDesigner.LogMessageUtil.LOG;
class ComplexRenderAction extends RenderActionQueue.RenderAction<AsyncResult<List<DocumentFactoryManager.DocumentInfo>>> {
private Document[] documents;
final boolean onlyStyle;
private final boolean reportProblems;
ComplexRenderAction(Document[] documents, boolean onlyStyle, boolean reportProblems) {
super(null, null, new AsyncResult<>());
this.documents = documents;
this.onlyStyle = onlyStyle;
this.reportProblems = reportProblems;
}
@Override
protected boolean isNeedEdt() {
return false;
}
void merge(Document[] otherDocuments) {
THashSet<Document> merged = new THashSet<>(documents.length + otherDocuments.length);
Collections.addAll(merged, documents);
Collections.addAll(merged, otherDocuments);
documents = merged.toArray(new Document[merged.size()]);
}
@Override
protected void doRun() {
renderDocumentsAndCheckLocalStyleModification(result);
result.doWhenDone((Consumer<List<DocumentFactoryManager.DocumentInfo>>)infos -> {
Application application = ApplicationManager.getApplication();
if (application.isDisposed()) {
return;
}
MessageBus messageBus = application.getMessageBus();
for (DocumentFactoryManager.DocumentInfo info : infos) {
messageBus.syncPublisher(DesignerApplicationManager.MESSAGE_TOPIC).documentRendered(info);
}
});
}
private void renderDocumentsAndCheckLocalStyleModification(AsyncResult<List<DocumentFactoryManager.DocumentInfo>> result) {
final Client client = Client.getInstance();
final List<DocumentFactoryManager.DocumentInfo> documentInfos = new ArrayList<>(documents.length);
final THashMap<ModuleInfo, List<LocalStyleHolder>> localStyleSources = new THashMap<>();
collectChanges(documentInfos, localStyleSources);
if (!localStyleSources.isEmpty()) {
updateLocalStyleSources(client, localStyleSources);
}
client.renderDocumentAndDependents(documentInfos, localStyleSources, result);
}
private void updateLocalStyleSources(final Client client, final THashMap<ModuleInfo, List<LocalStyleHolder>> localStyleSources) {
final ProblemsHolder problemsHolder = new ProblemsHolder();
final ProjectComponentReferenceCounter projectComponentReferenceCounter = new ProjectComponentReferenceCounter();
localStyleSources.forEachEntry(new TObjectObjectProcedure<ModuleInfo, List<LocalStyleHolder>>() {
@Override
public boolean execute(ModuleInfo moduleInfo, List<LocalStyleHolder> b) {
try {
List<LocalStyleHolder> oldList = moduleInfo.getLocalStyleHolders();
if (oldList == null) {
oldList = Collections.emptyList();
}
FlexLibrarySet flexLibrarySet = moduleInfo.getFlexLibrarySet();
final StringRegistry.StringWriter stringWriter = new StringRegistry.StringWriter();
stringWriter.startChange();
try {
List<LocalStyleHolder> list = ModuleInfoUtil.collectLocalStyle(moduleInfo, flexLibrarySet.getVersion(), stringWriter,
problemsHolder, projectComponentReferenceCounter,
flexLibrarySet.assetCounterInfo.demanded);
// todo we shouldn't create list, we should check while collecting
boolean hasChanges = true;
if (list.size() == oldList.size()) {
int diff = list.size();
for (LocalStyleHolder holder : list) {
if (oldList.contains(holder)) {
diff--;
}
}
hasChanges = diff != 0;
}
if (hasChanges) {
moduleInfo.setLocalStyleHolders(list);
client.fillAssetClassPoolIfNeed(flexLibrarySet);
client.updateLocalStyleHolders(localStyleSources, stringWriter);
if (projectComponentReferenceCounter.hasUnregistered()) {
client.registerDocumentReferences(projectComponentReferenceCounter.unregistered, null, problemsHolder);
}
}
else {
stringWriter.rollback();
localStyleSources.remove(moduleInfo);
}
}
catch (Throwable e) {
stringWriter.rollback();
LOG.error(e);
}
}
catch (Throwable e) {
LOG.error(e);
}
return true;
}
});
if (!problemsHolder.isEmpty() && reportProblems) {
DocumentProblemManager.getInstance().report(null, problemsHolder);
}
}
private void collectChanges(List<DocumentFactoryManager.DocumentInfo> documentInfos, THashMap<ModuleInfo, List<LocalStyleHolder>> localStyleSources) {
final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
final DocumentFactoryManager documentFactoryManager = DocumentFactoryManager.getInstance();
final Client client = Client.getInstance();
for (Document document : documents) {
final VirtualFile file = fileDocumentManager.getFile(document);
if (file == null) {
continue;
}
boolean isMxml = JavaScriptSupportLoader.isFlexMxmFile(file);
if (isMxml || file.getFileType() == CssFileType.INSTANCE) {
if (!collectChangedLocalStyleSources(localStyleSources, file) && onlyStyle) {
// if onlyStyle and we didn't find changed local style sources, so, it is new style source - we must collect style sources for appropriate module
Project p = ProjectUtil.guessProjectForFile(file);
if (p != null) {
ModuleInfo info = client.getRegisteredModules().getNullableInfo(ModuleUtilCore.findModuleForFile(file, p));
if (info != null) {
localStyleSources.put(info, Collections.emptyList());
}
}
}
}
final DocumentFactoryManager.DocumentInfo info = isMxml ? documentFactoryManager.getNullableInfo(file) : null;
if (info == null) {
continue;
}
else if (onlyStyle) {
info.documentModificationStamp = document.getModificationStamp();
continue;
}
if (info.documentModificationStamp == document.getModificationStamp()) {
continue;
}
final Project project = ProjectUtil.guessProjectForFile(file);
if (project == null) {
continue;
}
final Module module = ModuleUtilCore.findModuleForFile(file, project);
if (module == null) {
continue;
}
final XmlFile psiFile;
final AccessToken token = ReadAction.start();
try {
psiFile = (XmlFile)PsiDocumentManager.getInstance(project).getPsiFile(document);
if (psiFile == null) {
continue;
}
}
finally {
token.finish();
}
if (client.updateDocumentFactory(info.getId(), module, psiFile, reportProblems)) {
info.documentModificationStamp = document.getModificationStamp();
documentInfos.add(info);
}
}
}
private static boolean collectChangedLocalStyleSources(final THashMap<ModuleInfo, List<LocalStyleHolder>> holders,
final VirtualFile file) {
final Ref<Boolean> result = new Ref<>(false);
Client.getInstance().getRegisteredModules().forEach(moduleInfo -> {
if (holders.containsKey(moduleInfo)) {
result.set(true);
return false;
}
List<LocalStyleHolder> styleHolders = moduleInfo.getLocalStyleHolders();
if (styleHolders != null) {
List<LocalStyleHolder> list = null;
for (LocalStyleHolder styleHolder : styleHolders) {
if (styleHolder.file.equals(file)) {
if (list == null) {
list = new ArrayList<>();
holders.put(moduleInfo, list);
result.set(true);
}
list.add(styleHolder);
}
}
if (list != null) {
// well, local style applicable only for one module, so,
// if we found for this module, there is no reason to continue search
return false;
}
}
return true;
});
return result.get();
}
}