package org.apache.avro.compiler.schema;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.avro.JsonProperties;
import org.apache.avro.LogicalType;
import org.apache.avro.Schema;
import org.apache.avro.Schema.Field;
import org.apache.avro.compiler.specific.SpecificCompiler;
/**
* Avro Schema utilities, to traverse...
*/
public final class Schemas {
private Schemas() {
}
public static void copyAliases(final Schema from, final Schema to) {
switch (from.getType()) { // only named types.
case RECORD:
case ENUM:
case FIXED:
Set<String> aliases = from.getAliases();
for (String alias : aliases) {
to.addAlias(alias);
}
}
}
public static void copyAliases(final Schema.Field from, final Schema.Field to) {
Set<String> aliases = from.aliases();
for (String alias : aliases) {
to.addAlias(alias);
}
}
public static void copyLogicalTypes(final Schema from, final Schema to) {
LogicalType logicalType = from.getLogicalType();
if (logicalType != null) {
logicalType.addToSchema(to);
}
}
public static void copyProperties(final JsonProperties from, final JsonProperties to) {
Map<String, Object> objectProps = from.getObjectProps();
for (Map.Entry<String, Object> entry : objectProps.entrySet()) {
to.addProp(entry.getKey(), entry.getValue());
}
}
public static boolean hasGeneratedJavaClass(final Schema schema) {
Schema.Type type = schema.getType();
switch (type) {
case ENUM:
case RECORD:
case FIXED:
return true;
default:
return false;
}
}
public static String getJavaClassName(final Schema schema) {
String namespace = schema.getNamespace();
if (namespace == null) {
return SpecificCompiler.mangle(schema.getName());
} else {
return namespace + '.' + SpecificCompiler.mangle(schema.getName());
}
}
/**
* depth first visit.
*
* @param start
* @param visitor
*/
public static <T> T visit(final Schema start, final SchemaVisitor<T> visitor) {
// Set of Visited Schemas
IdentityHashMap<Schema, Schema> visited = new IdentityHashMap<Schema, Schema>();
// Stack that contains the Schams to process and afterVisitNonTerminal functions.
// Deque<Either<Schema, Supplier<SchemaVisitorAction>>>
// Using either has a cost which we want to avoid...
Deque<Object> dq = new ArrayDeque<Object>();
dq.addLast(start);
Object current;
while ((current = dq.pollLast()) != null) {
if (current instanceof Supplier) {
// we are executing a non terminal post visit.
SchemaVisitorAction action = ((Supplier<SchemaVisitorAction>) current).get();
switch (action) {
case CONTINUE:
break;
case SKIP_SUBTREE:
throw new UnsupportedOperationException();
case SKIP_SIBLINGS:
while (dq.getLast() instanceof Schema) {
dq.removeLast();
}
break;
case TERMINATE:
return visitor.get();
default:
throw new UnsupportedOperationException("Invalid action " + action);
}
} else {
Schema schema = (Schema) current;
boolean terminate;
if (!visited.containsKey(schema)) {
Schema.Type type = schema.getType();
switch (type) {
case ARRAY:
terminate = visitNonTerminal(visitor, schema, dq, Arrays.asList(schema.getElementType()));
visited.put(schema, schema);
break;
case RECORD:
terminate = visitNonTerminal(visitor, schema, dq,
Lists.transform(Lists.reverse(schema.getFields()), new Function<Field, Schema>() {
@Override
public Schema apply(Field f) {
return f.schema();
}
}));
visited.put(schema, schema);
break;
case UNION:
terminate = visitNonTerminal(visitor, schema, dq, schema.getTypes());
visited.put(schema, schema);
break;
case MAP:
terminate = visitNonTerminal(visitor, schema, dq, Arrays.asList(schema.getValueType()));
visited.put(schema, schema);
break;
case NULL:
case BOOLEAN:
case BYTES:
case DOUBLE:
case ENUM:
case FIXED:
case FLOAT:
case INT:
case LONG:
case STRING:
terminate = visitTerminal(visitor, schema, dq);
break;
default:
throw new UnsupportedOperationException("Invalid type " + type);
}
} else {
terminate = visitTerminal(visitor, schema, dq);
}
if (terminate) {
return visitor.get();
}
}
}
return visitor.get();
}
private static boolean visitNonTerminal(final SchemaVisitor visitor,
final Schema schema, final Deque<Object> dq,
final Iterable<Schema> itSupp) {
SchemaVisitorAction action = visitor.visitNonTerminal(schema);
switch (action) {
case CONTINUE:
dq.addLast(new Supplier<SchemaVisitorAction>() {
@Override
public SchemaVisitorAction get() {
return visitor.afterVisitNonTerminal(schema);
}
});
Iterator<Schema> it = itSupp.iterator();
while (it.hasNext()) {
Schema child = it.next();
dq.addLast(child);
}
break;
case SKIP_SUBTREE:
dq.addLast(new Supplier<SchemaVisitorAction>() {
@Override
public SchemaVisitorAction get() {
return visitor.afterVisitNonTerminal(schema);
}
});
break;
case SKIP_SIBLINGS:
while (!dq.isEmpty() && dq.getLast() instanceof Schema) {
dq.removeLast();
}
break;
case TERMINATE:
return true;
default:
throw new UnsupportedOperationException("Invalid action " + action + " for " + schema);
}
return false;
}
private static boolean visitTerminal(final SchemaVisitor visitor, final Schema schema,
final Deque<Object> dq) {
SchemaVisitorAction action = visitor.visitTerminal(schema);
switch (action) {
case CONTINUE:
break;
case SKIP_SUBTREE:
throw new UnsupportedOperationException("Invalid action " + action + " for " + schema);
case SKIP_SIBLINGS:
while (!dq.isEmpty() && dq.getLast() instanceof Schema) {
dq.removeLast();
}
break;
case TERMINATE:
return true;
default:
throw new UnsupportedOperationException("Invalid action " + action + " for " + schema);
}
return false;
}
}