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