com.google.template.soy.internal.proto.Field.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.internal.proto.Field.java

Source

/*
 * Copyright 2016 Google Inc.
 *
 * 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
 *
 *     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 com.google.template.soy.internal.proto;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.CaseFormat;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

/**
 * A proto member field.
 *
 * <p>This is used to calculate field names and handle ambiguous extensions. Additional logic should
 * be handled by subclasses.
 */
public abstract class Field {
    private static final Logger logger = Logger.getLogger(Field.class.getName());

    /** A factory for field types. */
    public interface Factory<T extends Field> {
        /** Returns a field. */
        T create(FieldDescriptor fieldDescriptor);

        /**
         * Creates a field for when there are several fields with conflicting soy names. This happens in
         * the case of extensions. It is expected that the concrete subclass throw an appropriate
         * exception (like {@link #ambiguousFieldsError}) when accessed.
         */
        T createAmbiguousFieldSet(Set<T> fields);
    }

    /** Returns the set of fields indexed by soy accessor name for the given type. */
    public static <T extends Field> ImmutableMap<String, T> getFieldsForType(Descriptor descriptor,
            Set<FieldDescriptor> extensions, Factory<T> factory) {
        ImmutableMap.Builder<String, T> fields = ImmutableMap.builder();
        for (FieldDescriptor fieldDescriptor : descriptor.getFields()) {
            if (ProtoUtils.shouldJsIgnoreField(fieldDescriptor)) {
                continue;
            }
            T field = factory.create(fieldDescriptor);
            fields.put(field.getName(), field);
        }

        SetMultimap<String, T> extensionsBySoyName = MultimapBuilder.hashKeys().hashSetValues().build();
        for (FieldDescriptor extension : extensions) {
            T field = factory.create(extension);
            extensionsBySoyName.put(field.getName(), field);
        }

        for (Map.Entry<String, Set<T>> group : Multimaps.asMap(extensionsBySoyName).entrySet()) {
            Set<T> ambiguousFields = group.getValue();
            String fieldName = group.getKey();
            if (ambiguousFields.size() == 1) {
                fields.put(fieldName, Iterables.getOnlyElement(ambiguousFields));
            } else {
                T value = factory.createAmbiguousFieldSet(ambiguousFields);
                logger.severe("Proto " + descriptor.getFullName() + " has multiple extensions with the name \""
                        + fieldName + "\": " + fullFieldNames(ambiguousFields)
                        + "\nThis field will not be accessible from soy");
                fields.put(fieldName, value);
            }
        }

        return fields.build();
    }

    private final FieldDescriptor fieldDesc;
    private final boolean shouldCheckFieldPresenceToEmulateJspbNullability;
    private final String name;

    protected Field(FieldDescriptor fieldDesc) {
        this.fieldDesc = checkNotNull(fieldDesc);
        this.name = computeSoyName(fieldDesc);
        this.shouldCheckFieldPresenceToEmulateJspbNullability = ProtoUtils
                .shouldCheckFieldPresenceToEmulateJspbNullability(fieldDesc);
    }

    /** Return the name of this member field. */
    public final String getName() {
        return name;
    }

    /**
     * Returns whether or not we need to check for field presence to handle nullability semantics on
     * the server.
     */
    public final boolean shouldCheckFieldPresenceToEmulateJspbNullability() {
        return shouldCheckFieldPresenceToEmulateJspbNullability;
    }

    public final FieldDescriptor getDescriptor() {
        return fieldDesc;
    }

    private static String computeSoyName(FieldDescriptor field) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, field.getName()) + fieldSuffix(field);
    }

    private static String fieldSuffix(FieldDescriptor field) {
        if (field.isMapField()) {
            return "Map";
        } else if (field.isRepeated()) {
            return "List";
        } else {
            return "";
        }
    }

    protected static RuntimeException ambiguousFieldsError(String name, Set<? extends Field> fields) {
        return new IllegalStateException(String.format(
                "Cannot access %s. It may refer to any one of the following extensions, "
                        + "and Soy doesn't have enough information to decide which.\n%s\nTo resolve ensure "
                        + "that all extension fields accessed from soy have unique names.",
                name, fullFieldNames(fields)));
    }

    private static ImmutableSet<String> fullFieldNames(Set<? extends Field> fields) {
        ImmutableSet.Builder<String> builder = ImmutableSet.builder();
        for (Field field : fields) {
            builder.add(field.getDescriptor().getFullName());
        }
        return builder.build();
    }
}