Demand background
Traditional business schemes create relationship tables based on business requirements, but they are aimed at fixed business structures, such as business record ID, creator, creation time, updater, update time, business related fields, etc. to expand fields here, you need to add fields to the corresponding business tables.
However, these fields are added based on fixed business, and users cannot customize their own panel data according to their own needs.
Of course, for simple scenarios, you can use mysql data to define a field of Json/longText type, and it is also possible to put a user-defined field on this field (to store well structured field data).
The following scheme is applied to complex scenes:
Requirements:
1. For different view Kanban, you can set whether to display different fields (the same data);
2. Sorting criteria can be applied to user-defined fields for sorting;
3. For the display order of all fields, you can drag custom field order;
4. Set different types of fields (text, number, date, hyperlink, picture, person, single choice, multiple choice, etc.);
5. Each record can be sorted by dragging;
6. Set the page display width for each field of each view.
7. Data can be synchronized to other data channels.
8. Field cannot create duplicate (name, field attribute)
For these changing requirements, the fixed table structure is not suitable for this demand scenario.
Practical scheme
For the above requirements, you can know:
1. A piece of data has different display methods (you can control whether the field is displayed)
2. There are many field types, and each field type requires different processing methods (sorting and formatting data)
3. The same data can be synchronized to other data channels (add fields that do not exist in the target data channel)
The traditional horizontal structure of fixed fields can not meet the complex needs of custom fields. You need to break up the data into a vertical table structure
Build related table structure:
1. Field table: field ID, field name, field type, field attribute (user defined, fixed, option [single multiple selection], format style [number, date])
2. View table: View ID, view name, view sort position, view type
3. View field association table: View ID, field ID, display or not, field order
4. Metadata table: field ID, record ID, field value (longText)
5. Channel record table: channel ID, record ID, record order, original channel ID (the channel to which the original data belongs) and whether to synchronize Association
Pseudo code
Related entity class
Field table DAO
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class DynamicField extends DynamicBaseEntity { /** * Primary key */ @TableId(type = IdType.AUTO) private Long id; /** * Field ID */ private String fieldId; /** * Field name */ private String name; /** * Field type * 1: Multiline text * 2: number * 3: Single choice * 4: Multiple choice * 5: date * 7: check box * 11: personnel * 15: Hyperlinks * 17: enclosure * 18: Unidirectional Association * #19: Find reference * #20: formula * #21: Bidirectional association * 22: comment * 1001: Creation time * 1002: Last update time * 1003: Founder * 1004: Modified by * 1005: Marking time */ @Builder.Default private int fieldType = 1; /** * Fixed field * Fixed field: title field; cannot be moved or modified */ private boolean isFixed; /** * Custom: used to distinguish default fields */ private boolean isCustom; /** * Field properties */ @TableField(typeHandler = FieldPropertyHandler.class) private FieldProperty property; /** * Field verification isolation level * Space ID */ private String spaceId; }
View table DAO
@Data @TableName(autoResultMap = true) public class DynamicView extends DynamicBaseEntity { /** * View ID */ private String id; /** * View name */ private String name; /** * View type */ private ViewTypeEnum viewType; /** * Space ID */ private String spaceId; /** * Channel ID */ private String channelId; /** * sort */ private Integer indexKey; /** * View page properties: * */ @TableField(typeHandler = ViewPropertyHandler.class) private ViewProperty viewProperty = new ViewProperty(); /** * Sorting rules */ @TableField(typeHandler = ListSortHandler.class, javaType = true) private List<DynamicSort> sortList; /** * Filter rule */ @TableField(typeHandler = ListConditionHandler.class, javaType = true) private List<DynamicCondition> conditions; /** * Eligible rules * Default: {@ link RuleEnum#ALL} */ private RuleEnum rule = RuleEnum.ALL; /** * Grouping rules */ @TableField(typeHandler = ListSortHandler.class, javaType = true) private List<DynamicSort> groupList; public boolean isNull() { if (StringUtils.isEmpty(this.id)) { return true; } return false; } }
Metadata table DAO
@Data public class MetaDataTable { /** * Data ID */ @TableId(type = IdType.AUTO) private Long id; /** * Data record ID */ private String recordId; /** * Field ID */ private String fieldId; /** * Field value: Jason */ private String value; }
View field association table DAO
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class ViewFieldRelation extends DynamicBaseEntity { /** * Associated data ID */ @TableId(type = IdType.AUTO) private Long id; /** * Field ID */ private String fieldId; /** * Space ID */ private String spaceId; /** * Channel ID */ private String channelId; /** * View ID */ private String viewId; /** * Show */ @Builder.Default private boolean isShow = true; /** * Field display order */ private int indexKey; /** * Field typesetting length */ private Integer fieldLength; }
Record channel association table DAO
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class ChannelRecordRelation { /** * Associated data ID */ @TableId(type = IdType.AUTO) private Long id; /** * Channel ID */ private String channelId; /** * Data record ID */ private String recordId; /** * Channel ID of the original data record */ private String originId; /** * Is it shared data * Default: false */ private boolean isShare; /** * Channel data display sequence */ private int indexKey; /** * Field verification isolation level * Space ID */ private String spaceId; @JsonFormat(locale="zh", timezone="GMT+8") private Date createdAt; }
Related classes
Dynamic filter criteria class
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class DynamicCondition { /** * Filter field ID */ private String fieldId; /** * Matching rules */ private MatchRuleEnum matchRule; /** * Match value */ private Set<String> matchValue; }
Dynamic sorting condition class
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class DynamicSort { /** * Sort field ID */ private String fieldId; /** * Sorting rules */ private SortEnum sort; /** * Fixed by default, not displayed */ private boolean isFixed; }
Field attribute class
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class FieldProperty { /** * Field Icon */ private String icon; /** * Number formatting: Generic */ private NumberTypeEnum formatter; /** * Date formatting: Generic */ private DateTypeEnum dateFormatter; /** * Allow adding multiple members */ private Boolean addMore; /** * Single or multiple choice: option attribute */ private List<Option> options; }
Option class
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Option { /** * Option ID */ private String id; /** * Option value */ private String value; /** * Option color: color number */ private String color; }
Hyperlink class
@Data public class HyperlinkEntity { /** * link */ private String link; /** * Link value */ private String linkValue; }
View property class
@Data @Builder @AllArgsConstructor @NoArgsConstructor public class ViewProperty { /** * Row height: * 0-Low, default * 1-secondary * 2-high * 3-super high */ private int rowHeight; /** * Cover: * Binding attachment field ID */ private String cover; /** * Cover effect: * 0-nothing * 1-Tailoring; * 2-adapt */ private int coverEffect; /** * Display mode * 1-routine * 2-compact */ @Builder.Default private int displayMode = 1; /** * Display field name: default display */ private boolean isShowFieldName = true; /** * Color display: user defined; no by default */ private boolean isCustom; /** * Color: color number */ private String color; /** * Field ID and options selected when following field color * Format: filedid & & ID */ private String colorId; }
Related enumeration
Date enumeration class
public enum DateTypeEnum { /** * Mm / DD / yy */ DATE_ONE("yyyy/MM/dd"), /** * Year / month / day hour: minute */ DATE_TWO("yyyy/MM/dd HH:mm"), /** * Year month day */ DATE_THREE("yyyy-MM-dd"), /** * Year month day hour: minute */ DATE_FOUR("yyyy-MM-dd HH:mm"), /** * Month day */ DATE_FIVE("MM-dd"), /** * Month / day / year */ DATE_SIX("MM/dd/yyyy"), /** * Day / month / year */ DATE_SEVEN("dd/MM/yyyy"), ; private String format; DateTypeEnum(String format) { this.format = format; } public String getFormat() { return this.format; } }
Numeric format enumeration class
public enum NumberTypeEnum { /** * integer */ INTEGER("0"), /** * Keep 1 decimal place */ KEEP_ONE("0.0"), /** * Keep 2 decimal places */ KEEP_TWO("0.00"), /** * Keep 3 decimal places */ KEEP_THREE("0.000"), /** * Keep 4 decimal places */ KEEP_FOUR("0.0000"), /** * Thousandth */ THOUSANDTH("###,###"), /** * Thousandth (two decimal places are reserved) */ THOUSANDTH_KEEP_TWO("###,###.00"), /** * percentage */ PERCENTAGE("###,###%"), /** * Percentage (two decimal places) */ PERCENTAGE_KEEP_TWO("###,###.00%"), /** * RMB */ RMB("¥###,###"), /** * RMB (with two decimal places) */ RMB_KEEP_TWO("¥###,###.00"), /** * dollar */ DOLLAR("$###,###"), /** * USD (with two decimal places) */ DOLLAR_KEEP_TWO("$###,###.00"), ; private String format; NumberTypeEnum(String format) { this.format = format; } public String getFormat() { return this.format; } }
Filter matching enumeration classes
public enum MatchRuleEnum { /** * be equal to * Applicable field types: single choice, multiple choice, text, person, date, number, check box, hyperlink */ EQUAL, /** * Not equal to * Applicable field types: single choice, multiple choice, text, person, date, number, hyperlink */ NOT_EQUAL, /** * greater than * Applicable field types: date, number */ THAN, /** * Greater than or equal to * Applicable field type: number */ THAN_EQUAL, /** * less than * Applicable field types: date, number */ LESS, /** * Less than or equal to * Applicable field type: number */ LESS_EQUAL, /** * contain * Applicable field types: single choice, multiple choice, text, person, hyperlink */ CONTAIN, /** * Not included * Applicable field types: single choice, multiple choice, text, person, hyperlink */ NOT_CONTAIN, /** * Empty * Applicable field types: single choice, multiple choice, text, person, date, attachment, number, hyperlink */ EMPTY, /** * Not empty * Applicable field types: single choice, multiple choice, text, person, date, attachment, number, hyperlink */ NOT_EMPTY, ; }
Filter criteria application rule enumeration class
public enum RuleEnum { /** * All */ ALL, /** * any */ ANY ; }
Sort enumeration class
public enum SortEnum { /** * positive sequence */ ASC, /** * Reverse order */ DESC ; }
View type enumeration class
public enum ViewTypeEnum { /** * List view */ LIST("list"), /** * Kanban view */ BOARD("bulletin board"), ; private String viewName; ViewTypeEnum(String viewName) { this.viewName = viewName; } public String getViewName() { return this.viewName; } }
Custom sort class
@Data @NoArgsConstructor @AllArgsConstructor public class RecordCompareVo implements Comparator<RecordVo> { /** * view */ private List<DynamicSort> sorts; @Override public int compare(RecordVo o1, RecordVo o2) { if (CollectionUtils.isEmpty(sorts)) { sorts = new ArrayList<>(); } //Default sort DynamicSort indexKey = new DynamicSort(); indexKey.setFieldId("indexKey"); indexKey.setSort(SortEnum.ASC); sorts.add(indexKey); //Default sort DynamicSort createdAt = new DynamicSort(); createdAt.setFieldId("createdAt"); createdAt.setSort(SortEnum.ASC); sorts.add(createdAt); Map<String, MetaDataTableVo> o1Maps = new HashMap<>(); Map<String, MetaDataTableVo> o2Maps = new HashMap<>(); List<MetaDataTableVo> o1List = o1.getData(); List<MetaDataTableVo> o2List = o2.getData(); if (CollectionUtils.isNotEmpty(o1List)) { o1Maps.putAll(o1List.stream().collect(Collectors.toMap(MetaDataTableVo::getFieldId, m -> m))); } if (CollectionUtils.isNotEmpty(o2List)) { o2Maps.putAll(o2List.stream().collect(Collectors.toMap(MetaDataTableVo::getFieldId, m -> m))); } AtomicInteger cr = new AtomicInteger(0); if (CollectionUtils.isNotEmpty(sorts)) { sorts.forEach(dynamicSort -> { if (Objects.equals(cr.get(), 0)) { if (Objects.equals(dynamicSort.getFieldId(), "indexKey")) { int sort = o1.getIndexKey() - o2.getIndexKey(); cr.set(Integer.compare(sort, 0)); return; } if (Objects.equals(dynamicSort.getFieldId(), "createdAt")) { int sort = o1.getCreateAt().compareTo(o2.getCreateAt()); cr.set(Integer.compare(sort, 0)); return; } if (!o1Maps.containsKey(dynamicSort.getFieldId()) && !o2Maps.containsKey(dynamicSort.getFieldId())) { return; } if (!o1Maps.containsKey(dynamicSort.getFieldId())) { cr.set(1); return; } if (!o2Maps.containsKey(dynamicSort.getFieldId())) { cr.set(-1); return; } String o1Value = o1Maps.get(dynamicSort.getFieldId()).getCompareValue(); String o2Value = o2Maps.get(dynamicSort.getFieldId()).getCompareValue(); if (StringUtils.isEmpty(o1Value) && StringUtils.isEmpty(o2Value)) { cr.set(0); } else if (StringUtils.isEmpty(o1Value)) { cr.set(Objects.equals(dynamicSort.getSort(), SortEnum.ASC) ? -1 : 1); } else if (StringUtils.isEmpty(o2Value)) { cr.set(Objects.equals(dynamicSort.getSort(), SortEnum.ASC) ? 1 : -1); } else { //time if (Objects.equals(o1Maps.get(dynamicSort.getFieldId()).getFieldType(), 5)) { o1Value = o1Value.replace("-", ""); o2Value = o2Value.replace("-", ""); } int sortValue = Objects.equals(dynamicSort.getSort(), SortEnum.ASC) ? o1Value.compareTo(o2Value) : o2Value.compareTo(o1Value); cr.set(Integer.compare(sortValue, 0)); } } }); } return cr.get(); } }
Building records: filtering and sorting
public List<RecordVo> buildResults(String spaceId, MetaDataBo bo, DynamicView dynamicView, List<ViewFieldRelationVo> viewFieldRelationVos, List<Map<String, Object>> metaDatas) { //return recording List<RecordVo> recordVoList = new ArrayList<>(); //sort criteria List<DynamicSort> sorts = dynamicView.getSortList(); Map<String, DynamicSort> sortMaps = new HashMap<>(); if (CollectionUtils.isNotEmpty(sorts)) { sortMaps.putAll(sorts.stream().collect(Collectors.toMap(DynamicSort::getFieldId, s -> s))); } if (CollectionUtils.isNotEmpty(metaDatas)) { //Group field data Map<String, List<Map<String, Object>>> map = metaDatas.stream().collect(Collectors.groupingBy(metaData -> (String) metaData.get("recordId"))); //Screening conditions List<DynamicCondition> conditions = new ArrayList<>(); //Add Title filter if (StringUtils.isNotEmpty(bo.getTitle())) { DynamicCondition title = new DynamicCondition(); title.setFieldId(MultiTableConstant.TITLE_FIELD_ID); title.setMatchRule(MatchRuleEnum.CONTAIN); title.setMatchValue(Set.of(bo.getTitle())); conditions.add(title); } Map<String, List<DynamicCondition>> conditionMaps = new HashMap<>(); //Judge whether the filter conditions are met if (CollectionUtils.isNotEmpty(dynamicView.getConditions())) { conditions.addAll(dynamicView.getConditions()); } if (CollectionUtils.isNotEmpty(conditions)) { conditionMaps.putAll(conditions.stream().collect(Collectors.groupingBy(DynamicCondition::getFieldId))); } map.forEach((recordId, metaDataTables) -> { AtomicInteger conditionCount = new AtomicInteger(conditions.size()); Set<Boolean> conditionSet = new HashSet<>(); RecordVo recordVo = new RecordVo(); List<MetaDataTableVo> metaDataTableVos = new ArrayList<>(); recordVo.setRecordId(recordId); AtomicBoolean first = new AtomicBoolean(true); //A single piece of data and each field data already exist Map<String, Map<String, Object>> fieldMaps = metaDataTables.stream().collect(Collectors.toMap(field -> (String) field.get("fieldId"), m -> m)); //Traverse view fields viewFieldRelationVos.forEach(vf -> { //data Map<String, Object> m = new HashMap<>(); if (Objects.nonNull(fieldMaps.get(vf.getFieldId()))) { m.putAll(fieldMaps.get(vf.getFieldId())); } //Get group if (Objects.equals(vf.getFieldId(), MultiTableConstant.PHASE_FIELD_ID)) { JSONArray.parseArray((String) m.get("value"), String.class).forEach(phaseId -> { String id = phaseId.split("&&")[0]; String channelId = phaseId.split("&&")[1]; if (Objects.equals(channelId, dynamicView.getChannelId())) { recordVo.setPhaseId(id); } }); } //Build field MetaDataTableVo value = new MetaDataTableVo(); value.setFieldId(vf.getFieldId()); value.setFieldLength(vf.getFieldLength()); value.setRecordId(recordId); value.setFieldType(vf.getFieldType()); //Field type matching settings switch (vf.getFieldType()) { //number case 2: if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : StringUtils.EMPTY, 2)); conditionCount.decrementAndGet(); }); } if (StringUtils.isNotEmpty((String) m.get("value"))) { //Number formatting DecimalFormat df = new DecimalFormat(); df.applyPattern(vf.getProperty().getFormatter().getFormat()); value.setValue(df.format(Double.valueOf((String) m.get("value")))); value.setCompareValue((String) m.get("value")); } break; //Single choice case 3: if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, Set.of(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : StringUtils.EMPTY), 3)); conditionCount.decrementAndGet(); }); } if (StringUtils.isNotEmpty((String) m.get("value"))) { value.setValue(m.get("value")); if (Objects.equals(vf.getFieldId(), MultiTableConstant.FINISH_STATUS)) { recordVo.setState(Integer.parseInt((String) m.get("value"))); } if (Objects.equals(vf.getFieldId(), MultiTableConstant.RECORD_STATE)) { recordVo.setRecordState(Integer.parseInt((String) m.get("value"))); } if (sortMaps.containsKey(vf.getFieldId()) && Objects.nonNull(vf.getProperty()) && Objects.nonNull(vf.getProperty().getOptions())) { Map<String, Option> options = vf.getProperty().getOptions().stream().collect(Collectors.toMap(Option::getId, o -> o)); Option option = options.get((String) m.get("value")); value.setCompareValue(Objects.nonNull(option) ? option.getValue() : StringUtils.EMPTY); } } break; //Multiple choice case 4: if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, Set.copyOf(JSONArray.parseArray(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : "[]", String.class)), 4)); conditionCount.decrementAndGet(); }); } if (StringUtils.isNotEmpty((String) m.get("value"))) { List<String> ids = JSONArray.parseArray((String) m.get("value"), String.class); value.setValue(ids); if (sortMaps.containsKey(vf.getFieldId()) && Objects.nonNull(vf.getProperty()) && Objects.nonNull(vf.getProperty().getOptions())) { Map<String, Option> options = vf.getProperty().getOptions().stream().collect(Collectors.toMap(Option::getId, o -> o)); StringBuilder sb = new StringBuilder(); ids.forEach(id -> { if (options.containsKey(id)) { sb.append(options.get(id)); } }); value.setCompareValue(sb.toString()); } } break; //date case 5: if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, Set.of(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : "0"), 5)); conditionCount.decrementAndGet(); }); } if (StringUtils.isNotEmpty((String) m.get("value"))) { value.setValue(m.get("value")); value.setCompareValue((String) m.get("value")); } else { value.setValue("0"); } break; //personnel case 11: if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, Set.copyOf(JSONArray.parseArray(StringUtils.isNotEmpty((String) m.get("value")) ? (String) m.get("value") : "[]")), 11)); conditionCount.decrementAndGet(); }); } if (StringUtils.isNotEmpty((String) m.get("value"))) { List<UserVO> userVOList = new ArrayList<>(); List<String> userIds = JSONArray.parseArray((String) m.get("value"), String.class); StringBuilder sb = new StringBuilder(); userIds.forEach(userId -> { if (userVOMaps.containsKey(userId)) { userVOList.add(userVOMaps.get(userId)); sb.append(userVOMaps.get(userId).getNickName()); } }); value.setValue(userVOList); value.setCompareValue(sb.toString()); } break; //Hyperlinks case 15: if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { String linkValue = ""; if (StringUtils.isNotEmpty((String) m.get("value"))) { linkValue = JSONObject.parseObject((String) m.get("value"), HyperlinkEntity.class).getLinkValue(); } String finalLinkValue = linkValue; conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, Set.of(finalLinkValue), 15)); conditionCount.decrementAndGet(); }); } if (StringUtils.isNotEmpty((String) m.get("value"))) { HyperlinkEntity hyperlink = JSONObject.parseObject((String) m.get("value"), HyperlinkEntity.class); value.setValue(hyperlink); value.setCompareValue(hyperlink.getLinkValue()); } break; //enclosure case 17: if (StringUtils.isNotEmpty((String) m.get("value"))) { if (spaceFileMaps.containsKey((String) m.get("value"))) { List<DemandFileExtendBO> bos = spaceFileMaps.get((String) m.get("value")); value.setValue(bos); StringBuilder sb = new StringBuilder(); if (sortMaps.containsKey(vf.getFieldId()) && CollectionUtils.isNotEmpty(bos)) { bos.forEach(fileExtendBO -> { sb.append(fileExtendBO.getName()); }); } value.setCompareValue(sb.toString()); } } if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, value.getCompareValue(), 17)); conditionCount.decrementAndGet(); }); } break; default: value.setCompareValue((String) m.get("value")); value.setValue(m.get("value")); if (CollectionUtils.isNotEmpty(conditionMaps.get(vf.getFieldId()))) { conditionMaps.get(vf.getFieldId()).forEach(condition -> { conditionSet.add(checkComparion(condition, Set.of(Optional.ofNullable(m.get("value")).orElse(StringUtils.EMPTY)), 1)); conditionCount.decrementAndGet(); }); } } metaDataTableVos.add(value); }); if (Objects.equals(dynamicView.getRule(), RuleEnum.ALL) && Objects.equals(conditionCount.get(), 0) && !conditionSet.contains(false)) { recordVo.setData(metaDataTableVos); recordVoList.add(recordVo); } else if (Objects.equals(dynamicView.getRule(), RuleEnum.ANY) && conditionCount.get() < conditions.size() && conditionSet.contains(true)) { recordVo.setData(metaDataTableVos); recordVoList.add(recordVo); } }); //sort Collections.sort(recordVoList, new RecordCompareVo(dynamicView.getSortList())); } return recordVoList; }
Screening processing method
/** * Filter matching * * @param condition * @param value * @param fieldType * @param <T> * @return */ private <T> Boolean checkComparion(DynamicCondition condition, T value, int fieldType) { Set<String> matchValue = condition.getMatchValue(); AtomicBoolean result = new AtomicBoolean(true); if (Objects.isNull(condition)) { return result.get(); } switch (condition.getMatchRule()) { //equal case EQUAL: if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) { result.set(matchValue.containsAll((Set) value) && matchValue.size() == ((Set) value).size()); } if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) { matchValue.forEach(mv -> { //time if (Objects.equals(fieldType, 5)) { Long d1 = Math.abs(Long.valueOf(mv)); Long d2 = Math.abs(Long.valueOf((String) value)); result.set((d2 - d1) == 0); } else if (Objects.equals(fieldType, 2)) { if (StringUtils.isNotEmpty((String) value)) { result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) == 0); } else { result.set(false); } } }); } break; //Not equal to case NOT_EQUAL: if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) { result.set(!((Set<?>) value).containsAll(matchValue)); } if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) { matchValue.forEach(mv -> { //time if (Objects.equals(fieldType, 5)) { Long d1 = Math.abs(Long.valueOf(mv)); Long d2 = Math.abs(Long.valueOf((String) value)); result.set((d2 - d1) != 0); } //number if (Objects.equals(fieldType, 2)) { if (StringUtils.isNotEmpty((String) value)) { result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) != 0); } else { result.set(((String) value).compareTo(mv) != 0); } } }); } break; //greater than case THAN: if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) { matchValue.forEach(mv -> { //time if (Objects.equals(fieldType, 5)) { Long d1 = Math.abs(Long.valueOf(mv)); Long d2 = Math.abs(Long.valueOf((String) value)); result.set((d2 - d1) > 0); } //number if (Objects.equals(fieldType, 2)) { if (StringUtils.isNotEmpty((String) value)) { result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) > 0); } else { result.set(false); } } }); } break; //Greater than or equal to case THAN_EQUAL: if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) { matchValue.forEach(mv -> { if (StringUtils.isNotEmpty((String) value)) { result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) >= 0); } else { result.set(false); } }); } break; //less than case LESS: if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) { matchValue.forEach(mv -> { //time if (Objects.equals(fieldType, 5)) { Long d1 = Math.abs(Long.valueOf(mv)); Long d2 = Math.abs(Long.valueOf((String) value)); result.set((d2 - d1) < 0); } //number if (Objects.equals(fieldType, 2)) { if (StringUtils.isNotEmpty((String) value)) { result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) < 0); } else { result.set(false); } } }); } break; //Less than or equal to case LESS_EQUAL: if ((value instanceof String) && CollectionUtils.isNotEmpty(matchValue)) { matchValue.forEach(mv -> { //number if (StringUtils.isNotEmpty((String) value)) { result.set((Double.valueOf((String) value)).compareTo((Double.valueOf(mv))) <= 0); } else { result.set(false); } }); } break; //contain case CONTAIN: if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) { AtomicBoolean skip = new AtomicBoolean(false); matchValue.forEach(mv -> { if (!skip.get()) { skip.set(((Set<?>) value).contains(mv)); } }); result.set(skip.get()); } break; //Not included case NOT_CONTAIN: if ((value instanceof Set) && CollectionUtils.isNotEmpty(matchValue)) { AtomicBoolean skip = new AtomicBoolean(false); matchValue.forEach(mv -> { if (!skip.get()) { skip.set(!((Set<?>) value).contains(mv)); } }); result.set(skip.get()); } break; //Empty case EMPTY: //time if (Objects.equals(fieldType, 5)) { result.set(Long.valueOf((String) value) == 0); } else if (Objects.nonNull(value)) { if ((value instanceof String) && StringUtils.isNotEmpty((String) value)) { result.set(false); } else { result.set(false); } } break; //Not empty case NOT_EMPTY: //time if (Objects.equals(fieldType, 5)) { result.set(Long.valueOf((String) value) != 0); } else if (Objects.isNull(value)) { result.set(false); } break; default: } return result.get(); }