/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.celements.navigation; import java.util.Objects; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.validation.constraints.NotNull; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.EntityReference; import com.celements.model.util.ModelUtils; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.xpn.xwiki.web.Utils; /** * Immutable TreeNode with optional lazy getter for PartName * needed as performance improvement for legacy NotMappedMenuItems * * @author Fabian Pichler, Marc Sladek */ @Immutable public final class TreeNode { private final EntityReference parentRef; private final Integer position; private final DocumentReference docRef; /** * The partName is only once set to a non-null value. This can be directly in the constructor or * if a PartNameGetter strategy is provided and NO partName then the strategy is lazily evaluated * on the first getPartName call. */ private volatile String partName; private final PartNameGetter partNameStrategy; public TreeNode(@NotNull DocumentReference docRef, @Nullable EntityReference parentRef, @Nullable Integer position, @Nullable String partName) { this(docRef, parentRef, position, partName, null); } public TreeNode(@NotNull DocumentReference docRef, @Nullable EntityReference parentRef, @Nullable Integer position, @NotNull PartNameGetter strategy) { this(docRef, parentRef, position, null, strategy); } public TreeNode(@NotNull DocumentReference docRef, @Nullable EntityReference parentRef, @Nullable Integer position) { this(docRef, parentRef, position, (String) null); } private TreeNode(@NotNull DocumentReference docRef, @Nullable EntityReference parentRef, @Nullable Integer position, @Nullable String partName, @Nullable PartNameGetter strategy) { Preconditions.checkNotNull(docRef, "Document reference for TreeNode may not be null."); this.docRef = getModelUtils().cloneRef(docRef, DocumentReference.class); this.parentRef = (parentRef != null) ? getModelUtils().cloneRef(parentRef) : null; this.position = MoreObjects.firstNonNull(position, new Integer(0)); this.partName = (strategy == null) ? Strings.nullToEmpty(partName) : partName; this.partNameStrategy = strategy; } public boolean isEmptyParentRef() { return (parentRef == null); } /** * @return fullName for documentReference backing TreeNode * @deprecated since 2.14.0 use getDocumentReference instead * 2016.09.20, FP: CAUTION: this method is still used in velocity scripts. * e.g. LinkPicker.vm */ @Deprecated @NotNull public String getFullName() { return getModelUtils().serializeRefLocal(docRef); } /** * @deprecated since 2.81 use getParentRef instead * 2016.09.20, FP: CAUTION: this method may be still used in velocity scripts. */ @Deprecated @NotNull public String getParent() { return getModelUtils().serializeRefLocal(getParentRef()); } @NotNull public EntityReference getParentRef() { EntityReference ref; if (isEmptyParentRef()) { ref = getDocumentReference().getLastSpaceReference(); } else { ref = parentRef; } return getModelUtils().cloneRef(ref); } @NotNull public Integer getPosition() { return position; } @NotNull public String getPartName() { if ((partName == null) && (partNameStrategy != null)) { synchronized (this) { if (partName == null) { this.partName = Strings.nullToEmpty(partNameStrategy.getPartName(docRef)); } } } return partName; } @NotNull public DocumentReference getDocumentReference() { return docRef; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof TreeNode) { // null check included TreeNode node = (TreeNode) obj; return Objects.equals(docRef, node.docRef) && Objects.equals(position, node.position); } return false; } @Override public int hashCode() { return Objects.hash(docRef, position); } @Override @NotNull public String toString() { return new StringBuilder().append("TreeNode [docRef=").append(docRef).append( ", parentRef=").append(parentRef).append(", position=").append(position).append( ", partName=").append(partName).append("]").toString(); } private static ModelUtils getModelUtils() { return Utils.getComponent(ModelUtils.class); } }