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.util.component;
018
019import java.util.ArrayList;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026/**
027 * Adds support for parameter name substitutions.
028 */
029public class ArgumentSubstitutionParser<T> extends ApiMethodParser<T> {
030
031    private final Map<Pattern, Map<Pattern, List<NameReplacement>>> methodMap;
032
033    /**
034     * Create a parser using regular expressions to adapt parameter names.
035     * @param proxyType Proxy class.
036     * @param substitutions an array of <b>ordered</b> Argument adapters.
037     */
038    public ArgumentSubstitutionParser(Class<T> proxyType, Substitution[] substitutions) {
039        super(proxyType);
040        Map<String, Map<String, List<NameReplacement>>> regexMap = new LinkedHashMap<>();
041
042        for (Substitution substitution : substitutions) {
043            substitution.validate();
044
045            final NameReplacement nameReplacement = new NameReplacement();
046            nameReplacement.replacement = substitution.replacement;
047            if (substitution.argType != null) {
048                nameReplacement.typePattern = Pattern.compile(substitution.argType);
049            }
050            nameReplacement.replaceWithType = substitution.replaceWithType;
051
052            Map<String, List<NameReplacement>> replacementMap = regexMap.get(substitution.method);
053            if (replacementMap == null) {
054                replacementMap = new LinkedHashMap<>();
055                regexMap.put(substitution.method, replacementMap);
056            }
057            List<NameReplacement> replacements = replacementMap.get(substitution.argName);
058            if (replacements == null) {
059                replacements = new ArrayList<>();
060                replacementMap.put(substitution.argName, replacements);
061            }
062            replacements.add(nameReplacement);
063        }
064
065        // now compile the patterns, all this because Pattern doesn't override equals()!!!
066        this.methodMap = new LinkedHashMap<>();
067        for (Map.Entry<String, Map<String, List<NameReplacement>>> method : regexMap.entrySet()) {
068            Map<Pattern, List<NameReplacement>> argMap = new LinkedHashMap<>();
069            for (Map.Entry<String, List<NameReplacement>> arg : method.getValue().entrySet()) {
070                argMap.put(Pattern.compile(arg.getKey()), arg.getValue());
071            }
072            methodMap.put(Pattern.compile(method.getKey()), argMap);
073        }
074    }
075
076    @Override
077    public List<ApiMethodModel> processResults(List<ApiMethodModel> parseResult) {
078        final List<ApiMethodModel> result = new ArrayList<>();
079
080        for (ApiMethodModel model : parseResult) {
081            // look for method name matches
082            for (Map.Entry<Pattern, Map<Pattern, List<NameReplacement>>> methodEntry : methodMap.entrySet()) {
083                // match the whole method name
084                if (methodEntry.getKey().matcher(model.getName()).matches()) {
085
086                    // look for arg name matches
087                    final List<ApiMethodArg> updatedArguments = new ArrayList<>();
088                    final Map<Pattern, List<NameReplacement>> argMap = methodEntry.getValue();
089                    for (ApiMethodArg argument : model.getArguments()) {
090
091                        final Class<?> argType = argument.getType();
092                        final String typeArgs = argument.getTypeArgs();
093                        final String argTypeName = argType.getCanonicalName();
094
095                        for (Map.Entry<Pattern, List<NameReplacement>> argEntry : argMap.entrySet()) {
096                            final Matcher matcher = argEntry.getKey().matcher(argument.getName());
097
098                            // match argument name substring
099                            if (matcher.find()) {
100                                final List<NameReplacement> adapters = argEntry.getValue();
101                                for (NameReplacement adapter : adapters) {
102                                    if (adapter.typePattern == null) {
103
104                                        // no type pattern
105                                        final String newName = getJavaArgName(matcher.replaceAll(adapter.replacement));
106                                        argument = new ApiMethodArg(newName, argType, typeArgs);
107
108                                    } else {
109
110                                        final Matcher typeMatcher = adapter.typePattern.matcher(argTypeName);
111                                        if (typeMatcher.find()) {
112                                            if (!adapter.replaceWithType) {
113                                                // replace argument name
114                                                final String newName = getJavaArgName(matcher.replaceAll(adapter.replacement));
115                                                argument = new ApiMethodArg(newName, argType, typeArgs);
116                                            } else {
117                                                // replace name with argument type name
118                                                final String newName = getJavaArgName(typeMatcher.replaceAll(adapter.replacement));
119                                                argument = new ApiMethodArg(newName, argType, typeArgs);
120                                            }
121                                        }
122                                    }
123                                }
124                            }
125                        }
126
127                        updatedArguments.add(argument);
128                    }
129
130                    model = new ApiMethodModel(model.getUniqueName(), model.getName(), model.getResultType(),
131                            updatedArguments, model.getMethod());
132                }
133            }
134
135            result.add(model);
136        }
137
138        return result;
139    }
140
141    private String getJavaArgName(String name) {
142        // make sure the first character is lowercase
143        // useful for replacement using type names
144        char firstChar = name.charAt(0);
145        if (Character.isLowerCase(firstChar)) {
146            return name;
147        } else {
148            return Character.toLowerCase(firstChar) + name.substring(1);
149        }
150    }
151
152    public static class Substitution {
153
154        private String method;
155        private String argName;
156        private String argType;
157        private String replacement;
158        private boolean replaceWithType;
159
160        /**
161         * Creates a substitution for all argument types.
162         * @param method regex to match method name
163         * @param argName regex to match argument name
164         * @param replacement replacement text for argument name
165         */
166        public Substitution(String method, String argName, String replacement) {
167            this.method = method;
168            this.argName = argName;
169            this.replacement = replacement;
170        }
171
172        /**
173         * Creates a substitution for a specific argument type.
174         * @param method regex to match method name
175         * @param argName regex to match argument name
176         * @param argType argument type as String
177         * @param replacement replacement text for argument name
178         */
179        public Substitution(String method, String argName, String argType, String replacement) {
180            this(method, argName, replacement);
181            this.argType = argType;
182        }
183
184        /**
185         * Create a substitution for a specific argument type and flag to indicate whether the replacement uses
186         * @param method
187         * @param argName
188         * @param argType
189         * @param replacement
190         * @param replaceWithType
191         */
192        public Substitution(String method, String argName, String argType, String replacement, boolean replaceWithType) {
193            this(method, argName, argType, replacement);
194            this.replaceWithType = replaceWithType;
195        }
196
197        public void validate() {
198            if (method == null || argName == null || replacement == null) {
199                throw new IllegalArgumentException("Properties method, argName and replacement MUST be provided");
200            }
201        }
202    }
203
204    private static class NameReplacement {
205        private String replacement;
206        private Pattern typePattern;
207        private boolean replaceWithType;
208    }
209}