| /** |
| * 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; |
| |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| |
| import org.apache.avro.AvroTypeException; |
| import org.apache.avro.Schema; |
| import org.apache.avro.io.parsing.Parser; |
| import org.apache.avro.io.parsing.Symbol; |
| import org.apache.avro.io.parsing.ValidatingGrammarGenerator; |
| import org.apache.avro.util.Utf8; |
| |
| /** |
| * An implementation of {@link Decoder} that ensures that the sequence |
| * of operations conforms to a schema. |
| * <p/> |
| * Use {@link DecoderFactory#validatingDecoder(Schema, Decoder)} to construct |
| * and configure. |
| * <p/> |
| * ValidatingDecoder is not thread-safe. |
| * @see Decoder |
| * @see DecoderFactory |
| */ |
| public class ValidatingDecoder extends ParsingDecoder |
| implements Parser.ActionHandler { |
| protected Decoder in; |
| |
| ValidatingDecoder(Symbol root, Decoder in) throws IOException { |
| super(root); |
| this.configure(in); |
| } |
| |
| ValidatingDecoder(Schema schema, Decoder in) throws IOException { |
| this(getSymbol(schema), in); |
| } |
| |
| private static Symbol getSymbol(Schema schema) { |
| if (null == schema) { |
| throw new NullPointerException("Schema cannot be null"); |
| } |
| return new ValidatingGrammarGenerator().generate(schema); |
| } |
| |
| /** Re-initialize, reading from a new underlying Decoder. */ |
| public ValidatingDecoder configure(Decoder in) throws IOException { |
| this.parser.reset(); |
| this.in = in; |
| return this; |
| } |
| |
| @Override |
| public void readNull() throws IOException { |
| parser.advance(Symbol.NULL); |
| in.readNull(); |
| } |
| |
| @Override |
| public boolean readBoolean() throws IOException { |
| parser.advance(Symbol.BOOLEAN); |
| return in.readBoolean(); |
| } |
| |
| @Override |
| public int readInt() throws IOException { |
| parser.advance(Symbol.INT); |
| return in.readInt(); |
| } |
| |
| @Override |
| public long readLong() throws IOException { |
| parser.advance(Symbol.LONG); |
| return in.readLong(); |
| } |
| |
| @Override |
| public float readFloat() throws IOException { |
| parser.advance(Symbol.FLOAT); |
| return in.readFloat(); |
| } |
| |
| @Override |
| public double readDouble() throws IOException { |
| parser.advance(Symbol.DOUBLE); |
| return in.readDouble(); |
| } |
| |
| @Override |
| public Utf8 readString(Utf8 old) throws IOException { |
| parser.advance(Symbol.STRING); |
| return in.readString(old); |
| } |
| |
| @Override |
| public String readString() throws IOException { |
| parser.advance(Symbol.STRING); |
| return in.readString(); |
| } |
| |
| @Override |
| public void skipString() throws IOException { |
| parser.advance(Symbol.STRING); |
| in.skipString(); |
| } |
| |
| @Override |
| public ByteBuffer readBytes(ByteBuffer old) throws IOException { |
| parser.advance(Symbol.BYTES); |
| return in.readBytes(old); |
| } |
| |
| @Override |
| public void skipBytes() throws IOException { |
| parser.advance(Symbol.BYTES); |
| in.skipBytes(); |
| } |
| |
| private void checkFixed(int size) throws IOException { |
| parser.advance(Symbol.FIXED); |
| Symbol.IntCheckAction top = (Symbol.IntCheckAction) parser.popSymbol(); |
| if (size != top.size) { |
| throw new AvroTypeException( |
| "Incorrect length for fixed binary: expected " + |
| top.size + " but received " + size + " bytes."); |
| } |
| } |
| |
| @Override |
| public void readFixed(byte[] bytes, int start, int len) throws IOException { |
| checkFixed(len); |
| in.readFixed(bytes, start, len); |
| } |
| |
| @Override |
| public void skipFixed(int length) throws IOException { |
| checkFixed(length); |
| in.skipFixed(length); |
| } |
| |
| @Override |
| protected void skipFixed() throws IOException { |
| parser.advance(Symbol.FIXED); |
| Symbol.IntCheckAction top = (Symbol.IntCheckAction) parser.popSymbol(); |
| in.skipFixed(top.size); |
| } |
| |
| @Override |
| public int readEnum() throws IOException { |
| parser.advance(Symbol.ENUM); |
| Symbol.IntCheckAction top = (Symbol.IntCheckAction) parser.popSymbol(); |
| int result = in.readEnum(); |
| if (result < 0 || result >= top.size) { |
| throw new AvroTypeException( |
| "Enumeration out of range: max is " + |
| top.size + " but received " + result); |
| } |
| return result; |
| } |
| |
| @Override |
| public long readArrayStart() throws IOException { |
| parser.advance(Symbol.ARRAY_START); |
| long result = in.readArrayStart(); |
| if (result == 0) { |
| parser.advance(Symbol.ARRAY_END); |
| } |
| return result; |
| } |
| |
| @Override |
| public long arrayNext() throws IOException { |
| parser.processTrailingImplicitActions(); |
| long result = in.arrayNext(); |
| if (result == 0) { |
| parser.advance(Symbol.ARRAY_END); |
| } |
| return result; |
| } |
| |
| @Override |
| public long skipArray() throws IOException { |
| parser.advance(Symbol.ARRAY_START); |
| for (long c = in.skipArray(); c != 0; c = in.skipArray()) { |
| while (c-- > 0) { |
| parser.skipRepeater(); |
| } |
| } |
| parser.advance(Symbol.ARRAY_END); |
| return 0; |
| } |
| |
| @Override |
| public long readMapStart() throws IOException { |
| parser.advance(Symbol.MAP_START); |
| long result = in.readMapStart(); |
| if (result == 0) { |
| parser.advance(Symbol.MAP_END); |
| } |
| return result; |
| } |
| |
| @Override |
| public long mapNext() throws IOException { |
| parser.processTrailingImplicitActions(); |
| long result = in.mapNext(); |
| if (result == 0) { |
| parser.advance(Symbol.MAP_END); |
| } |
| return result; |
| } |
| |
| @Override |
| public long skipMap() throws IOException { |
| parser.advance(Symbol.MAP_START); |
| for (long c = in.skipMap(); c != 0; c = in.skipMap()) { |
| while (c-- > 0) { |
| parser.skipRepeater(); |
| } |
| } |
| parser.advance(Symbol.MAP_END); |
| return 0; |
| } |
| |
| @Override |
| public int readIndex() throws IOException { |
| parser.advance(Symbol.UNION); |
| Symbol.Alternative top = (Symbol.Alternative) parser.popSymbol(); |
| int result = in.readIndex(); |
| parser.pushSymbol(top.getSymbol(result)); |
| return result; |
| } |
| |
| @Override |
| public Symbol doAction(Symbol input, Symbol top) throws IOException { |
| return null; |
| } |
| } |
| |