Skip to content

Commit 75ec3b9

Browse files
committed
[#231] Updated toString method generation
+ added stubbedInstrumentation
1 parent 3ad4676 commit 75ec3b9

File tree

11 files changed

+199
-24
lines changed

11 files changed

+199
-24
lines changed

atom-basis/src/main/java/com/pragmaticobjects/oo/atom/codegen/bytebuddy/smt/SmtAtomFieldToString.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
package com.pragmaticobjects.oo.atom.codegen.bytebuddy.smt;
2828

29+
import com.pragmaticobjects.oo.atom.codegen.bytebuddy.matchers.NaturalJavaAtom;
2930
import net.bytebuddy.description.field.FieldDescription;
3031
import net.bytebuddy.description.type.TypeDescription;
3132

@@ -35,17 +36,49 @@
3536
*
3637
* @author Kapralov Sergey
3738
*/
38-
public class SmtAtomFieldToString extends SmtCombined {
39+
public class SmtAtomFieldToString extends SmtInferred {
3940
/**
4041
* Ctor.
41-
* @param type Type
4242
* @param field Field
4343
*/
44-
public SmtAtomFieldToString(final TypeDescription type, final FieldDescription field) {
44+
public SmtAtomFieldToString(final FieldDescription field) {
4545
super(
46-
new SmtFieldName(field),
47-
new SmtLoadField(field),
48-
new SmtInvokeAtomToString(type)
46+
new Inference(field)
4947
);
5048
}
49+
50+
/**
51+
* {@link SmtAtomFieldToString} inference
52+
*
53+
*/
54+
private static class Inference implements StackManipulationToken.Inference {
55+
private final FieldDescription field;
56+
57+
/**
58+
* Ctor.
59+
* @param field Field
60+
*/
61+
public Inference(FieldDescription field) {
62+
this.field = field;
63+
}
64+
65+
@Override
66+
public final StackManipulationToken stackManipulationToken() {
67+
final TypeDescription declaringType = field.getDeclaringType().asErasure();
68+
final TypeDescription fieldType = field.getType().asErasure();
69+
if(new NaturalJavaAtom().matches(fieldType)) {
70+
return new SmtCombined(
71+
new SmtFieldName(field),
72+
new SmtLoadField(field),
73+
new SmtInvokeAtomToStringNatural(declaringType)
74+
);
75+
} else {
76+
return new SmtCombined(
77+
new SmtFieldName(field),
78+
new SmtLoadField(field),
79+
new SmtInvokeAtomToString(declaringType)
80+
);
81+
}
82+
}
83+
}
5184
}

atom-basis/src/main/java/com/pragmaticobjects/oo/atom/codegen/bytebuddy/smt/SmtAtomFieldsToString.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ public final StackManipulationToken stackManipulationToken() {
7575
List.of(type)
7676
.flatMap(TypeDescription::getDeclaredFields)
7777
.filter(f -> !f.isStatic())
78-
.<StackManipulationToken>map(f -> new SmtAtomFieldToString(type, f))
78+
.<StackManipulationToken>map(SmtAtomFieldToString::new)
7979
.prepend(new SmtAtomTypeToString(type))
80-
.toJavaArray(StackManipulationToken.class)
80+
.toJavaArray(i -> new StackManipulationToken[i])
8181
),
8282
new SmtInvokeMethod(
8383
new TypeDescription.ForLoadedType(String.class),

atom-basis/src/main/java/com/pragmaticobjects/oo/atom/codegen/bytebuddy/smt/SmtAtomTypeToString.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public SmtAtomTypeToString(final TypeDescription type) {
4343
super(
4444
new SmtString("@type"),
4545
new SmtTypeName(type),
46-
new SmtInvokeAtomToString(type)
46+
new SmtInvokeAtomToStringNatural(type)
4747
);
4848
}
4949
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2017 Kapralov Sergey.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package com.pragmaticobjects.oo.atom.codegen.bytebuddy.smt;
26+
27+
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
28+
import static net.bytebuddy.matcher.ElementMatchers.named;
29+
30+
import com.pragmaticobjects.oo.atom.codegen.bytebuddy.matchers.ConjunctionMatcher;
31+
import com.pragmaticobjects.oo.atom.codegen.javassist.templates.Atoms;
32+
33+
import net.bytebuddy.description.type.TypeDescription;
34+
35+
/**
36+
* Generates invocation of method {@link Atoms#atom$toString$natural}.
37+
*
38+
* @see Atoms
39+
* @author Kapralov Sergey
40+
*/
41+
public class SmtInvokeAtomToStringNatural extends SmtInvokeMethod {
42+
/**
43+
* Ctor.
44+
*
45+
* @param type Type to call method on.
46+
*/
47+
public SmtInvokeAtomToStringNatural(final TypeDescription type) {
48+
super(
49+
type,
50+
new ConjunctionMatcher<>(
51+
isSynthetic(),
52+
named("atom$toString$natural")
53+
)
54+
);
55+
}
56+
}

atom-basis/src/main/java/com/pragmaticobjects/oo/atom/codegen/javassist/plugin/InlineTemplates.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@
3838
* @author Kapralov Sergey
3939
*/
4040
public class InlineTemplates implements Plugin {
41+
private final boolean stubbedInstrumentation;
42+
43+
/**
44+
* Ctor.
45+
* @param stubbedInstrumentation stub calls to {@link System#identityHashCode}.
46+
* If set to true, 42 integer will be substituted where normally
47+
* {@link System#identityHashCode} is called
48+
*/
49+
public InlineTemplates(boolean stubbedInstrumentation) {
50+
this.stubbedInstrumentation = stubbedInstrumentation;
51+
}
52+
4153
@Override
4254
public final void operateOn(final CtClass clazz, final ClassPool classPool) {
4355
try {
@@ -63,6 +75,23 @@ public final void operateOn(final CtClass clazz, final ClassPool classPool) {
6375
m.setModifiers(AccessFlag.STATIC | AccessFlag.PRIVATE | AccessFlag.SYNTHETIC | AccessFlag.BRIDGE);
6476
clazz.addMethod(m);
6577
}
78+
{
79+
final CtMethod hashCode = classPool.get(Atoms.class.getName()).getDeclaredMethod("atom$toString$natural");
80+
CtMethod m = CtNewMethod.copy(hashCode, "atom$toString$natural", clazz, null);
81+
m.setModifiers(AccessFlag.STATIC | AccessFlag.PRIVATE | AccessFlag.SYNTHETIC | AccessFlag.BRIDGE);
82+
clazz.addMethod(m);
83+
}
84+
if(stubbedInstrumentation) {
85+
final CtMethod hashCode = classPool.get(Atoms.class.getName()).getDeclaredMethod("atom$hashCode$identity$stub");
86+
CtMethod m = CtNewMethod.copy(hashCode, "atom$hashCode$identity", clazz, null);
87+
m.setModifiers(AccessFlag.STATIC | AccessFlag.PRIVATE | AccessFlag.SYNTHETIC | AccessFlag.BRIDGE);
88+
clazz.addMethod(m);
89+
} else {
90+
final CtMethod hashCode = classPool.get(Atoms.class.getName()).getDeclaredMethod("atom$hashCode$identity");
91+
CtMethod m = CtNewMethod.copy(hashCode, "atom$hashCode$identity", clazz, null);
92+
m.setModifiers(AccessFlag.STATIC | AccessFlag.PRIVATE | AccessFlag.SYNTHETIC | AccessFlag.BRIDGE);
93+
clazz.addMethod(m);
94+
}
6695
} catch(Exception ex) {
6796
throw new RuntimeException(ex);
6897
}

atom-basis/src/main/java/com/pragmaticobjects/oo/atom/codegen/javassist/templates/Atoms.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,31 @@ private Atoms() {
6868
result = 31 * result;
6969
if(element != null) {
7070
final boolean annotationPresent = element.getClass().isAnnotationPresent(Atom.class);
71-
final int hashCode = annotationPresent ? element.hashCode() : System.identityHashCode(element);
71+
final int hashCode = annotationPresent ? element.hashCode() : atom$hashCode$identity(element);
7272
result = result + (element == null ? 0 : hashCode);
7373
}
7474
}
7575
return result;
7676
}
7777

78+
static int atom$hashCode$identity(Object element) {
79+
return System.identityHashCode(element);
80+
}
81+
82+
static int atom$hashCode$identity$stub(Object element) {
83+
return 42;
84+
}
85+
86+
private static String atom$toString$natural(String name, Object value) {
87+
return "\"" + name + "\": \"" + value.toString() + "\"";
88+
}
89+
7890
private static String atom$toString(String name, Object value) {
7991
final boolean annotationPresent = value.getClass().isAnnotationPresent(Atom.class);
8092
if(annotationPresent) {
8193
return "\"" + name + "\": " + value.toString();
8294
} else {
83-
return "\"" + name + "\": \"" + value.toString() + "\"";
95+
return "\"" + name + "\": \"" + value.getClass().getName() + "#" + atom$hashCode$identity(value) + "\"";
8496
}
8597
}
8698
}

atom-basis/src/main/java/com/pragmaticobjects/oo/atom/codegen/stage/StandardInstrumentationStage.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
public class StandardInstrumentationStage extends SequenceStage {
1717
/**
1818
* Ctor.
19+
*
20+
* @param stubbedInstrumentation generates stubbed implementations of certain calls. Useful for testing.
1921
*/
20-
public StandardInstrumentationStage() {
22+
public StandardInstrumentationStage(boolean stubbedInstrumentation) {
2123
super(
2224
new ShowBannerStage(
2325
new BnnrFromResource(
@@ -42,7 +44,7 @@ public StandardInstrumentationStage() {
4244
),
4345
new JavassistStage(
4446
new com.pragmaticobjects.oo.atom.codegen.javassist.plugin.VerbosePlugin(
45-
new InlineTemplates()
47+
new InlineTemplates(stubbedInstrumentation)
4648
)
4749
),
4850
new ByteBuddyStage(
@@ -57,4 +59,11 @@ public StandardInstrumentationStage() {
5759
)
5860
);
5961
}
62+
63+
/**
64+
* Default ctor.
65+
*/
66+
public StandardInstrumentationStage() {
67+
this(false);
68+
}
6069
}

atom-it/pom.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@
1313
<dependency>
1414
<groupId>com.pragmaticobjects.oo.atom</groupId>
1515
<artifactId>atom-basis</artifactId>
16-
<version>${project.version}</version>
1716
</dependency>
1817
<dependency>
1918
<groupId>io.vavr</groupId>
2019
<artifactId>vavr</artifactId>
21-
<version>0.10.0</version>
2220
</dependency>
2321
</dependencies>
2422

@@ -77,6 +75,9 @@
7775
<goal>instrument</goal>
7876
<goal>instrument-tests</goal>
7977
</goals>
78+
<configuration>
79+
<stubbedInstrumentation>true</stubbedInstrumentation>
80+
</configuration>
8081
</execution>
8182
</executions>
8283
</plugin>

atom-it/src/test/java/com/pragmaticobjects/oo/atom/it/ToStringTest.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.pragmaticobjects.oo.atom.tests.AssertAtomsToString;
3030
import com.pragmaticobjects.oo.atom.tests.TestCase;
3131
import com.pragmaticobjects.oo.atom.tests.TestsSuite;
32+
import java.util.HashMap;
3233

3334
/**
3435
* Tests suite for Atoms toString logic.
@@ -52,8 +53,8 @@ public ToStringTest() {
5253
"",
5354
"{",
5455
"\"@type\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$SimpleAtom\", ",
55-
"\"a\": \"1/2\", ",
56-
"\"b\": \"3/4\"",
56+
"\"a\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$NotAtom#42\", ",
57+
"\"b\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$NotAtom#42\"",
5758
"}"
5859
)
5960
)
@@ -77,17 +78,30 @@ public ToStringTest() {
7778
"\"@type\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$ComplexAtom\", ",
7879
"\"a\": {",
7980
"\"@type\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$SimpleAtom\", ",
80-
"\"a\": \"1/2\", ",
81-
"\"b\": \"3/4\"",
81+
"\"a\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$NotAtom#42\", ",
82+
"\"b\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$NotAtom#42\"",
8283
"}, ",
8384
"\"b\": {",
8485
"\"@type\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$SimpleAtom\", ",
85-
"\"a\": \"5/6\", ",
86-
"\"b\": \"7/8\"",
86+
"\"a\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$NotAtom#42\", ",
87+
"\"b\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$NotAtom#42\"",
8788
"}",
8889
"}"
8990
)
9091
)
92+
),
93+
new TestCase(
94+
"Atom with a link to itself via non-atom attribute",
95+
new AssertAtomsToString(
96+
new AtomWithRecursiveNonAtomLink(),
97+
String.join(
98+
"",
99+
"{",
100+
"\"@type\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$AtomWithRecursiveNonAtomLink\", ",
101+
"\"hashMap\": \"com.pragmaticobjects.oo.atom.it.ToStringTest$AtomWithRecursiveNonAtomLink$AHashMap#42\"",
102+
"}"
103+
)
104+
)
91105
)
92106
);
93107
}
@@ -128,4 +142,19 @@ public String toString() {
128142
return a + "/" + b;
129143
}
130144
}
145+
146+
public static class AtomWithRecursiveNonAtomLink {
147+
private final HashMap hashMap;
148+
149+
public AtomWithRecursiveNonAtomLink() {
150+
this.hashMap = new AHashMap(this);
151+
}
152+
153+
@com.pragmaticobjects.oo.atom.anno.NotAtom
154+
private static class AHashMap extends HashMap {
155+
public AHashMap(Object that) {
156+
put("this", that);
157+
}
158+
}
159+
}
131160
}

atom-maven-plugin/src/main/java/com/pragmaticobjects/oo/atom/maven/InstrumentMojo.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import java.nio.file.Paths;
3535

3636
/**
37-
* Mojo that instruments prodiction code
37+
* Mojo that instruments production code
3838
*
3939
* @author Kapralov Sergey
4040
*/
@@ -43,10 +43,13 @@ public class InstrumentMojo extends BaseMojo {
4343
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
4444
protected String outputDirectory;
4545

46+
@Parameter(defaultValue = "false", required = true, readonly = true)
47+
protected boolean stubbedInstrumentation;
48+
4649
@Override
4750
public final void execute() throws MojoExecutionException, MojoFailureException {
4851
doInstrumentation(
49-
new StandardInstrumentationStage(),
52+
new StandardInstrumentationStage(stubbedInstrumentation),
5053
Paths.get(outputDirectory)
5154
);
5255
}

0 commit comments

Comments
 (0)