Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added visibility matchers to check that a class isPublic(), etc. #291

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
57 changes: 57 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/reflection/Visibility.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.hamcrest.reflection;

import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.Objects;

/**
* Represents the 4 states of visibility.
*
* @author JJ Brown
*/
enum Visibility {
PUBLIC("public"),
PROTECTED("protected"),
PACKAGE_PROTECTED("package-protected (no modifiers)"),
PRIVATE("private");

public String getDescription() {
return description;
}

private final String description;

Visibility(String description) {
this.description = description;
}

static Visibility of(Class<?> clazz) {
Objects.requireNonNull(clazz, "Cannot determine the visibility of a null-valued reflective Class object");

if (Modifier.isPublic(clazz.getModifiers())) {
return Visibility.PUBLIC;
}
if (Modifier.isProtected(clazz.getModifiers())) {
return Visibility.PROTECTED;
}
if (Modifier.isPrivate(clazz.getModifiers())) {
return Visibility.PRIVATE;
}
return Visibility.PACKAGE_PROTECTED;
}

static Visibility of(Member member) {
Objects.requireNonNull(member, "Cannot determine the visibility of a null-valued reflective member object");

if (Modifier.isPublic(member.getModifiers())) {
return Visibility.PUBLIC;
}
if (Modifier.isProtected(member.getModifiers())) {
return Visibility.PROTECTED;
}
if (Modifier.isPrivate(member.getModifiers())) {
return Visibility.PRIVATE;
}
return Visibility.PACKAGE_PROTECTED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.hamcrest.reflection;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;

import java.lang.reflect.Member;

/**
* Matches the visibility of a reflective element, like a {@link Class} or a {@link java.lang.reflect.Method},
* to make assertions about the scope of a module's API.
* <p>
* This class is intentionally not exposed to the public API, to help keep implementation details hidden (and easy to change).
* Please use {@link VisibilityMatchers} to instantiate instances of this class.
*
* @param <T> the type of the element being matched; could be anything
* @author JJ Brown
* @see VisibilityMatchers
*/
class VisibilityMatcher<T> extends BaseMatcher<T> {
private final Visibility expectedVisibility;

VisibilityMatcher(Visibility expectedVisibility) {
this.expectedVisibility = expectedVisibility;
}

@Override
public boolean matches(Object actual) {
if (actual == null) {
return false;
}
if (actual instanceof Class) {
return expectedVisibility == Visibility.of((Class<?>) actual);
}
if (actual instanceof Member) {
return expectedVisibility == Visibility.of((Member) actual);
}
return false;
}

@Override
public void describeTo(Description description) {
description.appendText("is ").appendText(expectedVisibility.getDescription());
}

@Override
public void describeMismatch(Object item, Description description) {
if (item == null) {
description.appendText("was null");
} else if (item instanceof Class) {
description.appendText("was a ")
.appendText(Visibility.of((Class<?>) item).getDescription())
.appendText(" class");
} else if (item instanceof Member) {
description.appendText("was a ")
.appendText(Visibility.of((Member) item).getDescription())
.appendText(" ")
.appendText(item.getClass().getName());
} else {
description.appendText("was " + item.getClass().getName() + " instead of a reflective element like a Class<T>, Constructor<T>, or Method");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.hamcrest.reflection;

import org.hamcrest.Matcher;

/**
* Defines matchers that check the visibility of reflective objects like {@link java.lang.Class} or {@link java.lang.reflect.Method}.
* {@code null} values never match, nor do normal objects; these simply do not match, without raising an Exception.
*
* @author JJ Brown
*/
public class VisibilityMatchers {
// Each matcher is stateless and can match any type, so the individual instances are only made once and stored here for re-use.
private static final VisibilityMatcher<?> PUBLIC = new VisibilityMatcher<>(Visibility.PUBLIC);
private static final VisibilityMatcher<?> PROTECTED = new VisibilityMatcher<>(Visibility.PROTECTED);
private static final VisibilityMatcher<?> PACKAGE_PROTECTED = new VisibilityMatcher<>(Visibility.PACKAGE_PROTECTED);
private static final VisibilityMatcher<?> PRIVATE = new VisibilityMatcher<>(Visibility.PRIVATE);

/**
* Matchers reflective elements that have public visibility.
* Specifically, this matcher only matches elements marked with the keyword {@code public}.
* <br>
* This method matches {@link Class} objects or other {@link java.lang.reflect.Member reflective objects}
* like {@link java.lang.reflect.Field} or {@link java.lang.reflect.Method} used in reflection.
* Any other kind of object, or {@code null} values, do not match (but will not cause an Exception).
*
* @param <T> the type of the object being matched
* @return a matcher that matches reflective elements with exactly the given level of visibility
*/
@SuppressWarnings("unchecked")
public static <T> Matcher<T> isPublic() {
// Each matcher is stateless and can match any type (the generic <T> is for type safety at the use site),
// so it's fine to cast the non-reifiable generic type here at runtime and re-use the same instance.
return (Matcher<T>) PUBLIC;
}

/**
* Matchers reflective elements that have protected visibility.
* Specifically, this matcher only matches elements marked with the keyword {@code protected}; it does NOT match public or private elements.
* <br>
* This method matches {@link Class} objects or other {@link java.lang.reflect.Member reflective objects}
* like {@link java.lang.reflect.Field} or {@link java.lang.reflect.Method} used in reflection.
* Any other kind of object, or {@code null} values, do not match (but will not cause an Exception).
*
* @param <T> the type of the object being matched
* @return a matcher that matches reflective elements with exactly the given level of visibility
*/
@SuppressWarnings("unchecked")
public static <T> Matcher<T> isProtected() {
// Each matcher is stateless and can match any type (the generic <T> is for type safety at the use site),
// so it's fine to cast the non-reifiable generic type here at runtime and re-use the same instance.
return (Matcher<T>) PROTECTED;
}

/**
* Matchers reflective elements that have package-protected visibility.
* Specifically, this matcher only matches elements not marked with any of the visibility keywords {@code public}, {@code protected}, or {@code private}.
* <br>
* This method matches {@link Class} objects or other {@link java.lang.reflect.Member reflective objects}
* like {@link java.lang.reflect.Field} or {@link java.lang.reflect.Method} used in reflection.
* Any other kind of object, or {@code null} values, do not match (but will not cause an Exception).
*
* @param <T> the type of the object being matched
* @return a matcher that matches reflective elements with exactly the given level of visibility
*/
@SuppressWarnings("unchecked")
public static <T> Matcher<T> isPackageProtected() {
// Each matcher is stateless and can match any type (the generic <T> is for type safety at the use site),
// so it's fine to cast the non-reifiable generic type here at runtime and re-use the same instance.
return (Matcher<T>) PACKAGE_PROTECTED;
}

/**
* Matchers reflective elements that have private visibility.
* Specifically, this matcher only matches elements marked with the keyword {@code private}.
* <br>
* This method matches {@link Class} objects or other {@link java.lang.reflect.Member reflective objects}
* like {@link java.lang.reflect.Field} or {@link java.lang.reflect.Method} used in reflection.
* Any other kind of object, or {@code null} values, do not match (but will not cause an Exception).
*
* @param <T> the type of the object being matched
* @return a matcher that matches reflective elements with exactly the given level of visibility
*/
@SuppressWarnings("unchecked")
public static <T> Matcher<T> isPrivate() {
// Each matcher is stateless and can match any type (the generic <T> is for type safety at the use site),
// so it's fine to cast the non-reifiable generic type here at runtime and re-use the same instance.
return (Matcher<T>) PRIVATE;
}
}
11 changes: 11 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/reflection/package.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p>Matchers that perform checks on reflective elements, such as Class&lt;?&gt; and Method&lt;?&gt; objects.</p>
<p>This provides tools to enforce boundaries about the scope of visible items in a module,
and to explicitly ensure that items are available to reflection when they may only be loaded at runtime.</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.hamcrest.reflection;


import org.hamcrest.AbstractMatcherTest;
import org.hamcrest.Matcher;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import static org.hamcrest.reflection.VisibilityMatchers.isPackageProtected;
import static org.hamcrest.reflection.VisibilityMatchers.isPublic;

@SuppressWarnings("unused")
public class IsPackageProtectedTest extends AbstractMatcherTest {
@Override
protected Matcher<?> createMatcher() {
return isPackageProtected();
}

@Test
public void test_packageExposesPublicFactoryMethod() throws NoSuchMethodException {
assertMatches(isPublic(), VisibilityMatchers.class.getMethod("isPackageProtected"));
}

@Test
public void test_isPackageProtected_matchesOnlyPackageProtectedClasses() {
assertDoesNotMatch(isPackageProtected(), PublicClass.class);
assertDoesNotMatch(isPackageProtected(), ProtectedClass.class);
assertMatches(isPackageProtected(), PackageProtectedClass.class);
assertDoesNotMatch(isPackageProtected(), PrivateClass.class);

assertDescription("is package-protected (no modifiers)", isPackageProtected());

assertMismatchDescription("was a public class", isPackageProtected(), PublicClass.class);
assertMismatchDescription("was a protected class", isPackageProtected(), ProtectedClass.class);
assertMismatchDescription("was a private class", isPackageProtected(), PrivateClass.class);
}


@Test
public void test_isPackageProtected_matchesOnlyPackageProtectedFields() throws NoSuchFieldException {
Field publicField = ExampleFields.class.getDeclaredField("publicField");
Field protectedField = ExampleFields.class.getDeclaredField("protectedField");
Field packageProtectedField = ExampleFields.class.getDeclaredField("packageProtectedField");
Field privateField = ExampleFields.class.getDeclaredField("privateField");

assertDoesNotMatch(isPackageProtected(), publicField);
assertDoesNotMatch(isPackageProtected(), protectedField);
assertMatches(isPackageProtected(), packageProtectedField);
assertDoesNotMatch(isPackageProtected(), privateField);

assertDescription("is package-protected (no modifiers)", isPackageProtected());

assertMismatchDescription("was a public java.lang.reflect.Field", isPackageProtected(), publicField);
assertMismatchDescription("was a protected java.lang.reflect.Field", isPackageProtected(), protectedField);
assertMismatchDescription("was a private java.lang.reflect.Field", isPackageProtected(), privateField);
}


@Test
public void test_isPackageProtected_matchesOnlyPackageProtectedMethods() throws NoSuchMethodException {
Method publicMethod = ExampleMethods.class.getDeclaredMethod("publicMethod");
Method protectedMethod = ExampleMethods.class.getDeclaredMethod("protectedMethod");
Method packageProtectedMethod = ExampleMethods.class.getDeclaredMethod("packageProtectedMethod");
Method privateMethod = ExampleMethods.class.getDeclaredMethod("privateMethod");

assertDoesNotMatch(isPackageProtected(), publicMethod);
assertDoesNotMatch(isPackageProtected(), protectedMethod);
assertMatches(isPackageProtected(), packageProtectedMethod);
assertDoesNotMatch(isPackageProtected(), privateMethod);

assertDescription("is package-protected (no modifiers)", isPackageProtected());

assertMismatchDescription("was a public java.lang.reflect.Method", isPackageProtected(), publicMethod);
assertMismatchDescription("was a protected java.lang.reflect.Method", isPackageProtected(), protectedMethod);
assertMismatchDescription("was a private java.lang.reflect.Method", isPackageProtected(), privateMethod);
}

@Test
public void test_isPackageProtected_doesNotMatchNull() {
assertDoesNotMatch(isPackageProtected(), null);

assertMismatchDescription("was null", isPackageProtected(), null);
}

@Test
public void test_isPackageProtected_doesNotMatchNonReflectiveElement() {
assertDoesNotMatch(isPackageProtected(), new Object());

assertMismatchDescription("was java.lang.Object instead of a reflective element like a Class<T>, Constructor<T>, or Method", isPackageProtected(), new Object());
}

public static class PublicClass {
}

protected static class ProtectedClass {
}

static class PackageProtectedClass {
}

private static class PrivateClass {
}

private static class ExampleFields {
public Void publicField;
protected Void protectedField;
Void packageProtectedField;
private Void privateField;
}

private static class ExampleMethods {
public void publicMethod() {
}

protected void protectedMethod() {
}

void packageProtectedMethod() {
}

private void privateMethod() {
}
}
}
Loading