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

com.artemis.FactoryModel Maven / Gradle / Ivy

The newest version!
package com.artemis;

import static java.lang.String.format;
import static javax.tools.Diagnostic.Kind.ERROR;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;

import com.artemis.annotations.Bind;
import com.artemis.annotations.Sticky;
import com.artemis.annotations.UseSetter;

public class FactoryModel {
	private final Set components = new HashSet();
	private final List methods;
	final TypeElement declaration;
	private final Map autoResolvable;
	private final ProcessingEnvironment env;
	private Messager messager;
	boolean success = true;
	
	private static final List IGNORED_METHODS = Arrays.asList("getClass", "wait", "notify", "notifyAll", "equals",
			"hashCode", "equals", "toString", "copy",
			"create", "tag", "group");
	
	FactoryModel(TypeElement declaration, ProcessingEnvironment env) {
		this.declaration = declaration;
		this.env = env;
		messager = env.getMessager();
		
		autoResolvable = new HashMap();
		for (TypeElement parent : ProcessorUtil.parentInterfaces(declaration)) {
			autoResolvable.putAll(readGlobalCRefs(parent));
		}
		autoResolvable.putAll(readGlobalCRefs(declaration));
		
		// if something overrides
		
		methods = scanMethods(declaration);
		validate();
	}
	
	private void validate() {
		DeclaredType factory = ProcessorUtil.findFactory(declaration);
		if (factory == null) {
			success = false;
			messager.printMessage(ERROR, "Interface must extend com.artemis.EntityFactory", declaration);
			return;
		}
		
		// if empty, we're probably extending an existing factory
		if (factory.getTypeArguments().size() > 0) { 
			DeclaredType argument = ((DeclaredType) factory.getTypeArguments().get(0));
			TypeElement factoryType = (TypeElement) argument.asElement();
			if (!factoryType.getQualifiedName().equals(declaration.getQualifiedName())) {
				success = false;
				messager.printMessage(ERROR,
						format("Expected EntityFactory<%s>, but found EntityFactory<%s>",
								declaration.getSimpleName(),
								factoryType.getSimpleName()),
						declaration);
			}
		}
		
		for (FactoryMethod method : methods)
			success &= method.validate(messager);
	}
	
	public List getStickyMethods() {
		List m = new ArrayList();
		for (FactoryMethod fm : methods)
			if (fm.sticky && fm.setterMethod == null) m.add(fm);
		
		return m;
	}
	
	public List getInstanceMethods() {
		List m = new ArrayList();
		for (FactoryMethod fm : methods)
			if (!fm.sticky && fm.setterMethod == null) m.add(fm);
		
		return m;
	}
	
	public List getSetterMethods() {
		List m = new ArrayList();
		for (FactoryMethod fm : methods)
			if (!fm.sticky && fm.setterMethod != null) m.add(fm);
		
		return m;
	}

	public String getPackageName() {
		String pkg = declaration.getEnclosingElement().toString();
		if (pkg.startsWith("package ")) pkg = pkg.substring("package ".length());
		return pkg;
	}
	
	public String getFactoryName() {
		return declaration.getSimpleName().toString();
	}
	
	public List getComponents(boolean qualifiedName) {
		List components = new ArrayList();
		for (TypeElement c : this.components)
			components.add((qualifiedName ? c.getQualifiedName() : c.getSimpleName()).toString());
		
		return components;
	}
	
	public Set getMappedComponents() {
		Set components = new HashSet();
		for (FactoryMethod m : this.methods)
			components.add(m.component.getSimpleName().toString());
		
		return components;
	}
	
	public Set getFields() {
		Set fields = new TreeSet();
		for (FactoryMethod m : methods) {
			if (!m.sticky)
				fields.add("private boolean " + m.getFlagName());
			
			for (Param p : m.getParams()) {
				fields.add(format("private %s %s", p.type, p.field));
			}
		}
		
		return fields;
	}
	
	private List scanMethods(TypeElement factory) {
//		Elements util = env.getElementUtils();
//		return factoryMethods(util.getAllMembers(factory));
		return factoryMethods(ProcessorUtil.componentMethods(factory));
	}
	
	private List factoryMethods(List allMembers) {
		List methods = new ArrayList();
		for (ExecutableElement e : allMembers) {
			String elementName = e.getSimpleName().toString();

			if (!IGNORED_METHODS.contains(elementName)) {
				FactoryMethod method = factoryMethod(e);
				if (method != null) {
					methods.add(method);
				} else {
					success = false;
				}
			}
			else {
				if (!readCRef(e).isEmpty()) {
					String err = "Invalid method name for factory method";
					messager.printMessage(Kind.ERROR, err, e);
					success = false;
				}
			}
		}
		return methods;
	}

	private FactoryMethod factoryMethod(ExecutableElement e) {
		String elementName = e.getSimpleName().toString();

		List referenced = readCRef(e);
		if (referenced.size() == 0) {
			if (autoResolvable.containsKey(elementName)) {
				return new FactoryMethod(e, autoResolvable.get(elementName));
			} else {
				String err = "Unable to match component for " + e.getSimpleName();
				messager.printMessage(Kind.ERROR, err, e);
				return null;
			}
		} else if (referenced.size() == 1) {
			return bindMethod(e, (DeclaredType)referenced.get(0).getValue());
		} else {
			String err = "@Bind on methods limited to one component type, found " + referenced.size();
			messager.printMessage(Kind.ERROR, err, e);
			return null;
		}
	}

	private FactoryMethod bindMethod(ExecutableElement method, DeclaredType value) {
		TypeElement component = (TypeElement)value.asElement();
		components.add(component);
		
		// scan for UseSetter
		String setterMethod = null;
		AnnotationMirror setterMirror = ProcessorUtil.mirror(UseSetter.class, method);
		if (setterMirror != null) {
			AnnotationValue setter = readAnnotationField(setterMirror, "value");
			setterMethod = (setter != null) 
					? (String)setter.getValue()
					: method.getSimpleName().toString();
		}
		
		return new FactoryMethod(method, component, setterMethod);
	}

	private Map readGlobalCRefs(TypeElement declaration) {
		Map autoResolvable = new HashMap(); 
		for (AnnotationValue value : readCRef(declaration)) {
			TypeElement type = (TypeElement)((DeclaredType)value.getValue()).asElement();
			this.components.add(type);
			autoResolvable.put(key(type), type);
		}
		
		return autoResolvable;
	}

	@SuppressWarnings("unchecked")
	private static List readCRef(Element element) {
		AnnotationMirror cref = ProcessorUtil.mirror(Bind.class, element);
		if (cref == null)
			return Collections.emptyList();
		
		AnnotationValue components = readAnnotationField(cref, "value");
		return (List)components.getValue();
	}


	private static String key(TypeElement type) {
		String key = type.getSimpleName().toString();
		return key.toLowerCase().charAt(0) + key.substring(1);
	}
	
	static AnnotationValue readAnnotationField(AnnotationMirror annotation, String field) {
		for (ExecutableElement key : annotation.getElementValues().keySet()) {
			if (field.equals(key.getSimpleName().toString())) {
				return annotation.getElementValues().get(key);
			}
		}
		
		return null;
	}
	
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("FactoryModel:" + getPackageName() + declaration.getSimpleName() + "(\n");

		String delim = "";
		sb.append("\tarchetype=");
		for (TypeElement c : components) {
			sb.append(delim).append(c.getSimpleName());
			delim = ", ";
		}
		sb.append("\n");
		for (FactoryMethod m : this.methods) {
			sb.append('\t').append(m).append('\n');
		}
		sb.append(')');
		return sb.toString();
	}

	private static String camelCase(CharSequence s) {
		return Character.toLowerCase(s.charAt(0)) + s.toString().substring(1);
	}

	public static class FactoryMethod {
		public final boolean sticky;
		public final ExecutableElement method;
		public final TypeElement component;
		public final Map params;
		private final String setterMethod;
		
		private FactoryMethod(ExecutableElement method, TypeElement component) {
			this(method, component, null);
		}
		
		private FactoryMethod(ExecutableElement method, TypeElement component, String setterMethod) {
			assert(method != null);
			assert(component != null);
			this.method = method;
			this.sticky = method.getAnnotation(Sticky.class) != null;
			this.component = component;
			this.setterMethod = setterMethod;
			
			params = map(method.getParameters());
		}
		
		// refactor into own class
		boolean validate(Messager messager) {
			Map found = map(component.getEnclosedElements());
			boolean success = true;
			
			for (Entry param : params.entrySet()) {
				if (setterMethod == null)
					success &= validateFieldAccess(messager, found, param);
				else // invoking setter
					success &= validateSetterAccess(messager, found, param);
			}
			
			return success;
		}
		
		private boolean validateFieldAccess(Messager messager,
				Map found,	Entry param) {
			
			if (!found.containsKey(param.getKey())) {
				messager.printMessage(
						ERROR,
						format("%s has no field named %s", component.getSimpleName(), param.getKey()),
						param.getValue());
				
				return false;
			}

			if (!isParameterValid(param)) {
				messager.printMessage(
						ERROR,
						"Only primitive, enum and string types supported",
						param.getValue());
				
				return false;
			}
			
			return true;
		}

		private boolean isParameterValid(Entry param) {
			VariableElement value = param.getValue();
			TypeMirror type = value.asType();

			return type.getKind().isPrimitive()
					|| ProcessorUtil.isEnum(type)
					|| ProcessorUtil.isString(type);
		}

		private boolean validateSetterAccess(Messager messager,
				Map found,	Entry param) {
			
			// component has method
			if (!ProcessorUtil.hasMethod(component, setterMethod)) {
				messager.printMessage(
						ERROR,
						format("Expected to find method '%s' in component '%s'",
							setterMethod, component.getSimpleName()),
						method);
				
				return false;
			}
			
			// validate parameter list
			if (!ProcessorUtil.hasMethod(component, setterMethod, method.getParameters())) {
				StringBuilder signature = new StringBuilder();
				for (VariableElement e : method.getParameters()) {
					if (signature.length() > 0) signature.append(", ");
					signature.append(e.asType().toString());
				}
				
				messager.printMessage(
						ERROR,
						format("Expected to find %s.%s(%s)'",
							component.getSimpleName(), setterMethod, signature),
						method);
				
				return false;
			}
			
			return true;
		}
		
		private static  Map map(List elements) {
			Map map = new HashMap();
			for (T e : elements)
				map.put(e.getSimpleName(), e);
			
			return map;
		}

		public String getFlagName() {
			StringBuilder sb = new StringBuilder();
			sb.append("_id_").append(camelCase(method.getSimpleName())).append("_");
			for (VariableElement e : method.getParameters()) {
				sb.append(e.getSimpleName()).append("_");
			}
			return sb.toString();
		}
		
		public String getName() {
			return camelCase(method.getSimpleName());
		}
		
		public String getComponentName() {
			return component.getSimpleName().toString();
		}
		
		
		public String getParamsFull() {
			return getParamsFull(method.getParameters());
		}
		
		private String getParamsFull(List parameters) {
			StringBuilder sb = new StringBuilder();
			for (VariableElement param : method.getParameters()) {
				if (sb.length() > 0) sb.append(", ");
				sb.append(param.asType() + " " + param.getSimpleName());
			}
			return sb.toString();
		}
		
		public List getParams() {
			List params = new ArrayList();
			for (VariableElement param : method.getParameters())
				params.add(new Param(component, param));
			
			return params;
		}
		
		public String getParamArgs() {
			StringBuilder params = new StringBuilder();
			String delim = "";
			for (VariableElement param : method.getParameters()) {
				params.append(delim);
				params.append(new Param(component, param).field);
				delim = ", ";
			}
			
			return params.toString();
		}
		
		public String getSetter() {
			return setterMethod;
		}

		@Override
		public String toString() {
			String stickied = sticky ? "@Sticky " : "";
			String cref = "@CRef(" + component.getSimpleName() + ".class) ";
			String params = getParamsFull(method.getParameters());
			
			return "FactoryMethod [" + stickied + cref + method.getSimpleName() + "(" + params + ")]";
		}
	}
	
	public static class Param {
		private final String field;
		private final String param;
		private final String type;
		
		Param(TypeElement component, VariableElement param) {
			this.param = camelCase(param.getSimpleName());
			this.field = String.format("_%s_%s", camelCase(component.getSimpleName()), this.param);
			this.type = param.asType().toString();
		}
		
		public String getType() {
			return type;
		}

		public String getField() {
			return field;
		}

		public String getParam() {
			return param;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((field == null) ? 0 : field.hashCode());
			result = prime * result + ((param == null) ? 0 : param.hashCode());
			result = prime * result + ((type == null) ? 0 : type.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Param other = (Param) obj;
			if (field == null) {
				if (other.field != null)
					return false;
			} else if (!field.equals(other.field))
				return false;
			if (param == null) {
				if (other.param != null)
					return false;
			} else if (!param.equals(other.param))
				return false;
			if (type == null) {
				if (other.type != null)
					return false;
			} else if (!type.equals(other.type))
				return false;
			return true;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy