Eclipse Validator: Listen to inherited Resource

55 Views Asked by At

I have some custom (logic-)validators for Eclipse, but all are facing the same Problem:

The actual implementation of some logic can take place in any of the parent classes.

So, whenever a resource in question changes, I can went up the inheritance tree to see if the required code takes place in any of the parent classes - if not, i'm placing a marker into the validated file itself.

This marker ofc. gets verified, whenever changes to the resource in question are made. But how can i trigger a revalidation if one of the parent classes changes?

Is it possible to place some kind of "validator-callback" into other files, which will trigger an validation of the given file?

i.e. Three Classes: A extends B, B extends C - Now, the validator notes on A, that neither A nor B nor C is extending X - which is required for A due to annotations. (Logic-Contract)

Now the Error-Marker in A should be re-evaluated as soon as B or C are modified. (Currently i'm using a delta-builder which ofc. just will invoke validation whenever A changes...)

In a nutshell: I want to place a "marker", that gets re-validated whenever one out of X resources change.

1

There are 1 best solutions below

0
On

After I had some time to play around, I finally found a 99% solution to this issue.


Good news: It is possible.

Bad news: It is DIY. I was not able to find any useful method to achieve what I want.


The following post should outline the solution regarding my problem. There might be better, more efficent or easier solutions, but unfortunately the documentation around the validation framework of eclipse is very thin - so i'd like to share my solution - take it, improve it - or ignore it :)


First, you should understand the actual problem I tried to solve. Therefore I'm providing short snippets, without going into too much detail of the validator or my code-lineout:

In my project, I'm using Hibernate - and therefore a lot of classes annotated with @Entity. When using Hibernate along with LazyLoadingone needs to (manually) ensure, that PersistentBags are initialized when accessed.

In our application, there is a method called initializeCollection(Collection c) in the class LazyEntity, which handles everything around it.

This leads to a logic contract my validator should validate:

  • Whenever there is a class annotated with @Entity, AND this class is using FetchType.LAZY on any of its collections, there musst be two contraints met:
  • A.) the class - or any of its ancestors - needs to extend LazyEntity.
  • B.) the getter of the collection in question needs to call initializeCollection() before returning the collection.

I'll focus on point B, because this is, where the validation problem kicks in: How to revalidate an actual Entity, when it's ancestor changes?


I modified the actual validation method, to have two IFiles as Attributes:

public void checkLazyLoadingViolations(IFile actualFile, IFile triggeredFile) {
    //validation of actualFile takes place here
}

The delta-builder as well as the incremental builder are invoking this method like this:

class LazyLoadingResourceVisitor implements IResourceVisitor {
    public boolean visit(IResource resource) {
        if (resource instanceof IFile) {
            checkLazyLoadingViolations((IFile) resource, (IFile) resource);
        }
        // return true to continue visiting children.
        return true;
    }
}

Now - in a first step - the validator itself is taking care to validate the actualFile and bubbling up the inheritence tree to validate any parent file as well. If the validator hits the top-most-parent, without finding the required extension, an additional marker is placed.

The placement of the markers is happening with the following method. In case the file, where the marker should be placed differs from the file on which eclipse has invoked the validation, the IMarker.USER_EDITABLE Attribute is used to store a path to the file, on which the validation has been invoked (in order to retrigger validation):

/**
     * Creates a Marker in the give IFile, considering if it should be a direct
     * marker, or a dependency marker, which our Validator can resolve later.
     * 
     * @param actualFile
     *            The file that is currently validated.
     * @param triggeredFile
     *            The file on which eclipse invoked the validation.
     * @param message
     *            The message for the Marker.
     * @param lineNumber
     *            the line number, where the Marker should appear.
     * @param severity
     *            the severity of the marker.
     * @param callbackPath
     *            The full path to the file that should be validated, when the
     *            marker is revalidated.
     */
    public void addMarker(IFile actualFile, IFile triggeredFile,
            String message, int lineNumber, int severity, String callbackPath) {

        try {
            IMarker marker = actualFile.createMarker(MARKER_TYPE);
            marker.setAttribute(IMarker.MESSAGE, message);
            marker.setAttribute(IMarker.SEVERITY, severity);
            marker.setAttribute(IMarker.LINE_NUMBER, lineNumber == -1 ? 1
                    : lineNumber);
            if (callbackPath != null) {
                marker.setAttribute(IMarker.USER_EDITABLE, "callback:"
                        + triggeredFile.getFullPath());
            }
        } catch (CoreException e) {
            // eclipse made a boo boo.
        }
    }

Now, the validation errors are set: Each actual class contains its Errors - and in case the validation fails in the inheritance tree, the parent contains a marker as well:

(AdminUser extends TestUser in this Example)

enter image description here

When the validator gets triggered on the "parent" file due to changes, it graps all markers, and checks, if the marker provides the callback Attribute. If so, the validator invokes the validation of the resource instead of just validating the parent:

IMarker[] markers = null;
            try {
                markers = actualFile.findMarkers(IMarker.PROBLEM, true,
                        IResource.DEPTH_INFINITE);

                for (IMarker m : markers) {

                    // Our marker type?
                    if (m.getType().equals(MARKER_TYPE)) {

                        // dependent resource defined?
                        if (m.getAttribute(IMarker.USER_EDITABLE) != null) {
                            if (m.getAttribute(IMarker.USER_EDITABLE)
                                    .toString().startsWith("callback:")) {
                                String otherFile = m
                                        .getAttribute(IMarker.USER_EDITABLE)
                                        .toString().replace("callback:", "");

                                // call validation for that file as well.
                                // (recursion)
                                String relOther = otherFile.split(Pattern.quote(actualFile.getProject().getFullPath().toString()))[1];
                                IFile fileToValidateAsWell = actualFile
                                        .getProject().getFile(relOther);

                                //remove old markers!
                                deleteMarkers(fileToValidateAsWell);

                                //revalidate - which has no impact but triggering the root-resource again!
                                //this will recreate the just deleted markers if problem is not resolved.
                                checkLazyLoadingViolations(
                                        fileToValidateAsWell, triggeredFile);
                            }
                        }
                    }
                }
            } catch (CoreException e1) {

            }

Finally, a change to the root file in the inheritance tree causes the leaf file to be revalidated:

enter image description here

  • And ofc. when calling the initializeCollection-Method properly, there is no error at all :)

enter image description here


The only trade off so far is: IF the parent-file is valid - and gets modified to invalid - it will not re-trigger the validation of the leaf, because there is no error-marker containing any callback information.

The error then will appear the first time a full-build is performed. - For the moment, I can live with that.

Logic in a nutshell

  • If the validated resource is a leaf:
    • Place markers in the leaf
    • Place marker in the top-most-parent, while adding a callback link as marker attribute.
  • If the validated resource is a root
    • Remove markers
    • call validation on the linked leaf provided by the callback link inside the existing marker, which in trun will be case 1 and recreate all markers if applicable.