blob: e5ea3e3a38bbd3e91e0f3df45c4bafa2cd51bc7d [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.structure.io.binary.types;
import org.apache.tinkerpop.gremlin.structure.io.binary.DataType;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader;
import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP;
import org.apache.tinkerpop.gremlin.structure.io.Buffer;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Stephen Mallette (http://stephen.genoprime.com)
*/
public class PSerializer<T extends P> extends SimpleTypeSerializer<T> {
private final Class<T> classOfP;
private final ConcurrentHashMap<PFunctionId, CheckedFunction> methods = new ConcurrentHashMap<>();
public PSerializer(final DataType typeOfP, final Class<T> classOfP) {
super(typeOfP);
this.classOfP = classOfP;
}
@Override
protected T readValue(final Buffer buffer, final GraphBinaryReader context) throws IOException {
final String predicateName = context.readValue(buffer, String.class, false);
final int length = context.readValue(buffer, Integer.class, false);
final Object[] args = new Object[length];
final Class<?>[] argumentClasses = new Class[length];
for (int i = 0; i < length; i++) {
args[i] = context.read(buffer);
argumentClasses[i] = args[i].getClass();
}
if ("and".equals(predicateName))
return (T) ((P) args[0]).and((P) args[1]);
else if ("or".equals(predicateName))
return (T) ((P) args[0]).or((P) args[1]);
else if ("not".equals(predicateName))
return (T) P.not((P) args[0]);
final CheckedFunction<Object[], T> f = getMethod(predicateName, argumentClasses);
try {
return f.apply(args);
} catch (Exception ex) {
throw new IOException(String.format("Can't deserialize value into the predicate: '%s'", predicateName), ex);
}
}
private CheckedFunction getMethod(final String predicateName, final Class<?>[] argumentClasses) throws IOException {
final PFunctionId id = new PFunctionId(predicateName, argumentClasses);
CheckedFunction<Object[], T> result = methods.get(id);
if (result == null) {
boolean collectionType = false;
Method m;
try {
// do a direct lookup
m = classOfP.getMethod(predicateName, argumentClasses);
} catch (NoSuchMethodException ex0) {
// then try collection types
try {
m = classOfP.getMethod(predicateName, Collection.class);
collectionType = true;
} catch (NoSuchMethodException ex1) {
// finally go for the generics
try {
m = classOfP.getMethod(predicateName, Object.class);
} catch (NoSuchMethodException ex2) {
// finally go for the generics
try {
m = classOfP.getMethod(predicateName, Object.class, Object.class);
} catch (NoSuchMethodException ex3) {
throw new IOException(String.format("Can't find predicate method: '%s'", predicateName), ex2);
}
}
}
}
final Method finalMethod = m;
try {
if (Modifier.isStatic(m.getModifiers())) {
if (collectionType) {
result = (args) -> (T) finalMethod.invoke(null, Arrays.asList(args));
} else {
result = (args) -> (T) finalMethod.invoke(null, args);
}
} else {
// try an instance method as it might be a form of ConnectiveP which means there is a P as an
// argument that should be used as the object of an instance method
if (argumentClasses.length != 2) {
throw new IllegalStateException(String.format("Could not determine the form of P for %s and %s",
predicateName, Arrays.asList(argumentClasses)));
}
result = (args) -> {
if (!(args[0] instanceof P) || !(args[1] instanceof P))
throw new IllegalStateException(String.format("Could not determine the form of P for %s and %s",
predicateName, Arrays.asList(args)));
final P firstP = (P) args[0];
final P secondP = (P) args[1];
return (T) finalMethod.invoke(firstP, secondP);
};
}
methods.put(id, result);
} catch (Exception ex) {
throw new IOException(ex);
}
}
return result;
}
@Override
protected void writeValue(final T value, final Buffer buffer, final GraphBinaryWriter context) throws IOException {
// the predicate name is either a static method of P or an instance method when a type ConnectiveP
final boolean isConnectedP = value instanceof ConnectiveP;
final String predicateName = isConnectedP ?
(value instanceof AndP ? "and" : "or") :
value.getBiPredicate().toString();
final Object args = isConnectedP ? ((ConnectiveP<?>) value).getPredicates() : value.getValue();
final List<Object> argsAsList = args instanceof Collection ? new ArrayList<>((Collection) args) : Collections.singletonList(args);
final int length = argsAsList.size();
context.writeValue(predicateName, buffer, false);
context.writeValue(length, buffer, false);
for (Object o : argsAsList) {
context.write(o, buffer);
}
}
@FunctionalInterface
interface CheckedFunction<A, R> {
R apply(A t) throws InvocationTargetException, IllegalAccessException;
}
class PFunctionId {
private final String predicateName;
private final Class<?>[] argumentClasses;
PFunctionId(final String predicateName, final Class<?>[] argumentClasses) {
this.predicateName = predicateName;
this.argumentClasses = argumentClasses;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PFunctionId that = (PFunctionId) o;
return predicateName.equals(that.predicateName) &&
Arrays.equals(argumentClasses, that.argumentClasses);
}
@Override
public int hashCode() {
int result = Objects.hash(predicateName);
result = 31 * result + Arrays.hashCode(argumentClasses);
return result;
}
}
}