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