blob: 75f8e3f7298c8a005b6f71055894bd5978ee164a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.oak.plugins.tree.impl;
import com.google.common.base.Objects;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.tree.ReadOnly;
import org.apache.jackrabbit.oak.plugins.tree.TreeType;
import org.apache.jackrabbit.oak.plugins.tree.TreeTypeAware;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Immutable implementation of the {@code Tree} interface in order to provide
* the much feature rich API functionality for a given {@code NodeState}.
*
* <h3>Tree hierarchy</h3>
* Due to the nature of this {@code Tree} implementation creating a proper
* hierarchical view of the tree structure is the responsibility of the caller.
* It is recommended to start with the state of the
* {@link #ImmutableTree(org.apache.jackrabbit.oak.spi.state.NodeState) root node}
* and build up the hierarchy by calling
* {@link #ImmutableTree(ImmutableTree, String, org.apache.jackrabbit.oak.spi.state.NodeState)}
* for every subsequent child state. Note, that this implementation will not
* perform any kind of validation of the passed state and methods like {@link #isRoot()},
* {@link #getName()} or {@link #getPath()} will just make use of the hierarchy that has been
* create by that sequence. In order to create a disconnected individual tree in cases where
* the hierarchy information is not (yet) need or known it is suggested to use
* {@link #ImmutableTree(ImmutableTree.ParentProvider, String, org.apache.jackrabbit.oak.spi.state.NodeState)}
* an specify an appropriate {@code ParentProvider} implementation.
*
* <h3>ParentProvider</h3>
* Apart from create the tree hierarchy in traversal mode this tree implementation
* allows to instantiate disconnected trees that depending on the use may
* never or on demand retrieve hierarchy information. The following default
* implementations of this internal interface are present:
*
* <ul>
* <li>{@link DefaultParentProvider}: used with the default usage where the
* parent tree is passed to the constructor</li>
* <li>{@link ParentProvider#ROOT_PROVIDER}: the default parent provider for
* the root tree. All children will get {@link DefaultParentProvider}</li>
* <li>{@link ParentProvider#UNSUPPORTED}: throws {@code UnsupportedOperationException}
* upon hierarchy related methods like {@link #getParent()}, {@link #getPath()}</li>
* </ul>
*
* <h3>Filtering 'hidden' items</h3>
* This {@code Tree} implementation reflects the item hierarchy as exposed by the
* underlying {@code NodeState}. In contrast to the mutable implementations it
* does not filter out 'hidden' items as identified by
* {@code org.apache.jackrabbit.oak.spi.state.NodeStateUtils#isHidden(String)}.
*
* <h3>Equality and hash code</h3>
* In contrast to {@link org.apache.jackrabbit.oak.plugins.tree.impl.AbstractMutableTree}
* the {@code ImmutableTree} implements
* {@link Object#equals(Object)} and {@link Object#hashCode()}: Two {@code ImmutableTree}s
* are consider equal if their name and the underlying {@code NodeState}s are equal. Note
* however, that according to the contract defined in {@code NodeState} these
* objects are not expected to be used as hash keys.
*/
public final class ImmutableTree extends AbstractTree implements TreeTypeAware, ReadOnly {
/**
* Underlying node state
*/
private final NodeBuilder nodeBuilder;
/**
* Name of this tree
*/
private final String name;
private final ParentProvider parentProvider;
private String path;
private TreeType type;
public ImmutableTree(@NotNull NodeState rootState) {
this(ParentProvider.ROOT_PROVIDER, PathUtils.ROOT_NAME, rootState);
}
public ImmutableTree(@NotNull ImmutableTree parent, @NotNull String name, @NotNull NodeState state) {
this(new DefaultParentProvider(parent), name, state);
}
public ImmutableTree(@NotNull ParentProvider parentProvider, @NotNull String name, @NotNull NodeState state) {
this.nodeBuilder = new ReadOnlyBuilder(state);
this.name = name;
this.parentProvider = parentProvider;
}
//----------------------------------------------------------< TypeAware >---
@Nullable
public TreeType getType() {
return type;
}
public void setType(@NotNull TreeType type) {
this.type = type;
}
//-------------------------------------------------------< AbstractTree >---
@Override
@NotNull
protected ImmutableTree createChild(@NotNull String name) {
return new ImmutableTree(this, name, nodeBuilder.getNodeState().getChildNode(name));
}
@Override
public boolean isRoot() {
return PathUtils.ROOT_NAME.equals(name);
}
@Override
@Nullable
protected AbstractTree getParentOrNull() {
return parentProvider.getParent();
}
@NotNull
@Override
protected NodeBuilder getNodeBuilder() {
return nodeBuilder;
}
@Override
protected boolean isHidden(@NotNull String name) {
return false;
}
@NotNull
@Override
protected String[] getInternalNodeNames() {
return new String[0];
}
//---------------------------------------------------------------< Tree >---
@NotNull
@Override
public String getName() {
return name;
}
@Override
@NotNull
public String getPath() {
if (path == null) {
path = super.getPath();
}
return path;
}
@NotNull
@Override
public ImmutableTree getChild(@NotNull String name) throws IllegalArgumentException {
return createChild(name);
}
@Override
public boolean remove() {
throw new UnsupportedOperationException();
}
@Override
@NotNull
public Tree addChild(@NotNull String name) {
throw new UnsupportedOperationException();
}
@Override
public void setOrderableChildren(boolean enable) {
throw new UnsupportedOperationException();
}
@Override
public boolean orderBefore(@Nullable String name) {
throw new UnsupportedOperationException();
}
@Override
public void setProperty(@NotNull PropertyState property) {
throw new UnsupportedOperationException();
}
@Override
public <T> void setProperty(@NotNull String name, @NotNull T value) {
throw new UnsupportedOperationException();
}
@Override
public <T> void setProperty(@NotNull String name, @NotNull T value, @NotNull Type<T> type) {
throw new UnsupportedOperationException();
}
@Override
public void removeProperty(@NotNull String name) {
throw new UnsupportedOperationException();
}
//-------------------------------------------------------------< Object >---
@Override
public int hashCode() {
return Objects.hashCode(getName(), nodeBuilder.getNodeState());
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof ImmutableTree) {
ImmutableTree other = (ImmutableTree) o;
return getName().equals(other.getName()) && nodeBuilder.getNodeState().equals(other.nodeBuilder.getNodeState());
}
return false;
}
//--------------------------------------------------------------------------
public interface ParentProvider {
ParentProvider UNSUPPORTED = new ParentProvider() {
@Override
public ImmutableTree getParent() {
throw new UnsupportedOperationException("not supported.");
}
};
ParentProvider ROOT_PROVIDER = new ParentProvider() {
@Override
public ImmutableTree getParent() {
return null;
}
};
@Nullable
ImmutableTree getParent();
}
public static final class DefaultParentProvider implements ParentProvider {
private final ImmutableTree parent;
DefaultParentProvider(@NotNull ImmutableTree parent) {
this.parent = parent;
}
@Override
public ImmutableTree getParent() {
return parent;
}
}
}