Merge pull request #412 from mabaiwan/develop

Remove Equator
pull/413/head
小马哥 2 years ago committed by GitHub
commit 87faae5040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -76,7 +76,7 @@ Hippo-4J 获得了一些宝贵的荣誉,这属于每一位对 Hippo-4J 做出
## 鸣谢
Hippo-4J 项目基于或参考以下项目:[nacos](https://github.com/alibaba/nacos)、[eureka](https://github.com/Netflix/Eureka)、[mzt-biz-log](https://github.com/mouzt/mzt-biz-log)、[equator](https://github.com/dadiyang/equator)
Hippo-4J 项目基于或参考以下项目:[nacos](https://github.com/alibaba/nacos)、[eureka](https://github.com/Netflix/Eureka)、[mzt-biz-log](https://github.com/mouzt/mzt-biz-log)。
感谢 JetBrains 提供的免费开源 License

@ -1,202 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hippo4j.tools.logrecord.compare;
import java.util.*;
import java.util.stream.Collectors;
/**
* .
*
* @author chen.ma
* @date 2021/10/24 20:25
*/
public class AbstractEquator implements Equator {
private static final List<Class<?>> WRAPPER =
Arrays
.asList(
Byte.class,
Short.class,
Integer.class,
Long.class,
Float.class,
Double.class,
Character.class,
Boolean.class,
String.class);
private List<String> includeFields;
private List<String> excludeFields;
private boolean bothExistFieldOnly = true;
public AbstractEquator() {
includeFields = Collections.emptyList();
excludeFields = Collections.emptyList();
}
/**
* @param bothExistFieldOnly
*/
public AbstractEquator(boolean bothExistFieldOnly) {
includeFields = Collections.emptyList();
excludeFields = Collections.emptyList();
this.bothExistFieldOnly = bothExistFieldOnly;
}
/**
* .
*
* @param includeFields , null
* @param excludeFields , null
*/
public AbstractEquator(List<String> includeFields, List<String> excludeFields) {
this.includeFields = includeFields;
this.excludeFields = excludeFields;
}
/**
* .
*
* @param includeFields , null
* @param excludeFields , null
* @param bothExistFieldOnly , true
*/
public AbstractEquator(List<String> includeFields, List<String> excludeFields, boolean bothExistFieldOnly) {
this.includeFields = includeFields;
this.excludeFields = excludeFields;
this.bothExistFieldOnly = bothExistFieldOnly;
}
@Override
public boolean isEquals(Object first, Object second) {
List<FieldInfo> diff = getDiffFields(first, second);
return diff == null || diff.isEmpty();
}
@Override
public List<FieldInfo> getDiffFields(Object first, Object second) {
return null;
}
/**
* , equals.
* <p>
* .
*
* @param fieldInfo
* @return
*/
protected boolean isFieldEquals(FieldInfo fieldInfo) {
// 先判断排除, 如果需要排除, 则无论在不在包含范围, 都一律不比对
if (isExclude(fieldInfo)) {
return true;
}
// 如果有指定需要包含的字段而且当前字段不在需要包含的字段中则不比对
if (!isInclude(fieldInfo)) {
return true;
}
return nullableEquals(fieldInfo.getFirstVal(), fieldInfo.getSecondVal());
}
/**
* , , .
*
* @param fieldInfo
* @return
*/
protected boolean isExclude(FieldInfo fieldInfo) {
// 如果有指定需要排除的字段,而且当前字段是需要排除字段,则直接返回 true
return excludeFields != null && !excludeFields.isEmpty() && excludeFields.contains(fieldInfo.getFieldName());
}
/**
* , , .
*
* @param fieldInfo
* @return
*/
protected boolean isInclude(FieldInfo fieldInfo) {
// 没有指定需要包含的字段,则全部都包含
if (includeFields == null || includeFields.isEmpty()) {
return true;
}
return includeFields.contains(fieldInfo.getFieldName());
}
/**
* .
*
* @param first
* @param second
* @return
*/
protected List<FieldInfo> compareSimpleField(Object first, Object second) {
boolean eq = Objects.equals(first, second);
if (eq) {
return Collections.emptyList();
} else {
Object obj = first == null ? second : first;
Class<?> clazz = obj.getClass();
// 不等的字段名称使用类的名称
return Collections.singletonList(new FieldInfo(clazz.getSimpleName(), clazz, first, second));
}
}
/**
* .
*
* @param first
* @param second
* @return
*/
protected boolean isSimpleField(Object first, Object second) {
Object obj = first == null ? second : first;
Class<?> clazz = obj.getClass();
return clazz.isPrimitive() || WRAPPER.contains(clazz);
}
protected boolean nullableEquals(Object first, Object second) {
if (first instanceof Collection && second instanceof Collection) {
// 如果两个都是集合类型,尝试转换为数组再进行深度比较
return Objects.deepEquals(((Collection) first).toArray(), ((Collection) second).toArray());
}
return Objects.deepEquals(first, second);
}
protected Set<String> getAllFieldNames(Set<String> firstFields, Set<String> secondFields) {
Set<String> allFields;
// 只取交集
if (isBothExistFieldOnly()) {
allFields = firstFields.stream().filter(secondFields::contains).collect(Collectors.toSet());
} else {
// 否则取并集
allFields = new HashSet<>(firstFields);
allFields.addAll(secondFields);
}
return allFields;
}
public boolean isBothExistFieldOnly() {
return bothExistFieldOnly;
}
}

@ -1,48 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hippo4j.tools.logrecord.compare;
import java.util.List;
/**
* .
*
* @author chen.ma
* @date 2021/10/24 20:27
*/
public interface Equator {
/**
* .
*
* @param first
* @param second
* @return
*/
boolean isEquals(Object first, Object second);
/**
* .
*
* @param first
* @param second
* @return
*/
List<FieldInfo> getDiffFields(Object first, Object second);
}

@ -1,96 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hippo4j.tools.logrecord.compare;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Objects;
/**
* .
*
* @author chen.ma
* @date 2021/10/24 20:03
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FieldInfo {
/**
*
*/
private String fieldName;
/**
*
*/
private Class<?> firstFieldType;
/**
*
*/
private Class<?> secondFieldType;
/**
*
*/
private Object firstVal;
/**
*
*/
private Object secondVal;
public FieldInfo(String fieldName, Class<?> firstFieldType, Class<?> secondFieldType) {
this.fieldName = fieldName;
this.firstFieldType = firstFieldType;
this.secondFieldType = secondFieldType;
}
public FieldInfo(String fieldName, Class<?> fieldType, Object firstVal, Object secondVal) {
this.fieldName = fieldName;
this.firstFieldType = fieldType;
this.secondFieldType = fieldType;
this.firstVal = firstVal;
this.secondVal = secondVal;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
}
FieldInfo fieldInfo = (FieldInfo) o;
return Objects.equals(fieldName, fieldInfo.fieldName) &&
Objects.equals(firstFieldType, fieldInfo.firstFieldType) &&
Objects.equals(secondFieldType, fieldInfo.secondFieldType) &&
Objects.equals(firstVal, fieldInfo.firstVal) &&
Objects.equals(secondVal, fieldInfo.secondVal);
}
@Override
public int hashCode() {
return Objects.hash(fieldName, firstFieldType, secondFieldType, firstVal, secondVal);
}
}

@ -1,161 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.hippo4j.tools.logrecord.compare;
import lombok.NoArgsConstructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* getter
* <p>
* get is
*
* @author chen.ma
* @date 2021/10/24 20:36
*/
@NoArgsConstructor
public class GetterBaseEquator extends AbstractEquator {
private static final String GET = "get";
private static final String IS = "is";
private static final String GET_IS = "get|is";
private static final String GET_CLASS = "getClass";
private static final Map<Class<?>, Map<String, Method>> CACHE = new ConcurrentHashMap<>();
public GetterBaseEquator(boolean bothExistFieldOnly) {
super(bothExistFieldOnly);
}
public GetterBaseEquator(List<String> includeFields, List<String> excludeFields) {
super(includeFields, excludeFields);
}
public GetterBaseEquator(List<String> includeFields, List<String> excludeFields, boolean bothExistFieldOnly) {
super(includeFields, excludeFields, bothExistFieldOnly);
}
@Override
public List<FieldInfo> getDiffFields(Object first, Object second) {
if (first == null && second == null) {
return Collections.emptyList();
}
// 先尝试判断是否为普通数据类型
if (isSimpleField(first, second)) {
return compareSimpleField(first, second);
}
Set<String> allFieldNames;
// 获取所有字段
Map<String, Method> firstGetters = getAllGetters(first);
Map<String, Method> secondGetters = getAllGetters(second);
if (first == null) {
allFieldNames = secondGetters.keySet();
} else if (second == null) {
allFieldNames = firstGetters.keySet();
} else {
allFieldNames = getAllFieldNames(firstGetters.keySet(), secondGetters.keySet());
}
List<FieldInfo> diffFields = new LinkedList<>();
for (String fieldName : allFieldNames) {
try {
Method firstGetterMethod = firstGetters.getOrDefault(fieldName, null);
Method secondGetterMethod = secondGetters.getOrDefault(fieldName, null);
Object firstVal = firstGetterMethod != null ? firstGetterMethod.invoke(first) : null;
Object secondVal = secondGetterMethod != null ? secondGetterMethod.invoke(second) : null;
FieldInfo fieldInfo = new FieldInfo(fieldName, getReturnType(firstGetterMethod), getReturnType(secondGetterMethod));
fieldInfo.setFirstVal(firstVal);
fieldInfo.setSecondVal(secondVal);
if (!isFieldEquals(fieldInfo)) {
diffFields.add(fieldInfo);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("获取属性进行比对发生异常: " + fieldName, e);
}
}
return diffFields;
}
private Class<?> getReturnType(Method method) {
return method == null ? null : method.getReturnType();
}
private Map<String, Method> getAllGetters(Object obj) {
if (obj == null) {
return Collections.emptyMap();
}
return CACHE.computeIfAbsent(obj.getClass(), k -> {
Class<?> clazz = obj.getClass();
Map<String, Method> getters = new LinkedHashMap<>(8);
while (clazz != Object.class) {
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
// getter 方法必须是 public 且没有参数的
if (!Modifier.isPublic(m.getModifiers()) || m.getParameterTypes().length > 0) {
continue;
}
if (m.getReturnType() == Boolean.class || m.getReturnType() == boolean.class) {
// 如果返回值是 boolean 则兼容 isXxx 的写法
if (m.getName().startsWith(IS)) {
String fieldName = uncapitalize(m.getName().substring(2));
getters.put(fieldName, m);
continue;
}
}
// 以 get 开头但排除 getClass() 方法
if (m.getName().startsWith(GET) && !GET_CLASS.equals(m.getName())) {
String fieldName = uncapitalize(m.getName().replaceFirst(GET_IS, ""));
getters.put(fieldName, m);
}
}
// 得到父类, 然后赋给自己
clazz = clazz.getSuperclass();
}
return getters;
});
}
private String uncapitalize(final String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return str;
}
final int firstCodepoint = str.codePointAt(0);
final int newCodePoint = Character.toLowerCase(firstCodepoint);
if (firstCodepoint == newCodePoint) {
return str;
}
final int[] newCodePoints = new int[strLen];
int outOffset = 0;
newCodePoints[outOffset++] = newCodePoint;
for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen;) {
final int codepoint = str.codePointAt(inOffset);
newCodePoints[outOffset++] = codepoint;
inOffset += Character.charCount(codepoint);
}
return new String(newCodePoints, 0, outOffset);
}
}
Loading…
Cancel
Save