001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.model;
018
019import java.util.ArrayList;
020import java.util.Iterator;
021import java.util.LinkedHashSet;
022import java.util.List;
023import java.util.Set;
024
025import org.apache.camel.NamedNode;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Helper class for ProcessorDefinition and the other model classes.
031 */
032public final class ProcessorDefinitionHelper {
033
034    public static final String PREFIX = "{" + Constants.PLACEHOLDER_QNAME + "}";
035
036    private static final Logger LOG = LoggerFactory.getLogger(ProcessorDefinitionHelper.class);
037
038    private ProcessorDefinitionHelper() {
039    }
040
041    /**
042     * Looks for the given type in the list of outputs and recurring all the
043     * children as well.
044     *
045     * @param outputs list of outputs, can be null or empty.
046     * @param type the type to look for
047     * @return the found definitions, or <tt>null</tt> if not found
048     */
049    public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) {
050        return filterTypeInOutputs(outputs, type, -1);
051    }
052
053    /**
054     * Looks for the given type in the list of outputs and recurring all the
055     * children as well.
056     *
057     * @param outputs list of outputs, can be null or empty.
058     * @param type the type to look for
059     * @param maxDeep maximum levels deep to traverse
060     * @return the found definitions, or <tt>null</tt> if not found
061     */
062    public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type, int maxDeep) {
063        List<T> found = new ArrayList<>();
064        doFindType(outputs, type, found, maxDeep);
065        return found.iterator();
066    }
067
068    /**
069     * Looks for the given type in the list of outputs and recurring all the
070     * children as well. Will stop at first found and return it.
071     *
072     * @param outputs list of outputs, can be null or empty.
073     * @param type the type to look for
074     * @return the first found type, or <tt>null</tt> if not found
075     */
076    public static <T> T findFirstTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) {
077        List<T> found = new ArrayList<>();
078        doFindType(outputs, type, found, -1);
079        if (found.isEmpty()) {
080            return null;
081        }
082        return found.iterator().next();
083    }
084
085    /**
086     * Is the given child the first in the outputs from the parent?
087     *
088     * @param parentType the type the parent must be
089     * @param node the node
090     * @return <tt>true</tt> if first child, <tt>false</tt> otherwise
091     */
092    public static boolean isFirstChildOfType(Class<?> parentType, ProcessorDefinition<?> node) {
093        if (node == null || node.getParent() == null) {
094            return false;
095        }
096
097        if (node.getParent().getOutputs().isEmpty()) {
098            return false;
099        }
100
101        if (!(node.getParent().getClass().equals(parentType))) {
102            return false;
103        }
104
105        return node.getParent().getOutputs().get(0).equals(node);
106    }
107
108    /**
109     * Is the given node parent(s) of the given type
110     *
111     * @param parentType the parent type
112     * @param node the current node
113     * @param recursive whether or not to check grand parent(s) as well
114     * @return <tt>true</tt> if parent(s) is of given type, <tt>false</tt>
115     *         otherwise
116     */
117    public static boolean isParentOfType(Class<? extends ProcessorDefinition> parentType, ProcessorDefinition<?> node, boolean recursive) {
118        return findFirstParentOfType(parentType, node, recursive) != null;
119    }
120
121    /**
122     * Is the given node parent(s) of the given type
123     *
124     * @param parentType the parent type
125     * @param node the current node
126     * @param recursive whether or not to check grand parent(s) as well
127     * @return <tt>true</tt> if parent(s) is of given type, <tt>false</tt>
128     *         otherwise
129     */
130    public static <T extends ProcessorDefinition> T findFirstParentOfType(Class<T> parentType, ProcessorDefinition<?> node, boolean recursive) {
131        if (node == null || node.getParent() == null) {
132            return null;
133        }
134
135        if (parentType.isAssignableFrom(node.getParent().getClass())) {
136            return parentType.cast(node.getParent());
137        } else if (recursive) {
138            // recursive up the tree of parents
139            return findFirstParentOfType(parentType, node.getParent(), true);
140        } else {
141            // no match
142            return null;
143        }
144    }
145
146    /**
147     * Gets the route definition the given node belongs to.
148     *
149     * @param node the node
150     * @return the route, or <tt>null</tt> if not possible to find
151     */
152    public static RouteDefinition getRoute(NamedNode node) {
153        if (node == null) {
154            return null;
155        }
156
157        ProcessorDefinition<?> def = (ProcessorDefinition)node;
158        // drill to the top
159        while (def != null && def.getParent() != null) {
160            def = def.getParent();
161        }
162
163        if (def instanceof RouteDefinition) {
164            return (RouteDefinition)def;
165        } else {
166            // not found
167            return null;
168        }
169    }
170
171    /**
172     * Gets the route id the given node belongs to.
173     *
174     * @param node the node
175     * @return the route id, or <tt>null</tt> if not possible to find
176     */
177    public static String getRouteId(NamedNode node) {
178        RouteDefinition route = getRoute(node);
179        return route != null ? route.getId() : null;
180    }
181
182    /**
183     * Traverses the node, including its children (recursive), and gathers all
184     * the node ids.
185     *
186     * @param node the target node
187     * @param set set to store ids, if <tt>null</tt> a new set will be created
188     * @param onlyCustomId whether to only store custom assigned ids (ie.
189     *            {@link org.apache.camel.model.OptionalIdentifiedDefinition#hasCustomIdAssigned()}
190     * @param includeAbstract whether to include abstract nodes (ie.
191     *            {@link org.apache.camel.model.ProcessorDefinition#isAbstract()}
192     * @return the set with the found ids.
193     */
194    public static Set<String> gatherAllNodeIds(ProcessorDefinition<?> node, Set<String> set, boolean onlyCustomId, boolean includeAbstract) {
195        if (node == null) {
196            return set;
197        }
198
199        // skip abstract
200        if (node.isAbstract() && !includeAbstract) {
201            return set;
202        }
203
204        if (set == null) {
205            set = new LinkedHashSet<>();
206        }
207
208        // add ourselves
209        if (node.getId() != null) {
210            if (!onlyCustomId || node.hasCustomIdAssigned() && onlyCustomId) {
211                set.add(node.getId());
212            }
213        }
214
215        // traverse outputs and recursive children as well
216        List<ProcessorDefinition<?>> children = node.getOutputs();
217        if (children != null && !children.isEmpty()) {
218            for (ProcessorDefinition<?> child : children) {
219                // traverse children also
220                gatherAllNodeIds(child, set, onlyCustomId, includeAbstract);
221            }
222        }
223
224        return set;
225    }
226
227    private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int maxDeep) {
228
229        // do we have any top level abstracts, then we should max deep one more
230        // level down
231        // as that is really what we want to traverse as well
232        if (maxDeep > 0) {
233            for (ProcessorDefinition<?> out : outputs) {
234                if (out.isAbstract() && out.isTopLevelOnly()) {
235                    maxDeep = maxDeep + 1;
236                    break;
237                }
238            }
239        }
240
241        // start from level 1
242        doFindType(outputs, type, found, 1, maxDeep);
243    }
244
245    @SuppressWarnings({"unchecked", "rawtypes"})
246    private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int current, int maxDeep) {
247        if (outputs == null || outputs.isEmpty()) {
248            return;
249        }
250
251        // break out
252        if (maxDeep > 0 && current > maxDeep) {
253            return;
254        }
255
256        for (ProcessorDefinition out : outputs) {
257
258            // send is much common
259            if (out instanceof SendDefinition) {
260                SendDefinition send = (SendDefinition)out;
261                List<ProcessorDefinition<?>> children = send.getOutputs();
262                doFindType(children, type, found, ++current, maxDeep);
263            }
264
265            // special for choice
266            if (out instanceof ChoiceDefinition) {
267                ChoiceDefinition choice = (ChoiceDefinition)out;
268
269                // ensure to add ourself if we match also
270                if (type.isInstance(choice)) {
271                    found.add((T)choice);
272                }
273
274                // only look at when/otherwise if current < maxDeep (or max deep
275                // is disabled)
276                if (maxDeep < 0 || current < maxDeep) {
277                    for (WhenDefinition when : choice.getWhenClauses()) {
278                        if (type.isInstance(when)) {
279                            found.add((T)when);
280                        }
281                        List<ProcessorDefinition<?>> children = when.getOutputs();
282                        doFindType(children, type, found, ++current, maxDeep);
283                    }
284
285                    // otherwise is optional
286                    if (choice.getOtherwise() != null) {
287                        List<ProcessorDefinition<?>> children = choice.getOtherwise().getOutputs();
288                        doFindType(children, type, found, ++current, maxDeep);
289                    }
290                }
291
292                // do not check children as we already did that
293                continue;
294            }
295
296            // special for try ... catch ... finally
297            if (out instanceof TryDefinition) {
298                TryDefinition doTry = (TryDefinition)out;
299
300                // ensure to add ourself if we match also
301                if (type.isInstance(doTry)) {
302                    found.add((T)doTry);
303                }
304
305                // only look at children if current < maxDeep (or max deep is
306                // disabled)
307                if (maxDeep < 0 || current < maxDeep) {
308                    List<ProcessorDefinition<?>> doTryOut = doTry.getOutputsWithoutCatches();
309                    doFindType(doTryOut, type, found, ++current, maxDeep);
310
311                    List<CatchDefinition> doTryCatch = doTry.getCatchClauses();
312                    for (CatchDefinition doCatch : doTryCatch) {
313                        doFindType(doCatch.getOutputs(), type, found, ++current, maxDeep);
314                    }
315
316                    if (doTry.getFinallyClause() != null) {
317                        doFindType(doTry.getFinallyClause().getOutputs(), type, found, ++current, maxDeep);
318                    }
319                }
320
321                // do not check children as we already did that
322                continue;
323            }
324
325            // special for some types which has special outputs
326            if (out instanceof OutputDefinition) {
327                OutputDefinition outDef = (OutputDefinition)out;
328
329                // ensure to add ourself if we match also
330                if (type.isInstance(outDef)) {
331                    found.add((T)outDef);
332                }
333
334                List<ProcessorDefinition<?>> outDefOut = outDef.getOutputs();
335                doFindType(outDefOut, type, found, ++current, maxDeep);
336
337                // do not check children as we already did that
338                continue;
339            }
340
341            if (type.isInstance(out)) {
342                found.add((T)out);
343            }
344
345            // try children as well
346            List<ProcessorDefinition<?>> children = out.getOutputs();
347            doFindType(children, type, found, ++current, maxDeep);
348        }
349    }
350
351}