Wednesday, 2 November 2016

Multiple Validators for XText DSL

Xtext framework is equipped with robust syntax validators, which is in-built and works with the generated tokens. So for with Business/Semantic validators, xtext provides the mechanism to bind the validator through the RuntimeModule.

The default generated [MyDSL]Validator.java is attached to the framework in Abstract[MyDSL]RuntimeModule.java as shown below.

       // contributed by org.eclipse.xtext.generator.validation.ValidatorFragment
       @org.eclipse.xtext.service.SingletonBinding(eager=true)       public Class<? extends com.odcgroup.model.schema.validation.MyDSLValidator> bindMyDSLValidator() {
              return com.odcgroup.model.schema.validation.MyDSLValidator.class;
       }

However one can override this in MyDSLRuntimeModule.java. The MyDSLValidator.java is generated only once and is empty. All the business validation should be added here like as shown below.

    @Check
    def checkClassHierarchy(Entity e) {
        if (e.entityHierarchy.contains(e)) {
            error("cycle in hierarchy of entity '" + e.name + "'",
                SchemaPackage::eINSTANCE.entity_Parent,
                HIERARCHY_CYCLE,
                e.parent.name)
        }
    }

At times, there might be a need to have multiple validators attached to framework for various reasons like separation of concern, too many methods, etc. As shown above, we can attach only one validator in the RuntimeModule.java. The alternative way to attach multiple validators is through @ComposedChecks annotation. Add the annotation @ComposedChecks on the MyDSLValidator and specify the validators as shown below.

@ComposedChecks(validators = { MyDSLSchemaValidator1.class, MyDSLSchemaValidator2.class})
public class MyDSLSchemaValidator extends AbstractMyDSLValidator {
...
}

Note: The validators should extend  org.eclipse.xtext.validation.AbstractDeclarativeValidator, but i prefer the AbstractMyDSLValidator, as the validator will be aware of what EPackage it will deal with.

Tuesday, 27 September 2016

Custom Property Sorter in EMF Editor

The default property sheet provided by EMF sorts the attributes in Alphabetical Order, no matter what is the actual order of definition in the ecore. However EMF uses the generator GAP pattern, so one can change the generated code and mark such API as @generated NOT. So no mess up during regeneration

1. Implement a Custom Sorter
package com.odcgroup.edge.t24ui.common.presentation;

import org.eclipse.ui.views.properties.IPropertySheetEntry;
import org.eclipse.ui.views.properties.PropertySheetSorter;

public class CustomPropertySheetSorter extends PropertySheetSorter {

    @Override
    public void sort(IPropertySheetEntry[] entries) {
        // do nothing;
        // or may be your custom logic
    }
}

2. Implement a CustomPropertSheetPage
which sets the newly implemented sorter. In simple just replace the getPropertySheetPage() method in the generated Editor (ex : YourEditor.java) by the snippet below

    /**
     * This accesses a cached version of the property sheet.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated NOT
     */
    public IPropertySheetPage getPropertySheetPage() {
       
        class MyCustomPropertySheetPage extends ExtendedPropertySheetPage {

            /**
             * @param editingDomain
             */
            public MyCustomPropertySheetPage(AdapterFactoryEditingDomain editingDomain) {
                super(editingDomain);
                setSorter(new CustomPropertySheetSorter());
            }
           
            @Override
            public void setSelectionToViewer(List<?> selection) {
                YourEditor.this.setSelectionToViewer(selection);
                YourEditor.this.setFocus();
            }

            @Override
            public void setActionBars(IActionBars actionBars) {
                super.setActionBars(actionBars);
                getActionBarContributor().shareGlobalActions(this, actionBars);
            }
        }
        PropertySheetPage propertySheetPage = new MyCustomPropertySheetPage(editingDomain);
        propertySheetPage.setPropertySourceProvider(new AdapterFactoryContentProvider(adapterFactory));
        propertySheetPages.add(propertySheetPage);

        return propertySheetPage;
    }

Validating Siblings of different Type with same super Type in XText

Xtext provides various mechanism for validating the models. It as well provides some default implementation for recurring issues. One such implementation is NamesAreUniqueValidator, which validates the model names for uniqueness in the given scope. One can use this default validation by configuring the DSL workflow (*.mwe2) as shown below.

// Xtend-based API for validation
fragment = validation.ValidatorFragment auto-inject {
composedCheck = "org.eclipse.xtext.validation.ImportUriValidator"
composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator"
}

The above validator works great within any given scope. But the validator by default considers the super type as a context during validation. In the below example, Type is a super class of DataType, Entity, Enumeration

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Domainmodel:
    (elements+=Type)*;

Type:
    DataType | Entity | Enumeration;

DataType:
    'datatype' name=ID;

Entity:
    'entity' name=ID ('extends' superType=[Entity])? '{'
    (features+=Feature)*
    '}';

Enumeration:
    'enum' name=ID '{'
    literals+=Literal* '}';

Literal:
    'literal' name=ID;

Feature:
    (many?='many')? name=ID ':' type=[Type];

Which means, we will not be able to create a Type with same name. Precisely, I cannot have a same name for Enumeration and Entity. The editor will be marked with red ink as shown below.


The above behavior is perfectly normal, but this can be changed to check Entity, Enumeration and DataType separately within a scope. NamesAreUniqueValidator uses a helper NamesAreUniqueValidationHelper, which indeed does this classification of types. By default the above helper returns a base class as a type for context. So using XText binding mechanism, one can bind the custom helper

1. Custom Helper Implementation

package org.xtext.example.mydsl.validation;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.xtext.validation.NamesAreUniqueValidationHelper;
import org.xtext.example.mydsl.myDsl.MyDslPackage;

public class CustomNamesAreUniqueValidationHelper extends NamesAreUniqueValidationHelper {

    @Override
    protected EClass getAssociatedClusterType(EClass eClass) {
        if (MyDslPackage.Literals.TYPE.isSuperTypeOf(eClass)) {
            // you can check for entity, enumeration ,separately as well
            return eClass;
        }
        return super.getAssociatedClusterType(eClass);
    }
}

2. Binding in RuntimeModule
Now bind the new Custom helper in your *RuntimeModule.java a shown below

public Class<? extends INamesAreUniqueValidationHelper> bindINamesAreUniqueValidationHelper(){
    return CustomNamesAreUniqueValidationHelper.class;

}

Monday, 26 September 2016

The extension point org.eclipse.team.core.filetypes

Most of my eclipse friends would have already come across the extension point "org.eclipse.team.core.fileTypes". But I wonder how many of them would have got it better. Although there is a little documentation here.

Lately, I see XText and various other OSS project promotes this by generating the extension point in the plugin.xml for the DSL's.

Some of the Open Source Community have developed eclipse plugins for various repository like GIT, SVN, CVS, etc. For instance EGIT is such implementation for GIT.

Handling various types of files which contains different type of data is cumbersome. It would be really nice, if EGIT knows a little detail about what kind of files/data it's dealing with. So that the data is handled appropriately and also can be transmitted little faster. The idea of having "org.eclipse.team.core.fileTypes" extension point is to make sure these providers understand the file types. So as a mutual handshake eclipse has provided this extension point and Repository Client providers knows how to consume them.

So defining extension for the file types we deal with in our project would be really cool.

I guess JDT plugin will have an entry like below, i have not verified but having this makes sense!
 <extension point="org.eclipse.team.core.fileTypes">
  <fileTypes extension="java" type="text"/>
 </extension>

Adding such extensions for the files you deal with will help both Client Providers and Repository Provider to deal with your data very efficiently.

Character Encoding in Xtext

Earlier, I had blogged about Character Encoding in Eclipse. Now I would like to write about how we can configure encoding in Xtext at various levels.

Design time encoding
    Xtext generates the DSL artifacts like model, ui, test plugins and by default they will be encoded with platform defaults. However, Xtext provides the mechanism to provide encoding type to be used for encoding the generated plugins by adding an entry encoding = "UTF-8" in the Generator fragment in *.mwe2 file as shown below
In 2.8.4 version and above
component = Generator {
encoding = "UTF-8"
pathRtProject = runtimeProject
...
}

In Earlier version, the xtextGenerator fragment to be changed
component = xtextGenerator {
encoding = "UTF-8"
pathRtProject = runtimeProject
...
}


Encoding at Language Runtime
    Xtext is highly configurable through its mechanism of providing binding mechanism. One can implement the own encoding provider by implementing the IEncodingProvider interface of org.eclipse.xtext.parser. IEncodingProvider provides single api getEncoding(URI) to provide encoding for the given URI.

package org.eclipse.mydsl.encoding;

import org.eclipse.emf.common.util.URI;
import org.eclipse.xtext.parser.IEncodingProvider;

public class MyDslEncodingProvider implements IEncodingProvider {

    @Override
    public String getEncoding(URI uri) {
        return "UTF-8";
    }

}
Now, we need to bind this newly implemented EncodingProvider in both model and ui generated artifacts through *RuntimeModule.java and *UIModule.java, which is usually present in src folder of dsl and ui plugin respectively
1. Runtime Binding
    @Override
    public void configureRuntimeEncodingProvider(Binder binder) {
        binder.bind(IEncodingProvider.class)
        .annotatedWith(DispatchingProvider.Runtime.class)
        .to(MyDslEncodingProvider.class);
    }

2. UI Binding
    @Override
    public void configureUiEncodingProvider(Binder binder) {
        binder.bind(IEncodingProvider.class)
              .annotatedWith(DispatchingProvider.Ui.class)
              .to(MyDslEncodingProvider.class);
    }

So now xtext uses the MyDslEncodingProvider as the default provider during creation or saving of resource. Even this could be overridden like as shown below

Map<?,?> options = new HashMap();
options.put(XtextResource.OPTION_ENCODING, "UTF-8");
myXtextResource.load(options);

options.put(XtextResource.OPTION_ENCODING, "ISO-8859-1");
myXtextResource.save(options);

Character encoding in Eclipse

In an Eclipse workspace, files, folders, projects can have individual encoding. But typically by default the encoding is derived from the platform default settings. One can check the same by right clicking on the project-->Properties-->select Resource as shown in below from the dialog.
However, one can change the default settings to your desired encoding for the entire project by selecting the other radio button as shown above, now from the Combo box, you can select your desired encoding and then click Apply. Now this settings will be stored inside the hidden file in the project .settings/org.eclipse.core.resources.prefs. The hidden file will have a entry like as shown below

One can set the encoding at the specific folder level as well by selecting the particular folder in the project and follow the same instructions. Now contents will be shown as below

As already said, we can set the encoding to files as well

In most of the scenarios, we usually set encoding at the project level and the same will be guarded throughout the project