/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.foundation.validation;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.foundation.CodeType;
import org.openflexo.foundation.TargetType;
import org.openflexo.foundation.rm.FlexoProject;
import org.openflexo.foundation.utils.FlexoListModel;
import org.openflexo.localization.FlexoLocalization;
/**
* Used to store and manage a set of ValidationRule
*
* @author sguerin
*
*/
public abstract class ValidationModel extends FlexoListModel {
private static final Logger logger = Logger.getLogger(ValidationModel.class.getPackage().getName());
private Hashtable<Class, Vector<ValidationRule>> _rules;
private Hashtable<Class, ValidationRuleSet> _inheritedRules;
private boolean recomputeInheritedRules = true;
private FlexoProject _project;
private TargetType _targetType;
public ValidationModel(FlexoProject project, TargetType targetType) {
super();
_targetType = targetType;
_project = project;
_rules = new Hashtable<Class, Vector<ValidationRule>>();
_inheritedRules = new Hashtable<Class, ValidationRuleSet>();
}
@Override
public String getDeletedProperty() {
// TODO Auto-generated method stub
return null;
}
/**
* Validate supplied Validable object by returning boolean indicating if validation throw errors (warnings are not considered as invalid
* model).
*
* @param object
* @return
*/
public boolean isValid(Validable object) {
return validate(object).getErrorNb() == 0;
}
/**
* Validate supplied Validable object by building and returning a new ValidationReport object.
*
* @param object
* @return a newly created ValidationReport object
*/
public ValidationReport validate(Validable object) {
ValidationReport returned = new ValidationReport(this, object);
if (validate(object, returned)) {
returned.addToValidationIssues(new InformationIssue(object, "consistency_check_ok"));
}
return returned;
}
/**
* Validate supplied Validable object by appending ValidationIssues object to supplied ValidationReport. Return true if no validation
* issues were found, false otherwise
*
* @param object
* @param report
*/
public final <V extends Validable> boolean validate(V object, ValidationReport report) {
int addedIssues = 0;
// Get all the objects to validate
Vector<? extends Validable> allEmbeddedValidableObjects = object.getAllEmbeddedValidableObjects();
// logger.info("For object " + object + " objects to validate are: " + allEmbeddedValidableObjects);
// Remove duplicated objects
Vector<Validable> objectsToValidate = new Vector<Validable>();
for (Enumeration<? extends Validable> en = allEmbeddedValidableObjects.elements(); en.hasMoreElements();) {
Validable next = en.nextElement();
if (!objectsToValidate.contains(next)) {
objectsToValidate.add(next);
}
}
// Compute validation steps and notify validation initialization
int validationStepToNotify = 0;
for (Enumeration<Validable> en = objectsToValidate.elements(); en.hasMoreElements();) {
Validable next = en.nextElement();
if (shouldNotifyValidation(next)) {
validationStepToNotify++;
}
}
setChanged();
notifyObservers(new ValidationInitNotification(object, validationStepToNotify));
// Perform the validation
for (Enumeration<Validable> en = objectsToValidate.elements(); en.hasMoreElements();) {
Validable next = en.nextElement();
if (shouldNotifyValidation(next)) {
setChanged();
notifyObservers(new ValidationProgressNotification(object, next));
}
if (!next.isDeleted()) {
addedIssues += performValidation(next, report);
}
/*ValidationRuleSet rules = getValidationRulesForObjectType(next.getClass());
if (logger.isLoggable(Level.FINE))
logger.fine("Validating " + next.getFullyQualifiedName()+" "+next.toString());
if (shouldNotifyValidationRules()) {
setChanged();
notifyObservers(new ValidationSecondaryInitNotification(next, rules.getSize()));
}
for (Enumeration en2 = rules.elements(); en2.hasMoreElements();) {
ValidationRule rule = (ValidationRule) en2.nextElement();
if (logger.isLoggable(Level.FINE))
logger.fine("Applying rule " + rule.getLocalizedName());
if (shouldNotifyValidationRules()) {
setChanged();
notifyObservers(new ValidationSecondaryProgressNotification(next, rule));
}
if (performRuleValidation(rule, next, report)) addedIssues++;
}*/
}
// Notify validation is finished
setChanged();
notifyObservers(new ValidationFinishedNotification(object));
return addedIssues == 0;
}
private <V extends Validable> int performValidation(V next, ValidationReport report) {
int addedIssues = 0;
ValidationRuleSet rules = getValidationRulesForObjectType(next.getClass());
if (logger.isLoggable(Level.FINE)) {
logger.fine("Validating " + next.getFullyQualifiedName() + " " + next.toString());
}
if (shouldNotifyValidationRules()) {
setChanged();
notifyObservers(new ValidationSecondaryInitNotification(next, rules.getSize()));
}
for (Enumeration<ValidationRule> en = rules.elements(); en.hasMoreElements();) {
ValidationRule rule = en.nextElement();
if (logger.isLoggable(Level.FINE)) {
logger.fine("Applying rule " + rule.getLocalizedName());
}
if (shouldNotifyValidationRules()) {
setChanged();
notifyObservers(new ValidationSecondaryProgressNotification(next, rule));
}
if (performRuleValidation(rule, next, report)) {
addedIssues++;
}
}
return addedIssues;
}
private <R extends ValidationRule<R, V>, V extends Validable> boolean performRuleValidation(R rule, V next, ValidationReport report) {
ValidationIssue<R, V> issue = null;
try {
issue = rule.getIsEnabled() ? rule.applyValidation(next) : null;
} catch (Exception e) {
logger.warning("Exception occured during validation: " + e.getMessage() + " object was " + next + " deleted="
+ next.isDeleted());
e.printStackTrace();
}
if (issue != null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Adding issue " + issue);
}
issue.setCause(rule);
report.addToValidationIssues(issue);
if (fixAutomaticallyIfOneFixProposal()) {
if (issue instanceof ProblemIssue && ((ProblemIssue<R, V>) issue).getSize() == 1) {
if (logger.isLoggable(Level.INFO)) {
logger.info("Fixing automatically...");
}
((ProblemIssue<R, V>) issue).getElementAt(0).apply(false);
report.addToValidationIssues(new InformationIssue<R, V>(next, FlexoLocalization.localizedForKey("fixed_automatically:")
+ " " + issue.getLocalizedMessage() + " : "
+ ((ProblemIssue<R, V>) issue).getElementAt(0).getLocalizedMessage(), false));
} else if (issue instanceof CompoundIssue) {
for (ValidationIssue<R, V> containedIssue : ((CompoundIssue<R, V>) issue).getContainedIssues()) {
if (containedIssue instanceof ProblemIssue && containedIssue.getSize() == 1) {
report.addToValidationIssues(containedIssue);
if (logger.isLoggable(Level.INFO)) {
logger.info("Fixing automatically...");
}
((ProblemIssue<R, V>) containedIssue).getElementAt(0).apply(false);
report.addToValidationIssues(new InformationIssue<R, V>(containedIssue.getObject(), FlexoLocalization
.localizedForKey("fixed_automatically:")
+ " "
+ containedIssue.getLocalizedMessage()
+ " : "
+ ((ProblemIssue<R, V>) containedIssue).getElementAt(0).getLocalizedMessage(), false));
}
}
}
}
return true;
}
return false;
}
/**
* Return a boolean indicating if validation of supplied object must be notified
*
* @param next
* @return a boolean
*/
protected abstract boolean shouldNotifyValidation(Validable next);
/**
* Return a boolean indicating if validation of each rule must be notified
*
* @param next
* @return a boolean
*/
protected boolean shouldNotifyValidationRules() {
return false;
}
public abstract boolean fixAutomaticallyIfOneFixProposal();
/**
* Internally used to build ValidationModel
*
* @param validationRule
* @param objectType
*/
protected void registerRule(ValidationRule validationRule) {
Class objectType = validationRule.getObjectType();
if (_rules.get(objectType) != null) {
Vector<ValidationRule> existingRules = _rules.get(objectType);
existingRules.add(validationRule);
} else {
Vector<ValidationRule> newVector = new Vector<ValidationRule>();
newVector.add(validationRule);
_rules.put(objectType, newVector);
}
recomputeInheritedRules = true;
}
protected void unregisterRule(ValidationRule validationRule) {
Class objectType = validationRule.getObjectType();
if (_rules.get(objectType) != null) {
Vector<ValidationRule> existingRules = _rules.get(objectType);
existingRules.remove(validationRule);
}
recomputeInheritedRules = true;
}
/**
* Return all known ValidationRule object known for supplied object type (supports basic class inheritance scheme)
*
* @param objectType
* @return
*/
protected ValidationRuleSet getValidationRulesForObjectType(Class objectType) {
if (recomputeInheritedRules) {
recomputeInheritedRules(_filter);
}
if (_inheritedRules.get(objectType) != null) {
return _inheritedRules.get(objectType);
} else {
Class current = objectType;
while (current.getSuperclass() != null) {
if (_inheritedRules.get(current.getSuperclass()) != null) {
ValidationRuleSet newRuleSet = new ValidationRuleSet(objectType, _inheritedRules.get(current.getSuperclass())
.getRules());
_inheritedRules.put(objectType, newRuleSet);
return newRuleSet;
}
current = current.getSuperclass();
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("Could not find validation rules for type " + objectType.getName());
}
return new ValidationRuleSet(objectType);
}
}
/**
* Internally used to maintain association between classes and a related set of ValidationRule objects.
*/
private void recomputeInheritedRules(ValidationRuleFilter filter) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("BEGIN recomputeInheritedRules()");
}
for (Enumeration e1 = _rules.keys(); e1.hasMoreElements();) {
Class type1 = (Class) e1.nextElement();
Vector rulesForType = _rules.get(type1);
ValidationRuleSet ruleSet = new ValidationRuleSet(type1, rulesForType);
if (_project != null) {
for (Enumeration e2 = rulesForType.elements(); e2.hasMoreElements();) {
ValidationRule rule = (ValidationRule) e2.nextElement();
if (!rule.isValidForTarget(getTargetType()) || filter != null && !filter.accept(rule)) {
ruleSet.removeRule(rule);
}
}
}
_inheritedRules.put(type1, ruleSet);
}
for (Enumeration<Class> e1 = _rules.keys(); e1.hasMoreElements();) {
Class type1 = e1.nextElement();
for (Enumeration<Class> e2 = _rules.keys(); e2.hasMoreElements();) {
Class type2 = e2.nextElement();
if (type1.isAssignableFrom(type2) && type1 != type2) {
// type1 superclass for type2
ValidationRuleSet superRules = _inheritedRules.get(type1);
ValidationRuleSet childRules = _inheritedRules.get(type2);
for (Enumeration e3 = superRules.elements(); e3.hasMoreElements();) {
childRules.addRule((ValidationRule) e3.nextElement());
}
}
}
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("END recomputeInheritedRules()");
}
_keys = buildAllKeys();
Collections.sort(_keys, new ClassComparator());
recomputeInheritedRules = false;
}
private class ClassComparator implements Comparator<Class> {
private Collator collator;
ClassComparator() {
collator = Collator.getInstance();
}
@Override
public int compare(Class o1, Class o2) {
String className1 = null;
String className2 = null;
StringTokenizer st1 = new StringTokenizer(o1.getName(), ".");
while (st1.hasMoreTokens()) {
className1 = st1.nextToken();
}
StringTokenizer st2 = new StringTokenizer(o2.getName(), ".");
while (st2.hasMoreTokens()) {
className2 = st2.nextToken();
}
if (className1 != null && className2 != null) {
return collator.compare(className1, className2);
}
return 0;
}
}
private Vector<Class> _keys;
public Vector<Class> buildAllKeys() {
Vector<Class> returned = new Vector<Class>();
for (Enumeration e1 = _inheritedRules.elements(); e1.hasMoreElements();) {
ValidationRuleSet next = (ValidationRuleSet) e1.nextElement();
Class nextKey = next.getType();
returned.add(nextKey);
}
return returned;
}
public void update() {
recomputeInheritedRules(null);
}
private ValidationRuleFilter _filter = null;
public void update(ValidationRuleFilter filter) {
_filter = filter;
recomputeInheritedRules(filter);
}
/**
* Implements
*
* @see javax.swing.ListModel#getSize()
* @see javax.swing.ListModel#getSize()
*/
@Override
public int getSize() {
return _keys.size();
}
/**
* Implements
*
* @see javax.swing.ListModel#getElementAt(int)
* @see javax.swing.ListModel#getElementAt(int)
*/
@Override
public ValidationRuleSet getElementAt(int index) {
return _inheritedRules.get(_keys.elementAt(index));
}
public TargetType getTargetType() {
return _targetType;
}
public void setTargetType(CodeType targetType) {
_targetType = targetType;
}
}