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.node; 017 018import com.agentsflex.core.chain.Chain; 019import com.agentsflex.core.chain.Parameter; 020import com.agentsflex.core.chain.RefType; 021import com.agentsflex.core.chain.node.BaseNode; 022 023import java.util.*; 024 025public class LoopNode extends BaseNode { 026 027 private Parameter loopVar; 028 private Chain loopChain; 029 030 public Parameter getLoopVar() { 031 return loopVar; 032 } 033 034 public void setLoopVar(Parameter loopVar) { 035 this.loopVar = loopVar; 036 } 037 038 public Chain getLoopChain() { 039 return loopChain; 040 } 041 042 public void setLoopChain(Chain loopChain) { 043 this.loopChain = loopChain; 044 } 045 046 @Override 047 protected Map<String, Object> execute(Chain chain) { 048 loopChain.setParent(chain); 049 050 Map<String, Object> executeResult = new HashMap<>(); 051 Map<String, Object> chainMemory = chain.getMemory().getAll(); 052 053 Map<String, Object> loopVars = chain.getParameterValues(this, Collections.singletonList(loopVar)); 054 Object loopValue = loopVars.get(loopVar.getName()); 055 if (loopValue instanceof Iterable) { 056 Iterable<?> iterable = (Iterable<?>) loopValue; 057 int index = 0; 058 for (Object o : iterable) { 059 executeLoopChain(index++, o, chainMemory, executeResult); 060 } 061 } else if (loopValue instanceof Number || (loopValue instanceof String && isNumeric(loopValue.toString()))) { 062 int count = loopValue instanceof Number ? ((Number) loopValue).intValue() : Integer.parseInt(loopValue.toString().trim()); 063 for (int i = 0; i < count; i++) { 064 executeLoopChain(i, i, chainMemory, executeResult); 065 } 066 } 067 068 return executeResult; 069 } 070 071 private void executeLoopChain(int index, Object loopItem, Map<String, Object> parentMap, Map<String, Object> executeResult) { 072 Map<String, Object> loopParams = new HashMap<>(); 073 loopParams.put(this.id + ".index", index); 074 loopParams.put(this.id + ".loopItem", loopItem); 075 loopParams.putAll(parentMap); 076 try { 077 loopChain.execute(loopParams); 078 } finally { 079 fillResult(executeResult, loopChain); 080 loopChain.reset(); 081 } 082 } 083 084 /** 085 * 判断字符串是否是数字 086 * 087 * @param string 需要判断的字符串 088 * @return boolean 是数字返回 true,否则返回 false 089 */ 090 private boolean isNumeric(String string) { 091 if (string == null || string.isEmpty()) { 092 return false; 093 } 094 char[] chars = string.trim().toCharArray(); 095 for (char c : chars) { 096 if (!Character.isDigit(c)) { 097 return false; 098 } 099 } 100 return true; 101 } 102 103 /** 104 * 把子流程执行的结果填充到主流程的输出参数中 105 * 106 * @param executeResult 主流程的输出参数 107 * @param loopChain 子流程的 108 */ 109 private void fillResult(Map<String, Object> executeResult, Chain loopChain) { 110 List<Parameter> outputDefs = getOutputDefs(); 111 if (outputDefs != null) { 112 for (Parameter outputDef : outputDefs) { 113 Object value = null; 114 115 //引用 116 if (outputDef.getRefType() == RefType.REF) { 117 value = loopChain.get(outputDef.getRef()); 118 } 119 //固定值 120 else if (outputDef.getRefType() == RefType.FIXED) { 121 value = outputDef.getValue(); 122 } 123 124 @SuppressWarnings("unchecked") List<Object> existList = (List<Object>) executeResult.get(outputDef.getName()); 125 if (existList == null) { 126 existList = new ArrayList<>(); 127 } 128 existList.add(value); 129 executeResult.put(outputDef.getName(), existList); 130 } 131 } 132 } 133}