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