HelperTests.java 18.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Copyright 2002-2009 the original author or authors.
 *
 * 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.
 */
package org.springframework.expression.spel;

18 19
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
20 21
import java.util.ArrayList;
import java.util.List;
22

23 24 25 26
import junit.framework.Assert;

import org.junit.Test;
import org.springframework.expression.EvaluationContext;
27
import org.springframework.expression.EvaluationException;
28
import org.springframework.expression.ParseException;
29
import org.springframework.expression.TypedValue;
30
import org.springframework.expression.spel.ast.FormatHelper;
31
import org.springframework.expression.spel.support.ReflectionHelper;
32 33
import org.springframework.expression.spel.support.ReflectivePropertyResolver;
import org.springframework.expression.spel.support.StandardEvaluationContext;
34 35
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.expression.spel.support.ReflectionHelper.ArgsMatchKind;
36 37 38 39 40 41 42 43

/**
 * Tests for any helper code.
 * 
 * @author Andy Clement
 */
public class HelperTests extends ExpressionTestCase {

44
	@Test
45
	public void testFormatHelperForClassName() {
46 47 48 49 50
		Assert.assertEquals("java.lang.String",FormatHelper.formatClassNameForMessage(String.class));
		Assert.assertEquals("java.lang.String[]",FormatHelper.formatClassNameForMessage(new String[1].getClass()));
		Assert.assertEquals("int[]",FormatHelper.formatClassNameForMessage(new int[1].getClass()));
		Assert.assertEquals("int[][]",FormatHelper.formatClassNameForMessage(new int[1][2].getClass()));
		Assert.assertEquals("null",FormatHelper.formatClassNameForMessage(null));
51 52
	}
	
53
	@Test
54
	public void testFormatHelperForMethod() {
55 56 57
		Assert.assertEquals("foo(java.lang.String)",FormatHelper.formatMethodForMessage("foo", String.class));
		Assert.assertEquals("goo(java.lang.String,int[])",FormatHelper.formatMethodForMessage("goo", String.class,new int[1].getClass()));
		Assert.assertEquals("boo()",FormatHelper.formatMethodForMessage("boo"));
58
	}
59
	
60
	@Test
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
	public void testUtilities() throws ParseException {
		SpelExpression expr = (SpelExpression)parser.parseExpression("3+4+5+6+7-2");
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		PrintStream ps = new PrintStream(baos);
		SpelUtilities.printAbstractSyntaxTree(ps, expr);
		ps.flush();
		String s = baos.toString();
//		===> Expression '3+4+5+6+7-2' - AST start
//		OperatorMinus  value:(((((3 + 4) + 5) + 6) + 7) - 2)  #children:2
//		  OperatorPlus  value:((((3 + 4) + 5) + 6) + 7)  #children:2
//		    OperatorPlus  value:(((3 + 4) + 5) + 6)  #children:2
//		      OperatorPlus  value:((3 + 4) + 5)  #children:2
//		        OperatorPlus  value:(3 + 4)  #children:2
//		          CompoundExpression  value:3
//		            IntLiteral  value:3
//		          CompoundExpression  value:4
//		            IntLiteral  value:4
//		        CompoundExpression  value:5
//		          IntLiteral  value:5
//		      CompoundExpression  value:6
//		        IntLiteral  value:6
//		    CompoundExpression  value:7
//		      IntLiteral  value:7
//		  CompoundExpression  value:2
//		    IntLiteral  value:2
//		===> Expression '3+4+5+6+7-2' - AST end
87
		Assert.assertTrue(s.indexOf("===> Expression '3+4+5+6+7-2' - AST start")!=-1);
88
		Assert.assertTrue(s.indexOf(" OpPlus  value:((((3 + 4) + 5) + 6) + 7)  #children:2")!=-1);
89
	}
90
	
91
	@Test
92 93
	public void testTypedValue() {
		TypedValue tValue = new TypedValue("hello");
94 95
		Assert.assertEquals(String.class,tValue.getTypeDescriptor().getType());
		Assert.assertEquals("TypedValue: hello of type java.lang.String",tValue.toString());
96 97
	}
	
98
	@Test
99 100 101 102 103 104 105 106 107 108
	public void testReflectionHelperCompareArguments_ExactMatching() {
		StandardTypeConverter typeConverter = new StandardTypeConverter();
		
		// Calling foo(String) with (String) is exact match
		checkMatch(new Class[]{String.class},new Class[]{String.class},typeConverter,ArgsMatchKind.EXACT);
		
		// Calling foo(String,Integer) with (String,Integer) is exact match
		checkMatch(new Class[]{String.class,Integer.class},new Class[]{String.class,Integer.class},typeConverter,ArgsMatchKind.EXACT);
	}
	
109
	@Test
110 111 112 113 114 115 116 117 118 119 120 121 122
	public void testReflectionHelperCompareArguments_CloseMatching() {
		StandardTypeConverter typeConverter = new StandardTypeConverter();
		
		// Calling foo(List) with (ArrayList) is close match (no conversion required)
		checkMatch(new Class[]{ArrayList.class},new Class[]{List.class},typeConverter,ArgsMatchKind.CLOSE);
		
		// Passing (Sub,String) on call to foo(Super,String) is close match
		checkMatch(new Class[]{Sub.class,String.class},new Class[]{Super.class,String.class},typeConverter,ArgsMatchKind.CLOSE);
		
		// Passing (String,Sub) on call to foo(String,Super) is close match
		checkMatch(new Class[]{String.class,Sub.class},new Class[]{String.class,Super.class},typeConverter,ArgsMatchKind.CLOSE);
	}
	
123
	@Test
124
	public void testReflectionHelperCompareArguments_RequiresConversionMatching() {
125
		// TODO these are failing - for investigation
126 127 128
		StandardTypeConverter typeConverter = new StandardTypeConverter();
		
		// Calling foo(String,int) with (String,Integer) requires boxing conversion of argument one
K
Keith Donald 已提交
129
		checkMatch(new Class[]{String.class,Integer.TYPE},new Class[]{String.class,Integer.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,1);
130 131

		// Passing (int,String) on call to foo(Integer,String) requires boxing conversion of argument zero
K
Keith Donald 已提交
132
		checkMatch(new Class[]{Integer.TYPE,String.class},new Class[]{Integer.class, String.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,0);
133 134
		
		// Passing (int,Sub) on call to foo(Integer,Super) requires boxing conversion of argument zero
K
Keith Donald 已提交
135
		checkMatch(new Class[]{Integer.TYPE,Sub.class},new Class[]{Integer.class, Super.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,0);
136 137
		
		// Passing (int,Sub,boolean) on call to foo(Integer,Super,Boolean) requires boxing conversion of arguments zero and two
K
Keith Donald 已提交
138
		checkMatch(new Class[]{Integer.TYPE,Sub.class,Boolean.TYPE},new Class[]{Integer.class, Super.class,Boolean.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,0,2);
139 140
	}

141
	@Test
142 143 144 145 146 147
	public void testReflectionHelperCompareArguments_NotAMatch() {
		StandardTypeConverter typeConverter = new StandardTypeConverter();
		
		// Passing (Super,String) on call to foo(Sub,String) is not a match
		checkMatch(new Class[]{Super.class,String.class},new Class[]{Sub.class,String.class},typeConverter,null);
	}
148

149
	@Test
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
	public void testReflectionHelperCompareArguments_Varargs_ExactMatching() {
		StandardTypeConverter tc = new StandardTypeConverter();
		Class<?> stringArrayClass = new String[0].getClass();
		Class<?> integerArrayClass = new Integer[0].getClass();
				
		// Passing (String[]) on call to (String[]) is exact match
		checkMatch2(new Class[]{stringArrayClass},new Class[]{stringArrayClass},tc,ArgsMatchKind.EXACT);
		
		// Passing (Integer, String[]) on call to (Integer, String[]) is exact match
		checkMatch2(new Class[]{Integer.class,stringArrayClass},new Class[]{Integer.class,stringArrayClass},tc,ArgsMatchKind.EXACT);

		// Passing (String, Integer, String[]) on call to (String, String, String[]) is exact match
		checkMatch2(new Class[]{String.class,Integer.class,stringArrayClass},new Class[]{String.class,Integer.class,stringArrayClass},tc,ArgsMatchKind.EXACT);
		
		// Passing (Sub, String[]) on call to (Super, String[]) is exact match
		checkMatch2(new Class[]{Sub.class,stringArrayClass},new Class[]{Super.class,stringArrayClass},tc,ArgsMatchKind.CLOSE);

		// Passing (Integer, String[]) on call to (String, String[]) is exact match
		checkMatch2(new Class[]{Integer.class,stringArrayClass},new Class[]{String.class,stringArrayClass},tc,ArgsMatchKind.REQUIRES_CONVERSION,0);

		// Passing (Integer, Sub, String[]) on call to (String, Super, String[]) is exact match
		checkMatch2(new Class[]{Integer.class,Sub.class,String[].class},new Class[]{String.class,Super.class,String[].class},tc,ArgsMatchKind.REQUIRES_CONVERSION,0);
		
		// Passing (String) on call to (String[]) is exact match
		checkMatch2(new Class[]{String.class},new Class[]{stringArrayClass},tc,ArgsMatchKind.EXACT);
		
		// Passing (Integer,String) on call to (Integer,String[]) is exact match
		checkMatch2(new Class[]{Integer.class,String.class},new Class[]{Integer.class,stringArrayClass},tc,ArgsMatchKind.EXACT);

		// Passing (String) on call to (Integer[]) is conversion match (String to Integer)
		checkMatch2(new Class[]{String.class},new Class[]{integerArrayClass},tc,ArgsMatchKind.REQUIRES_CONVERSION,0);

		// Passing (Sub) on call to (Super[]) is close match
		checkMatch2(new Class[]{Sub.class},new Class[]{new Super[0].getClass()},tc,ArgsMatchKind.CLOSE);
		
		// Passing (Super) on call to (Sub[]) is not a match
		checkMatch2(new Class[]{Super.class},new Class[]{new Sub[0].getClass()},tc,null);

		checkMatch2(new Class[]{Unconvertable.class,String.class},new Class[]{Sub.class,Super[].class},tc,null);

		checkMatch2(new Class[]{Integer.class,Integer.class,String.class},new Class[]{String.class,String.class,Super[].class},tc,null);

		checkMatch2(new Class[]{Unconvertable.class,String.class},new Class[]{Sub.class,Super[].class},tc,null);

		checkMatch2(new Class[]{Integer.class,Integer.class,String.class},new Class[]{String.class,String.class,Super[].class},tc,null);

		checkMatch2(new Class[]{Integer.class,Integer.class,Sub.class},new Class[]{String.class,String.class,Super[].class},tc,ArgsMatchKind.REQUIRES_CONVERSION,0,1);

		checkMatch2(new Class[]{Integer.class,Integer.class,Integer.class},new Class[]{Integer.class,String[].class},tc,ArgsMatchKind.REQUIRES_CONVERSION,1,2);
		// what happens on (Integer,String) passed to (Integer[]) ?
	}
	
202
	@Test
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
	public void testConvertArguments() throws Exception {
		StandardTypeConverter tc = new StandardTypeConverter();

		// basic conversion int>String
		Object[] args = new Object[]{3};
		ReflectionHelper.convertArguments(new Class[]{String.class}, false, tc, new int[]{0}, args);
		checkArguments(args, "3");

		// varargs but nothing to convert
		args = new Object[]{3};
		ReflectionHelper.convertArguments(new Class[]{String.class,String[].class}, false, tc, new int[]{0}, args);
		checkArguments(args, "3");

		// varargs with nothing needing conversion
		args = new Object[]{3,"abc","abc"};
		ReflectionHelper.convertArguments(new Class[]{String.class,String[].class}, true, tc, new int[]{0,1,2}, args);
		checkArguments(args, "3","abc","abc");

		// varargs with conversion required
		args = new Object[]{3,false,3.0d};
		ReflectionHelper.convertArguments(new Class[]{String.class,String[].class}, true, tc, new int[]{0,1,2}, args);
		checkArguments(args, "3","false","3.0");
225
	}
226

227
	@Test
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
	public void testConvertArguments2() throws EvaluationException {
		StandardTypeConverter tc = new StandardTypeConverter();

		// Simple conversion: int to string
		Object[] args = new Object[]{3};
		ReflectionHelper.convertAllArguments(new Class[]{String.class}, false, tc, args);
		checkArguments(args,"3");

		// varargs conversion
		args = new Object[]{3,false,3.0f};
		ReflectionHelper.convertAllArguments(new Class[]{String.class,String[].class}, true, tc, args);
		checkArguments(args,"3","false","3.0");

		// varargs conversion but no varargs
		args = new Object[]{3};
		ReflectionHelper.convertAllArguments(new Class[]{String.class,String[].class}, true, tc, args);
		checkArguments(args,"3");

		// missing converter
		args = new Object[]{3,false,3.0f};
		try {
			ReflectionHelper.convertAllArguments(new Class[]{String.class,String[].class}, true, null, args);
250 251
			Assert.fail("Should have failed because no converter supplied");
		} catch (SpelEvaluationException se) {
252
			Assert.assertEquals(SpelMessage.TYPE_CONVERSION_ERROR,se.getMessageCode());
253 254 255 256 257 258 259 260
		}
		
		// null value
		args = new Object[]{3,null,3.0f};
		ReflectionHelper.convertAllArguments(new Class[]{String.class,String[].class}, true, tc, args);
		checkArguments(args,"3",null,"3.0");
	}
	
261
	@Test
262 263 264
	public void testSetupArguments() {
		Object[] newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(new Class[]{new String[0].getClass()},"a","b","c");
		
265
		Assert.assertEquals(1,newArray.length);
266
		Object firstParam = newArray[0];
267
		Assert.assertEquals(String.class,firstParam.getClass().getComponentType());
268
		Object[] firstParamArray = (Object[])firstParam;
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
		Assert.assertEquals(3,firstParamArray.length);
		Assert.assertEquals("a",firstParamArray[0]);
		Assert.assertEquals("b",firstParamArray[1]);
		Assert.assertEquals("c",firstParamArray[2]);
	}
	
	@Test
	public void testReflectivePropertyResolver() throws Exception {
		ReflectivePropertyResolver rpr = new ReflectivePropertyResolver();
		Tester t = new Tester();
		t.setProperty("hello");
		EvaluationContext ctx = new StandardEvaluationContext(t);
		Assert.assertTrue(rpr.canRead(ctx, t, "property"));
		Assert.assertEquals("hello",rpr.read(ctx, t, "property").getValue());
		Assert.assertEquals("hello",rpr.read(ctx, t, "property").getValue()); // cached accessor used

		Assert.assertTrue(rpr.canRead(ctx, t, "field"));
		Assert.assertEquals(3,rpr.read(ctx, t, "field").getValue());
		Assert.assertEquals(3,rpr.read(ctx, t, "field").getValue()); // cached accessor used
		
		Assert.assertTrue(rpr.canWrite(ctx, t, "property"));
		rpr.write(ctx, t, "property","goodbye");
		rpr.write(ctx, t, "property","goodbye"); // cached accessor used
				
		Assert.assertTrue(rpr.canWrite(ctx, t, "field"));
		rpr.write(ctx, t, "field",12);
		rpr.write(ctx, t, "field",12);

		// Attempted write as first activity on this field and property to drive testing 
		// of populating type descriptor cache
		rpr.write(ctx,t,"field2",3);
		rpr.write(ctx, t, "property2","doodoo");
		Assert.assertEquals(3,rpr.read(ctx,t,"field2").getValue());

		// Attempted read as first activity on this field and property (no canRead before them)
		Assert.assertEquals(0,rpr.read(ctx,t,"field3").getValue());
		Assert.assertEquals("doodoo",rpr.read(ctx,t,"property3").getValue());

		// Access through is method
//		Assert.assertEquals(0,rpr.read(ctx,t,"field3").getValue());
		Assert.assertEquals(false,rpr.read(ctx,t,"property4").getValue());
		Assert.assertTrue(rpr.canRead(ctx,t,"property4"));
311
	}
312
	
313 314

	// test classes
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	static class Tester {
		String property;
		public int field = 3;
		public int field2;
		public int field3 = 0;
		String property2;
		String property3 = "doodoo";
		boolean property4 = false;

		public String getProperty() { return property; }
		public void setProperty(String value) { property = value; }

		public void setProperty2(String value) { property2 = value; }

		public String getProperty3() { return property3; }
		
		public boolean isProperty4() { return property4; }
	}
333 334 335 336 337 338 339
	
	static class Super {
	}
	
	static class Sub extends Super {
	}
	
340 341
	static class Unconvertable {}
	
342 343 344 345 346 347 348 349
	// ---
	
	/**
	 * Used to validate the match returned from a compareArguments call.
	 */
	private void checkMatch(Class[] inputTypes, Class[] expectedTypes, StandardTypeConverter typeConverter,ArgsMatchKind expectedMatchKind,int... argsForConversion) {
		ReflectionHelper.ArgumentsMatchInfo matchInfo = ReflectionHelper.compareArguments(expectedTypes, inputTypes, typeConverter);
		if (expectedMatchKind==null) {
350
			Assert.assertNull("Did not expect them to match in any way", matchInfo);
351
		} else {
352
			Assert.assertNotNull("Should not be a null match", matchInfo);
353 354 355
		}

		if (expectedMatchKind==ArgsMatchKind.EXACT) {
356 357
			Assert.assertTrue(matchInfo.isExactMatch());
			Assert.assertNull(matchInfo.argsRequiringConversion);		
358
		} else if (expectedMatchKind==ArgsMatchKind.CLOSE) {
359 360
			Assert.assertTrue(matchInfo.isCloseMatch());
			Assert.assertNull(matchInfo.argsRequiringConversion);		
361
		} else if (expectedMatchKind==ArgsMatchKind.REQUIRES_CONVERSION) {
362
			Assert.assertTrue("expected to be a match requiring conversion, but was "+matchInfo,matchInfo.isMatchRequiringConversion());
363
			if (argsForConversion==null) {
364
				Assert.fail("there are arguments that need conversion");
365
			}
366
			Assert.assertEquals("The array of args that need conversion is different length to that expected",argsForConversion.length, matchInfo.argsRequiringConversion.length);
367
			for (int a=0;a<argsForConversion.length;a++) {
368
				Assert.assertEquals(argsForConversion[a],matchInfo.argsRequiringConversion[a]);
369 370 371
			}
		}
	}
372 373 374 375 376 377 378

	/**
	 * Used to validate the match returned from a compareArguments call.
	 */
	private void checkMatch2(Class[] inputTypes, Class[] expectedTypes, StandardTypeConverter typeConverter,ArgsMatchKind expectedMatchKind,int... argsForConversion) {
		ReflectionHelper.ArgumentsMatchInfo matchInfo = ReflectionHelper.compareArgumentsVarargs(expectedTypes, inputTypes, typeConverter);
		if (expectedMatchKind==null) {
379
			Assert.assertNull("Did not expect them to match in any way: "+matchInfo, matchInfo);
380
		} else {
381
			Assert.assertNotNull("Should not be a null match", matchInfo);
382 383 384
		}

		if (expectedMatchKind==ArgsMatchKind.EXACT) {
385 386
			Assert.assertTrue(matchInfo.isExactMatch());
			Assert.assertNull(matchInfo.argsRequiringConversion);		
387
		} else if (expectedMatchKind==ArgsMatchKind.CLOSE) {
388 389
			Assert.assertTrue(matchInfo.isCloseMatch());
			Assert.assertNull(matchInfo.argsRequiringConversion);		
390
		} else if (expectedMatchKind==ArgsMatchKind.REQUIRES_CONVERSION) {
391
			Assert.assertTrue("expected to be a match requiring conversion, but was "+matchInfo,matchInfo.isMatchRequiringConversion());
392
			if (argsForConversion==null) {
393
				Assert.fail("there are arguments that need conversion");
394
			}
395
			Assert.assertEquals("The array of args that need conversion is different length to that expected",argsForConversion.length, matchInfo.argsRequiringConversion.length);
396
			for (int a=0;a<argsForConversion.length;a++) {
397
				Assert.assertEquals(argsForConversion[a],matchInfo.argsRequiringConversion[a]);
398 399 400 401 402
			}
		}
	}

	private void checkArguments(Object[] args, Object... expected) {
403
		Assert.assertEquals(expected.length,args.length);
404 405 406 407 408 409
		for (int i=0;i<expected.length;i++) {
			checkArgument(expected[i],args[i]);
		}
	}
	
	private void checkArgument(Object expected, Object actual) {
410
		Assert.assertEquals(expected,actual);
411
	}
412
}