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 */
017
018package org.apache.commons.jxpath;
019
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026/**
027 * An object that aggregates {@link Functions} objects into a group Functions object. Since {@link JXPathContext} can only register a single Functions object,
028 * FunctionLibrary should always be used to group all Functions objects that need to be registered.
029 */
030public class FunctionLibrary implements Functions {
031
032    private final List<Functions> allFunctions = new ArrayList<>();
033    private Map<String, Object> byNamespace;
034
035    /**
036     * Constructs a new instance.
037     */
038    public FunctionLibrary() {
039        // empty
040    }
041
042    /**
043     * Add functions to the library
044     *
045     * @param functions to add
046     */
047    public void addFunctions(final Functions functions) {
048        allFunctions.add(functions);
049        synchronized (this) {
050            byNamespace = null;
051        }
052    }
053
054    /**
055     * Prepare the cache.
056     *
057     * @return cache map keyed by namespace
058     */
059    private synchronized Map<String, Object> functionCache() {
060        if (byNamespace == null) {
061            byNamespace = new HashMap<>();
062            final int count = allFunctions.size();
063            for (int i = 0; i < count; i++) {
064                final Functions funcs = allFunctions.get(i);
065                final Set<String> namespaces = funcs.getUsedNamespaces();
066                for (final String ns : namespaces) {
067                    final Object candidates = byNamespace.get(ns);
068                    if (candidates == null) {
069                        byNamespace.put(ns, funcs);
070                    } else if (candidates instanceof Functions) {
071                        final List<Object> lst = new ArrayList<>();
072                        lst.add(candidates);
073                        lst.add(funcs);
074                        byNamespace.put(ns, lst);
075                    } else {
076                        ((List) candidates).add(funcs);
077                    }
078                }
079            }
080        }
081        return byNamespace;
082    }
083
084    /**
085     * Gets a Function, if any, for the specified namespace, name and parameter types.
086     *
087     * @param namespace  function namespace
088     * @param name       function name
089     * @param parameters parameters
090     * @return Function found
091     */
092    @Override
093    public Function getFunction(final String namespace, final String name, final Object[] parameters) {
094        final Object candidates = functionCache().get(namespace);
095        if (candidates instanceof Functions) {
096            return ((Functions) candidates).getFunction(namespace, name, parameters);
097        }
098        if (candidates instanceof List) {
099            final List<Functions> list = (List<Functions>) candidates;
100            final int count = list.size();
101            for (int i = 0; i < count; i++) {
102                final Function function = list.get(i).getFunction(namespace, name, parameters);
103                if (function != null) {
104                    return function;
105                }
106            }
107        }
108        return null;
109    }
110
111    /**
112     * Gets a set containing all namespaces used by the aggregated Functions.
113     *
114     * @return a set containing all namespaces used by the aggregated Functions.
115     */
116    @Override
117    public Set<String> getUsedNamespaces() {
118        return functionCache().keySet();
119    }
120
121    /**
122     * Removes functions from the library.
123     *
124     * @param functions to remove.
125     */
126    public void removeFunctions(final Functions functions) {
127        allFunctions.remove(functions);
128        synchronized (this) {
129            byNamespace = null;
130        }
131    }
132}