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.components.rpc;
017
018import io.jboot.Jboot;
019import io.jboot.utils.AnnotationUtil;
020import io.jboot.utils.CollectionUtil;
021import io.jboot.utils.StrUtil;
022
023import java.lang.reflect.Field;
024import java.lang.reflect.Method;
025import java.lang.reflect.Modifier;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031/**
032 * @author michael yang (fuhai999@gmail.com)
033 * @Date: 2020/3/20
034 */
035public class RPCUtil {
036
037    /**
038     * 根据注解来设置对象内容,参考 dubbo 下的 AbstractConfig
039     * 参考 {{@org.apache.dubbo.config.AbstractConfig#appendAnnotation }}
040     *
041     * @param annotationClass
042     * @param annotation
043     * @param appendTo
044     */
045    public static void appendAnnotation(Class<?> annotationClass, Object annotation, Object appendTo) {
046        Method[] methods = annotationClass.getMethods();
047        for (Method method : methods) {
048            if (method.getDeclaringClass() != Object.class
049                    && method.getReturnType() != void.class
050                    && !"toString".equals(method.getName())
051                    && !"hashCode".equals(method.getName())
052                    && !"annotationType".equals(method.getName())
053                    && method.getParameterTypes().length == 0
054                    && Modifier.isPublic(method.getModifiers())
055                    && !Modifier.isStatic(method.getModifiers())) {
056                try {
057                    String property = method.getName();
058                    if ("interfaceClass".equals(property) || "interfaceName".equals(property)) {
059                        property = "interface";
060                    }
061                    String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
062                    Object value = method.invoke(annotation);
063                    if (value != null && !value.equals(method.getDefaultValue())) {
064                        Method setterMethod = null;
065                        if ("filter".equals(property) || "listener".equals(property) || "registry".equals(property)) {
066                            value = StrUtil.join((String[]) value, ",");
067                            setterMethod = getMethod(appendTo.getClass(), setter, String.class);
068                        } else if ("parameters".equals(property)) {
069                            value = CollectionUtil.string2Map((String) value);
070                            setterMethod = getMethod(appendTo.getClass(), setter, Map.class);
071                        } else {
072                            setterMethod = getMethod(appendTo.getClass(), setter, method.getReturnType());
073                        }
074
075                        //fixed : 值内容有 ${} 不生效的问题
076                        if (value instanceof String) {
077                            value = AnnotationUtil.get((String) value);
078                        }
079
080                        if (setterMethod != null) {
081                            setterMethod.invoke(appendTo, value);
082                        }
083                    }
084                } catch (Exception ex) {
085                    ex.printStackTrace();
086                }
087            }
088        }
089    }
090
091
092    /**
093     * copy object field value to other
094     *
095     * @param copyFrom
096     * @param copyTo
097     */
098    public static void copyDeclaredFields(Object copyFrom, Object copyTo) {
099        Field[] fields = copyFrom.getClass().getDeclaredFields();
100        for (Field field : fields) {
101            try {
102                String setterName = "set" + StrUtil.firstCharToUpperCase(field.getName());
103                Method method = getMethod(copyTo.getClass(), setterName, field.getType());
104
105                if (method != null) {
106                    field.setAccessible(true);
107                    Object value = field.get(copyFrom);
108                    if (value != null && !value.equals("0") && !value.equals("")) {
109                        method.invoke(copyTo, value);
110                    }
111                }
112            } catch (Exception ex) {
113                // ignore
114            }
115        }
116    }
117
118
119    private static Method getMethod(Class<?> clazz, String methodName, Class<?> type) {
120        try {
121            return clazz.getMethod(methodName, getBoxedClass(type));
122        } catch (NoSuchMethodException e) {
123            try {
124                return clazz.getMethod(methodName, type);
125            } catch (NoSuchMethodException ex) {
126            }
127        }
128        return null;
129    }
130
131
132    public static void copyNotNullFields(Object copyFrom, Object copyTo, boolean override) {
133        if (copyFrom == null || copyTo == null) {
134            return;
135        }
136
137        Method[] fromObjGetters = copyFrom.getClass().getMethods();
138        for (Method getter : fromObjGetters) {
139            String getterMethodName = getter.getName();
140            if (getterMethodName.length() > 3
141                    && getterMethodName.startsWith("get")
142                    && Modifier.isPublic(getter.getModifiers())
143                    && getter.getParameterCount() == 0) {
144                try {
145                    Class<?> returnType = getter.getReturnType();
146                    if (override) {
147                        Object newData = getter.invoke(copyFrom);
148                        if (newData != null) {
149                            Method setter = copyTo.getClass().getMethod("set" + getterMethodName.substring(3), returnType);
150                            setter.invoke(copyTo, newData);
151                        }
152                    } else {
153                        Object oldData = copyTo.getClass().getMethod(getterMethodName).invoke(copyTo);
154                        if (oldData == null) {
155                            Object newData = getter.invoke(copyFrom);
156                            if (newData != null) {
157                                Method setter = copyTo.getClass().getMethod("set" + getterMethodName.substring(3), returnType);
158                                setter.invoke(copyTo, newData);
159                            }
160                        }
161                    }
162                } catch (Exception e) {
163                    // doNothing
164                }
165            }
166        }
167    }
168
169
170    public static <T> boolean isDefaultConfigExist(Class<T> clazz, Map<String, T> ret) {
171        try {
172            Field field = clazz.getField("isDefault");
173            field.setAccessible(true);
174            for (Object obj : ret.values()) {
175                Boolean fieldValue = (Boolean) field.get(obj);
176                if (fieldValue != null && fieldValue) {
177                    return true;
178                }
179            }
180        } catch (NoSuchFieldException | IllegalAccessException e) {
181            // do nothing
182        }
183        return false;
184    }
185
186
187
188    /**
189     * 设置子节点配置,比如 ProviderConfig 下的 MethodsConfig ,或者 MethodConfig 下的 ArgumentConfig 等
190     *
191     * @param appendTo   要设置的对象
192     * @param dataSource 设置子节点的数据源
193     * @param prefix     要设置对象的配置前缀(jboot.properties 下的配置)
194     * @param arrName    要设置对象的属性名
195     * @param <T>
196     * @param <F>
197     */
198    public static <T, F> void setChildConfig(Map<String, T> appendTo, Map<String, F> dataSource, String prefix, String arrName) {
199        if (appendTo != null && !appendTo.isEmpty()) {
200            for (Map.Entry<String, T> entry : appendTo.entrySet()) {
201
202                String configKey = "default".equals(entry.getKey())
203                        ? prefix + "." + arrName //"jboot.rpc.dubbo.method.argument"
204                        : prefix + "." + entry.getKey() + "." + arrName;//"jboot.rpc.dubbo.method."+entry.getKey()+".argument";
205
206                String configValue = Jboot.configValue(configKey, "default");
207                List<F> argCfgList = new ArrayList<>();
208                Set<String> arguments = StrUtil.splitToSetByComma(configValue);
209                for (String arg : arguments) {
210                    F fillObj = dataSource.get(arg);
211                    if (fillObj != null) {
212                        argCfgList.add(fillObj);
213                    }
214                }
215                if (!argCfgList.isEmpty()) {
216                    try {
217                        //setArguments/setMethods/setRegistries
218                        String setterMethodName = arrName.equals("registry")
219                                ? "setRegistries"
220                                : "set" + StrUtil.firstCharToUpperCase(arrName) + "s";
221
222                        Method method = entry.getValue().getClass().getMethod(setterMethodName, List.class);
223                        method.invoke(entry.getValue(), argCfgList);
224                    } catch (Exception e) {
225                        e.printStackTrace();
226                    }
227                }
228            }
229        }
230    }
231
232    private static Class<?> getBoxedClass(Class<?> c) {
233        if (c == int.class) {
234            c = Integer.class;
235        } else if (c == boolean.class) {
236            c = Boolean.class;
237        } else if (c == long.class) {
238            c = Long.class;
239        } else if (c == float.class) {
240            c = Float.class;
241        } else if (c == double.class) {
242            c = Double.class;
243        } else if (c == char.class) {
244            c = Character.class;
245        } else if (c == byte.class) {
246            c = Byte.class;
247        } else if (c == short.class) {
248            c = Short.class;
249        }
250        return c;
251    }
252}