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.row;
017
018import com.mybatisflex.core.table.TableInfo;
019import com.mybatisflex.core.table.TableInfoFactory;
020import com.mybatisflex.core.util.ClassUtil;
021import com.mybatisflex.core.util.ConvertUtil;
022import com.mybatisflex.core.util.StringUtil;
023import com.mybatisflex.core.util.MapUtil;
024
025import java.lang.reflect.Method;
026import java.lang.reflect.Modifier;
027import java.util.*;
028import java.util.concurrent.ConcurrentHashMap;
029
030public class RowUtil {
031
032    private RowUtil() {
033    }
034
035    static final String INDEX_SEPARATOR = "$";
036
037    private static final Map<Class<?>, Map<String, Method>> classSettersCache = new ConcurrentHashMap<>();
038
039    public static <T> T toObject(Row row, Class<T> objectClass) {
040        return toObject(row, objectClass, 0);
041    }
042
043
044    public static <T> T toObject(Row row, Class<T> objectClass, int index) {
045        T instance = ClassUtil.newInstance(objectClass);
046        Map<String, Method> classSetters = getSetterMethods(objectClass);
047        Set<String> rowKeys = row.keySet();
048        classSetters.forEach((property, setter) -> {
049            try {
050                if (index <= 0) {
051                    for (String rowKey : rowKeys) {
052                        if (property.equalsIgnoreCase(rowKey)) {
053                            Object rowValue = row.get(rowKey);
054                            Object value = ConvertUtil.convert(rowValue, setter.getParameterTypes()[0], true);
055                            setter.invoke(instance, value);
056                        }
057                    }
058                } else {
059                    for (int i = index; i >= 0; i--) {
060                        String newProperty = i <= 0 ? property : property + INDEX_SEPARATOR + i;
061                        boolean fillValue = false;
062                        for (String rowKey : rowKeys) {
063                            if (newProperty.equalsIgnoreCase(rowKey)) {
064                                Object rowValue = row.get(rowKey);
065                                Object value = ConvertUtil.convert(rowValue, setter.getParameterTypes()[0], true);
066                                setter.invoke(instance, value);
067                                fillValue = true;
068                                break;
069                            }
070                        }
071                        if (fillValue) {
072                            break;
073                        }
074                    }
075                }
076            } catch (Exception e) {
077                throw new RuntimeException("Can not invoke method: " + setter);
078            }
079        });
080        return instance;
081    }
082
083
084    public static <T> List<T> toObjectList(List<Row> rows, Class<T> objectClass) {
085        return toObjectList(rows, objectClass, 0);
086    }
087
088
089    public static <T> List<T> toObjectList(List<Row> rows, Class<T> objectClass, int index) {
090        if (rows == null || rows.isEmpty()) {
091            return Collections.emptyList();
092        } else {
093            List<T> objectList = new ArrayList<>();
094            for (Row row : rows) {
095                objectList.add(toObject(row, objectClass, index));
096            }
097            return objectList;
098        }
099    }
100
101
102    public static <T> T toEntity(Row row, Class<T> entityClass) {
103        return toEntity(row, entityClass, 0);
104    }
105
106
107    public static <T> T toEntity(Row row, Class<T> entityClass, int index) {
108        TableInfo tableInfo = TableInfoFactory.ofEntityClass(entityClass);
109        return tableInfo.newInstanceByRow(row, index);
110    }
111
112
113    public static <T> List<T> toEntityList(List<Row> rows, Class<T> entityClass) {
114        return toEntityList(rows, entityClass, 0);
115    }
116
117
118    public static <T> List<T> toEntityList(List<Row> rows, Class<T> entityClass, int index) {
119        if (rows == null || rows.isEmpty()) {
120            return Collections.emptyList();
121        } else {
122            TableInfo tableInfo = TableInfoFactory.ofEntityClass(entityClass);
123            List<T> entityList = new ArrayList<>();
124            for (Row row : rows) {
125                T entity = tableInfo.newInstanceByRow(row, index);
126                entityList.add(entity);
127            }
128            return entityList;
129        }
130    }
131
132
133    public static void registerMapping(Class<?> clazz, Map<String, Method> columnSetterMapping) {
134        classSettersCache.put(clazz, columnSetterMapping);
135    }
136
137
138    public static void printPretty(Row row) {
139        if (row == null) {
140            return;
141        }
142        printPretty(Collections.singletonList(row));
143    }
144
145
146    public static void printPretty(List<Row> rows) {
147        if (rows == null || rows.isEmpty()) {
148            return;
149        }
150
151        Row firstRow = rows.get(0);
152        List<Integer> textConsoleLengthList = new ArrayList<>();
153        StringBuilder sb = new StringBuilder("\nTotal Count: " + rows.size() + "\n");
154        Set<String> keys = firstRow.keySet();
155        keys.forEach(s -> {
156            String sa = "|" + s + "     ";
157            sb.append(sa);
158            textConsoleLengthList.add(calcTextConsoleLength(sa));
159        });
160        sb.append("|\n");
161
162        rows.forEach(row -> {
163            int i = 0;
164            for (String key : keys) {
165                sb.append(getColString(row.get(key), textConsoleLengthList.get(i)));
166                i++;
167            }
168            sb.append("|\n");
169        });
170
171        System.out.println(sb);
172    }
173
174
175    private static String getColString(Object o, int len) {
176        String v = "|" + o;
177        while (calcTextConsoleLength(v) < len) {
178            v += " ";
179        }
180
181        while (calcTextConsoleLength(v) > len) {
182            v = v.substring(0, v.length() - 5) + "... ";
183        }
184        return v;
185    }
186
187
188    private static int calcTextConsoleLength(String s) {
189        int result = 0;
190        char[] chars = s.toCharArray();
191        for (char c : chars) {
192            if (isCJK(c)) {
193                result += 3;
194            } else {
195                result += 2;
196            }
197        }
198        return result % 2 != 0 ? result + 1 : result;
199    }
200
201
202    private static boolean isCJK(char c) {
203        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
204        return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
205            || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
206            || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
207            || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION;
208    }
209
210
211    private static Map<String, Method> getSetterMethods(Class<?> aClass) {
212        return MapUtil.computeIfAbsent(classSettersCache, aClass, aClass1 -> {
213            Map<String, Method> columnSetterMapping = new HashMap<>();
214            List<Method> setters = ClassUtil.getAllMethods(aClass1,
215                method -> method.getName().startsWith("set")
216                    && method.getParameterCount() == 1
217                    && Modifier.isPublic(method.getModifiers())
218            );
219            for (Method setter : setters) {
220                String column = setter.getName().substring(3);
221                columnSetterMapping.put(column, setter);
222                columnSetterMapping.put(StringUtil.camelToUnderline(column), setter);
223                columnSetterMapping.put(StringUtil.underlineToCamel(column), setter);
224            }
225            return columnSetterMapping;
226        });
227    }
228
229}