• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DefaultDataContext(Data data)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
getRootData()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
pushVariableScope()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
popVariableScope()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
createLocalVariableByValue(String name, String value)90   public void createLocalVariableByValue(String name, String value) {
91     createLocalVariableByValue(name, value, EscapeMode.ESCAPE_NONE);
92   }
93 
94   @Override
createLocalVariableByValue(String name, String value, EscapeMode mode)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
createLocalVariableByValue(String name, String value, boolean isFirst, boolean isLast)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
createLocalVariableByPath(String name, String path)112   public void createLocalVariableByPath(String name, String path) {
113     LocalVariable local = createLocalVariable(name);
114     local.value = path;
115     local.isPath = true;
116   }
117 
createLocalVariable(String name)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
findVariable(String name, boolean create)162   public Data findVariable(String name, boolean create) {
163     return findVariable(name, create, head);
164   }
165 
166   @Override
findVariableEscapeMode(String name)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 
findVariable(String name, boolean create, LocalVariable start)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 
getName()264     public String getName() {
265       return name;
266     }
267 
getValue()268     public String getValue() {
269       return value;
270     }
271 
setValue(String value)272     public void setValue(String value) {
273       this.value = value;
274     }
275 
getFullPath()276     public String getFullPath() {
277       return name;
278     }
279 
setAttribute(String key, String value)280     public void setAttribute(String key, String value) {
281       throw new UnsupportedOperationException("Not allowed on local variables.");
282     }
283 
getAttribute(String key)284     public String getAttribute(String key) {
285       return null;
286     }
287 
hasAttribute(String key)288     public boolean hasAttribute(String key) {
289       return false;
290     }
291 
getAttributeCount()292     public int getAttributeCount() {
293       return 0;
294     }
295 
getAttributes()296     public Iterable<Map.Entry<String, String>> getAttributes() {
297       return null;
298     }
299 
getRoot()300     public Data getRoot() {
301       return null;
302     }
303 
getParent()304     public Data getParent() {
305       return null;
306     }
307 
isFirstSibling()308     public boolean isFirstSibling() {
309       return isFirst;
310     }
311 
isLastSibling()312     public boolean isLastSibling() {
313       return isLast;
314     }
315 
getNextSibling()316     public Data getNextSibling() {
317       throw new UnsupportedOperationException("Not allowed on local variables.");
318     }
319 
getChildCount()320     public int getChildCount() {
321       return 0;
322     }
323 
getChildren()324     public Iterable<? extends Data> getChildren() {
325       return null;
326     }
327 
getChild(String path)328     public Data getChild(String path) {
329       return null;
330     }
331 
createChild(String path)332     public Data createChild(String path) {
333       throw new UnsupportedOperationException("Not allowed on local variables.");
334     }
335 
removeTree(String path)336     public void removeTree(String path) {
337       throw new UnsupportedOperationException("Not allowed on local variables.");
338     }
339 
setSymlink(String sourcePath, String destinationPath)340     public void setSymlink(String sourcePath, String destinationPath) {
341       throw new UnsupportedOperationException("Not allowed on local variables.");
342     }
343 
setSymlink(String sourcePath, Data destination)344     public void setSymlink(String sourcePath, Data destination) {
345       throw new UnsupportedOperationException("Not allowed on local variables.");
346     }
347 
setSymlink(Data symLink)348     public void setSymlink(Data symLink) {
349       throw new UnsupportedOperationException("Not allowed on local variables.");
350     }
351 
getSymlink()352     public Data getSymlink() {
353       return this;
354     }
355 
copy(String toPath, Data from)356     public void copy(String toPath, Data from) {
357       throw new UnsupportedOperationException("Not allowed on local variables.");
358     }
359 
copy(Data from)360     public void copy(Data from) {
361       throw new UnsupportedOperationException("Not allowed on local variables.");
362     }
363 
getValue(String path, String defaultValue)364     public String getValue(String path, String defaultValue) {
365       throw new UnsupportedOperationException("Not allowed on local variables.");
366     }
367 
write(Appendable out, int indent)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