001// --------------------------------------------------------------------------------
002// Copyright 2002-2025 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.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//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
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// --------------------------------------------------------------------------------
016
017package com.echothree.util.common.transfer;
018
019import com.echothree.util.common.string.StringUtils;
020import com.echothree.util.common.transfer.BaseTransfer;
021import com.echothree.util.common.transfer.BaseWrapper;
022import java.lang.reflect.InvocationTargetException;
023import java.util.HashSet;
024import java.util.Set;
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028public class BaseTransferUtils {
029
030    private static Log log = LogFactory.getLog(BaseTransferUtils.class);
031    
032    private BaseTransferUtils() {
033        super();
034    }
035
036    private static class BaseTransferUtilsHolder {
037        static BaseTransferUtils instance = new BaseTransferUtils();
038    }
039
040    public static BaseTransferUtils getInstance() {
041        return BaseTransferUtilsHolder.instance;
042    }
043
044    private void getEntityRefsFromBaseWrapper(EntityRefExclusions entityRefExclusions, Set<String> entityRefs, Set<Object> visitedObjects,
045            BaseWrapper<?> baseWrapper, int indentCount) {
046        var collection = baseWrapper.getCollection();
047
048        if(!collection.isEmpty()) {
049            collection.stream().filter((nextDependsOn) -> !visitedObjects.contains(nextDependsOn)).forEach((nextDependsOn) -> {
050                getEntityRefs(entityRefExclusions, entityRefs, visitedObjects, nextDependsOn, indentCount + 1);
051            });
052        }
053    }
054
055    private Set<String> getEntityRefs(EntityRefExclusions entityRefExclusions, Set<String> entityRefs, Set<Object> visitedObjects, Object dependsOn,
056            int indentCount) {
057        if(BaseTransferUtilsDebugFlags.LogVisits) {
058            log.info(StringUtils.getInstance().getIndent(4, indentCount) + "Visiting " + dependsOn);
059        }
060        visitedObjects.add(dependsOn);
061        
062        if(dependsOn instanceof BaseTransfer) {
063            var entityInstance = ((BaseTransfer)dependsOn).getEntityInstance();
064            var includeMethods = true;
065            
066            if(entityInstance != null) {
067                var nextDependsOnEntityRef = entityInstance.getEntityRef();
068
069                if(nextDependsOnEntityRef != null) {
070                    if(entityRefExclusions != null && nextDependsOnEntityRef != null && entityRefExclusions.contains(nextDependsOnEntityRef)) {
071                        if(BaseTransferUtilsDebugFlags.LogGetEntityRefs) {
072                            log.info(StringUtils.getInstance().getIndent(4, indentCount) + "Excluding " + nextDependsOnEntityRef);
073                        }
074
075                        includeMethods = false;
076                    } else {
077                        if(BaseTransferUtilsDebugFlags.LogGetEntityRefs) {
078                            log.info(StringUtils.getInstance().getIndent(4, indentCount) + "Including " + nextDependsOnEntityRef);
079                        }
080
081                        entityRefs.add(nextDependsOnEntityRef);
082                     }
083                }
084            }
085            
086            if(includeMethods) {
087                var methods = dependsOn.getClass().getMethods();
088                
089                for(var method : methods) {
090                    var name = method.getName();
091
092                    if(name.startsWith("get")) {
093                        try {
094                            var returnType = method.getReturnType();
095
096                            if(BaseTransfer.class.isAssignableFrom(returnType)) {
097                                // If it's a BaseTransfer...
098                                var nextDependsOn = (BaseTransfer)method.invoke(dependsOn);
099                                
100                                if(nextDependsOn != null) {
101                                    if(!visitedObjects.contains(nextDependsOn)) {
102                                        getEntityRefs(entityRefExclusions, entityRefs, visitedObjects, nextDependsOn, indentCount + 1);
103                                    } else {
104                                        if(BaseTransferUtilsDebugFlags.LogVisits) {
105                                            log.info(StringUtils.getInstance().getIndent(4, indentCount) + "Already visited " + nextDependsOn);
106                                        }
107                                    }
108                                }
109                            } else if(BaseWrapper.class.isAssignableFrom(returnType)) {
110                                // If it's a BaseWrapper
111                                var baseWrapper = (BaseWrapper<?>)method.invoke(dependsOn);
112
113                                if(baseWrapper != null) {
114                                    if(BaseTransferUtilsDebugFlags.LogGetEntityRefs) {
115                                        log.info(StringUtils.getInstance().getIndent(4, indentCount) + "BaseWrapper found: " + name.substring(3));
116                                    }
117
118                                    getEntityRefsFromBaseWrapper(entityRefExclusions, entityRefs, visitedObjects, baseWrapper, indentCount);
119                                }
120                            }
121                        } catch(IllegalAccessException iea) {
122                            throw new RuntimeException(iea);
123                        } catch(IllegalArgumentException iea) {
124                            throw new RuntimeException(iea);
125                        } catch(InvocationTargetException ite) {
126                            throw new RuntimeException(ite);
127                        }
128                    }
129                }
130            }
131        } else if(dependsOn instanceof BaseWrapper) {
132            var baseWrapper = (BaseWrapper<?>)dependsOn;
133            
134            if(baseWrapper != null) {
135                if(BaseTransferUtilsDebugFlags.LogGetEntityRefs) {
136                    log.info(StringUtils.getInstance().getIndent(4, indentCount) + "BaseWrapper passed in");
137                }
138                
139                getEntityRefsFromBaseWrapper(entityRefExclusions, entityRefs, visitedObjects, baseWrapper, indentCount);
140            }
141        }
142        
143        return entityRefs;
144    }
145
146    public Set<String> getEntityRefs(EntityRefExclusions entityRefExclusions, Object object) {
147        Set<String> entityRefs = new HashSet<>();
148        Set<Object> visitedObjects = new HashSet<>();
149
150        if(BaseTransferUtilsDebugFlags.LogGetEntityRefs) {
151            log.info("Starting getEntityRefs for " + object);
152        }
153        
154        if(object instanceof BaseTransfer || object instanceof BaseWrapper) {
155            getEntityRefs(entityRefExclusions, entityRefs, visitedObjects, object, 1);
156        } else {
157            throw new IllegalArgumentException("Object for getEntityRefs(...) must be a BaseTransfer or BaseWrapper object.");
158        }
159        
160        if(BaseTransferUtilsDebugFlags.LogGetEntityRefs) {
161            log.info("Ending getEntityRefs");
162        }
163        
164        return entityRefs;
165    }
166
167}