001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.relation;
017
018import com.mybatisflex.core.exception.FlexExceptions;
019import com.mybatisflex.core.query.QueryWrapper;
020import com.mybatisflex.core.row.Row;
021import com.mybatisflex.core.util.*;
022
023import java.lang.reflect.Field;
024import java.util.*;
025
026public class ToManyRelation<SelfEntity> extends AbstractRelation<SelfEntity> {
027
028    protected String mapKeyField;
029
030    protected FieldWrapper mapKeyFieldWrapper;
031
032    protected String orderBy;
033
034    protected long limit = 0;
035
036    protected String selfValueSplitBy;
037
038
039    public ToManyRelation(String selfField, String targetSchema, String targetTable, String targetField, String valueField,
040                          String joinTable, String joinSelfColumn, String joinTargetColumn,
041                          String dataSource, Class<SelfEntity> selfEntityClass, Field relationField,
042                          String extraCondition, String[] selectColumns) {
043        super(selfField, targetSchema, targetTable, targetField, valueField,
044            joinTable, joinSelfColumn, joinTargetColumn,
045            dataSource, selfEntityClass, relationField,
046            extraCondition, selectColumns
047        );
048    }
049
050    public static Class<? extends Map> getMapWrapType(Class<?> type) {
051        if (ClassUtil.canInstance(type.getModifiers())) {
052            return (Class<? extends Map>) type;
053        }
054
055        return HashMap.class;
056    }
057
058    /**
059     * 构建查询目标对象的 QueryWrapper
060     *
061     * @param targetValues 条件的值
062     * @return QueryWrapper
063     */
064    @Override
065    public QueryWrapper buildQueryWrapper(Set<Object> targetValues) {
066        if (StringUtil.hasText(selfValueSplitBy) && CollectionUtil.isNotEmpty(targetValues)) {
067            Set<Object> newTargetValues = new HashSet<>();
068            for (Object targetValue : targetValues) {
069                if (targetValue == null) {
070                    continue;
071                }
072                if (!(targetValue instanceof String)) {
073                    throw FlexExceptions.wrap("split field only support String type, but current type is: \"" + targetValue.getClass().getName() + "\"");
074                }
075                String[] splitValues = ((String) targetValue).split(selfValueSplitBy);
076                for (String splitValue : splitValues) {
077                    //优化分割后的数据类型(防止在数据库查询时候出现隐式转换)
078                    newTargetValues.add(ConvertUtil.convert(splitValue, targetFieldWrapper.getFieldType()));
079                }
080            }
081            targetValues = newTargetValues;
082        }
083        return super.buildQueryWrapper(targetValues);
084    }
085
086    @Override
087    public void customizeQueryWrapper(QueryWrapper queryWrapper) {
088        if (StringUtil.hasText(orderBy)) {
089            queryWrapper.orderBy(orderBy);
090        }
091
092        if (limit > 0) {
093            queryWrapper.limit(limit);
094        }
095    }
096
097    @SuppressWarnings("rawtypes")
098    @Override
099    public void join(List<SelfEntity> selfEntities, List<?> targetObjectList, List<Row> mappingRows) {
100
101        //目标表关联字段->目标表对象
102        Map<String, List<Object>> leftFieldToRightTableMap = new HashMap<>(targetObjectList.size());
103        for (Object targetObject : targetObjectList) {
104            Object targetJoinFieldValue = targetFieldWrapper.get(targetObject);
105            if (targetJoinFieldValue != null) {
106                leftFieldToRightTableMap.computeIfAbsent(targetJoinFieldValue.toString(), k -> new ArrayList<>(1)).add(targetObject);
107            }
108        }
109
110        //通过中间表
111        if (mappingRows != null) {
112            //当使用中间表时,需要重新映射关联关系
113            Map<String, List<Object>> temp = new HashMap<>(selfEntities.size());
114            for (Row mappingRow : mappingRows) {
115                Object midTableJoinSelfValue = mappingRow.getIgnoreCase(joinSelfColumn);
116                if (midTableJoinSelfValue == null) {
117                    continue;
118                }
119                Object midTableJoinTargetValue = mappingRow.getIgnoreCase(joinTargetColumn);
120                if (midTableJoinTargetValue == null) {
121                    continue;
122                }
123                List<Object> targetObjects = leftFieldToRightTableMap.get(midTableJoinTargetValue.toString());
124                if (targetObjects == null) {
125                    continue;
126                }
127                temp.computeIfAbsent(midTableJoinSelfValue.toString(), k -> new ArrayList<>(targetObjects.size())).addAll(targetObjects);
128            }
129            leftFieldToRightTableMap = temp;
130        }
131
132
133        //关联集合的类型
134        Class<?> fieldType = relationFieldWrapper.getFieldType();
135        boolean isMapType = Map.class.isAssignableFrom(fieldType);
136        Class<?> wrapType = isMapType ? getMapWrapType(fieldType) : MapperUtil.getCollectionWrapType(fieldType);
137        boolean splitMode = StringUtil.hasText(selfValueSplitBy);
138
139        for (SelfEntity selfEntity : selfEntities) {
140            if (selfEntity == null) {
141                continue;
142            }
143            Object selfValue = selfFieldWrapper.get(selfEntity);
144            if (selfValue == null) {
145                continue;
146            }
147            selfValue = selfValue.toString();
148
149            //只有当splitBy不为空时才会有多个值
150            Set<String> targetMappingValues;
151
152            if (splitMode) {
153                String[] splitValues = ((String) selfValue).split(selfValueSplitBy);
154                targetMappingValues = new LinkedHashSet<>(Arrays.asList(splitValues));
155            } else {
156                targetMappingValues = new HashSet<>(1);
157                targetMappingValues.add((String) selfValue);
158            }
159
160            if (targetMappingValues.isEmpty()) {
161                return;
162            }
163
164            // map
165            if (isMapType) {
166                Map map = (Map) ClassUtil.newInstance(wrapType);
167                Set<Object> validateCountSet = new HashSet<>(targetMappingValues.size());
168                for (String targetMappingValue : targetMappingValues) {
169                    List<Object> targetObjects = leftFieldToRightTableMap.get(targetMappingValue);
170                    //如果非真实外键约束 可能没有对应的对象
171                    if (targetObjects == null) {
172                        continue;
173                    }
174                    for (Object targetObject : targetObjects) {
175                        Object keyValue = mapKeyFieldWrapper.get(targetObject);
176                        Object needKeyValue = ConvertUtil.convert(keyValue, relationFieldWrapper.getKeyType());
177                        if (validateCountSet.contains(needKeyValue)) {
178                            //当字段类型为Map时,一个key对应的value只能有一个
179                            throw FlexExceptions.wrap("When fieldType is Map, the target entity can only be one,\n" +
180                                " current entity type is : " + selfEntity + "\n" +
181                                " relation field name is : " + relationField.getName() + "\n" +
182                                " target entity is : " + targetObjects);
183                        }
184                        validateCountSet.add(needKeyValue);
185
186                        //noinspection unchecked
187                        map.put(needKeyValue, targetObject);
188                    }
189                }
190                if (!map.isEmpty()) {
191                    relationFieldWrapper.set(map, selfEntity);
192                }
193
194            }
195            //集合
196            else {
197                Collection collection = (Collection) ClassUtil.newInstance(wrapType);
198                if (onlyQueryValueField) {
199                    Object first = targetObjectList.iterator().next();
200                    //将getter方法用单独的变量存储 FieldWrapper.of虽然有缓存 但每次调用至少有一个HashMap的get开销
201                    FieldWrapper fieldValueFieldWrapper = FieldWrapper.of(first.getClass(), valueField);
202                    for (String targetMappingValue : targetMappingValues) {
203                        List<Object> targetObjects = leftFieldToRightTableMap.get(targetMappingValue);
204                        if (targetObjects == null) {
205                            continue;
206                        }
207                        for (Object targetObject : targetObjects) {
208                            //仅绑定某个字段
209                            //noinspection unchecked
210                            collection.add(fieldValueFieldWrapper.get(targetObject));
211                        }
212                    }
213                } else {
214                    for (String targetMappingValue : targetMappingValues) {
215                        List<Object> targetObjects = leftFieldToRightTableMap.get(targetMappingValue);
216                        if (targetObjects == null) {
217                            continue;
218                        }
219                        //noinspection unchecked
220                        collection.addAll(targetObjects);
221                    }
222                }
223
224                relationFieldWrapper.set(collection, selfEntity);
225            }
226        }
227
228    }
229
230    public void setMapKeyField(String mapKeyField) {
231        this.mapKeyField = mapKeyField;
232        if (StringUtil.hasText(mapKeyField)) {
233            this.mapKeyFieldWrapper = FieldWrapper.of(targetEntityClass, mapKeyField);
234        } else {
235            if (Map.class.isAssignableFrom(relationFieldWrapper.getFieldType())) {
236                throw FlexExceptions.wrap("Please config mapKeyField for map field: " + relationFieldWrapper.getField());
237            }
238        }
239    }
240
241}