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

com.google.errorprone.bugpatterns.overloading.ParameterTrie Maven / Gradle / Ivy

There is a newer version: 2.27.1
Show newest version
/*
 * Copyright 2017 The Error Prone Authors.
 *
 * 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.errorprone.bugpatterns.overloading;

import static java.util.Comparator.comparingInt;

import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.sun.source.tree.MethodTree;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.TreeSet;
import javax.lang.model.element.Name;

/**
 * A simple trie implementation.
 *
 * 

Edges between trie nodes represent method parameters and each path represents some method * signature. This trie is an archetype that next method signatures must follow - we extend the trie * with new signatures reporting all ordering violations along the way. * * @author [email protected] (Łukasz Hanuszczak) */ class ParameterTrie { private final Map children; public ParameterTrie() { this.children = new HashMap<>(); } /** * Extends the trie with parameters of {@code methodTree}. * *

If any ordering {@link ParameterOrderingViolation} is discovered during extension procedure, * they are reported in the result. * *

The method signature is considered to violate the consistency if there exists a path in the * archetype (the trie) that could be used but would require parameter reordering. The extension * algorithm simply walks down the trie as long as possible until the signature has parameters * matching existing edges in the trie. Once this is no longer possible, new nodes are the trie is * extended with remaining parameters. */ public Optional extendAndComputeViolation(MethodTree methodTree) { return new ParameterTrieExtender(methodTree).execute(this); } /** * A convenience class used by {@link ParameterTrie#extendAndComputeViolation(MethodTree)} method. * *

The extension algorithm is inherently stateful. Because threading three state arguments in * every recursive function invocation is inconvenient, we use this class to keep track of the * state in class members. * *

The violation detection works like this: first a method signature (i.e. all the method * parameters) is added to the trie. As long as it is possible the algorithm tries to find a * parameter that can be followed using existing edges in the trie. When no such parameter is * found, the trie extension procedure begins where remaining parameters are added to the trie in * order in which they appear in the initial list. This whole procedure (a path that was followed * during the process) determines a "correct", "consistent" ordering of the parameters. If the * original input list of parameters has a different order that the one determined by the * algorithm a violation is reported. */ private static class ParameterTrieExtender { private final MethodTree methodTree; private final NavigableSet inputParameters; private final List outputParameters; public ParameterTrieExtender(MethodTree methodTree) { this.methodTree = methodTree; this.inputParameters = new TreeSet<>(comparingInt(Parameter::position)); this.outputParameters = new ArrayList<>(); } /** * Extends given {@code trie} with initial {@link MethodTree}. * *

If any {@link ParameterOrderingViolation} is found during the extension procedure, it is * reported in the result. */ public Optional execute(ParameterTrie trie) { Preconditions.checkArgument(trie != null); initialize(); walk(trie); return validate(); } /** * Initializes the input and output parameter collections. * *

After initialization, the input parameters should be equal to parameters of the given * {@link MethodTree} and output parameters should be empty. After processing this should be * reversed: input parameters list should be empty and output parameters should be an ordered * version of parameters of the given {@link MethodTree}. */ private void initialize() { for (int i = 0; i < getMethodTreeArity(methodTree); i++) { inputParameters.add(Parameter.create(methodTree, i)); } outputParameters.clear(); } /** * Processes input parameters by walking along the trie as long as there is a path. * *

Once the walk is no longer possible (there is no trie child that matches one of the input * parameters) the extender begins expansion procedure (see {@link * ParameterTrieExtender#execute(ParameterTrie)}). */ private void walk(ParameterTrie trie) { Preconditions.checkArgument(trie != null); for (Parameter parameter : inputParameters) { if (parameter.tree().isVarArgs() || !trie.children.containsKey(parameter.name())) { continue; } inputParameters.remove(parameter); outputParameters.add(parameter); walk(trie.children.get(parameter.name())); return; } // Walking not possible anymore, start expansion. expand(trie); } /** * Expands the trie with leftover input parameters. * *

It is assumed that given {@code trie} does not have a key corresponding to any input * parameter which should happen only if {@link ParameterTrieExtender#walk(ParameterTrie)} has * nowhere else to go. * *

Only non-varargs parameters are added to the trie. Any varargs parameters should always be * placed at the end of the method signature so it makes no sense to include them in the * archetype. */ private void expand(ParameterTrie trie) { Preconditions.checkArgument(trie != null); if (inputParameters.isEmpty()) { return; } Parameter parameter = inputParameters.first(); inputParameters.remove(parameter); outputParameters.add(parameter); ParameterTrie allocatedTrie = new ParameterTrie(); if (!parameter.tree().isVarArgs()) { trie.children.put(parameter.name(), allocatedTrie); } expand(allocatedTrie); } /** * Reports {@link ParameterOrderingViolation} if output parameters are not ordered as in input * file. * *

This method simply goes through the list of output parameters (input parameters ordered by * trie traversal algorithm). If a parameter at particular position differs from its original * position a violation is reported. * *

If the ordering is consistent then there is no violation and an empty optional is * returned. */ private Optional validate() { ImmutableList.Builder actual = ImmutableList.builder(); ImmutableList.Builder expected = ImmutableList.builder(); boolean valid = true; for (int i = 0; i < outputParameters.size(); i++) { Parameter parameter = outputParameters.get(i); if (parameter.position() != i) { valid = false; } actual.add(parameter.tree()); expected.add(getParameterTree(parameter.position())); } if (valid) { return Optional.empty(); } else { ParameterOrderingViolation violation = ParameterOrderingViolation.builder() .setMethodTree(methodTree) .setActual(actual.build()) .setExpected(expected.build()) .build(); return Optional.of(violation); } } /** Returns a {@link ParameterTree} at {@code position} in the associated method. */ private ParameterTree getParameterTree(int position) { return ParameterTree.create(methodTree.getParameters().get(position)); } } /** * A class used to represent a {@link ParameterTree} (which is essentially just a {@link * com.sun.source.tree.VariableTree} and its original position within the {@link MethodTree}. */ @AutoValue abstract static class Parameter { public abstract ParameterTree tree(); public abstract int position(); public Name name() { return tree().getName(); } public static Parameter create(MethodTree methodTree, int position) { ParameterTree parameterTree = ParameterTree.create(methodTree.getParameters().get(position)); return new AutoValue_ParameterTrie_Parameter(parameterTree, position); } } /** Returns arity (number of parameters) of given {@code methodTree}. */ static int getMethodTreeArity(MethodTree methodTree) { return methodTree.getParameters().size(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy