package org.uva.student.calinwouter.qlqls.qls;
import org.uva.student.calinwouter.qlqls.ql.exceptions.FieldNotFoundException;
import org.uva.student.calinwouter.qlqls.ql.interfaces.ITypeDescriptor;
import org.uva.student.calinwouter.qlqls.ql.types.BooleanValue;
import org.uva.student.calinwouter.qlqls.ql.types.IntegerValue;
import org.uva.student.calinwouter.qlqls.ql.types.StringValue;
import org.uva.student.calinwouter.qlqls.qls.abstractions.AbstractWidget;
import org.uva.student.calinwouter.qlqls.qls.model.FieldType;
import org.uva.student.calinwouter.qlqls.qls.model.QLSTypeCheckResults;
import org.uva.student.calinwouter.qlqls.qls.model.StylingSettings;
import org.uva.student.calinwouter.qlqls.qls.model.WidgetType;
import org.uva.student.calinwouter.qlqls.qls.model.functions.Defaults;
import org.uva.student.calinwouter.qlqls.qls.model.functions.StyleSheet;
import java.util.*;
/**
* This TypeChecker checks if:
*
* - No references are made to questions that are not in the QL program
* - All questions of the QL program are placed by the QLS program.
* - (Default) widget assignments are compatible with question types (e.g. no radio button for integer widgets).
* - There are no field duplications in QLS.
*/
public class QLSTypeChecker {
private final List<String> fields;
private final StyleSheet styleSheet;
private final List<FieldType> fieldTypes;
private final List<Defaults> collectedDefaults;
private List<Defaults> collectDefaults() {
return styleSheet.collectAllDefaultsInstances();
}
private List<String> collectFields() {
return styleSheet.collectFields();
}
private void addIfDuplicateField(Set<String> foundFields, Set<String> duplicateFields, String testField) {
if (!foundFields.add(testField)) {
duplicateFields.add(testField);
}
}
private Set<String> detectDuplicates() {
final Set<String> foundFields = new HashSet<String>();
final Set<String> duplicateFields = new HashSet<String>();
for (final String fieldName : fields) {
addIfDuplicateField(foundFields, duplicateFields, fieldName);
}
return duplicateFields;
}
private void addAllFieldNames(Set<String> qlFields) {
for (final FieldType fieldType : fieldTypes) {
qlFields.add(fieldType.getFieldName());
}
}
private void addIfNotReferenced(String fieldName, Set<String> qlFields, Set<String> undefinedReferences) {
if (!qlFields.contains(fieldName)) {
undefinedReferences.add(fieldName);
}
}
private Set<String> detectUndefinedReferences(Set<String> qlFields) {
final Set<String> undefinedReferences = new HashSet<String>();
for (final String fieldName : fields) {
addIfNotReferenced(fieldName, qlFields, undefinedReferences);
}
return undefinedReferences;
}
private Set<String> detectUndefinedReferences() {
final Set<String> qlFields = new HashSet<String>();
addAllFieldNames(qlFields);
return detectUndefinedReferences(qlFields);
}
private void addIfInvalidWidgetAssignment(Set<String> invalidWidgetAssignments, ITypeDescriptor fieldTypeDescriptor,
AbstractWidget abstractWidget, String fieldName) {
if (!fieldTypeDescriptor.isAllowed(abstractWidget)) {
invalidWidgetAssignments.add(fieldName);
}
}
private void detectInvalidWidgetAssignment(FieldType fieldType, Set<String> invalidWidgetAssignments) {
final String fieldName = fieldType.getFieldName();
try {
final StylingSettings stylingSettings = styleSheet.deriveStylingSettings(fieldType);
final AbstractWidget abstractWidget = stylingSettings.getWidget();
final ITypeDescriptor fieldTypeDescriptor = fieldType.getTypeDescriptor();
addIfInvalidWidgetAssignment(invalidWidgetAssignments, fieldTypeDescriptor, abstractWidget, fieldName);
} catch(FieldNotFoundException e) {
invalidWidgetAssignments.add(fieldName);
}
}
/**
* Detects invalid widget assignments (e.g. boolean to textbox) or no widget assignments at all.
*/
private Set<String> detectInvalidWidgetAssignments() {
final Set<String> invalidWidgetAssignments = new HashSet<String>();
for (final FieldType fieldType : fieldTypes) {
detectInvalidWidgetAssignment(fieldType, invalidWidgetAssignments);
}
return invalidWidgetAssignments;
}
private void detectInvalidBooleanDefaultWidgetAssignments(
Set<WidgetType> invalidDefaultWidgetAssignments, Defaults defaults) {
final Set<WidgetType> invalidBooleanDefaultWidgetAssignments =
detectInvalidDefaultWidgetAssignments(BooleanValue.BOOL_VALUE_TYPE_DESCRIPTOR, defaults);
invalidDefaultWidgetAssignments.addAll(invalidBooleanDefaultWidgetAssignments);
}
private void detectInvalidIntegerDefaultWidgetAssignments(
Set<WidgetType> invalidDefaultWidgetAssignments, Defaults defaults) {
final Set<WidgetType> invalidIntegerDefaultWidgetAssignments =
detectInvalidDefaultWidgetAssignments(IntegerValue.INTEGER_VALUE_TYPE_DESCRIPTOR, defaults);
invalidDefaultWidgetAssignments.addAll(invalidIntegerDefaultWidgetAssignments);
}
private void detectInvalidStringDefaultWidgetAssignments(
Set<WidgetType> invalidDefaultWidgetAssignments, Defaults defaults) {
final Set<WidgetType> invalidStringDefaultWidgetAssignments =
detectInvalidDefaultWidgetAssignments(StringValue.STRING_VALUE_TYPE_DESCRIPTOR, defaults);
invalidDefaultWidgetAssignments.addAll(invalidStringDefaultWidgetAssignments);
}
private Set<WidgetType> detectInvalidDefaultWidgetAssignments() {
final Set<WidgetType> invalidDefaultWidgetAssignments = new HashSet<WidgetType>();
for (final Defaults defaults : collectedDefaults) {
detectInvalidBooleanDefaultWidgetAssignments(invalidDefaultWidgetAssignments, defaults);
detectInvalidIntegerDefaultWidgetAssignments(invalidDefaultWidgetAssignments, defaults);
detectInvalidStringDefaultWidgetAssignments(invalidDefaultWidgetAssignments, defaults);
}
return invalidDefaultWidgetAssignments;
}
private AbstractWidget getWidgetFromDefaultStyles(Defaults defaults, ITypeDescriptor valueTypeDescriptor) {
return defaults.getWidget(valueTypeDescriptor);
}
private Set<WidgetType> detectInvalidDefaultWidgetAssignments(final ITypeDescriptor valueTypeDescriptor,
final Defaults defaults) {
final Set<WidgetType> invalidDefaultWidgetAssignments = new HashSet<WidgetType>();
final AbstractWidget widget = getWidgetFromDefaultStyles(defaults, valueTypeDescriptor);
if (widget != null && !valueTypeDescriptor.isAllowed(widget)) {
final WidgetType unallowedWidgetType = new WidgetType(widget, valueTypeDescriptor);
invalidDefaultWidgetAssignments.add(unallowedWidgetType);
}
return invalidDefaultWidgetAssignments;
}
private void checkMissingQLFieldInQLS(FieldType fieldType, Set<String> missingQLFieldsInQLS) {
if (!fields.contains(fieldType.getFieldName())) {
final String fieldTypeName = fieldType.getFieldName();
missingQLFieldsInQLS.add(fieldTypeName);
}
}
private Set<String> detectMissingQLFieldsInQLS() {
Set<String> missingQLFieldsInQLS = new HashSet<String>();
for (FieldType fieldType : fieldTypes) {
checkMissingQLFieldInQLS(fieldType, missingQLFieldsInQLS);
}
return missingQLFieldsInQLS;
}
/**
* Perform five different style checks on the provided stylesheet.
*/
public QLSTypeCheckResults typeCheck() {
return new QLSTypeCheckResults(
detectUndefinedReferences(),
detectMissingQLFieldsInQLS(),
detectInvalidWidgetAssignments(),
detectInvalidDefaultWidgetAssignments(),
detectDuplicates());
}
public QLSTypeChecker(StyleSheet styleSheet, List<FieldType> fieldTypes) {
this.styleSheet = styleSheet;
this.fieldTypes = fieldTypes;
this.fields = collectFields();
this.collectedDefaults = collectDefaults();
}
}