/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.aries.blueprint.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.concurrent.Future; import org.apache.aries.blueprint.di.CircularDependencyException; import org.apache.aries.blueprint.di.ExecutionContext; import org.apache.aries.blueprint.di.Recipe; import org.apache.aries.blueprint.services.ExtendedBlueprintContainer; import org.apache.aries.blueprint.utils.ReflectionUtils.PropertyDescriptor; import org.easymock.Capture; import org.easymock.EasyMock; import org.easymock.IAnswer; import org.junit.BeforeClass; import org.junit.Test; import org.osgi.service.blueprint.container.ComponentDefinitionException; import org.osgi.service.blueprint.container.ReifiedType; public class ReflectionUtilsTest { private PropertyDescriptor[] sut; private static ExtendedBlueprintContainer mockBlueprint; public static class GetterOnly { public String getValue() { return "test"; } } private class Inconvertible {} @BeforeClass public static void before() throws ClassNotFoundException { mockBlueprint = EasyMock.createNiceMock(ExtendedBlueprintContainer.class); final Capture<String> nameCapture = new Capture<String>(); EasyMock.expect(mockBlueprint.loadClass(EasyMock.capture(nameCapture))).andAnswer(new IAnswer<Class<?>>() { public Class<?> answer() throws Throwable { return Thread.currentThread().getContextClassLoader().loadClass(nameCapture.getValue()); } }); EasyMock.replay(mockBlueprint); ExecutionContext.Holder.setContext(new ExecutionContext() { public void addPartialObject(String name, Object object) {} public boolean containsObject(String name) { return false; } public Object convert(Object value, ReifiedType type) throws Exception { if (type.getRawClass().equals(Inconvertible.class)) throw new Exception(); else if (type.getRawClass().equals(String.class)) return String.valueOf(value); else if (type.getRawClass().equals(List.class)) { if (value == null) return null; else if (value instanceof Collection) return new ArrayList((Collection) value); else throw new Exception(); } else if (value == null) return null; else if (type.getRawClass().isInstance(value)) return value; else throw new Exception(); } public boolean canConvert(Object value, ReifiedType type) { if (value instanceof Inconvertible) return false; else if (type.getRawClass().equals(String.class)) return true; else if (type.getRawClass().equals(List.class) && (value == null || value instanceof Collection)) return true; else return false; } public Object getObject(String name) { return null; } public Object getPartialObject(String name) { return null; } public Recipe getRecipe(String name) { return null; } public Class loadClass(String className) throws ClassNotFoundException { return null; } public Recipe pop() { return null; } public void push(Recipe recipe) throws CircularDependencyException {} public void removePartialObject(String name) {} public Future<Object> addFullObject(String name, Future<Object> object) { return null; } }); } @Test public void testGetterOnly() throws Exception { loadProps(GetterOnly.class, true); assertEquals(2, sut.length); assertEquals("class", sut[0].getName()); assertEquals("value", sut[1].getName()); assertTrue(sut[1].allowsGet()); assertFalse(sut[1].allowsSet()); assertEquals("test", sut[1].get(new GetterOnly(), mockBlueprint)); } public static class SetterOnly { private String f; public void setField(String val) { f = val; } public String retrieve() { return f; } } @Test public void testSetterOnly() throws Exception { loadProps(SetterOnly.class, false); assertEquals(2, sut.length); assertEquals("field", sut[1].getName()); assertFalse(sut[1].allowsGet()); assertTrue(sut[1].allowsSet()); SetterOnly so = new SetterOnly(); sut[1].set(so, "trial", mockBlueprint); assertEquals("trial", so.retrieve()); } public static class SetterAndGetter { private String f; public void setField(String val) { f = val; } public String getField() { return f; } } @Test public void testSetterAndGetter() throws Exception { loadProps(SetterAndGetter.class, false); assertEquals(2, sut.length); assertEquals("field", sut[1].getName()); assertTrue(sut[1].allowsGet()); assertTrue(sut[1].allowsSet()); SetterAndGetter sag = new SetterAndGetter(); sut[1].set(sag, "tribulation", mockBlueprint); assertEquals("tribulation", sut[1].get(sag, mockBlueprint)); } static class DuplicateGetter { public boolean isField() { return true; } public boolean getField() { return false; } } @Test public void testDuplicateGetter() { loadProps(DuplicateGetter.class, false); assertEquals(1, sut.length); assertEquals("class", sut[0].getName()); } static class FieldsAndProps { private String hidden = "ordeal"; private String nonHidden; public String getHidden() { return hidden; } } @Test public void testFieldsAndProps() throws Exception { loadProps(FieldsAndProps.class, true); assertEquals(3, sut.length); FieldsAndProps fap = new FieldsAndProps(); // no mixing of setter and field injection assertEquals("hidden", sut[1].getName()); assertTrue(sut[1].allowsGet()); assertTrue(sut[1].allowsSet()); assertEquals("ordeal", sut[1].get(fap, mockBlueprint)); sut[1].set(fap, "calvary", mockBlueprint); assertEquals("calvary", sut[1].get(fap, mockBlueprint)); assertEquals("nonHidden", sut[2].getName()); assertTrue(sut[2].allowsGet()); assertTrue(sut[2].allowsSet()); sut[2].set(fap, "predicament", mockBlueprint); assertEquals("predicament", sut[2].get(fap, mockBlueprint)); } public static class OverloadedSetters { public Object field; public void setField(String val) { field = val; } public void setField(List<String> val) { field = val; } } @Test public void testOverloadedSetters() throws Exception { loadProps(OverloadedSetters.class, false); OverloadedSetters os = new OverloadedSetters(); sut[1].set(os, "scrutiny", mockBlueprint); assertEquals("scrutiny", os.field); sut[1].set(os, Arrays.asList("evaluation"), mockBlueprint); assertEquals(Arrays.asList("evaluation"), os.field); // conversion case, Integer -> String sut[1].set(os, new Integer(3), mockBlueprint); assertEquals("3", os.field); } @Test(expected=ComponentDefinitionException.class) public void testApplicableSetter() throws Exception { loadProps(OverloadedSetters.class, false); sut[1].set(new OverloadedSetters(), new Inconvertible(), mockBlueprint); } public static class MultipleMatchesByConversion { public void setField(String s) {} public void setField(List<String> list) {} } @Test(expected=ComponentDefinitionException.class) public void testMultipleMatchesByConversion() throws Exception { loadProps(MultipleMatchesByConversion.class, false); sut[1].set(new MultipleMatchesByConversion(), new HashSet<String>(), mockBlueprint); } public static class MultipleMatchesByType { public void setField(List<String> list) {} public void setField(Queue<String> list) {} public static int field; public void setOther(Collection<String> list) { field=1; } public void setOther(List<String> list) { field=2; } } @Test(expected=ComponentDefinitionException.class) public void testMultipleSettersMatchByType() throws Exception { loadProps(MultipleMatchesByType.class, false); sut[1].set(new MultipleMatchesByType(), new LinkedList<String>(), mockBlueprint); } @Test public void testDisambiguationByHierarchy() throws Exception { loadProps(MultipleMatchesByType.class, false); sut[2].set(new MultipleMatchesByType(), new ArrayList<String>(), mockBlueprint); assertEquals(2, MultipleMatchesByType.field); } public static class NullSetterDisambiguation { public static int field; public void setField(int i) { field = i; } public void setField(Integer i) { field = -1; } } @Test public void testNullDisambiguation() throws Exception { loadProps(NullSetterDisambiguation.class, false); sut[1].set(new NullSetterDisambiguation(), null, mockBlueprint); assertEquals(-1, NullSetterDisambiguation.field); } private void loadProps(Class<?> clazz, boolean allowsFieldInjection) { List<PropertyDescriptor> props = new ArrayList<PropertyDescriptor>( Arrays.asList(ReflectionUtils.getPropertyDescriptors(clazz, allowsFieldInjection))); Collections.sort(props, new Comparator<PropertyDescriptor>() { public int compare(PropertyDescriptor o1, PropertyDescriptor o2) { return o1.getName().compareTo(o2.getName()); } }); sut = props.toArray(new PropertyDescriptor[0]); } }