/*
* #%L
* Nazgul Project: nazgul-core-algorithms-tree-model
* %%
* Copyright (C) 2010 - 2017 jGuru Europe AB
* %%
* Licensed under the jGuru Europe AB license (the "License"), based
* on Apache License, Version 2.0; you may not use this file except
* in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt
*
* 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.
* #L%
*
*/
package se.jguru.nazgul.core.algorithms.tree.model.path;
import se.jguru.nazgul.core.algorithms.api.Validate;
import se.jguru.nazgul.core.algorithms.api.trees.path.Path;
import se.jguru.nazgul.core.persistence.model.NazgulEntity;
import se.jguru.nazgul.core.xmlbinding.api.XmlBinder;
import se.jguru.nazgul.tools.validation.api.exception.InternalStateValidationException;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotNull;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Non-semantic Path implementation using List as internal storage of KeyType segments.
* <strong>Note!</strong> This implementation is immutable, implying that the append method returns a new
* instance, rather than modifying this one.
*
* @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB
*/
@MappedSuperclass
@XmlType(namespace = XmlBinder.CORE_NAMESPACE, propOrder = {"compoundPath", "segmentSeparator"})
@XmlAccessorType(XmlAccessType.FIELD)
@SuppressWarnings("PMD.UnusedPrivateField")
public abstract class AbstractPath<SegmentType extends Serializable & Comparable<SegmentType>>
extends NazgulEntity implements Path<SegmentType> {
/**
* The separator between segments of this AbstractPath.
* This DEFAULT_SEGMENT_SEPARATOR is used in the default {@code toString} implementation,
* as glue between segments.
*/
public static final String DEFAULT_SEGMENT_SEPARATOR = "/";
// Internal state
@Basic(optional = false)
@Column(nullable = false)
@XmlElement(required = true)
private String compoundPath;
@Basic
@Column
@XmlElement(nillable = true)
private String segmentSeparator;
/**
* JPA/JAXB-friendly constructor.
* <strong>Note!</strong> This constructor is for framework use only.
*/
public AbstractPath() {
}
/**
* Convenience constructor, creating a new AbstractPath instance with the supplied compoundPath
* and using the {@code DEFAULT_SEGMENT_SEPARATOR} for separator between segments.
*
* @param compoundPath The compound path representation of this AbstractPath.
* Each segment string must be convertible to a {@code SegmentType} instance.
* @see #DEFAULT_SEGMENT_SEPARATOR
*/
public AbstractPath(@NotNull final String compoundPath) {
this(compoundPath, DEFAULT_SEGMENT_SEPARATOR);
}
/**
* Compound constructor, creating a new AbstractPath instance from the supplied data.
*
* @param compoundPath The compound path representation of this AbstractPath.
* Each segment string must be convertible to a {@code SegmentType} instance.
* @param segmentSeparator The string separating SegmentType representations from each other.
*/
public AbstractPath(@NotNull final String compoundPath,
@NotNull final String segmentSeparator) {
// Check sanity
Validate.notNull(compoundPath, "compoundPath");
Validate.notEmpty(segmentSeparator, "segmentSeparator");
// Assign internal state
this.compoundPath = compoundPath;
this.segmentSeparator = segmentSeparator;
}
/**
* @return The List of SegmentType instances which is this Path.
*/
protected abstract List<SegmentType> getSegments();
/**
* @return A compound string representation of this AbstractPath used for JPA storage and searches.
* The compoundPath is represented as the segments, separated by the {@code segmentSeparator} string,
* or the {@code DEFAULT_SEGMENT_SEPARATOR} if no explicit segmentSeparator is given.
*/
@NotNull
public String getCompoundPath() {
return compoundPath;
}
/**
* @return The segment separator string, or the {@code DEFAULT_SEGMENT_SEPARATOR} if no
* explicit segmentSeparator string is given.
*/
@NotNull
public String getSegmentSeparator() {
return segmentSeparator == null ? DEFAULT_SEGMENT_SEPARATOR : segmentSeparator;
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return getSegments().size();
}
/**
* {@inheritDoc}
*/
@Override
public int compareTo(final Path<SegmentType> that) {
final Iterator<SegmentType> thisIt = this.iterator();
final Iterator<SegmentType> thatIt = that.iterator();
while (true) {
// Unequal number of segments?
boolean thisHasNext = thisIt.hasNext();
boolean thatHasNext = thatIt.hasNext();
if (!thisHasNext && !thatHasNext) {
return 0;
}
if (!thisHasNext) {
return -1;
} else if (!thatHasNext) {
return 1;
}
// Both iterators seem to have a next element
SegmentType thisKey = thisIt.next();
SegmentType thatKey = thatIt.next();
// Compare this next segment, and return a non-0 result.
int result = thisKey.compareTo(thatKey);
if (result != 0) {
return result;
}
}
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("all")
public boolean equals(final Object obj) {
// Check sanity; fail fast.
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
if (obj == this) {
return true;
}
// Check sizes and types
final AbstractPath that = (AbstractPath) obj;
if (this.size() != that.size()) {
return false;
}
// Delegate
return this.toString().equals(that.toString());
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<SegmentType> iterator() {
return Collections.unmodifiableList(getSegments()).listIterator();
}
/**
* {@inheritDoc}
*/
@Override
public SegmentType get(final int index) throws IndexOutOfBoundsException {
return getSegments().get(index);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (SegmentType current : getSegments()) {
builder.append(current).append(getSegmentSeparator());
}
// Remove trailing separator
return builder.delete(builder.length() - getSegmentSeparator().length(), builder.length()).toString();
}
/**
* {@inheritDoc}
*/
@Override
protected void validateEntityState() throws InternalStateValidationException {
InternalStateValidationException.create()
.notNull(getSegments(), "segments")
.notNull(compoundPath, "compoundPath")
.endExpressionAndValidate();
}
}