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