package org.mutabilitydetector.checkers; /* * #%L * MutabilityDetector * %% * Copyright (C) 2008 - 2014 Graham Allan * %% * 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. * #L% */ import com.google.common.base.Objects; import org.mutabilitydetector.locations.Dotted; import org.objectweb.asm.Opcodes; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.valueOf; import static java.util.Collections.unmodifiableList; import static org.mutabilitydetector.locations.Dotted.dotted; import static org.mutabilitydetector.locations.Dotted.fromClass; public abstract class CollectionField { public final Dotted collectionType; public final Node root; /** * Get flat list of generics tree represented by {@code root} */ public List<GenericType> getGenericParameterTypes() { if (root.children.isEmpty()) { return null; } List<GenericType> genericParameters = new ArrayList<GenericType>(); putAllChildren(root, genericParameters); return unmodifiableList(new ArrayList<GenericType>(genericParameters)); } private void putAllChildren(Node root, List<GenericType> genericParameters) { for (Node child : root.children) { genericParameters.add(child.type); putAllChildren(child, genericParameters); } } private static final class RawCollection extends CollectionField { private RawCollection(Dotted collectionType) { super(collectionType, Node.dummyRoot()); } @Override public boolean isGeneric() { return false; } public String asString() { return "raw " + collectionType.asString(); } @Override public String asSimpleString() { return "raw " + collectionType.asSimpleString(); } @Override public CollectionField transformGenericTree(Function<GenericType, GenericType> function) { return this; } } private static final class GenericCollection extends CollectionField { public GenericCollection(Dotted collectionType, Node root) { super(collectionType, root); } @Override public boolean isGeneric() { return true; } public String asString() { return root.asString(); } @Override public String asSimpleString() { return root.asSimpleString(); } @Override public CollectionField transformGenericTree(Function<GenericType, GenericType> function) { Node newRoot = transformNode(root, function); return new GenericCollection(collectionType, newRoot); } private Node transformNode(Node node, Function<GenericType, GenericType> function) { Node transformed = new Node(function.apply(node.type)); for (Node child : node.children) { transformed.addChild(transformNode(child, function)); } return transformed; } } public abstract boolean isGeneric(); public abstract String asString(); /** * Similar to {@code CollectionField#asString}, but uses unqualified class names */ public abstract String asSimpleString(); protected CollectionField(Dotted collectionType, Node root) { this.collectionType = collectionType; this.root = root; } public static CollectionField from(String collectionType, String signature) { if (signature == null) { return new RawCollection(dotted(collectionType)); } GenericCollectionVisitor collectionTypeReader = new GenericCollectionVisitor(dotted(collectionType)); new SignatureReader(signature).accept(collectionTypeReader); return new GenericCollection(collectionTypeReader.state.collectionType, collectionTypeReader.root); } /** * Apply function to all generics tree nodes and return resulting tree */ public abstract CollectionField transformGenericTree(Function<GenericType, GenericType> function); /** * Constructs generics tree by visiting signature */ private static class GenericCollectionVisitor extends SignatureVisitor { public static final String OBJECT_ARRAY_PREFIX = "[L"; public static final String PRIMITIVE_ARRAY_PREFIX = "["; private GenericCollectionReaderState state; private Node root; private Node lastStored; public GenericCollectionVisitor(Dotted collectionType) { super(Opcodes.ASM5); state = new GenericCollectionReaderState(); root = new Node(GenericType.exact(collectionType)); } private GenericCollectionVisitor(GenericCollectionReaderState state, Node root) { super(Opcodes.ASM5); this.state = state; this.root = root; } @Override public void visitClassType(String name) { if (!state.seenOuterCollectionType) { state.collectionType = dotted(name); state.seenOuterCollectionType = true; } else { state.elementType = state.isElementTypeArray ? dotted(OBJECT_ARRAY_PREFIX + name) : dotted(name); storeNode(); } } @Override public void visitTypeVariable(String name) { state.typeVariable = dotted(name); storeNode(); } @Override public void visitTypeArgument() { state.wildcard = "?"; state.elementType = null; storeNode(); } @Override public void visitBaseType(char descriptor) { if (state.isElementTypeArray) { state.elementType = dotted(PRIMITIVE_ARRAY_PREFIX + descriptor); storeNode(); } else { throw new IllegalStateException("It shouldn't happen. Java doesn't support primitive generic types"); } } @Override public SignatureVisitor visitArrayType() { state.isElementTypeArray = true; return withRoot(firstNonNull(lastStored, root)); } @Override public SignatureVisitor visitTypeArgument(char wildcard) { state.wildcard = valueOf(wildcard); return withRoot(firstNonNull(lastStored, root)); } private void storeNode() { lastStored = new Node(createGenericType()); root.addChild(lastStored); } private GenericType createGenericType() { boolean isVariable = state.typeVariable != null; return new GenericType(isVariable ? state.typeVariable : state.elementType, state.wildcard, isVariable, state.isElementTypeArray); } /** * Return visitor representing child node with the same state */ private GenericCollectionVisitor withRoot(Node newRoot) { return new GenericCollectionVisitor(state, newRoot); } private static final class GenericCollectionReaderState { protected Dotted collectionType; protected Dotted elementType; protected Dotted typeVariable; protected String wildcard; protected boolean seenOuterCollectionType = false; boolean isElementTypeArray = false; } } public static class GenericType { public final Dotted type; public final String wildcard; public final boolean isVariable; public final boolean isArray; public GenericType(Dotted type, String wildcard, boolean isVariable, boolean isArray) { this.type = type; this.wildcard = checkNotNull(wildcard, "wildcard"); this.isVariable = isVariable; this.isArray = isArray; } public static GenericType wildcard() { return new GenericType(null, "?", false, false); } public static GenericType exact(Dotted type) { return new GenericType(type, "=", false, false); } public static GenericType extends_(Dotted type) { return new GenericType(type, "+", false, false); } public static GenericType super_(Dotted type) { return new GenericType(type, "-", false, false); } @Override public int hashCode() { return Objects.hashCode(type, wildcard); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } GenericType other = (GenericType) obj; if (type == null) { if (other.type != null) { return false; } } else if (!type.equals(other.type)) { return false; } if (wildcard == null) { if (other.wildcard != null) { return false; } } else if (!wildcard.equals(other.wildcard)) { return false; } return true; } @Override public String toString() { return toStringWithFunction(Object::toString); } /** * Similar to {@code GenericType#asString} but uses unqualified class names */ public String asSimpleString() { return toStringWithFunction(Dotted::asSimpleString); } private String toStringWithFunction(Function<? super Dotted, String> toStringFunction) { if (type == null) { return wildcard; } else { if (wildcard.equals("=")) { return toStringFunction.apply(type); } else if (wildcard.equals("+")) { return "? extends " + toStringFunction.apply(type); } else if (wildcard.equals("-")) { return "? super " + toStringFunction.apply(type); } } throw new IllegalStateException(); } public GenericType withoutWildcard() { if ("?".equals(wildcard)) { return new GenericType(fromClass(Object.class), "=", false, false); } return new GenericType(type, "=", false, false); } } /** * Represents a node of generic types tree in type declaration */ private static final class Node { public final List<Node> children = new ArrayList<Node>(); public final GenericType type; public static Node dummyRoot() { return new Node(null); } public Node(GenericType type) { this.type = type; } public void addChild(Node child) { children.add(checkNotNull(child)); } public String asString() { return asStringUsingFunctions(Object::toString, Object::toString); } /** * Similar to {@code GenericType#asString} but uses unqualified class names */ public String asSimpleString() { return asStringUsingFunctions(GenericType::asSimpleString, Node::asSimpleString); } private String asStringUsingFunctions(Function<? super GenericType, String> genericTypeStringFunction, Function<? super Node, String> nodeStringFunction) { String childrenPart = ""; if (children.size() > 0) { childrenPart = children.stream() .map(nodeStringFunction) .collect(Collectors.joining(", ", "<", ">")); } return genericTypeStringFunction.apply(type) + childrenPart; } @Override public String toString() { return asString(); } } }