package com.hannesdorfmann.fragmentargs.processor;
import com.hannesdorfmann.fragmentargs.annotation.Arg;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
/**
* Simple data holder class for fragment args of a certain fragment
*
* @author Hannes Dorfmann
*/
public class AnnotatedFragment {
private Set<ArgumentAnnotatedField> requiredFields = new TreeSet<ArgumentAnnotatedField>();
private Set<ArgumentAnnotatedField> optional = new TreeSet<ArgumentAnnotatedField>();
private Map<String, ArgumentAnnotatedField> bundleKeyMap =
new HashMap<String, ArgumentAnnotatedField>();
private TypeElement classElement;
// qualified Bundler class is KEY, Varibale / Field name = VALUE
private Map<String, String> bundlerVariableMap = new HashMap<String, String>();
private int bundlerCounter = 0;
// Setter methods will be used
private Map<String, ExecutableElement> setterMethods = new HashMap<String, ExecutableElement>();
public AnnotatedFragment(TypeElement classElement) {
this.classElement = classElement;
}
/**
* Checks if a field (with the given name) is already in this class
*/
public boolean containsField(ArgumentAnnotatedField field) {
return requiredFields.contains(field) || optional.contains(field);
}
/**
* Checks if a key for a bundle has already been used
*/
public ArgumentAnnotatedField containsBundleKey(ArgumentAnnotatedField field) {
return bundleKeyMap.get(field.getKey());
}
private void checkAndSetCustomBundler(ArgumentAnnotatedField field) {
if (field.hasCustomBundler()) {
String bundlerClass = field.getBundlerClass();
String varName = bundlerVariableMap.get(bundlerClass);
if (varName == null) {
varName = "bundler" + (++bundlerCounter);
bundlerVariableMap.put(bundlerClass, varName);
}
field.setBundlerFieldName(varName);
}
}
/**
* Adds an field as required
*/
public void addRequired(ArgumentAnnotatedField field) {
bundleKeyMap.put(field.getKey(), field);
requiredFields.add(field);
checkAndSetCustomBundler(field);
}
/**
* Adds an field as optional
*/
public void addOptional(ArgumentAnnotatedField field) {
bundleKeyMap.put(field.getKey(), field);
optional.add(field);
checkAndSetCustomBundler(field);
}
public Set<ArgumentAnnotatedField> getRequiredFields() {
return requiredFields;
}
public Set<ArgumentAnnotatedField> getOptionalFields() {
return optional;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AnnotatedFragment)) return false;
AnnotatedFragment fragment = (AnnotatedFragment) o;
if (!getQualifiedName().equals(fragment.getQualifiedName())) return false;
return true;
}
@Override
public int hashCode() {
return getQualifiedName().hashCode();
}
public String getQualifiedName() {
return classElement.getQualifiedName().toString();
}
public String getSimpleName() {
return classElement.getSimpleName().toString();
}
public Set<ArgumentAnnotatedField> getAll() {
Set<ArgumentAnnotatedField> all = new HashSet<ArgumentAnnotatedField>(getRequiredFields());
all.addAll(getOptionalFields());
return all;
}
public Map<String, String> getBundlerVariableMap() {
return bundlerVariableMap;
}
/**
* Checks if the given element is a valid setter method and add it to the internal setter
*
* @param classMember Could be everything except an field
*/
public void checkAndAddSetterMethod(Element classMember) {
if (classMember.getKind() == ElementKind.METHOD) {
ExecutableElement methodElement = (ExecutableElement) classMember;
String methodName = methodElement.getSimpleName().toString();
if (methodName.startsWith("set")) {
ExecutableElement existingSetter = setterMethods.get(methodName);
if (existingSetter != null) {
// Check for better visibility
if (ModifierUtils.compareModifierVisibility(methodElement, existingSetter) == -1) {
// this method has better visibility so use this one
setterMethods.put(methodName, methodElement);
}
} else {
setterMethods.put(methodName, methodElement);
}
}
}
}
/**
* Searches for a setter and returns the setter method
*
* @param field the {@link ArgumentAnnotatedField}
* @return the setter method
* @throws ProcessingException If no setter method has been found
*/
public ExecutableElement findSetterForField(ArgumentAnnotatedField field) throws ProcessingException {
String fieldName = field.getVariableName();
StringBuilder builder = new StringBuilder("set");
if (fieldName.length() == 1) {
builder.append(fieldName.toUpperCase());
} else {
builder.append(Character.toUpperCase(fieldName.charAt(0)));
builder.append(fieldName.substring(1));
}
String methodName = builder.toString();
ExecutableElement setterMethod = setterMethods.get(methodName);
if (setterMethod != null && isSetterApplicable(field, setterMethod)) {
return setterMethod; // setter method found
}
// Search for setter method with hungarian notion check
if (field.getName().length() > 1 && field.getName().matches("m[A-Z].*")) {
// m not in lower case
String hungarianMethodName = "set" + field.getName();
setterMethod = setterMethods.get(hungarianMethodName);
if (setterMethod != null && isSetterApplicable(field, setterMethod)) {
return setterMethod; // setter method found
}
// M in upper case
hungarianMethodName = "set" + Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1);
setterMethod = setterMethods.get(hungarianMethodName);
if (setterMethod != null && isSetterApplicable(field, setterMethod)) {
return setterMethod; // setter method found
}
}
throw new ProcessingException(field.getElement(), "The @%s annotated field '%s' in class %s has " +
"private or protected visibility. Hence a corresponding setter method must be provided " +
"called '%s(%s)'. Unfortunately this is not the case. Please add a setter method for " +
"this field!", Arg.class.getSimpleName().toString(), field.getName(), getSimpleName(), methodName,
field.getType());
}
private boolean isSetterApplicable(ArgumentAnnotatedField field, ExecutableElement setterMethod) {
List<? extends VariableElement> parameters = setterMethod.getParameters();
if (parameters == null || parameters.size() != 1) {
return false;
}
VariableElement parameter = parameters.get(0);
return parameter.asType().equals(field.getElement().asType());
}
}