001/** 002 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.gnu.org/licenses/lgpl-3.0.txt 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package dev.tinyflow.core.parser; 017 018import com.alibaba.fastjson.JSON; 019import com.alibaba.fastjson.JSONArray; 020import com.alibaba.fastjson.JSONObject; 021import dev.tinyflow.core.chain.ChainDefinition; 022import dev.tinyflow.core.chain.Edge; 023import dev.tinyflow.core.chain.JsCodeCondition; 024import dev.tinyflow.core.chain.Node; 025import dev.tinyflow.core.util.CollectionUtil; 026import dev.tinyflow.core.util.StringUtil; 027 028import java.util.HashMap; 029import java.util.Map; 030 031public class ChainParser { 032 033 private final Map<String, NodeParser<?>> nodeParserMap; 034 035 public ChainParser(Map<String, NodeParser<?>> nodeParserMap) { 036 this.nodeParserMap = nodeParserMap; 037 } 038 039 public Map<String, NodeParser<?>> getNodeParserMap() { 040 return nodeParserMap; 041 } 042 043 public void addNodeParser(String type, NodeParser<?> nodeParser) { 044 this.nodeParserMap.put(type, nodeParser); 045 } 046 047 public void removeNodeParser(String type) { 048 this.nodeParserMap.remove(type); 049 } 050 051 052 public ChainDefinition parse(String jsonString) { 053 if (StringUtil.noText(jsonString)) { 054 throw new IllegalStateException("jsonString is empty"); 055 } 056 057 JSONObject root = JSON.parseObject(jsonString); 058 JSONArray nodes = root.getJSONArray("nodes"); 059 JSONArray edges = root.getJSONArray("edges"); 060 061 return parse(root, nodes, edges); 062 } 063 064 065 public ChainDefinition parse(JSONObject chainJSONObject, JSONArray nodes, JSONArray edges) { 066 if (CollectionUtil.noItems(nodes) || CollectionUtil.noItems(edges)) { 067 return null; 068 } 069 070 ChainDefinition definition = new ChainDefinition(); 071 for (int i = 0; i < nodes.size(); i++) { 072 JSONObject nodeObject = nodes.getJSONObject(i); 073// if ((parentNode == null && StringUtil.noText(nodeObject.getString("parentId"))) 074// || (parentNode != null && parentNode.getString("id").equals(nodeObject.getString("parentId")))) { 075 Node node = parseNode(chainJSONObject, nodeObject); 076 if (node != null) { 077 definition.addNode(node); 078 } 079// } 080 } 081 082 for (int i = 0; i < edges.size(); i++) { 083// JSONObject edgeObject = edges.getJSONObject(i); 084// JSONObject edgeData = edgeObject.getJSONObject("data"); 085// if ((parentNode == null && (edgeData == null || StringUtil.noText(edgeData.getString("parentNodeId")))) 086// || (parentNode != null && edgeData != null && edgeData.getString("parentNodeId").equals(parentNode.getString("id")) 087// //不添加子流程里的第一条 edge(也就是父节点连接子节点的第一条线) 088// && !parentNode.getString("id").equals(edgeObject.getString("source")))) { 089// ChainEdge edge = parseEdge(edgeObject); 090// if (edge != null) { 091// chain.addEdge(edge); 092// } 093// } 094 095 JSONObject edgeObject = edges.getJSONObject(i); 096 Edge edge = parseEdge(edgeObject); 097 if (edge == null) { 098 continue; 099 } 100// if (parentNode == null || 101// //不添加子流程里的第一条 edge(也就是父节点连接子节点的第一条线) 102// (!parentNode.getString("id").equals(edgeObject.getString("source"))) 103// ) { 104 definition.addEdge(edge); 105// } 106 } 107 108 return definition; 109 } 110 111 private Node parseNode(JSONObject chainJSONObject, JSONObject nodeObject) { 112 String type = nodeObject.getString("type"); 113 if (StringUtil.noText(type)) { 114 return null; 115 } 116 117 NodeParser<?> nodeParser = nodeParserMap.get(type); 118 return nodeParser == null ? null : nodeParser.parse(nodeObject, chainJSONObject, this); 119 } 120 121 122 private Edge parseEdge(JSONObject edgeObject) { 123 if (edgeObject == null) return null; 124 Edge edge = new Edge(); 125 edge.setId(edgeObject.getString("id")); 126 edge.setSource(edgeObject.getString("source")); 127 edge.setTarget(edgeObject.getString("target")); 128 129 JSONObject data = edgeObject.getJSONObject("data"); 130 if (data == null || data.isEmpty()) { 131 return edge; 132 } 133 134 String conditionString = data.getString("condition"); 135 if (StringUtil.hasText(conditionString)) { 136 edge.setCondition(new JsCodeCondition(conditionString.trim())); 137 } 138 return edge; 139 } 140 141 public void addAllParsers(Map<String, NodeParser<?>> defaultNodeParsers) { 142 this.nodeParserMap.putAll(defaultNodeParsers); 143 } 144 145 146 public static Builder builder() { 147 return new Builder(); 148 } 149 150 151 public static final class Builder { 152 153 private final Map<String, NodeParser<?>> customParsers = new HashMap<>(); 154 private boolean includeDefaults = true; 155 156 private Builder() { 157 } 158 159 /** 160 * 是否包含默认节点解析器(默认为 true)。 161 */ 162 public Builder withDefaultParsers(boolean include) { 163 this.includeDefaults = include; 164 return this; 165 } 166 167 /** 168 * 添加自定义节点解析器(会覆盖同名的默认解析器)。 169 */ 170 public Builder addParser(String type, NodeParser<?> parser) { 171 if (type == null || parser == null) { 172 throw new IllegalArgumentException("type and parser must not be null"); 173 } 174 this.customParsers.put(type, parser); 175 return this; 176 } 177 178 /** 179 * 批量添加自定义解析器。 180 */ 181 public Builder addParsers(Map<String, NodeParser<?>> parsers) { 182 if (parsers != null) { 183 this.customParsers.putAll(parsers); 184 } 185 return this; 186 } 187 188 public ChainParser build() { 189 Map<String, NodeParser<?>> finalMap = new HashMap<>(); 190 191 if (includeDefaults) { 192 finalMap.putAll(DefaultNodeParsers.getDefaultNodeParsers()); 193 } 194 195 // 自定义解析器覆盖默认 196 finalMap.putAll(customParsers); 197 198 return new ChainParser(finalMap); 199 } 200 } 201}