package com.framsticks.params; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import com.framsticks.params.annotations.AutoAppendAnnotation; import com.framsticks.params.annotations.FramsClassAnnotation; import com.framsticks.params.annotations.ParamAnnotation; import com.framsticks.parsers.FileSource; import com.framsticks.parsers.Loaders; import com.framsticks.util.Builder; import com.framsticks.util.Misc; import com.framsticks.util.lang.Containers; import com.framsticks.util.lang.Strings; @FramsClassAnnotation(id = "class", name = "class") public class FramsClassBuilder implements Builder { private static final Logger log = Logger.getLogger(FramsClassBuilder.class); public static String getName(FramsClassAnnotation fca, Class javaClass) { return fca.name().equals("") ? javaClass.getSimpleName() : fca.name(); } public static String getId(FramsClassAnnotation fca, Class javaClass) { return fca.id().equals("") ? javaClass.getSimpleName() : fca.id(); } public static ParamBuilder induceParamType(ParamBuilder builder, Type type) { if (type instanceof ParameterizedType) { ParameterizedType p = (ParameterizedType) type; Type rawType = p.getRawType(); Type containedType = null; //TODO make implementation here boolean map = false; if (rawType.equals(Map.class)) { containedType = p.getActualTypeArguments()[1]; map = true; } else if (rawType.equals(List.class)) { containedType = p.getActualTypeArguments()[0]; } if (!(containedType instanceof Class)) { return builder; } Class containedClass = (Class) containedType; StringBuilder b = new StringBuilder(); b.append("l "); FramsClassAnnotation fca = containedClass.getAnnotation(FramsClassAnnotation.class); if (fca == null) { log.error("the class is not annotated: " + containedClass); return builder; } b.append(getName(fca, containedClass)); if (map) { b.append(" name"); } builder.type(b.toString()); return builder; } if (type instanceof Class) { Class cl = (Class) type; // TODO: future support for enum // if (cl.isEnum()) { // Class> enumType = (Class>) cl; // Enum[] enums = enumType.getEnumConstants(); // StringBuilder b = new StringBuilder(); // b.append("d 0 ").append(enums.length - 1).append(" 0 "); // for (Enum e : enums) { // b.append("~").append(e.name()); // } // return b.toString(); // } if (cl.equals(Integer.class) || cl.equals(int.class)) { builder.type("d"); return builder; } if (cl.equals(String.class)) { builder.type("s"); return builder; } if (cl.equals(Double.class) || cl.equals(double.class)) { builder.type("f"); return builder; } if (cl.equals(Boolean.class) || cl.equals(boolean.class)) { builder.type( "d 0 1"); return builder; } if (cl.equals(Object.class)) { builder.type("x"); return builder; } // builder.type("o " + (cl).getCanonicalName()); builder.type("o " + cl.getSimpleName()); builder.fillStorageType(cl); return builder; } throw new ConstructionException().msg("failed to find framsticks for native type").arg("type", type); } public static ParamBuilder induceParamType(ParamBuilder builder, ParamCandidate candidate) { Method method = candidate.getCaller(); if (method == null) { return induceParamType(builder, candidate.getType()); } if (!method.getReturnType().equals(Void.TYPE)) { builder.resultType(induceParamType(Param.build(), method.getGenericReturnType()).finish(ValueParam.class)); } List arguments = new ArrayList<>(); int number = 0; for (Type arg : method.getGenericParameterTypes()) { arguments.add(induceParamType(Param.build(), arg).idAndName("arg" + (number++)).finish(ValueParam.class)); } builder.argumentsType(arguments); return builder; } public static final String GENERATE_HELP_PREFIX = "automatically generated from: "; public static FramsClass readFromStream(InputStream stream) { return Loaders.loadFramsClass(new FileSource(stream)); } // public static Class getParamType(@Nonnull Class c) { // if (c.equals(Integer.class)) { // return DecimalParam.class; // } // if (c.equals(Double.class)) { // return FloatParam.class; // } // if (c.equals(String.class)) { // return StringParam.class; // } // if (c.equals(Object.class)) { // return UniversalParam.class; // } // return null; // } public static String extractIdOf(Member member) { if (member instanceof Field) { return member.getName(); } if (member instanceof Method) { Method m = (Method) member; String n = m.getName(); int argsNum = m.getParameterTypes().length; if (argsNum == 0) { return n.startsWith("get") ? Strings.uncapitalize(n.substring(3)) : n; } if (argsNum == 1) { return n.startsWith("set") ? Strings.uncapitalize(n.substring(3)) : n; } log.error("invalid number of arguments"); return null; } log.error("invalid kind of member"); return null; } public static String getName(ParamAnnotation annotation, Member member) { return annotation.name().equals("") ? Strings.capitalize(extractIdOf(member)) : annotation.name(); } public static String getId(ParamAnnotation annotation, Member member) { return annotation.id().equals("") ? extractIdOf(member) : annotation.id(); } public static ParamBuilder fill(ParamBuilder builder, Member member, ParamAnnotation annotation) { return builder .id(getId(annotation, member)) .name(getName(annotation, member)); } public static final Map, FramsClass> synchronizedCacheForBasedOnForJavaClass = Collections.synchronizedMap(new IdentityHashMap, FramsClass>()); public FramsClass forClass(Class javaClass) throws ConstructionException { FramsClass result = synchronizedCacheForBasedOnForJavaClass.get(javaClass); if (result != null) { return result; } log.debug("building for class " + javaClass); FramsClassAnnotation fca = javaClass.getAnnotation(FramsClassAnnotation.class); if (fca == null) { throw new ConstructionException().msg("java class is not annotated with FramsClassAnnotation").arg("java", javaClass); } id(getId(fca, javaClass)); name(getName(fca, javaClass)); for (ParamCandidate pc : ParamCandidate.getAllCandidates(javaClass).getOrder()) { ParamBuilder builder = Param.build().id(pc.getId()).name(pc.getName()).flags(pc.getFlags()); induceParamType(builder, pc); for (ParamAnnotation pa : pc.getAnnotations()) { if (!"".equals(pa.def())) { builder.def(pa.def()); } if (!"".equals(pa.help())) { builder.help(pa.help()); } if (!"".equals(pa.min())) { builder.min(pa.min()); } if (!"".equals(pa.max())) { builder.max(pa.max()); } builder.extra(pa.extra()); if (!"".equals(pa.stringType())) { builder.type(pa.stringType()); } if (!pa.paramType().equals(Param.class)) { builder.type(pa.paramType()); } } param(builder); } result = finish(); synchronizedCacheForBasedOnForJavaClass.put(javaClass, result); return result; } protected String id; protected String name; protected String description; protected final List params = new LinkedList<>(); protected List groupBuilders = new ArrayList(); @ParamAnnotation public FramsClassBuilder id(String id) { this.id = id; return this; } @ParamAnnotation public FramsClassBuilder name(String name) { this.name = name; return this; } public FramsClassBuilder idAndName(String v) { this.id = v; this.name = v; return this; } @ParamAnnotation(id = "desc") public FramsClassBuilder description(String description) { this.description = description; return this; } public FramsClassBuilder() { } public FramsClass finish() { return new FramsClass(this); } @AutoAppendAnnotation public FramsClassBuilder param(ParamBuilder builder) { Param param = builder.finish(); Integer group = param.getGroup(); if (group != null) { Containers.getFromList(groupBuilders, group, "group", this).addParam(param); } params.add(param); return this; } @AutoAppendAnnotation public FramsClassBuilder group(GroupBuilder builder) { groupBuilders.add(builder); return this; } /** * @return the id */ @ParamAnnotation public String getId() { return id; } /** * @return the name */ @ParamAnnotation public String getName() { return name; } /** * @return the description */ @ParamAnnotation(id = "desc") public String getDescription() { return description; } public FramsClassBuilder group(String group) { return group(new GroupBuilder().name(group)); } @Override public String toString() { return "FramsClassBuilder for " + Misc.returnNotNull(id, ""); } }