com.github.sevntu.checkstyle.ordering.MethodOrder.java Source code

Java tutorial

Introduction

Here is the source code for com.github.sevntu.checkstyle.ordering.MethodOrder.java

Source

////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2018 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////

package com.github.sevntu.checkstyle.ordering;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.MultiMapUtils;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.collections4.SetValuedMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.commons.lang.builder.CompareToBuilder;

import com.github.sevntu.checkstyle.domain.Dependencies;
import com.github.sevntu.checkstyle.domain.MethodDefinition;
import com.github.sevntu.checkstyle.domain.ResolvedCall;

public class MethodOrder {

    private final Map<String, Method> methods;

    private final List<Method> currentOrdering;

    private final List<Method> initialOrdering;

    private final Set<MethodInvocation> invocations;

    private final MultiValuedMap<MethodInvocation, MethodInvocation> invocationNesting;

    public MethodOrder(Dependencies dependencies) {
        this.methods = MapUtils.unmodifiableMap(getAllMethods(dependencies));
        this.initialOrdering = ListUtils.unmodifiableList(getInitialMethodOrdering(methods));
        this.currentOrdering = this.initialOrdering;
        final Map<ResolvedCall, MethodInvocation> callsToInvocations = getAllInvocations(dependencies, methods);
        this.invocations = SetUtils.unmodifiableSet(new HashSet<>(callsToInvocations.values()));
        this.invocationNesting = MultiMapUtils
                .unmodifiableMultiValuedMap(getMethodInvocationsNesting(callsToInvocations));
    }

    private MethodOrder(MethodOrder methodOrder, final List<Method> newMethodOrdering) {
        this.currentOrdering = ListUtils.unmodifiableList(newMethodOrdering);
        this.initialOrdering = methodOrder.initialOrdering;
        this.methods = methodOrder.methods;
        this.invocations = methodOrder.invocations;
        this.invocationNesting = methodOrder.invocationNesting;
    }

    public List<Method> getMethods() {
        return currentOrdering;
    }

    public int getMethodIndex(Method method) {
        return currentOrdering.indexOf(method);
    }

    public Method getMethodByInitialIndex(int index) {
        return initialOrdering.get(index);
    }

    public MethodOrder moveMethodBy(Method method, int indexShift) {
        final int currentIndex = getMethodIndex(method);
        final int newIndex = currentIndex + indexShift;
        if (0 <= newIndex && newIndex < methods.size()) {
            final ArrayList<Method> newOrdering = new ArrayList<>(currentOrdering);
            newOrdering.remove(currentIndex);
            newOrdering.add(newIndex, method);
            return new MethodOrder(this, newOrdering);
        } else {
            throw new IllegalArgumentException(
                    String.format("Trying to move method #%d by %d positions", currentIndex, newIndex));
        }
    }

    public MethodOrder reorder(List<Method> order) {
        final boolean allMethodsPresent = currentOrdering.stream().allMatch(order::contains);
        if (allMethodsPresent && currentOrdering.size() == order.size()) {
            return new MethodOrder(this, new ArrayList<>(order));
        } else {
            final String currentOrderingString = methodsSignatureList(currentOrdering);
            final String newOrderingString = methodsSignatureList(order);
            throw new IllegalArgumentException("New ordering contains another set of methods:\n "
                    + "currentOrdering " + currentOrderingString + "\n" + "newOrdering" + newOrderingString);
        }
    }

    private static String methodsSignatureList(Collection<Method> methods) {
        return methods.stream().map(Object::toString).collect(Collectors.joining("; ", "[", "]"));
    }

    public List<Method> getMethodDependenciesInAppearanceOrder(Method caller) {
        return invocations.stream().filter(methodInvocation -> methodInvocation.getCaller().equals(caller))
                .sorted(new AppearanceOrderMethodInvocationComparator())
                .filter(new UniqueCallerCalleeMethodInvocationFilter()).map(MethodInvocation::getCallee)
                .collect(Collectors.toList());
    }

    public List<Integer> getMethodDependenciesIndexesInAppearanceOrder(Method caller) {
        return getMethodDependenciesInAppearanceOrder(caller).stream().map(this::getMethodIndex)
                .collect(Collectors.toList());
    }

    public boolean hasMethodDependencies(Method method) {
        return !getMethodDependenciesInAppearanceOrder(method).isEmpty();
    }

    public List<Method> getMethodDependants(Method callee) {
        return invocations.stream().filter(methodInvocation -> methodInvocation.getCallee().equals(callee))
                .filter(new UniqueCallerCalleeMethodInvocationFilter()).map(MethodInvocation::getCaller)
                .collect(Collectors.toList());
    }

    public boolean hasMethodDependants(Method method) {
        return !getMethodDependants(method).isEmpty();
    }

    public boolean isInterfaceMethod(Method method) {
        return method.getAccessibility() == MethodDefinition.Accessibility.PUBLIC && !hasMethodDependencies(method)
                && !hasMethodDependants(method);
    }

    public boolean isMethodDependsOn(Method caller, Method callee) {
        return invocations.stream().anyMatch(mi -> mi.getCaller().equals(caller) && mi.getCallee().equals(callee));
    }

    public int getMethodsIndexDifference(Method caller, Method callee) {
        return getMethodIndex(callee) - getMethodIndex(caller);
    }

    public int getMethodsLineDifference(Method caller, Method callee) {
        return translateInitialLineNo(callee.getInitialLineNo())
                - translateInitialLineNo(caller.getInitialLineNo());
    }

    public int getTotalSumOfMethodDistances() {
        return currentOrdering.stream().collect(Collectors.summingInt(caller -> {
            return getMethodDependenciesInAppearanceOrder(caller).stream().collect(Collectors.summingInt(callee -> {
                return Math.abs(getMethodsIndexDifference(caller, callee));
            }));
        }));
    }

    public int getDeclarationBeforeUsageCases() {
        return (int) invocations.stream().filter(new UniqueCallerCalleeMethodInvocationFilter())
                .filter(invocation -> {
                    return getMethodsIndexDifference(invocation.getCaller(), invocation.getCallee()) < 0;
                }).count();
    }

    public int getCtorGroupsSplitCases() {
        final int result;
        final List<Method> constructors = currentOrdering.stream().filter(Method::isCtor)
                .collect(Collectors.toList());
        if (constructors.isEmpty()) {
            result = 0;
        } else {
            result = getMethodGroupSplitCount(constructors);
        }
        return result;
    }

    public int getOverrideGroupSplitCases() {
        final int result;
        final List<Method> overrideMethods = currentOrdering.stream().filter(Method::isOverride)
                .collect(Collectors.toList());
        if (overrideMethods.isEmpty()) {
            result = 0;
        } else {
            result = getMethodGroupSplitCount(overrideMethods);
        }
        return result;
    }

    public int getOverloadGroupsSplitCases() {
        return currentOrdering.stream().filter(method -> !method.isCtor())
                .collect(Collectors.groupingBy(Method::getName)).values().stream()
                .collect(Collectors.summingInt(this::getMethodGroupSplitCount));
    }

    public int getDependenciesBetweenDistantMethodsCases(int screenLinesCount) {
        return invocations.stream().collect(Collectors.groupingBy(MethodInvocation::getCaller)).values().stream()
                .collect(Collectors
                        .summingInt(callerInvocations -> (int) callerInvocations.stream().filter(invocation -> {
                            final int invocationLineNo = translateInitialLineNo(invocation.getInitialLineNo());
                            final int calleeLineNo = translateInitialLineNo(
                                    invocation.getCallee().getInitialLineNo());
                            return Math.abs(calleeLineNo - invocationLineNo) > screenLinesCount;
                        }).filter(new UniqueCallerCalleeMethodInvocationFilter()).count()));
    }

    public int getAccessorsSplitCases() {
        return currentOrdering.stream().filter(method -> method.isGetter() || method.isSetter())
                .collect(Collectors.groupingBy(Method::getAccessiblePropertyName)).values().stream()
                .collect(Collectors.summingInt(this::getMethodGroupSplitCount));
    }

    public int getRelativeOrderInconsistencyCases() {
        return getMethods().stream().collect(Collectors.summingInt(caller -> {
            int maxCalleeIndex = 0;
            int orderViolations = 0;
            for (final Method callee : getMethodDependenciesInAppearanceOrder(caller)) {
                final int calleeIndex = getMethodIndex(callee);
                if (calleeIndex < maxCalleeIndex) {
                    ++orderViolations;
                } else {
                    maxCalleeIndex = calleeIndex;
                }
            }
            return orderViolations;
        }));
    }

    private int getMethodGroupSplitCount(Collection<Method> methodGroup) {
        final List<Integer> methodIndices = methodGroup.stream().map(this::getMethodIndex)
                .collect(Collectors.toList());
        final MinMax<Integer> bounds = minMax(methodIndices);
        return bounds.getMax() - bounds.getMin() - methodIndices.size() + 1;
    }

    private int translateInitialLineNo(int lineNo) {
        return currentOrdering.stream().filter(method -> {
            final int start = method.getInitialLineNo();
            final int end = start + method.getLength();
            return start <= lineNo && lineNo <= end;
        }).findFirst().map(method -> {
            final int initialMethodIndex = initialOrdering.indexOf(method);
            final int currentMethodIndex = currentOrdering.indexOf(method);
            final int sumOfLengthsPresidingMethodsInInitialOrder = initialOrdering.subList(0, initialMethodIndex)
                    .stream().collect(Collectors.summingInt(Method::getLength));
            final int sumOfLengthsPresidingMethodInCurrentOrder = currentOrdering.subList(0, currentMethodIndex)
                    .stream().collect(Collectors.summingInt(Method::getLength));
            final int change = sumOfLengthsPresidingMethodInCurrentOrder
                    - sumOfLengthsPresidingMethodsInInitialOrder;
            return lineNo + change;
        }).orElseThrow(
                () -> new IllegalArgumentException(String.format("Line #%d does lies within any method", lineNo)));
    }

    private static Map<String, Method> getAllMethods(Dependencies dependencies) {
        return dependencies.getMethods().stream()
                .collect(Collectors.toMap(MethodDefinition::getSignature, Method::new));
    }

    private static Map<ResolvedCall, MethodInvocation> getAllInvocations(Dependencies dependencies,
            Map<String, Method> methods) {

        return dependencies.getResolvedCalls().stream()
                .collect(Collectors.toMap(Function.identity(), resolvedCall -> {
                    final String callerSignature = resolvedCall.getCaller().getSignature();
                    final String calleeSignature = resolvedCall.getCallee().getSignature();
                    return new MethodInvocation(resolvedCall, methods.get(callerSignature),
                            methods.get(calleeSignature));
                }));
    }

    private static MultiValuedMap<MethodInvocation, MethodInvocation> getMethodInvocationsNesting(
            Map<ResolvedCall, MethodInvocation> callToInvocation) {

        final SetValuedMap<MethodInvocation, MethodInvocation> nestedInside = new HashSetValuedHashMap<>();
        callToInvocation.entrySet().stream().forEach(entry -> {
            final ResolvedCall resolvedCall = entry.getKey();
            final MethodInvocation methodInvocation = entry.getValue();
            callToInvocation.keySet().stream().filter(rc -> !rc.equals(resolvedCall))
                    .filter(resolvedCall::isNestedInside)
                    .forEach(rc -> nestedInside.put(methodInvocation, callToInvocation.get(rc)));
        });
        return nestedInside;
    }

    private static List<Method> getInitialMethodOrdering(Map<String, Method> methods) {
        return methods.values().stream()
                .sorted((lhs, rhs) -> Integer.compare(lhs.getInitialIndex(), rhs.getInitialIndex()))
                .collect(Collectors.toList());
    }

    private static <T> MinMax<T> minMax(Collection<T> elements) {
        final SortedSet<T> sortedSet = new TreeSet<>(elements);
        return new MinMax<>(sortedSet.first(), sortedSet.last());
    }

    private static final class MinMax<T> {

        private final T min;

        private final T max;

        private MinMax(T min, T max) {
            this.min = min;
            this.max = max;
        }

        public T getMin() {
            return min;
        }

        public T getMax() {
            return max;
        }
    }

    private class AppearanceOrderMethodInvocationComparator implements Comparator<MethodInvocation> {

        @Override
        public int compare(MethodInvocation lhs, MethodInvocation rhs) {
            final int result;
            if (invocationNesting.containsMapping(lhs, rhs)) {
                result = -1;
            } else if (invocationNesting.containsMapping(rhs, lhs)) {
                result = 1;
            } else {
                result = new CompareToBuilder()
                        .append(translateInitialLineNo(lhs.getInitialLineNo()),
                                translateInitialLineNo(rhs.getInitialLineNo()))
                        .append(lhs.getColumnNo(), rhs.getColumnNo()).toComparison();
            }
            return result;
        }
    }

    private static class UniqueCallerCalleeMethodInvocationFilter implements Predicate<MethodInvocation> {

        private final Set<MethodInvocation> set = new TreeSet<>(new Comparator<MethodInvocation>() {
            @Override
            public int compare(MethodInvocation lhs, MethodInvocation rhs) {
                return new CompareToBuilder().append(lhs.getCaller().getSignature(), rhs.getCaller().getSignature())
                        .append(lhs.getCallee().getSignature(), rhs.getCallee().getSignature()).toComparison();
            }
        });

        /* This class is not unit test. Method name similarity is just accidence. */
        @SuppressWarnings("PMD.JUnit4TestShouldUseTestAnnotation")
        @Override
        public boolean test(MethodInvocation methodInvocation) {
            return set.add(methodInvocation);
        }
    }
}