com.google.template.soy.types.aggregate.UnionType.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.types.aggregate.UnionType.java

Source

/*
 * Copyright 2013 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.types.aggregate;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.template.soy.data.SoyValue;
import com.google.template.soy.types.SoyType;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.Set;

/**
 * Type representing a set of possible alternative types.
 *
 */
public final class UnionType implements SoyType {
    private static final Predicate<SoyType> IS_NULL = new Predicate<SoyType>() {
        @Override
        public boolean apply(SoyType memberType) {
            return memberType.getKind() == SoyType.Kind.NULL;
        }
    };

    /** Comparator that defines the ordering of types. */
    private static final Comparator<SoyType> MEMBER_ORDER = new Comparator<SoyType>() {
        @Override
        public int compare(SoyType st1, SoyType st2) {
            return st1.toString().compareTo(st2.toString());
        }
    };

    private final ImmutableSortedSet<SoyType> members;

    private UnionType(Iterable<? extends SoyType> members) {
        this.members = ImmutableSortedSet.copyOf(MEMBER_ORDER, members);
        Preconditions.checkArgument(this.members.size() != 1);
        for (SoyType type : this.members) {
            if (type.getKind() == Kind.UNKNOWN) {
                throw new IllegalArgumentException("Cannot create unions containing unknown: " + this.members);
            }
        }
    }

    /**
     * Convenience method for creating unions.
     * @param members Member types of the union.
     * @return Union of those types.
     *    If there is exactly one distinct type in members, then this will not be a UnionType.
     */
    public static SoyType of(SoyType... members) {
        return of(Arrays.asList(members));
    }

    /**
     * Create a union from a collection of types.
     * @param members Member types of the union.
     * @return Union of those types.
     *    If there is exactly one distinct type in members, then this will not be a UnionType.
     */
    public static SoyType of(Collection<SoyType> members) {
        ImmutableSet<SoyType> flattenedMembers = flatten(members);
        if (flattenedMembers.size() == 1) {
            return Iterables.getOnlyElement(flattenedMembers);
        }
        return new UnionType(flattenedMembers);
    }

    @Override
    public Kind getKind() {
        return Kind.UNION;
    }

    /**
     * Return the set of types contained in this union.
     */
    public Set<SoyType> getMembers() {
        return members;
    }

    @Override
    public boolean isAssignableFrom(SoyType srcType) {
        if (srcType.getKind() == Kind.UNION) {
            // A union is assignable to a union if every type in the source
            // union is assignable to some type in the destination union.
            UnionType fromUnion = (UnionType) srcType;
            for (SoyType fromMember : fromUnion.members) {
                if (!isAssignableFrom(fromMember)) {
                    return false;
                }
            }
            return true;
        } else {
            // A type can be assigned to a union iff it is assignable to at least one
            // member of the union.
            for (SoyType memberType : members) {
                if (memberType.isAssignableFrom(srcType)) {
                    return true;
                }
            }
            return false;
        }
    }

    @Override
    public boolean isInstance(SoyValue value) {
        for (SoyType memberType : members) {
            if (memberType.isInstance(value)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Class<? extends SoyValue> javaType() {
        // TODO(lukes): we can do better by finding the least common ancestor of all the javaTypes in
        // the union.  Possibly not worth it since the hierarchy is so flat (i.e. it would almost always
        // just resolve to SoyValue anyway)
        if (isNullable()) {
            return removeNullability().javaType();
        }
        return SoyValue.class;
    }

    /** Returns true if the union includes the null type. */
    public boolean isNullable() {
        return Iterables.any(members, IS_NULL);
    }

    /** Returns a Soy type that is equivalent to this one but with 'null' removed. */
    public SoyType removeNullability() {
        if (isNullable()) {
            return of(Collections2.filter(members, Predicates.not(IS_NULL)));
        }
        return this;
    }

    @Override
    public String toString() {
        return Joiner.on('|').join(members);
    }

    @Override
    public boolean equals(Object other) {
        return other != null && other.getClass() == this.getClass() && ((UnionType) other).members.equals(members);
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.getClass(), members);
    }

    /**
     * Create a set containing all of the types contained in the input collection.
     * If any of the members of the input collection are unions, add the
     * individual members to the result union, thus "flattening" the union.
     * @param members The input types.
     * @return The set of all types in the input collection.
     */
    private static ImmutableSet<SoyType> flatten(Collection<SoyType> members) {
        ImmutableSet.Builder<SoyType> builder = ImmutableSet.builder();
        for (SoyType type : members) {
            if (type.getKind() == Kind.UNKNOWN) {
                return ImmutableSet.of(type);
            }
            if (type.getKind() == Kind.UNION) {
                builder.addAll(((UnionType) type).members);
            } else {
                builder.add(type);
            }
        }
        return builder.build();
    }
}