Home | History | Annotate | Download | only in values
      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);
     45     EMPTY_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE,
     46         new StringValue("", EscapeMode.ESCAPE_NONE, 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);
     51     EMPTY_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new StringValue("", EscapeMode.ESCAPE_NONE, 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);
     56     ZERO_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, 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);
     61     ZERO_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(0, EscapeMode.ESCAPE_NONE, 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);
     66     ONE_PART_ESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, 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);
     71     ONE_UNESCAPED.put(EscapeMode.ESCAPE_NONE, new NumberValue(1, EscapeMode.ESCAPE_NONE, 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 
     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    */
     91   public abstract boolean asBoolean();
     92 
     93   /**
     94    * Fetch value as string.
     95    */
     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    */
    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    */
    108   public abstract boolean exists();
    109 
    110   public abstract boolean isEmpty();
    111 
    112   /**
    113    * Create a literal value using an int.
    114    */
    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    */
    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    */
    136   public static Value literalValue(boolean value, EscapeMode mode, boolean partiallyEscaped) {
    137     return getIntValue(mode, partiallyEscaped, value ? 1 : 0);
    138   }
    139 
    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    */
    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    */
    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    */
    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    */
    221   public static Value variableValue(String name, DataContext dataContext) {
    222     return new VariableValue(name, dataContext);
    223   }
    224 
    225   @Override
    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
    237   public int hashCode() {
    238     return toString().hashCode();
    239   }
    240 
    241   @Override
    242   public String toString() {
    243     return asString();
    244   }
    245 
    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 &lt;?cs escape &gt; 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    */
    267   public EscapeMode getEscapeMode() {
    268     return escapeMode;
    269   }
    270 
    271 }
    272