All Downloads are FREE. Search and download functionalities are using the official Maven repository.

prompto.processor.WidgetPropertiesProcessor Maven / Gradle / Ivy

The newest version!
package prompto.processor;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import prompto.declaration.CategoryDeclaration;
import prompto.declaration.IWidgetDeclaration;
import prompto.error.InternalError;
import prompto.expression.MethodExpression;
import prompto.expression.TypeExpression;
import prompto.grammar.Annotation;
import prompto.grammar.Identifier;
import prompto.intrinsic.PromptoSet;
import prompto.literal.BooleanLiteral;
import prompto.literal.DocEntry;
import prompto.literal.DocEntryList;
import prompto.literal.DocumentLiteral;
import prompto.literal.SetLiteral;
import prompto.literal.TextLiteral;
import prompto.literal.TypeLiteral;
import prompto.property.IPropertyValidator;
import prompto.property.Property;
import prompto.property.PropertyMap;
import prompto.property.TypeSetValidator;
import prompto.property.TypeValidator;
import prompto.property.ValidatorSetValidator;
import prompto.property.ValueSetValidator;
import prompto.runtime.Context;
import prompto.runtime.Context.InstanceContext;
import prompto.type.AnyType;
import prompto.type.IType;
import prompto.type.IntegerType;
import prompto.type.PropertiesType;
import prompto.type.TextType;
import prompto.type.TypeType;
import prompto.value.BooleanValue;
import prompto.value.IValue;
import prompto.value.IntegerValue;
import prompto.value.NullValue;
import prompto.value.SetValue;
import prompto.value.TextValue;
import prompto.value.TypeValue;

public class WidgetPropertiesProcessor extends AnnotationProcessor {

	@Override
	public void processCategory(Annotation annotation, Context context, CategoryDeclaration declaration) {
		if(declaration.isAWidget(context))
			doProcessCategory(annotation, context, declaration);
		else
			context.getProblemListener().reportIllegalAnnotation(annotation, "WidgetProperties is only applicable to widgets");
	}

	private void doProcessCategory(Annotation annotation, Context context, CategoryDeclaration declaration) {
		IWidgetDeclaration widget = declaration.asWidget();
		Object value = annotation.getDefaultArgument();
		PropertyMap properties = checkProperties(annotation, context, value);
		if(properties!=null) {
			widget.setProperties(properties);
			Annotation widgetField = findWidgetPropertiesFieldAnnotation(context, declaration);
			if(widgetField!=null)
				overrideWidgetFieldType(context, widgetField, new PropertiesType(properties));
		}
	}
	
	private void overrideWidgetFieldType(Context context, Annotation widgetField, IType type) {
		Object value = widgetField.getArgument("name");
		if(!(value instanceof TextLiteral))	
			return; // raise warning
		String name = ((TextLiteral)value).toString();
		InstanceContext instance = context.getClosestInstanceContext();
		if(instance==null)
			throw new InternalError("Expected an instance context. Please report this bug.");
		instance.overrideWidgetFieldType(new Identifier(name.substring(1, name.length() -1)), type, this);
	}

	private Annotation findWidgetPropertiesFieldAnnotation(Context context, CategoryDeclaration declaration) {
		return declaration.getAllAnnotationsAsStream(context)
				.filter(a->a.toString().equals("@WidgetField"))
				.filter(a->{
					Object value = a.getArgument("isProperties");
					return value instanceof BooleanLiteral && ((BooleanLiteral)value).getValue().getValue();
				}).findFirst()
				.orElse(null);
	}

	private PropertyMap checkProperties(Annotation annotation, Context context, Object value) {
		if(!(value instanceof DocumentLiteral)) {
			context.getProblemListener().reportIllegalAnnotation(annotation, "WidgetProperties expects a Document of types as unique parameter");
			return null;
		}
		return loadProperties(annotation, context, ((DocumentLiteral)value).getEntries());
	}
	
	public PropertyMap loadProperties(Annotation annotation, Context context, DocEntryList entries) {
		PropertyMap props = new PropertyMap();
		for(DocEntry entry : entries) {
			Property prop = loadProperty(annotation, context, entry);
			if(prop==null)
				continue;
			if(props.containsKey(prop.getName()))
				context.getProblemListener().reportIllegalAnnotation(entry.getKey(), "Duplicate property: " + prop.getName());
			else
				props.put(prop.getName(), prop);
		}
		return props;
	}
	
	private Property loadProperty(Annotation annotation, Context context, DocEntry entry) {
		Property prop = new Property();
		prop.setName(entry.getKey().toString());
		Object value = entry.getValue();
		if(value instanceof MethodExpression) {
			value = ((MethodExpression)value).asTypeLiteral(context);
		}
		if(value instanceof TypeExpression) {
			IType type = ((TypeExpression)value).getType();
			value = new TypeLiteral(type);
		}
		if(value instanceof TypeLiteral)
			return loadPropertyFromTypeLiteral(annotation, context, entry, prop, (TypeLiteral)value);
		else if(value instanceof SetLiteral)
			return loadPropertyFromSetLiteral(annotation, context, entry, prop, (SetLiteral)value);
		else if(value instanceof DocumentLiteral)
			return loadPropertyFromDocumentLiteral(annotation, context, entry, prop, (DocumentLiteral)value);
		else {
			context.getProblemListener().reportIllegalAnnotation(annotation, "WidgetProperties expects a Document of types as unique parameter");
			return null;
		}
	}
			
	private Property loadPropertyFromDocumentLiteral(Annotation annotation, Context context, DocEntry entry, Property prop, DocumentLiteral doc) {
		for(DocEntry child : doc.getEntries()) {
			String name = child.getKey().toString();
			Object value = child.getValue();
			switch(name) {
			case "required":
				if(value instanceof BooleanLiteral) {
					prop.setRequired(((BooleanLiteral)value).interpret(context)==BooleanValue.TRUE);
					break;
				}
				context.getProblemListener().reportIllegalAnnotation(child.getKey(), "Expected a Boolean value for 'required'.");
				return null;
			case "help":
				if(value instanceof TextLiteral) {
					prop.setHelp(((TextLiteral)value).getValue().getStorableData());
					break;
				}
				context.getProblemListener().reportIllegalAnnotation(child.getKey(), "Expected a Text value for 'help'.");
				return null;
			case "type":
				if(value instanceof TypeLiteral) {
					IPropertyValidator validator = newTypeValidator(annotation, context, entry, prop, ((TypeLiteral)value).getType());
					if(validator != null) {
						prop.setValidator(validator);
						break;
					}
				} else if(value instanceof DocumentLiteral) {
					PropertyMap embedded = loadProperties(annotation, context, ((DocumentLiteral)value).getEntries());
					if(embedded!=null) {
						prop.setValidator(new TypeValidator(new PropertiesType(embedded)));
						break;
					}
				}
				context.getProblemListener().reportIllegalAnnotation(child.getKey(), "Expected a Type for 'type'.");
				return null;
			case "types":
				if(value instanceof SetLiteral) {
					SetValue values = ((SetLiteral)value).interpret(context);
					if(values.getItemType() instanceof TypeType) {
						IPropertyValidator validator = newTypeSetValidator(annotation, context, values);
						if(validator != null) {
							prop.setValidator(validator);
							break;
						}
					}
				}
				context.getProblemListener().reportIllegalAnnotation(child.getKey(), "Expected a Set of types for 'types'.");
				return null;
			case "values":
				if(value instanceof SetLiteral) {
					SetValue values = ((SetLiteral)value).interpret(context);
					if(values.getItemType() instanceof TextType || values.getItemType() instanceof IntegerType) {
							IPropertyValidator validator = newValueSetValidator(annotation, context, values);
						if(validator != null) {
							prop.setValidator(validator);
							break;
						} 
					}
				}
				context.getProblemListener().reportIllegalAnnotation(child.getKey(), "Expected a Set of values for 'values'.");
				return null;
			default:
				context.getProblemListener().reportIllegalAnnotation(child.getKey(), "Unknown property attribute: " + name);
				return null;
			}
		}
		return prop;
	}

	private Property loadPropertyFromSetLiteral(Annotation annotation, Context context, DocEntry entry, Property prop, SetLiteral literal) {
		SetValue value = literal.interpret(context);
		IPropertyValidator validator = newValidatorFromSetValue(annotation, context, value) ;
		if(validator != null) {
			prop.setValidator(validator);
			return prop;
		} else {
			context.getProblemListener().reportIllegalAnnotation(entry.getKey(), "Expected a Set of types or values.");
			return null;
		}
	}
	

	private Property loadPropertyFromTypeLiteral(Annotation annotation, Context context, DocEntry entry, Property prop, TypeLiteral value) {
		IPropertyValidator validator = newTypeValidator(annotation, context, entry, prop, value.getType());
		if(validator==null)
			return null;
		prop.setValidator(validator);
		return prop;
	}

	private IPropertyValidator newTypeValidator(Annotation annotation, Context context, DocEntry entry, Property prop, IType type) {
		IType actual = type.resolve(context, t->context.getProblemListener().reportIllegalAnnotation(annotation, "Unknown type: " + t.getTypeName()));
		return actual==null ? null : new TypeValidator(actual);
	}

	private IPropertyValidator newValidatorFromSetValue(Annotation annotation, Context context, SetValue value) {
		IType itemType = value.getItemType();
		if(itemType instanceof TypeType)
			return newTypeSetValidator(annotation, context, value);
		else if(itemType == TextType.instance() || itemType == IntegerType.instance())
			return newValueSetValidator(annotation, context, value);
		else if(itemType == AnyType.instance()) {
			if(setContainsType(context, value))
				return newValidatorSetValidator(annotation, context, value);
			else
				return newValueSetValidator(annotation, context, value);
		} else 
			return null;
	}

	private boolean setContainsType(Context context, SetValue value) {
		return value.getItems().stream()
				.map(IValue::getType)
				.anyMatch(t -> t instanceof TypeType);
	}

	private IPropertyValidator newValidatorSetValidator(Annotation annotation, Context context, SetValue value) {
        List validators = value.getItems().stream()
                .filter(l -> l != NullValue.instance())
                .map(l -> newValidatorFromValue(annotation, context, l))
                .filter(Objects::nonNull)
                .map(v -> v.optional())
                .collect(Collectors.toList());
        IPropertyValidator result = new ValidatorSetValidator(validators);
        if(validators.size() == value.getLength()) // no null filtered out
        	result = result.required();
        return result;
	}
	
	private IPropertyValidator newValidatorFromValue(Annotation annotation, Context context, IValue value) {
       if(value instanceof SetValue)
            return newValidatorFromSetValue(annotation, context, (SetValue)value);
        else if(value instanceof TextValue || value instanceof IntegerValue) {
            value = new SetValue(value.getType(), new PromptoSet(Collections.singleton(value)));
            return newValueSetValidator(annotation, context, (SetValue)value);
        } else if(value instanceof TypeValue)
           return new TypeValidator(((TypeValue)value).getValue());
        else
            return null;
	}

	private IPropertyValidator newValueSetValidator(Annotation annotation, Context context, SetValue value) {
		Set texts = value.getItems().stream()
				.filter(v->v!=NullValue.instance())
				.map(Object::toString)
				.collect(Collectors.toSet());
		IPropertyValidator result = new ValueSetValidator(texts);
		if(texts.size()==value.getLength()) // no null filtered out
			result = result.required();
		return result;
	}

	private IPropertyValidator newTypeSetValidator(Annotation annotation, Context context, SetValue value) {
		Set types = value.getItems().stream()
			.filter(v->v!=NullValue.instance())
			.map(v->(TypeValue)v)
			.map(TypeValue::getValue)
			.map(type->type.resolve(context,t->context.getProblemListener().reportIllegalAnnotation(annotation, "Unkown type: " + t.getTypeName())))
			.collect(Collectors.toSet());
		if(types.contains(null))
			return null;
		IPropertyValidator result = new TypeSetValidator(types);
		if(types.size()==value.getLength()) // no null filtered out
			result = result.required();
		return result;
	} 
	

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy