org.mutabilitydetector.checkers.CollectionField Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of MutabilityDetector Show documentation
Show all versions of MutabilityDetector Show documentation
Lightweight analysis tool for detecting mutability in Java
classes.
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.asmoverride.AsmCompatibility;
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 getGenericParameterTypes() {
if (root.children.isEmpty()) {
return null;
}
List genericParameters = new ArrayList();
putAllChildren(root, genericParameters);
return unmodifiableList(new ArrayList(genericParameters));
}
private void putAllChildren(Node root, List 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 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 function) {
Node newRoot = transformNode(root, function);
return new GenericCollection(collectionType, newRoot);
}
private Node transformNode(Node node, Function 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 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(AsmCompatibility.AsmApiVersion);
state = new GenericCollectionReaderState();
root = new Node(GenericType.exact(collectionType));
}
private GenericCollectionVisitor(GenericCollectionReaderState state, Node root) {
super(AsmCompatibility.AsmApiVersion);
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 children = new ArrayList();
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();
}
}
}