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.wicket.util.string.interpolator;
018
019import org.apache.wicket.util.io.IClusterable;
020
021/**
022 * Base class for variable interpolators. An interpolator substitutes values into a
023 * <code>String</code>. So, a variable interpolator substitutes the values of one or more variables
024 * into a <code>String</code>.
025 * <p>
026 * The <code>String</code> to interpolate (substitute into) is passed to the
027 * <code>VariableInterpolator</code>'s constructor. Variables are denoted in this string by the
028 * syntax <code>${variableName}</code>. A subclass provides an implementation for the abstract
029 * method <code>getValue(String variableName)</code>. The <code>toString()</code> method then
030 * performs an interpolation by replacing each variable of the form <code>${variableName}</code>
031 * with the value returned by <code>getValue("variableName")</code>.
032 * <p>
033 * "$" is the escape char. Thus "$${text}" can be used to escape it (ignore interpretation). If
034 * '$3.24' is needed then '$$${amount}' should be used. The first $ sign escapes the second, and the
035 * third is used to interpolate the variable.
036 * 
037 * @author Jonathan Locke
038 * @since 1.2.6
039 */
040@SuppressWarnings("serial")
041public abstract class VariableInterpolator implements IClusterable
042{
043        /** The <code>String</code> to interpolate into */
044        protected final String string;
045
046        private final boolean exceptionOnNullVarValue;
047
048        /**
049         * Constructor.
050         * 
051         * @param string
052         *            a <code>String</code> to interpolate with variable values
053         */
054        public VariableInterpolator(final String string)
055        {
056                this(string, false);
057        }
058
059        /**
060         * Constructor.
061         * 
062         * @param string
063         *            a <code>String</code> to interpolate with variable values
064         * @param exceptionOnNullVarValue
065         *            if <code>true</code> an {@link IllegalStateException} will be thrown if
066         *            {@link #getValue(String)} returns <code>null</code>, otherwise the
067         *            <code>${varname}</code> string will be left in the <code>String</code> so that
068         *            multiple interpolators can be chained
069         */
070        public VariableInterpolator(final String string, final boolean exceptionOnNullVarValue)
071        {
072                this.string = string;
073                this.exceptionOnNullVarValue = exceptionOnNullVarValue;
074        }
075
076        /**
077         * Retrieves a value for a variable name during interpolation.
078         * 
079         * @param variableName
080         *            a variable name
081         * @return the value
082         */
083        protected abstract String getValue(String variableName);
084
085        private int lowerPositive(final int i1, final int i2)
086        {
087                if (i2 < 0)
088                {
089                        return i1;
090                }
091                else if (i1 < 0)
092                {
093                        return i2;
094                }
095                else
096                {
097                        return i1 < i2 ? i1 : i2;
098                }
099        }
100
101        /**
102         * Interpolates using variables.
103         * 
104         * @return the interpolated <code>String</code>
105         */
106        @Override
107        public String toString()
108        {
109                // If there's any reason to go to the expense of property expressions
110                if (!string.contains("${"))
111                {
112                        return string;
113                }
114
115                // Result buffer
116                final StringBuilder buffer = new StringBuilder();
117
118                // For each occurrences of "${"or "$$"
119                int start;
120                int pos = 0;
121
122                while ((start = lowerPositive(string.indexOf("$$", pos), string.indexOf("${", pos))) != -1)
123                {
124                        // Append text before possible variable
125                        buffer.append(string.substring(pos, start));
126
127                        if (string.charAt(start + 1) == '$')
128                        {
129                                buffer.append("$");
130                                pos = start + 2;
131                                continue;
132                        }
133
134
135                        // Position is now where we found the "${"
136                        pos = start;
137
138                        // Get start and end of variable name
139                        final int startVariableName = start + 2;
140                        final int endVariableName = string.indexOf('}', startVariableName);
141
142                        // Found a close brace?
143                        if (endVariableName != -1)
144                        {
145                                // Get variable name inside brackets
146                                final String variableName = string.substring(startVariableName, endVariableName);
147
148                                // Get value of variable
149                                final String value = getValue(variableName);
150
151                                // If there's no value
152                                if (value == null)
153                                {
154                                        if (exceptionOnNullVarValue)
155                                        {
156                                                throw new IllegalArgumentException("Value of variable [[" + variableName +
157                                                        "]] could not be resolved while interpolating [[" + string + "]]");
158                                        }
159                                        else
160                                        {
161                                                // Leave variable uninterpolated, allowing multiple
162                                                // interpolators to
163                                                // do their work on the same string
164                                                buffer.append("${").append(variableName).append("}");
165                                        }
166                                }
167                                else
168                                {
169                                        // Append variable value
170                                        buffer.append(value);
171                                }
172
173                                // Move past variable
174                                pos = endVariableName + 1;
175                        }
176                        else
177                        {
178                                break;
179                        }
180                }
181
182                // Append anything that might be left
183                if (pos < string.length())
184                {
185                        buffer.append(string.substring(pos));
186                }
187
188                // Convert result to String
189                return buffer.toString();
190        }
191}