Skip to content
Roman Schulte edited this page Aug 19, 2016 · 23 revisions

#JSBML Developer Wiki

##Offline Validation The offline validator is a alternative way to validate a document against the specifications of SBML. As the name will suggest it doesn't need internet connection.

###How it's work One of the most important classes of the offline validator is the ValidationContext. In the best case, this should be the only class the user will ever need. To reduce overhead, this class is also designed to be reusable.

This his how you setup a validation context and perform a simple validation:

// 1. Obtain a new instance
ValidationContext ctx = new ValidationContext();

// 2. Loading constraints to the context
ctx.loadConstraints(MyClass.class);

// 3. Perform validation
MyClass myObject = new MyClass();
boolean isValid = ctx.validate(myObject);

Notice that the ValidationContext is capable to validate EVERY class, as long as at least one motherclass or interface provides constraints. Let's see what these lines of code really do:

  1. This is a simple constructor call, nothing special here. The result is a default ValidationContext with has recursive validation turned on and only loads the constraints for the General SBML Consistency Issues
  2. Here's the real magic behind the validator. The first thing the context does is to obtain the shared instance of the ConstraintFactory. The factory now checks every superclass and interface of the given class and tries to find a ConstraintDeclaration. To avoid double checking, the factory remembers already visited classes. Be aware, that multiple calls of loadConstraint(*) or loadConstraintForAttribute(*) will override each other. There can always be only one set of constraints in a context.
  3. This function call triggers the validation. While loading constraints, the context remembers the root class for which the constraints were loaded. Before the validation starts, the context checks if the given object is assignable to the constraint type, if not the validation will return false and print a message to the console. If this test is passed, the HashMap of the context will be cleared and context calls the check(*) method of the root constraint after checking for null. If the root constraint is null, no rules exist and therefore the object must be valid.

Take control of the validation

The steps above perform a really simple validation, which only gives a quick result. If the validate(*) method returns false you couldn't say how many or which constraints are broken. If you want to have more informations about the validation process you could add a ValidationListener to a context. A context can have unlimited amount of these listeners. Each of them has two methods, one of them will be triggered before a constraint will be validated and one afterwards. The second method also gets the result of this constraint (true if everything is fine, false otherwise). This informations in combination with the error codes of the constraints could be used to retrieve more information about a broken constraint.

Notice that a ConstraintGroup will return false if at least one of their child constraints is broken. You can recognize a ConstraintGroup either by checking the class of the constraint or by comparing the error code:

class ValidationLogger implements ValidationListener {

   public void willValidate(ValidationContext ctx, AnyConstraint<?> c, Object o) {
      
      // using the instanceof operator to filter out groups
      if (c instanceof ConstraintGroup){
         system.out.println("enter group");
      }
   }

   public void didValidate(ValidationContext ctx, AnyConstraint<?> c, Object o, boolean success) {
      // all ConstraintGroups share the same error code
      if (c.getErrorCode == CoreSpecialErrorCodes.ID_GROUP) {
         system.out.println("leave group");
      }
      else 
      {
         // log a broken constraint
         if (!success)
         {
            system.out.println("constraint " + c.getErrorCode() + " was broken!");
         }
      }
   }
}

JSBML already provides a context which also creates SBMLErrors for broken constraints and collects them in a SBMLErrorLog. This context is called LoggingValidationContext which is a subclass of ValidationContext that also implements the ValidationListener interface and listens to itself.

By default the context only enables the check category GENERAL_CONSISTENCY which contains the most constraints and provide a solid base. To load additional constraints you can add more check categories to the context. After enabling/disabling new categories the constraints must be reloaded to take effect.

How constraints are loaded

When the ConstraintFactory is looking for ConstraintDeclarations it's actually using java reflection to find these classes. A ConstraintDeclaration must be follow these rules to be recognized:

  • Has package 'org.sbml.jsbml.validator.offline.constraints'
  • Implements the ConstraintDeclaration interface
  • Follows naming convention: className + "Constraints" (e.g. constraints for Species must be declared in SpeciesConstraints)

Because of these restriction there always can be only ONE ConstraintDeclaration per class. The easiest way to obtain a ConstraintDeclaration is to call AbstractConstraintDeclaration.getInstance(className) where className is the simple name of a class (like "Species", "Compartment", etc.). The AbstractConstraintDeclaration caches already found declarations and the names of the classes for which no declaration was found.

  • Looking for ConstraintDeclaration

    • Must be in package
    • Must be named like class and ends with 'Constraints'
    • Must be assignable to ConstraintDeclaration
  • AbstractConstraintDeclaration:

    • provides most functions
      • You only have to take care about which constraints should be loaded
      • and how each of the constraints will work
      • Creates automatically ConstraintGroups and ignores non existing constraints
      • Groups have always at least one member
    • uses Reflection to find constraint declaration
      • Caches found classes and remembers not existing classes
    • Caches constraints
      • Key = className + ErrorCode
      • Two constraints with same ErrorCode but different targets are possible
  • TreeNodeConstraints

    • Has special constraint which points the context to every child
    • The constraint does nothing if recursive validation is disabled

###Add Constraints The AbstractConstraintDeclaration is also a good point to start when you want to create your own constraints. It already provides a implementation of the most functions. It's only necessary to implement three functions on your own. The following example will demonstrate how you could create your constraints for MyClass:

´´´java // Be sure to use this package, otherwise the ConstraintFactory won’t find your constraints. package org.sbml.jsbml.validator.offline.constraints;

// This class will contain the constraints for a MyClass object public class MyClassConstraints extends AbstractConstraintDeclaration {

// 1. Add your error codes to the set. Use the level, version and category parameter // to select which error codes are needed. @Override public void addErrorCodesForCheck(Set set, int level, int version, CHECK_CATEGORY category) { switch (category) { case GENERAL_CONSISTENCY: if (level > 1) { // All official SBML error codes a hard coded in the SBMLErrorCodes interface set.add(CORE_10200); }

      // a small helper function
      addRange(set, CORE_10203, CORE_10208);
  break;
  case IDENTIFIER_CONSISTENCY:
  break;
  case MATHML_CONSISTENCY:
  break;
  case MODELING_PRACTICE:
  break;
  case OVERDETERMINED_MODEL:
  break;
  case SBO_CONSISTENCY:
  break;
  case UNITS_CONSISTENCY:
  break;
}

}

// 2. Nearly the same as before, but this time you're looking just for a single attribute. @Override public void addErrorCodesForAttribute(Set set, int level, int version, String attributeName) { switch (attributeName){ case TreeNodeChangeListener.size: set.add(CORE_10200); case "name": set.add(CORE_102004); } }

// 3. Here you provide the actual logic behind the constraints @Override public ValidationFunction<?> getValidationFunction(int errorCode) { ValidationFunction func = null;

 switch (errorCode) {
 case CORE_10200:
    func = new ValidationFunction<MyClass>() {
    
       public boolean check(ValidationContext ctx, MyClass myObject) {
       
           // Always use the level and version of the context and never the values from the object.
           // This will make compatibility checks very easy
           if (ctx.getLevel() > 1)
           {
               return myObject.isSetName();
           }

           return myObject.isSetName() && myObject.isNameUppercase();
       }
 };
 break;
 
 // other cases...
}

return func;

} ´´´

  • Create ConstraintDeclaration

    • Remember naming convention and package
    • Should extending AbstractConstraintDeclaration
  • Collect ErrorCodes

  • Provide ValidationFunctions

  • Use level/version of context

  • Remember that's cached

###Adding Error Objects

  • Stored in JSON
  • One big Dictionary/HashMap. ErrorCode as String is key for Entry
  • Entry is again Dic/HashMap. Has following attributes:
    • "NAME" =
  • Example Structure
Clone this wiki locally