Java code fails in Intellij but not in eclipse, with ambiguous method error - eclipse

I have the below Java class (with nested classes/interfaces). When running the main method from within Eclipse (Version: 2019-09 R (4.13.0)) I get the following output:
java.version: 1.8.0_241
PageA.m3() called
This is the command line used by Eclipse:
/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:52180 -javaagent:/Users/*****/eclipse/java-2019-09/Eclipse.app/Contents/Eclipse/configuration/org.eclipse.osgi/222/0/.cp/lib/javaagent-shaded.jar -Dfile.encoding=UTF-8 -classpath <a-list-of-.jar-files> _play.PageTest
When running the same code from within Intellij (IDEA 2019.3.3 (Community Edition)) I get the following output:
src/main/java/_play/PageTest.java:13: error: reference to m1 is ambiguous
.m1(pageAorB -> ((SpecialPage<?>) pageAorB).m3());
^
both method m1(Consumer<T#1>) in Page and method m1(Consumer<T#2>) in AbstractPage match
where T#1,T#2 are type-variables:
T#1 extends Page<T#1> declared in interface Page
T#2 extends Page<T#2> declared in class AbstractPage
Why do I get this error in Intellij but not in Eclipse? Is there a way to solve this so it runs without error in Intellij?
Here is the Java class:
package _play;
import java.util.function.Consumer;
import java.util.function.Function;
public class PageTest {
public static void main(String[] args) {
System.out.println("java.version: " + System.getProperty("java.version"));
new PageC()
.m2(pageC -> (1 == 1 ? new PageA() : new PageB()))
.m1(pageAorB -> ((SpecialPage<?>) pageAorB).m3());
}
public static interface Page<T extends Page<T>> {
T m1(Consumer<T> c);
<R> R m2(Function<? super T, ? extends R> f);
}
public static abstract class AbstractPage<T extends Page<T>> implements Page<T>{
#Override
public T m1(Consumer<T> c){
c.accept(self());
return self();
}
#Override
public final <R> R m2(Function<? super T, ? extends R> f) {
return f.apply(self());
}
abstract protected T self();
}
public static interface SpecialPage<T extends Page<T>> extends Page<T> {
T m3();
}
public static class PageC extends AbstractPage<PageC> {
#Override
protected PageC self() {
return this;
}
}
public static class PageB extends AbstractPage<PageB> implements SpecialPage<PageB> {
#Override
public PageB m3() {
System.out.println("PageB.m3() called");
return this;
}
#Override
public PageB self() {
return this;
}
}
public static class PageA extends AbstractPage<PageA> implements SpecialPage<PageA> {
#Override
public PageA m3() {
System.out.println("PageA.m3() called");
return this;
}
#Override
public PageA self() {
return this;
}
}
}
EDIT
In this case the Page interface and AbstractPage class are in a library that the client can't change. but the client should be able to extend the Page interface.

The question is, is the Eclipse compiler (ecj) of version 4.13 or javac 1.8.0_241 which is used by IntelliJ and Gradle by default right?
The error message of javac says it's ambiguous because in AbstractPage the type variable T refers to the type variable of AbstractPage (named T) and also refers to the different type variable of Page (also named T). But in fact, both type variable refer to the same type, not because they are named the same, but because AbstractPage implements Page<T>. It is not ambiguous and javac erroneously gives the compile error here.
As a workaround of this javac bug you can do one of the following:
Use the Eclipse compiler also in IntelliJ and Gradle
Rewrite the code in the main method using a variable of SpecialPage<? extends Page<?>> for the intermediate result so that javac need not infer it:
SpecialPage<? extends Page<?>> pageAorB = new PageC().m2(pageC -> (1 == 1 ? new PageA() : new PageB()));
pageAorB.m1(specialPage -> ((SpecialPage<?>) specialPage).m3());

Related

Dart & flutter : How can I pass an implementation of an abstract class to the constructor of another class? [duplicate]

Lets say that I have an abstract class
abstract class OnClickHandler {
void doA();
void doB();
}
I have a class
class MyClass {
OnClickHandler onClickHandler;
MyClass({
this.onClickHandler
})
void someFunction() {
onClickHandler.doA();
}
}
And I have a class
class Main implements onClickHandler {
// This throws me an error
MyClass _myClass = MyClass(onClickHandler = this); // <- Invalid reference to 'this' expression
#override
void doA() {}
#override
void doB() {}
}
How can I say that use the same implementations that the Main class has? or is there an easier/better way to do this?
Your problem is that this does not yet exists since the object are still being created. The construction of Dart objects is done in two phases which can be difficult to understand.
If you change you program to the following it will work:
abstract class OnClickHandler {
void doA();
void doB();
}
class MyClass {
OnClickHandler onClickHandler;
MyClass({this.onClickHandler});
void someFunction() {
onClickHandler.doA();
}
}
class Main implements OnClickHandler {
MyClass _myClass;
Main() {
_myClass = MyClass(onClickHandler: this);
}
#override
void doA() {}
#override
void doB() {}
}
The reason is that code running inside { } in the constructor are executed after the object itself has been created but before the object has been returned from the constructor.

Configure Xtext generator at runtime

I have implemented with Xtext a DSL, and I'm trying to find a way to configure dynamically the generation of code in mydsl.ui Eclipse plugin.
I introduced a preference parameter in order to configure generator.
I injected a custom GeneratorConfiguration object with MyDslRuntimeModule
Then I set the preference parameter in this object in "build" method of a custom BuilderParticipant (configured in plugin.xml).
// In mydsl plugin
class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
def Class<? extends IGeneratorConfiguration> bindIGeneratorConfiguration() {
return GeneratorConfiguration;
}
}
// In mydsl.ui plugin
package mydsl.ui;
public class MyBuildPartecipant extends BuilderParticipant {
#Inject IGeneratorConfiguration generatorConfiguration;
#Override
public void build(IBuildContext context, IProgressMonitor monitor) throws CoreException {
ScopedPreferenceStore scopedPreferenceStore = new ScopedPreferenceStore(InstanceScope.INSTANCE, "ID");
generatorConfiguration.setGeneratorProperty(scopedPreferenceStore.getInt("myDslProperty"));
super.build(context, monitor);
}
// In mydsl plugin
class MyDslGenerator extends AbstractGenerator {
#Inject IGeneratorConfiguration generatorConfiguration;
override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
println("Compiling with " + generatorConfiguration.generatorProperty)
The result is that the GeneratorConfiguration object obtained via #Inject decorator in class MyBuildPartecipant of mydsl.ui plugin (eclipse ui) is different from that obtained in class MyDslGenerator of mydsl plugin (Xtext generator plugin).
How can I pass a parameter from eclipse ui plugin to Xtext generator plugin (non ui plugin) in order to configure dynamically the code generation?
Thanks
Paolo
I solved with:
// In mydsl plugin
class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
def IGeneratorConfiguration bindIGeneratorConfiguration() {
return new GeneratorConfiguration();
}
you should mark the GeneratorConfiguration class with #Singleton.
or add a singleton binding using a configure method
def void configureIGeneratorConfiguration(Binder binder) {
binder.bind(IGeneratorConfiguration).to(GeneratorConfiguration).in(Scopes.SINGLETON)
}
or annotate the class binding with #SingletonBinding
#SingletonBinding
def Class<? extends IGeneratorConfiguration> bindIGeneratorConfiguration() {
GeneratorConfiguration
}
the way you do it wont work if you inject stuff into the GeneratorConfiguration class

kapt error while processing dagger2 code

In ClientModule.kt
#Singleton
#Provides
fun provideClient(application: Application, configuration: OkhttpConfiguration?,
sslConfiguration: SSLConfiguration?, cookieJarConfiguration: CookieJarConfiguration?, builder: OkHttpClient.Builder, intercept: Interceptor,
interceptors: List<Interceptor>?, handler: GlobalHttpHandler?): OkHttpClient{...}
In AppModule.kt
#Singleton
#Provides
fun provideInterceptors(): List<Interceptor>? {
return mInterceptors
}
The error msg is:
e: /Users/xiaoqidong/AndroidStudioProjects/DemoKotlin/framework/build/tmp/kapt3/stubs/debug/com/bnpparibas/framework/di/component/AppComponent.java:23: error: java.util.List<? extends okhttp3.Interceptor> cannot be provided without an #Provides-annotated method.
public abstract okhttp3.OkHttpClient okHttpClient();
^
java.util.List<? extends okhttp3.Interceptor> is injected at
com.bnpparibas.framework.di.module.ClientModule.provideClient(…, interceptors, …)
okhttp3.OkHttpClient is provided at
com.bnpparibas.framework.di.component.AppComponent.okHttpClient()
It's so weird that the generated java file changed my function signature from List< Interceptor> to List
The following is the generated file:
#Override
public OkHttpClient get() {
return Preconditions.checkNotNull(
module.provideClient(
applicationProvider.get(),
configurationProvider.get(),
sslConfigurationProvider.get(),
cookieJarConfigurationProvider.get(),
builderProvider.get(),
interceptProvider.get(),
interceptorsProvider.get(),
handlerProvider.get()),
"Cannot return null from a non-#Nullable #Provides method");
}
public static Factory<OkHttpClient> create(
ClientModule module,
Provider<Application> applicationProvider,
Provider<ClientModule.OkhttpConfiguration> configurationProvider,
Provider<ClientModule.SSLConfiguration> sslConfigurationProvider,
Provider<ClientModule.CookieJarConfiguration> cookieJarConfigurationProvider,
Provider<OkHttpClient.Builder> builderProvider,
Provider<Interceptor> interceptProvider,
Provider<List<? extends Interceptor>> interceptorsProvider,
Provider<GlobalHttpHandler> handlerProvider) {
return new ClientModule_ProvideClientFactory(
module,
applicationProvider,
configurationProvider,
sslConfigurationProvider,
cookieJarConfigurationProvider,
builderProvider,
interceptProvider,
interceptorsProvider,
handlerProvider);
}
}
How can stop the kapt change my function signature ?

Eclipse CDT extend AdapterFactory

I try to override the functionality of CDT ResumeAtLine, MoveToLine, RunToLine. For this reason I created a custom SuspendResumeAdapterFactory but it isn't loaded but compiles and runs without error. Do I maybe need a custom adaptableType too?
Here is the content of my plugin.xml.
<extension point="org.eclipse.core.runtime.adapters">
<factory
class="my.package.CustomSuspendResumeAdapterFactory"
adaptableType="org.eclipse.cdt.dsf.ui.viewmodel.IVMContext">
<adapter type="org.eclipse.debug.core.model.ISuspendResume"/>
</factory>
</extension>
And here my CustomSuspendResumeAdapterFactory this class is reconstructed from memory not 100% sure if the syntax is correct, but I think it should be clear to see what I want to do.
package my.package;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.debug.internal.ui.actions.MoveToLine;
import org.eclipse.cdt.dsf.debug.internal.ui.actions.ResumeAtLine;
import org.eclipse.cdt.dsf.debug.internal.ui.actions.RunToLine;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.ISuspendResume;
public class CustomSuspendResumeAdapterFactory implements IAdapterFactory {
static class SuspendResume implements ISuspendResume, IAdaptable {
private final CustomRunToLine fRunToLine;
private final CustomMoveToLine fMoveToLine;
private final CustomResumeAtLine fResumeAtLine;
SuspendResume(IExecutionDMContext execCtx) {
fRunToLine = new CustomRunToLine(execCtx);
fMoveToLine = new CustomMoveToLine(execCtx);
fResumeAtLine = new CustomResumeAtLine(execCtx);
}
#SuppressWarnings("unchecked")
#Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter.isInstance(RunToLine.class)) {
System.out.println("CUSTOM RUNTOLINE");
return (T)fRunToLine;
}
if (adapter.isInstance(MoveToLine.class)) {
System.out.println("CUSTOM MOVETOLINE");
return (T)fMoveToLine;
}
if (adapter.isInstance(ResumeToLine.class)) {
System.out.println("CUSTOM RESUMEATLINE");
return (T)fResumeAtLine;
}
return null;
}
#Override
public boolean canResume() { return false; }
#Override
public boolean canSuspend() { return false; }
// This must return true because the platform
// RunToLineActionDelegate will only enable the
// action if we are suspended
#Override
public boolean isSuspended() { return true; }
#Override
public void resume() throws DebugException {}
#Override
public void suspend() throws DebugException {}
}
#SuppressWarnings("unchecked")
#Override
public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) {
if (ISuspendResume.class.equals(adapterType)) {
if (adaptableObject instanceof IDMVMContext) {
IExecutionDMContext execDmc = DMContexts.getAncestorOfType(
((IDMVMContext)adaptableObject).getDMContext(),
IExecutionDMContext.class);
// It only makes sense to RunToLine, MoveToLine or
// ResumeAtLine if we are dealing with a thread, not a container
if (execDmc != null && !(execDmc instanceof IContainerDMContext)) {
return (T)new SuspendResume(execDmc);
}
}
}
return null;
}
#Override
public Class<?>[] getAdapterList() {
return new Class[] { ISuspendResume.class };
}
}
Why your code is not run
You have provided a new adapter factory that converts object types that are already handled. i.e. your plugin.xml says you can convert IVMContext to ISuspendResume. But the DSF plug-in already provides such an adapter factory. If you have a new target type (like IMySpecialRunToLine) you could install a factory for that, it would take IVMContext and convert it to a IMySpecialRunToLine).
Although dated, the Eclipse Corner Article on Adapter Pattern may be useful if this is a new concept.
How to do custom Run To Line implementation
If you want to provide different implementation of Run To Line, you need to provide your own version of org.eclipse.cdt.dsf.debug.service.IRunControl2.runToLine(IExecutionDMContext, String, int, boolean, RequestMonitor). The org.eclipse.cdt.dsf.debug.internal.ui.actions.RunToLine class is simply glue to connect UI features (such as buttons/etc some provided directly, some by the core eclipse debug) to the DSF backend. i.e. if you look at what RunToLine does, all it actually does is get the IRunControl2 service and call runToLine on it.
The way to provider your own implementation of IRunControl2 is override org.eclipse.cdt.dsf.gdb.service.GdbDebugServicesFactory.createRunControlService(DsfSession) and provide your own GdbDebugServicesFactory in your custom launch delegate by overriding org.eclipse.cdt.dsf.gdb.launching.GdbLaunchDelegate.newServiceFactory(ILaunchConfiguration, String)
RunToLine will be triggered when the user select Run To Line from the popup menu in the editor, as per this screenshot:

Using GWT Editors with a complex usecase

I'm trying to create a page which is very similar to the Google Form creation page.
This is how I am attempting to model it using the GWT MVP framework (Places and Activities), and Editors.
CreateFormActivity (Activity and presenter)
CreateFormView (interface for view, with nested Presenter interface)
CreateFormViewImpl (implements CreateFormView and Editor< FormProxy >
CreateFormViewImpl has the following sub-editors:
TextBox title
TextBox description
QuestionListEditor questionList
QuestionListEditor implements IsEditor< ListEditor< QuestionProxy, QuestionEditor>>
QuestionEditor implements Editor < QuestionProxy>
QuestionEditor has the following sub-editors:
TextBox questionTitle
TextBox helpText
ValueListBox questionType
An optional subeditor for each question type below.
An editor for each question type:
TextQuestionEditor
ParagraphTextQuestionEditor
MultipleChoiceQuestionEditor
CheckboxesQuestionEditor
ListQuestionEditor
ScaleQuestionEditor
GridQuestionEditor
Specific Questions:
What is the correct way to add / remove questions from the form. (see follow up question)
How should I go about creating the Editor for each question type? I attempted to listen to the questionType value changes, I'm not sure what to do after. (answered by BobV)
Should each question-type-specific editor be wrapper with an optionalFieldEditor? Since only one of can be used at a time. (answered by BobV)
How to best manage creating/removing objects deep in the object hierarchy. Ex) Specifying answers for a question number 3 which is of type multiple choice question. (see follow up question)
Can OptionalFieldEditor editor be used to wrap a ListEditor? (answered by BobV)
Implementation based on Answer
The Question Editor
public class QuestionDataEditor extends Composite implements
CompositeEditor<QuestionDataProxy, QuestionDataProxy, Editor<QuestionDataProxy>>,
LeafValueEditor<QuestionDataProxy>, HasRequestContext<QuestionDataProxy> {
interface Binder extends UiBinder<Widget, QuestionDataEditor> {}
private CompositeEditor.EditorChain<QuestionDataProxy, Editor<QuestionDataProxy>> chain;
private QuestionBaseDataEditor subEditor = null;
private QuestionDataProxy currentValue = null;
#UiField
SimplePanel container;
#UiField(provided = true)
#Path("dataType")
ValueListBox<QuestionType> dataType = new ValueListBox<QuestionType>(new Renderer<QuestionType>() {
#Override
public String render(final QuestionType object) {
return object == null ? "" : object.toString();
}
#Override
public void render(final QuestionType object, final Appendable appendable) throws IOException {
if (object != null) {
appendable.append(object.toString());
}
}
});
private RequestContext ctx;
public QuestionDataEditor() {
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
dataType.setValue(QuestionType.BooleanQuestionType, true);
dataType.setAcceptableValues(Arrays.asList(QuestionType.values()));
/*
* The type drop-down UI element is an implementation detail of the
* CompositeEditor. When a question type is selected, the editor will
* call EditorChain.attach() with an instance of a QuestionData subtype
* and the type-specific sub-Editor.
*/
dataType.addValueChangeHandler(new ValueChangeHandler<QuestionType>() {
#Override
public void onValueChange(final ValueChangeEvent<QuestionType> event) {
QuestionDataProxy value;
switch (event.getValue()) {
case MultiChoiceQuestionData:
value = ctx.create(QuestionMultiChoiceDataProxy.class);
setValue(value);
break;
case BooleanQuestionData:
default:
final QuestionNumberDataProxy value2 = ctx.create(BooleanQuestionDataProxy.class);
value2.setPrompt("this value doesn't show up");
setValue(value2);
break;
}
}
});
}
/*
* The only thing that calls createEditorForTraversal() is the PathCollector
* which is used by RequestFactoryEditorDriver.getPaths().
*
* My recommendation is to always return a trivial instance of your question
* type editor and know that you may have to amend the value returned by
* getPaths()
*/
#Override
public Editor<QuestionDataProxy> createEditorForTraversal() {
return new QuestionNumberDataEditor();
}
#Override
public void flush() {
//XXX this doesn't work, no data is returned
currentValue = chain.getValue(subEditor);
}
/**
* Returns an empty string because there is only ever one sub-editor used.
*/
#Override
public String getPathElement(final Editor<QuestionDataProxy> subEditor) {
return "";
}
#Override
public QuestionDataProxy getValue() {
return currentValue;
}
#Override
public void onPropertyChange(final String... paths) {
}
#Override
public void setDelegate(final EditorDelegate<QuestionDataProxy> delegate) {
}
#Override
public void setEditorChain(final EditorChain<QuestionDataProxy, Editor<QuestionDataProxy>> chain) {
this.chain = chain;
}
#Override
public void setRequestContext(final RequestContext ctx) {
this.ctx = ctx;
}
/*
* The implementation of CompositeEditor.setValue() just creates the
* type-specific sub-Editor and calls EditorChain.attach().
*/
#Override
public void setValue(final QuestionDataProxy value) {
// if (currentValue != null && value == null) {
chain.detach(subEditor);
// }
QuestionType type = null;
if (value instanceof QuestionMultiChoiceDataProxy) {
if (((QuestionMultiChoiceDataProxy) value).getCustomList() == null) {
((QuestionMultiChoiceDataProxy) value).setCustomList(new ArrayList<CustomListItemProxy>());
}
type = QuestionType.CustomList;
subEditor = new QuestionMultipleChoiceDataEditor();
} else {
type = QuestionType.BooleanQuestionType;
subEditor = new BooleanQuestionDataEditor();
}
subEditor.setRequestContext(ctx);
currentValue = value;
container.clear();
if (value != null) {
dataType.setValue(type, false);
container.add(subEditor);
chain.attach(value, subEditor);
}
}
}
Question Base Data Editor
public interface QuestionBaseDataEditor extends HasRequestContext<QuestionDataProxy>, IsWidget {
}
Example Subtype
public class BooleanQuestionDataEditor extends Composite implements QuestionBaseDataEditor {
interface Binder extends UiBinder<Widget, BooleanQuestionDataEditor> {}
#Path("prompt")
#UiField
TextBox prompt = new TextBox();
public QuestionNumberDataEditor() {
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
}
#Override
public void setRequestContext(final RequestContext ctx) {
}
}
The only issue left is that QuestionData subtype specific data isn't being displayed, or flushed. I think it has to do with the Editor setup I'm using.
For example, The value for prompt in the BooleanQuestionDataEditor is neither set nor flushed, and is null in the rpc payload.
My guess is: Since the QuestionDataEditor implements LeafValueEditor, the driver will not visit the subeditor, even though it has been attached.
Big thanks to anyone who can help!!!
Fundamentally, you want a CompositeEditor to handle cases where objects are dynamically added or removed from the Editor hierarchy. The ListEditor and OptionalFieldEditor adaptors implement CompositeEditor.
If the information required for the different types of questions is fundamentally orthogonal, then multiple OptionalFieldEditor could be used with different fields, one for each question type. This will work when you have only a few question types, but won't really scale well in the future.
A different approach, that will scale better would be to use a custom implementation of a CompositeEditor + LeafValueEditor that handles a polymorphic QuestionData type hierarchy. The type drop-down UI element would become an implementation detail of the CompositeEditor. When a question type is selected, the editor will call EditorChain.attach() with an instance of a QuestionData subtype and the type-specific sub-Editor. The newly-created QuestionData instance should be retained to implement LeafValueEditor.getValue(). The implementation of CompositeEditor.setValue() just creates the type-specific sub-Editor and calls EditorChain.attach().
FWIW, OptionalFieldEditor can be used with ListEditor or any other editor type.
We implemented similar approach (see accepted answer) and it works for us like this.
Since driver is initially unaware of simple editor paths that might be used by sub-editors, every sub-editor has own driver:
public interface CreatesEditorDriver<T> {
RequestFactoryEditorDriver<T, ? extends Editor<T>> createDriver();
}
public interface RequestFactoryEditor<T> extends CreatesEditorDriver<T>, Editor<T> {
}
Then we use the following editor adapter that would allow any sub-editor that implements RequestFactoryEditor to be used. This is our workaround to support polimorphism in editors:
public static class DynamicEditor<T>
implements LeafValueEditor<T>, CompositeEditor<T, T, RequestFactoryEditor<T>>, HasRequestContext<T> {
private RequestFactoryEditorDriver<T, ? extends Editor<T>> subdriver;
private RequestFactoryEditor<T> subeditor;
private T value;
private EditorDelegate<T> delegate;
private RequestContext ctx;
public static <T> DynamicEditor<T> of(RequestFactoryEditor<T> subeditor) {
return new DynamicEditor<T>(subeditor);
}
protected DynamicEditor(RequestFactoryEditor<T> subeditor) {
this.subeditor = subeditor;
}
#Override
public void setValue(T value) {
this.value = value;
subdriver = null;
if (null != value) {
RequestFactoryEditorDriver<T, ? extends Editor<T>> newSubdriver = subeditor.createDriver();
if (null != ctx) {
newSubdriver.edit(value, ctx);
} else {
newSubdriver.display(value);
}
subdriver = newSubdriver;
}
}
#Override
public T getValue() {
return value;
}
#Override
public void flush() {
if (null != subdriver) {
subdriver.flush();
}
}
#Override
public void onPropertyChange(String... paths) {
}
#Override
public void setDelegate(EditorDelegate<T> delegate) {
this.delegate = delegate;
}
#Override
public RequestFactoryEditor<T> createEditorForTraversal() {
return subeditor;
}
#Override
public String getPathElement(RequestFactoryEditor<T> subEditor) {
return delegate.getPath();
}
#Override
public void setEditorChain(EditorChain<T, RequestFactoryEditor<T>> chain) {
}
#Override
public void setRequestContext(RequestContext ctx) {
this.ctx = ctx;
}
}
Our example sub-editor:
public static class VirtualProductEditor implements RequestFactoryEditor<ProductProxy> {
interface Driver extends RequestFactoryEditorDriver<ProductProxy, VirtualProductEditor> {}
private static final Driver driver = GWT.create(Driver.class);
public Driver createDriver() {
driver.initialize(this);
return driver;
}
...
}
Our usage example:
#Path("")
DynamicEditor<ProductProxy> productDetailsEditor;
...
public void setProductType(ProductType type){
if (ProductType.VIRTUAL==type){
productDetailsEditor = DynamicEditor.of(new VirtualProductEditor());
} else if (ProductType.PHYSICAL==type){
productDetailsEditor = DynamicEditor.of(new PhysicalProductEditor());
}
}
Would be great to hear your comments.
Regarding your question why subtype specific data isn't displayed or flushed:
My scenario is a little bit different but I made the following observation:
GWT editor databinding does not work as one would expect with abstract editors in the editor hierarchy. The subEditor declared in your QuestionDataEditor is of type QuestionBaseDataEditor and this is fully abstract type (an interface). When looking for fields/sub editors to populate with data/flush GWT takes all the fields declared in this type. Since QuestionBaseDataEditor has no sub editors declared nothing is displayed/flushed. From debugging I found out that is happens due to GWT using a generated EditorDelegate for that abstract type rather than the EditorDelegate for the concrete subtype present at that moment.
In my case all the concrete sub editors had the same types of leaf value editors (I had two different concrete editors one to display and one to edit the same bean type) so I could do something like this to work around this limitation:
interface MyAbstractEditor1 extends Editor<MyBean>
{
LeafValueEditor<String> description();
}
// or as an alternative
abstract class MyAbstractEditor2 implements Editor<MyBean>
{
#UiField protected LeafValueEditor<String> name;
}
class MyConcreteEditor extends MyAbstractEditor2 implements MyAbstractEditor1
{
#UiField TextBox description;
public LeafValueEditor<String> description()
{
return description;
}
// super.name is bound to a TextBox using UiBinder :)
}
Now GWT finds the subeditors in the abstract base class and in both cases I get the corresponding fields name and description populated and flushed.
Unfortunately this approach is not suitable when the concrete subeditors have different values in your bean structure to edit :(
I think this is a bug of the editors framework GWT code generation, that can only be solved by the GWT development team.
Isn't the fundamental problem that the binding happens at compile time so will only bind to QuestionDataProxy so won't have sub-type specific bindings? The CompositeEditor javadoc says "An interface that indicates that a given Editor is composed of an unknown number of sub-Editors all of the same type" so that rules this usage out?
At my current job I'm pushing to avoid polymorphism altogether as the RDBMS doesn't support it either. Sadly we do have some at the moment so I'm experimenting with a dummy wrapper class that exposes all the sub-types with specific getters so the compiler has something to work on. Not pretty though.
Have you seen this post: http://markmail.org/message/u2cff3mfbiboeejr this seems along the right lines.
I'm a bit worried about code bloat though.
Hope that makes some sort of sense!