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}