/*
* Copyright 2012 Henry Coles
*
* 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 org.pitest.mutationtest.engine.gregor.inlinedcode;
import static org.pitest.functional.FCollection.bucket;
import static org.pitest.functional.FCollection.map;
import static org.pitest.functional.FCollection.mapTo;
import static org.pitest.functional.prelude.Prelude.isEqualTo;
import static org.pitest.functional.prelude.Prelude.not;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import org.pitest.functional.F;
import org.pitest.functional.FCollection;
import org.pitest.functional.FunctionalList;
import org.pitest.mutationtest.engine.MutationDetails;
import org.pitest.mutationtest.engine.MutationIdentifier;
import org.pitest.util.Log;
/**
* Detects mutations on same line, but within different code blocks. This
* pattern indicates code inlined for a finally block . . . or normal code that
* creates two blocks on the same line.
*
* Cannot be used with code that uses single line if statements
*/
public class InlinedFinallyBlockDetector implements InlinedCodeFilter {
private static final Logger LOG = Log.getLogger();
@Override
public Collection<MutationDetails> process(
final Collection<MutationDetails> mutations) {
final List<MutationDetails> combined = new ArrayList<MutationDetails>(
mutations.size());
final Map<LineMutatorPair, Collection<MutationDetails>> mutatorLinebuckets = bucket(
mutations, toLineMutatorPair());
for (final Entry<LineMutatorPair, Collection<MutationDetails>> each : mutatorLinebuckets
.entrySet()) {
if (each.getValue().size() > 1) {
checkForInlinedCode(combined, each);
} else {
combined.addAll(each.getValue());
}
}
/** FIXME tests rely on order of returned mutants **/
Collections.sort(combined, compareLineNumbers());
return combined;
}
private static Comparator<MutationDetails> compareLineNumbers() {
return new Comparator<MutationDetails>() {
@Override
public int compare(final MutationDetails arg0, final MutationDetails arg1) {
return arg0.getLineNumber() - arg1.getLineNumber();
}
};
}
private void checkForInlinedCode(final Collection<MutationDetails> combined,
final Entry<LineMutatorPair, Collection<MutationDetails>> each) {
final FunctionalList<MutationDetails> mutationsInHandlerBlock = FCollection
.filter(each.getValue(), isInFinallyHandler());
if (!isPossibleToCorrectInlining(mutationsInHandlerBlock)) {
combined.addAll(each.getValue());
return;
}
final MutationDetails baseMutation = mutationsInHandlerBlock.get(0);
final int firstBlock = baseMutation.getBlock();
// check that we have at least on mutation in a different block
// to the base one (is this not implied by there being only 1 mutation in
// the handler ????)
final FunctionalList<Integer> ids = map(each.getValue(), mutationToBlock());
if (ids.contains(not(isEqualTo(firstBlock)))) {
combined.add(makeCombinedMutant(each.getValue()));
} else {
combined.addAll(each.getValue());
}
}
private boolean isPossibleToCorrectInlining(
final List<MutationDetails> mutationsInHandlerBlock) {
if (mutationsInHandlerBlock.size() > 1) {
LOG.warning("Found more than one mutation similar on same line in a finally block. Can't correct for inlining.");
return false;
}
return !mutationsInHandlerBlock.isEmpty();
}
private static F<MutationDetails, Boolean> isInFinallyHandler() {
return new F<MutationDetails, Boolean>() {
@Override
public Boolean apply(final MutationDetails a) {
return a.isInFinallyBlock();
}
};
}
private static MutationDetails makeCombinedMutant(
final Collection<MutationDetails> value) {
final MutationDetails first = value.iterator().next();
final Set<Integer> indexes = new HashSet<Integer>();
mapTo(value, mutationToIndex(), indexes);
final MutationIdentifier id = new MutationIdentifier(first.getId()
.getLocation(), indexes, first.getId().getMutator());
return new MutationDetails(id, first.getFilename(), first.getDescription(),
first.getLineNumber(), first.getBlock());
}
private static F<MutationDetails, Integer> mutationToIndex() {
return new F<MutationDetails, Integer>() {
@Override
public Integer apply(final MutationDetails a) {
return a.getFirstIndex();
}
};
}
private static F<MutationDetails, Integer> mutationToBlock() {
return new F<MutationDetails, Integer>() {
@Override
public Integer apply(final MutationDetails a) {
return a.getBlock();
}
};
}
private static F<MutationDetails, LineMutatorPair> toLineMutatorPair() {
return new F<MutationDetails, LineMutatorPair>() {
@Override
public LineMutatorPair apply(final MutationDetails a) {
return new LineMutatorPair(a.getLineNumber(), a.getMutator());
}
};
}
}