001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (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 io.jboot.web.json;
017
018import com.alibaba.fastjson.JSON;
019import com.alibaba.fastjson.annotation.JSONField;
020import com.jfinal.json.JFinalJson;
021import com.jfinal.json.JFinalJsonKit;
022import com.jfinal.kit.LogKit;
023import com.jfinal.kit.StrKit;
024import com.jfinal.plugin.activerecord.CPI;
025import com.jfinal.plugin.activerecord.Model;
026import io.jboot.Jboot;
027import io.jboot.db.model.JbootModel;
028import io.jboot.utils.ClassUtil;
029import io.jboot.utils.StrUtil;
030
031import java.lang.reflect.Method;
032import java.lang.reflect.Modifier;
033import java.util.*;
034
035
036public class JbootJson extends JFinalJson {
037
038    private JbootJsonConfig config = Jboot.config(JbootJsonConfig.class);
039    protected static Map<Class<?>, MethodsAndFieldsWrapper> methodAndFieldsCache = new HashMap<>();
040
041    public JbootJson() {
042
043        //跳过 null 值输出到浏览器,提高传输性能
044        setSkipNullValueField(config.isSkipNullValueField());
045
046        //设置转换层级
047        setConvertDepth(config.getDepth());
048
049        //默认设置为 CamelCase 的属性模式
050        if (config.isCamelCaseJsonStyleEnable()) {
051            setModelAndRecordFieldNameConverter((fieldName) -> StrKit.toCamelCase(fieldName, config.isCamelCaseToLowerCaseAnyway()));
052        }
053
054        setToJsonFactory(o -> o instanceof Model ? jbootModelToJson : null);
055
056        if (StrUtil.isNotBlank(config.getTimestampPattern())) {
057            setTimestampPattern(config.getTimestampPattern());
058        }
059    }
060
061
062    protected JFinalJsonKit.ToJson<Model<?>> jbootModelToJson = (model, depth, ret) -> {
063        if (JFinalJsonKit.checkDepth(depth--, ret)) {
064            return;
065        }
066
067        Map<String, Object> map = new HashMap<>();
068
069        if (!config.isSkipModelAttrs()) {
070            fillModelAttrsToMap(CPI.getAttrs(model), map);
071        }
072
073        if (!config.isSkipBeanGetters()) {
074            fillBeanToMap(model, map);
075        }
076
077        optimizeMapAttrs(map);
078
079        JFinalJsonKit.mapToJson(map, depth, ret);
080    };
081
082
083    protected void fillModelAttrsToMap(Map<String, Object> attrs, Map<String, Object> toMap) {
084        if (attrs != null && !attrs.isEmpty()) {
085            for (Map.Entry<String, Object> entry : attrs.entrySet()) {
086                String fieldName = entry.getKey();
087                if (config.isCamelCaseJsonStyleEnable()) {
088                    fieldName = StrKit.toCamelCase(fieldName, config.isCamelCaseToLowerCaseAnyway());
089                }
090                toMap.put(fieldName, entry.getValue());
091            }
092        }
093    }
094
095
096    protected void fillBeanToMap(Object bean, Map<String, Object> toMap) {
097
098        MethodsAndFieldsWrapper wrapper = methodAndFieldsCache.get(bean.getClass());
099        if (wrapper == null) {
100            synchronized (this) {
101                if (wrapper == null) {
102                    wrapper = new MethodsAndFieldsWrapper(bean.getClass());
103                } else {
104                    wrapper = methodAndFieldsCache.get(bean.getClass());
105                }
106            }
107        }
108
109        for (String ignoreField : wrapper.ignoreFields) {
110            toMap.remove(ignoreField);
111        }
112
113
114        for (int i = 0; i < wrapper.fields.size(); i++) {
115            String originalField = wrapper.originalFields.get(i);
116            toMap.remove(originalField);
117
118            Object value = invokeGetterMethod(wrapper.getterMethods.get(i), bean);
119            String field = wrapper.fields.get(i);
120            toMap.put(field, value);
121        }
122
123    }
124
125
126    protected void optimizeMapAttrs(Map<String, Object> map) {
127    }
128
129    protected Object invokeGetterMethod(Method method, Object bean) {
130        try {
131            return method.invoke(bean);
132        } catch (Exception ex) {
133            LogKit.error("can not invoke method: " + ClassUtil.buildMethodString(method), ex);
134            return null;
135        }
136    }
137
138
139    @Override
140    public <T> T parse(String jsonString, Class<T> type) {
141        return JSON.parseObject(jsonString, type);
142    }
143
144
145    public static class MethodsAndFieldsWrapper {
146
147
148        private static boolean hasFastJson = ClassUtil.hasClass("com.alibaba.fastjson.JSON");
149
150        private List<String> fields = new LinkedList<>();
151        private List<Method> getterMethods = new LinkedList<>();
152        private List<String> originalFields = new LinkedList<>();
153
154        //需要忽略的字段
155        private List<String> ignoreFields = new ArrayList<>();
156
157        public MethodsAndFieldsWrapper(Class reflectiveClass) {
158
159            Method[] methodArray = reflectiveClass.getMethods();
160            for (Method method : methodArray) {
161                if (method.getParameterCount() != 0
162                        || method.getReturnType() == void.class
163                        || !Modifier.isPublic(method.getModifiers())
164                        || "getClass".equals(method.getName())
165                        || method.getDeclaringClass() == JbootModel.class
166                        || method.getDeclaringClass() == Model.class
167                        || method.getDeclaringClass() == Object.class
168                ) {
169                    continue;
170                }
171
172
173                String fieldName = getGetterMethodField(method.getName());
174                if (fieldName != null) {
175                    String attrName = StrKit.firstCharToLowerCase(fieldName);
176                    if (isIgnoreFiled(method)) {
177                        ignoreFields.add(attrName);
178                    } else {
179                        originalFields.add(attrName);
180                        fields.add(getDefineName(method, attrName));
181                        getterMethods.add(method);
182                    }
183                }
184
185            }
186        }
187
188        private String getGetterMethodField(String methodName) {
189            if (methodName.startsWith("get") && methodName.length() > 3) {
190                return methodName.substring(3);
191            } else if (methodName.startsWith("is") && methodName.length() > 2) {
192                return methodName.substring(2);
193            }
194            return null;
195        }
196
197
198        private String getDefineName(Method method, String orginalName) {
199            if (hasFastJson) {
200                JSONField jsonField = method.getAnnotation(JSONField.class);
201                if (jsonField != null && StrUtil.isNotBlank(jsonField.name())) {
202                    return jsonField.name();
203                }
204            }
205            return orginalName;
206        }
207
208        private boolean isIgnoreFiled(Method method) {
209            if (hasFastJson) {
210                JSONField jsonField = method.getAnnotation(JSONField.class);
211                if (jsonField != null && !jsonField.serialize()) {
212                    return true;
213                }
214            }
215            return method.getAnnotation(JsonIgnore.class) != null;
216        }
217    }
218}