/** * Copyright 2010 Wealthfront Inc. 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 com.kaching.platform.converters; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Lists.newArrayListWithCapacity; import static com.google.common.collect.Maps.newHashMap; import static com.kaching.platform.converters.ConstructorAnalysis.analyse; import static java.math.BigDecimal.ZERO; import static java.math.MathContext.DECIMAL32; import static java.util.Collections.emptyMap; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.junit.Ignore; import org.junit.Test; import com.google.common.collect.ImmutableMap; import com.kaching.platform.converters.ConstructorAnalysis.FormalParameter; import com.kaching.platform.converters.ConstructorAnalysis.IllegalConstructorException; public class ConstructorAnalysisTest { static class NoOp { } @Test public void noOp() throws Exception { assertAssignement(NoOp.class, emptyMap()); } static class OneAssignment { final int foo; OneAssignment(int foo) { this.foo = foo; } } @Test public void oneAssignment() throws Exception { assertAssignement( OneAssignment.class, ImmutableMap.of( "foo", "p0")); } static class TwoAssignments { final String foo; final int bar; TwoAssignments(int bar, String foo) { this.bar = bar; this.foo = foo; } } @Test public void twoAssignments() throws Exception { assertAssignement( TwoAssignments.class, ImmutableMap.of( "bar", "p0", "foo", "p1")); } @Test public void assignmentsToNatives() throws Exception { assertAssignement( Natives.class, ImmutableMap.<String, String> builder() .put("i", "p0") .put("d", "p1") .put("s", "p2") .put("c", "p3") .put("l", "p4") .put("b", "p5") .put("f", "p6") .put("y", "p7") .build()); } static class DupAssignment { int foo; DupAssignment(int bar, int foo) { this.foo = bar; this.foo = foo; } } @Test public void dupAssignment() throws Exception { assertAnalysisFails( DupAssignment.class, "duplicate assignment to field foo"); } static class KeepingSelfReference { KeepingSelfReference reference; KeepingSelfReference() { this.reference = this; } } @Test public void keepingSelfReference() throws Exception { assertAssignement( KeepingSelfReference.class, Collections.emptyMap()); } static class AssigningSomethingElseThanParamater { int data; AssigningSomethingElseThanParamater(int data) { this.data = 4; } } @Test public void assigningSomethingElseThanParamater() throws Exception { assertAssignement( AssigningSomethingElseThanParamater.class, Collections.emptyMap()); } static class ObjectInstantiation1 { ObjectInstantiation1() { new Object(); } } @Test public void objectInstantiation1() throws Exception { assertAssignement( ObjectInstantiation1.class, Collections.emptyMap()); } static class ObjectInstantiation2 { Object foo; ObjectInstantiation2() { this.foo = new Object(); } } @Test public void objectInstantiation2() throws Exception { assertAssignement( ObjectInstantiation2.class, Collections.emptyMap()); } static class ObjectInstantiation3 { Object foo; ObjectInstantiation3() { this.foo = new String("hello world!"); } } @Test public void objectInstantiation3() throws Exception { assertAssignement( ObjectInstantiation3.class, Collections.emptyMap()); } static class ObjectInstantiation4 { Object foo; ObjectInstantiation4() { this.foo = new BigDecimal(4.5, DECIMAL32); } } @Test public void objectInstantiation4() throws Exception { assertAssignement( ObjectInstantiation4.class, Collections.emptyMap()); } static class ObjectInstantiation5 { Object foo; ObjectInstantiation5(String value) { this.foo = new String(value); } } @Test public void objectInstantiation5() throws Exception { assertAnalysisFails( ObjectInstantiation5.class, "can not assign non-idempotent expression new java.lang.String.<init>(p0) to field"); } static class CheckArgument1 { final BigDecimal value; CheckArgument1(BigDecimal value) { checkArgument(ZERO.compareTo(value) <= 0); this.value = value; } } @Test @Ignore public void checkArgument1() throws Exception { assertAssignement( CheckArgument1.class, ImmutableMap.of( "value", "p0")); } static class DoingMathOperation1 { int foo; DoingMathOperation1(int foo) { this.foo = foo + 9; } } @Test public void doingMathOperation1() throws Exception { assertAnalysisFails( DoingMathOperation1.class, "can not assign non-idempotent expression p0 + 9 to field"); } static class DoingMathOperation2 { long foo; DoingMathOperation2(long foo) { this.foo = foo * 5; } } @Test public void doingMathOperation2() throws Exception { assertAnalysisFails( DoingMathOperation2.class, "can not assign non-idempotent expression p0 * 5 to field"); } static class ConstantHolder { static int CONSTANT = 4; } static class DoingMathOperation3 { int foo; DoingMathOperation3(int foo) { this.foo = foo % ConstantHolder.CONSTANT; } } @Test public void doingMathOperation3() throws Exception { assertAnalysisFails( DoingMathOperation3.class, "can not assign non-idempotent expression p0 % com.kaching.platform.converters.ConstructorAnalysisTest$ConstantHolder#CONSTANT to field"); } static class CalledSuperclass { CalledSuperclass() {} CalledSuperclass(int foo) {} } static class CallingSuperConstructorNoArgument extends CalledSuperclass { } static class CallingSuperConstructorWithArguments extends CalledSuperclass { CallingSuperConstructorWithArguments(int foo) { super(foo); } } @Test public void callingSuperConstructorNoArgument() throws Exception { assertAssignement( CallingSuperConstructorNoArgument.class, Collections.emptyMap()); } @Test public void callingSuperConstructorWithArguments() throws Exception { assertAnalysisFails( CallingSuperConstructorWithArguments.class, "can not call super constructor with argument(s)"); } static class DelegatingToAnotherConstructor1 { DelegatingToAnotherConstructor1(int foo) { this(); } DelegatingToAnotherConstructor1() { } } @Test public void delegatingToAnotherConstructor1() throws Exception { assertAnalysisFails( DelegatingToAnotherConstructor1.class, "can not delegate to another constructor"); } static class DelegatingToAnotherConstructor2 { DelegatingToAnotherConstructor2() { this(4); } DelegatingToAnotherConstructor2(int foo) { } } @Test public void delegatingToAnotherConstructor2() throws Exception { assertAnalysisFails( DelegatingToAnotherConstructor2.class, "can not delegate to another constructor"); } static class InvokeInterface { int size; InvokeInterface(Set<Integer> set) { this.size = set.size(); } } @Test public void invokeInterface() throws Exception { assertAnalysisFails( InvokeInterface.class, "can not assign non-idempotent expression p0.size() to field"); } abstract static class CalledByInvokeVirtual { abstract int callme(); } static class InvokeVirtual { int size; InvokeVirtual(CalledByInvokeVirtual value) { this.size = value.callme(); } } @Test public void invokeVirtual() throws Exception { assertAnalysisFails( InvokeVirtual.class, "can not assign non-idempotent expression p0.callme() to field"); } static class InvokeStatic1 { final Map<String, String> map1 = newHashMap(); final Map<String, String> map2; InvokeStatic1() { this.map2 = newHashMap(); } } @Test public void invokeStatic1() throws Exception { assertAssignement( InvokeStatic1.class, Collections.emptyMap()); } static class InvokeStatic2 { InvokeStatic2(int size) { newArrayListWithCapacity(size); } } @Test public void invokeStatic2() throws Exception { assertAssignement( InvokeStatic2.class, Collections.emptyMap()); } static class InvokeStatic3 { List<Object> list; InvokeStatic3(int size) { this.list = newArrayListWithCapacity(size); } } @Test public void invokeStatic3() throws Exception { assertAnalysisFails( InvokeStatic3.class, "can not assign non-idempotent expression com.google.common.collect.Lists.newArrayListWithCapacity(p0) to field"); } static class WriteToMyField { int myfield; } static class PutInDifferentField { PutInDifferentField(WriteToMyField value) { value.myfield = value.myfield + 5; } } @Test public void putInDifferentField() throws Exception { assertAssignement( PutInDifferentField.class, Collections.emptyMap()); } static class ContainerToTrickLibraryUsingAliasing { List<String> ref; } static class AliasingResolutionIsNotSupported { List<String> non_idempotent; AliasingResolutionIsNotSupported( ContainerToTrickLibraryUsingAliasing trick, List<String> names) { trick.ref = names; trick.ref.add(Integer.toString(trick.ref.size())); this.non_idempotent = names; } } @Test public void aliasingResolutionIsNotSupported() throws Exception { /* Due to the lack of support for aliasing, one can successfully trick the * analysis. This is not an important case to protect for. */ assertAssignement( AliasingResolutionIsNotSupported.class, ImmutableMap.of( "non_idempotent", "p1")); } @Test public void regression1() throws IOException { InputStream classInputStream = this.getClass().getResourceAsStream("example_scala_class01.bin"); assertNotNull(classInputStream); Map<String, FormalParameter> assignements = analyse(classInputStream, "com/kaching/trading/rules/formula/FormulaValue", "java/lang/Object", int.class).assignments; assertMapEqualAsString(ImmutableMap.of("number", "p0"), assignements); } @Test public void regression2() throws IOException { InputStream classInputStream = this.getClass().getResourceAsStream("example_scala_class02.bin"); assertNotNull(classInputStream); Map<String, FormalParameter> assignements = analyse(classInputStream, "com/kaching/user/GetAllModels", "com/kaching/platform/queryengine/AbstractQuery", Boolean.class).assignments; assertMapEqualAsString(ImmutableMap.of("com$kaching$user$GetAllModels$$withHistory", "p0"), assignements); } static class Regression3 { @SuppressWarnings("unused") private Object unimportant = ImmutableMap.builder().build(); } @Test public void regression3() throws IOException { assertAssignement( Regression3.class, Collections.emptyMap()); } static class CallingMethodOnStaticObject { static Object ref = new Object(); CallingMethodOnStaticObject() { CallingMethodOnStaticObject.ref.hashCode(); } } @Test public void callingMethodOnStaticObject() throws Exception { assertAssignement( CallingMethodOnStaticObject.class, Collections.emptyMap()); } static class BoxingBoolean { boolean unboxed; Boolean boxed; BoxingBoolean(Boolean unboxed, boolean boxed) { this.unboxed = unboxed; this.boxed = boxed; } } @Test public void boxingBoolean() throws Exception { assertAssignement( BoxingBoolean.class, ImmutableMap.of( "unboxed", "p0", "boxed", "p1")); } private void assertAnalysisFails(Class<?> klass, String message) throws IOException { try { ConstructorAnalysis.analyse(klass, klass.getDeclaredConstructors()[0]); fail("analysis should have failed"); } catch (IllegalConstructorException e) { assertEquals(message, e.getMessage()); } } private void assertAssignement( Class<?> klass, Map<?, ?> expected) throws IOException { Map<String, FormalParameter> assignements = ConstructorAnalysis.analyse(klass, klass.getDeclaredConstructors()[0]) .assignments; Map<String, String> actual = newHashMap(); for (Entry<String, FormalParameter> entry : assignements.entrySet()) { actual.put(entry.getKey(), entry.getValue().toString()); } assertEquals(expected, actual); } private void assertMapEqualAsString(Map<String, String> expected, Map<String, ?> actual) { Map<String, String> actualStrings = newHashMap(); for (Entry<String, ?> entry : actual.entrySet()) { actualStrings.put(entry.getKey(), entry.getValue().toString()); } assertEquals(expected, actualStrings); } }