package jalse.entities.functions;
import static jalse.entities.Entities.isEntityOrSubtype;
import static jalse.entities.functions.Functions.checkHasReturnType;
import static jalse.entities.functions.Functions.checkNotDefault;
import static jalse.entities.functions.Functions.firstGenericTypeArg;
import static jalse.entities.functions.Functions.getSingleIDSupplier;
import static jalse.entities.functions.Functions.returnTypeIs;
import static jalse.entities.functions.Functions.toClass;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import jalse.entities.DefaultEntityProxyFactory;
import jalse.entities.Entity;
import jalse.entities.EntityContainer;
import jalse.entities.annotations.EntityID;
import jalse.entities.annotations.GetEntity;
import jalse.entities.methods.GetEntityMethod;
/**
* This is a method function for {@link GetEntity} annotation. It will resolve an
* {@link GetEntityMethod} to be used by the entity typing system.<br>
* <br>
* The next example signatures will resolve to {@link EntityContainer#getEntity(UUID)()}.
*
* <pre>
* <code>
* {@code @GetEntity}
* Entity getGhost(UUID id);
*
* {@code @EntityID}
* {@code @GetEntity}
* Entity getGhost();
* </code>
* </pre>
*
* The next example signatures will resolve to {@link EntityContainer#getOptEntity(UUID)}.
*
* <pre>
* <code>
* {@code @GetEntity}
* {@code Optional<Entity>} getGhost(UUID id);
*
* {@code @EntityID}
* {@code @GetEntity}
* {@code Optional<Entity>} getGhost();
* </code>
* </pre>
*
* The next example signatures will resolve to {@link EntityContainer#getEntityAsType(UUID, Class)}.
*
* <pre>
* <code>
* {@code @GetEntity}
* Ghost getGhost(UUID id);
*
* {@code @EntityID}
* {@code @GetEntity}
* Ghost getGhost();
* </code>
* </pre>
*
* The next example signatures will resolve to
* {@link EntityContainer#getOptEntityAsType(UUID, Class)}.
*
* <pre>
* <code>
* {@code @GetEntity}
* {@code Optional<Ghost>} getGhost(UUID id);
*
* {@code @EntityID}
* {@code @GetEntity}
* {@code Optional<Ghost>} getGhost();
* </code>
* </pre>
*
* NOTE: This function will throw exceptions if {@link GetEntity} is present but the method
* signature is invalid.
*
* @author Elliot Ford
*
* @see DefaultEntityProxyFactory
*
*/
public class GetEntityFunction implements EntityMethodFunction {
@SuppressWarnings("unchecked")
@Override
public GetEntityMethod apply(final Method m) {
// Check for annotation
final GetEntity annonation = m.getAnnotation(GetEntity.class);
if (annonation == null) {
return null;
}
// Basic check method signature
checkHasReturnType(m);
checkNotDefault(m);
if (m.getParameterCount() > 1) {
throw new IllegalArgumentException("Cannot have over one param");
}
// Get and validate ID
final Supplier<UUID> idSupplier = getSingleIDSupplier(m);
// Check ID param
final Class<?>[] params = m.getParameterTypes();
final boolean idParam = params.length == 1;
if (idParam && !UUID.class.equals(params[0])) {
throw new IllegalArgumentException("Can only have ID parameter");
}
// Check duplicate ID definitions
if (idParam && idSupplier != null) {
throw new IllegalArgumentException(String.format("Cannot have %s annotation and ID param", EntityID.class));
}
// Check ID
if (!idParam && idSupplier == null) {
throw new IllegalArgumentException("Must provide ID via param or annotation");
}
Type entityType = m.getGenericReturnType();
// Check is optional
final boolean optional = returnTypeIs(m, Optional.class);
if (optional) {
entityType = firstGenericTypeArg(entityType);
}
// Check entity
if (!isEntityOrSubtype(toClass(entityType))) {
throw new IllegalArgumentException("Entity must be obtainable from return type");
}
// Create get entity method
final GetEntityMethod gem = new GetEntityMethod((Class<? extends Entity>) entityType, optional);
if (!idParam) {
gem.setIDSupplier(idSupplier);
}
return gem;
}
}