ConstValueProvider.java
- /*
- * Copyright 2016 Providence Authors
- *
- * 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 net.morimekta.providence.reflect.util;
- import net.morimekta.providence.PEnumBuilder;
- import net.morimekta.providence.PEnumValue;
- import net.morimekta.providence.PMessage;
- import net.morimekta.providence.PMessageBuilder;
- import net.morimekta.providence.PType;
- import net.morimekta.providence.descriptor.PDeclaredDescriptor;
- import net.morimekta.providence.descriptor.PDescriptor;
- import net.morimekta.providence.descriptor.PEnumDescriptor;
- import net.morimekta.providence.descriptor.PField;
- import net.morimekta.providence.descriptor.PList;
- import net.morimekta.providence.descriptor.PMap;
- import net.morimekta.providence.descriptor.PMessageDescriptor;
- import net.morimekta.providence.descriptor.PSet;
- import net.morimekta.providence.descriptor.PValueProvider;
- import net.morimekta.providence.reflect.parser.ThriftException;
- import net.morimekta.providence.reflect.parser.ThriftLexer;
- import net.morimekta.providence.reflect.parser.ThriftToken;
- import net.morimekta.providence.reflect.parser.ThriftTokenType;
- import net.morimekta.providence.types.TypeReference;
- import net.morimekta.providence.types.TypeRegistry;
- import net.morimekta.util.Binary;
- import net.morimekta.util.Strings;
- import net.morimekta.util.collect.UnmodifiableList;
- import net.morimekta.util.collect.UnmodifiableMap;
- import net.morimekta.util.collect.UnmodifiableSet;
- import net.morimekta.util.json.JsonException;
- import net.morimekta.util.json.JsonToken;
- import net.morimekta.util.json.JsonTokenizer;
- import net.morimekta.util.lexer.LexerException;
- import net.morimekta.util.lexer.Tokenizer;
- import net.morimekta.util.lexer.TokenizerRepeater;
- import net.morimekta.util.lexer.UncheckedLexerException;
- import javax.annotation.Nonnull;
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.io.UncheckedIOException;
- import java.util.Collections;
- import java.util.List;
- import java.util.Optional;
- import java.util.concurrent.atomic.AtomicReference;
- import static java.nio.charset.StandardCharsets.US_ASCII;
- import static net.morimekta.providence.reflect.parser.ThriftTokenizer.kNull;
- import static net.morimekta.providence.types.TypeReference.parseType;
- import static net.morimekta.providence.util.MessageUtil.coerceStrict;
- /**
- * A value provider for thrift constants.
- */
- public class ConstValueProvider implements PValueProvider<Object> {
- private final TypeRegistry registry;
- private final String programName;
- private final TypeReference constType;
- private final List<ThriftToken> constTokens;
- private AtomicReference<Object> parsedValue;
- public ConstValueProvider(@Nonnull TypeRegistry registry,
- @Nonnull String programName,
- @Nonnull TypeReference constType,
- @Nonnull List<ThriftToken> constTokens) {
- this.registry = registry;
- this.programName = programName;
- this.constType = constType;
- this.constTokens = constTokens;
- this.parsedValue = null;
- }
- @Override
- public Object get() {
- if (parsedValue == null) {
- PDescriptor type = registry
- .getTypeProvider(constType, Collections.emptyMap())
- .descriptor();
- Tokenizer<ThriftTokenType, ThriftToken> tokenizer = new TokenizerRepeater<>(constTokens);
- ThriftLexer lexer = new ThriftLexer(tokenizer);
- try {
- parsedValue = new AtomicReference<>(
- parseTypedValue(lexer.expect("const value"), lexer, type, true));
- } catch (LexerException e) {
- throw new UncheckedLexerException(e);
- } catch (IOException e) {
- throw new UncheckedIOException(e.getMessage(), e);
- }
- }
- return parsedValue.get();
- }
- /**
- * Parse JSON object as a message.
- *
- * @param lexer The thrift lexer.
- * @param type The message type.
- * @param <Message> Message generic type.
- * @return The parsed message.
- */
- private <Message extends PMessage<Message>>
- Message parseMessage(ThriftLexer lexer,
- PMessageDescriptor<Message> type) throws IOException {
- PMessageBuilder<Message> builder = type.builder();
- ThriftToken token = lexer.expect("message field or end");
- while (!token.isSymbol(ThriftToken.kMessageEnd)) {
- if (token.type() != ThriftTokenType.STRING) {
- throw lexer.failure(token, "Invalid field name token");
- }
- PField<?> field = type.findFieldByName(token.decodeString(true));
- if (field == null) {
- throw lexer.failure(
- token, "No such field in %s: %s",
- type.getQualifiedName(), token.decodeString(true));
- }
- lexer.expectSymbol("message key-value sep", ThriftToken.kKeyValueSep);
- builder.set(field.getId(),
- parseTypedValue(lexer.expect("parsing field value"),
- lexer,
- field.getDescriptor(),
- false));
- token = lexer.expect("message sep, field or end");
- if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
- token = lexer.expect("message field or end");
- }
- }
- return builder.build();
- }
- private Object parseTypedValue(ThriftToken token,
- ThriftLexer lexer,
- PDescriptor valueType,
- boolean allowNull)
- throws IOException {
- // Enum.VALUE
- // program.Enum.VALUE
- if (token.isQualifiedIdentifier() ||
- token.isDoubleQualifiedIdentifier()) {
- // Possible enum value. First strip after last '.' to get enum type name.
- String typeName = token.toString().replaceAll("\\.[^.]+$", "");
- Optional<PDeclaredDescriptor<?>> desc;
- if (typeName.equals(valueType.getName()) &&
- valueType.getType() == PType.ENUM) {
- desc = Optional.of((PDeclaredDescriptor<?>) valueType);
- } else {
- TypeReference ref = parseType(this.programName, typeName);
- desc = registry.getDeclaredType(ref);
- }
- if (desc.isPresent()) {
- if (!(desc.get() instanceof PEnumDescriptor)) {
- throw new IllegalArgumentException("Not an enum type " + desc.get().getQualifiedName());
- }
- String valueName = token.toString().replaceAll("^.*\\.", "");
- PEnumValue<?> value = ((PEnumDescriptor<?>) desc.get()).findByName(valueName);
- if (value != null) {
- return coerceStrict(valueType, value).orElseThrow(() -> new IllegalArgumentException("Non-matching enum value"));
- } else if (allowNull) {
- return null;
- } else {
- throw new IllegalArgumentException("No such " + desc.get().getQualifiedName() + " value " + valueName);
- }
- }
- }
- // kConstName
- // program.kConstName
- if (token.isIdentifier() || token.isQualifiedIdentifier()) {
- if (kNull.equals(token.toString())) {
- if (allowNull) {
- return null;
- }
- }
- Optional<Object> optional = registry.getConstantValue(parseType(this.programName, token.toString()));
- if (optional.isPresent()) {
- return coerceStrict(valueType, optional.get()).orElse(null);
- }
- }
- // Direct value.
- switch (valueType.getType()) {
- case BOOL:
- if (token.isIdentifier()) {
- return Boolean.parseBoolean(token.toString());
- } else if (token.isInteger()) {
- return token.parseInteger() != 0L;
- }
- throw lexer.failure(token, "Not boolean value: %s", token.toString());
- case BYTE:
- if (token.isInteger()) {
- return (byte) token.parseInteger();
- }
- return (byte) findEnumValue(token.toString(), token, lexer, "byte");
- case I16:
- if (token.isInteger()) {
- return (short) token.parseInteger();
- }
- return (short) findEnumValue(token.toString(), token, lexer, "i16");
- case I32:
- if (token.isInteger()) {
- return (int) token.parseInteger();
- }
- return findEnumValue(token.toString(), token, lexer, "i32");
- case I64:
- if (token.isInteger()) {
- return token.parseInteger();
- }
- return (long) findEnumValue(token.toString(), token, lexer, "i64");
- case DOUBLE:
- if (token.type() == ThriftTokenType.NUMBER) {
- return token.parseDouble();
- }
- throw lexer.failure(token, token + " is not a valid double value.");
- case STRING:
- if (token.type() == ThriftTokenType.STRING) {
- return token.decodeString(true);
- } else if (allowNull && token.toString().equals(kNull)) {
- return null;
- }
- throw lexer.failure(token, "Not a valid string value.");
- case BINARY:
- if (token.type() == ThriftTokenType.STRING) {
- return parseBinary(token.substring(1, -1)
- .toString());
- } else if (allowNull && token.toString().equals(kNull)) {
- return null;
- }
- throw lexer.failure(token, "Not a valid binary value.");
- case ENUM: {
- // Enum reference already handled.
- PEnumBuilder<?> eb = ((PEnumDescriptor<?>) valueType).builder();
- String name = token.toString();
- if (Strings.isInteger(name)) {
- Object ev = eb.setById(Integer.parseInt(name)).build();
- if (ev == null) {
- if (allowNull && token.toString().equals(kNull)) {
- return null;
- }
- throw lexer.failure(token,
- "No such " + valueType.getQualifiedName() + " enum value \"" + name);
- }
- return ev;
- }
- throw lexer.failure(token, "Not valid enum reference '" + name + "'");
- }
- case MESSAGE: {
- if (token.isSymbol(ThriftToken.kMessageStart)) {
- return parseMessage(lexer, (PMessageDescriptor<?>) valueType);
- } else if (allowNull && token.toString().equals(kNull)) {
- // messages can be null values in constants.
- return null;
- }
- throw lexer.failure(token, "Not a valid message start.");
- }
- case LIST: {
- PDescriptor itemType = ((PList<?>) valueType).itemDescriptor();
- UnmodifiableList.Builder<Object> list = UnmodifiableList.builder();
- if (!token.isSymbol(ThriftToken.kListStart)) {
- throw lexer.failure(token, "Expected list start, found " + token.toString());
- }
- token = lexer.expect("list item or end");
- while (!token.isSymbol(ThriftToken.kListEnd)) {
- list.add(parseTypedValue(token, lexer, itemType, false));
- token = lexer.expect("list item, sep or end");
- if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
- token = lexer.expect("list item or end");
- }
- }
- return list.build();
- }
- case SET: {
- PDescriptor itemType = ((PSet<?>) valueType).itemDescriptor();
- UnmodifiableSet.Builder<Object> set = UnmodifiableSet.builder();
- if (!token.isSymbol(ThriftToken.kListStart)) {
- throw lexer.failure(token, "Expected list start, found " + token.toString());
- }
- if (!token.isSymbol(ThriftToken.kListStart)) {
- throw lexer.failure(token, "Expected list start, found " + token.toString());
- }
- token = lexer.expect("list item or end");
- while (!token.isSymbol(ThriftToken.kListEnd)) {
- set.add(parseTypedValue(token, lexer, itemType, false));
- token = lexer.expect("list item, sep or end");
- if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
- token = lexer.expect("list item or end");
- }
- }
- return set.build();
- }
- case MAP: {
- PDescriptor itemType = ((PMap<?, ?>) valueType).itemDescriptor();
- PDescriptor keyType = ((PMap<?, ?>) valueType).keyDescriptor();
- UnmodifiableMap.Builder<Object, Object> map = UnmodifiableMap.builder();
- if (!token.isSymbol(ThriftToken.kMessageStart)) {
- throw lexer.failure(token, "Expected map start, found " + token.toString());
- }
- token = lexer.expect("map key or end");
- while (!token.isSymbol(ThriftToken.kMessageEnd)) {
- Object key;
- if (token.type() == ThriftTokenType.STRING) {
- key = parsePrimitiveKey(token.decodeString(true), token, lexer, keyType);
- } else if (token.isIdentifier() || token.isQualifiedIdentifier()) {
- key = registry.getConstantValue(parseType(programName, token.toString())).orElse(null);
- if (key == null) {
- if (keyType.getType().equals(PType.STRING) ||
- keyType.getType().equals(PType.BINARY)) {
- throw lexer.failure(token, "Expected string literal for string key");
- }
- key = parsePrimitiveKey(token.toString(), token, lexer, keyType);
- }
- } else {
- if (keyType.getType().equals(PType.STRING) ||
- keyType.getType().equals(PType.BINARY)) {
- throw lexer.failure(token, "Expected string literal for string key");
- }
- key = parsePrimitiveKey(token.toString(), token, lexer, keyType);
- }
- lexer.expectSymbol("map KV separator", ThriftToken.kKeyValueSep);
- map.put(key, parseTypedValue(lexer.expect("map value"), lexer, itemType, false));
- token = lexer.expect("map key, sep or end");
- if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
- token = lexer.expect("map key or end");
- }
- }
- return map.build();
- }
- default:
- throw new IllegalArgumentException("Unhandled item type " + valueType.getQualifiedName());
- }
- }
- private Object parsePrimitiveKey(String key, ThriftToken token, ThriftLexer tokenizer, PDescriptor keyType)
- throws IOException {
- switch (keyType.getType()) {
- case ENUM:
- PEnumBuilder<?> eb = ((PEnumDescriptor<?>) keyType).builder();
- if (Strings.isInteger(key)) {
- return eb.setById(Integer.parseInt(key))
- .build();
- } else {
- if (key.startsWith(keyType.getProgramName() + "." + keyType.getName() + ".")) {
- // Check for qualified type prefixed identifier ( e.g. program.EnumName.VALUE ).
- key = key.substring(keyType.getProgramName().length() + keyType.getName().length() + 2);
- } else if (key.startsWith(keyType.getName() + ".")) {
- // Check for type prefixed identifier ( e.g. EnumName.VALUE ).
- key = key.substring(keyType.getName().length() + 1);
- }
- return eb.setByName(key)
- .build();
- }
- case BOOL:
- return Boolean.parseBoolean(key);
- case BYTE:
- if (Strings.isInteger(key)) {
- return Byte.parseByte(key);
- } else {
- return (byte) findEnumValue(key, token, tokenizer, "byte");
- }
- case I16:
- if (Strings.isInteger(key)) {
- return Short.parseShort(key);
- } else {
- return (short) findEnumValue(key, token, tokenizer, "i16");
- }
- case I32:
- if (Strings.isInteger(key)) {
- return Integer.parseInt(key);
- } else {
- return findEnumValue(key, token, tokenizer, "i32");
- }
- case I64:
- if (Strings.isInteger(key)) {
- return Long.parseLong(key);
- } else {
- return (long) findEnumValue(key, token, tokenizer, "i64");
- }
- case DOUBLE: {
- try {
- ByteArrayInputStream bais = new ByteArrayInputStream(key.getBytes(US_ASCII));
- JsonTokenizer tokener = new JsonTokenizer(bais);
- JsonToken jt = tokener.expect("parsing double value");
- return jt.doubleValue();
- } catch (NumberFormatException | IOException | JsonException e) {
- throw new ThriftException(token, "Unable to parse double value").initCause(e);
- }
- }
- case STRING:
- return key;
- case BINARY:
- return parseBinary(key);
- default:
- throw new ThriftException("Illegal key type: " + keyType.getType());
- }
- }
- private int findEnumValue(String identifier, ThriftToken token, ThriftLexer tokenizer, String expectedType)
- throws IOException {
- String[] parts = identifier.split("\\.", Byte.MAX_VALUE);
- String typeName;
- String valueName;
- if (parts.length == 3) {
- typeName = parts[0] + "." + parts[1];
- valueName = parts[2];
- } else if (parts.length == 2) {
- typeName = parts[0];
- valueName = parts[1];
- } else {
- throw tokenizer.failure(token, identifier + " is not a valid " + expectedType + " value.");
- }
- try {
- PDeclaredDescriptor descriptor = registry.requireDeclaredType(parseType(programName, typeName));
- if (descriptor instanceof PEnumDescriptor) {
- PEnumDescriptor desc = (PEnumDescriptor) descriptor;
- PEnumValue value = desc.findByName(valueName);
- if (value != null) {
- return value.asInteger();
- }
- }
- throw tokenizer.failure(token, typeName + " is not an enum.");
- } catch (IllegalArgumentException e) {
- throw tokenizer.failure(token, "No type named " + typeName + ".");
- }
- }
- /**
- * Parse a string into binary format using the same rules as above.
- *
- * @param value The string to decode.
- * @return The decoded byte array.
- */
- private Binary parseBinary(String value) {
- return Binary.fromBase64(value);
- }
- }