com.google.api.codegen.metacode.InitCodeNode.java Source code

Java tutorial

Introduction

Here is the source code for com.google.api.codegen.metacode.InitCodeNode.java

Source

/* Copyright 2016 Google LLC
 *
 * Licensed 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
 *
 *      https://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 com.google.api.codegen.metacode;

import com.google.api.codegen.config.FieldConfig;
import com.google.api.codegen.config.FieldModel;
import com.google.api.codegen.config.OneofConfig;
import com.google.api.codegen.config.ProtoTypeRef;
import com.google.api.codegen.config.TypeModel;
import com.google.api.codegen.metacode.InitCodeContext.InitCodeOutputType;
import com.google.api.codegen.util.Name;
import com.google.api.codegen.util.SymbolTable;
import com.google.api.codegen.util.testing.TestValueGenerator;
import com.google.api.tools.framework.model.TypeRef;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/*
 * Represents a node in an tree of objects to be initialized.
 */
public class InitCodeNode {
    private static final TypeModel INT_TYPE = ProtoTypeRef.create(TypeRef.of(Type.TYPE_UINT64));

    private String key;
    private InitCodeLineType lineType;
    private InitValueConfig initValueConfig;
    private Map<String, InitCodeNode> children;
    private TypeModel typeRef;
    private FieldConfig nodeFieldConfig;
    private Name identifier;
    private OneofConfig oneofConfig;
    private String varName;

    /*
     * Get the key associated with the node. For InitCodeNode objects that are not a root object, they
     * will be stored in their parent node using this key. For elements of a structure, this is the
     * field name. For map elements, it is the map key. For list elements, it is the list index.
     */
    public String getKey() {
        return key;
    }

    /*
     * Get the variable name, which may be configured differently from the key.
     */
    public String getVarName() {
        return varName == null ? key : varName;
    }

    /*
     * Get the InitCodeLineType.
     */
    public InitCodeLineType getLineType() {
        return lineType;
    }

    void setLineType(InitCodeLineType lineType) {
        this.lineType = lineType;
    }

    /*
     * Get the InitValueConfig. For InitCodeNode objects with 1 or more children, the InitValueConfig
     * object will not contain an initial value or formatting config.
     */
    public InitValueConfig getInitValueConfig() {
        return initValueConfig;
    }

    /*
     * Updates the InitValueConfig.
     */
    void updateInitValueConfig(InitValueConfig initValueConfig) {
        this.initValueConfig = initValueConfig;
    }

    /*
     * Get a map of this nodes children. Each child is stored with key equal to child.getKey().
     */
    public Map<String, InitCodeNode> getChildren() {
        return children;
    }

    /*
     * Get the TypeModel of the node.
     */
    public TypeModel getType() {
        return typeRef;
    }

    /*
     * Get the FieldConfig of the node. Nodes that are children of Map or List nodes will share the
     * same field config as their parent.
     */
    public FieldConfig getFieldConfig() {
        return nodeFieldConfig;
    }

    /*
     * Get the unique Name that will be used as a variable name in the initialization code.
     */
    public Name getIdentifier() {
        return identifier;
    }

    public OneofConfig getOneofConfig() {
        return oneofConfig;
    }

    public static InitCodeNode create(String key) {
        return new InitCodeNode(key, InitCodeLineType.Unknown, InitValueConfig.create());
    }

    public static InitCodeNode createWithName(String key, String varName) {
        return new InitCodeNode(key, InitCodeLineType.Unknown, InitValueConfig.create(), varName);
    }

    public static InitCodeNode createWithValue(String key, InitValueConfig initValueConfig) {
        return new InitCodeNode(key, InitCodeLineType.SimpleInitLine, initValueConfig);
    }

    public static InitCodeNode createWithChildren(String key, InitCodeLineType type, InitCodeNode... children) {
        return createWithChildren(key, type, Lists.newArrayList(children));
    }

    public static InitCodeNode createWithChildren(String key, InitCodeLineType type,
            Iterable<InitCodeNode> children) {
        InitCodeNode item = new InitCodeNode(key, type, InitValueConfig.create());
        for (InitCodeNode child : children) {
            item.mergeChild(child);
        }
        return item;
    }

    public static InitCodeNode createSingletonList(String key) {
        InitCodeNode child = InitCodeNode.create("0");
        return InitCodeNode.createWithChildren(key, InitCodeLineType.ListInitLine, child);
    }

    /*
     * Constructs a tree of objects to be initialized using the provided context, and returns the root
     */
    public static InitCodeNode createTree(InitCodeContext context) {
        List<InitCodeNode> subTrees = buildSubTrees(context);
        InitCodeNode root = createWithChildren("root", InitCodeLineType.StructureInitLine, subTrees);
        root.resolveNamesAndTypes(context, context.initObjectType(), context.suggestedName(), null);
        return root;
    }

    /*
     * Generates a flattened list from a tree in post-order.
     */
    public List<InitCodeNode> listInInitializationOrder() {
        ArrayList<InitCodeNode> initOrder = new ArrayList<>();
        ArrayDeque<InitCodeNode> initStack = new ArrayDeque<>();
        initStack.add(this);

        while (!initStack.isEmpty()) {
            InitCodeNode node = initStack.pollLast();
            initStack.addAll(node.children.values());
            initOrder.add(node);
        }
        return Lists.reverse(initOrder);
    }

    private InitCodeNode(String key, InitCodeLineType nodeType, InitValueConfig initValueConfig) {
        this(key, nodeType, initValueConfig, key);
    }

    private InitCodeNode(String key, InitCodeLineType nodeType, InitValueConfig initValueConfig, String varName) {
        this.key = key;
        this.lineType = nodeType;
        this.initValueConfig = initValueConfig;
        this.children = new LinkedHashMap<>();
        this.varName = varName;
    }

    void mergeChild(InitCodeNode newChild) {
        InitCodeNode oldChild = children.get(newChild.key);
        if (oldChild != null && oldChild.lineType != InitCodeLineType.Unknown) {
            InitValueConfig mergedValueConfig = mergeInitValueConfig(oldChild.getInitValueConfig(),
                    newChild.getInitValueConfig());
            oldChild.updateInitValueConfig(mergedValueConfig);
            for (InitCodeNode newSubChild : newChild.children.values()) {
                oldChild.mergeChild(newSubChild);
            }
        } else {
            children.put(newChild.key, newChild);
        }
    }

    private static InitValueConfig mergeInitValueConfig(InitValueConfig oldConfig, InitValueConfig newConfig) {
        HashMap<String, InitValue> collectionValues = new HashMap<>();
        if (oldConfig.hasSimpleInitialValue() && newConfig.hasSimpleInitialValue()
                && !oldConfig.getInitialValue().equals(newConfig.getInitialValue())) {
            throw new IllegalArgumentException("Inconsistent init values");
        }
        if (oldConfig.hasFormattingConfigInitialValues()) {
            collectionValues.putAll(oldConfig.getResourceNameBindingValues());
        }
        if (newConfig.hasFormattingConfigInitialValues()) {
            collectionValues.putAll(newConfig.getResourceNameBindingValues());
        }
        return oldConfig.withInitialCollectionValues(collectionValues);
    }

    private static List<InitCodeNode> buildSubTrees(InitCodeContext context) {
        Preconditions.checkArgument(
                context.initFields() != null || context.outputType() != InitCodeOutputType.FieldList,
                "init field array is not set for flattened method");

        List<InitCodeNode> subTrees = new ArrayList<>();
        if (context.initFields() != null) {
            for (FieldModel field : context.initFields()) {
                String nameString = field.getNameAsParameter();
                InitValueConfig initValueConfig = context.initValueConfigMap().get(nameString);
                if (initValueConfig == null) {
                    subTrees.add(InitCodeNode.createWithName(nameString, nameString));
                } else {
                    subTrees.add(InitCodeNode.createWithValue(nameString, initValueConfig));
                }
            }
        }

        for (String initFieldConfigString : context.initFieldConfigStrings()) {
            subTrees.add(FieldStructureParser.parse(initFieldConfigString, context.initValueConfigMap()));
        }
        subTrees.addAll(context.additionalInitCodeNodes());
        return subTrees;
    }

    private void resolveNamesAndTypes(InitCodeContext context, TypeModel type, Name suggestedName,
            FieldConfig fieldConfig) {

        for (InitCodeNode child : children.values()) {
            validateKeyValue(type, child.key);
            child.resolveNamesAndTypes(context, getChildType(type, child.key),
                    getChildSuggestedName(suggestedName, lineType, child),
                    getChildFieldConfig(context.fieldConfigMap(), fieldConfig, type, child.key));
            if (type.isMessage()) {
                child.oneofConfig = type.getOneOfConfig(child.getKey());
            }
        }

        SymbolTable table = context.symbolTable();
        TestValueGenerator valueGenerator = context.valueGenerator();

        validateType(lineType, type, children.keySet());
        typeRef = type;
        nodeFieldConfig = fieldConfig;
        identifier = table.getNewSymbol(suggestedName);

        if (children.size() == 0) {
            // Set the lineType of childless nodes to SimpleInitLine
            lineType = InitCodeLineType.SimpleInitLine;

            // Validate initValueConfig, or generate random value
            if (initValueConfig.hasSimpleInitialValue()) {
                validateValue(type, initValueConfig.getInitialValue().getValue());
            } else if (initValueConfig.isEmpty() && type.isPrimitive() && !type.isRepeated()
                    && valueGenerator != null) {
                String newValue = valueGenerator.getAndStoreValue(type, identifier);
                initValueConfig = InitValueConfig.createWithValue(InitValue.createLiteral(newValue));
            }
        }
    }

    /*
     * Validate the lineType against the typeRef that has been set, and against child objects. In the
     * case of no child objects being present, update the lineType to SimpleInitLine.
     */
    private static void validateType(InitCodeLineType lineType, TypeModel typeRef, Set<String> childKeys) {
        switch (lineType) {
        case StructureInitLine:
            if (!typeRef.isMessage() || typeRef.isRepeated()) {
                throw new IllegalArgumentException("typeRef " + typeRef + " not compatible with " + lineType);
            }
            break;
        case ListInitLine:
            if (typeRef.isMap() || !typeRef.isRepeated()) {
                throw new IllegalArgumentException("typeRef " + typeRef + " not compatible with " + lineType);
            }
            for (int i = 0; i < childKeys.size(); i++) {
                if (!childKeys.contains(Integer.toString(i))) {
                    throw new IllegalArgumentException(
                            "typeRef " + typeRef + " must have ordered indices, got " + childKeys);
                }
            }
            break;
        case MapInitLine:
            if (!typeRef.isMap()) {
                throw new IllegalArgumentException("typeRef " + typeRef + " not compatible with " + lineType);
            }
            break;
        case SimpleInitLine:
            if (childKeys.size() != 0) {
                throw new IllegalArgumentException("node with SimpleInitLine type cannot have children");
            }
            break;
        case Unknown:
            // Any typeRef is acceptable, but we need to check that there are no children.
            if (childKeys.size() != 0) {
                throw new IllegalArgumentException("node with Unknown type cannot have children");
            }
            break;
        default:
            throw new IllegalArgumentException("unexpected InitcodeLineType: " + lineType);
        }
    }

    private static Name getChildSuggestedName(Name parentName, InitCodeLineType parentType, InitCodeNode child) {
        switch (parentType) {
        case StructureInitLine:
            return Name.anyLower(child.key);
        case MapInitLine:
            return parentName.join("item");
        case ListInitLine:
            return parentName.join("element");
        default:
            throw new IllegalArgumentException("Cannot generate child name for " + parentType);
        }
    }

    private static void validateKeyValue(TypeModel parentType, String key) {
        if (parentType.isMap()) {
            TypeModel keyType = parentType.getMapKeyType();
            validateValue(keyType, key);
        } else if (parentType.isRepeated()) {
            validateValue(INT_TYPE, key);
        } else {
            // Don't validate message types, field will be missing for a bad key
        }
    }

    private static TypeModel getChildType(TypeModel parentType, String key) {
        if (parentType.isMap()) {
            return parentType.getMapValueType();
        } else if (parentType.isRepeated()) {
            // Using the Optional cardinality replaces the Repeated cardinality
            return parentType.makeOptional();
        } else if (parentType.isMessage()) {
            FieldModel childField = parentType.getField(key);
            if (childField == null) {
                throw new IllegalArgumentException("Message type " + parentType + " does not have field " + key);
            }
            return childField.getType();
        } else {
            throw new IllegalArgumentException(
                    "Primitive type " + parentType + " cannot have children. Child key: " + key);
        }
    }

    private static FieldConfig getChildFieldConfig(Map<String, FieldConfig> fieldConfigMap,
            FieldConfig parentFieldConfig, TypeModel parentType, String key) {
        if (parentType.isMap()) {
            return parentFieldConfig;
        } else if (parentType.isRepeated()) {
            return parentFieldConfig;
        } else if (parentType.isMessage()) {
            FieldModel childField = parentType.getField(key);
            if (childField == null) {
                throw new IllegalArgumentException("Message type " + parentType + " does not have field " + key);
            }
            FieldConfig fieldConfig = fieldConfigMap.get(childField.getFullName());
            if (fieldConfig == null) {
                fieldConfig = FieldConfig.createDefaultFieldConfig(childField);
            }
            return fieldConfig;
        } else {
            throw new IllegalArgumentException(
                    "Primitive type " + parentType + " cannot have children. Child key: " + key);
        }
    }

    /**
     * Validates that the provided value matches the provided type. Throws an IllegalArgumentException
     * if the provided type is not supported or doesn't match the value.
     */
    private static void validateValue(TypeModel type, String value) {
        type.validateValue(value);
    }
}