com.github.fluorumlabs.cqt.Suite Maven / Gradle / Ivy
Show all versions of code-quality-test Show documentation
/*
* Copyright (c) 2020 Artem Godin
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.github.fluorumlabs.cqt;
import com.github.fluorumlabs.cqt.annotations.Report;
import com.github.fluorumlabs.cqt.annotations.Scopes;
import com.github.fluorumlabs.cqt.data.Inspection;
import com.github.fluorumlabs.cqt.data.ReferenceType;
import com.github.fluorumlabs.cqt.internals.CallFinder;
import com.github.fluorumlabs.cqt.internals.ModificationFinder;
import com.github.fluorumlabs.cqt.internals.Scanner;
import com.github.fluorumlabs.cqt.utils.PredicateUtils;
import com.github.fluorumlabs.cqt.utils.Unreflection;
import com.github.fluorumlabs.cqt.annotations.Disabled;
import com.github.fluorumlabs.cqt.data.Reference;
import com.github.fluorumlabs.cqt.internals.ExposedMembers;
import com.github.fluorumlabs.cqt.predicates.AnnotatedElementPredicates;
import com.github.fluorumlabs.cqt.predicates.FieldPredicates;
import com.github.fluorumlabs.cqt.predicates.MemberPredicates;
import com.github.fluorumlabs.cqt.predicates.TypePredicates;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* All inspection suites must extend this class.
*/
public class Suite
implements AnnotatedElementPredicates, FieldPredicates, TypePredicates, MemberPredicates {
private com.github.fluorumlabs.cqt.internals.Scanner scanner;
/**
* Predicate testing if any of {@link Reference} owner object backreferences
* conform to rule.
*
* @param referencePredicate the reference predicate
*
* @return the predicate
*/
public Predicate backreference(Predicate referencePredicate) {
return reference -> scanner
.getBackreferences(reference.getOwner())
.stream()
.anyMatch(referencePredicate);
}
/**
* Predicate testing if any of {@link Reference} owner object backreferences
* conform to rules.
*
* @param referencePredicates the reference predicates
*
* @return the predicate
*/
public Predicate backreference(Predicate... referencePredicates) {
return reference -> scanner
.getBackreferences(reference.getOwner())
.stream()
.anyMatch(and(referencePredicates));
}
/**
* AND predicate.
*
* @param predicates the predicates
*
* @return the predicate
*/
public Predicate and(Predicate... predicates) {
return PredicateUtils.and(predicates);
}
/**
* Predicate testing if {@link Field} methods are called from methods other
* than class initializer.
*
* @param methodNames the method names
*
* @return the predicate
*/
public Predicate calledByNonClassInit(String... methodNames) {
return field -> new CallFinder(
field,
Arrays.asList(methodNames)
).calledByNot("");
}
/**
* Predicate testing if {@link Field} methods are called from methods other
* than class constructor.
*
* @param methodNames the method names
*
* @return the predicate
*/
public Predicate calledByNonConstructor(String... methodNames) {
return field -> new CallFinder(
field,
Arrays.asList(methodNames)
).calledByNot("");
}
/**
* Predicate testing if {@link Reference} {@link Field} conforms to rules.
*
* @param rules the rules
*
* @return the predicate
*/
public Predicate field(Predicate... rules) {
return field(PredicateUtils.and(rules));
}
/**
* Predicate testing if {@link Reference} {@link Field} conforms to rule.
*
* @param rule the rule
*
* @return the predicate
*/
public Predicate field(Predicate rule) {
return ref -> ref.getField() != null && rule.test(ref.getField());
}
/**
* Predicate testing if {@link Reference} {@link Field} is exposed for
* reading.
*
* This means that one of conditions is satisfied: - Field can be read in
* other classes - Field is public - Field is protected - There is a getter
* that returns field value and that getter can be called in other classes
* or is public or protected
*
* @return the predicate
*/
public Predicate fieldIsExposedForReading() {
return field(isReadInOtherClasses()
.or(isPublic())
.or(isProtected())).or(fieldIsExposedViaGetter().and(field(getter(isCalledFromOtherClasses()
.or(isPublic())
.or(isProtected())))));
}
/**
* Predicate testing if {@link Field} is can be read in other classes.
*
* @return the predicate
*/
public Predicate isReadInOtherClasses() {
return ExposedMembers::isFieldExposedForReading;
}
/**
* Predicate testing if {@link Reference} there is a getter for {@link
* Field} that returns field value.
*
* @return the predicate
*/
public Predicate fieldIsExposedViaGetter() {
return reference -> {
if (reference.getField() == null || reference.getOwner() == null) {
return false;
}
return field(getter(method -> {
try {
method.setAccessible(true);
return method.invoke(reference.getOwner())
== reference.getTarget();
} catch (IllegalAccessException | InvocationTargetException e) {
return false;
}
})).test(reference);
};
}
/**
* Predicate testing if {@link Method} is can be called in other classes.
*
* @return the predicate
*/
public Predicate isCalledFromOtherClasses() {
return ExposedMembers::isMethodExposed;
}
/**
* Predicate testing if {@link Reference} {@link Field} is exposed for
* updating.
*
* This means that one of conditions is satisfied: - Field can be updated in
* other classes - Field is public - Field is protected - There is a setter
* and that setter can be called in other classes or is public or protected
*
* @return the predicate
*/
public Predicate fieldIsExposedForUpdating() {
return field(isUpdatedInOtherClasses().or(isPublic()).or(isProtected()))
.or(field(setter(isCalledFromOtherClasses()
.or(isPublic())
.or(isProtected()))));
}
/**
* Predicate testing if {@link Field} is can be updated in other classes.
*
* @return the predicate
*/
public Predicate isUpdatedInOtherClasses() {
return ExposedMembers::isFieldExposedForWriting;
}
/**
* Predicate testing if any of {@link Class} has a method.
*
* @param name the name
* @param args the args
*
* @return the predicate
*/
public Predicate> hasMethod(String name, Class... args) {
return cz -> {
try {
Unreflection.getDeclaredMethod(
cz,
name,
args
);
return true;
} catch (NoSuchMethodException e) {
return false;
}
};
}
/**
* Predicate testing if {@link Reference} has field.
*
* @return the predicate
*/
public Predicate isField() {
return ref -> ref.getField() != null;
}
/**
* Predicate testing if {@link Reference} belongs to scopes.
*
* @param scopes the scopes
*
* @return the predicate
*/
public Predicate isInScope(String... scopes) {
return reference -> {
String data = reference.getScope();
for (String scope : scopes) {
if (scope.equals(data)) {
return true;
}
}
return false;
};
}
/**
* Predicate testing if {@link Reference} has no field.
*
* @return the predicate
*/
public Predicate isNotField() {
return ref -> ref.getField() == null;
}
/**
* Predicate testing if {@link Reference} does not belong to scopes.
*
* @param scopes the scopes
*
* @return the predicate
*/
public Predicate isNotInScope(String... scopes) {
return reference -> {
String data = reference.getScope();
for (String scope : scopes) {
if (scope.equals(data)) {
return false;
}
}
return true;
};
}
/**
* Predicate testing if {@link Field} is updated in methods other than class
* initializer.
*
* @return the predicate
*/
public Predicate modifiedByNonClassInit() {
return field -> new ModificationFinder(field).modifiedByNot("");
}
/**
* Predicate testing if {@link Field} is updated in methods other than class
* constructor.
*
* @return the predicate
*/
public Predicate modifiedByNonConstructor() {
return field -> new ModificationFinder(field).modifiedByNot("");
}
/**
* OR predicate.
*
* @param predicates the predicates
*
* @return the predicate
*/
public Predicate or(Predicate... predicates) {
return PredicateUtils.or(predicates);
}
/**
* Predicate testing if {@link Reference} owner object conforms to rules.
*
* @param rules the rules
*
* @return the predicate
*/
public Predicate owner(Predicate