/*
* 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.devtools.cyclefinder;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
/**
* Generates various kinds of names for types:
* - Unique type signature for identifying nodes.
* - Qualified names used by whitelists.
* - Readable names for printing edges.
*
* @author Keith Stanger
*/
public class NameUtil {
private final ElementUtil elementUtil;
private final TypeUtil typeUtil;
private final Map<Element, String> captureNames = new HashMap<>();
private static int captureCount = 1;
public NameUtil(TypeUtil typeUtil) {
this.elementUtil = typeUtil.elementUtil();
this.typeUtil = typeUtil;
}
/**
* Generates a unique signature for this type that can be used as a key.
*/
public String getSignature(TypeMirror type) {
StringBuilder sb = new StringBuilder();
buildTypeSignature(type, sb);
return sb.toString();
}
private void buildTypeSignature(TypeMirror type, StringBuilder sb) {
switch (type.getKind()) {
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
case VOID:
sb.append(TypeUtil.getBinaryName(type));
break;
case ARRAY:
sb.append('[');
buildTypeSignature(((ArrayType) type).getComponentType(), sb);
break;
case DECLARED:
buildDeclaredType((DeclaredType) type, sb);
sb.append(';');
break;
case TYPEVAR:
buildTypeVariable((TypeVariable) type, sb);
break;
case INTERSECTION:
sb.append("&{");
for (TypeMirror bound : ((IntersectionType) type).getBounds()) {
buildTypeSignature(bound, sb);
}
sb.append('}');
break;
case WILDCARD:
TypeMirror upperBound = ((WildcardType) type).getExtendsBound();
TypeMirror lowerBound = ((WildcardType) type).getSuperBound();
if (upperBound != null) {
sb.append('+');
buildTypeSignature(upperBound, sb);
} else if (lowerBound != null) {
sb.append('-');
buildTypeSignature(lowerBound, sb);
} else {
sb.append('*');
}
break;
default:
throw new AssertionError("Unexpected type kind: " + type.getKind());
}
}
private void buildDeclaredType(DeclaredType type, StringBuilder sb) {
TypeMirror enclosing = type.getEnclosingType();
if (TypeUtil.isNone(enclosing)) {
sb.append('L');
buildElementSignature(type.asElement(), sb);
} else {
buildDeclaredType((DeclaredType) enclosing, sb);
String binaryName = elementUtil.getBinaryName(TypeUtil.asTypeElement(type));
String enclosingBinaryName = elementUtil.getBinaryName(TypeUtil.asTypeElement(enclosing));
assert binaryName.startsWith(enclosingBinaryName + "$");
sb.append('.').append(binaryName.substring(enclosingBinaryName.length() + 1));
}
buildTypeArguments(type, sb);
}
private void buildTypeArguments(DeclaredType type, StringBuilder sb) {
Element elem = type.asElement();
List<? extends TypeMirror> typeArguments = type.getTypeArguments();
if (!typeArguments.isEmpty()) {
sb.append('<');
for (TypeMirror typeArg : typeArguments) {
TypeParameterElement typeParam = TypeUtil.asTypeParameterElement(typeArg);
if (typeParam != null && elem.equals(typeParam.getEnclosingElement())) {
// The type param is directly declared by the type being emitted so we don't need to fully
// qualify it as buildTypeSignature() would.
sb.append('T');
sb.append(ElementUtil.getName(typeParam));
sb.append(';');
} else {
buildTypeSignature(typeArg, sb);
}
}
sb.append('>');
}
}
private void buildTypeVariable(TypeVariable type, StringBuilder sb) {
TypeParameterElement typeParam = (TypeParameterElement) type.asElement();
if (isCapture(typeParam)) {
String name = captureNames.get(typeParam);
if (name == null) {
name = "!CAP" + captureCount++ + '!';
captureNames.put(typeParam, name);
}
sb.append(name);
} else {
sb.append("T:");
buildElementSignature(typeParam.getEnclosingElement(), sb);
sb.append(':').append(ElementUtil.getName(typeParam)).append(';');
}
}
private void buildElementSignature(Element elem, StringBuilder sb) {
if (ElementUtil.isTypeElement(elem)) {
sb.append(elementUtil.getBinaryName((TypeElement) elem).replace('.', '/'));
} else if (ElementUtil.isExecutableElement(elem)) {
buildExecutableElementSignature((ExecutableElement) elem, sb);
} else {
throw new AssertionError("Unexpected element: " + elem + " with kind: " + elem.getKind());
}
}
private void buildExecutableElementSignature(ExecutableElement elem, StringBuilder sb) {
buildElementSignature(elem.getEnclosingElement(), sb);
sb.append('.').append(ElementUtil.getName(elem)).append('(');
for (VariableElement var : elem.getParameters()) {
buildTypeSignature(typeUtil.erasure(var.asType()), sb);
}
sb.append(')');
}
/**
* Generates a readable name for this type.
*/
public static String getName(TypeMirror type) {
StringBuilder sb = new StringBuilder();
buildTypeName(type, sb);
return sb.toString();
}
private static void buildTypeName(TypeMirror type, StringBuilder sb) {
switch (type.getKind()) {
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
sb.append(TypeUtil.getName(type));
break;
case ARRAY:
buildTypeName(((ArrayType) type).getComponentType(), sb);
sb.append("[]");
break;
case DECLARED: {
sb.append(ElementUtil.getName(TypeUtil.asTypeElement(type)));
List<? extends TypeMirror> typeArguments = ((DeclaredType) type).getTypeArguments();
if (!typeArguments.isEmpty()) {
sb.append('<');
boolean first = true;
for (TypeMirror typeArg : typeArguments) {
if (!first) {
sb.append(", ");
}
first = false;
buildTypeName(typeArg, sb);
}
sb.append('>');
}
break;
}
case TYPEVAR:
sb.append(ElementUtil.getName(((TypeVariable) type).asElement()));
break;
case INTERSECTION: {
boolean first = true;
for (TypeMirror bound : ((IntersectionType) type).getBounds()) {
if (!first) {
sb.append(" & ");
}
first = false;
buildTypeName(bound, sb);
}
break;
}
case WILDCARD: {
sb.append('?');
TypeMirror extendsBound = ((WildcardType) type).getExtendsBound();
TypeMirror superBound = ((WildcardType) type).getSuperBound();
if (extendsBound != null) {
sb.append(" extends ");
buildTypeName(extendsBound, sb);
}
if (superBound != null) {
sb.append(" super ");
buildTypeName(superBound, sb);
}
break;
}
default:
throw new AssertionError("Unexpected type: " + type + " with kind: " + type.getKind());
}
}
/**
* Gets the qualified name for this type, as expected by a NameList instance.
*/
public static String getQualifiedName(TypeMirror type) {
switch (type.getKind()) {
case DECLARED:
return getQualifiedNameForTypeElement((TypeElement) ((DeclaredType) type).asElement());
case TYPEVAR:
return getQualifiedNameForElement(((TypeVariable) type).asElement());
case INTERSECTION:
return "&";
case WILDCARD:
return "?";
default:
throw new AssertionError("Unexpected type: " + type + " with kind: " + type.getKind());
}
}
private static String getQualifiedNameForTypeElement(TypeElement type) {
switch (type.getNestingKind()) {
case ANONYMOUS:
return getQualifiedNameForElement(type.getEnclosingElement()) + ".$";
case LOCAL:
return getQualifiedNameForElement(type.getEnclosingElement()) + '.'
+ ElementUtil.getName(type);
default:
return ElementUtil.getQualifiedName(type);
}
}
private static String getQualifiedNameForElement(Element e) {
if (isCapture(e)) {
return "!";
} else if (ElementUtil.isTypeElement(e)) {
return getQualifiedNameForTypeElement((TypeElement) e);
} else if (ElementUtil.isExecutableElement(e) || ElementUtil.isTypeParameterElement(e)) {
return getQualifiedNameForElement(e.getEnclosingElement()) + '.' + ElementUtil.getName(e);
}
return getQualifiedNameForElement(e.getEnclosingElement());
}
private static boolean isCapture(Element e) {
return ElementUtil.isTypeParameterElement(e)
&& e.getEnclosingElement().getKind() == ElementKind.OTHER;
}
}