156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/* 256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Copyright (C) 2010 Google Inc. 356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Licensed under the Apache License, Version 2.0 (the "License"); 556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * you may not use this file except in compliance with the License. 656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * You may obtain a copy of the License at 756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * http://www.apache.org/licenses/LICENSE-2.0 956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 1056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Unless required by applicable law or agreed to in writing, software 1156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * distributed under the License is distributed on an "AS IS" BASIS, 1256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * See the License for the specific language governing permissions and 1456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * limitations under the License. 1556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 1656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 1756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpackage com.google.clearsilver.jsilver.data; 1856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 1956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 2056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.io.IOException; 2156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.Collections; 2256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.HashMap; 2356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.Iterator; 2456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.Map; 2556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.NoSuchElementException; 2656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 2756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/** 2856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Represents a hierarchical data set of primitives. 2956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 3056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * This is the JSilver equivalent to ClearSilver's HDF object. 3156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 3256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * This class has no synchronization logic. Follow the same thread-safety semantics as you would for 3356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * a java.util.ArrayList (i.e. concurrent reads allowed, but ensure you have exclusive access when 3456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * modifying - should not read whilst another thread writes). 3556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 3656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpublic class NestedMapData extends AbstractData { 3756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 3856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 3956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Number of children a node can have before we bother allocating a HashMap. We currently allocate 4056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * the HashMap on the 5th child. 4156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 4256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static final int CHILD_MAP_THRESHOLD = 4; 4356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 4456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private String name; 4556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData parent; 4656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private final NestedMapData root; 4756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 4856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Lazily intitialized after CHILD_MAP_THRESHOLD is hit. 4956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private Map<String, NestedMapData> children = null; 5056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Number of children 5156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private int childCount = 0; 5256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // First child (first sibling of children) 5356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData firstChild = null; 5456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Last child (last sibling of children) 5556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData lastChild = null; 5656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 5756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 5856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Single object returned by getChildren(). Constructs ChildrenIterator objects. 5956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 6056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private Iterable<NestedMapData> iterableChildren = null; 6156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 6256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Holds the attributes for this HDF node. 6356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private Map<String, String> attributeList = null; 6456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 6556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private String value = null; 6656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData symLink = this; 6756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 6856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Doubly linked list of siblings. 6956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData prevSibling = null; 7056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData nextSibling = null; 7156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 7256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public NestedMapData() { 7356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson name = null; 7456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson parent = null; 7556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson root = this; 7656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 7756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 7856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson protected NestedMapData(String name, NestedMapData parent, NestedMapData root) { 7956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.name = name; 8056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.parent = parent; 8156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.root = root; 8256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 8356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 8456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // ******************* Node creation and removal ******************* 8556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Must be kept in sync. 8656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 8756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 8856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Creates a child of this node. 8956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 9056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * @param chunk name to give the new child node. 9156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * @return the NestedMapData object corresponding to the new node. 9256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 9356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson protected NestedMapData createChildNode(String chunk) { 9456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData sym = followSymLinkToTheBitterEnd(); 9556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData data = new NestedMapData(chunk, sym, sym.root); 9656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 9756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // If the parent node's child count is 5 or more and it does not have a 9856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // children Hashmap, initialize it now. 9956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (sym.children == null && sym.childCount >= CHILD_MAP_THRESHOLD) { 10056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sym.children = new HashMap<String, NestedMapData>(); 10156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Copy in existing children. 10256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData curr = sym.firstChild; 10356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson while (curr != null) { 10456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sym.children.put(curr.getName(), curr); 10556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson curr = curr.nextSibling; 10656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 10756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 10856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // If the parent node has a children map, add the new child node to it. 10956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (sym.children != null) { 11056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sym.children.put(chunk, data); 11156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 11256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 11356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson data.prevSibling = sym.lastChild; 11456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (sym.lastChild != null) { 11556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Update previous lastChild to point to new child. 11656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sym.lastChild.nextSibling = data; 11756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 11856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // There were no previous children so this is the first. 11956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sym.firstChild = data; 12056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 12156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sym.lastChild = data; 12256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 12356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sym.childCount++; 12456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 12556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return data; 12656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 12756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 12856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private void severNode() { 12956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (parent == null) { 13056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return; 13156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 13256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (parent.children != null) { 13356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson parent.children.remove(name); 13456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 13556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (prevSibling != null) { 13656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson prevSibling.nextSibling = nextSibling; 13756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 13856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson parent.firstChild = nextSibling; 13956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 14056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (nextSibling != null) { 14156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson nextSibling.prevSibling = prevSibling; 14256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 14356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson parent.lastChild = prevSibling; 14456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 14556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson parent.childCount--; 14656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 14756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Need to cleal the parent pointer or else if someone has a direct reference to this node 14856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // they will get very strange results. 14956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson parent = null; 15056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 15156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 15256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // ******************* Node data ******************* 15356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 15456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 15556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns the name of this HDF node. The root node has no name, so calling this on the root node 15656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * will return null. 15756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 15856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public String getName() { 15956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return name; 16056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 16156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 16256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 16356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Recursive method that builds the full path to this node via its parent links into the given 16456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * StringBuilder. 16556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 16656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private void getPathName(StringBuilder sb) { 16756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (parent != null && parent != root) { 16856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson parent.getPathName(sb); 16956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sb.append("."); 17056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 17156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String name = getName(); 17256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (name != null) { 17356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson sb.append(name); 17456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 17556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 17656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 17756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 17856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns the full path to this node via its parent links. 17956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 18056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public String getFullPath() { 18156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson StringBuilder sb = new StringBuilder(); 18256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson getPathName(sb); 18356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return sb.toString(); 18456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 18556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 18656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 18756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns the value of this HDF node, or null if this node has no value. Every node in the tree 18856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * can have a value, a child, and a next peer. 18956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 19056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public String getValue() { 19156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return followSymLinkToTheBitterEnd().value; 19256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 19356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 19456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 19556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Set the value of this node. Any symlink that may have been set for this node will be replaced. 19656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 19756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void setValue(String value) { 19856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Clearsilver behaviour is to replace any symlink that may already exist 19956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // here with the new value, rather than following the symlink. 20056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.symLink = this; 20156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.value = value; 20256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 20356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 20456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // ******************* Attributes ******************* 20556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 20656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // We don't expect attributes to be heavily used. They are not used for template parsing. 20756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 20856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void setAttribute(String key, String value) { 20956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (key == null) { 21056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson throw new NullPointerException("Attribute name cannot be null."); 21156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 21256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (attributeList == null) { 21356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Do we need to worry about synchronization? 21456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson attributeList = new HashMap<String, String>(); 21556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 21656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (value == null) { 21756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson attributeList.remove(key); 21856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 21956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson attributeList.put(key, value); 22056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 22156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 22256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 22356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public String getAttribute(String key) { 22456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return attributeList == null ? null : attributeList.get(key); 22556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 22656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 22756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public boolean hasAttribute(String key) { 22856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return attributeList != null && attributeList.containsKey(key); 22956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 23056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 23156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public int getAttributeCount() { 23256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return attributeList == null ? 0 : attributeList.size(); 23356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 23456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 23556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public Iterable<Map.Entry<String, String>> getAttributes() { 23656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (attributeList == null) { 23756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return Collections.emptySet(); 23856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 23956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return attributeList.entrySet(); 24056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 24156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 24256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // ******************* Children ******************* 24356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 24456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 24556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Return the root of the tree where the current node lies. If the current node is the root, 24656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * return this. 24756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 24856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public Data getRoot() { 24956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return root; 25056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 25156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 25256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 25356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Get the parent node. 25456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 25556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public Data getParent() { 25656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return parent; 25756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 25856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 25956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 26056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Is this the first of its siblings? 26156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 26256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @Override 26356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public boolean isFirstSibling() { 26456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return prevSibling == null; 26556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 26656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 26756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 26856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Is this the last of its siblings? 26956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 27056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @Override 27156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public boolean isLastSibling() { 27256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return nextSibling == null; 27356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 27456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 27556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public Data getNextSibling() { 27656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return nextSibling; 27756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 27856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 27956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 28056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns number of child nodes. 28156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 28256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @Override 28356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public int getChildCount() { 28456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return followSymLinkToTheBitterEnd().childCount; 28556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 28656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 28756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 28856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Returns children of this node. 28956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 29056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @Override 29156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public Iterable<? extends Data> getChildren() { 29256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (iterableChildren == null) { 29356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson iterableChildren = new IterableChildren(); 29456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 29556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return iterableChildren; 29656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 29756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 29856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 29956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Retrieves the object that is the root of the subtree at hdfpath, returning null if the subtree 30056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * doesn't exist 30156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 30256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public NestedMapData getChild(String path) { 30356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData current = this; 30456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (int lastDot = 0, nextDot = 0; nextDot != -1 && current != null; lastDot = nextDot + 1) { 30556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson nextDot = path.indexOf('.', lastDot); 30656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot); 30756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson current = current.followSymLinkToTheBitterEnd().getChildNode(chunk); 30856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 30956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return current; 31056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 31156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 31256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 31356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Retrieves the HDF object that is the root of the subtree at hdfpath, create the subtree if it 31456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * doesn't exist 31556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 31656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public NestedMapData createChild(String path) { 31756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData current = this; 31856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (int lastDot = 0, nextDot = 0; nextDot != -1; lastDot = nextDot + 1) { 31956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson nextDot = path.indexOf('.', lastDot); 32056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String chunk = nextDot == -1 ? path.substring(lastDot) : path.substring(lastDot, nextDot); 32156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData currentSymLink = current.followSymLinkToTheBitterEnd(); 32256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson current = currentSymLink.getChildNode(chunk); 32356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (current == null) { 32456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson current = currentSymLink.createChildNode(chunk); 32556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 32656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 32756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return current; 32856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 32956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 33056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 33156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Non-recursive method that only returns a Data node if this node has a child whose name matches 33256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * the specified name. 33356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 33456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * @param name String containing the name of the child to look for. 33556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * @return a Data node that is the child of this node and named 'name', otherwise {@code null}. 33656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 33756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData getChildNode(String name) { 33856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData sym = followSymLinkToTheBitterEnd(); 33956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (sym.getChildCount() == 0) { 34056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // No children. Just return null. 34156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return null; 34256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 34356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (sym.children != null) { 34456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // children map exists. Look it up there. 34556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return sym.children.get(name); 34656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 34756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Iterate through linked list of children and look for a name match. 34856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData curr = sym.firstChild; 34956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson while (curr != null) { 35056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (curr.getName().equals(name)) { 35156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return curr; 35256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 35356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson curr = curr.nextSibling; 35456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 35556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return null; 35656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 35756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 35856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 35956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Remove the specified subtree. 36056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 36156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void removeTree(String path) { 36256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData removed = getChild(path); 36356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (removed != null) { 36456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson removed.severNode(); 36556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 36656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 36756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 36856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private NestedMapData followSymLinkToTheBitterEnd() { 36956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData current; 37056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (current = this; current.symLink != current; current = current.symLink); 37156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return current; 37256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 37356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 37456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // ******************* Symbolic links ******************* 37556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 37656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 37756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Set the source node to be a symbolic link to the destination. 37856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 37956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void setSymlink(String sourcePath, String destinationPath) { 38056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson setSymlink(sourcePath, createChild(destinationPath)); 38156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 38256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 38356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 38456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Set the source node to be a symbolic link to the destination. 38556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 38656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void setSymlink(String sourcePath, Data destination) { 38756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson createChild(sourcePath).setSymlink(destination); 38856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 38956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 39056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 39156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Set this node to be a symbolic link to another node. 39256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 39356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void setSymlink(Data symLink) { 39456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (symLink instanceof NestedMapData) { 39556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.symLink = (NestedMapData) symLink; 39656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 39756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String errorMessage = 39856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson "Cannot set symlink of incompatible Data type: " + symLink.getClass().getName(); 39956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (symLink instanceof ChainedData) { 40056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson errorMessage += 40156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson "\nOther type is ChainedData indicating there are " 40256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson + "multiple valid Data nodes for the path: " + symLink.getFullPath(); 40356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 40456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson throw new IllegalArgumentException(errorMessage); 40556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 40656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 40756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 40856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 40956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Retrieve the symbolic link this node points to. Will return reference to self if not a symlink. 41056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 41156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public Data getSymlink() { 41256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return symLink; 41356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 41456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 41556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // ************************ Copy ************************* 41656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 41756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void copy(String toPath, Data from) { 41856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (toPath == null) { 41956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson throw new NullPointerException("Invalid copy destination path"); 42056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 42156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (from == null) { 42256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Is this a nop or should we throw an error? 42356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return; 42456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 42556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson Data to = createChild(toPath); 42656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson to.copy(from); 42756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 42856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 42956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void copy(Data from) { 43056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (from == null) { 43156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Is this a nop or should we throw an error? 43256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return; 43356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 43456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Clear any existing symlink. 43556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.symLink = this; 43656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 43756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Clear any existing attributes and copy the ones from the source. 43856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (this.attributeList != null) { 43956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson this.attributeList.clear(); 44056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 44156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (Map.Entry<String, String> attribute : from.getAttributes()) { 44256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson setAttribute(attribute.getKey(), attribute.getValue()); 44356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 44456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 44556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // If the source node was a symlink, just copy the link over and we're done. 44656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (from.getSymlink() != from) { 44756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson setSymlink(from.getSymlink()); 44856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return; 44956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 45056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 45156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Copy over the node's value. 45256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson setValue(from.getValue()); 45356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 45456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // For each source child, create a new child with the same name and recurse. 45556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (Data fromChild : from.getChildren()) { 45656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson Data toChild = createChild(fromChild.getName()); 45756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson toChild.copy(fromChild); 45856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 45956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 46056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 46156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 46256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Write out the String representation of this HDF node. 46356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 46456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void write(Appendable out, int indent) throws IOException { 46556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (symLink != this) { 46656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson indent(out, indent); 46756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson writeNameAttrs(out); 46856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(" : ").append(symLink.getFullPath()).append('\n'); 46956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return; 47056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 47156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (getValue() != null) { 47256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson indent(out, indent); 47356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson writeNameAttrs(out); 47456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (getValue().contains("\n")) { 47556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson writeMultiline(out); 47656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 47756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(" = ").append(getValue()).append('\n'); 47856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 47956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 48056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (getChildCount() > 0) { 48156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson int childIndent = indent; 48256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (this != root) { 48356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson indent(out, indent); 48456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson writeNameAttrs(out); 48556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(" {\n"); 48656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson childIndent++; 48756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 48856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (Data child : getChildren()) { 48956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson child.write(out, childIndent); 49056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 49156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (this != root) { 49256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson indent(out, indent); 49356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append("}\n"); 49456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 49556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 49656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 49756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 49856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 49956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Here we optimize the structure for long-term use. We call intern() on all Strings to reduce the 50056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * copies of the same string that appear 50156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 50256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson @Override 50356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void optimize() { 50456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson name = name == null ? null : name.intern(); 50556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson value = value == null ? null : value.intern(); 50656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (attributeList != null) { 50756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson Map<String, String> newList = new HashMap<String, String>(attributeList.size()); 50856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (Map.Entry<String, String> entry : attributeList.entrySet()) { 50956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String key = entry.getKey(); 51056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String value = entry.getValue(); 51156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson key = key == null ? null : key.intern(); 51256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson value = value == null ? null : value.intern(); 51356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson newList.put(key, value); 51456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 51556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson attributeList = newList; 51656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 51756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (NestedMapData child = firstChild; child != null; child = child.nextSibling) { 51856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson child.optimize(); 51956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 52056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 52156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 52256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private void writeMultiline(Appendable out) throws IOException { 52356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson String marker = "EOM"; 52456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson while (getValue().contains(marker)) { 52556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson marker += System.nanoTime() % 10; 52656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 52756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(" << ").append(marker).append('\n').append(getValue()); 52856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (!getValue().endsWith("\n")) { 52956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append('\n'); 53056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 53156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(marker).append('\n'); 53256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 53356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 53456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private void indent(Appendable out, int indent) throws IOException { 53556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (int i = 0; i < indent; i++) { 53656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(" "); 53756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 53856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 53956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 54056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private void writeNameAttrs(Appendable out) throws IOException { 54156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Output name 54256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(getName()); 54356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (attributeList != null && !attributeList.isEmpty()) { 54456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Output parameters. 54556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(" ["); 54656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson boolean first = true; 54756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (Map.Entry<String, String> attr : attributeList.entrySet()) { 54856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (first) { 54956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson first = false; 55056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } else { 55156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(", "); 55256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 55356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(attr.getKey()); 55456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (attr.getValue().equals("1")) { 55556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson continue; 55656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 55756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(" = \""); 55856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson writeAttributeValue(out, attr.getValue()); 55956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append('"'); 56056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 56156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(']'); 56256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 56356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 56456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 56556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson // Visible for testing 56656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson static void writeAttributeValue(Appendable out, String value) throws IOException { 56756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson for (int i = 0; i < value.length(); i++) { 56856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson char c = value.charAt(i); 56956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson switch (c) { 57056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson case '"': 57156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append("\\\""); 57256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson break; 57356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson case '\n': 57456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append("\\n"); 57556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson break; 57656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson case '\t': 57756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append("\\t"); 57856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson break; 57956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson case '\\': 58056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append("\\\\"); 58156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson break; 58256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson case '\r': 58356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append("\\r"); 58456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson break; 58556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson default: 58656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson out.append(c); 58756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 58856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 58956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 59056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 59156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 59256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * A single instance of this is created per NestedMapData object. Its single method returns an 59356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * iterator over the children of this node. 59456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <p> 59556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Note: This returns an iterator that starts with the first child at the time of iterator() being 59656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * called, not when this Iterable object was handed to the code. This might result in slightly 59756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * unexpected behavior if the children list is modified between when getChildren() is called and 59856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * iterator is called on the returned object but this should not be an issue in practice as 59956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * iterator is usually called immediately after getChildren(). 60056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * 60156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 60256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private class IterableChildren implements Iterable<NestedMapData> { 60356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public Iterator<NestedMapData> iterator() { 60456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return new ChildrenIterator(followSymLinkToTheBitterEnd().firstChild); 60556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 60656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 60756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 60856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson /** 60956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Iterator implementation for children. We do not check for concurrent modification like in other 61056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Collection iterators. 61156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */ 61256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson private static class ChildrenIterator implements Iterator<NestedMapData> { 61356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData current; 61456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson NestedMapData next; 61556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 61656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson ChildrenIterator(NestedMapData first) { 61756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson next = first; 61856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson current = null; 61956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 62056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 62156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public boolean hasNext() { 62256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return next != null; 62356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 62456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 62556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public NestedMapData next() { 62656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (next == null) { 62756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson throw new NoSuchElementException(); 62856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 62956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson current = next; 63056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson next = next.nextSibling; 63156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson return current; 63256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 63356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson 63456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson public void remove() { 63556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson if (current == null) { 63656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson throw new NoSuchElementException(); 63756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 63856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson current.severNode(); 63956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson current = null; 64056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 64156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson } 64256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson} 643