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}