1 /* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.clearsilver.jsilver.values; 18 19 import com.google.clearsilver.jsilver.autoescape.EscapeMode; 20 import com.google.clearsilver.jsilver.data.DataContext; 21 22 import java.util.HashMap; 23 import java.util.Map; 24 25 /** 26 * Dynamic typing system used by JSilver interpreter. A value (e.g. "2") can act as a string, 27 * integer and boolean. Values can be literal or references to variables held elsewhere (e.g. in 28 * external data structures such as HDF). 29 */ 30 public abstract class Value { 31 32 private static final Map<EscapeMode, Value> EMPTY_PART_ESCAPED; 33 private static final Map<EscapeMode, Value> EMPTY_UNESCAPED; 34 private static final Map<EscapeMode, Value> ZERO_PART_ESCAPED; 35 private static final Map<EscapeMode, Value> ZERO_UNESCAPED; 36 private static final Map<EscapeMode, Value> ONE_PART_ESCAPED; 37 private static final Map<EscapeMode, Value> ONE_UNESCAPED; 38 39 static { 40 // Currently a Value's EscapeMode is either ESCAPE_NONE (no escaping) or 41 // ESCAPE_IS_CONSTANT (is a constant or has some escaping applied). 42 // This may change in the future if we implement stricter auto escaping. 43 // See EscapeMode.combineModes. 44 EMPTY_PART_ESCAPED = new HashMap<EscapeMode, Value>(2); EMPTY_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new StringValue("", EscapeMode.ESCAPE_NONE, true))45 EMPTY_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, 46 new StringValue("", EscapeMode.ESCAPE_NONE, true)); EMPTY_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new StringValue("", EscapeMode.ESCAPE_IS_CONSTANT, true))47 EMPTY_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new StringValue("", 48 EscapeMode.ESCAPE_IS_CONSTANT, true)); 49 50 EMPTY_UNESCAPED = new HashMap<EscapeMode, Value>(2); EMPTY_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new StringValue("", EscapeMode.ESCAPE_NONE, false))51 EMPTY_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new StringValue("", EscapeMode.ESCAPE_NONE, false)); EMPTY_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new StringValue("", EscapeMode.ESCAPE_IS_CONSTANT, false))52 EMPTY_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new StringValue("", 53 EscapeMode.ESCAPE_IS_CONSTANT, false)); 54 55 ZERO_PART_ESCAPED = new HashMap<EscapeMode, Value>(2); ZERO_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, true))56 ZERO_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, true)); ZERO_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(0, EscapeMode.ESCAPE_IS_CONSTANT, true))57 ZERO_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(0, 58 EscapeMode.ESCAPE_IS_CONSTANT, true)); 59 60 ZERO_UNESCAPED = new HashMap<EscapeMode, Value>(2); ZERO_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, false))61 ZERO_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, false)); ZERO_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(0, EscapeMode.ESCAPE_IS_CONSTANT, false))62 ZERO_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(0, 63 EscapeMode.ESCAPE_IS_CONSTANT, false)); 64 65 ONE_PART_ESCAPED = new HashMap<EscapeMode, Value>(2); ONE_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, true))66 ONE_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, true)); ONE_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(1, EscapeMode.ESCAPE_IS_CONSTANT, true))67 ONE_PART_ESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(1, 68 EscapeMode.ESCAPE_IS_CONSTANT, true)); 69 70 ONE_UNESCAPED = new HashMap<EscapeMode, Value>(2); ONE_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, false))71 ONE_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, false)); ONE_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(1, EscapeMode.ESCAPE_IS_CONSTANT, false))72 ONE_UNESCAPED.put(EscapeMode.ESCAPE_IS_CONSTANT, new NumberValue(1, 73 EscapeMode.ESCAPE_IS_CONSTANT, false)); 74 } 75 76 /** 77 * True if either the {@code Value} was escaped, or it was created from a combination of escaped 78 * and unescaped values. 79 */ 80 private final boolean partiallyEscaped; 81 private final EscapeMode escapeMode; 82 Value(EscapeMode escapeMode, boolean partiallyEscaped)83 public Value(EscapeMode escapeMode, boolean partiallyEscaped) { 84 this.escapeMode = escapeMode; 85 this.partiallyEscaped = partiallyEscaped; 86 } 87 88 /** 89 * Fetch value as boolean. All non empty strings and numbers != 0 are treated as true. 90 */ asBoolean()91 public abstract boolean asBoolean(); 92 93 /** 94 * Fetch value as string. 95 */ asString()96 public abstract String asString(); 97 98 /** 99 * Fetch value as number. If number is not parseable, 0 is returned (this is consistent with 100 * ClearSilver). 101 */ asNumber()102 public abstract int asNumber(); 103 104 /** 105 * Whether this value exists. Literals always return true, but variable references will return 106 * false if the value behind it is null. 107 */ exists()108 public abstract boolean exists(); 109 isEmpty()110 public abstract boolean isEmpty(); 111 112 /** 113 * Create a literal value using an int. 114 */ literalValue(int value, EscapeMode mode, boolean partiallyEscaped)115 public static Value literalValue(int value, EscapeMode mode, boolean partiallyEscaped) { 116 return getIntValue(mode, partiallyEscaped, value); 117 } 118 119 /** 120 * Create a literal value using a String. 121 */ literalValue(String value, EscapeMode mode, boolean partiallyEscaped)122 public static Value literalValue(String value, EscapeMode mode, boolean partiallyEscaped) { 123 if (value.isEmpty()) { 124 Value v = (partiallyEscaped ? EMPTY_PART_ESCAPED : EMPTY_UNESCAPED).get(mode); 125 if (v != null) { 126 return v; 127 } 128 } 129 130 return new StringValue(value, mode, partiallyEscaped); 131 } 132 133 /** 134 * Create a literal value using a boolean. 135 */ literalValue(boolean value, EscapeMode mode, boolean partiallyEscaped)136 public static Value literalValue(boolean value, EscapeMode mode, boolean partiallyEscaped) { 137 return getIntValue(mode, partiallyEscaped, value ? 1 : 0); 138 } 139 getIntValue(EscapeMode mode, boolean partiallyEscaped, int num)140 private static Value getIntValue(EscapeMode mode, boolean partiallyEscaped, int num) { 141 Value v = null; 142 if (num == 0) { 143 v = (partiallyEscaped ? ZERO_PART_ESCAPED : ZERO_UNESCAPED).get(mode); 144 } else if (num == 1) { 145 v = (partiallyEscaped ? ONE_PART_ESCAPED : ONE_UNESCAPED).get(mode); 146 } 147 148 if (v != null) { 149 return v; 150 } 151 152 return new NumberValue(num, mode, partiallyEscaped); 153 } 154 155 /** 156 * Create a literal value using an int with a {@code escapeMode} of {@code 157 * EscapeMode.ESCAPE_IS_CONSTANT} and {@code partiallyEscaped} based on the {@code 158 * partiallyEscaped} values of the inputs. 159 * 160 * @param value integer value of the literal 161 * @param inputs Values that were used to compute the integer value. 162 */ literalConstant(int value, Value... inputs)163 public static Value literalConstant(int value, Value... inputs) { 164 boolean isPartiallyEscaped = false; 165 for (Value input : inputs) { 166 if (input.isPartiallyEscaped()) { 167 isPartiallyEscaped = true; 168 break; 169 } 170 } 171 return literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, isPartiallyEscaped); 172 } 173 174 /** 175 * Create a literal value using a string with a {@code escapeMode} of {@code 176 * EscapeMode.ESCAPE_IS_CONSTANT} and {@code partiallyEscaped} based on the {@code 177 * partiallyEscaped} values of the inputs. 178 * 179 * @param value String value of the literal 180 * @param inputs Values that were used to compute the string value. 181 */ literalConstant(String value, Value... inputs)182 public static Value literalConstant(String value, Value... inputs) { 183 boolean isPartiallyEscaped = false; 184 for (Value input : inputs) { 185 if (input.isPartiallyEscaped()) { 186 isPartiallyEscaped = true; 187 break; 188 } 189 } 190 return literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, isPartiallyEscaped); 191 } 192 193 /** 194 * Create a literal value using a boolean with a {@code escapeMode} of {@code 195 * EscapeMode.ESCAPE_IS_CONSTANT} and {@code partiallyEscaped} based on the {@code 196 * partiallyEscaped} values of the inputs. 197 * 198 * @param value boolean value of the literal 199 * @param inputs Values that were used to compute the boolean value. 200 */ literalConstant(boolean value, Value... inputs)201 public static Value literalConstant(boolean value, Value... inputs) { 202 boolean isPartiallyEscaped = false; 203 for (Value input : inputs) { 204 if (input.isPartiallyEscaped()) { 205 isPartiallyEscaped = true; 206 break; 207 } 208 } 209 return literalValue(value, EscapeMode.ESCAPE_IS_CONSTANT, isPartiallyEscaped); 210 } 211 212 /** 213 * Create a value linked to a variable name. 214 * 215 * @param name The pathname of the variable relative to the given {@link DataContext} 216 * @param dataContext The DataContext defining the scope and Data objects to use when 217 * dereferencing the name. 218 * @return A Value object that allows access to the variable name, the variable Data object (if it 219 * exists) and to the value of the variable. 220 */ variableValue(String name, DataContext dataContext)221 public static Value variableValue(String name, DataContext dataContext) { 222 return new VariableValue(name, dataContext); 223 } 224 225 @Override equals(Object other)226 public boolean equals(Object other) { 227 if (other == null || !(other instanceof Value)) { 228 return false; 229 } 230 Value otherValue = (Value) other; 231 // This behaves the same way as ClearSilver. 232 return exists() == otherValue.exists() 233 && (asString().equals(otherValue.asString()) || (isEmpty() && otherValue.isEmpty())); 234 } 235 236 @Override hashCode()237 public int hashCode() { 238 return toString().hashCode(); 239 } 240 241 @Override toString()242 public String toString() { 243 return asString(); 244 } 245 isPartiallyEscaped()246 public boolean isPartiallyEscaped() { 247 return partiallyEscaped; 248 } 249 250 /** 251 * Indicates the escaping that was applied to the expression represented by this value. 252 * 253 * <p> 254 * May be checked by the JSilver code before applying autoescaping. It differs from {@code 255 * isEscaped}, which is true iff any part of the variable expression contains an escaping 256 * function, even if the entire expression has not been escaped. Both methods are required, 257 * {@code isEscaped} to determine whether <?cs escape > commands should be applied, and 258 * {@code getEscapeMode} for autoescaping. This is done to maintain compatibility with 259 * ClearSilver's behaviour. 260 * 261 * @return {@code EscapeMode.ESCAPE_IS_CONSTANT} if the value represents a constant string 262 * literal. Or the appropriate {@link EscapeMode} if the value is the output of an 263 * escaping function. 264 * 265 * @see EscapeMode 266 */ getEscapeMode()267 public EscapeMode getEscapeMode() { 268 return escapeMode; 269 } 270 271 } 272