package sk.stuba.fiit.perconik.core;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import sk.stuba.fiit.perconik.core.annotations.Experimental;
import sk.stuba.fiit.perconik.core.annotations.Internal;
import sk.stuba.fiit.perconik.core.annotations.Persistent;
import sk.stuba.fiit.perconik.core.annotations.Unsafe;
import sk.stuba.fiit.perconik.core.annotations.Unsupported;
import sk.stuba.fiit.perconik.core.annotations.Version;
import sk.stuba.fiit.perconik.osgi.framework.Bundles;
import sk.stuba.fiit.perconik.utilities.reflect.Reflections;
import sk.stuba.fiit.perconik.utilities.reflect.annotation.Annotable;
import sk.stuba.fiit.perconik.utilities.reflect.annotation.Annotations;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.newArrayList;
/**
* Static helper methods pertaining to the core registrables.
*
* @author Pavol Zbell
* @since 1.0
*/
public final class Registrables {
private Registrables() {}
@Persistent
private enum PersistentMark {
}
private static final class VersionHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 0L;
private final String value;
VersionHandler(final String value) {
this.value = value;
}
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
String member = method.getName();
Class<?>[] parameters = method.getParameterTypes();
if (member.equals("equals") && parameters.length == 1 && parameters[0] == Object.class) {
Object o = args[0];
return (o == this) || (o instanceof Version && equal(this.value, ((Version) o).value()));
}
switch (member) {
case "hashCode":
return (127 * "value".hashCode()) ^ Objects.hashCode(this.value);
case "toString":
return "@" + Version.class.getName() + "(" + String.valueOf(this.value) + ")";
case "annotationType":
return Version.class;
case "value":
return this.value;
default:
throw new IncompleteAnnotationException(Version.class, member);
}
}
}
private static final Persistent persistent = PersistentMark.class.getAnnotation(Persistent.class);
private static <R extends Registrable> Version version(final Class<R> type) {
String value = Bundles.forClass(type).getVersion().toString();
if (isNullOrEmpty(value)) {
throw new IllegalArgumentException();
}
return (Version) Proxy.newProxyInstance(Version.class.getClassLoader(), new Class[] {Version.class}, new VersionHandler(value));
}
public static <R extends Registrable> Annotable toAnnotable(final Class<R> type) {
LinkedList<Class<? super R>> types = Reflections.collectSuperclasses(type);
types.addFirst(type);
List<Annotation> annotations = newArrayList(Annotations.ofClasses(types));
if (Serializable.class.isAssignableFrom(type)) {
annotations.add(persistent);
}
Annotable annotable = Annotations.asAnnotable(annotations);
if (annotable.hasAnnotation(Version.class)) {
return annotable;
}
annotations.add(version(type));
return Annotations.asAnnotable(annotations);
}
public static String getVersion(final Class<? extends Registrable> type) {
return firstNonNull(type.getAnnotation(Version.class), version(type)).value();
}
public static boolean isExperimental(final Class<? extends Registrable> type) {
return type.isAnnotationPresent(Experimental.class);
}
public static boolean isInternal(final Class<? extends Registrable> type) {
return type.isAnnotationPresent(Internal.class);
}
public static boolean isPersistent(final Class<? extends Registrable> type) {
return type.isAnnotationPresent(Persistent.class);
}
public static boolean isUnsafe(final Class<? extends Registrable> type) {
return type.isAnnotationPresent(Unsafe.class);
}
public static boolean isUnsupported(final Class<? extends Registrable> type) {
return type.isAnnotationPresent(Unsupported.class);
}
}