blob: c20c68fde0bf83fc2bfa98f9a0303bfddfb9bf36 [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.tinkerpop.gremlin.process.traversal.step.util;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Property;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
*/
public class HasContainer implements Serializable, Cloneable, Predicate<Element> {
private String key;
private P predicate;
private final boolean testingIdString;
public HasContainer(final String key, final P<?> predicate) {
this.key = key;
this.predicate = predicate;
this.testingIdString = isStringTestable();
}
public final boolean test(final Element element) {
// it is OK to evaluate equality of ids via toString(), given that the test suite enforces the value of
// id().toString() to be a first class representation of the identifier. a string test is only executed
// if the predicate value is a String. this allows stuff like: g.V().has(id,lt(10)) to work properly
if (this.key != null) {
if (this.key.equals(T.id.getAccessor()))
return testingIdString ? testIdAsString(element) : testId(element);
if (this.key.equals(T.label.getAccessor()))
return testLabel(element);
}
final Iterator<? extends Property> itty = element.properties(this.key);
try {
while (itty.hasNext()) {
if (testValue(itty.next()))
return true;
}
} finally {
CloseableIterator.closeIterator(itty);
}
return false;
}
public final boolean test(final Property property) {
if (this.key != null) {
if (this.key.equals(T.value.getAccessor()))
return testValue(property);
if (this.key.equals(T.key.getAccessor()))
return testKey(property);
}
if (property instanceof Element)
return test((Element) property);
return false;
}
protected boolean testId(final Element element) {
return this.predicate.test(element.id());
}
protected boolean testIdAsString(final Element element) {
return this.predicate.test(element.id().toString());
}
protected boolean testLabel(final Element element) {
return this.predicate.test(element.label());
}
protected boolean testValue(final Property property) {
return this.predicate.test(property.value());
}
protected boolean testKey(final Property property) {
return this.predicate.test(property.key());
}
public final String toString() {
return Objects.toString(this.key) + '.' + this.predicate;
}
public HasContainer clone() {
try {
final HasContainer clone = (HasContainer) super.clone();
clone.predicate = this.predicate.clone();
return clone;
} catch (final CloneNotSupportedException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
@Override
public int hashCode() {
return (this.key != null ? this.key.hashCode() : 0) ^ (this.predicate != null ? this.predicate.hashCode() : 0);
}
public final String getKey() {
return this.key;
}
public final void setKey(final String key) {
this.key = key;
}
public final P<?> getPredicate() {
return this.predicate;
}
public final BiPredicate<?, ?> getBiPredicate() {
return this.predicate.getBiPredicate();
}
public final Object getValue() {
return this.predicate.getValue();
}
////////////
/**
* Determines if the value of the predicate is testable via {@code toString()} representation of an element which
* is only relevant if the has relates to an {@link T#id}.
*/
private boolean isStringTestable() {
if (this.key != null && this.key.equals(T.id.getAccessor())) {
final Object predicateValue = null == this.predicate ? null : this.predicate.getValue();
if (predicateValue instanceof Collection) {
final Collection collection = (Collection) predicateValue;
if (!collection.isEmpty()) {
return ((Collection) predicateValue).stream().allMatch(c -> null == c || c instanceof String);
}
}
return predicateValue instanceof String;
}
return false;
}
public static <V> boolean testAll(final Property<V> property, final List<HasContainer> hasContainers) {
return internalTestAll(property, hasContainers);
}
public static boolean testAll(final Element element, final List<HasContainer> hasContainers) {
return internalTestAll(element, hasContainers);
}
private static <S> boolean internalTestAll(final S element, final List<HasContainer> hasContainers) {
final boolean isProperty = element instanceof Property;
for (final HasContainer hasContainer : hasContainers) {
if (isProperty) {
if (!hasContainer.test((Property) element))
return false;
} else {
if (!hasContainer.test((Element) element))
return false;
}
}
return true;
}
}