// Path: annotations/src/main/java/org/leandreck/endpoints/processor/model/typefactories/TypeNodeUtils.java
// public class TypeNodeUtils {
// 
//     static final String NO_TEMPLATE = "NO_TEMPLATE";
//     static final String UNDEFINED_TYPE_NAME = "UNDEFINED";
//     static final String JAVA_LANG_OBJECT = "java.lang.Object";
//     static TypeMirror objectMirror;
// 
//     public static <A extends Annotation> A getAnnotationForClass(final TypeMirror typeMirror, final Class<A> annotation, final Types typeUtils) {
//         final TypeKind kind = typeMirror.getKind();
//         final TypeMirror realMirror;
//         if (TypeKind.ARRAY.equals(kind)) {
//             realMirror = ((ArrayType) typeMirror).getComponentType();
//         } else {
//             realMirror = typeMirror;
//         }
// 
//         final Element definingElement = typeUtils.asElement(realMirror);
//         return (definingElement != null) ? definingElement.getAnnotation(annotation) : null;
//     }
// 
//     static String defineTemplate(final String typeNodeTemplate,
//                                  final TypeScriptType typeScriptTypeAnnotation,
//                                  final Element element) {
// 
//         if (typeNodeTemplate == null || typeNodeTemplate.isEmpty()) {
//             throw new MissingConfigurationTemplateException("TemplateConfiguration is null while processing Element", element);
//         }
// 
//         final String template;
//         if (typeScriptTypeAnnotation == null || typeScriptTypeAnnotation.template().isEmpty()) {
//             template = typeNodeTemplate;
//         } else {
//             template = typeScriptTypeAnnotation.template();
//         }
// 
//         return template;
//     }
// 
//     static String defineName(final TypeMirror typeMirror, final TypeScriptType typeScriptTypeAnnotation, final Function<TypeMirror, String> nameFunction) {
//         //check if has a annotation and a type
//         final String typeFromAnnotation = TypeNodeUtils.defineTypeFromAnnotation(typeScriptTypeAnnotation);
//         if (!UNDEFINED_TYPE_NAME.equals(typeFromAnnotation)) {
//             return typeFromAnnotation;
//         }
//         //forward to furter determination
//         return nameFunction.apply(typeMirror);
//     }
// 
//     static TypeMirror getObjectMirror(final Elements elementUtils) {
//         if (objectMirror == null) {
//             objectMirror = elementUtils.getTypeElement(JAVA_LANG_OBJECT).asType();
//         }
//         return objectMirror;
//     }
// 
//     private static String defineTypeFromAnnotation(final TypeScriptType annotation) {
//         if (annotation != null && !annotation.value().isEmpty()) {
//             return annotation.value();
//         }
//         return UNDEFINED_TYPE_NAME;
//     }
// 
//     /**
//      * No Instances of this utility class.
//      */
//     private TypeNodeUtils() {
//     }
// }
// 
// Path: annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java
// protected static final String EMPTY_JAVA_DOC = "";


import org.leandreck.endpoints.annotations.TypeScriptIgnore;
import org.leandreck.endpoints.processor.config.TemplateConfiguration;
import org.leandreck.endpoints.processor.model.typefactories.ConcreteTypeNodeFactory;
import org.leandreck.endpoints.processor.model.typefactories.TypeNodeKind;
import org.leandreck.endpoints.processor.model.typefactories.TypeNodeUtils;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static java.util.stream.Collectors.toList;
import static org.leandreck.endpoints.processor.model.TypeNode.EMPTY_JAVA_DOC;
import static org.leandreck.endpoints.processor.model.typefactories.TypeNodeKind.OPTIONAL;
        return initType(fieldName, parameterName, optionalByAnnotation || optionalByType, typeNodeKind, typeMirror, containingType, doc);
    }

    public List<TypeNode> defineChildren(final TypeElement typeElement, final DeclaredType typeMirror) {
        final List<String> publicGetter = definePublicGetter(typeElement, typeMirror, typeUtils);

        return ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
                .filter(c -> c.getAnnotation(TypeScriptIgnore.class) == null)
                .filter(c -> !c.getModifiers().contains(Modifier.TRANSIENT))
                .filter(c -> filterVariableElements(c, publicGetter))
                .map(it -> this.createTypeNode(it, /* parameterName */ null, typeMirror))
                .collect(toList());
    }

    private static List<String> definePublicGetter(final TypeElement typeElement, final TypeMirror typeMirror, final Types typeUtils) {

        final List<String> publicGetters = ElementFilter.methodsIn(typeElement.getEnclosedElements()).stream()
                .filter(g -> g.getSimpleName().toString().startsWith("get") || g.getSimpleName().toString().startsWith("is"))
                .filter(g -> g.getModifiers().contains(Modifier.PUBLIC))
                .filter(g -> !g.getModifiers().contains(Modifier.ABSTRACT))//FIXME filter remaining modifiers
                .map(g -> g.getSimpleName().toString())
                .collect(toList());

        if (isLombokAnnotatedType(typeMirror, typeUtils)) {
            ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
                    .filter(g -> !g.getModifiers().contains(Modifier.STATIC))
                    .map(g -> g.getSimpleName().toString())
                    .forEach(publicGetters::add);
        }

// Path: annotations/src/main/java/org/leandreck/endpoints/processor/model/typefactories/TypeNodeUtils.java
// public class TypeNodeUtils {
// 
//     static final String NO_TEMPLATE = "NO_TEMPLATE";
//     static final String UNDEFINED_TYPE_NAME = "UNDEFINED";
//     static final String JAVA_LANG_OBJECT = "java.lang.Object";
//     static TypeMirror objectMirror;
// 
//     public static <A extends Annotation> A getAnnotationForClass(final TypeMirror typeMirror, final Class<A> annotation, final Types typeUtils) {
//         final TypeKind kind = typeMirror.getKind();
//         final TypeMirror realMirror;
//         if (TypeKind.ARRAY.equals(kind)) {
//             realMirror = ((ArrayType) typeMirror).getComponentType();
//         } else {
//             realMirror = typeMirror;
//         }
// 
//         final Element definingElement = typeUtils.asElement(realMirror);
//         return (definingElement != null) ? definingElement.getAnnotation(annotation) : null;
//     }
// 
//     static String defineTemplate(final String typeNodeTemplate,
//                                  final TypeScriptType typeScriptTypeAnnotation,
//                                  final Element element) {
// 
//         if (typeNodeTemplate == null || typeNodeTemplate.isEmpty()) {
//             throw new MissingConfigurationTemplateException("TemplateConfiguration is null while processing Element", element);
//         }
// 
//         final String template;
//         if (typeScriptTypeAnnotation == null || typeScriptTypeAnnotation.template().isEmpty()) {
//             template = typeNodeTemplate;
//         } else {
//             template = typeScriptTypeAnnotation.template();
//         }
// 
//         return template;
//     }
// 
//     static String defineName(final TypeMirror typeMirror, final TypeScriptType typeScriptTypeAnnotation, final Function<TypeMirror, String> nameFunction) {
//         //check if has a annotation and a type
//         final String typeFromAnnotation = TypeNodeUtils.defineTypeFromAnnotation(typeScriptTypeAnnotation);
//         if (!UNDEFINED_TYPE_NAME.equals(typeFromAnnotation)) {
//             return typeFromAnnotation;
//         }
//         //forward to furter determination
//         return nameFunction.apply(typeMirror);
//     }
// 
//     static TypeMirror getObjectMirror(final Elements elementUtils) {
//         if (objectMirror == null) {
//             objectMirror = elementUtils.getTypeElement(JAVA_LANG_OBJECT).asType();
//         }
//         return objectMirror;
//     }
// 
//     private static String defineTypeFromAnnotation(final TypeScriptType annotation) {
//         if (annotation != null && !annotation.value().isEmpty()) {
//             return annotation.value();
//         }
//         return UNDEFINED_TYPE_NAME;
//     }
// 
//     /**
//      * No Instances of this utility class.
//      */
//     private TypeNodeUtils() {
//     }
// }
// 
// Path: annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNode.java
// protected static final String EMPTY_JAVA_DOC = "";


// Path: annotations/src/main/java/org/leandreck/endpoints/processor/model/TypeNodeFactory.java
import org.leandreck.endpoints.annotations.TypeScriptIgnore;
import org.leandreck.endpoints.processor.config.TemplateConfiguration;
import org.leandreck.endpoints.processor.model.typefactories.ConcreteTypeNodeFactory;
import org.leandreck.endpoints.processor.model.typefactories.TypeNodeKind;
import org.leandreck.endpoints.processor.model.typefactories.TypeNodeUtils;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static java.util.stream.Collectors.toList;
import static org.leandreck.endpoints.processor.model.TypeNode.EMPTY_JAVA_DOC;
import static org.leandreck.endpoints.processor.model.typefactories.TypeNodeKind.OPTIONAL;

        return initType(fieldName, parameterName, optionalByAnnotation || optionalByType, typeNodeKind, typeMirror, containingType, doc);
    }

    public List<TypeNode> defineChildren(final TypeElement typeElement, final DeclaredType typeMirror) {
        final List<String> publicGetter = definePublicGetter(typeElement, typeMirror, typeUtils);

        return ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
                .filter(c -> c.getAnnotation(TypeScriptIgnore.class) == null)
                .filter(c -> !c.getModifiers().contains(Modifier.TRANSIENT))
                .filter(c -> filterVariableElements(c, publicGetter))
                .map(it -> this.createTypeNode(it, /* parameterName */ null, typeMirror))
                .collect(toList());
    }

    private static List<String> definePublicGetter(final TypeElement typeElement, final TypeMirror typeMirror, final Types typeUtils) {

        final List<String> publicGetters = ElementFilter.methodsIn(typeElement.getEnclosedElements()).stream()
                .filter(g -> g.getSimpleName().toString().startsWith("get") || g.getSimpleName().toString().startsWith("is"))
                .filter(g -> g.getModifiers().contains(Modifier.PUBLIC))
                .filter(g -> !g.getModifiers().contains(Modifier.ABSTRACT))//FIXME filter remaining modifiers
                .map(g -> g.getSimpleName().toString())
                .collect(toList());

        if (isLombokAnnotatedType(typeMirror, typeUtils)) {
            ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
                    .filter(g -> !g.getModifiers().contains(Modifier.STATIC))
                    .map(g -> g.getSimpleName().toString())
                    .forEach(publicGetters::add);
        }

        return publicGetters;