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