package edu.uw.zookeeper.data; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Throwables; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import edu.uw.zookeeper.common.Pair; public final class ZNodeSchema { public static <E extends NameTrie.Node<E>> E matchPath(NameTrie<E> trie, ZNodePath path) { Iterator<ZNodeLabel> remaining = path.iterator(); E node = trie.root(); while (remaining.hasNext() && (node != null)) { ZNodeLabel next = remaining.next(); node = matchChild(node, next); } return node; } public static <E extends NameTrie.Node<E>> E matchChild(E node, ZNodeName name) { E child = node.get(name); if (child == null) { final String labelString = name.toString(); for (Map.Entry<ZNodeName, E> entry: node.entrySet()) { if (labelString.matches(entry.getKey().toString())) { child = entry.getValue(); break; } } } return child; } public static List<Acls.Acl> inheritedAcl(ValueNode<ZNodeSchema> node) { List<Acls.Acl> none = Acls.Definition.NONE.asList(); List<Acls.Acl> acl = node.get().getAcl(); if (none.equals(acl)) { Iterator<NameTrie.Pointer<? extends ValueNode<ZNodeSchema>>> itr = SimpleLabelTrie.parentIterator(node.parent()); while (itr.hasNext()) { ValueNode<ZNodeSchema> next = itr.next().get(); acl = next.get().getAcl(); if (! none.equals(acl)) { break; } } } return acl; } public static Iterator<DeclarationTraversal.Element> fromHierarchy(Class<?> root) { return new DeclarationTraversal(root); } public static final class DeclarationTraversal extends AbstractIterator<DeclarationTraversal.Element> { public static final class Element { private final ZNodePath path; private final ZNodeSchema.Builder builder; private final Object element; public Element(ZNodePath path, ZNodeSchema.Builder builder, Object element) { this.path = path; this.builder = builder; this.element = element; } public ZNodePath getPath() { return path; } public ZNodeSchema.Builder getBuilder() { return builder; } public Object getElement() { return element; } } protected final LinkedList<DeclarationTraversal.Element> pending; public DeclarationTraversal(Class<?> root) { this.pending = Lists.newLinkedList(); ZNodeSchema.Builder builder = Builder.fromClass(root); if (builder != null) { pending.add(new Element(RootZNodePath.getInstance(), builder, root)); } } @Override protected DeclarationTraversal.Element computeNext() { if (pending.isEmpty()) { return endOfData(); } else { DeclarationTraversal.Element next = pending.pop(); ZNodePath path = next.path; ZNodeSchema.Builder parent = next.builder; if (parent.getName().length() > 0) { path = path.join(ZNodeLabel.fromString(parent.getName())); } Object obj = parent.getDeclaration(); Class<?> type = (obj instanceof Class) ? (Class<?>)obj : obj.getClass(); Set<Field> fields = Sets.newHashSet(type.getFields()); fields.addAll(Arrays.asList(type.getFields())); for (Field f: fields) { ZNodeSchema.Builder builder = Builder.fromAnnotatedMember(f); if (builder != null) { pending.push(new Element(path, builder, f)); } } Set<Method> methods = Sets.newHashSet(type.getMethods()); methods.addAll(Arrays.asList(type.getMethods())); for (Method m: methods) { ZNodeSchema.Builder builder = Builder.fromAnnotatedMember(m); if (builder != null) { pending.push(new Element(path, builder, m)); } } for (Class<?> c: type.getDeclaredClasses()) { ZNodeSchema.Builder builder = Builder.fromClass(c); if (builder != null) { pending.push(new Element(path, builder, c)); } } return next; } } } public static final class Builder { public static ZNodeSchema.Builder fromDefault() { return fromSchema(DEFAULT); } public static ZNodeSchema.Builder fromSchema(ZNodeSchema schema) { return new Builder(schema.getDeclaration(), schema.getName(), schema.getNameType(), schema.getCreateMode(), schema.getAcl(), schema.getDataType()); } public static ZNodeSchema.Builder fromAnnotation(Object declaration, ZNode annotation) { String label = annotation.name(); NameType labelType = annotation.nameType(); CreateMode createMode = annotation.createMode(); List<Acls.Acl> acl = annotation.acl().asList(); Class<?> dataType = annotation.dataType(); return new Builder(declaration, label, labelType, createMode, acl, dataType); } public static ZNodeSchema.Builder fromAnnotated(AnnotatedElement element) { ZNode annotation = element.getAnnotation(ZNode.class); if (annotation == null) { return null; } ZNodeSchema.Builder builder = fromAnnotation(element, annotation); if (Void.class == builder.getDataType()) { if (element instanceof Field) { builder.setDataType(((Field) element).getType()); } else if (element instanceof Method) { builder.setDataType(((Method) element).getReturnType()); } } return builder; } public static ZNodeSchema.Builder fromClass(Class<?> cls) { ZNodeSchema.Builder builder = fromAnnotated(cls); if (builder == null) { return null; } Pair<Name, ? extends Member> memberName = null; // Method Name annotation overrides class and field Method[][] allMethods = {cls.getDeclaredMethods(), cls.getMethods()}; for (Method[] methods: allMethods) { if (memberName != null) { break; } for (Method m: methods) { Name annotation = m.getAnnotation(Name.class); if (annotation != null) { memberName = Pair.create(annotation, m); break; } } } // Field Name annotation overrides class Field[][] allFields = {cls.getDeclaredFields(), cls.getFields()}; for (Field[] fields: allFields) { if (memberName != null) { break; } for (Field f: fields) { Name annotation = f.getAnnotation(Name.class); if (annotation != null) { memberName = Pair.create(annotation, f); break; } } } if (memberName != null) { builder.setNameType(memberName.first().type()); Member member = memberName.second(); try { builder.setName( (member instanceof Method) ? (String) ((Method)member).invoke(cls) : ((Field)member).get(cls).toString()); } catch (Exception e) { throw Throwables.propagate(e); } } return builder; } public static <T extends Member & AnnotatedElement> ZNodeSchema.Builder fromAnnotatedMember(T member) { ZNodeSchema.Builder builder = fromAnnotated(member); if (builder == null) { return null; } if (builder.getName().length() == 0) { builder.setName(member.getName()); } return builder; } protected static final ZNodeSchema DEFAULT = new ZNodeSchema(null, "", NameType.NONE, CreateMode.PERSISTENT, Acls.Definition.NONE.asList(), Void.class); protected Object declaration; protected String name; protected NameType labelType; protected CreateMode createMode; protected List<Acls.Acl> acl; protected Class<?> dataType; public Builder( Object declaration, String name, NameType labelType, CreateMode createMode, List<Acls.Acl> acl, Class<?> dataType) { this.declaration = declaration; this.name = name; this.labelType = labelType; this.createMode = createMode; this.acl = acl; this.dataType = dataType; } public Object getDeclaration() { return declaration; } public ZNodeSchema.Builder setDeclaration(Object declaration) { this.declaration = declaration; return this; } public String getName() { return name; } public ZNodeSchema.Builder setName(String label) { this.name = label; return this; } public NameType getNameType() { return labelType; } public ZNodeSchema.Builder setNameType(NameType labelType) { this.labelType = labelType; return this; } public CreateMode getCreateMode() { return createMode; } public ZNodeSchema.Builder setCreateMode(CreateMode createMode) { this.createMode = createMode; return this; } public List<Acls.Acl> getAcl() { return acl; } public ZNodeSchema.Builder setAcl(List<Acls.Acl> acl) { this.acl = acl; return this; } public Class<?> getDataType() { return dataType; } public ZNodeSchema.Builder setDataType(Class<?> dataType) { this.dataType = dataType; return this; } public ZNodeSchema build() { return ZNodeSchema.create( getDeclaration(), getName(), getNameType(), getCreateMode(), getAcl(), getDataType()); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("declaration", getDeclaration()) .add("name", getName()) .add("nameType", getNameType()) .add("createMode", getCreateMode()) .add("acl", getAcl()) .add("dataType", getDataType()) .toString(); } } public static ZNodeSchema getDefault() { return Builder.DEFAULT; } public static ZNodeSchema create( Object declaration, String name, NameType nameType, CreateMode createMode, List<Acls.Acl> acl, Class<?> dataType) { return new ZNodeSchema(declaration, name, nameType, createMode, ImmutableList.copyOf(acl), dataType); } private final Object declaration; private final String name; private final NameType nameType; private final CreateMode createMode; private final List<Acls.Acl> acl; private final Class<?> dataType; protected ZNodeSchema( Object declaration, String name, NameType nameType, CreateMode createMode, List<Acls.Acl> acl, Class<?> dataType) { this.declaration = declaration; this.name = name; this.nameType = nameType; this.createMode = createMode; this.acl = acl; this.dataType = dataType; } public Object getDeclaration() { return declaration; } public String getName() { return name; } public NameType getNameType() { return nameType; } public CreateMode getCreateMode() { return createMode; } public List<Acls.Acl> getAcl() { return acl; } public Class<?> getDataType() { return dataType; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("declaration", getDeclaration()) .add("name", getName()) .add("nameType", getNameType()) .add("createMode", getCreateMode()) .add("acl", getAcl()) .add("dataType", getDataType()) .toString(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (! (obj instanceof ZNodeSchema)) { return false; } ZNodeSchema other = (ZNodeSchema) obj; return Objects.equal(getDeclaration(), other.getDeclaration()) && Objects.equal(getName(), other.getName()) && Objects.equal(getNameType(), other.getNameType()) && Objects.equal(getCreateMode(), other.getCreateMode()) && Objects.equal(getAcl(), other.getAcl()) && Objects.equal(getDataType(), other.getDataType()); } @Override public int hashCode() { return Objects.hashCode(getDeclaration(), getName()); } }