com.google.api.codegen.transformer.SampleTransformer.java Source code

Java tutorial

Introduction

Here is the source code for com.google.api.codegen.transformer.SampleTransformer.java

Source

/* Copyright 2018 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.transformer;

import com.google.api.codegen.OutputSpec;
import com.google.api.codegen.SampleInitAttribute;
import com.google.api.codegen.SampleParameters;
import com.google.api.codegen.SampleValueSet;
import com.google.api.codegen.config.FieldConfig;
import com.google.api.codegen.config.MethodConfig;
import com.google.api.codegen.config.SampleParameterConfig;
import com.google.api.codegen.config.SampleSpec.SampleType;
import com.google.api.codegen.config.SampleSpec.ValueSetAndTags;
import com.google.api.codegen.metacode.InitCodeContext;
import com.google.api.codegen.metacode.InitCodeContext.InitCodeOutputType;
import com.google.api.codegen.util.Name;
import com.google.api.codegen.viewmodel.ApiMethodView;
import com.google.api.codegen.viewmodel.CallingForm;
import com.google.api.codegen.viewmodel.ImportSectionView;
import com.google.api.codegen.viewmodel.InitCodeView;
import com.google.api.codegen.viewmodel.MethodSampleView;
import com.google.api.codegen.viewmodel.OutputView;
import com.google.api.codegen.viewmodel.SampleValueSetView;
import com.google.auto.value.AutoValue;
import com.google.common.base.CaseFormat;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * A class that performs the transformations needed to generate the MethodSampleView for the
 * applicable sample types.
 */
@AutoValue
public abstract class SampleTransformer {

    private static final InitCodeTransformer defaultInitCodeTransformer = new InitCodeTransformer();
    private static final OutputTransformer defaultOutputTransformer = new OutputTransformer();
    private static final SampleImportTransformer defaultSampleImportTransformer = new StandardSampleImportTransformer(
            new StandardImportSectionTransformer());

    public abstract SampleType sampleType();

    public abstract InitCodeTransformer initCodeTransformer();

    public abstract OutputTransformer outputTransformer();

    public abstract SampleImportTransformer sampleImportTransformer();

    public static Builder newBuilder() {
        return new AutoValue_SampleTransformer.Builder().sampleType(SampleType.IN_CODE)
                .initCodeTransformer(defaultInitCodeTransformer).outputTransformer(defaultOutputTransformer)
                .sampleImportTransformer(defaultSampleImportTransformer);
    }

    @AutoValue.Builder
    public abstract static class Builder {

        public abstract Builder sampleType(SampleType val);

        public abstract Builder initCodeTransformer(InitCodeTransformer val);

        public abstract Builder outputTransformer(OutputTransformer val);

        public abstract Builder sampleImportTransformer(SampleImportTransformer val);

        public abstract SampleTransformer build();
    }

    /**
     * Constructs the SampleTransfomer
     *
     * @param sampleType The SampleType this transformer should concern itself with. Other SampleTypes
     *     configured on the methods are ignored.
     */
    public static SampleTransformer create(SampleType sampleType) {
        return newBuilder().sampleType(sampleType).build();
    }

    public static SampleTransformer create(SampleType sampleType, OutputTransformer outputTransformer) {
        return newBuilder().sampleType(sampleType).outputTransformer(outputTransformer).build();
    }

    /**
     * A placeholder id for the ValueSet we synthesize in generateSamples() to accommodate legacy
     * configurations that still rely on sample_code_init_fields. The format of this string is
     * purposefully set to something that will cause errors if it makes it to an artifact.
     */
    private static final String INIT_CODE_SHIM = "[ shim for initcode ]";

    /**
     * Populates {@code methodViewBuilder.samples} with the appropriate {@code MethodSampleView}s.
     * This method provides backward compatibility for configuring in-code samples via {@code
     * MethodView.initCode}.
     *
     * @param methodViewBuilder
     * @param context
     * @param fieldConfigs
     * @param callingForms The list of calling forms applicable to this method, for which we will
     *     generate samples if so configured via {@code context.getMethodConfig()}
     */
    public void generateSamples(ApiMethodView.Builder methodViewBuilder, MethodContext context,
            Collection<FieldConfig> fieldConfigs, InitCodeOutputType initCodeOutputType,
            List<CallingForm> callingForms) {
        generateSamples(methodViewBuilder, context, null, fieldConfigs, initCodeOutputType, callingForms);
    }

    /**
     * Populates {@code methodViewBuilder.samples} with the appropriate {@code MethodSampleView}s.
     * This method provides backward compatibility for configuring in-code samples via {@code
     * MethodView.initCode}.
     *
     * <p>TODO: Once MethodView.initCode is removed, this function becomes a one-liner (calling the
     * overloaded form of generateSamples) that should then be inlined at the call sites:
     * methodViewBuilder.samples( generateSamples(context, initContext, fieldConfigs,
     * initCodeOutputType, callingForms));
     *
     * @param methodViewBuilder
     * @param context
     * @param initContext If null, an {@code InitCodeContext} will be created for each sample based on
     *     the {@code fieldConfigs} and {@code initCodeOutputType}.
     * @param fieldConfigs Only used if {@code initContext} is null to create an {@code
     *     InitCodeContext}. Can be null otherwise.
     * @param initCodeOutputType Only used if {@code initContext} is null to create an {@code
     *     InitCodeContext}. Can be null otherwise.
     * @param callingForms The list of calling forms applicable to this method, for which we will
     *     generate samples if so configured via {@code context.getMethodConfig()}
     */
    public void generateSamples(ApiMethodView.Builder methodViewBuilder, MethodContext context,
            InitCodeContext initContext, Collection<FieldConfig> fieldConfigs,
            InitCodeOutputType initCodeOutputType, List<CallingForm> callingForms) {

        List<MethodSampleView> methodSampleViews = generateSamples(context, initContext, fieldConfigs,
                initCodeOutputType, callingForms);

        // Until we get rid of the non-nullable StaticLangApiMethodView.initCode field, set it to a
        // non-null value. For an in-code sample not specified through the SampleSpec, the correct value
        // to
        // use is the first one on the list, since we set it in the overload of generateSamples.
        if (methodSampleViews.size() > 0) {
            methodViewBuilder.initCode(methodSampleViews.get(0).sampleInitCode());

            // Don't emit samples that were specifically generated for backward-compatibility. We only
            // wanted them for initCode above.
            if (methodSampleViews.get(0).valueSet().id().equals(INIT_CODE_SHIM)) {
                methodSampleViews = Collections.emptyList();
            }
        }

        methodViewBuilder.samples(methodSampleViews);
    }

    /**
     * Returns a list of MethodSampleViews for the given MethodContext.
     *
     * @param initContext
     * @param methodContext
     * @param fieldConfigs
     * @param initCodeOutputType
     * @param callingForms The list of calling forms applicable to this method, for which we will
     *     generate samples if so configured via context.getMethodConfig()
     * @return A list of of the MethodSampleView, each of which corresponds to a specific sample
     *     forthe method
     */
    public List<MethodSampleView> generateSamples(MethodContext methodContext, InitCodeContext initContext,
            Collection<FieldConfig> fieldConfigs, InitCodeOutputType initCodeOutputType,
            List<CallingForm> callingForms) {

        List<MethodSampleView> methodSampleViews = new ArrayList<>();
        MethodConfig methodConfig = methodContext.getMethodConfig();
        ImmutableList<ValueSetAndTags> defaultValueSets = defaultValueSets(methodConfig);
        for (CallingForm form : callingForms) {
            List<ValueSetAndTags> matchingValueSets = methodConfig.getSampleSpec().getMatchingValueSets(form,
                    sampleType());

            if (sampleType() == SampleType.IN_CODE || !methodConfig.getSampleSpec().isConfigured()
                    || matchingValueSets.isEmpty()) {
                matchingValueSets = defaultValueSets;
            }

            for (ValueSetAndTags setAndTag : matchingValueSets) {
                // Don't overwrite the initContext in outer scope.
                InitCodeContext thisContext = initContext;
                SampleValueSet valueSet = setAndTag.values();
                if (thisContext == null) {
                    thisContext = createInitCodeContext(methodContext, fieldConfigs, initCodeOutputType, valueSet);
                }
                methodSampleViews.add(generateSample(setAndTag, form, methodContext, thisContext));
            }
        }
        return methodSampleViews;
    }

    private MethodSampleView generateSample(ValueSetAndTags setAndTag, CallingForm form,
            MethodContext methodContext, InitCodeContext initCodeContext) {
        methodContext = methodContext.cloneWithEmptyTypeTable();
        InitCodeView initCodeView = initCodeTransformer().generateInitCode(methodContext, initCodeContext);
        SampleValueSet valueSet = setAndTag.values();
        List<OutputSpec> outputs = valueSet.getOnSuccessList();
        if (outputs.isEmpty()) {
            outputs = OutputTransformer.defaultOutputSpecs(methodContext.getMethodModel());
        }
        ImmutableList<OutputView> outputViews = outputTransformer().toViews(outputs, methodContext, valueSet, form);
        sampleImportTransformer().addSampleBodyImports(methodContext, form);
        sampleImportTransformer().addOutputImports(methodContext, outputViews);
        sampleImportTransformer().addInitCodeImports(methodContext, methodContext.getTypeTable(),
                initCodeTransformer().getInitCodeNodes(methodContext, initCodeContext.cloneWithEmptySymbolTable())); // to avoid symbol collision
        ImportSectionView sampleImportSectionView = sampleImportTransformer().generateImportSection(methodContext);
        return MethodSampleView.newBuilder().callingForm(form).valueSet(SampleValueSetView.of(valueSet))
                .sampleInitCode(initCodeView).outputs(outputViews).outputImports(
                        // TODO(hzyi): remove outputImports once we implement PythonSampleImportTransformer
                        outputTransformer().getOutputImportTransformer()
                                .generateOutputImports(methodContext, outputViews))
                .sampleImports(sampleImportSectionView)
                .regionTag(regionTagFromSpec(setAndTag.regionTag(), methodContext.getMethodModel().getSimpleName(),
                        form, valueSet.getId()))
                .sampleFunctionName(methodContext.getNamer().getSampleFunctionName(methodContext.getMethodModel()))
                .build();
    }

    /**
     * Creates a region tag from the spec, replacing any {@code %m}, {@code %c}, and {@code %v}
     * placeholders with the method name, calling form name, and value set id, respectively.
     */
    private static String regionTagFromSpec(String spec, String methodName, CallingForm callingForm,
            String valueSetId) {
        final String DEFAULT_REGION_SPEC = "sample";

        if (Strings.isNullOrEmpty(spec)) {
            spec = DEFAULT_REGION_SPEC;
        }
        return spec.replace("%m", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, methodName))
                .replace("%c", callingForm.toLowerCamel()).replace("%v", valueSetId);
    }

    private InitCodeContext createInitCodeContext(MethodContext context, Collection<FieldConfig> fieldConfigs,
            InitCodeOutputType initCodeOutputType, SampleValueSet valueSet) {
        return InitCodeContext.newBuilder().initObjectType(context.getMethodModel().getInputType())
                .suggestedName(Name.from("request"))
                .initFieldConfigStrings(valueSet.getParameters().getDefaultsList())
                .sampleParamConfigMap(sampleParamConfigMapFromValueSet(valueSet))
                .initValueConfigMap(InitCodeTransformer.createCollectionMap(context))
                .initFields(FieldConfig.toFieldTypeIterable(fieldConfigs)).outputType(initCodeOutputType)
                .fieldConfigMap(FieldConfig.toFieldConfigMap(fieldConfigs)).build();
    }

    private ImmutableList<ValueSetAndTags> defaultValueSets(MethodConfig methodConfig) {
        // For backwards compatibility in the configs, we need to use sample_code_init_fields instead
        // to generate the samples in various scenarios. Once all the configs have been migrated to
        // use the SampleSpec, we can delete the code below as well as sample_code_init_fields.
        String defaultId = (sampleType() == SampleType.IN_CODE) ? "sample_code_init_field" : INIT_CODE_SHIM;
        ImmutableList<ValueSetAndTags> defaultValueSets = ImmutableList.of(ValueSetAndTags.newBuilder()
                .values(SampleValueSet.newBuilder()
                        .setParameters(SampleParameters.newBuilder()
                                .addAllDefaults(methodConfig.getSampleCodeInitFields()).build())
                        .setId(defaultId).setDescription("value set imported from sample_code_init_fields")
                        .setTitle("Sample Values").build())
                .regionTag("").build());
        return defaultValueSets;
    }

    private ImmutableMap<String, SampleParameterConfig> sampleParamConfigMapFromValueSet(SampleValueSet valueSet) {
        ImmutableMap.Builder<String, SampleParameterConfig> builder = ImmutableMap.builder();
        for (SampleInitAttribute attr : valueSet.getParameters().getAttributesList()) {
            String identifier = attr.getParameter();
            SampleParameterConfig config = SampleParameterConfig.newBuilder().identifier(identifier)
                    .readFromFile(attr.getReadFile()).sampleArgumentName(attr.getSampleArgumentName()).build();
            builder.put(identifier, config);
        }
        return builder.build();
    }
}