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

2.0 #7

Open
wants to merge 1 commit into
base: 2.0
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
80 changes: 53 additions & 27 deletions src/main/java/org/fest/reflect/field/FluentField.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.fest.reflect.exception.ReflectionError;
import org.fest.reflect.field.decorator.DecoratorInvocationHandler;
Expand All @@ -36,6 +39,7 @@
class FluentField<T> implements Name<T>, Target<T>, Invoker<T> {

private final Class<T> type;
private List<String> path;
private String name;
private Object target;
private Field field;
Expand All @@ -55,7 +59,9 @@ class FluentField<T> implements Name<T>, Target<T>, Invoker<T> {
public Target<T> withName(String name) {
if (name == null) throw new NullPointerException("The name of the field to access should not be null");
if (name.length() == 0) throw new IllegalArgumentException("The name of the field to access should not be empty");
this.name = name;
this.path = new ArrayList<String>(Arrays.asList(name.split("\\.")));
this.path.remove(path.size()-1);
this.name = name.substring(name.lastIndexOf('.') + 1, name.length());
return this;
}

Expand All @@ -71,73 +77,93 @@ public Invoker<T> in(Class<?> target) {
return updateTarget(target);
}

private Invoker<T> updateTarget(Object target) {
this.target = target;
findField();
private Invoker<T> updateTarget(Object t) {
Object nestedTarget = null;
for (String fieldName : path) {
Object realTarget = nestedTarget == null ? t : nestedTarget;
nestedTarget = get(findField(fieldName, realTarget, false), realTarget);
}

this.target = nestedTarget == null ? t : nestedTarget;
this.field = findField(name, target, true);
return this;
}

private void findField() {
findFieldInTypeHierarchy(targetType());
checkRightType();
private Field findField(String fieldName, Object target, boolean checkFieldType) {
Field field = findFieldInTypeHierarchy(fieldName, targetType(target));
checkRightType(target, field, checkFieldType);
return field;
}

private Class<?> targetType() {
private Class<?> targetType(Object target) {
if (target instanceof Class<?>) return (Class<?>) target;
return target.getClass();
}

private void findFieldInTypeHierarchy(Class<?> targetType) {
private Field findFieldInTypeHierarchy(String fieldName, Class<?> targetType) {
Class<?> current = targetType;
Field field;
while (current != null) {
field = findFieldIn(current);
if (field != null) return;
field = findFieldIn(fieldName, current);
if (field != null) return field;
current = current.getSuperclass();
}
throw cannotFindField(targetType);
throw cannotFindField(fieldName, targetType);
}

private Field findFieldIn(Class<?> declaringType) {
private Field findFieldIn(String fieldName, Class<?> declaringType) {
try {
return declaringType.getDeclaredField(name);
return declaringType.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
return null;
}
}

private ReflectionError cannotFindField(Class<?> targetType) {
private ReflectionError cannotFindField(String name, Class<?> targetType) {
String message = String.format("Unable to find field '%s' in %s", name, targetType.getName());
return new ReflectionError(message);
}

private void checkRightType() {
private void checkRightType(Object target, Field field, boolean checkFieldType) {
boolean isAccessible = field.isAccessible();
try {
makeAccessible(field);
Class<?> actualType = field.getType();
if (!type.isAssignableFrom(actualType)) throw wrongType(actualType);
if (checkFieldType && !type.isAssignableFrom(actualType)) throw wrongType(target, actualType);
} finally {
setAccessibleIgnoringExceptions(field, isAccessible);
}
}

private ReflectionError wrongType(Class<?> actual) {
private ReflectionError wrongType(Object target, Class<?> actual) {
String format = "The type of the field '%s' in %s should be <%s> but was <%s>";
String message = String.format(format, name, target.getClass().getName(), type.getName(), actual.getName());
throw new ReflectionError(message);
}

/** {@inheritDoc} */
public T get() {
boolean accessible = field.isAccessible();
try {
setAccessible(field, true);
return cast(field.get(target), type);
} catch (Throwable t) {
throw new ReflectionError(String.format("Unable to obtain the value in field '%s'", field.getName()), t);
} finally {
setAccessibleIgnoringExceptions(field, accessible);
}
boolean accessible = field.isAccessible();
try {
setAccessible(field, true);
return cast(field.get(target), type);
} catch (Throwable t) {
throw new ReflectionError(String.format("Unable to obtain the value in field '%s'", field.getName()), t);
} finally {
setAccessibleIgnoringExceptions(field, accessible);
}
}

private T get(Field field, Object target) {
boolean accessible = field.isAccessible();
try {
setAccessible(field, true);
return (T) field.get(target);
} catch (Throwable t) {
throw new ReflectionError(String.format("Unable to obtain the value in field '%s'", field.getName()), t);
} finally {
setAccessibleIgnoringExceptions(field, accessible);
}
}

/** {@inheritDoc} */
Expand Down
193 changes: 193 additions & 0 deletions src/test/java/org/fest/reflect/field/NestedFieldTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Created on Jun 30, 2012
*
* 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.
*
* Copyright @2006-2009 the original author or authors.
*/
package org.fest.reflect.field;


import org.fest.reflect.exception.ReflectionError;
import org.fest.reflect.reference.TypeRef;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.matchers.JUnitMatchers;
import org.junit.rules.ExpectedException;

import java.util.Arrays;
import java.util.List;

import static org.fest.reflect.field.Fields.fieldOfType;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
* @author Ivan Hristov
*/
public class NestedFieldTest {
@Rule public ExpectedException expectedException = ExpectedException.none();

private static final class BusinessService {
private final NotificationService notificationService;

public BusinessService() {
this.notificationService = new NotificationService();
}

public void doLogic() {
notificationService.save();
}

public NotificationService getNotificationService() {
return notificationService;
}
// ...
}

private static final class NotificationService {
private final Logger logger = new Logger();
private final IClientStatusDao clientStatusDao;

public NotificationService() {
this.clientStatusDao = new ClientStatusDao();
}

// ...

public void save() {
clientStatusDao.update();
}

public Logger getLogger() {
return logger;
}
}

private static class Logger {
// ...
}

private static interface IClientStatusDao {
void update();
}

private static class ClientStatusDao implements IClientStatusDao {
private final Session session;
private final List<String> listOfNames = Arrays.asList("Ivan", "Joel", "Alex");

public ClientStatusDao() {
this.session = new SessionImpl();
}

public void update() {
session.manageSession();
}
}

private static class SessionImpl implements Session {
private final SessionMonitor sessionMonitor = new SessionMonitor();

public void manageSession() {
sessionMonitor.monitor();
// logic goes here
}

}

private static interface Session {
void manageSession();
}

private static class SessionMonitor {
public void monitor() {
// monitoring logic here
}
}

@Test public void shouldSetOneLevelNestedLoggerField() {
// GIVEN
BusinessService businessService = new BusinessService();
Logger loggerMock = mock(Logger.class);

// WHEN
fieldOfType(Logger.class).withName("notificationService.logger").in(businessService).set(loggerMock);
//field("notificationService.logger").ofType(Logger.class).in(businessService).set(loggerMock);

// THEN
org.junit.Assert.assertEquals(businessService.getNotificationService().getLogger(), loggerMock);
}

@Test public void shouldThrowExceptionBecauseOfWrongOrder() {

expectedException.expect(ReflectionError.class);
expectedException.expectMessage(JUnitMatchers.containsString(//
"Unable to find field 'clientStatusDao' in org.fest.reflect.field.NestedFieldTest$BusinessService"));

// GIVEN
BusinessService businessService = new BusinessService();
Session sessionMock = mock(Session.class);
fieldOfType(Session.class).withName("clientStatusDao.notificationService.session").in(businessService).set(sessionMock);
// field("clientStatusDao.notificationService.session").ofType(Session.class).in(businessService).set(sessionMock);

// WHEN
businessService.doLogic();

// THEN
verify(sessionMock, times(1)).manageSession();
}

@Test public void shouldSetSecondLevelNestedSessionField() {
// GIVEN
BusinessService businessService = new BusinessService();
Session sessionMock = mock(Session.class);
fieldOfType(Session.class).withName("notificationService.clientStatusDao.session").in(businessService).set(sessionMock);
// field("notificationService.clientStatusDao.session").ofType(Session.class).in(businessService).set(sessionMock);

// WHEN
businessService.doLogic();

// THEN
verify(sessionMock, times(1)).manageSession();
}

@Test public void shouldGetSecondLevelNestedListOfNamesField() {
// GIVEN
BusinessService businessService = new BusinessService();

// WHEN
List<String> listOfNames = fieldOfType(new TypeRef<List<String>>() {}).withName("notificationService.clientStatusDao.listOfNames")//
.in(businessService).get();
// List<String> listOfNames = field("notificationService.clientStatusDao.listOfNames").ofType(new TypeRef<List<String>>() {})
// .in(businessService).get();

// THEN
Assert.assertThat(listOfNames, JUnitMatchers.hasItems("Ivan", "Joel", "Alex"));
}

@Test public void shouldSetThirdLevelNestedSessionFactoryField() {
// GIVEN
BusinessService businessService = new BusinessService();
SessionMonitor sessionMonitorMock = mock(SessionMonitor.class);

fieldOfType(SessionMonitor.class).withName("notificationService.clientStatusDao.session.sessionMonitor")//
.in(businessService).set(sessionMonitorMock);
// field("notificationService.clientStatusDao.session.sessionMonitor").ofType(SessionMonitor.class)//
// .in(businessService).set(sessionMonitorMock);

// WHEN
businessService.doLogic();

// THEN
verify(sessionMonitorMock, times(1)).monitor();
}
}