/** * 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.avro.io.parsing; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.avro.Schema; import org.apache.avro.Schema.Field; /** * The class that generates validating grammar. */ public class ValidatingGrammarGenerator { /** * Returns the non-terminal that is the start symbol * for the grammar for the given schema <tt>sc</tt>. */ public Symbol generate(Schema schema) { return Symbol.root(generate(schema, new HashMap<LitS, Symbol>())); } /** * Returns the non-terminal that is the start symbol * for the grammar for the given schema <tt>sc</tt>. If there is already an entry * for the given schema in the given map <tt>seen</tt> then * that entry is returned. Otherwise a new symbol is generated and * an entry is inserted into the map. * @param sc The schema for which the start symbol is required * @param seen A map of schema to symbol mapping done so far. * @return The start symbol for the schema */ public Symbol generate(Schema sc, Map<LitS, Symbol> seen) { switch (sc.getType()) { case NULL: return Symbol.NULL; case BOOLEAN: return Symbol.BOOLEAN; case INT: return Symbol.INT; case LONG: return Symbol.LONG; case FLOAT: return Symbol.FLOAT; case DOUBLE: return Symbol.DOUBLE; case STRING: return Symbol.STRING; case BYTES: return Symbol.BYTES; case FIXED: return Symbol.seq(Symbol.intCheckAction(sc.getFixedSize()), Symbol.FIXED); case ENUM: return Symbol.seq(Symbol.intCheckAction(sc.getEnumSymbols().size()), Symbol.ENUM); case ARRAY: return Symbol.seq(Symbol.repeat(Symbol.ARRAY_END, generate(sc.getElementType(), seen)), Symbol.ARRAY_START); case MAP: return Symbol.seq(Symbol.repeat(Symbol.MAP_END, generate(sc.getValueType(), seen), Symbol.STRING), Symbol.MAP_START); case RECORD: { LitS wsc = new LitS(sc); Symbol rresult = seen.get(wsc); if (rresult == null) { Symbol[] production = new Symbol[sc.getFields().size()]; /** * We construct a symbol without filling the array. Please see * {@link Symbol#production} for the reason. */ rresult = Symbol.seq(production); seen.put(wsc, rresult); int i = production.length; for (Field f : sc.getFields()) { production[--i] = generate(f.schema(), seen); } } return rresult; } case UNION: List<Schema> subs = sc.getTypes(); Symbol[] symbols = new Symbol[subs.size()]; String[] labels = new String[subs.size()]; int i = 0; for (Schema b : sc.getTypes()) { symbols[i] = generate(b, seen); labels[i] = b.getFullName(); i++; } return Symbol.seq(Symbol.alt(symbols, labels), Symbol.UNION); default: throw new RuntimeException("Unexpected schema type"); } } /** A wrapper around Schema that does "==" equality. */ static class LitS { public final Schema actual; public LitS(Schema actual) { this.actual = actual; } /** * Two LitS are equal if and only if their underlying schema is * the same (not merely equal). */ public boolean equals(Object o) { if (! (o instanceof LitS)) return false; return actual == ((LitS)o).actual; } public int hashCode() { return actual.hashCode(); } } }