XML DOM API - Bind attributes Follow
I'm trying to implement XML DOM API and bind xml to UI developed with Swing. I generated interfaces from dtd using IDEA's tool, implemented my own file editor provider that extends PerspectiveFileEditorProvider and editor itself that extends BasicDomElementComponent, built swing form and bound fields using bindProperties();
The problem is when a type set in an interface is GenericDomValue then everything works fine but for fields that bound to GenericAttributeValue the UI filed becomes disabled
If I create TextControl for such fields manually then everything works fine except for default value: I have to call the control's reset() method explicitly to fill the UI field with a value taken from xml, otherwise it's empty but if I start typing then changes reflect in xml so it means that binding was successful.
The main problem is it's not elegant to create text controls manually because I will have to create too many of them and, what much more important, undoing (CTRL+Z) for these fields causes "java.lang.RuntimeException: java.lang.reflect.InvocationTargetException" or
"java.lang.IllegalStateException: Attempt to modify PSI for non-committed Document!" depending on a way I create the controls, with DomUIFactory or TextControl constructor respectively.
interface field:
@NotNull
@Required
GenericAttributeValue<String> getPackagePath();
Creation with DomUIFactory:
DomUIControl<GenericDomValue> packagePathControl = DomUIFactory.createControl(domElement.getPackagePath(), true);
packagePathControl.bind(myPackagePath);
packagePathControl.reset();
Creation with TextControl constructor:
TextControl packagePathControl = new TextControl(new DomWrapper<String>() {
@NotNull
@Override
public DomElement getExistingDomElement() {
return domElement.getPackagePath();
}
@Nullable
@Override
public DomElement getWrappedElement() {
return null;
}
@Override
public void setValue(String value) throws IllegalAccessException, InvocationTargetException {
domElement.getPackagePath().setValue(value);
}
@Override
public String getValue() throws IllegalAccessException, InvocationTargetException {
return domElement.getPackagePath().getValue();
}
}, true);
packagePathControl.bind(myPackagePath);
packagePathControl.reset();
myPackagePath is a var of type TextPanel.
Overriden commit() method to prevent
java.lang.Throwable: Write-unsafe context! Model changes are allowed from write-safe contexts only. Please ensure you're using invokeLater/invokeAndWait with a correct modality state (not "any").
@Override
public void commit() {
ApplicationManager.getApplication().invokeLater(super::commit);
}
I will be grateful for the help.
Please sign in to leave a comment.
Please link full sources of everything around this DOM editing. Why does your editor implement BasicDomElementComponent instead of DomFileEditor?
Oh, my bad. I think I explained the editor implementing wrong. Here is a couple of code snippets related to the editor. Maybe I misunderstood the XML DOM API DevGuide...
ServiceBuilder represents a root tag so all other tags are included into it.
Here is a link to github repo. This is a kind of working repo so there are some unused classes and parts of code that are written just for testing purposes.
https://github.com/IvanRiazantsev/service-builder-editor
Thanks for sharing the code.
Indeed attributes are not bound automatically in com.intellij.util.xml.ui.BasicDomElementComponent#bindProperties(com.intellij.util.xml.DomElement) due to check ("description instanceof DomFixedChildDescription)". You could easily override bindProperties() in your class and add similar automatic handling for attributes.
PS: How is this existing plugin related to yours? https://plugins.jetbrains.com/plugin/10739-liferay-intellij-plugin
Thanks, I'll try to override bindProperties(). One more question if you don't mind: DomCollectionControl and DomTableView work fine but if I change xml file explicitly then the changes don't reflect in the table. I solve the problem by adding a dom event listener but the problem is with Disposable parentDisposable.
Whatever I pass, If I change the xml then changes reflect in UI but also the method throws an exception. Here is a stack trace and a code snippet of my last attempt where I try to pass "this" as parentDisposable (this at current context relates to DomEditorWindow class).
com.intellij.psi.PsiInvalidElementAccessException: Element: class com.intellij.psi.impl.source.xml.XmlTagImpl #XML because: containing file is null
invalidated at: see attachment
at com.intellij.psi.impl.source.tree.CompositePsiElement.getContainingFile(CompositePsiElement.java:107)
at com.intellij.util.xml.impl.DomImplUtil.getContainingFile(DomImplUtil.java:303)
at com.intellij.util.xml.impl.DomImplUtil.getFile(DomImplUtil.java:289)
at com.intellij.util.xml.impl.PhysicalDomParentStrategy.getContainingFile(PhysicalDomParentStrategy.java:84)
at com.intellij.util.xml.impl.DomInvocationHandler.getFile(DomInvocationHandler.java:547)
at com.intellij.util.xml.impl.CollectionChildDescriptionImpl.getCollectionSubTags(CollectionChildDescriptionImpl.java:53)
at com.intellij.util.xml.impl.DomInvocationHandler.getCollectionSubTags(DomInvocationHandler.java:882)
at com.intellij.util.xml.impl.DomInvocationHandler.getCollectionChildren(DomInvocationHandler.java:858)
at com.intellij.util.xml.impl.CollectionChildDescriptionImpl.getValues(CollectionChildDescriptionImpl.java:100)
at com.intellij.util.xml.ui.DomCollectionControl.getCollectionElements(DomCollectionControl.java:283)
at com.intellij.util.xml.ui.DomCollectionControl.reset(DomCollectionControl.java:277)
at com.ivanryazantsev.liferay.servicebuildereditor.toolwindow.DomEditorWindow.lambda$new$0(DomEditorWindow.java:102)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.util.EventDispatcher.dispatchVoidMethod(EventDispatcher.java:132)
at com.intellij.util.EventDispatcher.access$000(EventDispatcher.java:26)
at com.intellij.util.EventDispatcher$1.invoke(EventDispatcher.java:90)
at com.sun.proxy.$Proxy125.eventOccured(Unknown Source)
at com.intellij.util.xml.impl.DomManagerImpl.fireEvent(DomManagerImpl.java:213)
at com.intellij.util.xml.impl.DomManagerImpl$1.modelChanged(DomManagerImpl.java:95)
at com.intellij.pom.core.impl.PomModelImpl.lambda$null$1(PomModelImpl.java:185)
at com.intellij.psi.impl.DebugUtil.performPsiModification(DebugUtil.java:553)
at com.intellij.pom.core.impl.PomModelImpl.lambda$runTransaction$2(PomModelImpl.java:144)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeNonCancelableSection(CoreProgressManager.java:187)
at com.intellij.pom.core.impl.PomModelImpl.runTransaction(PomModelImpl.java:135)
at com.intellij.psi.impl.DiffLog.lambda$doActualPsiChange$0(DiffLog.java:267)
at com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl.lambda$performActionWithFormatterDisabled$6(CodeStyleManagerImpl.java:692)
at com.intellij.psi.impl.source.PostprocessReformattingAspect.disablePostprocessFormattingInside(PostprocessReformattingAspect.java:104)
at com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl.lambda$performActionWithFormatterDisabled$8(CodeStyleManagerImpl.java:721)
at com.intellij.formatting.FormatterImpl.runWithFormattingDisabled(FormatterImpl.java:691)
at com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl.performActionWithFormatterDisabled(CodeStyleManagerImpl.java:719)
at com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl.performActionWithFormatterDisabled(CodeStyleManagerImpl.java:691)
at com.intellij.psi.impl.DiffLog.doActualPsiChange(DiffLog.java:255)
at com.intellij.psi.impl.DocumentCommitThread.lambda$doCommit$8(DocumentCommitThread.java:701)
at com.intellij.psi.impl.PsiDocumentManagerBase.commitToExistingPsi(PsiDocumentManagerBase.java:397)
at com.intellij.psi.impl.PsiDocumentManagerBase.lambda$finishCommitInWriteAction$1(PsiDocumentManagerBase.java:369)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:586)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:532)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:86)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeNonCancelableSection(CoreProgressManager.java:192)
at com.intellij.psi.impl.PsiDocumentManagerBase.finishCommitInWriteAction(PsiDocumentManagerBase.java:364)
at com.intellij.psi.impl.PsiDocumentManagerImpl.finishCommitInWriteAction(PsiDocumentManagerImpl.java:145)
at com.intellij.psi.impl.PsiDocumentManagerBase$1.run(PsiDocumentManagerBase.java:325)
at com.intellij.openapi.application.impl.ApplicationImpl.runWriteAction(ApplicationImpl.java:1054)
at com.intellij.psi.impl.PsiDocumentManagerBase.finishCommit(PsiDocumentManagerBase.java:332)
at com.intellij.psi.impl.DocumentCommitThread.lambda$createFinishCommitInEDTRunnable$5(DocumentCommitThread.java:488)
at com.intellij.openapi.application.TransactionGuardImpl.runSyncTransaction(TransactionGuardImpl.java:88)
at com.intellij.openapi.application.TransactionGuardImpl.lambda$submitTransaction$1(TransactionGuardImpl.java:111)
at com.intellij.openapi.application.impl.LaterInvocator$FlushQueue.doRun(LaterInvocator.java:435)
at com.intellij.openapi.application.impl.LaterInvocator$FlushQueue.runNextEvent(LaterInvocator.java:419)
at com.intellij.openapi.application.impl.LaterInvocator$FlushQueue.run(LaterInvocator.java:403)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:764)
at java.awt.EventQueue.access$500(EventQueue.java:98)
at java.awt.EventQueue$3.run(EventQueue.java:715)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:734)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:729)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:678)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:373)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Answering to your PS, you attached a link to an official Liferay plugin but it doesn't provide a GUI editor for service builder XML. There's Liferay IDE which is an official plugin for Eclipse and it provides much more tools, including GUI service builder editor which I'm trying to create as an IDEA plugin.
Oh, I forgot to mention! The exception gets thrown if I try to remove, for example, service-builder tag's attributes explicitly
Ok, I've found proper solution to your problem
add
addComponent(exceptionsControl);
after creation and remove DomEventListener completely.
Reason: your "main" panel doesn't know about "sub-panels" like exception table, so its reset() is not called upon activation of GUI tab.
Thank you for the help! Good luck!
Could you please help me a bit more? Is it possible to define a default value for boolean attribute? I tried to achieve this by implementing custom DomWrapper and it worked but if I explicitly remove attribute from XML then it doesn't appear again when I check the corresponding checkBox.
There is no such builtin feature for handling booleans AFAIK.