/* * Copyright 2017 LINE Corporation * * LINE Corporation licenses this file to you 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.linecorp.armeria.server.docs; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; /** * Type signature of a method parameter, a method return value or a struct/exception field. * A type signature can be represented as a string in one of the following forms: * <ul> * <li>Base types: {@code "{type name}"}<ul> * <li>{@code "i64"}</li> * <li>{@code "double"}</li> * <li>{@code "string"}</li> * </ul></li> * <li>Container types: {@code "{type name}<{element type signature}[, {element type signature}]*>"}<ul> * <li>{@code "list<i32>"}</li> * <li>{@code "repeated<set<string>>"}</li> * <li>{@code "map<string, com.example.FooStruct>"}</li> * <li>{@code "tuple<i8, string, double>"}</li> * </ul></li> * <li>Named types (enums, structs and exceptions): {@code "{fully qualified type name}"}<ul> * <li>{@code "com.example.FooStruct"}</li> * </ul></li> * <li>Unresolved types: {@code "?{type name}"}<ul> * <li>{@code "?BarStruct"}</li> * <li>{@code "?com.example.BarStruct"}</li> * </ul></li> * </ul> */ @JsonSerialize(using = TypeSignatureJsonSerializer.class) public final class TypeSignature { private static final Pattern BASE_PATTERN = Pattern.compile("^([^.<>]+)$"); private static final Pattern NAMED_PATTERN = Pattern.compile("^([^.<>]+(?:\\.[^.<>]+)+)$"); /** * Creates a new type signature for a base type. * * @throws IllegalArgumentException if the specified type name is not valid */ public static TypeSignature ofBase(String baseTypeName) { checkBaseTypeName(baseTypeName, "baseTypeName"); return new TypeSignature(baseTypeName, ImmutableList.of()); } /** * Creates a new container type with the specified container type name and the type signatures of the * elements it contains. * * @throws IllegalArgumentException if the specified type name is not valid or * {@code elementTypeSignatures} is empty. */ public static TypeSignature ofContainer(String containerTypeName, TypeSignature... elementTypeSignatures) { requireNonNull(elementTypeSignatures, "elementTypeSignatures"); return ofContainer(containerTypeName, ImmutableList.copyOf(elementTypeSignatures)); } /** * Creates a new container type with the specified container type name and the type signatures of the * elements it contains. * * @throws IllegalArgumentException if the specified type name is not valid or * {@code elementTypeSignatures} is empty. */ public static TypeSignature ofContainer(String containerTypeName, Iterable<TypeSignature> elementTypeSignatures) { checkBaseTypeName(containerTypeName, "containerTypeName"); requireNonNull(elementTypeSignatures, "elementTypeSignatures"); final List<TypeSignature> elementTypeSignaturesCopy = ImmutableList.copyOf(elementTypeSignatures); checkArgument(!elementTypeSignaturesCopy.isEmpty(), "elementTypeSignatures is empty."); return new TypeSignature(containerTypeName, elementTypeSignaturesCopy); } private static void checkBaseTypeName(String baseTypeName, String parameterName) { requireNonNull(baseTypeName, parameterName); checkArgument(BASE_PATTERN.matcher(baseTypeName).matches(), "%s: %s", parameterName, baseTypeName); } /** * Creates a new type signature for the list with the specified element type signature. * This method is a shortcut of: * <pre>{@code * ofContainer("list", elementTypeSignature); * }</pre> */ public static TypeSignature ofList(TypeSignature elementTypeSignature) { requireNonNull(elementTypeSignature, "elementTypeSignature"); return ofContainer("list", elementTypeSignature); } /** * Creates a new type signature for the list with the specified named element type. * This method is a shortcut of: * <pre>{@code * ofList(ofNamed(namedElementType)); * }</pre> */ public static TypeSignature ofList(Class<?> namedElementType) { return ofList(ofNamed(namedElementType, "namedElementType")); } /** * Creates a new type signature for the set with the specified element type signature. * This method is a shortcut of: * <pre>{@code * ofContainer("set", elementTypeSignature); * }</pre> */ public static TypeSignature ofSet(TypeSignature elementTypeSignature) { requireNonNull(elementTypeSignature, "elementTypeSignature"); return ofContainer("set", elementTypeSignature); } /** * Creates a new type signature for the set with the specified named element type. * This method is a shortcut of: * <pre>{@code * ofSet(ofNamed(namedElementType)); * }</pre> */ public static TypeSignature ofSet(Class<?> namedElementType) { return ofSet(ofNamed(namedElementType, "namedElementType")); } /** * Creates a new type signature for the map with the specified key and value type signatures. * This method is a shortcut of: * <pre>{@code * ofMap("map", keyTypeSignature, valueTypeSignature); * }</pre> */ public static TypeSignature ofMap(TypeSignature keyTypeSignature, TypeSignature valueTypeSignature) { requireNonNull(keyTypeSignature, "keyTypeSignature"); requireNonNull(valueTypeSignature, "valueTypeSignature"); return ofContainer("map", keyTypeSignature, valueTypeSignature); } /** * Creates a new type signature for the map with the specified named key and value types. * This method is a shortcut of: * <pre>{@code * ofMap(ofNamed(namedKeyType), ofNamed(namedValueType)); * }</pre> */ public static TypeSignature ofMap(Class<?> namedKeyType, Class<?> namedValueType) { return ofMap(ofNamed(namedKeyType, "namedKeyType"), ofNamed(namedValueType, "namedValueType")); } /** * Creates a new named type signature for the specified type. */ public static TypeSignature ofNamed(Class<?> namedType) { return ofNamed(namedType, "namedType"); } /** * Creates a new named type signature for the provided name and arbitrary descriptor. */ public static TypeSignature ofNamed(String name, Object namedTypeDescriptor) { return new TypeSignature(requireNonNull(name, "name"), requireNonNull(namedTypeDescriptor, "namedTypeDescriptor")); } private static TypeSignature ofNamed(Class<?> namedType, String parameterName) { requireNonNull(namedType, parameterName); final String typeName = namedType.getName(); checkArgument(NAMED_PATTERN.matcher(typeName).matches(), "%s: %s", parameterName, typeName); checkArgument(!namedType.isArray(), "%s is an array: %s", parameterName, typeName); checkArgument(!namedType.isPrimitive(), "%s is a primitive type: %s", parameterName, typeName); return new TypeSignature(namedType); } /** * Creates a new unresolved type signature with the specified type name. */ public static TypeSignature ofUnresolved(String unresolvedTypeName) { requireNonNull(unresolvedTypeName, "unresolvedTypeName"); return new TypeSignature('?' + unresolvedTypeName, ImmutableList.of()); } private final String name; private final Object namedTypeDescriptor; private final List<TypeSignature> typeParameters; /** * Creates a new non-named type signature. */ private TypeSignature(String name, List<TypeSignature> typeParameters) { this.name = name; this.typeParameters = typeParameters; namedTypeDescriptor = null; } /** * Creates a new type signature for a named type. */ private TypeSignature(Class<?> namedTypeDescriptor) { name = namedTypeDescriptor.getName(); this.namedTypeDescriptor = namedTypeDescriptor; typeParameters = ImmutableList.of(); } private TypeSignature(String name, Object namedTypeDescriptor) { this.name = name; this.namedTypeDescriptor = namedTypeDescriptor; typeParameters = ImmutableList.of(); } /** * Returns the name of the type. */ public String name() { return name; } /** * Returns the descriptor of the type if and only if this type signature represents a named type. * For reflection-based {@link DocServicePlugin}s, this will probably be a {@link Class}, but * other plugins may use an actual instance with descriptor information. */ public Optional<Object> namedTypeDescriptor() { return Optional.ofNullable(namedTypeDescriptor); } /** * Returns the list of the type parameters of this type signature. */ public List<TypeSignature> typeParameters() { return typeParameters; } /** * Returns the {@link String} representation of this type signature, as described in the class * documentation. */ public String signature() { if (typeParameters.isEmpty()) { return name; } else { return name + '<' + Joiner.on(", ").join(typeParameters) + '>'; } } /** * Returns if this type signature represents a base type. */ public boolean isBase() { return !isUnresolved() && !isNamed() && !isContainer(); } /** * Returns if this type signature represents a container type. */ public boolean isContainer() { return !typeParameters.isEmpty(); } /** * Returns if this type signature represents a named type. */ public boolean isNamed() { return namedTypeDescriptor != null; } /** * Returns if this type signature represents an unresolved type. */ public boolean isUnresolved() { return name.startsWith("?"); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final TypeSignature that = (TypeSignature) o; if (!name.equals(that.name)) { return false; } return Objects.equals(namedTypeDescriptor, that.namedTypeDescriptor); } @Override public int hashCode() { return Objects.hash(name, namedTypeDescriptor, typeParameters); } @Override public String toString() { return signature(); } }