001/* 002 * Copyright (c) 2012, 2013, Werner Keil, Credit Suisse (Anatole Tresch). Licensed under the Apache 003 * License, Version 2.0 (the "License"); you may not use this file except in compliance with the 004 * License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 005 * Unless required by applicable law or agreed to in writing, software distributed under the License 006 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 007 * or implied. See the License for the specific language governing permissions and limitations under 008 * the License. Contributors: Anatole Tresch - initial version. 009 */ 010package org.javamoney.tck; 011 012import junit.framework.Assert; 013import org.mutabilitydetector.unittesting.AllowedReason; 014import org.mutabilitydetector.unittesting.MutabilityAssert; 015import org.mutabilitydetector.unittesting.MutabilityMatchers; 016import org.testng.AssertJUnit; 017 018import javax.money.*; 019import java.io.ByteArrayOutputStream; 020import java.io.ObjectOutputStream; 021import java.io.Serializable; 022import java.lang.reflect.InvocationTargetException; 023import java.lang.reflect.Method; 024import java.lang.reflect.Modifier; 025import java.math.BigDecimal; 026import java.math.MathContext; 027import java.util.Arrays; 028import java.util.Currency; 029import java.util.Random; 030 031 032public class TestUtils{ 033 034 private static final StringBuffer warnings = new StringBuffer(); 035 036 private TestUtils(){ 037 038 } 039 040 041 public static BigDecimal createNumberWithPrecision(MonetaryAmountFactory f, int precision){ 042 if(precision == 0){ 043 precision = new Random().nextInt(100); 044 } 045 StringBuilder b = new StringBuilder(precision + 1); 046 for(int i = 0; i < precision; i++){ 047 b.append(String.valueOf(i % 10)); 048 } 049 return new BigDecimal(b.toString(), MathContext.UNLIMITED); 050 } 051 052 public static BigDecimal createNumberWithScale(MonetaryAmountFactory f, int scale){ 053 StringBuilder b = new StringBuilder(scale + 2); 054 b.append("9."); 055 for(int i = 0; i < scale; i++){ 056 b.append(String.valueOf(i % 10)); 057 } 058 return new BigDecimal(b.toString(), MathContext.UNLIMITED); 059 } 060 061 062 public static void testSerializable(String section, Class c){ 063 if(!Serializable.class.isAssignableFrom(c)){ 064 throw new TCKValidationException(section + ": Class must be serializable: " + c.getName()); 065 } 066 } 067 068 public static void testImmutable(String section, Class c){ 069 try{ 070 MutabilityAssert.assertInstancesOf(c, MutabilityMatchers.areImmutable(), AllowedReason 071 .provided(Currency.class, MonetaryAmount.class, 072 CurrencyUnit.class, NumberValue.class, 073 MonetaryOperator.class, MonetaryQuery.class) 074 .areAlsoImmutable(), AllowedReason.allowingForSubclassing(), 075 AllowedReason.allowingNonFinalFields()); 076 } 077 catch(Exception e){ 078 throw new TCKValidationException(section + ": Class is not immutable: " + c.getName(), e); 079 } 080 } 081 082 public static void testSerializable(String section, Object o){ 083 if(!(o instanceof Serializable)){ 084 throw new TCKValidationException(section + ": Class must be serializable: " + o.getClass().getName()); 085 } 086 try( 087 ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream())){ 088 oos.writeObject(o); 089 } 090 catch(Exception e){ 091 throw new TCKValidationException( 092 "Class must be serializable, but serialization failed: " + o.getClass().getName(), e); 093 } 094 } 095 096 public static void testImplementsInterface(String section, Class type, Class iface){ 097 for(Class ifa : type.getInterfaces()){ 098 if(ifa.equals(iface)){ 099 return; 100 } 101 } 102 Assert.fail(section + ": Class must implement " + iface.getName() + ", but does not: " + type.getName()); 103 } 104 105 public static void testHasPublicMethod(String section, Class type, Class returnType, String name, 106 Class... paramTypes){ 107 Class current = type; 108 while(current != null){ 109 for(Method m : current.getDeclaredMethods()){ 110 if(returnType.equals(returnType) && 111 m.getName().equals(name) && 112 ((m.getModifiers() & Modifier.PUBLIC) != 0) && 113 Arrays.equals(m.getParameterTypes(), paramTypes)){ 114 return; 115 } 116 } 117 current = current.getSuperclass(); 118 } 119 throw new TCKValidationException( 120 section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " + 121 returnType.getName() + ", but does not: " + type.getName()); 122 } 123 124 public static void testHasPublicStaticMethod(String section, Class type, Class returnType, String name, 125 Class... paramTypes){ 126 Class current = type; 127 while(current != null){ 128 for(Method m : current.getDeclaredMethods()){ 129 if(returnType.equals(returnType) && 130 m.getName().equals(name) && 131 ((m.getModifiers() & Modifier.PUBLIC) != 0) && 132 ((m.getModifiers() & Modifier.STATIC) != 0) && 133 Arrays.equals(m.getParameterTypes(), paramTypes)){ 134 return; 135 } 136 } 137 current = current.getSuperclass(); 138 } 139 throw new TCKValidationException( 140 section + ": Class must implement method " + name + '(' + Arrays.toString(paramTypes) + "): " + 141 returnType.getName() + ", but does not: " + type.getName()); 142 } 143 144 public static void testHasNotPublicMethod(String section, Class type, Class returnType, String name, 145 Class... paramTypes){ 146 Class current = type; 147 while(current != null){ 148 for(Method m : current.getDeclaredMethods()){ 149 if(returnType.equals(returnType) && 150 m.getName().equals(name) && 151 Arrays.equals(m.getParameterTypes(), paramTypes)){ 152 throw new TCKValidationException( 153 section + ": Class must NOT implement method " + name + '(' + Arrays.toString(paramTypes) + 154 "): " + returnType.getName() + ", but does: " + type.getName()); 155 } 156 } 157 current = current.getSuperclass(); 158 } 159 } 160 161 public static void testComparable(String section, Class type){ 162 testImplementsInterface(section, type, Comparable.class); 163 } 164 165 public static void assertValue(String section, Object value, String methodName, Object instance) 166 throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, 167 InvocationTargetException{ 168 Method m = instance.getClass().getDeclaredMethod(methodName); 169 Assert.assertEquals(section + ": " + m.getName() + '(' + instance + ") returned invalid value:", value, 170 m.invoke(instance)); 171 } 172 173 public static boolean testImmutableOpt(String section, Class type){ 174 try{ 175 testImmutable(section, type); 176 return true; 177 } 178 catch(Exception e){ 179 warnings.append(section).append(": Recommendation failed: Class should be immutable: ") 180 .append(type.getName()).append(", details: ").append(e.getMessage()).append("\n"); 181 return false; 182 } 183 } 184 185 public static boolean testSerializableOpt(String section, Class type){ 186 try{ 187 testSerializable(section, type); 188 return true; 189 } 190 catch(Exception e){ 191 warnings.append(section).append(": Recommendation failed: Class should be serializable: ") 192 .append(type.getName()).append(", details: ").append(e.getMessage()).append("\n"); 193 return false; 194 } 195 } 196 197 public static boolean testHasPublicStaticMethodOpt(String section, Class type, Class returnType, String methodName, 198 Class... paramTypes){ 199 try{ 200 testHasPublicStaticMethod(section, type, returnType, methodName, paramTypes); 201 return true; 202 } 203 catch(Exception e){ 204 warnings.append(section).append(": Recommendation failed: Missing method [public static ") 205 .append(methodName).append('(').append(Arrays.toString(paramTypes)).append("):") 206 .append(returnType.getName()).append("] on: ").append(type.getName()).append("\n"); 207 return false; 208 } 209 } 210 211 public static boolean testSerializableOpt(String section, Object instance){ 212 try{ 213 testSerializable(section, instance); 214 return true; 215 } 216 catch(Exception e){ 217 warnings.append(section) 218 .append(": Recommendation failed: Class is serializable, but serialization failed: ") 219 .append(instance.getClass().getName()).append("\n"); 220 return false; 221 } 222 } 223 224 public static void resetWarnings(){ 225 warnings.setLength(0); 226 } 227 228 public static String getWarnings(){ 229 return warnings.toString(); 230 } 231 232 public static MonetaryAmount createAmountWithScale(int scale){ 233 MonetaryAmountFactoryQuery tgtContext = MonetaryAmountFactoryQueryBuilder.of().setMaxScale(scale).build(); 234 MonetaryAmountFactory<?> exceedingFactory; 235 try{ 236 exceedingFactory = MonetaryAmounts.getAmountFactory(tgtContext); 237 AssertJUnit.assertNotNull(exceedingFactory); 238 MonetaryAmountFactory<? extends MonetaryAmount> bigFactory = 239 MonetaryAmounts.getAmountFactory(exceedingFactory.getAmountType()); 240 return bigFactory.setCurrency("CHF").setNumber(createNumberWithScale(bigFactory, scale)).create(); 241 } 242 catch(MonetaryException e){ 243 return null; 244 } 245 } 246 247 public static MonetaryAmount createAmountWithPrecision(int precision){ 248 MonetaryAmountFactoryQuery tgtContext = MonetaryAmountFactoryQueryBuilder.of().setPrecision(precision).build(); 249 MonetaryAmountFactory<?> exceedingFactory; 250 try{ 251 exceedingFactory = MonetaryAmounts.getAmountFactory(tgtContext); 252 AssertJUnit.assertNotNull(exceedingFactory); 253 MonetaryAmountFactory<? extends MonetaryAmount> bigFactory = 254 MonetaryAmounts.getAmountFactory(exceedingFactory.getAmountType()); 255 return bigFactory.setCurrency("CHF").setNumber(createNumberWithPrecision(bigFactory, precision)).create(); 256 } 257 catch(MonetaryException e){ 258 return null; 259 } 260 } 261}