Configure Xtext generator at runtime - eclipse

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

Related

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

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());

How to use dependency injection in JUnit for Eclipse E4 plugin projects

When I set up a context in a JUnit test case to enable testing of a test object (E4 plugin project), which uses dependency injection for a service IMyServiceInterface, the result is always the same:
InjectionException: Unable to process "MyTestObject.myServiceInterface" no actual value was found for the argument IMyServiceInterface".
My idea is to set up a Eclipse context in a test case within JUnit and inject the test object together with its stubbed dependencies (i.e. not mocked).
The test object is a class used in a E4 plugin project and have a reference to an injected service interface.
I've tried several ways of setting up a context in a JUnit test case (with both ContextInjectionFactory.make(...) and InjectorFactory.getDefault().make(...)) to enable testing of the test object.
Here is a simplification of my test object (E4 plugin project) with its two dependencies; IMyServiceInterface and IMyPartInterface:
#Creatable
#Singleton
public class MyTestObject {
#Inject IMyServiceInterface myServiceInterface;
public void myMethod(IMyPartInterface myPartInterface) {
this.myServiceInterface.update();
myPartInterface.set();
}
}
Here is a simplification of my test case (JUnit project):
class AllTests {
#Test
void myTestCase() {
InjectorFactory.getDefault().make(MyPart_Stub.class, null);
InjectorFactory.getDefault().make(MyService_Stub.class, null);
MyTestObject myTestObject = InjectorFactory.getDefault().make(MyTestObject.class, null);
}
}
Here are my stubbed dependencies (JUnit project):
public class MyService_Stub implements IMyServiceInterface {
public void update() {
}
}
public class MyPart_Stub implements IMyPartInterface {
public void set() {
}
}
When I run the test case I get: InjectionException: Unable to process "MyTestObject.myServiceInterface" no actual value was found for the argument IMyServiceInterface".
Finally I've understood whats wrong. I haven't been aware of the fact that ContextInjectionFactory.make(...) only creates an object (i.e. it doesn't inject it in the context as well). To inject the created object I also have to use the set method in the context. This is how I got my basic example to work:
class AllTests {
#Test
void myTestCase() {
IEclipseContext context = EclipseContextFactory.create();
IMyPart myPart_Stub = ContextInjectionFactory.make(MyPart_Stub.class, context);
context.set(IMyPart.class, myPart_Stub);
IMyService myService_Stub = ContextInjectionFactory.make(MyService_Stub.class, context);
context.set(IMyService.class, myService_Stub);
MyTestObject myTestObject = ContextInjectionFactory.make(MyTestObject.class, context);
context.set(MyTestObject.class, myTestObject);
}
}

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:

Eclipse RCP e4 Logging

I amtrying to add logging capabilities to my RCP e4 application. I found the following Snippet.
import org.eclipse.e4.core.di.annotations.Creatable;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.log.Logger;
#Creatable
public class LoggerWrapper extends Logger {
#Optional
#Inject
private Logger logger;
#Override
public boolean isErrorEnabled() {
if (logger != null) {
return logger.isErrorEnabled();
}
return false;
}
#Override
public void error(Throwable t, String message) {
if (logger != null && isErrorEnabled()) {
logger.error(t, withPluginInfo(message));
}
}
}
But I am not sure how to configure/initialize the Logger? Any help will be appreciated. Thanks!
If I read E4Application correctly it will always initialize the application context to contain a Logger which is implemented by org.eclipse.e4.ui.internal.workbench.WorkbenchLogger.
You could override this in the PostContextCreate method of your Life Cycle class (if you have one).
You can also inject StatusReporter which provides simple logging facilities in the Eclipse log (based on Status objects).

ClientBundle for multiple "themes"

We have a web application that needs a different theme for each major client. The original developer did this by looking at the URL in javascript and adding a stylesheet to override the default theme.
One problem with this is the site has the default look for a few seconds then suddenly swaps to the correct theme. Another is that it seems to waste a lot of bandwidth/time.
My current idea is to create a "default" ClientBundle with our default look and feel extend that interface and override each entry (as needed) with the client's images using the various annotations like #ImageResouce and pointing to a different location.
Has anybody had experience doing this? One problem I forsee is not being able to use the uibinder style tags as they statically point to a specific resource bundle.
Any ideas?
Overriden bundles
Yes you can.
I've did the override thing with ClientBundles and works fine. One thing you MUST do is inherit the types of the properties too. By example:
BigBundle {
Nestedundle otherBundle();
ImageResource otherImage();
Styles css();
}
And then you must inherit this way:
OtherBigBundle extends BigBundle {
OtherNestedBundle otherBundle(); // if you want to change it
ImageResource otherImage(); // of you want to change it
OtherStyles css(); // of you want to change it
}
and OtherNestedBundle extends NestedBundle
and OtherStyles extends Styles
At least with css's: if the properties are declared NOT USING the child interface they will produce styles for the same CSS classname and all will be mixed. So declare overriden styles with the child interfaces :)
Flexible UIBinders
You can set from outside the bundle to use if you use UiField(provided=true) annotation. In this way you first set the bundle and then call the uibindler. It will use the resource field assuming it's already created.
Deferred binding
You could use GWT.runAsync for loading just the correct bundle.
Some example
The ui.xml
<ui:with field='res' type='your.package.TheBundle'/>
the corresponding class
#UiField(provided=true) TheBundle bundle;
private void createTheThing() {
this.bundle = factory.createBundle();
MyUiBindler binder = GWT.create(MyUiBindler.class);
this.panel = binder.createAndBindUi(this);
...
}
Some bundle interfaces
interface TheBundle extends ClientBundle {
#ImageResource("default.png")
ImageResource image1();
#Source("default.css")
TheCss css();
}
interface Theme1Bundle extends TheBundle {
#ImageResource("one.png")
ImageResource image1(); // type: imageresource is ok
#Source("one.css")
OneCss css(); // type: OneCss => use other compiled css class-names
interface OneCss extends TheCss { // inner-interface, just for fun
// don't need to declare each String method
}
}
If you don't override something it's ok
Options for the bundle factory
1) just altogether
if (...) {
return GWT.create(TheBundle.class);
} else if (...) {
return GWT.create(Theme1Bundle.class);
}
2) runAsync (just load the needed part... but after the initial part is executed)
if (...) {
GWT.runAsync(new RunAsyncCallback() {
public void onSuccess() {
return GWT.create(TheBundle.class);
}
// please program the onFailure method
});
} else if (...) {
GWT.runAsync(new RunAsyncCallback() {
public void onSuccess() {
return GWT.create(Theme1Bundle.class);
}
// please program the onFailure method
});
}
3) use deferred-binding and generators for autogenerating factory in compile-time based on annotated bundles like #ThemeBundle("one")
This example is from the real world. I use a DynamicEntryPointWidgetFactory (DEPWidgetFactory for short) for creating widget based on an identifier string. Each widget is an application screen and each main menu ítem has the widgetName it has to create.
In your case the id will be the theme to create.
Important: if you use runAsync you cannot create the resourcebundle just before creating the UI like in the sample code before. You must ask for the theme and when it's ready (in the callback) pass it to your widget constructor and your widget can assign it to its field.
The factory interface:
public interface DynamicEntryPointWidgetFactory
{
public void buildWidget(String widgetName, AsyncCallback<Widget> callback);
}
The annotation for widgets to generate:
#Target(ElementType.TYPE)
public #interface EntryPointWidget
{
/**
* The name wich will be used to identify this widget.
*/
String value();
}
The module configuration:
It says: the implementation for the Factory will be generated with this class (the other option is to use replace-with, but in our case we don't have predefined options for each locale or browser, but something more dynamic).
<generate-with class="com.dia.nexdia.services.gwt.rebind.entrypoint.DynamicEntryPointFactoryGenerator">
<when-type-assignable class="com.dia.nexdia.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory" />
</generate-with>
The generator:
public class DynamicEntryPointFactoryGenerator extends Generator {
#Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
PrintWriter pw = context.tryCreate(logger,
"x.services.gwt.client.entrypoint",
"DynamicEntryPointWidgetFactoryImpl");
if (pw != null) {
// write package, imports, whatever
pw.append("package x.services.gwt.client.entrypoint;");
pw.append("import x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactory;");
pw.append("import com.google.gwt.core.client.GWT;");
pw.append("import com.google.gwt.core.client.RunAsyncCallback;");
pw.append("import com.google.gwt.user.client.rpc.AsyncCallback;");
pw.append("import com.google.gwt.user.client.ui.Widget;");
// the class
pw.append("public class DynamicEntryPointWidgetFactoryImpl implements DynamicEntryPointWidgetFactory {");
// buildWidget method
pw.append(" public void buildWidget(String widgetName, final AsyncCallback<Widget> callback) {");
// iterates over all the classes to find those with EntryPointWidget annotation
TypeOracle oracle = context.getTypeOracle();
JPackage[] packages = oracle.getPackages();
for (JPackage pack : packages)
{
JClassType[] classes = pack.getTypes();
for (JClassType classtype : classes)
{
EntryPointWidget annotation = classtype.getAnnotation(EntryPointWidget.class);
if (annotation != null)
{
String fullName = classtype.getQualifiedSourceName();
logger.log(TreeLogger.INFO, "Entry-point widget found: " + fullName);
pw.append("if (\"" + annotation.value() + "\".equals(widgetName)) {");
pw.append(" GWT.runAsync(" + fullName + ".class, new RunAsyncCallback() {");
pw.append(" public void onFailure(Throwable t) {");
pw.append(" callback.onFailure(t);");
pw.append(" }");
pw.append(" public void onSuccess() {");
pw.append(" callback.onSuccess(new " + fullName + "());");
pw.append(" }");
pw.append(" });");
pw.append(" return;");
pw.append("}");
}
}
}
pw.append("callback.onFailure(new IllegalArgumentException(\"Widget '\" + widgetName + \"' not recognized.\"));");
pw.append(" }");
pw.append("}");
context.commit(logger, pw);
}
// return the name of the generated class
return "x.services.gwt.client.entrypoint.DynamicEntryPointWidgetFactoryImpl";
}