Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.sonar.java.checks.helpers.logic;

import org.sonar.plugins.java.api.semantic.Symbol;

import static org.sonar.java.checks.helpers.logic.Ternary.UNKNOWN;

/**
* Demo for {@link Ternary}
*/
public class HelperPredicates {
private HelperPredicates() {}

public static Ternary isUsed(Symbol symbol) {
if(symbol.isUnknown()) {
return UNKNOWN;
}
return Ternary.of(!symbol.usages().isEmpty());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.sonar.java.checks.helpers.logic;

import static org.sonar.java.checks.helpers.logic.Ternary.FALSE;
import static org.sonar.java.checks.helpers.logic.Ternary.TRUE;
import static org.sonar.java.checks.helpers.logic.Ternary.UNKNOWN;

/**
* Utility to for implementing logical "and" and "or" in {@link Ternary}, both
* directly and as a {@link java.util.stream.Collector}.
*/
class Summary {
private boolean allTrue = true;
private boolean anyTrue = false;
private boolean allFalse = true;
private boolean anyFalse = false;

void add(Ternary arg) {
allTrue &= arg.is(true);
anyTrue |= arg.is(true);
allFalse &= arg.is(false);
anyFalse |= arg.is(false);
}

Summary addAll(Ternary... args) {
for (Ternary arg : args) {
add(arg);
}
return this;
}

Summary combine(Summary other) {
allTrue &= other.allTrue;
anyTrue |= other.anyTrue;
allFalse &= other.allFalse;
anyFalse |= other.anyFalse;
return this;
}

Ternary logicalAnd() {
if (allTrue) {
return TRUE;
} else if (anyFalse) {
return FALSE;
} else {
return UNKNOWN;
}
}

Ternary logicalOr() {
if (allFalse) {
return FALSE;
} else if (anyTrue) {
return TRUE;
} else {
return UNKNOWN;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.sonar.java.checks.helpers.logic;

import java.util.stream.Collector;

/**
* Implementation of tree-valued logic in which a proposition can be true, false, or unknown.
*
* <p>This class allows us to be precise in certain predicates, for instance
* {@code type.isSubtypeOf("com.example.MySuper")}, where the exact value is unknown
* due to missing semantics.
*/
public enum Ternary {
TRUE, FALSE, UNKNOWN;

/**
* Adapter for {@code boolean}.
*/
public static Ternary of(boolean value) {
return value ? TRUE : FALSE;
}

/**
* Adapter for {@code boolean} where {@code null} means {@code UNKNOWN}.
*/
public static Ternary ofNullable(Boolean value) {
return value == null ? UNKNOWN : of(value);
}

/**
* Checks whether the object is exactly {@code TRUE} or {@code FALSE}.
*/
public boolean is(boolean value) {
if (value) {
return this == TRUE;
} else {
return this == FALSE;
}
}

/**
* The value is known and true.
*/
public boolean isTrue() {
return this == TRUE;
}

/**
* The value is known and false.
*/
public boolean isFalse() {
return this == FALSE;
}

public boolean maybeTrue() {
return this == TRUE || this == UNKNOWN;
}

public boolean maybeFalse() {
return this == FALSE || this == UNKNOWN;
}

/**
* Negation. Unknown stays unknown, otherwise the usual boolean logic.
*/
public Ternary not() {
return switch (this) {
case TRUE -> FALSE;
case FALSE -> TRUE;
case UNKNOWN -> UNKNOWN;
};
}

/**
* Alternative:
* <ul>
* <li> {@code TRUE} if any argument is true,
* <li> {@code FALSE} if all arguments are false,
* <li> {@code UNKNOWN} otherwise.
* </ul>
*/
public static Ternary or(Ternary... args) {
return new Summary().addAll(args).logicalOr();
}

/**
* Conjunction:
* <ul>
* <li> {@code TRUE} if all arguments are true,
* <li> {@code FALSE} if any argument is false,
* <li> {@code UNKNOWN} otherwise.
* </ul>
*/
public static Ternary and(Ternary... args) {
return new Summary().addAll(args).logicalAnd();
}

/**
* Returns a collector creating a new ternary value following the logic of {@link #and(Ternary...)}.
*/
public static Collector<Ternary, Summary, Ternary> and() {
return Collector.of(Summary::new, Summary::add, Summary::combine, Summary::logicalAnd);
}

/**
* Returns a collector creating a new ternary value following the logic of {@link #or(Ternary...)}.
*/
public static Collector<Ternary, Summary, Ternary> or() {
return Collector.of(Summary::new, Summary::add, Summary::combine, Summary::logicalOr);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.sonar.java.checks.helpers.logic;

import java.util.List;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.java.checks.helpers.logic.Ternary.FALSE;
import static org.sonar.java.checks.helpers.logic.Ternary.TRUE;
import static org.sonar.java.checks.helpers.logic.Ternary.UNKNOWN;
import static org.sonar.java.checks.helpers.logic.Ternary.and;
import static org.sonar.java.checks.helpers.logic.Ternary.or;


class TernaryTest {
@Test
void test_from_boolean_conversion() {
assertThat(Ternary.of(true)).isEqualTo(TRUE);
assertThat(Ternary.ofNullable(true)).isEqualTo(TRUE);

assertThat(Ternary.of(false)).isEqualTo(FALSE);
assertThat(Ternary.ofNullable(false)).isEqualTo(FALSE);

assertThat(Ternary.ofNullable(null)).isEqualTo(UNKNOWN);
}

@Test
void test_or() {
assertThat(or(TRUE, FALSE, UNKNOWN)).isEqualTo(TRUE);
assertThat(or(FALSE, UNKNOWN)).isEqualTo(UNKNOWN);
assertThat(or(FALSE, FALSE)).isEqualTo(FALSE);
}

@Test
void test_and() {
assertThat(and(TRUE, FALSE, UNKNOWN)).isEqualTo(FALSE);
assertThat(and(TRUE, UNKNOWN)).isEqualTo(UNKNOWN);
assertThat(and(TRUE, TRUE)).isEqualTo(TRUE);
}

@Test
void test_collectors() {
List<Ternary> tfu = List.of(TRUE, FALSE, UNKNOWN);
assertThat(tfu.stream().collect(and())).isEqualTo(FALSE);
assertThat(tfu.stream().collect(or())).isEqualTo(TRUE);

List<Ternary> uut = List.of(UNKNOWN, UNKNOWN, TRUE);
assertThat(uut.stream().collect(and())).isEqualTo(UNKNOWN);
assertThat(uut.stream().collect(or())).isEqualTo(TRUE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.sonar.plugins.java.api.tree.TypeParameterTree;
import org.sonar.plugins.java.api.tree.TypeParameters;

import static org.sonar.java.checks.helpers.logic.HelperPredicates.isUsed;

@Rule(key = "S2326")
public class UnusedTypeParameterCheck extends IssuableSubscriptionVisitor {

Expand All @@ -43,7 +45,7 @@ public void visitNode(Tree tree) {
TypeParameters typeParameters = tree.is(Tree.Kind.METHOD) ? ((MethodTree) tree).typeParameters() : ((ClassTree) tree).typeParameters();
for (TypeParameterTree typeParameter : typeParameters) {
Symbol symbol = typeParameter.symbol();
if (!symbol.isUnknown() && symbol.usages().isEmpty()) {
if(isUsed(symbol).isFalse()) {
String message = String.format(ISSUE_MESSAGE, symbol.name(), tree.kind().name().toLowerCase(Locale.ROOT));
reportIssue(typeParameter.identifier(), message);
}
Expand Down