Home | History | Annotate | Download | only in data
      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.data;
     18 
     19 import com.google.clearsilver.jsilver.autoescape.EscapeMode;
     20 
     21 import java.io.IOException;
     22 import java.util.Map;
     23 
     24 /**
     25  * This is the basic implementation of the DataContext. It stores the root Data node and a stack of
     26  * Data objects that hold local variables. By definition, local variables are limited to single HDF
     27  * names, with no '.' allowed. We use this to limit our search in the stack for the first occurence
     28  * of the first chunk in the variable name.
     29  */
     30 public class DefaultDataContext implements DataContext {
     31 
     32   /**
     33    * Root node of the Data structure used by the current context.
     34    */
     35   private final Data rootData;
     36 
     37   /**
     38    * Head of the linked list of local variables, starting with the newest variable created.
     39    */
     40   private LocalVariable head = null;
     41 
     42   /**
     43    * Indicates whether the renderer has pushed a new variable scope but no variable has been created
     44    * yet.
     45    */
     46   private boolean newScope = false;
     47 
     48   public DefaultDataContext(Data data) {
     49     if (data == null) {
     50       throw new IllegalArgumentException("rootData is null");
     51     }
     52     this.rootData = data;
     53   }
     54 
     55   @Override
     56   public Data getRootData() {
     57     return rootData;
     58   }
     59 
     60   /**
     61    * Starts a new variable scope. It is illegal to call this twice in a row without declaring a
     62    * local variable.
     63    */
     64   @Override
     65   public void pushVariableScope() {
     66     if (newScope) {
     67       throw new IllegalStateException("PushVariableScope called twice with no "
     68           + "variables declared in between.");
     69     }
     70     newScope = true;
     71   }
     72 
     73   /**
     74    * Removes the current variable scope and references to the variables in it. It is illegal to call
     75    * this more times than {@link #pushVariableScope} has been called.
     76    */
     77   @Override
     78   public void popVariableScope() {
     79     if (newScope) {
     80       // We pushed but created no local variables.
     81       newScope = false;
     82     } else {
     83       // Note, this will throw a NullPointerException if there is no scope to
     84       // pop.
     85       head = head.nextScope;
     86     }
     87   }
     88 
     89   @Override
     90   public void createLocalVariableByValue(String name, String value) {
     91     createLocalVariableByValue(name, value, EscapeMode.ESCAPE_NONE);
     92   }
     93 
     94   @Override
     95   public void createLocalVariableByValue(String name, String value, EscapeMode mode) {
     96     LocalVariable local = createLocalVariable(name);
     97     local.value = value;
     98     local.isPath = false;
     99     local.setEscapeMode(mode);
    100   }
    101 
    102   @Override
    103   public void createLocalVariableByValue(String name, String value, boolean isFirst, boolean isLast) {
    104     LocalVariable local = createLocalVariable(name);
    105     local.value = value;
    106     local.isPath = false;
    107     local.isFirst = isFirst;
    108     local.isLast = isLast;
    109   }
    110 
    111   @Override
    112   public void createLocalVariableByPath(String name, String path) {
    113     LocalVariable local = createLocalVariable(name);
    114     local.value = path;
    115     local.isPath = true;
    116   }
    117 
    118   private LocalVariable createLocalVariable(String name) {
    119     if (head == null && !newScope) {
    120       throw new IllegalStateException("Must call pushVariableScope before "
    121           + "creating local variable.");
    122     }
    123     // First look for a local variable with the same name in the current scope
    124     // and return that if it exists. If we don't do this then loops and each
    125     // can cause the list of local variables to explode.
    126     //
    127     // We only look at the first local variable (head) if it is part of the
    128     // current scope (we're not defining a new scope). Since each and loop
    129     // variables are always in their own scope, there would only be one variable
    130     // in the current scope if it was a reuse case. For macro calls (which are
    131     // the only other way createLocalVariable is called multiple times in a
    132     // single scope and thus head may not be the only local variable in the
    133     // current scope) it is illegal to use the same argument name more than
    134     // once. Therefore we don't need to worry about checking to see if the new
    135     // local variable name matches beyond the first local variable in the
    136     // current scope.
    137 
    138     if (!newScope && head != null && name.equals(head.name)) {
    139       // Clear out the fields that aren't set by the callers.
    140       head.isFirst = true;
    141       head.isLast = true;
    142       head.node = null;
    143       return head;
    144     }
    145 
    146     LocalVariable local = new LocalVariable();
    147     local.name = name;
    148     local.next = head;
    149     if (newScope) {
    150       local.nextScope = head;
    151       newScope = false;
    152     } else if (head != null) {
    153       local.nextScope = head.nextScope;
    154     } else {
    155       local.nextScope = null;
    156     }
    157     head = local;
    158     return local;
    159   }
    160 
    161   @Override
    162   public Data findVariable(String name, boolean create) {
    163     return findVariable(name, create, head);
    164   }
    165 
    166   @Override
    167   public EscapeMode findVariableEscapeMode(String name) {
    168     Data var = findVariable(name, false);
    169     if (var == null) {
    170       return EscapeMode.ESCAPE_NONE;
    171     } else {
    172       return var.getEscapeMode();
    173     }
    174   }
    175 
    176   private Data findVariable(String name, boolean create, LocalVariable start) {
    177     // When traversing the stack, we first look for the first chunk of the
    178     // name. This is so we respect scope. If we are searching for 'foo.bar'
    179     // and 'foo' is defined in many scopes, we should stop searching
    180     // after the first time we find 'foo', even if that local variable does
    181     // not have a child 'bar'.
    182     String firstChunk = name;
    183     int dot = name.indexOf('.');
    184     if (dot != -1) {
    185       firstChunk = name.substring(0, dot);
    186     }
    187 
    188     LocalVariable curr = start;
    189 
    190     while (curr != null) {
    191       if (curr.name.equals(firstChunk)) {
    192         if (curr.isPath) {
    193           // The local variable references another Data node, dereference it.
    194           if (curr.node == null) {
    195             // We haven't resolved the path yet. Do it now and cache it if
    196             // not null. Note we begin looking for the dereferenced in the next
    197             // scope.
    198             curr.node = findVariable(curr.value, create, curr.nextScope);
    199             if (curr.node == null) {
    200               // Node does not exist. Any children won't either.
    201               return null;
    202             }
    203           }
    204           // We have a reference to the Data node directly. Use it.
    205           if (dot == -1) {
    206             // This is the node we're interested in.
    207             return curr.node;
    208           } else {
    209             if (create) {
    210               return curr.node.createChild(name.substring(dot + 1));
    211             } else {
    212               return curr.node.getChild(name.substring(dot + 1));
    213             }
    214           }
    215         } else {
    216           // This is a literal value local variable. It has no children, nor
    217           // can it. We want to throw an error on creation of children.
    218           if (dot == -1) {
    219             return curr;
    220           }
    221           if (create) {
    222             throw new IllegalStateException("Cannot create children of a "
    223                 + "local literal variable");
    224           } else {
    225             // No children.
    226             return null;
    227           }
    228         }
    229       }
    230       curr = curr.next;
    231     }
    232     if (create) {
    233       return rootData.createChild(name);
    234     } else {
    235       return rootData.getChild(name);
    236     }
    237   }
    238 
    239   /**
    240    * This class holds the name and value/path of a local variable. Objects of this type should only
    241    * be exposed outside of this class for value-based local variables.
    242    * <p>
    243    * Fields are not private to avoid the performance overhead of hidden access methods used for
    244    * outer classes to access private fields of inner classes.
    245    */
    246   private static class LocalVariable extends AbstractData {
    247     // Pointer to next LocalVariable in the list.
    248     LocalVariable next;
    249     // Pointer to the first LocalVariable in the next scope down.
    250     LocalVariable nextScope;
    251 
    252     String name;
    253     String value;
    254     // True if value represents the path to another Data variable.
    255     boolean isPath;
    256     // Once the path resolves to a valid Data node, store it here to avoid
    257     // refetching.
    258     Data node = null;
    259 
    260     // Used only for loop local variables
    261     boolean isFirst = true;
    262     boolean isLast = true;
    263 
    264     public String getName() {
    265       return name;
    266     }
    267 
    268     public String getValue() {
    269       return value;
    270     }
    271 
    272     public void setValue(String value) {
    273       this.value = value;
    274     }
    275 
    276     public String getFullPath() {
    277       return name;
    278     }
    279 
    280     public void setAttribute(String key, String value) {
    281       throw new UnsupportedOperationException("Not allowed on local variables.");
    282     }
    283 
    284     public String getAttribute(String key) {
    285       return null;
    286     }
    287 
    288     public boolean hasAttribute(String key) {
    289       return false;
    290     }
    291 
    292     public int getAttributeCount() {
    293       return 0;
    294     }
    295 
    296     public Iterable<Map.Entry<String, String>> getAttributes() {
    297       return null;
    298     }
    299 
    300     public Data getRoot() {
    301       return null;
    302     }
    303 
    304     public Data getParent() {
    305       return null;
    306     }
    307 
    308     public boolean isFirstSibling() {
    309       return isFirst;
    310     }
    311 
    312     public boolean isLastSibling() {
    313       return isLast;
    314     }
    315 
    316     public Data getNextSibling() {
    317       throw new UnsupportedOperationException("Not allowed on local variables.");
    318     }
    319 
    320     public int getChildCount() {
    321       return 0;
    322     }
    323 
    324     public Iterable<? extends Data> getChildren() {
    325       return null;
    326     }
    327 
    328     public Data getChild(String path) {
    329       return null;
    330     }
    331 
    332     public Data createChild(String path) {
    333       throw new UnsupportedOperationException("Not allowed on local variables.");
    334     }
    335 
    336     public void removeTree(String path) {
    337       throw new UnsupportedOperationException("Not allowed on local variables.");
    338     }
    339 
    340     public void setSymlink(String sourcePath, String destinationPath) {
    341       throw new UnsupportedOperationException("Not allowed on local variables.");
    342     }
    343 
    344     public void setSymlink(String sourcePath, Data destination) {
    345       throw new UnsupportedOperationException("Not allowed on local variables.");
    346     }
    347 
    348     public void setSymlink(Data symLink) {
    349       throw new UnsupportedOperationException("Not allowed on local variables.");
    350     }
    351 
    352     public Data getSymlink() {
    353       return this;
    354     }
    355 
    356     public void copy(String toPath, Data from) {
    357       throw new UnsupportedOperationException("Not allowed on local variables.");
    358     }
    359 
    360     public void copy(Data from) {
    361       throw new UnsupportedOperationException("Not allowed on local variables.");
    362     }
    363 
    364     public String getValue(String path, String defaultValue) {
    365       throw new UnsupportedOperationException("Not allowed on local variables.");
    366     }
    367 
    368     public void write(Appendable out, int indent) throws IOException {
    369       for (int i = 0; i < indent; i++) {
    370         out.append("  ");
    371       }
    372       out.append(getName()).append(" = ").append(getValue());
    373     }
    374   }
    375 }
    376