feat (接口定义): 操作历史重构
This commit is contained in:
parent
ba08a5578c
commit
af785a1d4e
|
@ -201,7 +201,7 @@ public class MsLogAspect {
|
|||
}
|
||||
if (StringUtils.isNotEmpty(content) && StringUtils.isNotEmpty(msLog.beforeValue())) {
|
||||
OperatingLogDetails details = JSON.parseObject(content, OperatingLogDetails.class);
|
||||
List<DetailColumn> columns = ReflexObjectUtil.compared(JSON.parseObject(msLog.beforeValue(), OperatingLogDetails.class), details);
|
||||
List<DetailColumn> columns = ReflexObjectUtil.compared(JSON.parseObject(msLog.beforeValue(), OperatingLogDetails.class), details,msLog.module());
|
||||
details.setColumns(columns);
|
||||
msOperLog.setOperContent(JSON.toJSONString(details));
|
||||
msOperLog.setSourceId(details.getSourceId());
|
||||
|
|
|
@ -7,6 +7,8 @@ import com.google.gson.GsonBuilder;
|
|||
import com.google.gson.JsonObject;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.log.utils.dff.ApiDefinitionDiffUtil;
|
||||
import io.metersphere.log.utils.json.diff.GsonDiff;
|
||||
import io.metersphere.log.vo.DetailColumn;
|
||||
import io.metersphere.log.vo.OperatingLogDetails;
|
||||
import io.metersphere.log.vo.StatusReference;
|
||||
|
@ -76,6 +78,39 @@ public class ReflexObjectUtil {
|
|||
return columnList;
|
||||
}
|
||||
|
||||
public static List<DetailColumn> getColumns(Object obj) {
|
||||
List<DetailColumn> columnList = new LinkedList<>();
|
||||
if (obj == null) {
|
||||
return columnList;
|
||||
}
|
||||
// 得到类对象
|
||||
Class clazz = obj.getClass();
|
||||
// 得到类中的所有属性集合
|
||||
List<Field[]> fields = new LinkedList<>();
|
||||
// 遍历所有父类字节码对象
|
||||
while (clazz != null) {
|
||||
// 获取字节码对象的属性对象数组
|
||||
Field[] declaredFields = clazz.getDeclaredFields();
|
||||
fields.add(declaredFields);
|
||||
// 获得父类的字节码对象
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
for (Field[] fs : fields) {
|
||||
for (int i = 0; i < fs.length; i++) {
|
||||
Field f = fs[i];
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
Object val = f.get(obj);
|
||||
DetailColumn column = new DetailColumn(f.getName(), f.getName(), val, "");
|
||||
columnList.add(column);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return columnList;
|
||||
}
|
||||
|
||||
public static boolean isJsonArray(String content) {
|
||||
try {
|
||||
JSONArray array = JSON.parseArray(content);
|
||||
|
@ -96,7 +131,7 @@ public class ReflexObjectUtil {
|
|||
}));
|
||||
}
|
||||
|
||||
public static List<DetailColumn> compared(OperatingLogDetails obj, OperatingLogDetails newObj) {
|
||||
public static List<DetailColumn> compared(OperatingLogDetails obj, OperatingLogDetails newObj, String module) {
|
||||
List<DetailColumn> comparedColumns = new LinkedList<>();
|
||||
try {
|
||||
if (obj != null && newObj != null) {
|
||||
|
@ -116,10 +151,23 @@ public class ReflexObjectUtil {
|
|||
if (StringUtils.isEmpty(JSON.toJSONString(originalColumns.get(i).getOriginalValue())) && StringUtils.isEmpty(JSON.toJSONString(newColumns.get(i).getOriginalValue()))) {
|
||||
continue;
|
||||
}
|
||||
// 深度对比
|
||||
DetailColumn column = new DetailColumn();
|
||||
BeanUtils.copyBean(column, originalColumns.get(i));
|
||||
column.setNewValue(newColumns.get(i).getOriginalValue());
|
||||
if (originalColumns.get(i).getColumnName().equals("tags")) {
|
||||
GsonDiff diff = new GsonDiff();
|
||||
String oldTags = "{\"root\":" + originalColumns.get(i).getOriginalValue().toString() + "}";
|
||||
String newTags = "{\"root\":" + newColumns.get(i).getOriginalValue().toString() + "}";
|
||||
String diffStr = diff.diff(oldTags, newTags);
|
||||
String diffValue = diff.apply(newTags, diffStr);
|
||||
column.setDiffValue(diffValue);
|
||||
}
|
||||
// 深度对比
|
||||
else if (StringUtils.equals(module, "api_definition")) {
|
||||
String newValue = newColumns.get(i).getOriginalValue().toString();
|
||||
String oldValue = column.getOriginalValue().toString();
|
||||
column.setDiffValue(ApiDefinitionDiffUtil.diff(newValue, oldValue));
|
||||
}
|
||||
comparedColumns.add(column);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
package io.metersphere.log.utils.dff;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsDubboSampler;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
|
||||
import io.metersphere.log.utils.ReflexObjectUtil;
|
||||
import io.metersphere.log.utils.json.diff.JacksonDiff;
|
||||
import io.metersphere.log.utils.json.diff.JsonDiff;
|
||||
import io.metersphere.log.vo.DetailColumn;
|
||||
import io.metersphere.log.vo.OperatingLogDetails;
|
||||
import io.metersphere.log.vo.api.DefinitionReference;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ApiDefinitionDiffUtil {
|
||||
static final String JSON_START = "{\"root\":";
|
||||
static final String JSON_END = "}";
|
||||
|
||||
public static String diff(String newValue, String oldValue) {
|
||||
try {
|
||||
JSONObject bloBsNew = JSON.parseObject(newValue);
|
||||
JSONObject bloBsOld = JSON.parseObject(oldValue);
|
||||
if (bloBsNew == null || StringUtils.isEmpty(bloBsNew.getString("type"))) {
|
||||
return null;
|
||||
}
|
||||
Map<String, String> diffMap = new LinkedHashMap<>();
|
||||
diffMap.put("type", bloBsNew.getString("type"));
|
||||
JsonDiff jsonDiff = new JacksonDiff();
|
||||
if (bloBsNew.getString("type").equals("TCPSampler")) {
|
||||
MsTCPSampler tcpSamplerNew = bloBsNew.toJavaObject(MsTCPSampler.class);
|
||||
MsTCPSampler tcpSamplerOld = bloBsOld.toJavaObject(MsTCPSampler.class);
|
||||
diffTcp(tcpSamplerNew, tcpSamplerOld, jsonDiff, diffMap);
|
||||
} else if (bloBsNew.getString("type").equals("HTTPSamplerProxy")) {
|
||||
MsHTTPSamplerProxy httpSamplerProxyNew = bloBsNew.toJavaObject(MsHTTPSamplerProxy.class);
|
||||
MsHTTPSamplerProxy httpSamplerProxyOld = bloBsOld.toJavaObject(MsHTTPSamplerProxy.class);
|
||||
diffHttp(httpSamplerProxyNew, httpSamplerProxyOld, jsonDiff, diffMap);
|
||||
} else if (bloBsNew.getString("type").equals("JDBCSampler")) {
|
||||
MsJDBCSampler jdbcSamplerNew = bloBsNew.toJavaObject(MsJDBCSampler.class);
|
||||
MsJDBCSampler jdbcSamplerOld = bloBsOld.toJavaObject(MsJDBCSampler.class);
|
||||
diffJdbc(jdbcSamplerNew, jdbcSamplerOld, jsonDiff, diffMap);
|
||||
} else {
|
||||
MsDubboSampler dubboSamplerNew = bloBsNew.toJavaObject(MsDubboSampler.class);
|
||||
MsDubboSampler dubboSamplerOld = bloBsOld.toJavaObject(MsDubboSampler.class);
|
||||
diffDubbo(dubboSamplerNew, dubboSamplerOld, jsonDiff, diffMap);
|
||||
}
|
||||
return JSON.toJSONString(diffMap);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void diffHttp(MsHTTPSamplerProxy httpNew, MsHTTPSamplerProxy httpOld, JsonDiff jsonDiff, Map<String, String> diffMap) {
|
||||
// 请求头对比 old/new
|
||||
if (CollectionUtils.isNotEmpty(httpNew.getHeaders())) {
|
||||
httpNew.getHeaders().remove(httpNew.getHeaders().size() - 1);
|
||||
httpOld.getHeaders().remove(httpOld.getHeaders().size() - 1);
|
||||
}
|
||||
String headerNew = JSON_START + JSON.toJSONString(httpNew.getHeaders()) + JSON_END;
|
||||
String headerOld = JSON_START + JSON.toJSONString(httpOld.getHeaders()) + JSON_END;
|
||||
if (!StringUtils.equals(headerNew, headerOld)) {
|
||||
String patch = jsonDiff.diff(headerOld, headerNew);
|
||||
String diffPatch = jsonDiff.apply(headerNew, patch);
|
||||
if (StringUtils.isNotEmpty(diffPatch)) {
|
||||
diffMap.put("header", diffPatch);
|
||||
}
|
||||
}
|
||||
// 对比QUERY参数
|
||||
if (CollectionUtils.isNotEmpty(httpNew.getArguments())) {
|
||||
httpNew.getArguments().remove(httpNew.getArguments().size() - 1);
|
||||
httpOld.getArguments().remove(httpOld.getArguments().size() - 1);
|
||||
}
|
||||
String queryNew = JSON_START + JSON.toJSONString(httpNew.getArguments()) + JSON_END;
|
||||
String queryOld = JSON_START + JSON.toJSONString(httpOld.getArguments()) + JSON_END;
|
||||
if (!StringUtils.equals(queryNew, queryOld)) {
|
||||
String patch = jsonDiff.diff(queryOld, queryNew);
|
||||
String diff = jsonDiff.apply(queryNew, patch);
|
||||
if (StringUtils.isNotEmpty(diff)) {
|
||||
diffMap.put("query", diff);
|
||||
}
|
||||
}
|
||||
// 对比REST参数
|
||||
if (CollectionUtils.isNotEmpty(httpNew.getRest())) {
|
||||
httpNew.getRest().remove(httpNew.getRest().size() - 1);
|
||||
httpOld.getRest().remove(httpOld.getRest().size() - 1);
|
||||
}
|
||||
String restNew = JSON_START + JSON.toJSONString(httpNew.getRest()) + JSON_END;
|
||||
String restOld = JSON_START + JSON.toJSONString(httpOld.getRest()) + JSON_END;
|
||||
if (!StringUtils.equals(restNew, restOld)) {
|
||||
String patch = jsonDiff.diff(restOld, restNew);
|
||||
String diff = jsonDiff.apply(restNew, patch);
|
||||
if (StringUtils.isNotEmpty(diff)) {
|
||||
diffMap.put("rest", diff);
|
||||
}
|
||||
}
|
||||
// 对比BODY-JSON参数
|
||||
if (httpNew.getBody() != null) {
|
||||
String bodyNew = JSON.toJSONString(httpNew.getBody());
|
||||
String bodyOld = JSON.toJSONString(httpOld.getBody());
|
||||
if (!StringUtils.equals(bodyNew, bodyOld)) {
|
||||
String patch = jsonDiff.diff(bodyOld, bodyNew);
|
||||
String diff = jsonDiff.apply(bodyNew, patch);
|
||||
if (StringUtils.isNotEmpty(diff)) {
|
||||
diffMap.put("body", diff);
|
||||
}
|
||||
}
|
||||
// 对比BODY-FORM参数
|
||||
if (CollectionUtils.isNotEmpty(httpNew.getBody().getKvs())) {
|
||||
httpNew.getBody().getKvs().remove(httpNew.getBody().getKvs().size() - 1);
|
||||
httpOld.getBody().getKvs().remove(httpOld.getBody().getKvs().size() - 1);
|
||||
}
|
||||
String bodyFormNew = JSON_START + JSON.toJSONString(httpNew.getBody().getKvs()) + JSON_END;
|
||||
String bodyFormOld = JSON_START + JSON.toJSONString(httpOld.getBody().getKvs()) + JSON_END;
|
||||
if (!StringUtils.equals(bodyFormNew, bodyFormOld)) {
|
||||
String patch = jsonDiff.diff(bodyFormOld, bodyFormNew);
|
||||
String diff = jsonDiff.apply(bodyFormNew, patch);
|
||||
if (StringUtils.isNotEmpty(diff)) {
|
||||
diffMap.put("body_form", diff);
|
||||
}
|
||||
}
|
||||
// 对比BODY-XML参数
|
||||
if (!StringUtils.equals(httpNew.getBody().getRaw(), httpOld.getBody().getRaw())) {
|
||||
diffMap.put("body_raw_1", httpNew.getBody().getRaw());
|
||||
diffMap.put("body_raw_2", httpOld.getBody().getRaw());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void diffTcp(MsTCPSampler tcpNew, MsTCPSampler tcpOld, JsonDiff jsonDiff, Map<String, String> diffMap) {
|
||||
// 对比请求参数
|
||||
if (CollectionUtils.isNotEmpty(tcpNew.getParameters())) {
|
||||
tcpNew.getParameters().remove(tcpNew.getParameters().size() - 1);
|
||||
tcpOld.getParameters().remove(tcpOld.getParameters().size() - 1);
|
||||
}
|
||||
String queryNew = JSON_START + JSON.toJSONString(tcpNew.getParameters()) + JSON_END;
|
||||
String queryOld = JSON_START + JSON.toJSONString(tcpOld.getParameters()) + JSON_END;
|
||||
if (!StringUtils.equals(queryNew, queryOld)) {
|
||||
String patch = jsonDiff.diff(queryOld, queryNew);
|
||||
String diff = jsonDiff.apply(queryNew, patch);
|
||||
if (StringUtils.isNotEmpty(diff)) {
|
||||
diffMap.put("query", diff);
|
||||
}
|
||||
}
|
||||
// 对比BODY-JSON参数
|
||||
if (!StringUtils.equals(tcpNew.getJsonDataStruct(), tcpOld.getJsonDataStruct())) {
|
||||
String patch = jsonDiff.diff(tcpOld.getJsonDataStruct(), tcpNew.getJsonDataStruct());
|
||||
String diff = jsonDiff.apply(tcpNew.getJsonDataStruct(), patch);
|
||||
if (StringUtils.isNotEmpty(diff)) {
|
||||
diffMap.put("body_json", diff);
|
||||
}
|
||||
}
|
||||
// 对比BODY-XML参数
|
||||
String xmlNew = JSON_START + JSON.toJSONString(tcpNew.getXmlDataStruct()) + JSON_END;
|
||||
String xmlOld = JSON_START + JSON.toJSONString(tcpOld.getXmlDataStruct()) + JSON_END;
|
||||
if (!StringUtils.equals(xmlNew, xmlOld)) {
|
||||
diffMap.put("body_xml_1", JSON.toJSONString(tcpNew.getXmlDataStruct()));
|
||||
diffMap.put("body_xml_2", JSON.toJSONString(tcpOld.getXmlDataStruct()));
|
||||
String patch = jsonDiff.diff(xmlOld, xmlNew);
|
||||
String diffPatch = jsonDiff.apply(xmlNew, patch);
|
||||
if (StringUtils.isNotEmpty(diffPatch)) {
|
||||
diffMap.put("body_xml", diffPatch);
|
||||
}
|
||||
}
|
||||
// 对比BODY-RAW参数
|
||||
if (!StringUtils.equals(tcpNew.getRawDataStruct(), tcpOld.getRawDataStruct())) {
|
||||
diffMap.put("body_raw_1", tcpNew.getRawDataStruct());
|
||||
diffMap.put("body_raw_2", tcpOld.getRawDataStruct());
|
||||
}
|
||||
// 对比pre参数
|
||||
if (tcpNew.getTcpPreProcessor() != null && !StringUtils.equals(tcpNew.getTcpPreProcessor().getScript(), tcpOld.getTcpPreProcessor().getScript())) {
|
||||
diffMap.put("script_1", tcpNew.getTcpPreProcessor().getScript());
|
||||
diffMap.put("script_2", tcpOld.getTcpPreProcessor().getScript());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<DetailColumn> getColumn(List<DetailColumn> columnsNew, List<DetailColumn> columnsOld) {
|
||||
OperatingLogDetails detailsNew = new OperatingLogDetails();
|
||||
detailsNew.setColumns(columnsNew);
|
||||
OperatingLogDetails detailsOld = new OperatingLogDetails();
|
||||
detailsOld.setColumns(columnsOld);
|
||||
List<DetailColumn> diffColumns = ReflexObjectUtil.compared(detailsOld, detailsNew, "");
|
||||
return diffColumns;
|
||||
}
|
||||
|
||||
private static void diffJdbc(MsJDBCSampler jdbcNew, MsJDBCSampler jdbcOld, JsonDiff jsonDiff, Map<String, String> diffMap) {
|
||||
// 基础参数对比
|
||||
List<DetailColumn> columns = ReflexObjectUtil.getColumns(jdbcNew, DefinitionReference.jdbcColumns);
|
||||
List<DetailColumn> columnsOld = ReflexObjectUtil.getColumns(jdbcOld, DefinitionReference.jdbcColumns);
|
||||
List<DetailColumn> diffColumns = getColumn(columns, columnsOld);
|
||||
if (CollectionUtils.isNotEmpty(diffColumns)) {
|
||||
diffMap.put("base", JSON.toJSONString(diffColumns));
|
||||
}
|
||||
// 自定义变量对比
|
||||
if (CollectionUtils.isNotEmpty(jdbcNew.getVariables())) {
|
||||
jdbcNew.getVariables().remove(jdbcNew.getVariables().size() - 1);
|
||||
jdbcOld.getVariables().remove(jdbcOld.getVariables().size() - 1);
|
||||
}
|
||||
String variablesNew = JSON_START + JSON.toJSONString(jdbcNew.getVariables()) + JSON_END;
|
||||
String variablesOld = JSON_START + JSON.toJSONString(jdbcOld.getVariables()) + JSON_END;
|
||||
if (!StringUtils.equals(variablesNew, variablesOld)) {
|
||||
String patch = jsonDiff.diff(variablesOld, variablesNew);
|
||||
String diffPatch = jsonDiff.apply(variablesNew, patch);
|
||||
if (StringUtils.isNotEmpty(diffPatch)) {
|
||||
diffMap.put("variables", diffPatch);
|
||||
}
|
||||
}
|
||||
if (!StringUtils.equals(jdbcNew.getQuery(), jdbcOld.getQuery())) {
|
||||
diffMap.put("query_1", jdbcNew.getQuery());
|
||||
diffMap.put("query_2", jdbcOld.getQuery());
|
||||
}
|
||||
}
|
||||
|
||||
private static void diffDubbo(MsDubboSampler dubboNew, MsDubboSampler dubboOld, JsonDiff jsonDiff, Map<String, String> diffMap) {
|
||||
// Config对比
|
||||
List<DetailColumn> diffColumns = getColumn(ReflexObjectUtil.getColumns(dubboNew.getConfigCenter()), ReflexObjectUtil.getColumns(dubboOld.getConfigCenter()));
|
||||
if (CollectionUtils.isNotEmpty(diffColumns)) {
|
||||
diffMap.put("config", JSON.toJSONString(diffColumns));
|
||||
}
|
||||
// Registry对比
|
||||
List<DetailColumn> registryColumns = getColumn(ReflexObjectUtil.getColumns(dubboNew.getRegistryCenter()), ReflexObjectUtil.getColumns(dubboOld.getRegistryCenter()));
|
||||
if (CollectionUtils.isNotEmpty(registryColumns)) {
|
||||
diffMap.put("registry", JSON.toJSONString(registryColumns));
|
||||
}
|
||||
// service对比
|
||||
List<DetailColumn> serviceColumns = getColumn(ReflexObjectUtil.getColumns(dubboNew.getConsumerAndService()), ReflexObjectUtil.getColumns(dubboOld.getConsumerAndService()));
|
||||
if (CollectionUtils.isNotEmpty(serviceColumns)) {
|
||||
diffMap.put("service", JSON.toJSONString(serviceColumns));
|
||||
}
|
||||
// 对比Args参数
|
||||
if (CollectionUtils.isNotEmpty(dubboNew.getArgs())) {
|
||||
dubboNew.getArgs().remove(dubboNew.getArgs().size() - 1);
|
||||
dubboOld.getArgs().remove(dubboOld.getArgs().size() - 1);
|
||||
}
|
||||
String argsNew = JSON_START + JSON.toJSONString(dubboNew.getArgs()) + JSON_END;
|
||||
String argsOld = JSON_START + JSON.toJSONString(dubboOld.getArgs()) + JSON_END;
|
||||
if (!StringUtils.equals(argsNew, argsOld)) {
|
||||
String patch = jsonDiff.diff(argsOld, argsNew);
|
||||
String diffPatch = jsonDiff.apply(argsNew, patch);
|
||||
if (StringUtils.isNotEmpty(diffPatch)) {
|
||||
diffMap.put("args", diffPatch);
|
||||
}
|
||||
}
|
||||
// 对比Attachment参数
|
||||
if (CollectionUtils.isNotEmpty(dubboNew.getAttachmentArgs())) {
|
||||
dubboNew.getAttachmentArgs().remove(dubboNew.getAttachmentArgs().size() - 1);
|
||||
dubboOld.getAttachmentArgs().remove(dubboOld.getAttachmentArgs().size() - 1);
|
||||
}
|
||||
String attachmentNew = JSON_START + JSON.toJSONString(dubboNew.getAttachmentArgs()) + JSON_END;
|
||||
String attachmentOld = JSON_START + JSON.toJSONString(dubboOld.getAttachmentArgs()) + JSON_END;
|
||||
if (!StringUtils.equals(attachmentNew, attachmentOld)) {
|
||||
String patch = jsonDiff.diff(attachmentOld, attachmentNew);
|
||||
String diffPatch = jsonDiff.apply(attachmentNew, patch);
|
||||
if (StringUtils.isNotEmpty(diffPatch)) {
|
||||
diffMap.put("attachment", diffPatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Created by tomerga on 04/09/2016.
|
||||
*/
|
||||
public enum CompatibilityFlags {
|
||||
MISSING_VALUES_AS_NULLS,
|
||||
REMOVE_NONE_EXISTING_ARRAY_ELEMENT,
|
||||
ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE;
|
||||
|
||||
public static EnumSet<CompatibilityFlags> defaults() {
|
||||
return EnumSet.noneOf(CompatibilityFlags.class);
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
* User: gopi.vishwakarma
|
||||
* Date: 10/07/15
|
||||
* Time: 10:35 AM
|
||||
*/
|
||||
final class Constants {
|
||||
public static final String OP = "op";
|
||||
public static final String VALUE = "value";
|
||||
public static final String PATH = "path";
|
||||
public static final String FROM = "from";
|
||||
public static final String FROM_VALUE = "fromValue";
|
||||
|
||||
private Constants() {}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
class CopyingApplyProcessor extends InPlaceApplyProcessor {
|
||||
|
||||
CopyingApplyProcessor(JsonNode target) {
|
||||
this(target, CompatibilityFlags.defaults());
|
||||
}
|
||||
|
||||
CopyingApplyProcessor(JsonNode target, EnumSet<CompatibilityFlags> flags) {
|
||||
super(target.deepCopy(), flags);
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class Diff {
|
||||
private final Operation operation;
|
||||
private final JsonPointer path;
|
||||
private final JsonNode value;
|
||||
private JsonPointer toPath;
|
||||
private final JsonNode srcValue;
|
||||
|
||||
Diff(Operation operation, JsonPointer path, JsonNode value) {
|
||||
this.operation = operation;
|
||||
this.path = path;
|
||||
this.value = value;
|
||||
this.srcValue = null;
|
||||
}
|
||||
|
||||
Diff(Operation operation, JsonPointer fromPath, JsonPointer toPath) {
|
||||
this.operation = operation;
|
||||
this.path = fromPath;
|
||||
this.toPath = toPath;
|
||||
this.value = null;
|
||||
this.srcValue = null;
|
||||
}
|
||||
|
||||
Diff(Operation operation, JsonPointer path, JsonNode srcValue, JsonNode value) {
|
||||
this.operation = operation;
|
||||
this.path = path;
|
||||
this.value = value;
|
||||
this.srcValue = srcValue;
|
||||
}
|
||||
|
||||
public Operation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public JsonPointer getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public JsonNode getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static Diff generateDiff(Operation replace, JsonPointer path, JsonNode target) {
|
||||
return new Diff(replace, path, target);
|
||||
}
|
||||
|
||||
public static Diff generateDiff(Operation replace, JsonPointer path, JsonNode source, JsonNode target) {
|
||||
return new Diff(replace, path, source, target);
|
||||
}
|
||||
|
||||
JsonPointer getToPath() {
|
||||
return toPath;
|
||||
}
|
||||
|
||||
public JsonNode getSrcValue() {
|
||||
return srcValue;
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package io.metersphere.log.utils.dff;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public enum DiffFlags {
|
||||
|
||||
/**
|
||||
* This flag omits the <i>value</i> field on remove operations.
|
||||
* This is a default flag.
|
||||
*/
|
||||
OMIT_VALUE_ON_REMOVE,
|
||||
|
||||
/**
|
||||
* This flag omits all {@link Operation#MOVE} operations, leaving only
|
||||
* {@link Operation#ADD}, {@link Operation#REMOVE}, {@link Operation#REPLACE}
|
||||
* and {@link Operation#COPY} operations. In other words, without this flag,
|
||||
* {@link Operation#ADD} and {@link Operation#REMOVE} operations are not normalized
|
||||
* into {@link Operation#MOVE} operations.
|
||||
*/
|
||||
OMIT_MOVE_OPERATION,
|
||||
|
||||
/**
|
||||
* This flag omits all {@link Operation#COPY} operations, leaving only
|
||||
* {@link Operation#ADD}, {@link Operation#REMOVE}, {@link Operation#REPLACE}
|
||||
* and {@link Operation#MOVE} operations. In other words, without this flag,
|
||||
* {@link Operation#ADD} operations are not normalized into {@link Operation#COPY}
|
||||
* operations.
|
||||
*/
|
||||
OMIT_COPY_OPERATION,
|
||||
|
||||
/**
|
||||
* This flag adds a <i>fromValue</i> field to all {@link Operation#REPLACE} operations.
|
||||
* <i>fromValue</i> represents the the value replaced by a {@link Operation#REPLACE}
|
||||
* operation, in other words, the original value. This can be useful for debugging
|
||||
* output or custom processing of the diffs by downstream systems.
|
||||
* Please note that this is a non-standard extension to RFC 6902 and will not affect
|
||||
* how patches produced by this library are processed by this or other libraries.
|
||||
*
|
||||
* @since 0.4.1
|
||||
*/
|
||||
ADD_ORIGINAL_VALUE_ON_REPLACE,
|
||||
|
||||
/**
|
||||
* This flag normalizes a {@link Operation#REPLACE} operation into its respective
|
||||
* {@link Operation#REMOVE} and {@link Operation#ADD} operations. Although it adds
|
||||
* a redundant step, this can be useful for auditing systems in which immutability
|
||||
* is a requirement.
|
||||
* <p>
|
||||
* For the flag to work, {@link DiffFlags#ADD_ORIGINAL_VALUE_ON_REPLACE} has to be
|
||||
* enabled as the new instructions in the patch need to grab the old <i>fromValue</i>
|
||||
* {@code "op": "replace", "fromValue": "F1", "value": "F2" }
|
||||
* The above instruction will be split into
|
||||
* {@code "op":"remove", "value":"F1" } and {@code "op":"add", "value":"F2"} respectively.
|
||||
* <p>
|
||||
* Please note that this is a non-standard extension to RFC 6902 and will not affect
|
||||
* how patches produced by this library are processed by this or other libraries.
|
||||
*
|
||||
* @since 0.4.11
|
||||
*/
|
||||
ADD_EXPLICIT_REMOVE_ADD_ON_REPLACE,
|
||||
|
||||
/**
|
||||
* This flag instructs the diff generator to emit {@link Operation#TEST} operations
|
||||
* that validate the state of the source document before each mutation. This can be
|
||||
* useful if you want to ensure data integrity prior to applying the patch.
|
||||
* The resulting patches are standard per RFC 6902 and should be processed correctly
|
||||
* by any compliant library; due to the associated space and performance costs,
|
||||
* however, this isn't default behavior.
|
||||
*
|
||||
* @since 0.4.8
|
||||
*/
|
||||
EMIT_TEST_OPERATIONS;
|
||||
|
||||
|
||||
public static EnumSet<DiffFlags> defaults() {
|
||||
return EnumSet.of(OMIT_VALUE_ON_REMOVE);
|
||||
}
|
||||
|
||||
public static EnumSet<DiffFlags> dontNormalizeOpIntoMoveAndCopy() {
|
||||
return EnumSet.of(OMIT_MOVE_OPERATION, OMIT_COPY_OPERATION);
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
class InPlaceApplyProcessor implements JsonPatchProcessor {
|
||||
|
||||
private JsonNode target;
|
||||
private EnumSet<CompatibilityFlags> flags;
|
||||
|
||||
InPlaceApplyProcessor(JsonNode target) {
|
||||
this(target, CompatibilityFlags.defaults());
|
||||
}
|
||||
|
||||
InPlaceApplyProcessor(JsonNode target, EnumSet<CompatibilityFlags> flags) {
|
||||
this.target = target;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public JsonNode result() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move(JsonPointer fromPath, JsonPointer toPath) throws JsonPointerEvaluationException {
|
||||
JsonNode valueNode = fromPath.evaluate(target);
|
||||
remove(fromPath);
|
||||
set(toPath, valueNode, Operation.MOVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy(JsonPointer fromPath, JsonPointer toPath) throws JsonPointerEvaluationException {
|
||||
JsonNode valueNode = fromPath.evaluate(target);
|
||||
JsonNode valueToCopy = valueNode != null ? valueNode.deepCopy() : null;
|
||||
set(toPath, valueToCopy, Operation.COPY);
|
||||
}
|
||||
|
||||
private static String show(JsonNode value) {
|
||||
if (value == null || value.isNull())
|
||||
return "null";
|
||||
else if (value.isArray())
|
||||
return "array";
|
||||
else if (value.isObject())
|
||||
return "object";
|
||||
else
|
||||
return "value " + value.toString(); // Caveat: numeric may differ from source (e.g. trailing zeros)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void test(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException {
|
||||
JsonNode valueNode = path.evaluate(target);
|
||||
if (!valueNode.equals(value))
|
||||
throw new JsonPatchApplicationException(
|
||||
"Expected " + show(value) + " but found " + show(valueNode), Operation.TEST, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException {
|
||||
set(path, value, Operation.ADD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replace(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException {
|
||||
if (path.isRoot()) {
|
||||
target = value;
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode parentNode = path.getParent().evaluate(target);
|
||||
JsonPointer.RefToken token = path.last();
|
||||
if (parentNode.isObject()) {
|
||||
if (!flags.contains(CompatibilityFlags.ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE) &&
|
||||
!parentNode.has(token.getField()))
|
||||
throw new JsonPatchApplicationException(
|
||||
"Missing field \"" + token.getField() + "\"", Operation.REPLACE, path.getParent());
|
||||
((ObjectNode) parentNode).replace(token.getField(), value);
|
||||
} else if (parentNode.isArray()) {
|
||||
if (token.getIndex() >= parentNode.size())
|
||||
throw new JsonPatchApplicationException(
|
||||
"Array index " + token.getIndex() + " out of bounds", Operation.REPLACE, path.getParent());
|
||||
((ArrayNode) parentNode).set(token.getIndex(), value);
|
||||
} else {
|
||||
throw new JsonPatchApplicationException(
|
||||
"Can't reference past scalar value", Operation.REPLACE, path.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(JsonPointer path) throws JsonPointerEvaluationException {
|
||||
if (path.isRoot())
|
||||
throw new JsonPatchApplicationException("Cannot remove document root", Operation.REMOVE, path);
|
||||
|
||||
JsonNode parentNode = path.getParent().evaluate(target);
|
||||
JsonPointer.RefToken token = path.last();
|
||||
if (parentNode.isObject())
|
||||
((ObjectNode) parentNode).remove(token.getField());
|
||||
else if (parentNode.isArray()) {
|
||||
if (!flags.contains(CompatibilityFlags.REMOVE_NONE_EXISTING_ARRAY_ELEMENT) &&
|
||||
token.getIndex() >= parentNode.size())
|
||||
throw new JsonPatchApplicationException(
|
||||
"Array index " + token.getIndex() + " out of bounds", Operation.REPLACE, path.getParent());
|
||||
((ArrayNode) parentNode).remove(token.getIndex());
|
||||
} else {
|
||||
throw new JsonPatchApplicationException(
|
||||
"Cannot reference past scalar value", Operation.REPLACE, path.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void set(JsonPointer path, JsonNode value, Operation forOp) throws JsonPointerEvaluationException {
|
||||
if (path.isRoot())
|
||||
target = value;
|
||||
else {
|
||||
JsonNode parentNode = path.getParent().evaluate(target);
|
||||
if (!parentNode.isContainerNode())
|
||||
throw new JsonPatchApplicationException("Cannot reference past scalar value", forOp, path.getParent());
|
||||
else if (parentNode.isArray())
|
||||
addToArray(path, value, parentNode);
|
||||
else
|
||||
addToObject(path, parentNode, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void addToObject(JsonPointer path, JsonNode node, JsonNode value) {
|
||||
final ObjectNode target = (ObjectNode) node;
|
||||
String key = path.last().getField();
|
||||
target.set(key, value);
|
||||
}
|
||||
|
||||
private void addToArray(JsonPointer path, JsonNode value, JsonNode parentNode) {
|
||||
final ArrayNode target = (ArrayNode) parentNode;
|
||||
int idx = path.last().getIndex();
|
||||
|
||||
if (idx == JsonPointer.LAST_INDEX) {
|
||||
// see http://tools.ietf.org/html/rfc6902#section-4.1
|
||||
target.add(value);
|
||||
} else {
|
||||
if (idx > target.size())
|
||||
throw new JsonPatchApplicationException(
|
||||
"Array index " + idx + " out of bounds", Operation.ADD, path.getParent());
|
||||
target.insert(idx, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
class InternalUtils {
|
||||
|
||||
static List<JsonNode> toList(ArrayNode input) {
|
||||
int size = input.size();
|
||||
List<JsonNode> toReturn = new ArrayList<JsonNode>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
toReturn.add(input.get(i));
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
static List<JsonNode> longestCommonSubsequence(final List<JsonNode> a, final List<JsonNode> b) {
|
||||
if (a == null || b == null) {
|
||||
throw new NullPointerException("List must not be null for longestCommonSubsequence");
|
||||
}
|
||||
|
||||
List<JsonNode> toReturn = new LinkedList<JsonNode>();
|
||||
|
||||
int aSize = a.size();
|
||||
int bSize = b.size();
|
||||
int temp[][] = new int[aSize + 1][bSize + 1];
|
||||
|
||||
for (int i = 1; i <= aSize; i++) {
|
||||
for (int j = 1; j <= bSize; j++) {
|
||||
if (i == 0 || j == 0) {
|
||||
temp[i][j] = 0;
|
||||
} else if (a.get(i - 1).equals(b.get(j - 1))) {
|
||||
temp[i][j] = temp[i - 1][j - 1] + 1;
|
||||
} else {
|
||||
temp[i][j] = Math.max(temp[i][j - 1], temp[i - 1][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
int i = aSize, j = bSize;
|
||||
while (i > 0 && j > 0) {
|
||||
if (a.get(i - 1).equals(b.get(j - 1))) {
|
||||
toReturn.add(a.get(i - 1));
|
||||
i--;
|
||||
j--;
|
||||
} else if (temp[i - 1][j] > temp[i][j - 1])
|
||||
i--;
|
||||
else
|
||||
j--;
|
||||
}
|
||||
Collections.reverse(toReturn);
|
||||
return toReturn;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
/**
|
||||
* User: holograph
|
||||
* Date: 03/08/16
|
||||
*/
|
||||
public class InvalidJsonPatchException extends JsonPatchApplicationException {
|
||||
public InvalidJsonPatchException(String message) {
|
||||
super(message, null, null);
|
||||
}
|
||||
}
|
|
@ -1,512 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* User: gopi.vishwakarma
|
||||
* Date: 30/07/14
|
||||
*/
|
||||
public final class JsonDiff {
|
||||
|
||||
private final List<Diff> diffs = new ArrayList<Diff>();
|
||||
private final EnumSet<DiffFlags> flags;
|
||||
|
||||
private JsonDiff(EnumSet<DiffFlags> flags) {
|
||||
this.flags = flags.clone();
|
||||
}
|
||||
|
||||
public static JsonNode asJson(final JsonNode source, final JsonNode target) {
|
||||
return asJson(source, target, DiffFlags.defaults());
|
||||
}
|
||||
|
||||
public static JsonNode asJson(final JsonNode source, final JsonNode target, EnumSet<DiffFlags> flags) {
|
||||
JsonDiff diff = new JsonDiff(flags);
|
||||
if (source == null && target != null) {
|
||||
// return add node at root pointing to the target
|
||||
diff.diffs.add(Diff.generateDiff(Operation.ADD, JsonPointer.ROOT, target));
|
||||
}
|
||||
if (source != null && target == null) {
|
||||
// return remove node at root pointing to the source
|
||||
diff.diffs.add(Diff.generateDiff(Operation.REMOVE, JsonPointer.ROOT, source));
|
||||
}
|
||||
if (source != null && target != null) {
|
||||
diff.generateDiffs(JsonPointer.ROOT, source, target);
|
||||
|
||||
if (!flags.contains(DiffFlags.OMIT_MOVE_OPERATION))
|
||||
// Merging remove & add to move operation
|
||||
diff.introduceMoveOperation();
|
||||
|
||||
if (!flags.contains(DiffFlags.OMIT_COPY_OPERATION))
|
||||
// Introduce copy operation
|
||||
diff.introduceCopyOperation(source, target);
|
||||
|
||||
if (flags.contains(DiffFlags.ADD_EXPLICIT_REMOVE_ADD_ON_REPLACE))
|
||||
// Split replace into remove and add instructions
|
||||
diff.introduceExplicitRemoveAndAddOperation();
|
||||
}
|
||||
return diff.getJsonNodes();
|
||||
}
|
||||
|
||||
public static List<Diff> jsonDiff(final JsonNode source, final JsonNode target) {
|
||||
return diff(source, target);
|
||||
}
|
||||
|
||||
public static List<Diff> diff(final JsonNode source, final JsonNode target) {
|
||||
JsonDiff diff = new JsonDiff(DiffFlags.defaults());
|
||||
if (source == null && target != null) {
|
||||
// return add node at root pointing to the target
|
||||
diff.diffs.add(Diff.generateDiff(Operation.ADD, JsonPointer.ROOT, target));
|
||||
}
|
||||
if (source != null && target == null) {
|
||||
// return remove node at root pointing to the source
|
||||
diff.diffs.add(Diff.generateDiff(Operation.REMOVE, JsonPointer.ROOT, source));
|
||||
}
|
||||
if (source != null && target != null) {
|
||||
diff.generateDiffs(JsonPointer.ROOT, source, target);
|
||||
|
||||
if (!DiffFlags.defaults().contains(DiffFlags.OMIT_MOVE_OPERATION))
|
||||
// Merging remove & add to move operation
|
||||
diff.introduceMoveOperation();
|
||||
|
||||
if (!DiffFlags.defaults().contains(DiffFlags.OMIT_COPY_OPERATION))
|
||||
// Introduce copy operation
|
||||
diff.introduceCopyOperation(source, target);
|
||||
|
||||
if (DiffFlags.defaults().contains(DiffFlags.ADD_EXPLICIT_REMOVE_ADD_ON_REPLACE))
|
||||
// Split replace into remove and add instructions
|
||||
diff.introduceExplicitRemoveAndAddOperation();
|
||||
}
|
||||
return diff.getDiffs();
|
||||
}
|
||||
|
||||
private static JsonPointer getMatchingValuePath(Map<JsonNode, JsonPointer> unchangedValues, JsonNode value) {
|
||||
return unchangedValues.get(value);
|
||||
}
|
||||
|
||||
private void introduceCopyOperation(JsonNode source, JsonNode target) {
|
||||
Map<JsonNode, JsonPointer> unchangedValues = getUnchangedPart(source, target);
|
||||
|
||||
for (int i = 0; i < diffs.size(); i++) {
|
||||
Diff diff = diffs.get(i);
|
||||
if (Operation.ADD != diff.getOperation()) continue;
|
||||
|
||||
JsonPointer matchingValuePath = getMatchingValuePath(unchangedValues, diff.getValue());
|
||||
if (matchingValuePath != null && isAllowed(matchingValuePath, diff.getPath())) {
|
||||
// Matching value found; replace add with copy
|
||||
if (flags.contains(DiffFlags.EMIT_TEST_OPERATIONS)) {
|
||||
// Prepend test node
|
||||
diffs.add(i, new Diff(Operation.TEST, matchingValuePath, diff.getValue()));
|
||||
i++;
|
||||
}
|
||||
diffs.set(i, new Diff(Operation.COPY, matchingValuePath, diff.getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isNumber(String str) {
|
||||
int size = str.length();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!Character.isDigit(str.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
// TODO this is quite unclear and needs some serious documentation
|
||||
private static boolean isAllowed(JsonPointer source, JsonPointer destination) {
|
||||
boolean isSame = source.equals(destination);
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
// Hack to fix broken COPY operation, need better handling here
|
||||
while (i < source.size() && j < destination.size()) {
|
||||
JsonPointer.RefToken srcValue = source.get(i);
|
||||
JsonPointer.RefToken dstValue = destination.get(j);
|
||||
String srcStr = srcValue.toString();
|
||||
String dstStr = dstValue.toString();
|
||||
if (isNumber(srcStr) && isNumber(dstStr)) {
|
||||
|
||||
if (srcStr.compareTo(dstStr) > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
|
||||
}
|
||||
return !isSame;
|
||||
}
|
||||
|
||||
private static Map<JsonNode, JsonPointer> getUnchangedPart(JsonNode source, JsonNode target) {
|
||||
Map<JsonNode, JsonPointer> unchangedValues = new HashMap<JsonNode, JsonPointer>();
|
||||
computeUnchangedValues(unchangedValues, JsonPointer.ROOT, source, target);
|
||||
return unchangedValues;
|
||||
}
|
||||
|
||||
private static void computeUnchangedValues(Map<JsonNode, JsonPointer> unchangedValues, JsonPointer path, JsonNode source, JsonNode target) {
|
||||
if (source.equals(target)) {
|
||||
if (!unchangedValues.containsKey(target)) {
|
||||
unchangedValues.put(target, path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final NodeType firstType = NodeType.getNodeType(source);
|
||||
final NodeType secondType = NodeType.getNodeType(target);
|
||||
|
||||
if (firstType == secondType) {
|
||||
switch (firstType) {
|
||||
case OBJECT:
|
||||
computeObject(unchangedValues, path, source, target);
|
||||
break;
|
||||
case ARRAY:
|
||||
computeArray(unchangedValues, path, source, target);
|
||||
break;
|
||||
default:
|
||||
/* nothing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeArray(Map<JsonNode, JsonPointer> unchangedValues, JsonPointer path, JsonNode source, JsonNode target) {
|
||||
final int size = Math.min(source.size(), target.size());
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
JsonPointer currPath = path.append(i);
|
||||
computeUnchangedValues(unchangedValues, currPath, source.get(i), target.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeObject(Map<JsonNode, JsonPointer> unchangedValues, JsonPointer path, JsonNode source, JsonNode target) {
|
||||
final Iterator<String> firstFields = source.fieldNames();
|
||||
while (firstFields.hasNext()) {
|
||||
String name = firstFields.next();
|
||||
if (target.has(name)) {
|
||||
JsonPointer currPath = path.append(name);
|
||||
computeUnchangedValues(unchangedValues, currPath, source.get(name), target.get(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method merge 2 diffs ( remove then add, or vice versa ) with same value into one Move operation,
|
||||
* all the core logic resides here only
|
||||
*/
|
||||
private void introduceMoveOperation() {
|
||||
for (int i = 0; i < diffs.size(); i++) {
|
||||
Diff diff1 = diffs.get(i);
|
||||
|
||||
// if not remove OR add, move to next diff
|
||||
if (!(Operation.REMOVE == diff1.getOperation() ||
|
||||
Operation.ADD == diff1.getOperation())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = i + 1; j < diffs.size(); j++) {
|
||||
Diff diff2 = diffs.get(j);
|
||||
if (!diff1.getValue().equals(diff2.getValue())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Diff moveDiff = null;
|
||||
if (Operation.REMOVE == diff1.getOperation() &&
|
||||
Operation.ADD == diff2.getOperation()) {
|
||||
JsonPointer relativePath = computeRelativePath(diff2.getPath(), i + 1, j - 1, diffs);
|
||||
moveDiff = new Diff(Operation.MOVE, diff1.getPath(), relativePath);
|
||||
|
||||
} else if (Operation.ADD == diff1.getOperation() &&
|
||||
Operation.REMOVE == diff2.getOperation()) {
|
||||
JsonPointer relativePath = computeRelativePath(diff2.getPath(), i, j - 1, diffs); // diff1's add should also be considered
|
||||
moveDiff = new Diff(Operation.MOVE, relativePath, diff1.getPath());
|
||||
}
|
||||
if (moveDiff != null) {
|
||||
diffs.remove(j);
|
||||
diffs.set(i, moveDiff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method splits a {@link Operation#REPLACE} operation within a diff into a {@link Operation#REMOVE}
|
||||
* and {@link Operation#ADD} in order, respectively.
|
||||
* Does nothing if {@link Operation#REPLACE} op does not contain a from value
|
||||
*/
|
||||
private void introduceExplicitRemoveAndAddOperation() {
|
||||
List<Diff> updatedDiffs = new ArrayList<Diff>();
|
||||
for (Diff diff : diffs) {
|
||||
if (!diff.getOperation().equals(Operation.REPLACE) || diff.getSrcValue() == null) {
|
||||
updatedDiffs.add(diff);
|
||||
continue;
|
||||
}
|
||||
//Split into two #REMOVE and #ADD
|
||||
updatedDiffs.add(new Diff(Operation.REMOVE, diff.getPath(), diff.getSrcValue()));
|
||||
updatedDiffs.add(new Diff(Operation.ADD, diff.getPath(), diff.getValue()));
|
||||
}
|
||||
diffs.clear();
|
||||
diffs.addAll(updatedDiffs);
|
||||
}
|
||||
|
||||
//Note : only to be used for arrays
|
||||
//Finds the longest common Ancestor ending at Array
|
||||
private static JsonPointer computeRelativePath(JsonPointer path, int startIdx, int endIdx, List<Diff> diffs) {
|
||||
List<Integer> counters = new ArrayList<Integer>(path.size());
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
counters.add(0);
|
||||
}
|
||||
|
||||
for (int i = startIdx; i <= endIdx; i++) {
|
||||
Diff diff = diffs.get(i);
|
||||
//Adjust relative path according to #ADD and #Remove
|
||||
if (Operation.ADD == diff.getOperation() || Operation.REMOVE == diff.getOperation()) {
|
||||
updatePath(path, diff, counters);
|
||||
}
|
||||
}
|
||||
return updatePathWithCounters(counters, path);
|
||||
}
|
||||
|
||||
private static JsonPointer updatePathWithCounters(List<Integer> counters, JsonPointer path) {
|
||||
List<JsonPointer.RefToken> tokens = path.decompose();
|
||||
for (int i = 0; i < counters.size(); i++) {
|
||||
int value = counters.get(i);
|
||||
if (value != 0) {
|
||||
int currValue = tokens.get(i).getIndex();
|
||||
tokens.set(i, new JsonPointer.RefToken(Integer.toString(currValue + value)));
|
||||
}
|
||||
}
|
||||
return new JsonPointer(tokens);
|
||||
}
|
||||
|
||||
private static void updatePath(JsonPointer path, Diff pseudo, List<Integer> counters) {
|
||||
//find longest common prefix of both the paths
|
||||
|
||||
if (pseudo.getPath().size() <= path.size()) {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < pseudo.getPath().size() - 1; i++) {
|
||||
if (pseudo.getPath().get(i).equals(path.get(i))) {
|
||||
idx = i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx == pseudo.getPath().size() - 2) {
|
||||
if (pseudo.getPath().get(pseudo.getPath().size() - 1).isArrayIndex()) {
|
||||
updateCounters(pseudo, pseudo.getPath().size() - 1, counters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateCounters(Diff pseudo, int idx, List<Integer> counters) {
|
||||
if (Operation.ADD == pseudo.getOperation()) {
|
||||
counters.set(idx, counters.get(idx) - 1);
|
||||
} else {
|
||||
if (Operation.REMOVE == pseudo.getOperation()) {
|
||||
counters.set(idx, counters.get(idx) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayNode getJsonNodes() {
|
||||
JsonNodeFactory FACTORY = JsonNodeFactory.instance;
|
||||
final ArrayNode patch = FACTORY.arrayNode();
|
||||
for (Diff diff : diffs) {
|
||||
ObjectNode jsonNode = getJsonNode(FACTORY, diff, flags);
|
||||
patch.add(jsonNode);
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
private List<Diff> getDiffs() {
|
||||
return diffs;
|
||||
}
|
||||
|
||||
private static ObjectNode getJsonNode(JsonNodeFactory FACTORY, Diff diff, EnumSet<DiffFlags> flags) {
|
||||
ObjectNode jsonNode = FACTORY.objectNode();
|
||||
jsonNode.put(Constants.OP, diff.getOperation().rfcName());
|
||||
|
||||
switch (diff.getOperation()) {
|
||||
case MOVE:
|
||||
case COPY:
|
||||
jsonNode.put(Constants.FROM, diff.getPath().toString()); // required {from} only in case of Move Operation
|
||||
jsonNode.put(Constants.PATH, diff.getToPath().toString()); // destination Path
|
||||
break;
|
||||
|
||||
case REMOVE:
|
||||
jsonNode.put(Constants.PATH, diff.getPath().toString());
|
||||
if (!flags.contains(DiffFlags.OMIT_VALUE_ON_REMOVE))
|
||||
jsonNode.set(Constants.VALUE, diff.getValue());
|
||||
break;
|
||||
|
||||
case REPLACE:
|
||||
if (flags.contains(DiffFlags.ADD_ORIGINAL_VALUE_ON_REPLACE)) {
|
||||
jsonNode.set(Constants.FROM_VALUE, diff.getSrcValue());
|
||||
}
|
||||
case ADD:
|
||||
case TEST:
|
||||
jsonNode.put(Constants.PATH, diff.getPath().toString());
|
||||
jsonNode.set(Constants.VALUE, diff.getValue());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Safety net
|
||||
throw new IllegalArgumentException("Unknown operation specified:" + diff.getOperation());
|
||||
}
|
||||
|
||||
return jsonNode;
|
||||
}
|
||||
|
||||
private void generateDiffs(JsonPointer path, JsonNode source, JsonNode target) {
|
||||
if (!source.equals(target)) {
|
||||
final NodeType sourceType = NodeType.getNodeType(source);
|
||||
final NodeType targetType = NodeType.getNodeType(target);
|
||||
|
||||
if (sourceType == NodeType.ARRAY && targetType == NodeType.ARRAY) {
|
||||
//both are arrays
|
||||
compareArray(path, source, target);
|
||||
} else if (sourceType == NodeType.OBJECT && targetType == NodeType.OBJECT) {
|
||||
//both are json
|
||||
compareObjects(path, source, target);
|
||||
} else {
|
||||
//can be replaced
|
||||
if (flags.contains(DiffFlags.EMIT_TEST_OPERATIONS))
|
||||
diffs.add(new Diff(Operation.TEST, path, source));
|
||||
diffs.add(Diff.generateDiff(Operation.REPLACE, path, source, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void compareArray(JsonPointer path, JsonNode source, JsonNode target) {
|
||||
List<JsonNode> lcs = getLCS(source, target);
|
||||
int srcIdx = 0;
|
||||
int targetIdx = 0;
|
||||
int lcsIdx = 0;
|
||||
int srcSize = source.size();
|
||||
int targetSize = target.size();
|
||||
int lcsSize = lcs.size();
|
||||
|
||||
int pos = 0;
|
||||
while (lcsIdx < lcsSize) {
|
||||
JsonNode lcsNode = lcs.get(lcsIdx);
|
||||
JsonNode srcNode = source.get(srcIdx);
|
||||
JsonNode targetNode = target.get(targetIdx);
|
||||
|
||||
|
||||
if (lcsNode.equals(srcNode) && lcsNode.equals(targetNode)) { // Both are same as lcs node, nothing to do here
|
||||
srcIdx++;
|
||||
targetIdx++;
|
||||
lcsIdx++;
|
||||
pos++;
|
||||
} else {
|
||||
if (lcsNode.equals(srcNode)) { // src node is same as lcs, but not targetNode
|
||||
//addition
|
||||
JsonPointer currPath = path.append(pos);
|
||||
diffs.add(Diff.generateDiff(Operation.ADD, currPath, targetNode));
|
||||
pos++;
|
||||
targetIdx++;
|
||||
} else if (lcsNode.equals(targetNode)) { //targetNode node is same as lcs, but not src
|
||||
//removal,
|
||||
JsonPointer currPath = path.append(pos);
|
||||
if (flags.contains(DiffFlags.EMIT_TEST_OPERATIONS))
|
||||
diffs.add(new Diff(Operation.TEST, currPath, srcNode));
|
||||
diffs.add(Diff.generateDiff(Operation.REMOVE, currPath, srcNode));
|
||||
srcIdx++;
|
||||
} else {
|
||||
JsonPointer currPath = path.append(pos);
|
||||
//both are unequal to lcs node
|
||||
generateDiffs(currPath, srcNode, targetNode);
|
||||
srcIdx++;
|
||||
targetIdx++;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((srcIdx < srcSize) && (targetIdx < targetSize)) {
|
||||
JsonNode srcNode = source.get(srcIdx);
|
||||
JsonNode targetNode = target.get(targetIdx);
|
||||
JsonPointer currPath = path.append(pos);
|
||||
generateDiffs(currPath, srcNode, targetNode);
|
||||
srcIdx++;
|
||||
targetIdx++;
|
||||
pos++;
|
||||
}
|
||||
pos = addRemaining(path, target, pos, targetIdx, targetSize);
|
||||
removeRemaining(path, pos, srcIdx, srcSize, source);
|
||||
}
|
||||
|
||||
private void removeRemaining(JsonPointer path, int pos, int srcIdx, int srcSize, JsonNode source) {
|
||||
while (srcIdx < srcSize) {
|
||||
JsonPointer currPath = path.append(pos);
|
||||
if (flags.contains(DiffFlags.EMIT_TEST_OPERATIONS))
|
||||
diffs.add(new Diff(Operation.TEST, currPath, source.get(srcIdx)));
|
||||
diffs.add(Diff.generateDiff(Operation.REMOVE, currPath, source.get(srcIdx)));
|
||||
srcIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
private int addRemaining(JsonPointer path, JsonNode target, int pos, int targetIdx, int targetSize) {
|
||||
while (targetIdx < targetSize) {
|
||||
JsonNode jsonNode = target.get(targetIdx);
|
||||
JsonPointer currPath = path.append(pos);
|
||||
diffs.add(Diff.generateDiff(Operation.ADD, currPath, jsonNode.deepCopy()));
|
||||
pos++;
|
||||
targetIdx++;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private void compareObjects(JsonPointer path, JsonNode source, JsonNode target) {
|
||||
Iterator<String> keysFromSrc = source.fieldNames();
|
||||
while (keysFromSrc.hasNext()) {
|
||||
String key = keysFromSrc.next();
|
||||
if (!target.has(key)) {
|
||||
//remove case
|
||||
JsonPointer currPath = path.append(key);
|
||||
if (flags.contains(DiffFlags.EMIT_TEST_OPERATIONS))
|
||||
diffs.add(new Diff(Operation.TEST, currPath, source.get(key)));
|
||||
diffs.add(Diff.generateDiff(Operation.REMOVE, currPath, source.get(key)));
|
||||
continue;
|
||||
}
|
||||
JsonPointer currPath = path.append(key);
|
||||
generateDiffs(currPath, source.get(key), target.get(key));
|
||||
}
|
||||
Iterator<String> keysFromTarget = target.fieldNames();
|
||||
while (keysFromTarget.hasNext()) {
|
||||
String key = keysFromTarget.next();
|
||||
if (!source.has(key)) {
|
||||
//add case
|
||||
JsonPointer currPath = path.append(key);
|
||||
diffs.add(Diff.generateDiff(Operation.ADD, currPath, target.get(key)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<JsonNode> getLCS(final JsonNode first, final JsonNode second) {
|
||||
return ListUtils.longestCommonSubsequence(InternalUtils.toList((ArrayNode) first), InternalUtils.toList((ArrayNode) second));
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* User: gopi.vishwakarma
|
||||
* Date: 31/07/14
|
||||
*/
|
||||
public final class JsonPatch {
|
||||
|
||||
private JsonPatch() {
|
||||
}
|
||||
|
||||
private static JsonNode getPatchAttr(JsonNode jsonNode, String attr) {
|
||||
JsonNode child = jsonNode.get(attr);
|
||||
if (child == null)
|
||||
throw new InvalidJsonPatchException("Invalid JSON Patch payload (missing '" + attr + "' field)");
|
||||
return child;
|
||||
}
|
||||
|
||||
private static JsonNode getPatchAttrWithDefault(JsonNode jsonNode, String attr, JsonNode defaultValue) {
|
||||
JsonNode child = jsonNode.get(attr);
|
||||
if (child == null)
|
||||
return defaultValue;
|
||||
else
|
||||
return child;
|
||||
}
|
||||
|
||||
private static void process(JsonNode patch, JsonPatchProcessor processor, EnumSet<CompatibilityFlags> flags)
|
||||
throws InvalidJsonPatchException {
|
||||
|
||||
if (!patch.isArray())
|
||||
throw new InvalidJsonPatchException("Invalid JSON Patch payload (not an array)");
|
||||
Iterator<JsonNode> operations = patch.iterator();
|
||||
while (operations.hasNext()) {
|
||||
JsonNode jsonNode = operations.next();
|
||||
if (!jsonNode.isObject()) throw new InvalidJsonPatchException("Invalid JSON Patch payload (not an object)");
|
||||
Operation operation = Operation.fromRfcName(getPatchAttr(jsonNode, Constants.OP).textValue());
|
||||
JsonPointer path = JsonPointer.parse(getPatchAttr(jsonNode, Constants.PATH).textValue());
|
||||
|
||||
try {
|
||||
switch (operation) {
|
||||
case REMOVE: {
|
||||
processor.remove(path);
|
||||
break;
|
||||
}
|
||||
|
||||
case ADD: {
|
||||
JsonNode value;
|
||||
if (!flags.contains(CompatibilityFlags.MISSING_VALUES_AS_NULLS))
|
||||
value = getPatchAttr(jsonNode, Constants.VALUE);
|
||||
else
|
||||
value = getPatchAttrWithDefault(jsonNode, Constants.VALUE, NullNode.getInstance());
|
||||
processor.add(path, value.deepCopy());
|
||||
break;
|
||||
}
|
||||
|
||||
case REPLACE: {
|
||||
JsonNode value;
|
||||
if (!flags.contains(CompatibilityFlags.MISSING_VALUES_AS_NULLS))
|
||||
value = getPatchAttr(jsonNode, Constants.VALUE);
|
||||
else
|
||||
value = getPatchAttrWithDefault(jsonNode, Constants.VALUE, NullNode.getInstance());
|
||||
processor.replace(path, value.deepCopy());
|
||||
break;
|
||||
}
|
||||
|
||||
case MOVE: {
|
||||
JsonPointer fromPath = JsonPointer.parse(getPatchAttr(jsonNode, Constants.FROM).textValue());
|
||||
processor.move(fromPath, path);
|
||||
break;
|
||||
}
|
||||
|
||||
case COPY: {
|
||||
JsonPointer fromPath = JsonPointer.parse(getPatchAttr(jsonNode, Constants.FROM).textValue());
|
||||
processor.copy(fromPath, path);
|
||||
break;
|
||||
}
|
||||
|
||||
case TEST: {
|
||||
JsonNode value;
|
||||
if (!flags.contains(CompatibilityFlags.MISSING_VALUES_AS_NULLS))
|
||||
value = getPatchAttr(jsonNode, Constants.VALUE);
|
||||
else
|
||||
value = getPatchAttrWithDefault(jsonNode, Constants.VALUE, NullNode.getInstance());
|
||||
processor.test(path, value.deepCopy());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonPointerEvaluationException e) {
|
||||
throw new JsonPatchApplicationException(e.getMessage(), operation, e.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void validate(JsonNode patch, EnumSet<CompatibilityFlags> flags) throws InvalidJsonPatchException {
|
||||
process(patch, NoopProcessor.INSTANCE, flags);
|
||||
}
|
||||
|
||||
public static void validate(JsonNode patch) throws InvalidJsonPatchException {
|
||||
validate(patch, CompatibilityFlags.defaults());
|
||||
}
|
||||
|
||||
public static JsonNode apply(JsonNode patch, JsonNode source, EnumSet<CompatibilityFlags> flags) throws JsonPatchApplicationException {
|
||||
CopyingApplyProcessor processor = new CopyingApplyProcessor(source, flags);
|
||||
process(patch, processor, flags);
|
||||
return processor.result();
|
||||
}
|
||||
|
||||
public static JsonNode apply(JsonNode patch, JsonNode source) throws JsonPatchApplicationException {
|
||||
return apply(patch, source, CompatibilityFlags.defaults());
|
||||
}
|
||||
|
||||
public static void applyInPlace(JsonNode patch, JsonNode source) {
|
||||
applyInPlace(patch, source, CompatibilityFlags.defaults());
|
||||
}
|
||||
|
||||
public static void applyInPlace(JsonNode patch, JsonNode source, EnumSet<CompatibilityFlags> flags) {
|
||||
InPlaceApplyProcessor processor = new InPlaceApplyProcessor(source, flags);
|
||||
process(patch, processor, flags);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
/**
|
||||
* User: holograph
|
||||
* Date: 03/08/16
|
||||
*/
|
||||
public class JsonPatchApplicationException extends RuntimeException {
|
||||
Operation operation;
|
||||
JsonPointer path;
|
||||
|
||||
public JsonPatchApplicationException(String message, Operation operation, JsonPointer path) {
|
||||
super(message);
|
||||
this.operation = operation;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (operation != null) sb.append('[').append(operation).append(" Operation] ");
|
||||
sb.append(getMessage());
|
||||
if (path != null) sb.append(" at ").append(path.isRoot() ? "root" : path);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
interface JsonPatchProcessor {
|
||||
void remove(JsonPointer path) throws JsonPointerEvaluationException;
|
||||
void replace(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException;
|
||||
void add(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException;
|
||||
void move(JsonPointer fromPath, JsonPointer toPath) throws JsonPointerEvaluationException;
|
||||
void copy(JsonPointer fromPath, JsonPointer toPath) throws JsonPointerEvaluationException;
|
||||
void test(JsonPointer path, JsonNode value) throws JsonPointerEvaluationException;
|
||||
}
|
|
@ -1,346 +0,0 @@
|
|||
package io.metersphere.log.utils.dff;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Implements RFC 6901 (JSON Pointer)
|
||||
*
|
||||
* <p>For full details, please refer to <a href="https://tools.ietf.org/html/rfc6901">RFC 6901</a>.
|
||||
*
|
||||
* <p></p>Generally, a JSON Pointer is a string representation of a path into a JSON document.
|
||||
* This class implements the RFC as closely as possible, and offers several helpers and
|
||||
* utility methods on top of it:
|
||||
*
|
||||
* <pre>
|
||||
* // Parse, build or render a JSON pointer
|
||||
* String path = "/a/0/b/1";
|
||||
* JsonPointer ptr1 = JsonPointer.{@link #parse}(path);
|
||||
* JsonPointer ptr2 = JsonPointer.{@link #ROOT}.append("a").append(0).append("b").append(1);
|
||||
* assert(ptr1.equals(ptr2));
|
||||
* assert(path.equals(ptr1.toString()));
|
||||
* assert(path.equals(ptr2.toString()));
|
||||
*
|
||||
* // Evaluate a JSON pointer against a live document
|
||||
* ObjectMapper om = new ObjectMapper();
|
||||
* JsonNode doc = om.readTree("{\"foo\":[\"bar\", \"baz\"]}");
|
||||
* JsonNode baz = JsonPointer.parse("/foo/1").{@link #evaluate(JsonNode) evaluate}(doc);
|
||||
* assert(baz.textValue().equals("baz"));
|
||||
* </pre>
|
||||
*
|
||||
* <p>Instances of {@link JsonPointer} and its constituent {@link RefToken}s are <b>immutable</b>.
|
||||
*
|
||||
* @since 0.4.8
|
||||
*/
|
||||
class JsonPointer {
|
||||
private final RefToken[] tokens;
|
||||
|
||||
/** A JSON pointer representing the root node of a JSON document */
|
||||
public final static JsonPointer ROOT = new JsonPointer(new RefToken[] {});
|
||||
|
||||
private JsonPointer(RefToken[] tokens) {
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new pointer from a list of reference tokens.
|
||||
*
|
||||
* @param tokens The list of reference tokens from which to construct the new pointer. This list is not modified.
|
||||
*/
|
||||
public JsonPointer(List<RefToken> tokens) {
|
||||
this.tokens = tokens.toArray(new RefToken[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a valid string representation of a JSON Pointer.
|
||||
*
|
||||
* @param path The string representation to be parsed.
|
||||
* @return An instance of {@link JsonPointer} conforming to the specified string representation.
|
||||
* @throws IllegalArgumentException The specified JSON Pointer is invalid.
|
||||
*/
|
||||
public static JsonPointer parse(String path) throws IllegalArgumentException {
|
||||
StringBuilder reftoken = null;
|
||||
List<RefToken> result = new ArrayList<RefToken>();
|
||||
|
||||
for (int i = 0; i < path.length(); ++i) {
|
||||
char c = path.charAt(i);
|
||||
|
||||
// Require leading slash
|
||||
if (i == 0) {
|
||||
if (c != '/') throw new IllegalArgumentException("Missing leading slash");
|
||||
reftoken = new StringBuilder();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
// Escape sequences
|
||||
case '~':
|
||||
switch (path.charAt(++i)) {
|
||||
case '0': reftoken.append('~'); break;
|
||||
case '1': reftoken.append('/'); break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid escape sequence ~" + path.charAt(i) + " at index " + i);
|
||||
}
|
||||
break;
|
||||
|
||||
// New reftoken
|
||||
case '/':
|
||||
result.add(new RefToken(reftoken.toString()));
|
||||
reftoken.setLength(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
reftoken.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (reftoken == null)
|
||||
return ROOT;
|
||||
|
||||
result.add(RefToken.parse(reftoken.toString()));
|
||||
return new JsonPointer(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether or not this instance points to the root of a JSON document.
|
||||
* @return {@code true} if this pointer represents the root node, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return tokens.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON pointer to the specified field of the object referenced by this instance.
|
||||
*
|
||||
* @param field The desired field name, or any valid JSON Pointer reference token
|
||||
* @return The new {@link JsonPointer} instance.
|
||||
*/
|
||||
JsonPointer append(String field) {
|
||||
RefToken[] newTokens = Arrays.copyOf(tokens, tokens.length + 1);
|
||||
newTokens[tokens.length] = new RefToken(field);
|
||||
return new JsonPointer(newTokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON pointer to an indexed element of the array referenced by this instance.
|
||||
*
|
||||
* @param index The desired index, or {@link #LAST_INDEX} to point past the end of the array.
|
||||
* @return The new {@link JsonPointer} instance.
|
||||
*/
|
||||
JsonPointer append(int index) {
|
||||
return append(Integer.toString(index));
|
||||
}
|
||||
|
||||
/** Returns the number of reference tokens comprising this instance. */
|
||||
int size() {
|
||||
return tokens.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this instance
|
||||
*
|
||||
* @return
|
||||
* An <a href="https://tools.ietf.org/html/rfc6901#section-5">RFC 6901 compliant</a> string
|
||||
* representation of this JSON pointer.
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (RefToken token : tokens) {
|
||||
sb.append('/');
|
||||
sb.append(token);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decomposes this JSON pointer into its reference tokens.
|
||||
*
|
||||
* @return A list of {@link RefToken}s. Modifications to this list do not affect this instance.
|
||||
*/
|
||||
public List<RefToken> decompose() {
|
||||
return Arrays.asList(tokens.clone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the reference token at the specified index.
|
||||
*
|
||||
* @param index The desired reference token index.
|
||||
* @return The specified instance of {@link RefToken}.
|
||||
* @throws IndexOutOfBoundsException The specified index is illegal.
|
||||
*/
|
||||
public RefToken get(int index) throws IndexOutOfBoundsException {
|
||||
if (index < 0 || index >= tokens.length) throw new IndexOutOfBoundsException("Illegal index: " + index);
|
||||
return tokens[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the last reference token for this JSON pointer.
|
||||
*
|
||||
* @return The last {@link RefToken} comprising this instance.
|
||||
* @throws IllegalStateException Last cannot be called on {@link #ROOT root} pointers.
|
||||
*/
|
||||
public RefToken last() {
|
||||
if (isRoot()) throw new IllegalStateException("Root pointers contain no reference tokens");
|
||||
return tokens[tokens.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON pointer to the parent of the node represented by this instance.
|
||||
*
|
||||
* The parent of the {@link #ROOT root} pointer is the root pointer itself.
|
||||
*
|
||||
* @return A {@link JsonPointer} to the parent node.
|
||||
*/
|
||||
public JsonPointer getParent() {
|
||||
return isRoot() ? this : new JsonPointer(Arrays.copyOf(tokens, tokens.length - 1));
|
||||
}
|
||||
|
||||
private void error(int atToken, String message, JsonNode document) throws JsonPointerEvaluationException {
|
||||
throw new JsonPointerEvaluationException(
|
||||
message,
|
||||
new JsonPointer(Arrays.copyOf(tokens, atToken)),
|
||||
document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a target document and resolves the node represented by this instance.
|
||||
*
|
||||
* The evaluation semantics are described in
|
||||
* <a href="https://tools.ietf.org/html/rfc6901#section-4">RFC 6901 sectino 4</a>.
|
||||
*
|
||||
* @param document The target document against which to evaluate the JSON pointer.
|
||||
* @return The {@link JsonNode} resolved by evaluating this JSON pointer.
|
||||
* @throws JsonPointerEvaluationException The pointer could not be evaluated.
|
||||
*/
|
||||
public JsonNode evaluate(final JsonNode document) throws JsonPointerEvaluationException {
|
||||
JsonNode current = document;
|
||||
|
||||
for (int idx = 0; idx < tokens.length; ++idx) {
|
||||
final RefToken token = tokens[idx];
|
||||
|
||||
if (current.isArray()) {
|
||||
if (!token.isArrayIndex())
|
||||
error(idx, "Can't reference field \"" + token.getField() + "\" on array", document);
|
||||
if (token.getIndex() == LAST_INDEX || token.getIndex() >= current.size())
|
||||
error(idx, "Array index " + token.toString() + " is out of bounds", document);
|
||||
current = current.get(token.getIndex());
|
||||
}
|
||||
else if (current.isObject()) {
|
||||
if (!current.has(token.getField()))
|
||||
error(idx,"Missing field \"" + token.getField() + "\"", document);
|
||||
current = current.get(token.getField());
|
||||
}
|
||||
else
|
||||
error(idx, "Can't reference past scalar value", document);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
JsonPointer that = (JsonPointer) o;
|
||||
|
||||
// Probably incorrect - comparing Object[] arrays with Arrays.equals
|
||||
return Arrays.equals(tokens, that.tokens);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(tokens);
|
||||
}
|
||||
|
||||
/** Represents a single JSON Pointer reference token. */
|
||||
static class RefToken {
|
||||
private String decodedToken;
|
||||
transient private Integer index = null;
|
||||
|
||||
public RefToken(String decodedToken) {
|
||||
if (decodedToken == null) throw new IllegalArgumentException("Token can't be null");
|
||||
this.decodedToken = decodedToken;
|
||||
}
|
||||
|
||||
private static final Pattern DECODED_TILDA_PATTERN = Pattern.compile("~0");
|
||||
private static final Pattern DECODED_SLASH_PATTERN = Pattern.compile("~1");
|
||||
|
||||
private static String decodePath(Object object) {
|
||||
String path = object.toString(); // see http://tools.ietf.org/html/rfc6901#section-4
|
||||
path = DECODED_SLASH_PATTERN.matcher(path).replaceAll("/");
|
||||
return DECODED_TILDA_PATTERN.matcher(path).replaceAll("~");
|
||||
}
|
||||
|
||||
private static final Pattern ENCODED_TILDA_PATTERN = Pattern.compile("~");
|
||||
private static final Pattern ENCODED_SLASH_PATTERN = Pattern.compile("/");
|
||||
|
||||
private static String encodePath(Object object) {
|
||||
String path = object.toString(); // see http://tools.ietf.org/html/rfc6901#section-4
|
||||
path = ENCODED_TILDA_PATTERN.matcher(path).replaceAll("~0");
|
||||
return ENCODED_SLASH_PATTERN.matcher(path).replaceAll("~1");
|
||||
}
|
||||
|
||||
private static final Pattern VALID_ARRAY_IND = Pattern.compile("-|0|(?:[1-9][0-9]*)");
|
||||
|
||||
public static RefToken parse(String rawToken) {
|
||||
if (rawToken == null) throw new IllegalArgumentException("Token can't be null");
|
||||
return new RefToken(decodePath(rawToken));
|
||||
}
|
||||
|
||||
public boolean isArrayIndex() {
|
||||
if (index != null) return true;
|
||||
Matcher matcher = VALID_ARRAY_IND.matcher(decodedToken);
|
||||
if (matcher.matches()) {
|
||||
index = matcher.group().equals("-") ? LAST_INDEX : Integer.parseInt(matcher.group());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
if (!isArrayIndex()) throw new IllegalStateException("Object operation on array target");
|
||||
return index;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return decodedToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return encodePath(decodedToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RefToken refToken = (RefToken) o;
|
||||
|
||||
return decodedToken.equals(refToken.decodedToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return decodedToken.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an array index pointing past the end of the array.
|
||||
*
|
||||
* Such an index is represented by the JSON pointer reference token "{@code -}"; see
|
||||
* <a href="https://tools.ietf.org/html/rfc6901#section-4">RFC 6901 section 4</a> for
|
||||
* more details.
|
||||
*/
|
||||
final static int LAST_INDEX = Integer.MIN_VALUE;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class JsonPointerEvaluationException extends Exception {
|
||||
private final JsonPointer path;
|
||||
private final JsonNode target;
|
||||
|
||||
public JsonPointerEvaluationException(String message, JsonPointer path, JsonNode target) {
|
||||
super(message);
|
||||
this.path = path;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public JsonPointer getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public JsonNode getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
enum NodeType {
|
||||
/**
|
||||
* Array nodes
|
||||
*/
|
||||
ARRAY("array"),
|
||||
/**
|
||||
* Boolean nodes
|
||||
*/
|
||||
BOOLEAN("boolean"),
|
||||
/**
|
||||
* Integer nodes
|
||||
*/
|
||||
INTEGER("integer"),
|
||||
/**
|
||||
* Number nodes (ie, decimal numbers)
|
||||
*/
|
||||
NULL("null"),
|
||||
/**
|
||||
* Object nodes
|
||||
*/
|
||||
NUMBER("number"),
|
||||
/**
|
||||
* Null nodes
|
||||
*/
|
||||
OBJECT("object"),
|
||||
/**
|
||||
* String nodes
|
||||
*/
|
||||
STRING("string");
|
||||
|
||||
/**
|
||||
* The name for this type, as encountered in a JSON schema
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
private static final Map<JsonToken, NodeType> TOKEN_MAP
|
||||
= new EnumMap<JsonToken, NodeType>(JsonToken.class);
|
||||
|
||||
static {
|
||||
TOKEN_MAP.put(JsonToken.START_ARRAY, ARRAY);
|
||||
TOKEN_MAP.put(JsonToken.VALUE_TRUE, BOOLEAN);
|
||||
TOKEN_MAP.put(JsonToken.VALUE_FALSE, BOOLEAN);
|
||||
TOKEN_MAP.put(JsonToken.VALUE_NUMBER_INT, INTEGER);
|
||||
TOKEN_MAP.put(JsonToken.VALUE_NUMBER_FLOAT, NUMBER);
|
||||
TOKEN_MAP.put(JsonToken.VALUE_NULL, NULL);
|
||||
TOKEN_MAP.put(JsonToken.START_OBJECT, OBJECT);
|
||||
TOKEN_MAP.put(JsonToken.VALUE_STRING, STRING);
|
||||
|
||||
}
|
||||
|
||||
NodeType(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static NodeType getNodeType(final JsonNode node) {
|
||||
final JsonToken token = node.asToken();
|
||||
final NodeType ret = TOKEN_MAP.get(token);
|
||||
if (ret == null) throw new NullPointerException("unhandled token type " + token);
|
||||
return ret;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* A JSON patch processor that does nothing, intended for testing and validation.
|
||||
*/
|
||||
public class NoopProcessor implements JsonPatchProcessor {
|
||||
static final NoopProcessor INSTANCE;
|
||||
static {
|
||||
INSTANCE = new NoopProcessor();
|
||||
}
|
||||
|
||||
@Override public void remove(JsonPointer path) {}
|
||||
@Override public void replace(JsonPointer path, JsonNode value) {}
|
||||
@Override public void add(JsonPointer path, JsonNode value) {}
|
||||
@Override public void move(JsonPointer fromPath, JsonPointer toPath) {}
|
||||
@Override public void copy(JsonPointer fromPath, JsonPointer toPath) {}
|
||||
@Override public void test(JsonPointer path, JsonNode value) {}
|
||||
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 flipkart.com zjsonpatch.
|
||||
*
|
||||
* Licensed 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 io.metersphere.log.utils.dff;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: gopi.vishwakarma
|
||||
* Date: 30/07/14
|
||||
*/
|
||||
public enum Operation {
|
||||
ADD("添加"),
|
||||
REMOVE("移除"),
|
||||
REPLACE("修改"),
|
||||
MOVE("移动"),
|
||||
COPY("复制"),
|
||||
TEST("测试");
|
||||
|
||||
private final static Map<String, Operation> OPS = createImmutableMap();
|
||||
|
||||
private static Map<String, Operation> createImmutableMap() {
|
||||
Map<String, Operation> map = new HashMap<String, Operation>();
|
||||
map.put(ADD.rfcName, ADD);
|
||||
map.put(REMOVE.rfcName, REMOVE);
|
||||
map.put(REPLACE.rfcName, REPLACE);
|
||||
map.put(MOVE.rfcName, MOVE);
|
||||
map.put(COPY.rfcName, COPY);
|
||||
map.put(TEST.rfcName, TEST);
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
private String rfcName;
|
||||
|
||||
Operation(String rfcName) {
|
||||
this.rfcName = rfcName;
|
||||
}
|
||||
|
||||
public static Operation fromRfcName(String rfcName) throws InvalidJsonPatchException {
|
||||
if (rfcName == null) throw new InvalidJsonPatchException("rfcName cannot be null");
|
||||
Operation op = OPS.get(rfcName.toLowerCase());
|
||||
if (op == null) throw new InvalidJsonPatchException("unknown / unsupported operation " + rfcName);
|
||||
return op;
|
||||
}
|
||||
|
||||
public String rfcName() {
|
||||
return this.rfcName;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
class ArrNode extends Node {
|
||||
|
||||
int index;
|
||||
|
||||
ArrNode(Node parent, int index) {
|
||||
super(parent);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
void rehash(Node newParent) {
|
||||
this.parent = newParent;
|
||||
this.parentHashCode = newParent.hashCode;
|
||||
int i = this.parentHashCode;
|
||||
|
||||
i = i * 31 + ArrNode.class.hashCode();
|
||||
hashCode = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
int doHash(boolean indexed) {
|
||||
|
||||
// this must either be the first node in which case passing
|
||||
// false to lastArrNode must be correct, or it isn't
|
||||
// in which case passing false is also correct.
|
||||
int i = parent.doHash(indexed);
|
||||
|
||||
i = i * 31 + ArrNode.class.hashCode();
|
||||
if (indexed) {
|
||||
int adjusted = index;
|
||||
i = i * 31 + adjusted;
|
||||
}
|
||||
return i;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + index;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
public class DiffTest {
|
||||
public static void main(String[] args) {
|
||||
GsonDiff diff = new GsonDiff();
|
||||
|
||||
String newValue= "{\n" +
|
||||
" \"username\": \"zyy\",\n" +
|
||||
" \"username2\": \"zyy\",\n" +
|
||||
" \"password\": \"Calong@2015\"\n" +
|
||||
"}";
|
||||
String oldValue = "{\n" +
|
||||
" \"username\": \"zyy\",\n" +
|
||||
" \"username1\": \"zyy\",\n" +
|
||||
" \"password\": \"Calong@201512\"\n" +
|
||||
"}";
|
||||
String d = diff.diff(oldValue,newValue);
|
||||
|
||||
System.out.println(d);
|
||||
|
||||
System.out.println(diff.apply(newValue,d));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.gson.GsonWrapper;
|
||||
|
||||
public class GsonDiff extends JsonDiff {
|
||||
|
||||
public GsonDiff() {
|
||||
super(new GsonWrapper());
|
||||
}
|
||||
|
||||
public JsonObject diff(JsonElement from, JsonElement to) throws IllegalArgumentException {
|
||||
return (JsonObject) super.diff(from, to);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,500 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/*
|
||||
Copyright (c) 2009, incava.org
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of incava.org nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compares two lists, returning a list of the additions, changes, and deletions between them. A <code>Comparator</code>
|
||||
* may be passed as an argument to the constructor, and will thus be used. If not provided, the initial value in the
|
||||
* <code>a</code> ("from") list will be looked at to see if it supports the <code>Comparable</code> interface. If so,
|
||||
* its <code>equals</code> and <code>compareTo</code> methods will be invoked on the instances in the "from" and "to"
|
||||
* lists; otherwise, for speed, hash codes from the objects will be used instead for comparison.
|
||||
*
|
||||
* <p>
|
||||
* The file FileDiff.java shows an example usage of this class, in an application similar to the Unix "diff" program.
|
||||
* </p>
|
||||
*/
|
||||
public class IncavaDiff<Type>
|
||||
{
|
||||
|
||||
/**
|
||||
* The source list, AKA the "from" values.
|
||||
*/
|
||||
protected List<Type> a;
|
||||
|
||||
/**
|
||||
* The target list, AKA the "to" values.
|
||||
*/
|
||||
protected List<Type> b;
|
||||
|
||||
/**
|
||||
* The list of differences, as <code>Difference</code> instances.
|
||||
*/
|
||||
protected List<IncavaEntry> diffs = new ArrayList<IncavaEntry>();
|
||||
|
||||
/**
|
||||
* The pending, uncommitted difference.
|
||||
*/
|
||||
private IncavaEntry pending;
|
||||
|
||||
/**
|
||||
* The comparator used, if any.
|
||||
*/
|
||||
private Comparator<Type> comparator;
|
||||
|
||||
/**
|
||||
* The thresholds.
|
||||
*/
|
||||
private TreeMap<Integer, Integer> thresh;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs the Diff object for the two arrays, using the given comparator.
|
||||
*/
|
||||
public IncavaDiff(Type[] a, Type[] b, Comparator<Type> comp)
|
||||
{
|
||||
this(Arrays.asList(a), Arrays.asList(b), comp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs the Diff object for the two arrays, using the default comparison mechanism between the objects, such
|
||||
* as <code>equals</code> and <code>compareTo</code>.
|
||||
*/
|
||||
public IncavaDiff(Type[] a, Type[] b)
|
||||
{
|
||||
this(a, b, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs the Diff object for the two lists, using the given comparator.
|
||||
*/
|
||||
public IncavaDiff(List<Type> a, List<Type> b, Comparator<Type> comp)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.comparator = comp;
|
||||
this.thresh = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs the Diff object for the two lists, using the default comparison mechanism between the objects, such as
|
||||
* <code>equals</code> and <code>compareTo</code>.
|
||||
*/
|
||||
public IncavaDiff(List<Type> a, List<Type> b)
|
||||
{
|
||||
this(a, b, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs diff and returns the results.
|
||||
*/
|
||||
public List<IncavaEntry> diff()
|
||||
{
|
||||
traverseSequences();
|
||||
|
||||
// add the last difference, if pending:
|
||||
if (pending != null) {
|
||||
diffs.add(pending);
|
||||
}
|
||||
|
||||
return diffs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Traverses the sequences, seeking the longest common subsequences, invoking the methods <code>finishedA</code>,
|
||||
* <code>finishedB</code>, <code>onANotB</code>, and <code>onBNotA</code>.
|
||||
*/
|
||||
protected void traverseSequences()
|
||||
{
|
||||
Integer[] matches = getLongestCommonSubsequences();
|
||||
|
||||
int lastA = a.size() - 1;
|
||||
int lastB = b.size() - 1;
|
||||
int bi = 0;
|
||||
int ai;
|
||||
|
||||
int lastMatch = matches.length - 1;
|
||||
|
||||
for (ai = 0; ai <= lastMatch; ++ai) {
|
||||
Integer bLine = matches[ai];
|
||||
|
||||
if (bLine == null) {
|
||||
onANotB(ai, bi);
|
||||
}
|
||||
else {
|
||||
while (bi < bLine) {
|
||||
onBNotA(ai, bi++);
|
||||
}
|
||||
|
||||
onMatch(ai, bi++);
|
||||
}
|
||||
}
|
||||
|
||||
boolean calledFinishA = false;
|
||||
boolean calledFinishB = false;
|
||||
|
||||
while (ai <= lastA || bi <= lastB) {
|
||||
|
||||
// last A?
|
||||
if (ai == lastA + 1 && bi <= lastB) {
|
||||
if (!calledFinishA && callFinishedA()) {
|
||||
finishedA(lastA);
|
||||
calledFinishA = true;
|
||||
}
|
||||
else {
|
||||
while (bi <= lastB) {
|
||||
onBNotA(ai, bi++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// last B?
|
||||
if (bi == lastB + 1 && ai <= lastA) {
|
||||
if (!calledFinishB && callFinishedB()) {
|
||||
finishedB(lastB);
|
||||
calledFinishB = true;
|
||||
}
|
||||
else {
|
||||
while (ai <= lastA) {
|
||||
onANotB(ai++, bi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ai <= lastA) {
|
||||
onANotB(ai++, bi);
|
||||
}
|
||||
|
||||
if (bi <= lastB) {
|
||||
onBNotA(ai, bi++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override and return true in order to have <code>finishedA</code> invoked at the last element in the
|
||||
* <code>a</code> array.
|
||||
*/
|
||||
protected boolean callFinishedA()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override and return true in order to have <code>finishedB</code> invoked at the last element in the
|
||||
* <code>b</code> array.
|
||||
*/
|
||||
protected boolean callFinishedB()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoked at the last element in <code>a</code>, if <code>callFinishedA</code> returns true.
|
||||
*/
|
||||
protected void finishedA(int lastA)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoked at the last element in <code>b</code>, if <code>callFinishedB</code> returns true.
|
||||
*/
|
||||
protected void finishedB(int lastB)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoked for elements in <code>a</code> and not in <code>b</code>.
|
||||
*/
|
||||
protected void onANotB(int ai, int bi)
|
||||
{
|
||||
if (pending == null) {
|
||||
pending = new IncavaEntry(ai, ai, bi, -1);
|
||||
}
|
||||
else {
|
||||
pending.setDeleted(ai);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoked for elements in <code>b</code> and not in <code>a</code>.
|
||||
*/
|
||||
protected void onBNotA(int ai, int bi)
|
||||
{
|
||||
if (pending == null) {
|
||||
pending = new IncavaEntry(ai, -1, bi, bi);
|
||||
}
|
||||
else {
|
||||
pending.setAdded(bi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoked for elements matching in <code>a</code> and <code>b</code>.
|
||||
*/
|
||||
protected void onMatch(int ai, int bi)
|
||||
{
|
||||
if (pending == null) {
|
||||
// no current pending
|
||||
}
|
||||
else {
|
||||
diffs.add(pending);
|
||||
pending = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares the two objects, using the comparator provided with the constructor, if any.
|
||||
*/
|
||||
protected boolean equals(Type x, Type y)
|
||||
{
|
||||
return comparator == null ? x.equals(y) : comparator.compare(x, y) == 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of the longest common subsequences.
|
||||
*/
|
||||
public Integer[] getLongestCommonSubsequences()
|
||||
{
|
||||
int aStart = 0;
|
||||
int aEnd = a.size() - 1;
|
||||
|
||||
int bStart = 0;
|
||||
int bEnd = b.size() - 1;
|
||||
|
||||
TreeMap<Integer, Integer> matches = new TreeMap<Integer, Integer>();
|
||||
|
||||
while (aStart <= aEnd && bStart <= bEnd && equals(a.get(aStart), b.get(bStart))) {
|
||||
matches.put(aStart++, bStart++);
|
||||
}
|
||||
|
||||
while (aStart <= aEnd && bStart <= bEnd && equals(a.get(aEnd), b.get(bEnd))) {
|
||||
matches.put(aEnd--, bEnd--);
|
||||
}
|
||||
|
||||
Map<Type, List<Integer>> bMatches = null;
|
||||
if (comparator == null) {
|
||||
if (a.size() > 0 && a.get(0) instanceof Comparable) {
|
||||
// this uses the Comparable interface
|
||||
bMatches = new TreeMap<Type, List<Integer>>();
|
||||
}
|
||||
else {
|
||||
// this just uses hashCode()
|
||||
bMatches = new HashMap<Type, List<Integer>>();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we don't really want them sorted, but this is the only Map
|
||||
// implementation (as of JDK 1.4) that takes a comparator.
|
||||
bMatches = new TreeMap<Type, List<Integer>>(comparator);
|
||||
}
|
||||
|
||||
for (int bi = bStart; bi <= bEnd; ++bi) {
|
||||
Type element = b.get(bi);
|
||||
Type key = element;
|
||||
List<Integer> positions = bMatches.get(key);
|
||||
|
||||
if (positions == null) {
|
||||
positions = new ArrayList<Integer>();
|
||||
bMatches.put(key, positions);
|
||||
}
|
||||
|
||||
positions.add(bi);
|
||||
}
|
||||
|
||||
thresh = new TreeMap<Integer, Integer>();
|
||||
Map<Integer, Object[]> links = new HashMap<Integer, Object[]>();
|
||||
|
||||
for (int i = aStart; i <= aEnd; ++i) {
|
||||
Type aElement = a.get(i);
|
||||
List<Integer> positions = bMatches.get(aElement);
|
||||
|
||||
if (positions != null) {
|
||||
Integer k = 0;
|
||||
ListIterator<Integer> pit = positions.listIterator(positions.size());
|
||||
while (pit.hasPrevious()) {
|
||||
Integer j = pit.previous();
|
||||
|
||||
k = insert(j, k);
|
||||
|
||||
if (k == null) {
|
||||
// nothing
|
||||
}
|
||||
else {
|
||||
Object value = k > 0 ? links.get(k - 1) : null;
|
||||
links.put(k, new Object[] { value, i, j });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (thresh.size() > 0) {
|
||||
Integer ti = thresh.lastKey();
|
||||
Object[] link = (Object[]) links.get(ti);
|
||||
while (link != null) {
|
||||
Integer x = (Integer) link[1];
|
||||
Integer y = (Integer) link[2];
|
||||
matches.put(x, y);
|
||||
link = (Object[]) link[0];
|
||||
}
|
||||
}
|
||||
|
||||
int size = matches.size() == 0 ? 0 : 1 + matches.lastKey();
|
||||
Integer[] ary = new Integer[size];
|
||||
for (Integer idx : matches.keySet()) {
|
||||
Integer val = matches.get(idx);
|
||||
ary[idx] = val;
|
||||
}
|
||||
return ary;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the integer is not zero (including if it is not null).
|
||||
*/
|
||||
protected static boolean isNonzero(Integer i)
|
||||
{
|
||||
return i != null && i != 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the value in the map for the given index is greater than the given value.
|
||||
*/
|
||||
protected boolean isGreaterThan(Integer index, Integer val)
|
||||
{
|
||||
Integer lhs = thresh.get(index);
|
||||
return lhs != null && val != null && lhs.compareTo(val) > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the value in the map for the given index is less than the given value.
|
||||
*/
|
||||
protected boolean isLessThan(Integer index, Integer val)
|
||||
{
|
||||
Integer lhs = thresh.get(index);
|
||||
return lhs != null && (val == null || lhs.compareTo(val) < 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value for the greatest key in the map.
|
||||
*/
|
||||
protected Integer getLastValue()
|
||||
{
|
||||
return thresh.get(thresh.lastKey());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given value to the "end" of the threshold map, that is, with the greatest index/key.
|
||||
*/
|
||||
protected void append(Integer value)
|
||||
{
|
||||
Integer addIdx = null;
|
||||
if (thresh.size() == 0) {
|
||||
addIdx = 0;
|
||||
}
|
||||
else {
|
||||
Integer lastKey = thresh.lastKey();
|
||||
addIdx = lastKey + 1;
|
||||
}
|
||||
thresh.put(addIdx, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts the given values into the threshold map.
|
||||
*/
|
||||
protected Integer insert(Integer j, Integer k)
|
||||
{
|
||||
if (isNonzero(k) && isGreaterThan(k, j) && isLessThan(k - 1, j)) {
|
||||
thresh.put(k, j);
|
||||
}
|
||||
else {
|
||||
int high = -1;
|
||||
|
||||
if (isNonzero(k)) {
|
||||
high = k;
|
||||
}
|
||||
else if (thresh.size() > 0) {
|
||||
high = thresh.lastKey();
|
||||
}
|
||||
|
||||
// off the end?
|
||||
if (high == -1 || j.compareTo(getLastValue()) > 0) {
|
||||
append(j);
|
||||
k = high + 1;
|
||||
}
|
||||
else {
|
||||
// binary search for insertion point:
|
||||
int low = 0;
|
||||
|
||||
while (low <= high) {
|
||||
int index = (high + low) / 2;
|
||||
Integer val = thresh.get(index);
|
||||
int cmp = j.compareTo(val);
|
||||
|
||||
if (cmp == 0) {
|
||||
return null;
|
||||
}
|
||||
else if (cmp > 0) {
|
||||
low = index + 1;
|
||||
}
|
||||
else {
|
||||
high = index - 1;
|
||||
}
|
||||
}
|
||||
|
||||
thresh.put(low, j);
|
||||
k = low;
|
||||
}
|
||||
}
|
||||
|
||||
return k;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
/*
|
||||
Copyright (c) 2009, incava.org
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of incava.org nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a difference, as used in <code>Diff</code>. A difference consists of two pairs of starting and ending
|
||||
* points, each pair representing either the "from" or the "to" collection passed to <code>Diff</code>. If an ending
|
||||
* point is -1, then the difference was either a deletion or an addition. For example, if <code>getDeletedEnd()</code>
|
||||
* returns -1, then the difference represents an addition.
|
||||
*/
|
||||
public class IncavaEntry
|
||||
{
|
||||
|
||||
public static final int NONE = -1;
|
||||
|
||||
/**
|
||||
* The point at which the deletion starts.
|
||||
*/
|
||||
private int delStart = NONE;
|
||||
|
||||
/**
|
||||
* The point at which the deletion ends.
|
||||
*/
|
||||
private int delEnd = NONE;
|
||||
|
||||
/**
|
||||
* The point at which the addition starts.
|
||||
*/
|
||||
private int addStart = NONE;
|
||||
|
||||
/**
|
||||
* The point at which the addition ends.
|
||||
*/
|
||||
private int addEnd = NONE;
|
||||
|
||||
|
||||
/**
|
||||
* Creates the difference for the given start and end points for the deletion and addition.
|
||||
*/
|
||||
public IncavaEntry(int delStart, int delEnd, int addStart, int addEnd)
|
||||
{
|
||||
this.delStart = delStart;
|
||||
this.delEnd = delEnd;
|
||||
this.addStart = addStart;
|
||||
this.addEnd = addEnd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The point at which the deletion starts, if any. A value equal to <code>NONE</code> means this is an addition.
|
||||
*/
|
||||
public int getDeletedStart()
|
||||
{
|
||||
return delStart;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The point at which the deletion ends, if any. A value equal to <code>NONE</code> means this is an addition.
|
||||
*/
|
||||
public int getDeletedEnd()
|
||||
{
|
||||
return delEnd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The point at which the addition starts, if any. A value equal to <code>NONE</code> means this must be an
|
||||
* addition.
|
||||
*/
|
||||
public int getAddedStart()
|
||||
{
|
||||
return addStart;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The point at which the addition ends, if any. A value equal to <code>NONE</code> means this must be an addition.
|
||||
*/
|
||||
public int getAddedEnd()
|
||||
{
|
||||
return addEnd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the point as deleted. The start and end points will be modified to include the given line.
|
||||
*/
|
||||
public void setDeleted(int line)
|
||||
{
|
||||
delStart = Math.min(line, delStart);
|
||||
delEnd = Math.max(line, delEnd);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the point as added. The start and end points will be modified to include the given line.
|
||||
*/
|
||||
public void setAdded(int line)
|
||||
{
|
||||
addStart = Math.min(line, addStart);
|
||||
addEnd = Math.max(line, addEnd);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares this object to the other for equality. Both objects must be of type Difference, with the same starting
|
||||
* and ending points.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj instanceof IncavaEntry) {
|
||||
IncavaEntry other = (IncavaEntry) obj;
|
||||
|
||||
return (delStart == other.delStart &&
|
||||
delEnd == other.delEnd &&
|
||||
addStart == other.addStart && addEnd == other.addEnd);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of this difference.
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("del: [" + delStart + ", " + delEnd + "]");
|
||||
buf.append(" ");
|
||||
buf.append("add: [" + addStart + ", " + addEnd + "]");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.jackson2.Jackson2Wrapper;
|
||||
|
||||
public class Jackson2Diff extends JsonDiff {
|
||||
|
||||
public Jackson2Diff() {
|
||||
super(new Jackson2Wrapper());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.jackson.JacksonWrapper;
|
||||
|
||||
public class JacksonDiff extends JsonDiff {
|
||||
|
||||
public JacksonDiff() {
|
||||
super(new JacksonWrapper());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,537 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.Wrapper;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.gson.GsonJsonPrimitive;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.jackson.JacksonJsonObject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Util for comparing two json-objects and create a new object with a set of instructions to transform the first to the second.
|
||||
*
|
||||
* <p>
|
||||
* Syntax for instructions:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {
|
||||
* "key": "replaced", // added or replacing key
|
||||
* "~key": "replaced", // added or replacing key (~ doesn't matter for primitive data types)
|
||||
* "key": null, // added or replacing key with null.
|
||||
* "~key": null, // added or replacing key with null (~ doesn't matter for null)
|
||||
* "-key": 0 // key removed (value is ignored)
|
||||
* "key": { "sub": "replaced" } // whole object "key" replaced
|
||||
* "~key": { "sub": "merged" } // key "sub" merged into object "key", rest of object untouched
|
||||
* "key": [ "replaced" ] // whole array added/replaced
|
||||
* "~key": [ "replaced" ] // whole array added/replaced (~ doesn't matter for whole array)
|
||||
* "key[4]": { "sub": "replaced" } // object replacing element 4, rest of array untouched
|
||||
* "~key[4]": { "sub": "merged"} // merging object at element 4, rest of array untouched
|
||||
* "key[+4]": { "sub": "array add"} // object inserted after 3 becoming the new 4 (current 4 pushed right)
|
||||
* "~key[+4]":{ "sub": "array add"} // object inserted after 3 becoming the new 4 (current 4 pushed right)
|
||||
* "-key[4]: 0 // removing element 4 current 5 becoming new 4 (value is ignored)
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Instruction order is merge, set, insert, delete. This is important when altering arrays, since insertions will affect the array index of subsequent delete instructions.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* When diffing, the object is expanded to a structure like this: <code>Example: {a:[{b:1,c:2},{d:3}]}</code>
|
||||
* Becomes a list of:
|
||||
* <ol>
|
||||
* <li>Leaf: obj
|
||||
* <li>Leaf: array 0
|
||||
* <li>Leaf: obj
|
||||
* <li>Leaf: b: 1
|
||||
* <li>Leaf: c: 2
|
||||
* <li>Leaf: array 1
|
||||
* <li>Leaf: obj
|
||||
* <li>Leaf: d: 3
|
||||
* </ol>
|
||||
*
|
||||
* @author Martin Algesten
|
||||
*/
|
||||
public class JsonDiff {
|
||||
|
||||
static final String MOD = "~";
|
||||
static final String DIFF_ADD = "++";
|
||||
static final String DIFF_EDIT = "**";
|
||||
static final String DIFF_DEL = "--";
|
||||
|
||||
static class Instruction {
|
||||
Oper oper;
|
||||
int index;
|
||||
String key;
|
||||
|
||||
boolean isIndexed() {
|
||||
return index > -1;
|
||||
}
|
||||
}
|
||||
|
||||
static final Logger LOG = Logger.getLogger(JsonDiff.class.getName());
|
||||
|
||||
protected final Wrapper factory;
|
||||
|
||||
final static Comparator<Entry<String, JzonElement>> INSTRUCTIONS_COMPARATOR = new Comparator<Entry<String, JzonElement>>() {
|
||||
|
||||
@Override
|
||||
public int compare(Entry<String, JzonElement> o1, Entry<String, JzonElement> o2) {
|
||||
if (o1.getKey().startsWith(MOD) && !o2.getKey().startsWith(MOD)) {
|
||||
return 1;
|
||||
} else if (!o1.getKey().startsWith(MOD) && o2.getKey().startsWith(MOD)) {
|
||||
return -1;
|
||||
}
|
||||
return o1.getKey().compareTo(o2.getKey());
|
||||
}
|
||||
};
|
||||
|
||||
final static Comparator<Entry<String, JzonElement>> OBJECT_KEY_COMPARATOR = new Comparator<Entry<String, JzonElement>>() {
|
||||
|
||||
@Override
|
||||
public int compare(Entry<String, JzonElement> o1, Entry<String, JzonElement> o2) {
|
||||
return o1.getKey().compareTo(o2.getKey());
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Visitor visitor;
|
||||
|
||||
JsonDiff(Wrapper factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
boolean accept(Leaf leaf, JzonArray instructions, JzonObject childPatch) {
|
||||
JzonObject object = (JzonObject) factory.parse(leaf.val.toString());
|
||||
JzonObject patch = factory.createJsonObject();
|
||||
patch.add(MOD, instructions);
|
||||
if (!childPatch.entrySet().isEmpty()) {
|
||||
patch.entrySet().addAll((Collection) childPatch.entrySet());
|
||||
}
|
||||
apply(object, patch);
|
||||
return visitor.shouldCreatePatch(leaf.val.unwrap(), object.unwrap());
|
||||
}
|
||||
|
||||
void apply(JzonElement origEl, JzonElement patchEl) throws IllegalArgumentException {
|
||||
|
||||
JzonObject patch = (JzonObject) patchEl;
|
||||
Set<Entry<String, JzonElement>> memb = new TreeSet<Entry<String, JzonElement>>(INSTRUCTIONS_COMPARATOR);
|
||||
memb.addAll(patch.entrySet());
|
||||
for (Entry<String, JzonElement> entry : memb) {
|
||||
String key = entry.getKey();
|
||||
JzonElement value = entry.getValue();
|
||||
if (key.startsWith(MOD)) {
|
||||
JzonElement partialInstructions = entry.getValue();
|
||||
if (!partialInstructions.isJsonArray()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
JzonArray array = (JzonArray) partialInstructions;
|
||||
JzonElement applyTo;
|
||||
if (key.equals(MOD)) {
|
||||
applyTo = origEl;
|
||||
} else if (origEl.isJsonArray()) {
|
||||
int index = Integer.parseInt(key.substring(1));
|
||||
applyTo = ((JzonArray) origEl).get(index);
|
||||
} else {
|
||||
applyTo = ((JzonObject) origEl).get(key.substring(1));
|
||||
}
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
JzonElement partial = array.get(i);
|
||||
if (!partial.isJsonObject()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
Entry<String, JzonElement> childentry = ((JzonObject) partial).entrySet().iterator().next();
|
||||
String childKey = childentry.getKey();
|
||||
Instruction instruction = create(childKey);
|
||||
boolean newAppliance = false;
|
||||
if (instruction.isIndexed() && !applyTo.isJsonArray()) {
|
||||
applyTo = factory.createJsonArray();
|
||||
newAppliance = true;
|
||||
} else if (!instruction.isIndexed() && !applyTo.isJsonObject()) {
|
||||
applyTo = factory.createJsonObject();
|
||||
newAppliance = true;
|
||||
}
|
||||
if (newAppliance) {
|
||||
if (origEl.isJsonArray()) {
|
||||
int index = Integer.parseInt(key);
|
||||
((JzonArray) origEl).insert(index, applyTo);
|
||||
} else {
|
||||
((JzonObject) origEl).add(key.substring(1), applyTo);
|
||||
}
|
||||
}
|
||||
applyPartial(applyTo, instruction, childentry.getValue());
|
||||
}
|
||||
} else {
|
||||
Instruction instruction = create(key);
|
||||
if (instruction.oper == Oper.INSERT || instruction.oper == Oper.DELETE) {
|
||||
applyPartial(origEl, instruction, value);
|
||||
} else if (instruction.isIndexed()) {
|
||||
if (!origEl.isJsonArray()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (value.isJsonPrimitive()) {
|
||||
((JzonArray) origEl).set(instruction.index, value);
|
||||
} else {
|
||||
if (((JzonArray) origEl).size() <= instruction.index) {
|
||||
throw new IllegalArgumentException("Wrong index " + instruction.index + " for " + origEl);
|
||||
}
|
||||
JzonElement childEl = ((JzonArray) origEl).get(instruction.index);
|
||||
apply(childEl, value);
|
||||
}
|
||||
} else if (origEl.isJsonObject()) {
|
||||
if (value.isJsonPrimitive() || value.isJsonNull()) {
|
||||
((JzonObject) origEl).add(key, value);
|
||||
} else {
|
||||
JzonElement childEl = ((JzonObject) origEl).get(key);
|
||||
apply(childEl, value);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches the first argument with the second. Accepts two GSON JsonObject or (if jar is provided) a Jackson style ObjectNode.
|
||||
*
|
||||
* @param orig Object to patch. One of JsonObject or ObjectNode (if jar available).
|
||||
* @param patch Object holding patch instructions. One of JsonObject or ObjectNode (if jar available).
|
||||
* @throws IllegalArgumentException if the given arguments are not accepted.
|
||||
*/
|
||||
public void apply(Object orig, Object patch) {
|
||||
|
||||
JzonElement origEl = factory.wrap(orig);
|
||||
JzonElement patchEl = factory.wrap(patch);
|
||||
|
||||
apply(origEl, patchEl);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the given original JSON object using the instructions provided and returns the result. Each argument is expected to be a JSON object {}.
|
||||
*
|
||||
* @param orig The original JSON object to modify.
|
||||
* @param patch The set of instructions to use.
|
||||
* @return The modified JSON object.
|
||||
* @throws IllegalArgumentException if the given arguments are not accepted.
|
||||
*/
|
||||
public String apply(String orig, String patch) throws IllegalArgumentException {
|
||||
|
||||
// by providing null as hint we default to GSON.
|
||||
JzonElement origEl = factory.parse(orig);
|
||||
JzonElement patchEl = factory.parse(patch);
|
||||
|
||||
apply(origEl, patchEl);
|
||||
|
||||
return origEl.toString();
|
||||
|
||||
}
|
||||
|
||||
void applyPartial(JzonElement applyTo, Instruction instruction, JzonElement value) {
|
||||
if (instruction.oper == Oper.DELETE) {
|
||||
if (instruction.isIndexed()) {
|
||||
if (((JzonArray) applyTo).size() < instruction.index) {
|
||||
throw new IllegalArgumentException("Wrong index " + instruction.index + " for " + applyTo);
|
||||
}
|
||||
// 修改标记,增加IF内容
|
||||
if (value.isJsonPrimitive()) {
|
||||
String valueJson = DIFF_DEL + (value != null && StringUtils.isNotEmpty(value.toString()) ? value.toString().substring(1, value.toString().length() - 1) : "");
|
||||
JsonPrimitive gsonJsonPrimitive = new JsonPrimitive(valueJson);
|
||||
GsonJsonPrimitive primitive = new GsonJsonPrimitive(gsonJsonPrimitive);
|
||||
((JzonArray) applyTo).insert(instruction.index, primitive);
|
||||
} else {
|
||||
try {
|
||||
JacksonJsonObject object = (JacksonJsonObject) value;
|
||||
if (object != null && object.get("name") != null) {
|
||||
object.add(DIFF_DEL + "name", object.get("name"));
|
||||
} else {
|
||||
object.add(DIFF_DEL + "name", new JacksonJsonObject(null));
|
||||
}
|
||||
if (instruction.index > 0 && ((JzonArray) applyTo).size() == instruction.index) {
|
||||
((JzonArray) applyTo).insert(instruction.index, object);
|
||||
} else {
|
||||
((JzonArray) applyTo).set(instruction.index, object);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
((JzonArray) applyTo).remove(instruction.index);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
((JzonObject) applyTo).add(DIFF_DEL + instruction.key, value);
|
||||
}
|
||||
} else if (instruction.oper == Oper.INSERT) {
|
||||
if (instruction.isIndexed()) {
|
||||
if (((JzonArray) applyTo).size() < instruction.index) {
|
||||
throw new IllegalArgumentException("Wrong index " + instruction.index + " for " + applyTo);
|
||||
}
|
||||
// 修改标记,增加IF内容
|
||||
if (value.isJsonPrimitive()) {
|
||||
String valueJson = DIFF_ADD + (value != null && StringUtils.isNotEmpty(value.toString()) ? value.toString().substring(1, value.toString().length() - 1) : "");
|
||||
JsonPrimitive gsonJsonPrimitive = new JsonPrimitive(valueJson);
|
||||
GsonJsonPrimitive primitive = new GsonJsonPrimitive(gsonJsonPrimitive);
|
||||
((JzonArray) applyTo).set(instruction.index, primitive);
|
||||
} else {
|
||||
try {
|
||||
JacksonJsonObject object = (JacksonJsonObject) value;
|
||||
if (object != null && object.get("name") != null) {
|
||||
object.add(DIFF_ADD + "name", object.get("name"));
|
||||
} else {
|
||||
object.add(DIFF_ADD + "name", new JacksonJsonObject(null));
|
||||
}
|
||||
if (instruction.index > 0 && ((JzonArray) applyTo).size() == instruction.index) {
|
||||
((JzonArray) applyTo).set(instruction.index - 1, object);
|
||||
} else {
|
||||
((JzonArray) applyTo).set(instruction.index, object);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
((JzonArray) applyTo).insert(instruction.index, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
((JzonObject) applyTo).remove(instruction.key);
|
||||
((JzonObject) applyTo).add(DIFF_ADD + instruction.key, value);
|
||||
}
|
||||
} else if (applyTo.isJsonArray()) {
|
||||
if (((JzonArray) applyTo).size() <= instruction.index) {
|
||||
throw new IllegalArgumentException("Wrong index " + instruction.index + " for " + applyTo);
|
||||
}
|
||||
((JzonArray) applyTo).set(instruction.index, value);
|
||||
} else {
|
||||
((JzonObject) applyTo).add(DIFF_EDIT + instruction.key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void checkIndex(JzonElement applyTo, int index) {
|
||||
if (((JzonArray) applyTo).size() < index) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
Instruction create(String childKey) {
|
||||
Instruction instruction = new Instruction();
|
||||
if (childKey.startsWith("-")) {
|
||||
instruction.key = childKey.substring(1);
|
||||
instruction.index = isIndexed(instruction.key);
|
||||
instruction.oper = Oper.DELETE;
|
||||
} else if (childKey.startsWith("+")) {
|
||||
instruction.key = childKey.substring(1);
|
||||
instruction.index = isIndexed(instruction.key);
|
||||
instruction.oper = Oper.INSERT;
|
||||
} else {
|
||||
instruction.key = childKey;
|
||||
instruction.index = isIndexed(instruction.key);
|
||||
instruction.oper = Oper.SET;
|
||||
}
|
||||
return instruction;
|
||||
}
|
||||
|
||||
JzonObject diff(JzonElement fromEl, JzonElement toEl) {
|
||||
|
||||
if (!fromEl.isJsonObject()) {
|
||||
throw new IllegalArgumentException("From is not a json object");
|
||||
}
|
||||
if (!toEl.isJsonObject()) {
|
||||
throw new IllegalArgumentException("To is not a json object");
|
||||
}
|
||||
|
||||
JzonObject from = (JzonObject) fromEl;
|
||||
JzonObject to = (JzonObject) toEl;
|
||||
|
||||
Root fromRoot = new Root();
|
||||
Root toRoot = new Root();
|
||||
|
||||
ArrayList<Leaf> fromLeaves = new ArrayList<Leaf>();
|
||||
ArrayList<Leaf> toLeaves = new ArrayList<Leaf>();
|
||||
|
||||
HashMap<Integer, ArrNode> fromArrs = new HashMap<Integer, ArrNode>();
|
||||
HashMap<Integer, ArrNode> toArrs = new HashMap<Integer, ArrNode>();
|
||||
|
||||
findLeaves(fromRoot, from, fromLeaves, fromArrs);
|
||||
findLeaves(toRoot, to, toLeaves, toArrs);
|
||||
|
||||
IncavaDiff<Leaf> idiff = new IncavaDiff<Leaf>(fromLeaves, toLeaves);
|
||||
|
||||
List<IncavaEntry> diff = idiff.diff();
|
||||
int delta = 0;
|
||||
// be careful with direct use of indexOf: need instance equality, not equals!
|
||||
for (IncavaEntry incavaEntry : diff) {
|
||||
int deletes = Math.max(0, incavaEntry.getDeletedEnd() - incavaEntry.getDeletedStart() + 1);
|
||||
int insertionIndex = (incavaEntry.getDeletedStart() > 0) ? incavaEntry.getDeletedStart() + delta - 1 : 0;
|
||||
Leaf fromLeaf = (fromLeaves.size() > insertionIndex) ? fromLeaves.get(insertionIndex) : fromLeaves.get(fromLeaves.size() - 1);
|
||||
for (int i = incavaEntry.getDeletedStart(); i < incavaEntry.getDeletedEnd() + 1; i++) {
|
||||
// ensure not orphan
|
||||
fromLeaf.recover(fromLeaves);
|
||||
// proceed to delete
|
||||
Leaf toLeaf = fromLeaves.get(i + delta);
|
||||
fromLeaf.delete(toLeaf, null);
|
||||
fromLeaf = toLeaf;
|
||||
}
|
||||
if (incavaEntry.getAddedEnd() < 0) {
|
||||
continue;
|
||||
}
|
||||
fromLeaf = (fromLeaves.size() > insertionIndex) ? fromLeaves.get(insertionIndex) : fromLeaves.get(fromLeaves.size() - 1);
|
||||
while (fromLeaf.oper == Oper.DELETE && insertionIndex > 0) {
|
||||
// find a NOT deleted node for set / insertion - parent traversal will be done later
|
||||
insertionIndex--;
|
||||
fromLeaf = fromLeaves.get(insertionIndex);
|
||||
}
|
||||
for (int i = incavaEntry.getAddedStart(); i < incavaEntry.getAddedEnd() + 1; i++) {
|
||||
// ensure not orphan
|
||||
fromLeaf.recover(fromLeaves);
|
||||
Leaf toLeaf = toLeaves.get(i);
|
||||
if (deletes > 0) {
|
||||
deletes--;
|
||||
Leaf deleted = fromLeaves.get(incavaEntry.getDeletedStart() + delta + (i - incavaEntry.getAddedStart()));
|
||||
deleted.recover(fromLeaves);
|
||||
if (!fromLeaf.cancelDelete(deleted, toLeaf)) {
|
||||
// couldn't cancel delete (different obj key): INSERT
|
||||
fromLeaf.insert(toLeaf, null);
|
||||
fromLeaves.add(insertionIndex + 1, toLeaf);
|
||||
fromLeaf = toLeaf;
|
||||
delta++;
|
||||
} else {
|
||||
// cancel delete: pure SET
|
||||
fromLeaf = deleted;
|
||||
}
|
||||
} else {
|
||||
// regular INSERT
|
||||
fromLeaf.insert(toLeaf, null);
|
||||
fromLeaves.add(insertionIndex + 1, toLeaf);
|
||||
fromLeaf = toLeaf;
|
||||
delta++;
|
||||
}
|
||||
insertionIndex++;
|
||||
}
|
||||
}
|
||||
// recover all pending orphans: this could be easily optimized
|
||||
int i = 0;
|
||||
for (Leaf fromLeaf : fromLeaves) {
|
||||
if (fromLeaf.isOrphan()) {
|
||||
fromLeaf.recover(i, fromLeaves);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
JzonObject patch = fromLeaves.iterator().next().patch();
|
||||
// prints the new structure
|
||||
// fromLeaves.iterator().next().print();
|
||||
return patch;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a diff using underlying JSON parser implementations. Accepts two GSON JsonObject or (if jar is provided) a Jackson style ObjectNode. The returned type
|
||||
* is the same as the received.
|
||||
*
|
||||
* @param from Object to transform from. One of JsonObject or ObjectNode (if jar available).
|
||||
* @param to Object to transform to. One of JsonObject or ObjectNode (if jar available).
|
||||
* @return Object containing the instructions. The type will be the same as that passed in constructor.
|
||||
* @throws IllegalArgumentException if the given arguments are not accepted.
|
||||
*/
|
||||
public Object diff(Object from, Object to) throws IllegalArgumentException {
|
||||
|
||||
JzonElement fromEl = factory.wrap(from);
|
||||
JzonElement toEl = factory.wrap(to);
|
||||
|
||||
JzonObject diff = diff(fromEl, toEl);
|
||||
|
||||
return diff.unwrap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a diff on the two given JSON objects given as string to produce another JSON object with instructions of how to transform the first argument to the second. Both from/to
|
||||
* are expected to be objects {}.
|
||||
*
|
||||
* @param from The origin to transform
|
||||
* @param to The desired result
|
||||
* @return The set of instructions to go from to as a JSON object {}.
|
||||
* @throws IllegalArgumentException if the given arguments are not accepted.
|
||||
*/
|
||||
public String diff(String from, String to) throws IllegalArgumentException {
|
||||
|
||||
JzonElement fromEl = factory.parse(from);
|
||||
JzonElement toEl = factory.parse(to);
|
||||
|
||||
return diff(fromEl, toEl).toString();
|
||||
|
||||
}
|
||||
|
||||
Leaf findLeaves(Node parent, JzonElement el, List<Leaf> leaves, HashMap<Integer, ArrNode> arrs) {
|
||||
|
||||
// create leaf for this part
|
||||
Leaf leaf = new Leaf(parent, el);
|
||||
leaf.factory = factory;
|
||||
if (visitor != null) {
|
||||
leaf.visitor = this;
|
||||
}
|
||||
leaves.add(leaf);
|
||||
|
||||
if (el.isJsonObject()) {
|
||||
|
||||
Set<Entry<String, JzonElement>> memb = new TreeSet<Entry<String, JzonElement>>(OBJECT_KEY_COMPARATOR);
|
||||
memb.addAll(((JzonObject) el).entrySet());
|
||||
for (Entry<String, JzonElement> e : memb) {
|
||||
|
||||
ObjNode newParent = new ObjNode(parent, e.getKey());
|
||||
Leaf child = findLeaves(newParent, e.getValue(), leaves, arrs);
|
||||
leaf.children.add(child);
|
||||
}
|
||||
|
||||
} else if (el.isJsonArray()) {
|
||||
|
||||
JzonArray arr = (JzonArray) el;
|
||||
for (int i = 0, n = arr.size(); i < n; i++) {
|
||||
|
||||
ArrNode newParent = new ArrNode(parent, i);
|
||||
|
||||
// this array saves a reference to all arrnodes
|
||||
// which is used to adjust arr node indexes.
|
||||
arrs.put(newParent.doHash(true), newParent);
|
||||
|
||||
Leaf child = findLeaves(newParent, arr.get(i), leaves, arrs);
|
||||
leaf.children.add(child);
|
||||
}
|
||||
|
||||
}
|
||||
leaf.init();
|
||||
return leaf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the registered visitor if any
|
||||
* @see Visitor
|
||||
*/
|
||||
public Visitor<?> getVisitor() {
|
||||
return visitor;
|
||||
}
|
||||
|
||||
int isIndexed(String childKey) {
|
||||
try {
|
||||
return Integer.parseInt(childKey);
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new visitor.
|
||||
*
|
||||
* @param visitor - visitor to register
|
||||
* @see Visitor
|
||||
*/
|
||||
public void setVisitor(Visitor<?> visitor) {
|
||||
this.visitor = visitor;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,382 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.Wrapper;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
class Leaf implements Comparable<Leaf> {
|
||||
|
||||
Wrapper factory;
|
||||
|
||||
final Node parent;
|
||||
JzonElement val;
|
||||
Oper oper;
|
||||
|
||||
List<Leaf> children = new LinkedList<Leaf>();
|
||||
List<Leaf> newStructure = new LinkedList<Leaf>();
|
||||
|
||||
Leaf(Node parent, JzonElement val) {
|
||||
this.parent = parent;
|
||||
this.val = val;
|
||||
this.parent.leaf = this;
|
||||
}
|
||||
|
||||
boolean attach(Leaf leaf, Leaf at) {
|
||||
Leaf attach = this;
|
||||
int myIndex = (parent.parent == null) ? 0 : exactIndex(parent.parent.leaf.newStructure, this);
|
||||
int atIndex = (at == this) ? 0 : exactIndex(newStructure, at) + 1;
|
||||
while (leaf.oper != Oper.DELETE && attach.oper == Oper.DELETE && myIndex > 0) {
|
||||
myIndex--;
|
||||
attach = parent.parent.leaf.newStructure.get(myIndex);
|
||||
atIndex = attach.newStructure.size();
|
||||
}
|
||||
if (leaf.parent.parentHashCode == attach.parent.hashCode) {
|
||||
// direct attachment
|
||||
if (leaf.oper != Oper.DELETE && attach.oper == Oper.DELETE) {
|
||||
return attach.parent.parent.leaf.attach(leaf, this);
|
||||
}
|
||||
if (leaf.oper == null && (leaf.val.isJsonPrimitive() || leaf.val.isJsonNull())) {
|
||||
leaf.oper = Oper.SET;
|
||||
}
|
||||
attach.newStructure.add(atIndex, leaf);
|
||||
leaf.rehash(attach);
|
||||
if (JsonDiff.LOG.isLoggable(Level.FINE))
|
||||
JsonDiff.LOG.info("ATT " + leaf + " @" + this);
|
||||
return true;
|
||||
}
|
||||
if (parent.parent == null) {
|
||||
return false;
|
||||
}
|
||||
return parent.parent.leaf.attach(leaf, this);
|
||||
}
|
||||
|
||||
boolean cancelDelete(Leaf deleted, Leaf with) {
|
||||
if (deleted.parent.hashCode == with.parent.hashCode) {
|
||||
if (JsonDiff.LOG.isLoggable(Level.FINE))
|
||||
JsonDiff.LOG.info("SET " + deleted + " @" + with);
|
||||
with.newStructure.clear();
|
||||
deleted.oper = Oper.SET;
|
||||
//deleted.val = with.val;
|
||||
Leaf newParent = deleted.parent.parent.leaf;
|
||||
// recover deleted children (orphans)
|
||||
for (Leaf orphan : deleted.children) {
|
||||
orphan.parent.parent = deleted.parent;
|
||||
deleted.newStructure.add(orphan);
|
||||
}
|
||||
deleted.rehash(newParent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void rehash(Leaf newParent) {
|
||||
parent.rehash(newParent.parent);
|
||||
for (Leaf child : newStructure) {
|
||||
child.rehash(this);
|
||||
}
|
||||
}
|
||||
|
||||
Leaf checkCancelation(Leaf possibleCancellation) {
|
||||
for (Iterator<Leaf> iterator2 = newStructure.iterator(); iterator2.hasNext();) {
|
||||
Leaf check = iterator2.next();
|
||||
if (check != possibleCancellation) {
|
||||
if (!check.parent.getClass().equals(possibleCancellation.parent.getClass())) {
|
||||
// type node change: cancellation
|
||||
return check;
|
||||
}
|
||||
if (possibleCancellation.parent instanceof ObjNode && check.parent.hashCode == possibleCancellation.parent.hashCode) {
|
||||
return check;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void checkCancellations() {
|
||||
for (Iterator<Leaf> it = newStructure.iterator(); it.hasNext();) {
|
||||
Leaf child = it.next();
|
||||
if (child.oper == Oper.DELETE) {
|
||||
Leaf cancelled = checkCancelation(child);
|
||||
if (cancelled != null) {
|
||||
if (cancelled.newStructure.isEmpty()) {
|
||||
cancelled.oper = Oper.SET;
|
||||
}
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Leaf o) {
|
||||
return hashCode() - o.hashCode();
|
||||
}
|
||||
|
||||
JsonDiff visitor;
|
||||
|
||||
JzonArray createPatch(JzonObject patch) {
|
||||
JzonArray instructions = factory.createJsonArray();
|
||||
if (oper != Oper.DELETE) {
|
||||
checkCancellations();
|
||||
int i = 0, deletes = 0;
|
||||
for (Iterator<Leaf> it = newStructure.iterator(); it.hasNext();) {
|
||||
Leaf child = it.next();
|
||||
String key = child.parent.toString();
|
||||
String reIndexedKey = key;
|
||||
if (child.parent instanceof ArrNode) {
|
||||
((ArrNode) child.parent).index = i - deletes;
|
||||
reIndexedKey = child.parent.toString();
|
||||
}
|
||||
JzonObject insert = factory.createJsonObject();
|
||||
boolean deeper = true;
|
||||
if (child.oper == Oper.INSERT) {
|
||||
insert.add("+" + reIndexedKey, child.val);
|
||||
instructions.insert(instructions.size(), insert);
|
||||
deeper = false;
|
||||
} else if (child.oper == Oper.SET) {
|
||||
insert.add(reIndexedKey, child.val);
|
||||
instructions.insert(instructions.size(), insert);
|
||||
deeper = false;
|
||||
} else if (child.oper == Oper.DELETE) {
|
||||
insert.add("-" + reIndexedKey, child.val);
|
||||
instructions.insert(instructions.size(), insert);
|
||||
deeper = false;
|
||||
}
|
||||
if (deeper) {
|
||||
JzonObject childPatch = factory.createJsonObject();
|
||||
JzonArray childInstructions = child.createPatch(childPatch);
|
||||
if (childInstructions.size() > 0) {
|
||||
if (visitor != null && !child.val.isJsonPrimitive() && !visitor.accept(child, childInstructions, childPatch)) {
|
||||
continue;
|
||||
}
|
||||
patch.add("~" + key, childInstructions);
|
||||
}
|
||||
if (!childPatch.entrySet().isEmpty()) {
|
||||
patch.add(key, childPatch);
|
||||
}
|
||||
}
|
||||
if (child.oper == Oper.DELETE) {
|
||||
deletes++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
newStructure.clear();
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
void delete(Leaf leaf, Leaf at) {
|
||||
if (JsonDiff.LOG.isLoggable(Level.FINE))
|
||||
JsonDiff.LOG.info("DELETE " + leaf + " @" + this);
|
||||
leaf.oper = Oper.DELETE;
|
||||
for (Leaf orphan : leaf.newStructure) {
|
||||
orphan.parent.orphan();
|
||||
}
|
||||
leaf.newStructure.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return hashCode() == ((Leaf) obj).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int i = parent.hashCode;
|
||||
if (val.isJsonArray()) {
|
||||
// for arr and obj we must hash in a type qualifier
|
||||
// since otherwise changes between these kinds of
|
||||
// nodes will be considered equal
|
||||
i = i * 31 + ArrNode.class.hashCode();
|
||||
} else if (val.isJsonObject()) {
|
||||
i = i * 31 + ObjNode.class.hashCode();
|
||||
} else {
|
||||
i = i * 31 + (val.isJsonPrimitive() || val.isJsonNull() ? val.hashCode() : 0);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void init() {
|
||||
this.parent.hashCode = this.parent.doHash(false);
|
||||
this.parent.parentHashCode = (this.parent.parent == null) ? 0 : this.parent.parent.doHash(false);
|
||||
this.newStructure.addAll(children);
|
||||
}
|
||||
|
||||
void insert(Leaf leaf, Leaf where) {
|
||||
int hashCode = parent.hashCode;
|
||||
int insCode = leaf.parent.parent.hashCode;
|
||||
if (hashCode == 0 || insCode == hashCode) {
|
||||
// eligible for insertion - check for sets after building the new graph
|
||||
leaf.oper = Oper.INSERT;
|
||||
leaf.parent.parent = parent;
|
||||
leaf.newStructure.clear();
|
||||
if (where != null) {
|
||||
int insertAt = exactIndex(newStructure, where) + 1;
|
||||
newStructure.add(insertAt, leaf);
|
||||
} else {
|
||||
// direct insertion
|
||||
newStructure.add(0, leaf);
|
||||
}
|
||||
if (JsonDiff.LOG.isLoggable(Level.FINE))
|
||||
JsonDiff.LOG.info("INSERTed " + leaf + " @" + this);
|
||||
} else {
|
||||
orphans(where);
|
||||
parent.parent.leaf.insert(leaf, this);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isOrphan() {
|
||||
return parent.hashCode != 0 && parent.isOrphan();
|
||||
}
|
||||
|
||||
void orphans(Leaf where) {
|
||||
List<Leaf> orphans = null;
|
||||
int insertDeletionsIndex = 0;
|
||||
if ((where == null && !newStructure.isEmpty()) || newStructure.size() == 1) {
|
||||
orphans = newStructure;
|
||||
} else if (newStructure.size() > 1) {
|
||||
insertDeletionsIndex = exactIndex(newStructure, where) + 1;
|
||||
orphans = newStructure.subList(insertDeletionsIndex, newStructure.size());
|
||||
}
|
||||
if (orphans != null) {
|
||||
List<Leaf> newOrphans = new ArrayList<Leaf>();
|
||||
for (Leaf orphan : orphans) {
|
||||
if (orphan.oper != Oper.DELETE) {
|
||||
orphan.parent.parent = null;
|
||||
Node clone = orphan.parent.clone();
|
||||
Leaf leafClone = new Leaf(clone, orphan.val);
|
||||
leafClone.visitor = visitor;
|
||||
clone.leaf = leafClone;
|
||||
leafClone.oper = Oper.DELETE;
|
||||
newOrphans.add(leafClone);
|
||||
} else {
|
||||
newOrphans.add(orphan);
|
||||
}
|
||||
}
|
||||
orphans.clear();
|
||||
newStructure.addAll(insertDeletionsIndex, newOrphans);
|
||||
}
|
||||
}
|
||||
|
||||
JzonObject patch() {
|
||||
checkCancellations();
|
||||
JzonObject patch = factory.createJsonObject();
|
||||
if (oper == Oper.INSERT) {
|
||||
patch.add("+" + parent.toString(), val);
|
||||
} else if (oper == Oper.SET) {
|
||||
patch.add(parent.toString(), val);
|
||||
} else if (oper == Oper.DELETE) {
|
||||
patch.add("-" + parent.toString(), val);
|
||||
} else {
|
||||
JzonArray childInstructions = createPatch(patch);
|
||||
if (childInstructions.size() > 0) {
|
||||
patch.add("~", childInstructions);
|
||||
}
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
void print() {
|
||||
print(0);
|
||||
}
|
||||
|
||||
void print(int tab) {
|
||||
for (Leaf lEntry : newStructure) {
|
||||
for (int i = 0; i < tab; i++) {
|
||||
System.out.print("\t");
|
||||
}
|
||||
System.out.println(lEntry);
|
||||
lEntry.print(tab + 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int exactIndex(Collection<Leaf> c, Leaf check) {
|
||||
int i = -1;
|
||||
for (Leaf l : c) {
|
||||
i++;
|
||||
if (l == check) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void recover(List<Leaf> fromLeaves) {
|
||||
if (isOrphan()) {
|
||||
int thisIndex = exactIndex(fromLeaves, this);
|
||||
recover(thisIndex, fromLeaves);
|
||||
}
|
||||
if (parent.parent != null) {
|
||||
parent.parent.leaf.recover(fromLeaves);
|
||||
}
|
||||
}
|
||||
|
||||
void recover(int thisIndex, List<Leaf> fromLeaves) {
|
||||
if (isOrphan()) {
|
||||
Leaf newParent = null;
|
||||
while (newParent == null || (oper != Oper.DELETE && newParent.oper == Oper.DELETE)) {
|
||||
thisIndex--;
|
||||
newParent = fromLeaves.get(thisIndex);
|
||||
if (newParent.isOrphan()) {
|
||||
newParent.recover(thisIndex, fromLeaves);
|
||||
}
|
||||
}
|
||||
while (newParent.parent.parent != null && newParent.parent.hashCode != parent.parentHashCode) {
|
||||
newParent = newParent.parent.parent.leaf;
|
||||
if (newParent.isOrphan()) {
|
||||
newParent.recover(fromLeaves);
|
||||
}
|
||||
}
|
||||
if (newParent.oper == Oper.DELETE) {
|
||||
return;
|
||||
}
|
||||
if (newParent.attach(this, null)) {
|
||||
if (JsonDiff.LOG.isLoggable(Level.FINE))
|
||||
JsonDiff.LOG.info("RECOVERed " + this + " @" + newParent);
|
||||
} else {
|
||||
recover(thisIndex, fromLeaves);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuilder bld = new StringBuilder(newStructure.size() + "->");
|
||||
|
||||
bld.append("LEAF");
|
||||
if (parent != null && parent instanceof ArrNode) {
|
||||
bld.append(parent.toString());
|
||||
}
|
||||
bld.append("<");
|
||||
if (oper != null) {
|
||||
bld.append(oper);
|
||||
bld.append("_");
|
||||
}
|
||||
if (val.isJsonPrimitive() || val.isJsonNull()) {
|
||||
bld.append("{");
|
||||
bld.append(val);
|
||||
bld.append("}");
|
||||
} else {
|
||||
bld.append(parent.toString());
|
||||
bld.append(":");
|
||||
bld.append(val);
|
||||
}
|
||||
bld.append("_");
|
||||
bld.append(hashCode());
|
||||
if (parent.isOrphan()) {
|
||||
bld.append("_ORPHAN");
|
||||
}
|
||||
bld.append(">");
|
||||
bld.append("\n");
|
||||
|
||||
return bld.toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
abstract class Node implements Cloneable {
|
||||
|
||||
// keep the original hash code since we'll be unsetting the parent leaf
|
||||
int hashCode, parentHashCode;
|
||||
|
||||
Node parent;
|
||||
Leaf leaf;
|
||||
|
||||
Node(Node parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node clone() {
|
||||
try {
|
||||
return (Node) super.clone();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract int doHash(boolean indexed);
|
||||
|
||||
abstract void rehash(Node newParent);
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return doHash(false);
|
||||
}
|
||||
|
||||
boolean isOrphan() {
|
||||
return hashCode != 0 && parent == null;
|
||||
}
|
||||
|
||||
void orphan() {
|
||||
parent = null;
|
||||
if (leaf != null) {
|
||||
for (Leaf c : leaf.newStructure) {
|
||||
c.parent.orphan();
|
||||
}
|
||||
leaf.newStructure.clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
class ObjNode extends Node {
|
||||
|
||||
final String key;
|
||||
|
||||
ObjNode(Node parent, String key) {
|
||||
super(parent);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
void rehash(Node newParent) {
|
||||
this.parent = newParent;
|
||||
this.parentHashCode = newParent.hashCode;
|
||||
int i = this.parentHashCode;
|
||||
|
||||
i = i * 31 + ObjNode.class.hashCode();
|
||||
i = i * 31 + key.hashCode();
|
||||
hashCode = i;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
int doHash(boolean indexed) {
|
||||
|
||||
// just pass through the arguments as is since
|
||||
// it's the arr node that alters them.
|
||||
int i = parent.doHash(indexed);
|
||||
|
||||
i = i * 31 + ObjNode.class.hashCode();
|
||||
i = i * 31 + key.hashCode();
|
||||
return i;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
enum Oper {
|
||||
INSERT, DELETE, SET
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
class Root extends Node {
|
||||
|
||||
Root() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node clone() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
int doHash(boolean indexed) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "root";
|
||||
}
|
||||
|
||||
@Override
|
||||
void rehash(Node newParent) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.metersphere.log.utils.json.diff;
|
||||
|
||||
|
||||
/**
|
||||
* Interface that allows filtering patch instructions.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface Visitor<E> {
|
||||
|
||||
/**
|
||||
* Should a patch instruction be created for an element like <code>to</code> if its destiny is an element like <code>to</code>?
|
||||
*
|
||||
* @param from
|
||||
* - from element
|
||||
* @param to
|
||||
* - to element
|
||||
* @return if the instruction should be created
|
||||
*/
|
||||
boolean shouldCreatePatch(E from, E to);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap;
|
||||
|
||||
/**
|
||||
* Common abstaraction for a json array.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface JzonArray extends JzonElement {
|
||||
|
||||
/**
|
||||
* @return array size
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* Returns element at given index.
|
||||
*
|
||||
* @param index
|
||||
* - index to retreive element from
|
||||
* @return element at given index
|
||||
*/
|
||||
JzonElement get(int index);
|
||||
|
||||
/**
|
||||
* Inserts element at given index.
|
||||
*
|
||||
* @param index
|
||||
* - index to insert element at
|
||||
* @param el
|
||||
* - element to insert
|
||||
*/
|
||||
void insert(int index, JzonElement el);
|
||||
|
||||
/**
|
||||
* Sets element at given index.
|
||||
*
|
||||
* @param index
|
||||
* - index to set element to
|
||||
* @param el
|
||||
* - element to set
|
||||
*/
|
||||
void set(int index, JzonElement el);
|
||||
|
||||
/**
|
||||
* Remove element at given index.
|
||||
*
|
||||
* @param index
|
||||
* - index to remove element from
|
||||
*/
|
||||
void remove(int index);
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap;
|
||||
|
||||
/**
|
||||
* Common abstraction for json elements.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface JzonElement {
|
||||
|
||||
/**
|
||||
* @return if this element is an object
|
||||
*/
|
||||
boolean isJsonObject();
|
||||
|
||||
/**
|
||||
* @return if this element is an array
|
||||
*/
|
||||
boolean isJsonArray();
|
||||
|
||||
/**
|
||||
* @return if this element is a primitive value
|
||||
*/
|
||||
boolean isJsonPrimitive();
|
||||
|
||||
/**
|
||||
* @return if this element is null
|
||||
*/
|
||||
boolean isJsonNull();
|
||||
|
||||
/**
|
||||
* @return the underlying implementation element
|
||||
*/
|
||||
Object unwrap();
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap;
|
||||
|
||||
/**
|
||||
* Common abstraction for json null.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface JzonNull extends JzonElement {
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Common abstraction for json objects.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface JzonObject extends JzonElement {
|
||||
|
||||
/**
|
||||
* Returns if this object has an element with the given key.
|
||||
*
|
||||
* @param key
|
||||
* - the key to look for
|
||||
* @return if this object has a key named liked that
|
||||
*/
|
||||
boolean has(String key);
|
||||
|
||||
/**
|
||||
* Adds an element with the given key.
|
||||
*
|
||||
* @param key
|
||||
* - the key for the new element
|
||||
*/
|
||||
void add(String key, JzonElement prop);
|
||||
|
||||
/**
|
||||
* Adds an integer with the given key.
|
||||
*
|
||||
* @param key
|
||||
* - the key for the new integer
|
||||
*/
|
||||
void addProperty(String key, int prop);
|
||||
|
||||
/**
|
||||
* @return entry set with key / value pairs
|
||||
*/
|
||||
Collection<? extends Entry<String, JzonElement>> entrySet();
|
||||
|
||||
/**
|
||||
* Returns element at given key.
|
||||
*
|
||||
* @param key
|
||||
* - key to look for
|
||||
* @return the element at that key if any
|
||||
*/
|
||||
JzonElement get(String key);
|
||||
|
||||
/**
|
||||
* Removes an element with the given key.
|
||||
*
|
||||
* @param key
|
||||
* - key to look for
|
||||
*/
|
||||
void remove(String key);
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap;
|
||||
|
||||
/**
|
||||
* Common abstraction for json primitive values.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface JzonPrimitive extends JzonElement {
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap;
|
||||
|
||||
/**
|
||||
* Factory wrapper interface for multiple json implementations.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface Wrapper {
|
||||
|
||||
/**
|
||||
* Parses an element given a string.
|
||||
*
|
||||
* @param json
|
||||
* - string
|
||||
* @return parsed element
|
||||
*/
|
||||
JzonElement parse(String json);
|
||||
|
||||
/**
|
||||
* Wraps a given json element.
|
||||
*
|
||||
* @param o
|
||||
* - element to wrap
|
||||
* @return the wrapped element
|
||||
*/
|
||||
JzonElement wrap(Object o);
|
||||
|
||||
/**
|
||||
* @return a new implementation independent json object
|
||||
*/
|
||||
JzonObject createJsonObject();
|
||||
|
||||
/**
|
||||
* @return a new implementation independent json array
|
||||
*/
|
||||
JzonArray createJsonArray();
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.gson;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
|
||||
public class GsonJsonArray extends GsonJsonElement implements JzonArray {
|
||||
|
||||
private final JsonArray wrapped;
|
||||
|
||||
public GsonJsonArray(JsonArray wrapped) {
|
||||
super(wrapped);
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return wrapped.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement get(int index) {
|
||||
return GsonWrapper.wrap(wrapped.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(int index, JzonElement el) {
|
||||
getElements(wrapped).add(index, (JsonElement) el.unwrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, JzonElement el) {
|
||||
getElements(wrapped).set(index, (JsonElement) el.unwrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(int index) {
|
||||
getElements(wrapped).remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
private final static Field JsonArray_elements;
|
||||
|
||||
static {
|
||||
|
||||
try {
|
||||
JsonArray_elements = JsonArray.class.getDeclaredField("elements");
|
||||
JsonArray_elements.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static ArrayList<JsonElement> getElements(JsonArray arr) {
|
||||
|
||||
try {
|
||||
return (ArrayList<JsonElement>) JsonArray_elements.get(arr);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.gson;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
|
||||
|
||||
public class GsonJsonElement implements JzonElement {
|
||||
|
||||
final JsonElement wrapped;
|
||||
|
||||
|
||||
protected GsonJsonElement(JsonElement wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonObject() {
|
||||
return wrapped.isJsonObject();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonArray() {
|
||||
return wrapped.isJsonArray();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonPrimitive() {
|
||||
return wrapped.isJsonPrimitive();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonNull() {
|
||||
return wrapped.isJsonNull();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object unwrap() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return wrapped.equals(obj);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return wrapped.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.gson;
|
||||
|
||||
import com.google.gson.JsonNull;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonNull;
|
||||
|
||||
|
||||
public class GsonJsonNull extends GsonJsonElement implements JzonNull {
|
||||
|
||||
static final JsonNull JNULL = new JsonNull();
|
||||
|
||||
|
||||
public final static GsonJsonNull INSTANCE = new GsonJsonNull();
|
||||
|
||||
|
||||
public GsonJsonNull() {
|
||||
super(JNULL);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.gson;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
|
||||
|
||||
public class GsonJsonObject extends GsonJsonElement implements JzonObject {
|
||||
|
||||
private final JsonObject wrapped;
|
||||
|
||||
|
||||
public GsonJsonObject(JsonObject wrapped) {
|
||||
super(wrapped);
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean has(String key) {
|
||||
return wrapped.has(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void add(String key, JzonElement prop) {
|
||||
wrapped.add(key, (JsonElement) prop.unwrap());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addProperty(String key, int prop) {
|
||||
wrapped.addProperty(key, prop);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<? extends Entry<String, JzonElement>> entrySet() {
|
||||
|
||||
Set<Entry<String, JsonElement>> set = wrapped.entrySet();
|
||||
|
||||
HashSet<Entry<String, JzonElement>> jset = new HashSet<Entry<String, JzonElement>>();
|
||||
|
||||
for (final Entry<String, JsonElement> e : set) {
|
||||
|
||||
final JzonElement el = GsonWrapper.wrap(e.getValue());
|
||||
|
||||
jset.add(new Entry<String, JzonElement>() {
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return e.getKey();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement getValue() {
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement setValue(JzonElement value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return jset;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement get(String key) {
|
||||
return GsonWrapper.wrap(wrapped.get(key));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void remove(String key) {
|
||||
wrapped.remove(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.gson;
|
||||
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonPrimitive;
|
||||
|
||||
|
||||
public class GsonJsonPrimitive extends GsonJsonElement implements JzonPrimitive {
|
||||
|
||||
public GsonJsonPrimitive(JsonPrimitive wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.gson;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.Wrapper;
|
||||
|
||||
public class GsonWrapper implements Wrapper {
|
||||
|
||||
private final static JsonParser JSON = new JsonParser();
|
||||
|
||||
public static JzonElement wrap(JsonElement el) {
|
||||
if (el == null || el.isJsonNull()) {
|
||||
return GsonJsonNull.INSTANCE;
|
||||
} else if (el.isJsonArray()) {
|
||||
return new GsonJsonArray((JsonArray) el);
|
||||
} else if (el.isJsonObject()) {
|
||||
return new GsonJsonObject((JsonObject) el);
|
||||
} else if (el.isJsonPrimitive()) {
|
||||
return new GsonJsonPrimitive((JsonPrimitive) el);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement parse(String json) {
|
||||
return wrap(JSON.parse(json));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement wrap(Object o) {
|
||||
return wrap((JsonElement) o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonObject createJsonObject() {
|
||||
return (JzonObject) wrap(new JsonObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonArray createJsonArray() {
|
||||
return (JzonArray) wrap(new JsonArray());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.node.ArrayNode;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
|
||||
public class JacksonJsonArray extends JacksonJsonElement implements JzonArray {
|
||||
|
||||
private final ArrayNode wrapped;
|
||||
|
||||
public JacksonJsonArray(ArrayNode wrapped) {
|
||||
super(wrapped);
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return wrapped.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement get(int index) {
|
||||
return JacksonWrapper.wrap(wrapped.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(int index, JzonElement el) {
|
||||
wrapped.insert(index, (JsonNode) el.unwrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, JzonElement el) {
|
||||
wrapped.set(index, (JsonNode) el.unwrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(int index) {
|
||||
wrapped.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
|
||||
|
||||
public class JacksonJsonElement implements JzonElement {
|
||||
|
||||
final JsonNode wrapped;
|
||||
|
||||
|
||||
protected JacksonJsonElement(JsonNode wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonObject() {
|
||||
return wrapped.isObject();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonArray() {
|
||||
return wrapped.isArray();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonPrimitive() {
|
||||
return wrapped.isValueNode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonNull() {
|
||||
return wrapped.isNull();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object unwrap() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return wrapped.equals(obj);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return wrapped.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson;
|
||||
|
||||
import org.codehaus.jackson.node.NullNode;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonNull;
|
||||
|
||||
|
||||
public class JacksonJsonNull extends JacksonJsonElement implements JzonNull {
|
||||
|
||||
static final NullNode JNULL = NullNode.getInstance();
|
||||
|
||||
|
||||
public final static JacksonJsonNull INSTANCE = new JacksonJsonNull();
|
||||
|
||||
|
||||
public JacksonJsonNull() {
|
||||
super(JNULL);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.node.ObjectNode;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
|
||||
|
||||
public class JacksonJsonObject extends JacksonJsonElement implements JzonObject {
|
||||
|
||||
private final ObjectNode wrapped;
|
||||
|
||||
|
||||
public JacksonJsonObject(ObjectNode wrapped) {
|
||||
super(wrapped);
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean has(String key) {
|
||||
return wrapped.has(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void add(String key, JzonElement prop) {
|
||||
wrapped.put(key, (JsonNode) prop.unwrap());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addProperty(String key, int prop) {
|
||||
wrapped.put(key, prop);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<? extends Entry<String, JzonElement>> entrySet() {
|
||||
|
||||
HashSet<Entry<String, JzonElement>> jset = new HashSet<Entry<String, JzonElement>>();
|
||||
|
||||
for (Iterator<Entry<String, JsonNode>> i = wrapped.getFields(); i.hasNext();) {
|
||||
|
||||
final Entry<String, JsonNode> e = i.next();
|
||||
|
||||
final JzonElement el = JacksonWrapper.wrap(e.getValue());
|
||||
|
||||
jset.add(new Entry<String, JzonElement>() {
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return e.getKey();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement getValue() {
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement setValue(JzonElement value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return jset;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement get(String key) {
|
||||
return JacksonWrapper.wrap(wrapped.get(key));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void remove(String key) {
|
||||
wrapped.remove(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson;
|
||||
|
||||
import org.codehaus.jackson.node.ValueNode;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonPrimitive;
|
||||
|
||||
|
||||
public class JacksonJsonPrimitive extends JacksonJsonElement implements JzonPrimitive {
|
||||
|
||||
public JacksonJsonPrimitive(ValueNode wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.JsonParser;
|
||||
import org.codehaus.jackson.JsonProcessingException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.node.ArrayNode;
|
||||
import org.codehaus.jackson.node.ObjectNode;
|
||||
import org.codehaus.jackson.node.ValueNode;
|
||||
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.Wrapper;
|
||||
|
||||
public class JacksonWrapper implements Wrapper {
|
||||
|
||||
private final static ObjectMapper JSON = new ObjectMapper();
|
||||
|
||||
public static JzonElement wrap(JsonNode el) {
|
||||
if (el == null || el.isNull()) {
|
||||
return JacksonJsonNull.INSTANCE;
|
||||
} else if (el.isArray()) {
|
||||
return new JacksonJsonArray((ArrayNode) el);
|
||||
} else if (el.isObject()) {
|
||||
return new JacksonJsonObject((ObjectNode) el);
|
||||
} else if (el.isValueNode()) {
|
||||
return new JacksonJsonPrimitive((ValueNode) el);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement parse(String json) {
|
||||
try {
|
||||
JsonParser parser = JSON.getJsonFactory().createJsonParser(json);
|
||||
parser.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
parser.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
return wrap(parser.readValueAsTree());
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new JacksonWrapperException("Failed to parse JSON", e);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonWrapperException("IOException parsing a String?", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement wrap(Object o) {
|
||||
return wrap((JsonNode) o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonObject createJsonObject() {
|
||||
return (JzonObject) wrap(JSON.createObjectNode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonArray createJsonArray() {
|
||||
return (JzonArray) wrap(JSON.createArrayNode());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class JacksonWrapperException extends RuntimeException {
|
||||
|
||||
public JacksonWrapperException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public JacksonWrapperException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public JacksonWrapperException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public JacksonWrapperException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
|
||||
public class Jackson2JsonArray extends Jackson2JsonElement implements JzonArray {
|
||||
|
||||
private final ArrayNode wrapped;
|
||||
|
||||
public Jackson2JsonArray(ArrayNode wrapped) {
|
||||
super(wrapped);
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return wrapped.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement get(int index) {
|
||||
return Jackson2Wrapper.wrap(wrapped.get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(int index, JzonElement el) {
|
||||
wrapped.insert(index, (JsonNode) el.unwrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, JzonElement el) {
|
||||
wrapped.set(index, (JsonNode) el.unwrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(int index) {
|
||||
wrapped.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson2;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
|
||||
|
||||
public class Jackson2JsonElement implements JzonElement {
|
||||
|
||||
final JsonNode wrapped;
|
||||
|
||||
|
||||
protected Jackson2JsonElement(JsonNode wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonObject() {
|
||||
return wrapped.isObject();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonArray() {
|
||||
return wrapped.isArray();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonPrimitive() {
|
||||
return wrapped.isValueNode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isJsonNull() {
|
||||
return wrapped.isNull();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object unwrap() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return wrapped.equals(obj);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return wrapped.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson2;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonNull;
|
||||
|
||||
|
||||
public class Jackson2JsonNull extends Jackson2JsonElement implements JzonNull {
|
||||
|
||||
static final NullNode JNULL = NullNode.getInstance();
|
||||
|
||||
|
||||
public final static Jackson2JsonNull INSTANCE = new Jackson2JsonNull();
|
||||
|
||||
|
||||
public Jackson2JsonNull() {
|
||||
super(JNULL);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
||||
public class Jackson2JsonObject extends Jackson2JsonElement implements JzonObject {
|
||||
|
||||
private final ObjectNode wrapped;
|
||||
|
||||
|
||||
public Jackson2JsonObject(ObjectNode wrapped) {
|
||||
super(wrapped);
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean has(String key) {
|
||||
return wrapped.has(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void add(String key, JzonElement prop) {
|
||||
wrapped.put(key, (JsonNode) prop.unwrap());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addProperty(String key, int prop) {
|
||||
wrapped.put(key, prop);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<? extends Entry<String, JzonElement>> entrySet() {
|
||||
|
||||
HashSet<Entry<String, JzonElement>> jset = new HashSet<Entry<String, JzonElement>>();
|
||||
|
||||
for (Iterator<Entry<String, JsonNode>> i = wrapped.fields() ; i.hasNext();) {
|
||||
|
||||
final Entry<String, JsonNode> e = i.next();
|
||||
|
||||
final JzonElement el = Jackson2Wrapper.wrap(e.getValue());
|
||||
|
||||
jset.add(new Entry<String, JzonElement>() {
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return e.getKey();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement getValue() {
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement setValue(JzonElement value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return jset;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JzonElement get(String key) {
|
||||
return Jackson2Wrapper.wrap(wrapped.get(key));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void remove(String key) {
|
||||
wrapped.remove(key);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return wrapped.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ValueNode;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonPrimitive;
|
||||
|
||||
|
||||
public class Jackson2JsonPrimitive extends Jackson2JsonElement implements JzonPrimitive {
|
||||
|
||||
public Jackson2JsonPrimitive(ValueNode wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.ValueNode;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonArray;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonElement;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.JzonObject;
|
||||
import io.metersphere.log.utils.json.diff.jsonwrap.Wrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Jackson2Wrapper implements Wrapper {
|
||||
|
||||
private final static ObjectMapper JSON = new ObjectMapper();
|
||||
|
||||
public static JzonElement wrap(JsonNode el) {
|
||||
if (el == null || el.isNull()) {
|
||||
return Jackson2JsonNull.INSTANCE;
|
||||
} else if (el.isArray()) {
|
||||
return new Jackson2JsonArray((ArrayNode) el);
|
||||
} else if (el.isObject()) {
|
||||
return new Jackson2JsonObject((ObjectNode) el);
|
||||
} else if (el.isValueNode()) {
|
||||
return new Jackson2JsonPrimitive((ValueNode) el);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement parse(String json) {
|
||||
try {
|
||||
JsonParser parser = JSON.getJsonFactory().createJsonParser(json);
|
||||
parser.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
parser.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
return wrap(parser.readValueAsTree());
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new Jackson2WrapperException("Failed to parse JSON", e);
|
||||
} catch (IOException e) {
|
||||
throw new Jackson2WrapperException("IOException parsing a String?", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonElement wrap(Object o) {
|
||||
return wrap((JsonNode) o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonObject createJsonObject() {
|
||||
return (JzonObject) wrap(JSON.createObjectNode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JzonArray createJsonArray() {
|
||||
return (JzonArray) wrap(JSON.createArrayNode());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package io.metersphere.log.utils.json.diff.jsonwrap.jackson2;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class Jackson2WrapperException extends RuntimeException {
|
||||
|
||||
public Jackson2WrapperException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Jackson2WrapperException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public Jackson2WrapperException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public Jackson2WrapperException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ public class DetailColumn {
|
|||
private String columnName;
|
||||
private Object originalValue;
|
||||
private Object newValue;
|
||||
private Object diffValue;
|
||||
|
||||
public DetailColumn() {
|
||||
|
||||
|
|
|
@ -6,10 +6,12 @@ import java.util.Map;
|
|||
public class DefinitionReference {
|
||||
public static Map<String, String> definitionColumns = new LinkedHashMap<>();
|
||||
public static Map<String, String> caseColumns = new LinkedHashMap<>();
|
||||
public static Map<String, String> jdbcColumns = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
definitionColumns.clear();
|
||||
caseColumns.clear();
|
||||
jdbcColumns.clear();
|
||||
definitionColumns.put("name", "接口名称");
|
||||
definitionColumns.put("createUser", "创建人");
|
||||
definitionColumns.put("method", "请求类型");
|
||||
|
@ -39,5 +41,11 @@ public class DefinitionReference {
|
|||
// 深度对比字段
|
||||
caseColumns.put("ms-dff-col", "request,tags");
|
||||
|
||||
jdbcColumns.put("environmentId", "运行环境");
|
||||
jdbcColumns.put("dataSourceId", "数据源名称");
|
||||
jdbcColumns.put("queryTimeout", "超时时间");
|
||||
jdbcColumns.put("resultVariable", "存储结果");
|
||||
jdbcColumns.put("variableNames", "按列存储");
|
||||
|
||||
}
|
||||
}
|
|
@ -203,14 +203,16 @@ export default {
|
|||
this.currentApi.request = JSON.parse(this.currentApi.request);
|
||||
}
|
||||
}
|
||||
if (!this.currentApi.request.hashTree) {
|
||||
if (this.currentApi && this.currentApi.request && !this.currentApi.request.hashTree) {
|
||||
this.currentApi.request.hashTree = [];
|
||||
}
|
||||
if (this.currentApi.request.body && !this.currentApi.request.body.binary) {
|
||||
if (this.currentApi && this.currentApi.request && this.currentApi.request.body && !this.currentApi.request.body.binary) {
|
||||
this.currentApi.request.body.binary = [];
|
||||
}
|
||||
this.currentApi.request.clazzName = TYPE_TO_C.get(this.currentApi.request.type);
|
||||
this.sort(this.currentApi.request.hashTree);
|
||||
if (this.currentApi.request) {
|
||||
this.currentApi.request.clazzName = TYPE_TO_C.get(this.currentApi.request.type);
|
||||
this.sort(this.currentApi.request.hashTree);
|
||||
}
|
||||
},
|
||||
mockSetting() {
|
||||
let mockParam = {};
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
<div v-if="scope.row.details && scope.row.details.columns">
|
||||
<div v-for="detail in scope.row.details.columns" :key="detail.id">
|
||||
<div v-if="linkDatas.indexOf(detail.columnName)!== -1">
|
||||
<el-link style="color: #409EFF" @click="openDetail(scope.row,detail)">{{$t('operating_log.info')}}</el-link>
|
||||
<el-link style="color: #409EFF" @click="openDetail(scope.row,detail)">{{ $t('operating_log.info') }}</el-link>
|
||||
</div>
|
||||
<el-tooltip :content="detail.originalValue" v-else>
|
||||
<div class="current-value">{{ detail.originalValue ?detail.originalValue :"空值"}}</div>
|
||||
<div class="current-value">{{ detail.originalValue ? detail.originalValue : "空值" }}</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,10 +35,10 @@
|
|||
<div v-if="scope.row.details && scope.row.details.columns">
|
||||
<div v-for="detail in scope.row.details.columns" :key="detail.id">
|
||||
<div v-if="linkDatas.indexOf(detail.columnName)!== -1">
|
||||
<el-link style="color: #409EFF" @click="openDetail(scope.row,detail)">{{$t('operating_log.info')}}</el-link>
|
||||
<el-link style="color: #409EFF" @click="openDetail(scope.row,detail)">{{ $t('operating_log.info') }}</el-link>
|
||||
</div>
|
||||
<el-tooltip :content="detail.newValue" v-else>
|
||||
<div class="current-value">{{ detail.newValue ? detail.newValue : "空值"}}</div>
|
||||
<div class="current-value">{{ detail.newValue ? detail.newValue : "空值" }}</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -46,62 +46,73 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<ms-history-detail ref="historyDetail"></ms-history-detail>
|
||||
<ms-history-detail ref="historyDetail"/>
|
||||
<ms-tags-history-detail ref="tagsHistoryDetail"/>
|
||||
<ms-api-history-detail ref="apiHistoryDetail"/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import MsHistoryDetail from "./HistoryDetail";
|
||||
import MsHistoryDetail from "./HistoryDetail";
|
||||
import MsTagsHistoryDetail from "./tags/TagsHistoryDetail";
|
||||
import MsApiHistoryDetail from "./api/ApiHistoryDetail";
|
||||
|
||||
export default {
|
||||
name: "MsChangeHistory",
|
||||
components: {MsHistoryDetail},
|
||||
props: {
|
||||
title: String,
|
||||
export default {
|
||||
name: "MsChangeHistory",
|
||||
components: {MsHistoryDetail, MsTagsHistoryDetail, MsApiHistoryDetail},
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
infoVisible: false,
|
||||
loading: false,
|
||||
details: [],
|
||||
linkDatas: ["prerequisite", "steps", "remark", "request", "response", "scenarioDefinition", "tags", "loadConfiguration", "advancedConfiguration"],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.infoVisible = false;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
infoVisible: false,
|
||||
loading: false,
|
||||
details: [],
|
||||
linkDatas: ["prerequisite", "steps", "remark", "request", "response", "scenarioDefinition","tags", "loadConfiguration", "advancedConfiguration"],
|
||||
getDetails(id) {
|
||||
this.result = this.$get("/operating/log/get/source/" + id, response => {
|
||||
let data = response.data;
|
||||
this.loading = false;
|
||||
if (data) {
|
||||
this.details = data;
|
||||
}
|
||||
})
|
||||
},
|
||||
open(id) {
|
||||
this.infoVisible = true;
|
||||
this.loading = true;
|
||||
this.getDetails(id);
|
||||
},
|
||||
openDetail(row, value) {
|
||||
value.createUser = row.details.createUser;
|
||||
value.operTime = row.operTime;
|
||||
if (value.columnName === "tags") {
|
||||
this.$refs.tagsHistoryDetail.open(value);
|
||||
} else if (value.columnName === "request" &&
|
||||
(row.operModule === "接口定义" || row.operModule === "接口定義" || row.operModule === "Api definition")) {
|
||||
this.$refs.apiHistoryDetail.open(value);
|
||||
} else {
|
||||
this.$refs.historyDetail.open(value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.infoVisible = false;
|
||||
},
|
||||
getDetails(id) {
|
||||
this.result = this.$get("/operating/log/get/source/" + id, response => {
|
||||
let data = response.data;
|
||||
this.loading =false;
|
||||
if (data) {
|
||||
this.details = data;
|
||||
}
|
||||
})
|
||||
},
|
||||
open(id) {
|
||||
this.infoVisible = true;
|
||||
this.loading = true;
|
||||
this.getDetails(id);
|
||||
},
|
||||
openDetail(row, value) {
|
||||
value.createUser = row.details.createUser;
|
||||
value.operTime = row.operTime;
|
||||
this.$refs.historyDetail.open(value);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.current-value {
|
||||
display: inline-block;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 0;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: 120px;
|
||||
}
|
||||
.current-value {
|
||||
display: inline-block;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 0;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="Config Center" name="config" v-if="request.config && request.config.length > 0">
|
||||
<el-table :data="request.config">
|
||||
<el-table-column prop="columnTitle" :label="$t('operating_log.change_field')">
|
||||
</el-table-column>
|
||||
<el-table-column prop="originalValue" :label="$t('operating_log.before_change')">
|
||||
</el-table-column>
|
||||
<el-table-column prop="newValue" :label="$t('operating_log.after_change')">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Registry Center" name="registry" v-if="request.registry && request.registry.length > 0">
|
||||
<el-table :data="request.registry">
|
||||
<el-table-column prop="columnTitle" :label="$t('operating_log.change_field')"/>
|
||||
<el-table-column prop="originalValue" :label="$t('operating_log.before_change')">
|
||||
<template v-slot:default="scope">
|
||||
<el-tooltip :content="scope.row.originalValue">
|
||||
<div class="current-value ms-tag-del">{{ scope.row.originalValue }}</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="newValue" :label="$t('operating_log.after_change')">
|
||||
<template v-slot:default="scope">
|
||||
<el-tooltip :content="scope.row.newValue">
|
||||
<div class="current-value ms-tag-add">{{ scope.row.newValue }}</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Consumer & service" name="service" v-if="request.service && request.service.length > 0">
|
||||
<el-table :data="request.service">
|
||||
<el-table-column prop="columnTitle" :label="$t('operating_log.change_field')">
|
||||
</el-table-column>
|
||||
<el-table-column prop="originalValue" :label="$t('operating_log.before_change')">
|
||||
</el-table-column>
|
||||
<el-table-column prop="newValue" :label="$t('operating_log.after_change')">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="Args" name="args" v-if="request.args && request.args.length >0 ">
|
||||
<ms-api-key-value-detail :show-required="true" :items="request.args" :showDesc="true" :format="request.headerId"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Attachment Args" name="attachment" v-if="request.attachment && request.attachment.length >0 ">
|
||||
<ms-api-key-value-detail :show-required="true" :items="request.attachment" :showDesc="true" :format="request.headerId"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValueDetail from "./common/ApiKeyValueDetail";
|
||||
|
||||
export default {
|
||||
name: "ApiDubboParameters",
|
||||
components: {MsApiKeyValueDetail},
|
||||
props: {
|
||||
request: {},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
spanNum: 21,
|
||||
activeName: "config",
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.request.config && this.request.config.length > 0) {
|
||||
this.activeName = "config";
|
||||
} else if (this.request.registry && this.request.registry.length > 0) {
|
||||
this.activeName = "registry";
|
||||
} else if (this.request.service && this.request.service.length > 0) {
|
||||
this.activeName = "service";
|
||||
} else if (this.request.args && this.request.args.length > 0) {
|
||||
this.activeName = "args";
|
||||
} else if (this.request.attachment && this.request.attachment.length > 0) {
|
||||
this.activeName = "attachment";
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'request.headerId'() {
|
||||
if (this.request.config && this.request.config.length > 0) {
|
||||
this.activeName = "config";
|
||||
} else if (this.request.registry && this.request.registry.length > 0) {
|
||||
this.activeName = "registry";
|
||||
} else if (this.request.service && this.request.service.length > 0) {
|
||||
this.activeName = "service";
|
||||
} else if (this.request.args && this.request.args.length > 0) {
|
||||
this.activeName = "args";
|
||||
} else if (this.request.attachment && this.request.attachment.length > 0) {
|
||||
this.activeName = "attachment";
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.current-value {
|
||||
display: inline-block;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 0;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: 120px;
|
||||
}
|
||||
.ms-tag-del {
|
||||
text-decoration:line-through;
|
||||
text-decoration-color: red;
|
||||
-moz-text-decoration-line: line-through;
|
||||
background: #F3E6E7;
|
||||
}
|
||||
|
||||
.ms-tag-add {
|
||||
background: #E2ECDC;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,218 @@
|
|||
<template>
|
||||
|
||||
<el-dialog :close-on-click-modal="false" :title="$t('operating_log.info')" :visible.sync="infoVisible" width="900px" :destroy-on-close="true"
|
||||
@close="handleClose" append-to-body>
|
||||
<div style="height: 700px;overflow: auto">
|
||||
<div v-if="detail.createUser">
|
||||
<p class="tip">{{ this.$t('report.user_name') }} :{{ detail.createUser }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="tip">{{ this.$t('operating_log.time') }} :{{ detail.operTime | timestampFormatDate }}</p>
|
||||
</div>
|
||||
<div style="overflow: auto">
|
||||
<p class="tip">{{ this.$t('report.test_log_details') }} </p>
|
||||
<ms-api-http-request-params :request="detail" v-if="detail.type === 'HTTPSamplerProxy'"/>
|
||||
<ms-api-tcp-parameters :request="detail" v-if="detail.type === 'TCPSampler'"/>
|
||||
<ms-api-jdbc-parameters :request="detail" v-if="detail.type === 'JDBCSampler'"/>
|
||||
<ms-api-dubbo-parameters :request="detail" v-if="detail.type === 'DubboSampler'"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import MsApiHttpRequestParams from "./ApiHttpRequestParams";
|
||||
import MsApiTcpParameters from "./ApiTcpParameters";
|
||||
import MsApiJdbcParameters from "./ApiJdbcParameters";
|
||||
import MsApiDubboParameters from "./ApiDubboParameters";
|
||||
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
import Convert from "@/business/components/common/json-schema/convert/convert";
|
||||
|
||||
export default {
|
||||
name: "MsApiHistoryDetail",
|
||||
components: {MsApiHttpRequestParams, MsApiTcpParameters, MsApiJdbcParameters, MsApiDubboParameters},
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
infoVisible: false,
|
||||
detail: {headerId: getUUID(), body: {}, type: ""},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.infoVisible = false;
|
||||
},
|
||||
open(value) {
|
||||
this.infoVisible = true;
|
||||
this.detail = value;
|
||||
let diffValue = value.diffValue;
|
||||
if (diffValue) {
|
||||
if (value != null && value.diffValue != 'null' && value.diffValue != '' && value.diffValue != undefined) {
|
||||
if (Object.prototype.toString.call(value.diffValue).match(/\[object (\w+)\]/)[1].toLowerCase() !== 'object'
|
||||
&& Object.prototype.toString.call(value.diffValue).match(/\[object (\w+)\]/)[1].toLowerCase() !== 'array') {
|
||||
diffValue = JSON.parse(value.diffValue);
|
||||
}
|
||||
}
|
||||
if (diffValue.type === 'HTTPSamplerProxy') {
|
||||
this.formatHttp(diffValue);
|
||||
} else if (diffValue.type === 'TCPSampler') {
|
||||
this.formatTcp(diffValue);
|
||||
} else if (diffValue.type === 'JDBCSampler') {
|
||||
this.formatJdbc(diffValue);
|
||||
} else if (diffValue.type === 'DubboSampler') {
|
||||
this.formatDubbo(diffValue);
|
||||
}
|
||||
this.detail.type = diffValue.type;
|
||||
}
|
||||
},
|
||||
formatJson(properties) {
|
||||
if (properties) {
|
||||
for (let key in properties) {
|
||||
let value = JSON.stringify(properties[key].mock);
|
||||
if (value && value.indexOf("**mock") !== -1) {
|
||||
properties["++" + key] = JSON.parse(JSON.stringify(properties[key]));
|
||||
properties["--" + key] = JSON.parse(JSON.stringify(properties[key]));
|
||||
properties["--" + key].mock = {mock: JSON.parse(value)["**mock"]};
|
||||
this.$delete(properties, key);
|
||||
}
|
||||
if (properties[key] && properties[key]["++description"]) {
|
||||
properties["++" + key] = JSON.parse(JSON.stringify(properties[key]));
|
||||
properties["++" + key].description = properties[key]["++description"];
|
||||
properties["--" + key] = JSON.parse(JSON.stringify(properties[key]));
|
||||
this.$delete(properties, key);
|
||||
}
|
||||
if (properties[key] && properties[key]["**description"]) {
|
||||
properties["++" + key] = JSON.parse(JSON.stringify(properties[key]));
|
||||
properties["--" + key] = JSON.parse(JSON.stringify(properties[key]));
|
||||
properties["--" + key].description = properties[key]["**description"];
|
||||
this.$delete(properties, key);
|
||||
}
|
||||
if (properties[key] && properties[key].properties) {
|
||||
this.formatJson(properties[key].properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
formatHttp(diffValue) {
|
||||
this.detail.body = {};
|
||||
this.detail.headerId = getUUID();
|
||||
if (diffValue.body) {
|
||||
let jsonSchema = (JSON.parse(diffValue.body)).jsonSchema;
|
||||
this.formatJson(jsonSchema.properties);
|
||||
this.detail.body.jsonSchema = jsonSchema;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.body_form) {
|
||||
let form = (JSON.parse(diffValue.body_form)).root;
|
||||
this.detail.body.form = form;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.body_raw_1 || diffValue.body_raw_2) {
|
||||
this.detail.body.raw_1 = diffValue.body_raw_1;
|
||||
this.detail.body.raw_2 = diffValue.body_raw_2;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.header) {
|
||||
let header = (JSON.parse(diffValue.header)).root;
|
||||
this.detail.header = header;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.query) {
|
||||
let query = (JSON.parse(diffValue.query)).root;
|
||||
this.detail.query = query;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.rest) {
|
||||
let rest = (JSON.parse(diffValue.rest)).root;
|
||||
this.detail.rest = rest;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
},
|
||||
formatTcp(diffValue) {
|
||||
if (!this.detail.body) {
|
||||
this.detail.body = {};
|
||||
}
|
||||
if (diffValue.query) {
|
||||
let parameters = (JSON.parse(diffValue.query)).root;
|
||||
this.detail.parameters = parameters;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.body_json) {
|
||||
const MsConvert = new Convert();
|
||||
let data = MsConvert.format(JSON.parse(diffValue.body_json));
|
||||
this.detail.body.jsonSchema = data;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.body_xml) {
|
||||
let parameters = (JSON.parse(diffValue.body_xml)).root;
|
||||
this.detail.body.xml = parameters;
|
||||
this.detail.body.xml_1 = diffValue.body_xml_1;
|
||||
this.detail.body.xml_2 = diffValue.body_xml_2;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.body_raw_1 || diffValue.body_raw_2) {
|
||||
this.detail.body.raw_1 = diffValue.body_raw_1 ? diffValue.body_raw_1 : "";
|
||||
this.detail.body.raw_2 = diffValue.body_raw_2 ? diffValue.body_raw_2 : "";
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.body_xml) {
|
||||
this.detail.body.xml = diffValue.body_xml;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.script_1 || diffValue.script_2) {
|
||||
this.detail.script_1 = diffValue.script_1;
|
||||
this.detail.script_2 = diffValue.script_2;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
},
|
||||
formatJdbc(diffValue) {
|
||||
if (diffValue.base) {
|
||||
let parameters = JSON.parse(diffValue.base);
|
||||
this.detail.base = parameters;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.variables) {
|
||||
let parameters = (JSON.parse(diffValue.variables)).root;
|
||||
this.detail.variables = parameters;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.query_1 || diffValue.query_2) {
|
||||
this.detail.query_1 = diffValue.query_1;
|
||||
this.detail.query_2 = diffValue.query_2;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
},
|
||||
formatDubbo(diffValue) {
|
||||
if (diffValue.config) {
|
||||
this.detail.config = JSON.parse(diffValue.config);
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.registry) {
|
||||
this.detail.registry = JSON.parse(diffValue.registry);
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.service) {
|
||||
this.detail.service = JSON.parse(diffValue.service);
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.args) {
|
||||
let parameters = (JSON.parse(diffValue.args)).root;
|
||||
this.detail.args = parameters;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
if (diffValue.attachment) {
|
||||
let parameters = (JSON.parse(diffValue.attachment)).root;
|
||||
this.detail.attachment = parameters;
|
||||
this.detail.headerId = getUUID();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,151 @@
|
|||
<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">
|
||||
<!-- HTTP 请求参数 -->
|
||||
<div style="border:1px #DCDFE6 solid;" v-if="!loading">
|
||||
<el-tabs v-model="activeName" class="request-tabs">
|
||||
<!-- 请求头-->
|
||||
<el-tab-pane :label="$t('api_test.request.headers')" name="headers" v-if="request.header">
|
||||
<ms-api-key-value-detail :items="request.header" :showDesc="true" :format="request.headerId"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!--query 参数-->
|
||||
<el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters" v-if="request.query">
|
||||
<ms-api-key-value-detail :show-required="true" :items="request.query" :showDesc="true" :format="request.headerId"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!--REST 参数-->
|
||||
<el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest" v-if="request.rest">
|
||||
<ms-api-key-value-detail :show-required="true" :items="request.rest" :showDesc="true" :format="request.headerId"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!--请求体-->
|
||||
<el-tab-pane :label="$t('api_test.request.body')" name="body" v-if="request.body && (request.body.jsonSchema ||request.body.raw_1 || request.body.raw_2 )">
|
||||
<el-radio-group v-model="activeBody" size="mini">
|
||||
<el-radio-button label="json"/>
|
||||
<el-radio-button label="raw"/>
|
||||
<el-radio-button label="form"/>
|
||||
</el-radio-group>
|
||||
<ms-json-code-edit :body="request.body" ref="jsonCodeEdit" v-if="activeBody === 'json'"/>
|
||||
<pre v-html="getDiff(request.body.raw_2,request.body.raw_1)" v-if="activeBody === 'raw'"></pre>
|
||||
<ms-api-key-value-detail :show-required="true" :items="request.body.form" :showDesc="true" :format="request.headerId" v-if="activeBody === 'form'"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 认证配置 -->
|
||||
<!-- <el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig">-->
|
||||
<!-- </el-tab-pane>-->
|
||||
|
||||
<!-- <el-tab-pane :label="$t('api_test.definition.request.other_config')" name="advancedConfig">-->
|
||||
<!-- </el-tab-pane>-->
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsJsonCodeEdit from "./json-view/ComparedEditor";
|
||||
import MsApiKeyValueDetail from "./common/ApiKeyValueDetail";
|
||||
|
||||
const jsondiffpatch = require('jsondiffpatch');
|
||||
const formattersHtml = jsondiffpatch.formatters.html;
|
||||
|
||||
export default {
|
||||
name: "MsApiHttpRequestParams",
|
||||
components: {MsJsonCodeEdit, MsApiKeyValueDetail},
|
||||
props: {
|
||||
method: String,
|
||||
request: {},
|
||||
type: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: "",
|
||||
activeBody: "",
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.request.body && (this.request.body.jsonSchema || this.request.body.form || this.request.body.raw_1 || this.request.body.raw_2)) {
|
||||
this.activeName = "body";
|
||||
if (this.request.body.jsonSchema) {
|
||||
this.activeBody = "json";
|
||||
}
|
||||
if (this.request.body.form) {
|
||||
this.activeBody = "form";
|
||||
}
|
||||
if (this.request.body.raw_1 || this.request.body.raw_2) {
|
||||
this.activeBody = "raw";
|
||||
}
|
||||
} else if (this.request.query) {
|
||||
this.activeName = "parameters";
|
||||
} else if (this.request.header) {
|
||||
this.activeName = "headers";
|
||||
} else if (this.request.rest) {
|
||||
this.activeName = "rest";
|
||||
}
|
||||
this.reloadCodeEdit();
|
||||
},
|
||||
watch: {
|
||||
'request.headerId'() {
|
||||
if (this.request.body && (this.request.body.jsonSchema || this.request.body.form || this.request.body.raw_1 || this.request.body.raw_2)) {
|
||||
this.activeName = "body";
|
||||
if (this.request.body.json) {
|
||||
this.activeBody = "json";
|
||||
}
|
||||
if (this.request.body.form) {
|
||||
this.activeBody = "form";
|
||||
}
|
||||
if (this.request.body.raw_1 || this.request.body.raw_2) {
|
||||
this.activeBody = "raw";
|
||||
}
|
||||
} else if (this.request.query) {
|
||||
this.activeName = "parameters";
|
||||
} else if (this.request.header) {
|
||||
this.activeName = "headers";
|
||||
} else if (this.request.rest) {
|
||||
this.activeName = "rest";
|
||||
}
|
||||
this.reloadCodeEdit();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDiff(v1, v2) {
|
||||
let delta = jsondiffpatch.diff(v1, v2);
|
||||
return formattersHtml.format(delta, v1);
|
||||
},
|
||||
reloadCodeEdit() {
|
||||
this.loading = true;
|
||||
this.$nextTick(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ms-query {
|
||||
background: #783887;
|
||||
color: white;
|
||||
height: 18px;
|
||||
border-radius: 42%;
|
||||
}
|
||||
|
||||
.ms-header {
|
||||
background: #783887;
|
||||
color: white;
|
||||
height: 18px;
|
||||
border-radius: 42%;
|
||||
}
|
||||
|
||||
.request-tabs {
|
||||
margin: 20px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.ms-el-link {
|
||||
float: right;
|
||||
margin-right: 45px;
|
||||
}
|
||||
|
||||
@import "~jsondiffpatch/dist/formatters-styles/html.css";
|
||||
@import "~jsondiffpatch/dist/formatters-styles/annotated.css";
|
||||
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div v-loading="isReloadData">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane :label="$t('api_test.definition.request.req_param')" name="parameters" v-if="request.base && request.base.length > 0">
|
||||
<el-table :data="request.base">
|
||||
<el-table-column prop="columnTitle" :label="$t('operating_log.change_field')">
|
||||
</el-table-column>
|
||||
<el-table-column prop="originalValue" :label="$t('operating_log.before_change')">
|
||||
</el-table-column>
|
||||
<el-table-column prop="newValue" :label="$t('operating_log.after_change')">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.scenario.variables')" name="variables" v-if="request.variables && request.variables.length >0">
|
||||
<ms-api-key-value-detail :items="request.variables" :showDesc="true" :format="request.headerId"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.sql.sql_script')" name="sql" v-if="request.query_1 || request.query_2">
|
||||
<pre v-html="getDiff(request.query_2,request.query_1)"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValueDetail from "./common/ApiKeyValueDetail";
|
||||
|
||||
const jsondiffpatch = require('jsondiffpatch');
|
||||
const formattersHtml = jsondiffpatch.formatters.html;
|
||||
|
||||
export default {
|
||||
name: "MsApiJdbcParameters",
|
||||
components: {MsApiKeyValueDetail},
|
||||
props: {
|
||||
request: {},
|
||||
basisData: {},
|
||||
moduleOptions: Array,
|
||||
showScript: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
spanNum: 21,
|
||||
environments: [],
|
||||
currentEnvironment: {},
|
||||
databaseConfigsOptions: [],
|
||||
isReloadData: false,
|
||||
activeName: "variables",
|
||||
rules: {},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'request.headerId'() {
|
||||
if (this.request.base) {
|
||||
this.activeName = "parameters";
|
||||
} else if (this.request.variables) {
|
||||
this.activeName = "variables";
|
||||
} else if (this.request.query_1 || this.request.query_2) {
|
||||
this.activeName = "sql";
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.request.base) {
|
||||
this.activeName = "parameters";
|
||||
} else if (this.request.variables) {
|
||||
this.activeName = "variables";
|
||||
} else if (this.request.query_1 || this.request.query_2) {
|
||||
this.activeName = "sql";
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
getDiff(v1, v2) {
|
||||
let delta = jsondiffpatch.diff(v1, v2);
|
||||
return formattersHtml.format(delta, v1);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.one-row .el-form-item {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.one-row .el-form-item:nth-child(2) {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
@import "~jsondiffpatch/dist/formatters-styles/html.css";
|
||||
@import "~jsondiffpatch/dist/formatters-styles/annotated.css";
|
||||
|
||||
</style>
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 98% ;">
|
||||
<el-form class="tcp" :model="request" ref="request" :disabled="isReadOnly" style="margin: 20px">
|
||||
<el-tabs v-model="activeName" class="request-tabs">
|
||||
|
||||
<el-tab-pane name="parameters" :label="$t('api_test.definition.request.req_param')" v-if="request.parameters">
|
||||
<ms-api-key-value-detail :items="request.parameters" :show-required="true" :showDesc="true" :format="request.headerId"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.definition.document.request_body')" name="request" v-if="request.body && (request.body.jsonSchema ||request.body.xml || request.body.raw_1 || request.body.raw_2)">
|
||||
<el-radio-group v-model="reportType" size="mini" style="margin: 10px 0px;">
|
||||
<el-radio :disabled="isReadOnly" label="json">
|
||||
json
|
||||
</el-radio>
|
||||
<el-radio :disabled="isReadOnly" label="xml">
|
||||
xml
|
||||
</el-radio>
|
||||
<el-radio :disabled="isReadOnly" label="raw">
|
||||
raw
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="reportType === 'xml'">
|
||||
<pre v-html="getDiff(request.body.xml_2,request.body.xml_1)"></pre>
|
||||
</div>
|
||||
<div v-if="reportType === 'json'">
|
||||
<div class="send-request">
|
||||
<ms-json-code-edit :body="request.body" ref="jsonCodeEdit"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="reportType === 'raw'">
|
||||
<div class="send-request">
|
||||
<pre v-html="getDiff(request.body.raw_2,request.body.raw_1)"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.definition.request.pre_script')" name="script" v-if="request.script_1 || request.script_2">
|
||||
<pre v-html="getDiff(request.script_2,request.script_1)"></pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- <el-tab-pane :label="$t('api_test.definition.request.other_config')" name="other" class="other-config">-->
|
||||
|
||||
<!-- </el-tab-pane>-->
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsJsonCodeEdit from "./json-view/ComparedEditor";
|
||||
import MsApiKeyValueDetail from "./common/ApiKeyValueDetail";
|
||||
|
||||
const jsondiffpatch = require('jsondiffpatch');
|
||||
const formattersHtml = jsondiffpatch.formatters.html;
|
||||
|
||||
export default {
|
||||
name: "MsApiTcpParameters",
|
||||
components: {MsJsonCodeEdit, MsApiKeyValueDetail},
|
||||
props: {
|
||||
request: {},
|
||||
basisData: {},
|
||||
moduleOptions: Array,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showScript: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
referenced: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
spanNum: 21,
|
||||
activeName: "request",
|
||||
reportType: "xml",
|
||||
isReloadData: false,
|
||||
refreshedXmlTable: true,
|
||||
currentProjectId: "",
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.request.body && (this.request.body.jsonSchema || this.request.body.xml || this.request.body.raw_1 || this.request.body.raw_2)) {
|
||||
this.activeName = "request";
|
||||
if (this.request.body.jsonSchema) {
|
||||
this.reportType = "json";
|
||||
}
|
||||
if (this.request.body.xml) {
|
||||
this.reportType = "xml";
|
||||
}
|
||||
if (this.request.body.raw_1 || this.request.body.raw_2) {
|
||||
this.reportType = "raw";
|
||||
}
|
||||
} else if (this.request.parameters) {
|
||||
this.activeName = "parameters";
|
||||
} else if (this.request.script_1 || this.request.script_2) {
|
||||
this.activeName = "script";
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'request.headerId'() {
|
||||
if (this.request.body) {
|
||||
this.activeName = "request";
|
||||
if (this.request.body.jsonSchema) {
|
||||
this.reportType = "json";
|
||||
}
|
||||
if (this.request.body.xml) {
|
||||
this.reportType = "xml";
|
||||
}
|
||||
if (this.request.body.raw_1 || this.request.body.raw_2) {
|
||||
this.reportType = "raw";
|
||||
}
|
||||
} else if (this.request.parameters) {
|
||||
this.activeName = "parameters";
|
||||
} else if (this.request.script_1 || this.request.script_2) {
|
||||
this.activeName = "script";
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDiff(v1, v2) {
|
||||
let delta = jsondiffpatch.diff(v1, v2);
|
||||
return formattersHtml.format(delta, v1);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tcp >>> .el-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.send-request {
|
||||
padding: 0px 0;
|
||||
height: 300px;
|
||||
border: 1px #DCDFE6 solid;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ms-left-cell {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.ms-left-buttion {
|
||||
margin: 6px 0px 8px 30px;
|
||||
}
|
||||
|
||||
/deep/ .el-form-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ms-left-cell {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.ms-left-buttion {
|
||||
margin: 6px 0px 8px 30px;
|
||||
}
|
||||
|
||||
/deep/ .el-form-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/deep/ .instructions-icon {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.request-tabs {
|
||||
margin: 20px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.other-config {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
@import "~jsondiffpatch/dist/formatters-styles/html.css";
|
||||
@import "~jsondiffpatch/dist/formatters-styles/annotated.css";
|
||||
|
||||
</style>
|
|
@ -0,0 +1,179 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="kv-row item" v-for="(item, index) in data" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle" :style="item.style">
|
||||
<div class="box" v-if="item.box"/>
|
||||
<el-col class="kv-checkbox" v-if="isShowEnable">
|
||||
<el-checkbox v-if="!isDisable(index)" v-model="item.enable" :disabled="isReadOnly"/>
|
||||
</el-col>
|
||||
<span style="margin-left: 10px" v-else></span>
|
||||
|
||||
<el-col class="item">
|
||||
<input class="el-input el-input__inner" v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200" show-word-limit :style="item.style"/>
|
||||
<el-autocomplete :disabled="isReadOnly" :maxlength="400" v-if="suggestions" v-model="item.name" size="small" show-word-limit :style="item.style"/>
|
||||
</el-col>
|
||||
<el-col v-if="showRequired">
|
||||
<input class="el-input el-input__inner" :disabled="isReadOnly" v-model="item.required" size="small" :style="item.style"/>
|
||||
</el-col>
|
||||
<el-col class="item">
|
||||
<input class="el-input el-input__inner" :disabled="isReadOnly" v-model="item.value" size="small" show-word-limit :style="item.style"/>
|
||||
</el-col>
|
||||
<el-col class="item" v-if="showDesc">
|
||||
<input class="el-input el-input__inner" v-model="item.description" size="small" maxlength="200"
|
||||
:style="item.style"
|
||||
:placeholder="$t('commons.description')" show-word-limit/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsApiKeyValueDetail",
|
||||
components: {},
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
isShowEnable: {
|
||||
type: Boolean,
|
||||
},
|
||||
description: String,
|
||||
items: Array,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
suggestions: Array,
|
||||
needMock: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showDesc: Boolean,
|
||||
showRequired: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
format: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyValues: [],
|
||||
loading: false,
|
||||
currentItem: {},
|
||||
isSelectAll: true,
|
||||
data: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSelectAll: function (to, from) {
|
||||
if (from == false && to == true) {
|
||||
this.selectAll();
|
||||
} else if (from == true && to == false) {
|
||||
this.invertSelect();
|
||||
}
|
||||
},
|
||||
format: function (to, from) {
|
||||
this.formatItem();
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
this.loading = true
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
isDisable: function (index) {
|
||||
return this.items.length - 1 === index;
|
||||
},
|
||||
|
||||
selectAll() {
|
||||
this.items.forEach(item => {
|
||||
item.enable = true;
|
||||
});
|
||||
},
|
||||
invertSelect() {
|
||||
this.items.forEach(item => {
|
||||
item.enable = false;
|
||||
});
|
||||
},
|
||||
formatItem() {
|
||||
this.data = [];
|
||||
if (this.items && this.items.length > 0) {
|
||||
for (let i in this.items) {
|
||||
let item = this.items[i];
|
||||
item.required = item.required ? this.$t('commons.selector.required') : this.$t('commons.selector.not_required');
|
||||
let itemMap = new Map(Object.entries(item));
|
||||
let newObj = new Map()
|
||||
let itemStr = JSON.stringify(item);
|
||||
if (itemStr.indexOf("--name") !== -1) {
|
||||
itemStr = itemStr.replaceAll("--", "");
|
||||
let obj = JSON.parse(itemStr);
|
||||
obj.style = "background:#F3E6E7;text-decoration:line-through;text-decoration-color:red";
|
||||
obj.box = true;
|
||||
this.data.push(obj);
|
||||
} else if (itemStr.indexOf("++name") !== -1) {
|
||||
itemStr = itemStr.replaceAll("++", "");
|
||||
let obj = JSON.parse(itemStr);
|
||||
obj.style = "background:#E2ECDC";
|
||||
this.data.push(obj);
|
||||
} else if (itemStr.indexOf("**") !== -1) {
|
||||
itemMap.forEach(function (value, key) {
|
||||
if (key && key.indexOf("**") !== -1) {
|
||||
item.style = item.style ? item.style : "";
|
||||
newObj[key.substr(2)] = value;
|
||||
} else {
|
||||
item.style = item.style ? item.style : "";
|
||||
newObj[key] = value;
|
||||
}
|
||||
});
|
||||
item.style = "background:#E2ECDC";
|
||||
this.data.push(item);
|
||||
newObj["box"] = true;
|
||||
newObj["style"] = "background:#F3E6E7;text-decoration:line-through;text-decoration-color:red";
|
||||
newObj["required"] = newObj.required ? this.$t('commons.selector.required') : this.$t('commons.selector.not_required');
|
||||
this.data.push(newObj);
|
||||
} else {
|
||||
this.data.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.formatItem();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-input {
|
||||
margin: 0px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.box {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
top: 16px;
|
||||
z-index: 999;
|
||||
border-color: red;
|
||||
background: red;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
color: #783887;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<div id="app" v-loading="loading">
|
||||
<div style="width: 98%">
|
||||
<compared-editor class="schema" :value="schema" lang="zh_CN" custom/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const Convert = require('./convert/convert.js');
|
||||
const MsConvert = new Convert();
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
props: {
|
||||
body: {},
|
||||
showPreview: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (!this.body.jsonSchema && this.body.raw && this.checkIsJson(this.body.raw)) {
|
||||
let obj = {"root": MsConvert.format(JSON.parse(this.body.raw))}
|
||||
this.schema = obj;
|
||||
} else if (this.body.jsonSchema) {
|
||||
this.schema = {"root": this.body.jsonSchema};
|
||||
}
|
||||
this.body.jsonSchema = this.schema.root;
|
||||
},
|
||||
watch: {
|
||||
schema: {
|
||||
handler(newValue, oldValue) {
|
||||
this.body.jsonSchema = this.schema.root;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
body: {
|
||||
handler(newValue, oldValue) {
|
||||
if (!this.body.jsonSchema && this.body.raw && this.checkIsJson(this.body.raw)) {
|
||||
let obj = {"root": MsConvert.format(JSON.parse(this.body.raw))}
|
||||
this.schema = obj;
|
||||
} else if (this.body.jsonSchema) {
|
||||
this.schema = {"root": this.body.jsonSchema};
|
||||
}
|
||||
this.body.jsonSchema = this.schema.root;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
schema:
|
||||
{
|
||||
"root": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
}
|
||||
},
|
||||
loading: false,
|
||||
preview: null,
|
||||
activeName: "apiTemplate",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openOneClickOperation() {
|
||||
this.$refs.importJson.openOneClickOperation();
|
||||
},
|
||||
checkIsJson(json) {
|
||||
try {
|
||||
JSON.parse(json);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
jsonData(data) {
|
||||
this.schema.root = {};
|
||||
this.$nextTick(() => {
|
||||
this.schema.root = data;
|
||||
this.body.jsonSchema = this.schema.root;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,32 @@
|
|||
const Mock = require('mockjs');
|
||||
const jsf = require('json-schema-faker');
|
||||
|
||||
jsf.extend('mock', function () {
|
||||
return {
|
||||
mock: function (xx) {
|
||||
if (xx && xx.startsWith("@")) {
|
||||
return Mock.mock(xx);
|
||||
}
|
||||
return xx;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const defaultOptions = {
|
||||
failOnInvalidTypes: false,
|
||||
failOnInvalidFormat: false
|
||||
};
|
||||
|
||||
export const schemaToJson = (schema, options = {}) => {
|
||||
Object.assign(options, defaultOptions);
|
||||
|
||||
jsf.option(options);
|
||||
let result;
|
||||
try {
|
||||
result = jsf.generate(schema);
|
||||
} catch (err) {
|
||||
result = err.message;
|
||||
}
|
||||
jsf.option(defaultOptions);
|
||||
return result;
|
||||
};
|
|
@ -0,0 +1,202 @@
|
|||
const isBoolean = require("lodash.isboolean");
|
||||
const isEmpty = require("lodash.isempty");
|
||||
const isInteger = require("lodash.isinteger");
|
||||
const isNull = require("lodash.isnull");
|
||||
const isNumber = require("lodash.isnumber");
|
||||
const isObject = require("lodash.isobject");
|
||||
const isString = require("lodash.isstring");
|
||||
const {post} = require("@/common/js/ajax");
|
||||
const isArray = Array.isArray;
|
||||
|
||||
|
||||
class Convert {
|
||||
constructor() {
|
||||
this._option = {
|
||||
$id: "http://example.com/root.json",
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
}
|
||||
this._object = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换函数
|
||||
* @param {*} object 需要转换的对象
|
||||
* @param {*} ?option 可选参数,目前只有能设置 root 节点的 $id 和 $schema
|
||||
*/
|
||||
format(object, option = {}) {
|
||||
// 数据校验,确保传入的的object只能是对象或数组
|
||||
if (!isObject(object)) {
|
||||
throw new TypeError("传入参数只能是对象或数组");
|
||||
}
|
||||
// 合并属性
|
||||
this._option = Object.assign(this._option, option);
|
||||
// 需要转换的对象
|
||||
this._object = object;
|
||||
let convertRes;
|
||||
// 数组类型和对象类型结构不一样
|
||||
if (isArray(object)) {
|
||||
convertRes = this._arrayToSchema();
|
||||
} else {
|
||||
convertRes = this._objectToSchema();
|
||||
}
|
||||
// 释放
|
||||
this._object = null;
|
||||
return convertRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组类型转换成JSONSCHEMA
|
||||
*/
|
||||
_arrayToSchema() {
|
||||
// root节点基本信息
|
||||
let result = this._value2object(this._object, this._option.$id, "", true);
|
||||
if (this._object.length > 0) {
|
||||
let itemArr = [];
|
||||
for (let index = 0; index < this._object.length; index++) {
|
||||
// 创建items对象的基本信息
|
||||
let objectItem = this._object[index]
|
||||
let item = this._value2object(objectItem, `#/items`, 'items');
|
||||
if (isObject(objectItem) && !isEmpty(objectItem)) {
|
||||
// 递归遍历
|
||||
let objectItemSchema = this._json2schema(objectItem, `#/items`);
|
||||
// 合并对象
|
||||
item = Object.assign(item, objectItemSchema);
|
||||
}
|
||||
itemArr.push(item);
|
||||
}
|
||||
result["items"] = itemArr;
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象类型转换成JSONSCHEMA
|
||||
*/
|
||||
_objectToSchema() {
|
||||
let baseResult = this._value2object(this._object, this._option.$id, "", true)
|
||||
let objectSchema = this._json2schema(this._object)
|
||||
baseResult = Object.assign(baseResult, objectSchema)
|
||||
return baseResult
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归函数,转换object对象为json schmea 格式
|
||||
* @param {*} object 需要转换对象
|
||||
* @param {*} name $id值
|
||||
*/
|
||||
_json2schema(object, name = "") {
|
||||
// 如果递归值不是对象,那么return掉
|
||||
if (!isObject(object)) {
|
||||
return;
|
||||
}
|
||||
// 处理当前路径$id
|
||||
if (name === "" || name == undefined) {
|
||||
name = "#"
|
||||
}
|
||||
let result = {};
|
||||
// 判断传入object是对象还是数组。
|
||||
if (isArray(object)) {
|
||||
result.items = {};
|
||||
} else {
|
||||
result.properties = {};
|
||||
}
|
||||
// 遍历传入的对象
|
||||
for (const key in object) {
|
||||
if (object.hasOwnProperty(key)) {
|
||||
const element = object[key];
|
||||
// 如果只是undefined。跳过
|
||||
if (element === undefined) {
|
||||
continue;
|
||||
}
|
||||
let $id = `${name}/properties/${key}`
|
||||
// 判断当前 element 的值 是否也是对象,如果是就继续递归,不是就赋值给result
|
||||
if (isObject(element)) {
|
||||
// 创建当前属性的基本信息
|
||||
result["properties"][key] = this._value2object(element, $id, key)
|
||||
if (isArray(element)) {
|
||||
// 针对空数组和有值的数组做不同处理
|
||||
if (element.length > 0) {
|
||||
// 是数组
|
||||
let itemArr = [];
|
||||
for (let index = 0; index < element.length; index++) {
|
||||
let elementItem = element[index];
|
||||
// 创建items对象的基本信息
|
||||
let item = this._value2object(elementItem, `${$id}/items`, key + 'items');
|
||||
// 判断第一项是否是对象,且对象属性不为空
|
||||
if (isObject(elementItem) && !isEmpty(elementItem)) {
|
||||
// 新增的properties才合并进来
|
||||
item = Object.assign(item, this._json2schema(elementItem, `${$id}/items`));
|
||||
}
|
||||
itemArr.push(item);
|
||||
}
|
||||
result["properties"][key]["items"] = itemArr;
|
||||
}
|
||||
} else {
|
||||
// 不是数组,递归遍历获取,然后合并对象属性
|
||||
result["properties"][key] = Object.assign(result["properties"][key], this._json2schema(element, $id));
|
||||
}
|
||||
} else {
|
||||
// 一般属性直接获取基本信息
|
||||
result["properties"][key] = this._value2object(element, $id, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把json的值转换成对象类型
|
||||
* @param {*} value
|
||||
* @param {*} $id
|
||||
* @param {*} key
|
||||
*/
|
||||
_value2object(value, $id, key = '', root = false) {
|
||||
let objectTemplate = {
|
||||
$id: $id,
|
||||
title: `The ${key} Schema`,
|
||||
mock: {
|
||||
"mock": value
|
||||
},
|
||||
}
|
||||
|
||||
// 判断是否为初始化root数据
|
||||
if (root) {
|
||||
objectTemplate["$schema"] = this._option.$schema;
|
||||
objectTemplate["title"] = `The Root Schema`;
|
||||
objectTemplate["mock"] = undefined;
|
||||
}
|
||||
if (isBoolean(value)) {
|
||||
objectTemplate.type = "boolean";
|
||||
} else if (isInteger(value)) {
|
||||
objectTemplate.type = "integer";
|
||||
} else if (isNumber(value)) {
|
||||
objectTemplate.type = "number";
|
||||
} else if (isString(value)) {
|
||||
objectTemplate.type = "string";
|
||||
} else if (isNull(value)) {
|
||||
objectTemplate.type = "null";
|
||||
} else if (isArray(value)) {
|
||||
objectTemplate.type = "array";
|
||||
objectTemplate["mock"] = undefined;
|
||||
} else if (isObject(value)) {
|
||||
objectTemplate.type = "object"
|
||||
objectTemplate["mock"] = undefined;
|
||||
}
|
||||
|
||||
return objectTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台转换
|
||||
* @param callback
|
||||
*/
|
||||
schemaToJsonStr(schema, callback) {
|
||||
post('/api/definition/preview', schema, (response) => {
|
||||
if (callback) {
|
||||
callback(response.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Convert;
|
|
@ -0,0 +1,7 @@
|
|||
import ComparedEditor from './main.vue'
|
||||
|
||||
ComparedEditor.install = function (Vue) {
|
||||
Vue.component(ComparedEditor.name, ComparedEditor)
|
||||
}
|
||||
|
||||
export default ComparedEditor
|
|
@ -0,0 +1,430 @@
|
|||
<template>
|
||||
<div class="json-schema-editor">
|
||||
<el-row id="rowId" class="row" :gutter="20">
|
||||
<div class="box" v-if="pickOpt"/>
|
||||
<el-col :span="8" class="ms-col-name">
|
||||
<div :style="{marginLeft:`${10*deep}px`}" class="ms-col-name-c"/>
|
||||
<span v-if="pickValue.type==='object'" :class="hidden? 'el-icon-caret-left ms-transform':
|
||||
'el-icon-caret-bottom'" @click="hidden = !hidden"/>
|
||||
<span v-else style="width:10px;display:inline-block"/>
|
||||
<input class="el-input el-input__inner ms-input-css" :style="{'background':getBg()}" :disabled="disabled" :value="pickKey" @blur="onInputName" size="small"/>
|
||||
|
||||
<el-tooltip v-if="root" :content="$t('schema.checked_all')" placement="top">
|
||||
<input type="checkbox" :disabled="disabled" class="ms-col-name-required" :style="{'background':getBg()}" @change="onRootCheck"/>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else :content="$t('schema.required')" placement="top">
|
||||
<input type="checkbox" :disabled="disabled" :checked="checked" :style="{'background-color':getBg()}" class="ms-col-name-required" @change="onCheck"/>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<input v-model="pickValue.type" :disabled="disabled" class="el-input el-input__inner ms-input-css" size="small" :style="{'background-color':getBg()}"/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<input v-if="pickValue && pickValue.mock" v-model="pickValue.mock.mock" :disabled="disabled" class="el-input el-input__inner ms-input-css" size="small" :style="{'background-color':getBg()}"/>
|
||||
<input v-else v-model="defaultValue" :disabled="disabled" class="el-input el-input__inner ms-input-css" size="small" :style="{'background-color':getBg()}"/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<input v-model="pickValue.description" :disabled="disabled" class="el-input el-input__inner ms-input-css" size="small" :style="{'background-color':getBg()}"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<template v-if="!hidden && pickValue.properties && !isArray && reloadItemOver">
|
||||
<compared-editor v-for="(item,key,index) in pickValue.properties"
|
||||
:value="{[key]:item}" :parent="pickValue" :key="index" :deep="deep+1" :root="false" class="children" :lang="lang" :custom="custom" @changeAllItemsType="changeAllItemsType" @reloadItems="reloadItems"/>
|
||||
</template>
|
||||
<template v-if="isArray && reloadItemOver">
|
||||
<compared-editor v-for="(item,key,index) in pickValue.items" :value="{[key]:item}" :parent="pickValue" :key="index" :deep="deep+1" :root="false" class="children" :lang="lang" :custom="custom" @changeAllItemsType="changeAllItemsType"/>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {isNull} from './util'
|
||||
import {TYPE_NAME, TYPE} from './type/type'
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'ComparedEditor',
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
disabled: { //name不可编辑,根节点name不可编辑,数组元素name不可编辑
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabledType: { //禁用类型选择
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isItem: { //是否数组元素
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
deep: { // 节点深度,根节点deep=0
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
root: { //是否root节点
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
parent: { //父节点
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
custom: { //enable custom properties
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
lang: { // i18n language
|
||||
type: String,
|
||||
default: 'zh_CN'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pickValue() {
|
||||
return Object.values(this.value)[0]
|
||||
},
|
||||
pickOpt() {
|
||||
let value = Object.keys(this.value)[0];
|
||||
if (value && value.indexOf("--") !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
pickKey() {
|
||||
let value = Object.keys(this.value)[0];
|
||||
if (value && value.indexOf("--") !== -1) {
|
||||
return value.substr(2);
|
||||
} else if (value && value.indexOf("++") !== -1) {
|
||||
return value.substr(2);
|
||||
}
|
||||
return Object.keys(this.value)[0]
|
||||
},
|
||||
isObject() {
|
||||
return this.pickValue.type === 'object'
|
||||
},
|
||||
isArray() {
|
||||
return this.pickValue.type === 'array'
|
||||
},
|
||||
checked() {
|
||||
return this.parent && this.parent.required && this.parent.required.indexOf(this.pickKey) >= 0
|
||||
},
|
||||
advanced() {
|
||||
return TYPE[this.pickValue.type]
|
||||
},
|
||||
advancedAttr() {
|
||||
return TYPE[this.pickValue.type].attr
|
||||
},
|
||||
advancedNotEmptyValue() {
|
||||
const jsonNode = Object.assign({}, this.advancedValue);
|
||||
for (let key in jsonNode) {
|
||||
isNull(jsonNode[key]) && delete jsonNode[key]
|
||||
}
|
||||
return jsonNode
|
||||
},
|
||||
completeNodeValue() {
|
||||
const t = {}
|
||||
for (const item of this.customProps) {
|
||||
t[item.key] = item.value
|
||||
}
|
||||
return Object.assign({}, this.pickValue, this.advancedNotEmptyValue, t)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
TYPE_NAME,
|
||||
hidden: false,
|
||||
countAdd: 1,
|
||||
modalVisible: false,
|
||||
reloadItemOver: true,
|
||||
advancedValue: {},
|
||||
addProp: {},// 自定义属性
|
||||
customProps: [],
|
||||
customing: false,
|
||||
defaultValue: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getBg() {
|
||||
let value = Object.keys(this.value)[0];
|
||||
if (value && value.indexOf("--") !== -1) {
|
||||
return "#F3E6E7";
|
||||
} else if (value && value.indexOf("++") !== -1) {
|
||||
return "#E2ECDC";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
onInputName(e) {
|
||||
const val = e.target.value
|
||||
const p = {};
|
||||
for (let key in this.parent.properties) {
|
||||
if (key != this.pickKey) {
|
||||
p[key] = this.parent.properties[key]
|
||||
} else {
|
||||
p[val] = this.parent.properties[key]
|
||||
delete this.parent.properties[key]
|
||||
}
|
||||
}
|
||||
this.$set(this.parent, 'properties', p)
|
||||
},
|
||||
onChangeType() {
|
||||
if (this.parent && this.parent.type === 'array') {
|
||||
this.$emit('changeAllItemsType', this.pickValue.type);
|
||||
} else {
|
||||
this.$delete(this.pickValue, 'properties')
|
||||
this.$delete(this.pickValue, 'items')
|
||||
this.$delete(this.pickValue, 'required')
|
||||
this.$delete(this.pickValue, 'mock')
|
||||
if (this.isArray) {
|
||||
this.$set(this.pickValue, 'items', [{type: 'string', mock: {mock: ""}}]);
|
||||
}
|
||||
}
|
||||
},
|
||||
changeAllItemsType(changeType) {
|
||||
if (this.isArray && this.pickValue.items && this.pickValue.items.length > 0) {
|
||||
this.pickValue.items.forEach(item => {
|
||||
item.type = changeType;
|
||||
this.$delete(item, 'properties')
|
||||
this.$delete(item, 'items')
|
||||
this.$delete(item, 'required')
|
||||
this.$delete(item, 'mock')
|
||||
if (changeType === 'array') {
|
||||
this.$set(item, 'items', [{type: 'string', mock: {mock: ""}}]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onCheck(e) {
|
||||
this._checked(e.target.checked, this.parent)
|
||||
},
|
||||
onRootCheck(e) {
|
||||
const checked = e.target.checked
|
||||
this._deepCheck(checked, this.pickValue)
|
||||
},
|
||||
_deepCheck(checked, node) {
|
||||
if (node.type === 'object' && node.properties) {
|
||||
checked ? this.$set(node, 'required', Object.keys(node.properties)) : this.$delete(node, 'required')
|
||||
Object.keys(node.properties).forEach(key => this._deepCheck(checked, node.properties[key]))
|
||||
} else if (node.type === 'array' && node.items.type === 'object') {
|
||||
checked ? this.$set(node.items, 'required', Object.keys(node.items.properties)) : this.$delete(node.items, 'required')
|
||||
Object.keys(node.items.properties).forEach(key => this._deepCheck(checked, node.items.properties[key]))
|
||||
}
|
||||
},
|
||||
_checked(checked, parent) {
|
||||
let required = parent.required
|
||||
if (checked) {
|
||||
required || this.$set(this.parent, 'required', [])
|
||||
|
||||
required = this.parent.required
|
||||
required.indexOf(this.pickKey) === -1 && required.push(this.pickKey)
|
||||
} else {
|
||||
const pos = required.indexOf(this.pickKey)
|
||||
pos >= 0 && required.splice(pos, 1)
|
||||
}
|
||||
required.length === 0 && this.$delete(parent, 'required')
|
||||
},
|
||||
addChild() {
|
||||
const node = this.pickValue;
|
||||
if (this.isArray) {
|
||||
let childObj = {type: 'string', mock: {mock: ""}}
|
||||
if (node.items && node.items.length > 0) {
|
||||
childObj.type = node.items[0].type;
|
||||
node.items.push(childObj);
|
||||
} else {
|
||||
this.$set(this.pickValue, 'items', [childObj]);
|
||||
}
|
||||
|
||||
} else {
|
||||
const name = this._joinName()
|
||||
const type = 'string'
|
||||
node.properties || this.$set(node, 'properties', {})
|
||||
const props = node.properties
|
||||
this.$set(props, name, {type: type, mock: {mock: ""}})
|
||||
}
|
||||
},
|
||||
addCustomNode() {
|
||||
this.$set(this.addProp, 'key', this._joinName())
|
||||
this.$set(this.addProp, 'value', '')
|
||||
this.customing = true
|
||||
},
|
||||
confirmAddCustomNode() {
|
||||
this.customProps.push(this.addProp)
|
||||
this.addProp = {}
|
||||
this.customing = false
|
||||
},
|
||||
removeNode() {
|
||||
if (this.parent.type && this.parent.type === 'object') {
|
||||
const {properties, required} = this.parent
|
||||
this.$delete(properties, this.pickKey)
|
||||
if (required) {
|
||||
const pos = required.indexOf(this.pickKey)
|
||||
pos >= 0 && required.splice(pos, 1)
|
||||
required.length === 0 && this.$delete(this.parent, 'required')
|
||||
}
|
||||
} else if (this.parent.type && this.parent.type === 'array') {
|
||||
const {items, required} = this.parent
|
||||
this.$delete(items, this.pickKey)
|
||||
if (required) {
|
||||
const pos = required.indexOf(this.pickKey)
|
||||
pos >= 0 && required.splice(pos, 1)
|
||||
required.length === 0 && this.$delete(this.parent, 'required')
|
||||
}
|
||||
}
|
||||
this.parentReloadItems();
|
||||
},
|
||||
_joinName() {
|
||||
return `feild_${this.deep}_${this.countAdd++}_${getUUID().substring(0, 5)}`
|
||||
},
|
||||
onSetting() {
|
||||
this.modalVisible = true;
|
||||
this.advancedValue = {};
|
||||
this.advancedValue = this.advanced.value
|
||||
for (const k in this.advancedValue) {
|
||||
this.advancedValue[k] = this.pickValue[k]
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.modalVisible = false;
|
||||
},
|
||||
handleOk() {
|
||||
this.modalVisible = false
|
||||
for (const key in this.advancedValue) {
|
||||
if (isNull(this.advancedValue[key])) {
|
||||
this.$delete(this.pickValue, key)
|
||||
} else {
|
||||
this.$set(this.pickValue, key, this.advancedValue[key])
|
||||
}
|
||||
}
|
||||
for (const item of this.customProps) {
|
||||
this.$set(this.pickValue, item.key, item.value)
|
||||
}
|
||||
},
|
||||
parentReloadItems() {
|
||||
this.$emit("reloadItems");
|
||||
},
|
||||
reloadItems() {
|
||||
this.reloadItemOver = false;
|
||||
this.$nextTick(() => {
|
||||
this.reloadItemOver = true;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
.row-add {
|
||||
background: #E2ECDC;
|
||||
}
|
||||
|
||||
.row-del {
|
||||
text-decoration: none;
|
||||
text-decoration-color: red;
|
||||
background: #F3E6E7;
|
||||
}
|
||||
|
||||
.row-update {
|
||||
background: #E2ECDC;
|
||||
}
|
||||
|
||||
.json-schema-editor .row {
|
||||
display: flex;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .ms-col-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .ms-col-name .ms-col-name-c {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .ms-col-name .ms-col-name-required {
|
||||
flex: 0 0 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .ms-col-type {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .ms-col-setting {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .setting-icon {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .plus-icon {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ms-input-css {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.json-schema-editor .row .close-icon {
|
||||
color: #888;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.json-schema-editor-advanced-modal {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.json-schema-editor-advanced-modal pre {
|
||||
font-family: monospace;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.json-schema-editor-advanced-modal h3 {
|
||||
display: block;
|
||||
border-left: 3px solid #1890ff;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.json-schema-editor-advanced-modal .ms-advanced-search-form {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.json-schema-editor-advanced-modal .ms-advanced-search-form .ms-form-item .ms-form-item-control-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.col-item-setting {
|
||||
padding-top: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ms-transform {
|
||||
transform: rotate(-180deg);
|
||||
transition: 0ms;
|
||||
}
|
||||
|
||||
.box {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
top: 16px;
|
||||
z-index: 999;
|
||||
border-color: red;
|
||||
background: red;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,23 @@
|
|||
const value = {
|
||||
description: null
|
||||
}
|
||||
const attr = {
|
||||
description: {
|
||||
name: '描述',
|
||||
type: 'string'
|
||||
},
|
||||
maxItems:{
|
||||
name: '最大元素个数',
|
||||
type: 'integer'
|
||||
},
|
||||
minItems:{
|
||||
name: '最小元素个数',
|
||||
type: 'integer'
|
||||
},
|
||||
uniqueItems:{
|
||||
name:'元素不可重复',
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
const wrapper = {value, attr}
|
||||
export default wrapper
|
|
@ -0,0 +1,11 @@
|
|||
const value = {
|
||||
description: null
|
||||
}
|
||||
const attr = {
|
||||
description: {
|
||||
name: '描述',
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
const wrapper = {value, attr}
|
||||
export default wrapper
|
|
@ -0,0 +1,40 @@
|
|||
const value = {
|
||||
maximum: null,
|
||||
minimum: null,
|
||||
default: null,
|
||||
enum: null,
|
||||
description: null,
|
||||
}
|
||||
const attr = {
|
||||
description: {
|
||||
name: '描述',
|
||||
type: 'string',
|
||||
},
|
||||
maximum: {
|
||||
name: '最大值',
|
||||
type: 'integer'
|
||||
},
|
||||
minimum: {
|
||||
name: '最小值',
|
||||
type: 'integer'
|
||||
},
|
||||
exclusiveMaximum: {
|
||||
name: '不包含最大值',
|
||||
type: 'boolean'
|
||||
},
|
||||
exclusiveMinimum: {
|
||||
name: '不包含最小值',
|
||||
type: 'boolean'
|
||||
},
|
||||
default: {
|
||||
name: '默认值',
|
||||
type: 'integer',
|
||||
},
|
||||
enum: {
|
||||
name: '枚举值',
|
||||
type: 'textarea',
|
||||
description: "一行一个枚举值"
|
||||
},
|
||||
}
|
||||
const wrapper = {value, attr}
|
||||
export default wrapper
|
|
@ -0,0 +1,32 @@
|
|||
const value = {
|
||||
maximum: null,
|
||||
minimum: null,
|
||||
default: null,
|
||||
enum: null,
|
||||
description: null,
|
||||
}
|
||||
const attr = {
|
||||
description: {
|
||||
name: '描述',
|
||||
type: 'string',
|
||||
},
|
||||
maximum: {
|
||||
name: '最大值',
|
||||
type: 'number'
|
||||
},
|
||||
minimum: {
|
||||
name: '最小值',
|
||||
type: 'number'
|
||||
},
|
||||
default: {
|
||||
name: '默认值',
|
||||
type: 'string',
|
||||
},
|
||||
enum: {
|
||||
name: '枚举值',
|
||||
type: 'textarea',
|
||||
description: "一行一个枚举值"
|
||||
},
|
||||
}
|
||||
const wrapper = {value, attr}
|
||||
export default wrapper
|
|
@ -0,0 +1,19 @@
|
|||
const value = {
|
||||
description: null
|
||||
}
|
||||
const attr = {
|
||||
description: {
|
||||
name: '描述',
|
||||
type: 'string',
|
||||
},
|
||||
maxProperties:{
|
||||
name:'最大元素个数',
|
||||
type:'integer'
|
||||
},
|
||||
minProperties:{
|
||||
name:'最小元素个数',
|
||||
type:'integer'
|
||||
}
|
||||
}
|
||||
const wrapper = {value, attr}
|
||||
export default wrapper
|
|
@ -0,0 +1,43 @@
|
|||
const value = {
|
||||
maxLength: null,
|
||||
minLength: null,
|
||||
default: null,
|
||||
enum: null,
|
||||
pattern: null,
|
||||
format: null,
|
||||
description: null,
|
||||
}
|
||||
const attr = {
|
||||
maxLength: {
|
||||
name: '最大字符数',
|
||||
type: 'integer'
|
||||
},
|
||||
minLength: {
|
||||
name: '最小字符数',
|
||||
type: 'integer'
|
||||
},
|
||||
default: {
|
||||
name: '默认值',
|
||||
type: 'string',
|
||||
},
|
||||
enum: {
|
||||
name: '枚举值',
|
||||
type: 'textarea',
|
||||
description:"一行一个枚举值"
|
||||
},
|
||||
pattern: {
|
||||
name: '正则表达式',
|
||||
type: 'string'
|
||||
},
|
||||
format: {
|
||||
name: '格式',
|
||||
type: 'array',
|
||||
enums: ['date', 'date-time', 'email', 'hostname', 'ipv4', 'ipv6', 'uri']
|
||||
},
|
||||
description: {
|
||||
name: '描述',
|
||||
type: 'string',
|
||||
}
|
||||
}
|
||||
const wrapper = {value, attr}
|
||||
export default wrapper
|
|
@ -0,0 +1,17 @@
|
|||
import _object from './object'
|
||||
import _string from './string'
|
||||
import _array from './array'
|
||||
import _boolean from './boolean'
|
||||
import _integer from './integer'
|
||||
import _number from './number'
|
||||
const TYPE_NAME = ['string', 'number', 'integer','object', 'array', 'boolean']
|
||||
|
||||
const TYPE = {
|
||||
'object': _object,
|
||||
'string': _string,
|
||||
'array': _array,
|
||||
'boolean': _boolean,
|
||||
'integer': _integer,
|
||||
'number': _number
|
||||
}
|
||||
export {TYPE ,TYPE_NAME}
|
|
@ -0,0 +1,10 @@
|
|||
export function isNull(ele) {
|
||||
if (typeof ele === 'undefined') {
|
||||
return true;
|
||||
} else if (ele === null) {
|
||||
return true;
|
||||
} else if (ele === '') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import ComparedEditor from './editor/index'
|
||||
|
||||
const components = [
|
||||
ComparedEditor
|
||||
]
|
||||
|
||||
// 定义 install 方法
|
||||
const install = function (Vue) {
|
||||
if (install.installed) {
|
||||
return;
|
||||
}
|
||||
install.installed = true;
|
||||
// 遍历并注册全局组件
|
||||
components.map(component => {
|
||||
Vue.component(component.name, component)
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
install(window.Vue)
|
||||
}
|
||||
|
||||
export default {
|
||||
// 导出的对象必须具备一个 install 方法
|
||||
install,
|
||||
// 组件列表
|
||||
...components
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<div
|
||||
class="el-input-tag input-tag-wrapper"
|
||||
:class="[size ? 'el-input-tag--' + size : '']"
|
||||
style="height: auto">
|
||||
|
||||
<el-tag
|
||||
:class="getClass(tag)"
|
||||
v-for="(tag, idx) in innerTags"
|
||||
v-bind="$attrs"
|
||||
type="info"
|
||||
:key="tag"
|
||||
:size="size"
|
||||
:closable="!readOnly"
|
||||
:disable-transitions="false"
|
||||
@close="remove(idx)">
|
||||
{{ getTag(tag) }}
|
||||
</el-tag>
|
||||
<input
|
||||
:disabled="readOnly"
|
||||
class="tag-input el-input"
|
||||
v-model="newTag"
|
||||
:placeholder="$t('commons.tag_tip')"
|
||||
@keydown.delete.stop="removeLastTag"
|
||||
@keydown="addNew"
|
||||
@blur="addNew"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MsInputTag',
|
||||
props: {
|
||||
data: {},
|
||||
addTagOnKeys: {
|
||||
type: Array,
|
||||
default: () => [13, 188, 9]
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {type: String, default: "small"},
|
||||
prop: {
|
||||
type: String,
|
||||
default: "diffValue"
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.data[this.prop]) {
|
||||
this.data[this.prop] = [];
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newTag: '',
|
||||
innerTags: this.data[this.prop] ? [...this.data[this.prop]] : []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerTags() {
|
||||
this.data[this.prop] = this.innerTags;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addNew(e) {
|
||||
if (e && (!this.addTagOnKeys.includes(e.keyCode)) && (e.type !== 'blur')) {
|
||||
return
|
||||
}
|
||||
if (e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
let addSuccess = false
|
||||
if (this.newTag.includes(',')) {
|
||||
this.newTag.split(',').forEach(item => {
|
||||
if (this.addTag(item.trim())) {
|
||||
addSuccess = true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (this.addTag(this.newTag.trim())) {
|
||||
addSuccess = true
|
||||
}
|
||||
}
|
||||
if (addSuccess) {
|
||||
this.tagChange()
|
||||
this.newTag = ''
|
||||
}
|
||||
},
|
||||
addTag(tag) {
|
||||
tag = tag.trim()
|
||||
if (tag && !this.innerTags.includes(tag)) {
|
||||
this.innerTags.push(tag)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
remove(index) {
|
||||
this.innerTags.splice(index, 1)
|
||||
this.tagChange()
|
||||
},
|
||||
removeLastTag() {
|
||||
if (this.newTag) {
|
||||
return
|
||||
}
|
||||
this.innerTags.pop()
|
||||
this.tagChange()
|
||||
},
|
||||
tagChange() {
|
||||
this.$emit('input', this.innerTags)
|
||||
},
|
||||
getTag(tag) {
|
||||
if (tag && (tag.indexOf("++") !== -1 || tag.indexOf("--") !== -1)) {
|
||||
tag = tag.substring(2);
|
||||
}
|
||||
return tag && tag.length > 10 ? tag.substring(0, 10) + "..." : tag;
|
||||
},
|
||||
getClass(tag) {
|
||||
if (tag && tag.indexOf("++") !== -1) {
|
||||
return "ms-tag-add";
|
||||
}
|
||||
if (tag && tag.indexOf("--") !== -1) {
|
||||
return "ms-tag-del";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.input-tag-wrapper {
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdfe6;
|
||||
box-sizing: border-box;
|
||||
color: #606266;
|
||||
display: inline-block;
|
||||
outline: none;
|
||||
padding: 0 10px 0 5px;
|
||||
transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.tag-input {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: #303133;
|
||||
font-size: 12px;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
|
||||
outline: none;
|
||||
padding-left: 0;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.el-input-tag {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.el-input-tag--mini {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-input-tag--small {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.el-input-tag--medium {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.ms-tag-del {
|
||||
text-decoration:line-through;
|
||||
text-decoration-color: red;
|
||||
-moz-text-decoration-line: line-through;
|
||||
background: #F3E6E7;
|
||||
}
|
||||
|
||||
.ms-tag-add {
|
||||
background: #E2ECDC;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
|
||||
<el-dialog :close-on-click-modal="false" :title="$t('operating_log.info')" :visible.sync="infoVisible" width="900px" :destroy-on-close="true"
|
||||
@close="handleClose" append-to-body>
|
||||
<div style="height: 700px;overflow: auto">
|
||||
<div v-if="detail.createUser">
|
||||
<p class="tip">{{ this.$t('report.user_name') }} :{{ detail.createUser }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="tip">{{ this.$t('operating_log.time') }} :{{ detail.operTime | timestampFormatDate }}</p>
|
||||
</div>
|
||||
<div style="overflow: auto">
|
||||
<p class="tip">{{ this.$t('report.test_log_details') }} </p>
|
||||
<div v-if="!loading">
|
||||
{{ $t('commons.tag') }}:
|
||||
<ms-input-tag :read-only="true" :data="detail" ref="tag" style="width: 90%"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsInputTag from "./MsInputTag";
|
||||
|
||||
export default {
|
||||
name: "MsTagsHistoryDetail",
|
||||
components: {MsInputTag},
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
infoVisible: false,
|
||||
detail: {},
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.infoVisible = false;
|
||||
this.detail = {};
|
||||
},
|
||||
open(value) {
|
||||
this.infoVisible = true;
|
||||
this.detail = value;
|
||||
if (value != null && value.diffValue != 'null' && value.diffValue != '' && value.diffValue != undefined) {
|
||||
if (Object.prototype.toString.call(value.diffValue).match(/\[object (\w+)\]/)[1].toLowerCase() !== 'object'
|
||||
&& Object.prototype.toString.call(value.diffValue).match(/\[object (\w+)\]/)[1].toLowerCase() !== 'array') {
|
||||
let diffValue = JSON.parse(value.diffValue);
|
||||
if (diffValue) {
|
||||
this.detail.diffValue = diffValue.root;
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.reload();
|
||||
},
|
||||
getType(type) {
|
||||
return this.LOG_TYPE_MAP.get(type);
|
||||
},
|
||||
reload() {
|
||||
this.loading = true
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -20,6 +20,7 @@ import CKEditor from '@ckeditor/ckeditor5-vue';
|
|||
import VueFab from 'vue-float-action-button'
|
||||
import {left2RightDrag, bottom2TopDrag, right2LeftDrag} from "../common/js/directive";
|
||||
import JsonSchemaEditor from './components/common/json-schema/schema/index';
|
||||
import ComparedEditor from './components/history/api/json-view/schema/index';
|
||||
import JSONPathPicker from 'vue-jsonpath-picker';
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import vueMinderEditor from 'vue-minder-editor-plus'
|
||||
|
@ -32,6 +33,8 @@ Vue.use(mavonEditor)
|
|||
Vue.use(vueMinderEditor)
|
||||
|
||||
Vue.use(JsonSchemaEditor);
|
||||
Vue.use(ComparedEditor);
|
||||
|
||||
import VuePapaParse from 'vue-papa-parse'
|
||||
Vue.use(VuePapaParse)
|
||||
Vue.use(formCreate);
|
||||
|
|
Loading…
Reference in New Issue