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

com.github.fluorumlabs.cqt.Suite Maven / Gradle / Ivy

The newest version!
/*
 * 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... rules) { return owner(PredicateUtils.and(rules)); } /** * Predicate testing if {@link Reference} owner object conforms to rule. * * @param rule the rule * * @return the predicate */ public Predicate owner(Predicate rule) { return reference -> rule.test(reference.getOwner()); } /** * Predicate testing if {@link Reference} owner class conforms to rules. * * @param rules the rules * * @return the predicate */ public Predicate ownerType(Predicate>... rules) { return ownerType(PredicateUtils.and(rules)); } /** * Predicate testing if {@link Reference} owner class conforms to rule. * * @param rule the rule * * @return the predicate */ public Predicate ownerType(Predicate> rule) { return reference -> reference.getOwnerClass() != null && rule.test(reference.getOwnerClass()); } /** * Predicate testing if {@link Reference} has specified {@link * ReferenceType}. * * @param first the first * * @return the predicate */ public Predicate referenceTypeIs(ReferenceType first) { return reference -> EnumSet .of(first) .contains(reference.getReferenceType()); } /** * Predicate testing if {@link Reference} has one of specified {@link * ReferenceType}. * * @param first the first * @param referenceTypes the reference types * * @return the predicate */ public Predicate referenceTypeIs(ReferenceType first, ReferenceType... referenceTypes) { return reference -> EnumSet.of( first, referenceTypes ).contains(reference.getReferenceType()); } /** * Predicate testing if {@link Reference} has not specified {@link * ReferenceType}. * * @param first the first * * @return the predicate */ public Predicate referenceTypeIsNot(ReferenceType first) { return reference -> !EnumSet .of(first) .contains(reference.getReferenceType()); } /** * Predicate testing if {@link Reference} has not one of specified {@link * ReferenceType}. * * @param first the first * @param referenceTypes the reference types * * @return the predicate */ public Predicate referenceTypeIsNot(ReferenceType first, ReferenceType... referenceTypes) { return reference -> !EnumSet.of( first, referenceTypes ).contains(reference.getReferenceType()); } /** * Register inspection suite in scanner. * * @param scanner the scanner * * @return the list */ @SuppressWarnings("unchecked") public List register(com.github.fluorumlabs.cqt.internals.Scanner scanner) { this.scanner = scanner; List inspections = new ArrayList<>(); for (Method method : getClass().getMethods()) { if (method.getParameterCount() > 0 || !Predicate.class.isAssignableFrom(method.getReturnType()) || Modifier.isStatic(method.getModifiers())) { continue; } if (method.getAnnotation(Disabled.class) != null) { continue; } Optional report = Arrays .stream(method.getDeclaredAnnotations()) .filter(annotation -> annotation .annotationType() .getAnnotation(Report.class) != null) .findFirst(); report.ifPresent(annotation -> { Report reportAnnotation = annotation .annotationType() .getAnnotation(Report.class); Scopes scopes = method.getAnnotation(Scopes.class); Predicate preFilter = null; if (scopes != null && scopes.value().length > 0) { Optional> filter = Stream.of(scopes.value()).>map(scope -> (Reference reference) -> scope .equals(reference.getScope())).reduce(Predicate::or); preFilter = filter.get(); } if (scopes != null && scopes.exclude().length > 0) { Optional> filter = Stream.of(scopes.exclude()).>map(scope -> (Reference reference) -> !scope .equals(reference.getScope())).reduce(Predicate::and); if (preFilter == null) { preFilter = filter.get(); } else { preFilter = preFilter.and(filter.get()); } } try { Predicate predicate = (Predicate) method .invoke(this); if (preFilter != null) { predicate = preFilter.and(predicate); } String message = (String) annotation .annotationType() .getMethod("value") .invoke(annotation); inspections.add(new Inspection( reportAnnotation.level(), predicate, reportAnnotation.name(), message, getClass().getSimpleName() + "." + method.getName() )); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new IllegalStateException( "Cannot import test suite method", e ); } }); } return inspections; } /** * Predicate testing if {@link Reference} target object conforms to rules. * * @param rules the rules * * @return the predicate */ public Predicate target(Predicate... rules) { return target(PredicateUtils.and(rules)); } /** * Predicate testing if {@link Reference} target object conforms to rule. * * @param rule the rule * * @return the predicate */ public Predicate target(Predicate rule) { return reference -> rule.test(reference.getTarget()); } /** * Predicate testing if any of {@link Reference} target object * backreferences conform to rule. * * @param referencePredicate the reference predicate * * @return the predicate */ public Predicate targetBackreference(Predicate referencePredicate) { return reference -> reference.getTarget() != null && scanner .getBackreferences(reference.getTarget()) .stream() .anyMatch(referencePredicate); } /** * Predicate testing if any of {@link Reference} target object * backreferences conform to rules. * * @param referencePredicates the reference predicates * * @return the predicate */ public Predicate targetBackreference(Predicate... referencePredicates) { return reference -> reference.getTarget() != null && scanner .getBackreferences(reference.getTarget()) .stream() .anyMatch(and(referencePredicates)); } /** * Predicate testing if {@link Reference} target class conforms to rules. * * @param rules the rules * * @return the predicate */ public Predicate targetType(Predicate>... rules) { return targetType(PredicateUtils.and(rules)); } /** * Predicate testing if {@link Reference} target class conforms to rule. * * @param rule the rule * * @return the predicate */ public Predicate targetType(Predicate> rule) { return reference -> reference.getTargetClass() != null && rule.test(reference.getTargetClass()); } /** * Gets scanner. * * @return the scanner */ public Scanner getScanner() { return scanner; } }