All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mutabilitydetector.checkers.CollectionField Maven / Gradle / Ivy

There is a newer version: 0.10.6
Show newest version
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 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 genericTypeStringFunction,
                                              Function 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();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy