package com.framsticks.params; import com.framsticks.params.annotations.FramsClassAnnotation; import com.framsticks.params.annotations.ParamAnnotation; import com.framsticks.params.types.*; import com.framsticks.util.Builder; import com.framsticks.util.FramsticksException; import com.framsticks.util.Misc; import com.framsticks.util.lang.FlagsUtil; import com.framsticks.util.lang.Strings; import org.apache.log4j.Logger; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; /** * The class ParamBuilder helps building Param objects. * * @author Mateusz Jarus (please replace name and * surname with my personal data) * * @author Piotr Ĺšniegowski */ @FramsClassAnnotation(name = "prop", id = "prop") public class ParamBuilder implements Builder { private final static Logger log = Logger.getLogger(ParamBuilder.class.getName()); private static final String ID_FIELD = "id"; private static final String NAME_FIELD = "name"; private static final String HELP_FIELD = "help"; private static final String GROUP_FIELD = "group"; private static final String TYPE_FIELD = "type"; private static final String FLAGS_FIELD = "flags"; /** The parameter id. */ private String id; /** The number of group, that parameter belongs to. */ private Integer group; /** The flags stored as a bit sum. */ private int flags = 0; /** The parameter name. */ private String name; /** The help (description) concerning parameter. */ private String help; /** The type of parameter. */ private Class paramType; private Object min; private Object max; private Object def; private int extra = 0; protected String containedTypeName; protected String eventArgumentTypeName; protected Class storageType; protected FramsClassBuilder classBuilder; public ParamBuilder() { this(null); } protected ValueParam resultType; protected List argumentsType; /** * @param classBuilder */ public ParamBuilder(FramsClassBuilder classBuilder) { this.classBuilder = classBuilder; } /** * @return the min */ @ParamAnnotation public Object getMin() { return min; } /** * @return the max */ @ParamAnnotation public Object getMax() { return max; } /** * @return the def */ @ParamAnnotation public Object getDef() { return def; } public String getContainedTypeName() { return Strings.notEmpty(containedTypeName) ? containedTypeName : null; } public ParamBuilder containedTypeName(String containedTypeName) { this.containedTypeName = containedTypeName; return this; } /** * @return the resultType */ public ValueParam getResultType() { return resultType; } /** * @param resultType the resultType to set */ public ParamBuilder resultType(ValueParam resultType) { this.resultType = resultType; return this; } /** * @return the argumentsType */ public List getArgumentsType() { return argumentsType; } /** * @param argumentsType the argumentsType to set */ public ParamBuilder argumentsType(List argumentsType) { this.argumentsType = argumentsType; return this; } /** * @return the enumValues */ public List getEnumValues() { return enumValues; } /** * @return the uid */ public String getUid() { return uid; } public ParamBuilder uid(String uid) { this.uid = uid; return this; } public @Nonnull T finish(Class requested) { Param param = finish(); if (!requested.isInstance(param)) { throw new FramsticksException().msg("param is of wrong type").arg("requested", requested).arg("actual", param.getClass()); } return requested.cast(param); } /** * Build Param based on provided data. * * @return Param object * @throws Exception * when Param getType is not defined */ public @Nonnull Param finish() { try { if (paramType == null) { throw new FramsticksException().msg("trying to finish incomplete param while type is missing"); } return paramType.getConstructor(ParamBuilder.class).newInstance(this); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | FramsticksException e) { throw new FramsticksException().msg("failed to create param").cause(e).arg("name", name); } } @ParamAnnotation public ParamBuilder id(String id) { this.id = id; return this; } public ParamBuilder type(Class type) { assert type != null; this.paramType = type; return this; } /** * @return the id */ @ParamAnnotation public String getId() { return id; } @ParamAnnotation public ParamBuilder group(Integer group) { this.group = group; return this; } @ParamAnnotation public ParamBuilder flags(int flags) { this.flags = flags; return this; } @ParamAnnotation public ParamBuilder name(String name) { this.name = name; return this; } protected void parseMinMaxDefNumber(Class type, String second, String third, String fourth) { if (second != null) { min = second; } if (third != null) { max = third; } if (fourth != null) { def = fourth; } } protected List enumValues; public ParamBuilder enums(List values) { enumValues = values; return type(EnumParam.class); } protected String uid; @ParamAnnotation public ParamBuilder type(String type) { // typeString = type; assert type != null; log.trace("parsing type: " + type); String[] typeSplitted = type.split(" "); String first = typeSplitted[0]; String second = typeSplitted.length > 1 ? typeSplitted[1] : null; String third = typeSplitted.length > 2 ? typeSplitted[2] : null; String fourth = typeSplitted.length > 3 ? typeSplitted[3] : null; switch (first.charAt(0)) { case 'o': { containedTypeName = second != null ? second : first.substring(1); type(ObjectParam.class); break; } case 'p': { type(ProcedureParam.class); signature(type.substring(1)); break; } case 'd': { int tildeIndex = type.indexOf("~"); if (tildeIndex != -1) { enums(Arrays.asList(type.substring(tildeIndex + 1).split("~"))); } else { if (first.length() >= 2) { switch (first.charAt(1)) { case 'b': { type(BinaryParam.class); break; } case 'c': { type(ColorParam.class); break; } default: { log.error("unknown type: " + first); return this; } } } if ("0".equals(second) && "1".equals(third)) { type(BooleanParam.class); } if (paramType == null) { type(DecimalParam.class); } } if (DecimalParam.class.isAssignableFrom(this.paramType)) { parseMinMaxDefNumber(Integer.class, second, third, fourth); } break; } case 'f': { type(FloatParam.class); parseMinMaxDefNumber(Double.class, second, third, fourth); break; } case 'x': { type(UniversalParam.class); break; } case 's': { type(StringParam.class); min(second); max(third); break; } case 'e': { type(EventParam.class); eventArgumentTypeName(second); break; } case 'l': { containedTypeName = second; if (third != null) { type(UniqueListParam.class); uid = third; } else { type(ArrayListParam.class); } break; } default: { log.error("unknown type: " + first); return this; } } return this; } public ParamBuilder eventArgumentTypeName(String eventArgumentTypeName) { this.eventArgumentTypeName = eventArgumentTypeName; return this; } @ParamAnnotation public ParamBuilder help(String help) { this.help = help; return this; } /** * @return the group */ @ParamAnnotation public Integer getGroup() { return group; } /** * @return the flags */ @ParamAnnotation public int getFlags() { return flags; } /** * @return the name */ @ParamAnnotation public String getName() { return name; } /** * @return the help */ @ParamAnnotation public String getHelp() { return help; } @ParamAnnotation public String getType() { return "?"; } @ParamAnnotation(id = "xtra") public int getExtra() { return extra; } /** * @return the paramType */ public Class getParamType() { return paramType; } @ParamAnnotation(id = "xtra") public ParamBuilder extra(int extra) { this.extra = extra; return this; } @ParamAnnotation public ParamBuilder min(Object min) { this.min = min; return this; } @ParamAnnotation public ParamBuilder max(Object max) { this.max = max; return this; } @ParamAnnotation public ParamBuilder def(Object def) { this.def = def; return this; } public Param build(String line) throws Exception { String[] paramEntryValues = line.split(","); if (paramEntryValues.length == 0) { log.warn("field empty or wrong format (" + line + ") - omitting"); return null; } for (int i = 0; i < paramEntryValues.length; ++i) { paramEntryValues[i] = paramEntryValues[i].trim(); } try { id(paramEntryValues[0]); group(Integer.valueOf(paramEntryValues[1])); flags(FlagsUtil.read(ParamFlags.class, paramEntryValues[2])); name(paramEntryValues[3]); type(paramEntryValues[4]); help(paramEntryValues[6]); } catch (IndexOutOfBoundsException e) { /** everything is ok, parameters have just finished*/ } catch (NumberFormatException ex) { log.warn("wrong format of entry: " + line + ", omitting"); return null; } return finish(); } public void setField(String key, String value) { switch (key) { case ID_FIELD: id(value); break; case NAME_FIELD: name(value); break; case TYPE_FIELD: type(value); break; case FLAGS_FIELD: flags(FlagsUtil.read(ParamFlags.class, value)); break; case HELP_FIELD: help(value); break; case GROUP_FIELD: group(Integer.valueOf(value)); break; default: log.error("unknown field for Param: " + key); break; } } public ParamBuilder fillDef(Object def) { if (this.def == null) { return def(def); } return this; } public ParamBuilder fillStorageType(Class storageType) { if (this.storageType == null) { this.storageType = storageType; } return this; } /** * @return the eventArgumentTypeName */ public String getEventArgumentTypeName() { return eventArgumentTypeName; } public Class getStorageType() { return storageType; } protected static ValueParam parseProcedureTypePart(String type, String name) { return Param.build().type(type).name(name).id(name).finish(ValueParam.class); } private static Pattern signaturePattern = Pattern.compile("^([^\\(]+)?\\(([^\\)]*)\\)$"); public ParamBuilder signature(String signature) { argumentsType = new ArrayList<>(); if (!Strings.notEmpty(signature)) { resultType = null; return this; } Matcher matcher = signaturePattern.matcher(signature); if (!matcher.matches()) { throw new FramsticksException().msg("invalid signature"); } String result = Strings.collapse(matcher.group(1)); if (result != null) { resultType = Param.build().type(result).finish(ValueParam.class); } else { resultType = null; } String arguments = matcher.group(2); if (!Strings.notEmpty(arguments)) { return this; } int number = 0; for (String a : arguments.split(",")) { ParamBuilder arg = Param.build(); int space = a.indexOf(' '); if (space == -1) { arg.type(a).id("arg" + number); } else { String name = a.substring(space + 1); arg.type(a.substring(0, space)).id(name).name(name); } argumentsType.add(arg.finish(ValueParam.class)); ++number; } return this; } public ParamBuilder idAndName(String name) { id(name); name(name); return this; } @Override public String toString() { return "ParamBuilder for " + Misc.returnNotNull(id, ""); } }