001/*
002 * Copyright 2002-2018 the original author or authors.
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 *      https://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 org.springframework.beans.factory.xml;
018
019import java.util.Collection;
020
021import org.w3c.dom.Attr;
022import org.w3c.dom.Element;
023import org.w3c.dom.Node;
024
025import org.springframework.beans.factory.config.BeanDefinition;
026import org.springframework.beans.factory.config.BeanDefinitionHolder;
027import org.springframework.beans.factory.config.ConstructorArgumentValues;
028import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
029import org.springframework.beans.factory.config.RuntimeBeanReference;
030import org.springframework.core.Conventions;
031import org.springframework.lang.Nullable;
032import org.springframework.util.StringUtils;
033
034/**
035 * Simple {@code NamespaceHandler} implementation that maps custom
036 * attributes directly through to bean properties. An important point to note is
037 * that this {@code NamespaceHandler} does not have a corresponding schema
038 * since there is no way to know in advance all possible attribute names.
039 *
040 * <p>An example of the usage of this {@code NamespaceHandler} is shown below:
041 *
042 * <pre class="code">
043 * &lt;bean id=&quot;author&quot; class=&quot;..TestBean&quot; c:name=&quot;Enescu&quot; c:work-ref=&quot;compositions&quot;/&gt;
044 * </pre>
045 *
046 * Here the '{@code c:name}' corresponds directly to the '{@code name}
047 * ' argument declared on the constructor of class '{@code TestBean}'. The
048 * '{@code c:work-ref}' attributes corresponds to the '{@code work}'
049 * argument and, rather than being the concrete value, it contains the name of
050 * the bean that will be considered as a parameter.
051 *
052 * <b>Note</b>: This implementation supports only named parameters - there is no
053 * support for indexes or types. Further more, the names are used as hints by
054 * the container which, by default, does type introspection.
055 *
056 * @author Costin Leau
057 * @since 3.1
058 * @see SimplePropertyNamespaceHandler
059 */
060public class SimpleConstructorNamespaceHandler implements NamespaceHandler {
061
062        private static final String REF_SUFFIX = "-ref";
063
064        private static final String DELIMITER_PREFIX = "_";
065
066
067        @Override
068        public void init() {
069        }
070
071        @Override
072        @Nullable
073        public BeanDefinition parse(Element element, ParserContext parserContext) {
074                parserContext.getReaderContext().error(
075                                "Class [" + getClass().getName() + "] does not support custom elements.", element);
076                return null;
077        }
078
079        @Override
080        public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
081                if (node instanceof Attr) {
082                        Attr attr = (Attr) node;
083                        String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr));
084                        String argValue = StringUtils.trimWhitespace(attr.getValue());
085
086                        ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues();
087                        boolean ref = false;
088
089                        // handle -ref arguments
090                        if (argName.endsWith(REF_SUFFIX)) {
091                                ref = true;
092                                argName = argName.substring(0, argName.length() - REF_SUFFIX.length());
093                        }
094
095                        ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue);
096                        valueHolder.setSource(parserContext.getReaderContext().extractSource(attr));
097
098                        // handle "escaped"/"_" arguments
099                        if (argName.startsWith(DELIMITER_PREFIX)) {
100                                String arg = argName.substring(1).trim();
101
102                                // fast default check
103                                if (!StringUtils.hasText(arg)) {
104                                        cvs.addGenericArgumentValue(valueHolder);
105                                }
106                                // assume an index otherwise
107                                else {
108                                        int index = -1;
109                                        try {
110                                                index = Integer.parseInt(arg);
111                                        }
112                                        catch (NumberFormatException ex) {
113                                                parserContext.getReaderContext().error(
114                                                                "Constructor argument '" + argName + "' specifies an invalid integer", attr);
115                                        }
116                                        if (index < 0) {
117                                                parserContext.getReaderContext().error(
118                                                                "Constructor argument '" + argName + "' specifies a negative index", attr);
119                                        }
120
121                                        if (cvs.hasIndexedArgumentValue(index)) {
122                                                parserContext.getReaderContext().error(
123                                                                "Constructor argument '" + argName + "' with index "+ index+" already defined using <constructor-arg>." +
124                                                                " Only one approach may be used per argument.", attr);
125                                        }
126
127                                        cvs.addIndexedArgumentValue(index, valueHolder);
128                                }
129                        }
130                        // no escaping -> ctr name
131                        else {
132                                String name = Conventions.attributeNameToPropertyName(argName);
133                                if (containsArgWithName(name, cvs)) {
134                                        parserContext.getReaderContext().error(
135                                                        "Constructor argument '" + argName + "' already defined using <constructor-arg>." +
136                                                        " Only one approach may be used per argument.", attr);
137                                }
138                                valueHolder.setName(Conventions.attributeNameToPropertyName(argName));
139                                cvs.addGenericArgumentValue(valueHolder);
140                        }
141                }
142                return definition;
143        }
144
145        private boolean containsArgWithName(String name, ConstructorArgumentValues cvs) {
146                return (checkName(name, cvs.getGenericArgumentValues()) ||
147                                checkName(name, cvs.getIndexedArgumentValues().values()));
148        }
149
150        private boolean checkName(String name, Collection<ValueHolder> values) {
151                for (ValueHolder holder : values) {
152                        if (name.equals(holder.getName())) {
153                                return true;
154                        }
155                }
156                return false;
157        }
158
159}