From ba4503319eda13c7896eed0db4042105a534178e Mon Sep 17 00:00:00 2001 From: Ivan Hristov Date: Sun, 1 Jul 2012 12:33:02 +0200 Subject: [PATCH] added nested field logic --- .../org/fest/reflect/field/FluentField.java | 80 +++++--- .../fest/reflect/field/NestedFieldTest.java | 193 ++++++++++++++++++ 2 files changed, 246 insertions(+), 27 deletions(-) create mode 100644 src/test/java/org/fest/reflect/field/NestedFieldTest.java diff --git a/src/main/java/org/fest/reflect/field/FluentField.java b/src/main/java/org/fest/reflect/field/FluentField.java index e5c3048..237023e 100644 --- a/src/main/java/org/fest/reflect/field/FluentField.java +++ b/src/main/java/org/fest/reflect/field/FluentField.java @@ -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; @@ -36,6 +39,7 @@ class FluentField implements Name, Target, Invoker { private final Class type; + private List path; private String name; private Object target; private Field field; @@ -55,7 +59,9 @@ class FluentField implements Name, Target, Invoker { public Target 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(Arrays.asList(name.split("\\."))); + this.path.remove(path.size()-1); + this.name = name.substring(name.lastIndexOf('.') + 1, name.length()); return this; } @@ -71,57 +77,65 @@ public Invoker in(Class target) { return updateTarget(target); } - private Invoker updateTarget(Object target) { - this.target = target; - findField(); + private Invoker 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); @@ -129,15 +143,27 @@ private ReflectionError wrongType(Class actual) { /** {@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} */ diff --git a/src/test/java/org/fest/reflect/field/NestedFieldTest.java b/src/test/java/org/fest/reflect/field/NestedFieldTest.java new file mode 100644 index 0000000..ce5b00c --- /dev/null +++ b/src/test/java/org/fest/reflect/field/NestedFieldTest.java @@ -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 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 listOfNames = fieldOfType(new TypeRef>() {}).withName("notificationService.clientStatusDao.listOfNames")// + .in(businessService).get(); +// List listOfNames = field("notificationService.clientStatusDao.listOfNames").ofType(new TypeRef>() {}) +// .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(); + } +}