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