ConstValueProvider.java

  1. /*
  2.  * Copyright 2016 Providence Authors
  3.  *
  4.  * Licensed to the Apache Software Foundation (ASF) under one
  5.  * or more contributor license agreements. See the NOTICE file
  6.  * distributed with this work for additional information
  7.  * regarding copyright ownership. The ASF licenses this file
  8.  * to you under the Apache License, Version 2.0 (the
  9.  * "License"); you may not use this file except in compliance
  10.  * with the License. You may obtain a copy of the License at
  11.  *
  12.  *   http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing,
  15.  * software distributed under the License is distributed on an
  16.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  17.  * KIND, either express or implied. See the License for the
  18.  * specific language governing permissions and limitations
  19.  * under the License.
  20.  */
  21. package net.morimekta.providence.reflect.util;

  22. import net.morimekta.providence.PEnumBuilder;
  23. import net.morimekta.providence.PEnumValue;
  24. import net.morimekta.providence.PMessage;
  25. import net.morimekta.providence.PMessageBuilder;
  26. import net.morimekta.providence.PType;
  27. import net.morimekta.providence.descriptor.PDeclaredDescriptor;
  28. import net.morimekta.providence.descriptor.PDescriptor;
  29. import net.morimekta.providence.descriptor.PEnumDescriptor;
  30. import net.morimekta.providence.descriptor.PField;
  31. import net.morimekta.providence.descriptor.PList;
  32. import net.morimekta.providence.descriptor.PMap;
  33. import net.morimekta.providence.descriptor.PMessageDescriptor;
  34. import net.morimekta.providence.descriptor.PSet;
  35. import net.morimekta.providence.descriptor.PValueProvider;
  36. import net.morimekta.providence.reflect.parser.ThriftException;
  37. import net.morimekta.providence.reflect.parser.ThriftLexer;
  38. import net.morimekta.providence.reflect.parser.ThriftToken;
  39. import net.morimekta.providence.reflect.parser.ThriftTokenType;
  40. import net.morimekta.providence.types.TypeReference;
  41. import net.morimekta.providence.types.TypeRegistry;
  42. import net.morimekta.util.Binary;
  43. import net.morimekta.util.Strings;
  44. import net.morimekta.util.collect.UnmodifiableList;
  45. import net.morimekta.util.collect.UnmodifiableMap;
  46. import net.morimekta.util.collect.UnmodifiableSet;
  47. import net.morimekta.util.json.JsonException;
  48. import net.morimekta.util.json.JsonToken;
  49. import net.morimekta.util.json.JsonTokenizer;
  50. import net.morimekta.util.lexer.LexerException;
  51. import net.morimekta.util.lexer.Tokenizer;
  52. import net.morimekta.util.lexer.TokenizerRepeater;
  53. import net.morimekta.util.lexer.UncheckedLexerException;

  54. import javax.annotation.Nonnull;
  55. import java.io.ByteArrayInputStream;
  56. import java.io.IOException;
  57. import java.io.UncheckedIOException;
  58. import java.util.Collections;
  59. import java.util.List;
  60. import java.util.Optional;
  61. import java.util.concurrent.atomic.AtomicReference;

  62. import static java.nio.charset.StandardCharsets.US_ASCII;
  63. import static net.morimekta.providence.reflect.parser.ThriftTokenizer.kNull;
  64. import static net.morimekta.providence.types.TypeReference.parseType;
  65. import static net.morimekta.providence.util.MessageUtil.coerceStrict;

  66. /**
  67.  * A value provider for thrift constants.
  68.  */
  69. public class ConstValueProvider implements PValueProvider<Object> {
  70.     private final TypeRegistry      registry;
  71.     private final String            programName;
  72.     private final TypeReference     constType;
  73.     private final List<ThriftToken> constTokens;
  74.     private AtomicReference<Object> parsedValue;

  75.     public ConstValueProvider(@Nonnull TypeRegistry registry,
  76.                               @Nonnull String programName,
  77.                               @Nonnull TypeReference constType,
  78.                               @Nonnull List<ThriftToken> constTokens) {
  79.         this.registry = registry;
  80.         this.programName = programName;
  81.         this.constType = constType;
  82.         this.constTokens = constTokens;
  83.         this.parsedValue = null;
  84.     }

  85.     @Override
  86.     public Object get() {
  87.         if (parsedValue == null) {
  88.             PDescriptor type = registry
  89.                     .getTypeProvider(constType, Collections.emptyMap())
  90.                     .descriptor();
  91.             Tokenizer<ThriftTokenType, ThriftToken> tokenizer = new TokenizerRepeater<>(constTokens);
  92.             ThriftLexer                             lexer     = new ThriftLexer(tokenizer);
  93.             try {
  94.                 parsedValue = new AtomicReference<>(
  95.                         parseTypedValue(lexer.expect("const value"), lexer, type, true));
  96.             } catch (LexerException e) {
  97.                 throw new UncheckedLexerException(e);
  98.             } catch (IOException e) {
  99.                 throw new UncheckedIOException(e.getMessage(), e);
  100.             }
  101.         }

  102.         return parsedValue.get();
  103.     }

  104.     /**
  105.      * Parse JSON object as a message.
  106.      *
  107.      * @param lexer     The thrift lexer.
  108.      * @param type      The message type.
  109.      * @param <Message> Message generic type.
  110.      * @return The parsed message.
  111.      */
  112.     private <Message extends PMessage<Message>>
  113.     Message parseMessage(ThriftLexer lexer,
  114.                          PMessageDescriptor<Message> type) throws IOException {
  115.         PMessageBuilder<Message> builder = type.builder();

  116.         ThriftToken token = lexer.expect("message field or end");
  117.         while (!token.isSymbol(ThriftToken.kMessageEnd)) {
  118.             if (token.type() != ThriftTokenType.STRING) {
  119.                 throw lexer.failure(token, "Invalid field name token");
  120.             }
  121.             PField<?> field = type.findFieldByName(token.decodeString(true));
  122.             if (field == null) {
  123.                 throw lexer.failure(
  124.                         token, "No such field in %s: %s",
  125.                         type.getQualifiedName(), token.decodeString(true));
  126.             }
  127.             lexer.expectSymbol("message key-value sep", ThriftToken.kKeyValueSep);

  128.             builder.set(field.getId(),
  129.                         parseTypedValue(lexer.expect("parsing field value"),
  130.                                         lexer,
  131.                                         field.getDescriptor(),
  132.                                         false));

  133.             token = lexer.expect("message sep, field or end");
  134.             if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
  135.                 token = lexer.expect("message field or end");
  136.             }
  137.         }

  138.         return builder.build();
  139.     }

  140.     private Object parseTypedValue(ThriftToken token,
  141.                                    ThriftLexer lexer,
  142.                                    PDescriptor valueType,
  143.                                    boolean allowNull)
  144.             throws IOException {
  145.         // Enum.VALUE
  146.         // program.Enum.VALUE
  147.         if (token.isQualifiedIdentifier() ||
  148.             token.isDoubleQualifiedIdentifier()) {
  149.             // Possible enum value. First strip after last '.' to get enum type name.
  150.             String typeName = token.toString().replaceAll("\\.[^.]+$", "");

  151.             Optional<PDeclaredDescriptor<?>> desc;
  152.             if (typeName.equals(valueType.getName()) &&
  153.                 valueType.getType() == PType.ENUM) {
  154.                 desc = Optional.of((PDeclaredDescriptor<?>) valueType);
  155.             } else {
  156.                 TypeReference ref = parseType(this.programName, typeName);
  157.                 desc = registry.getDeclaredType(ref);
  158.             }

  159.             if (desc.isPresent()) {
  160.                 if (!(desc.get() instanceof PEnumDescriptor)) {
  161.                     throw new IllegalArgumentException("Not an enum type " + desc.get().getQualifiedName());
  162.                 }

  163.                 String        valueName = token.toString().replaceAll("^.*\\.", "");
  164.                 PEnumValue<?> value     = ((PEnumDescriptor<?>) desc.get()).findByName(valueName);
  165.                 if (value != null) {
  166.                     return coerceStrict(valueType, value).orElseThrow(() -> new IllegalArgumentException("Non-matching enum value"));
  167.                 } else if (allowNull) {
  168.                     return null;
  169.                 } else {
  170.                     throw new IllegalArgumentException("No such " + desc.get().getQualifiedName() + " value " + valueName);
  171.                 }
  172.             }
  173.         }

  174.         // kConstName
  175.         // program.kConstName
  176.         if (token.isIdentifier() || token.isQualifiedIdentifier()) {
  177.             if (kNull.equals(token.toString())) {
  178.                 if (allowNull) {
  179.                     return null;
  180.                 }
  181.             }

  182.             Optional<Object> optional = registry.getConstantValue(parseType(this.programName, token.toString()));
  183.             if (optional.isPresent()) {
  184.                 return coerceStrict(valueType, optional.get()).orElse(null);
  185.             }
  186.         }

  187.         // Direct value.
  188.         switch (valueType.getType()) {
  189.             case BOOL:
  190.                 if (token.isIdentifier()) {
  191.                     return Boolean.parseBoolean(token.toString());
  192.                 } else if (token.isInteger()) {
  193.                     return token.parseInteger() != 0L;
  194.                 }
  195.                 throw lexer.failure(token, "Not boolean value: %s", token.toString());
  196.             case BYTE:
  197.                 if (token.isInteger()) {
  198.                     return (byte) token.parseInteger();
  199.                 }
  200.                 return (byte) findEnumValue(token.toString(), token, lexer, "byte");
  201.             case I16:
  202.                 if (token.isInteger()) {
  203.                     return (short) token.parseInteger();
  204.                 }
  205.                 return (short) findEnumValue(token.toString(), token, lexer, "i16");
  206.             case I32:
  207.                 if (token.isInteger()) {
  208.                     return (int) token.parseInteger();
  209.                 }
  210.                 return findEnumValue(token.toString(), token, lexer, "i32");
  211.             case I64:
  212.                 if (token.isInteger()) {
  213.                     return token.parseInteger();
  214.                 }
  215.                 return (long) findEnumValue(token.toString(), token, lexer, "i64");
  216.             case DOUBLE:
  217.                 if (token.type() == ThriftTokenType.NUMBER) {
  218.                     return token.parseDouble();
  219.                 }
  220.                 throw lexer.failure(token, token + " is not a valid double value.");
  221.             case STRING:
  222.                 if (token.type() == ThriftTokenType.STRING) {
  223.                     return token.decodeString(true);
  224.                 } else if (allowNull && token.toString().equals(kNull)) {
  225.                     return null;
  226.                 }
  227.                 throw lexer.failure(token, "Not a valid string value.");
  228.             case BINARY:
  229.                 if (token.type() == ThriftTokenType.STRING) {
  230.                     return parseBinary(token.substring(1, -1)
  231.                                             .toString());
  232.                 } else if (allowNull && token.toString().equals(kNull)) {
  233.                     return null;
  234.                 }
  235.                 throw lexer.failure(token, "Not a valid binary value.");
  236.             case ENUM: {
  237.                 // Enum reference already handled.
  238.                 PEnumBuilder<?> eb   = ((PEnumDescriptor<?>) valueType).builder();
  239.                 String          name = token.toString();
  240.                 if (Strings.isInteger(name)) {
  241.                     Object ev = eb.setById(Integer.parseInt(name)).build();
  242.                     if (ev == null) {
  243.                         if (allowNull && token.toString().equals(kNull)) {
  244.                             return null;
  245.                         }
  246.                         throw lexer.failure(token,
  247.                                                 "No such " + valueType.getQualifiedName() + " enum value \"" + name);
  248.                     }
  249.                     return ev;
  250.                 }
  251.                 throw lexer.failure(token, "Not valid enum reference '" + name + "'");
  252.             }
  253.             case MESSAGE: {
  254.                 if (token.isSymbol(ThriftToken.kMessageStart)) {
  255.                     return parseMessage(lexer, (PMessageDescriptor<?>) valueType);
  256.                 } else if (allowNull && token.toString().equals(kNull)) {
  257.                     // messages can be null values in constants.
  258.                     return null;
  259.                 }
  260.                 throw lexer.failure(token, "Not a valid message start.");
  261.             }
  262.             case LIST: {
  263.                 PDescriptor                      itemType = ((PList<?>) valueType).itemDescriptor();
  264.                 UnmodifiableList.Builder<Object> list     = UnmodifiableList.builder();

  265.                 if (!token.isSymbol(ThriftToken.kListStart)) {
  266.                     throw lexer.failure(token, "Expected list start, found " + token.toString());
  267.                 }
  268.                 token = lexer.expect("list item or end");
  269.                 while (!token.isSymbol(ThriftToken.kListEnd)) {
  270.                     list.add(parseTypedValue(token, lexer, itemType, false));
  271.                     token = lexer.expect("list item, sep or end");
  272.                     if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
  273.                         token = lexer.expect("list item or end");
  274.                     }
  275.                 }

  276.                 return list.build();
  277.             }
  278.             case SET: {
  279.                 PDescriptor                     itemType = ((PSet<?>) valueType).itemDescriptor();
  280.                 UnmodifiableSet.Builder<Object> set      = UnmodifiableSet.builder();

  281.                 if (!token.isSymbol(ThriftToken.kListStart)) {
  282.                     throw lexer.failure(token, "Expected list start, found " + token.toString());
  283.                 }

  284.                 if (!token.isSymbol(ThriftToken.kListStart)) {
  285.                     throw lexer.failure(token, "Expected list start, found " + token.toString());
  286.                 }
  287.                 token = lexer.expect("list item or end");
  288.                 while (!token.isSymbol(ThriftToken.kListEnd)) {
  289.                     set.add(parseTypedValue(token, lexer, itemType, false));
  290.                     token = lexer.expect("list item, sep or end");
  291.                     if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
  292.                         token = lexer.expect("list item or end");
  293.                     }
  294.                 }

  295.                 return set.build();
  296.             }
  297.             case MAP: {
  298.                 PDescriptor itemType = ((PMap<?, ?>) valueType).itemDescriptor();
  299.                 PDescriptor keyType = ((PMap<?, ?>) valueType).keyDescriptor();

  300.                 UnmodifiableMap.Builder<Object, Object> map = UnmodifiableMap.builder();

  301.                 if (!token.isSymbol(ThriftToken.kMessageStart)) {
  302.                     throw lexer.failure(token, "Expected map start, found " + token.toString());
  303.                 }

  304.                 token = lexer.expect("map key or end");
  305.                 while (!token.isSymbol(ThriftToken.kMessageEnd)) {
  306.                     Object key;
  307.                     if (token.type() == ThriftTokenType.STRING) {
  308.                         key = parsePrimitiveKey(token.decodeString(true), token, lexer, keyType);
  309.                     } else if (token.isIdentifier() || token.isQualifiedIdentifier()) {
  310.                         key = registry.getConstantValue(parseType(programName, token.toString())).orElse(null);
  311.                         if (key == null) {
  312.                             if (keyType.getType().equals(PType.STRING) ||
  313.                                 keyType.getType().equals(PType.BINARY)) {
  314.                                 throw lexer.failure(token, "Expected string literal for string key");
  315.                             }
  316.                             key = parsePrimitiveKey(token.toString(), token, lexer, keyType);
  317.                         }
  318.                     } else {
  319.                         if (keyType.getType().equals(PType.STRING) ||
  320.                             keyType.getType().equals(PType.BINARY)) {
  321.                             throw lexer.failure(token, "Expected string literal for string key");
  322.                         }
  323.                         key = parsePrimitiveKey(token.toString(), token, lexer, keyType);
  324.                     }
  325.                     lexer.expectSymbol("map KV separator", ThriftToken.kKeyValueSep);
  326.                     map.put(key, parseTypedValue(lexer.expect("map value"), lexer, itemType, false));

  327.                     token = lexer.expect("map key, sep or end");
  328.                     if (token.isSymbol(ThriftToken.kLineSep1) || token.isSymbol(ThriftToken.kLineSep2)) {
  329.                         token = lexer.expect("map key or end");
  330.                     }
  331.                 }

  332.                 return map.build();
  333.             }
  334.             default:
  335.                 throw new IllegalArgumentException("Unhandled item type " + valueType.getQualifiedName());
  336.         }
  337.     }

  338.     private Object parsePrimitiveKey(String key, ThriftToken token, ThriftLexer tokenizer, PDescriptor keyType)
  339.             throws IOException {
  340.         switch (keyType.getType()) {
  341.             case ENUM:
  342.                 PEnumBuilder<?> eb = ((PEnumDescriptor<?>) keyType).builder();
  343.                 if (Strings.isInteger(key)) {
  344.                     return eb.setById(Integer.parseInt(key))
  345.                              .build();
  346.                 } else {
  347.                     if (key.startsWith(keyType.getProgramName() + "." + keyType.getName() + ".")) {
  348.                         // Check for qualified type prefixed identifier ( e.g. program.EnumName.VALUE ).
  349.                         key = key.substring(keyType.getProgramName().length() + keyType.getName().length() + 2);
  350.                     } else if (key.startsWith(keyType.getName() + ".")) {
  351.                         // Check for type prefixed identifier ( e.g. EnumName.VALUE ).
  352.                         key = key.substring(keyType.getName().length() + 1);
  353.                     }
  354.                     return eb.setByName(key)
  355.                              .build();
  356.                 }
  357.             case BOOL:
  358.                 return Boolean.parseBoolean(key);
  359.             case BYTE:
  360.                 if (Strings.isInteger(key)) {
  361.                     return Byte.parseByte(key);
  362.                 } else {
  363.                     return (byte) findEnumValue(key, token, tokenizer, "byte");
  364.                 }
  365.             case I16:
  366.                 if (Strings.isInteger(key)) {
  367.                     return Short.parseShort(key);
  368.                 } else {
  369.                     return (short) findEnumValue(key, token, tokenizer, "i16");
  370.                 }
  371.             case I32:
  372.                 if (Strings.isInteger(key)) {
  373.                     return Integer.parseInt(key);
  374.                 } else {
  375.                     return findEnumValue(key, token, tokenizer, "i32");
  376.                 }
  377.             case I64:
  378.                 if (Strings.isInteger(key)) {
  379.                     return Long.parseLong(key);
  380.                 } else {
  381.                     return (long) findEnumValue(key, token, tokenizer, "i64");
  382.                 }
  383.             case DOUBLE: {
  384.                 try {
  385.                     ByteArrayInputStream bais    = new ByteArrayInputStream(key.getBytes(US_ASCII));
  386.                     JsonTokenizer        tokener = new JsonTokenizer(bais);
  387.                     JsonToken            jt      = tokener.expect("parsing double value");
  388.                     return jt.doubleValue();
  389.                 } catch (NumberFormatException | IOException | JsonException e) {
  390.                     throw new ThriftException(token, "Unable to parse double value").initCause(e);
  391.                 }
  392.             }
  393.             case STRING:
  394.                 return key;
  395.             case BINARY:
  396.                 return parseBinary(key);
  397.             default:
  398.                 throw new ThriftException("Illegal key type: " + keyType.getType());
  399.         }
  400.     }

  401.     private int findEnumValue(String identifier, ThriftToken token, ThriftLexer tokenizer, String expectedType)
  402.             throws IOException {
  403.         String[] parts = identifier.split("\\.", Byte.MAX_VALUE);
  404.         String typeName;
  405.         String valueName;
  406.         if (parts.length == 3) {
  407.             typeName = parts[0] + "." + parts[1];
  408.             valueName = parts[2];
  409.         } else if (parts.length == 2) {
  410.             typeName = parts[0];
  411.             valueName = parts[1];
  412.         } else {
  413.             throw tokenizer.failure(token, identifier + " is not a valid " + expectedType + " value.");
  414.         }

  415.         try {
  416.             PDeclaredDescriptor descriptor = registry.requireDeclaredType(parseType(programName, typeName));
  417.             if (descriptor instanceof PEnumDescriptor) {
  418.                 PEnumDescriptor desc = (PEnumDescriptor) descriptor;
  419.                 PEnumValue value = desc.findByName(valueName);
  420.                 if (value != null) {
  421.                     return value.asInteger();
  422.                 }
  423.             }

  424.             throw tokenizer.failure(token, typeName + " is not an enum.");
  425.         } catch (IllegalArgumentException e) {
  426.             throw tokenizer.failure(token, "No type named " + typeName + ".");
  427.         }
  428.     }

  429.     /**
  430.      * Parse a string into binary format using the same rules as above.
  431.      *
  432.      * @param value The string to decode.
  433.      * @return The decoded byte array.
  434.      */
  435.     private Binary parseBinary(String value) {
  436.         return Binary.fromBase64(value);
  437.     }

  438. }