Skip to content

Commit 1789ba0

Browse files
committed
Add parameter name reflection
1 parent b42ba41 commit 1789ba0

File tree

6 files changed

+128
-10
lines changed

6 files changed

+128
-10
lines changed

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
<source>1.8</source>
9494
<target>1.8</target>
9595
<optimize>true</optimize>
96+
<compilerArgs>
97+
<arg>-parameters</arg>
98+
</compilerArgs>
9699
</configuration>
97100
</plugin>
98101
<plugin>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package j2html.attributes;
2+
3+
// Written by Benjamin Weber (http://benjiweber.co.uk/blog/author/benji/)
4+
5+
import j2html.reflection.MethodFinder;
6+
import java.util.function.Function;
7+
8+
public interface LambdaAttribute extends MethodFinder, Function<String, Object> {
9+
10+
default String name() {
11+
checkParametersEnabled();
12+
return parameter(0).getName();
13+
}
14+
15+
default String value() {
16+
checkParametersEnabled();
17+
return String.valueOf(this.apply(name()));
18+
}
19+
20+
default void checkParametersEnabled() {
21+
if ("arg0".equals(parameter(0).getName())) {
22+
throw new IllegalStateException("You also need java 8u60 or newer for parameter reflection to work");
23+
}
24+
}
25+
26+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package j2html.reflection;
2+
3+
// Written by Benjamin Weber (http://benjiweber.co.uk/blog/author/benji/)
4+
5+
import java.io.Serializable;
6+
import java.lang.invoke.SerializedLambda;
7+
import java.lang.reflect.Method;
8+
import java.lang.reflect.Parameter;
9+
import java.util.Arrays;
10+
import java.util.Objects;
11+
12+
public interface MethodFinder extends Serializable {
13+
14+
default SerializedLambda serialized() {
15+
try {
16+
Method replaceMethod = getClass().getDeclaredMethod("writeReplace");
17+
replaceMethod.setAccessible(true);
18+
return (SerializedLambda) replaceMethod.invoke(this);
19+
} catch (Exception e) {
20+
throw new RuntimeException(e);
21+
}
22+
}
23+
24+
default Class<?> getContainingClass() {
25+
try {
26+
String className = serialized().getImplClass().replaceAll("/", ".");
27+
return Class.forName(className);
28+
} catch (Exception e) {
29+
throw new RuntimeException(e);
30+
}
31+
}
32+
33+
default Method method() {
34+
SerializedLambda lambda = serialized();
35+
Class<?> containingClass = getContainingClass();
36+
return Arrays.stream(containingClass.getDeclaredMethods())
37+
.filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName()))
38+
.findFirst()
39+
.orElseThrow(UnableToGuessMethodException::new);
40+
}
41+
42+
default Parameter parameter(int n) {
43+
return method().getParameters()[n];
44+
}
45+
46+
class UnableToGuessMethodException extends RuntimeException {
47+
}
48+
}

src/main/java/j2html/tags/Tag.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import j2html.attributes.Attr;
44
import j2html.attributes.Attribute;
5+
import j2html.attributes.LambdaAttribute;
56
import java.io.IOException;
67
import java.util.ArrayList;
78
import java.util.Iterator;
@@ -79,7 +80,7 @@ public T attr(String attribute, Object value) {
7980
setAttribute(attribute, value == null ? null : String.valueOf(value));
8081
return (T) this;
8182
}
82-
83+
8384
/**
8485
* Adds the specified attribute. If the Tag previously contained an attribute with the same name, the old attribute is replaced by the specified attribute.
8586
*
@@ -134,11 +135,6 @@ public boolean equals(Object obj) {
134135
return ((Tag) obj).render().equals(this.render());
135136
}
136137

137-
/**
138-
* Convenience methods that call attr with predefined attributes
139-
*
140-
* @return itself for easy chaining
141-
*/
142138
public T withClasses(String... classes) {
143139
StringBuilder sb = new StringBuilder();
144140
for (String s : classes) {
@@ -147,6 +143,19 @@ public T withClasses(String... classes) {
147143
return attr(Attr.CLASS, sb.toString().trim());
148144
}
149145

146+
public T withAttrs(LambdaAttribute... lambdaAttributes) {
147+
for (LambdaAttribute attr : lambdaAttributes) {
148+
attr(attr.name(), attr.value());
149+
}
150+
return (T) this;
151+
}
152+
153+
/**
154+
* Convenience methods that call attr with predefined attributes
155+
*
156+
* @return itself for easy chaining
157+
*/
158+
150159
public T isAutoComplete() {
151160
return attr(Attr.AUTOCOMPLETE, null);
152161
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package j2html.reflection;
2+
3+
import j2html.attributes.LambdaAttribute;
4+
import org.junit.Test;
5+
import static org.hamcrest.CoreMatchers.is;
6+
import static org.hamcrest.MatcherAssert.assertThat;
7+
8+
public class NamedValueTest {
9+
10+
@Test
11+
public void testNamedValueWorks() {
12+
LambdaAttribute pair = five -> 5;
13+
assertThat("five", is(pair.name()));
14+
assertThat("5", is(pair.value()));
15+
}
16+
17+
}

src/test/java/j2html/tags/TagTest.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package j2html.tags;
22

3+
import j2html.Config;
4+
import j2html.model.DynamicHrefAttribute;
5+
import org.junit.Test;
6+
import static j2html.TagCreator.a;
37
import static j2html.TagCreator.body;
48
import static j2html.TagCreator.div;
59
import static j2html.TagCreator.footer;
10+
import static j2html.TagCreator.form;
611
import static j2html.TagCreator.header;
712
import static j2html.TagCreator.html;
813
import static j2html.TagCreator.iff;
@@ -13,9 +18,6 @@
1318
import static j2html.TagCreator.tag;
1419
import static org.hamcrest.MatcherAssert.assertThat;
1520
import static org.hamcrest.Matchers.is;
16-
import j2html.Config;
17-
import j2html.model.DynamicHrefAttribute;
18-
import org.junit.Test;
1921

2022
public class TagTest {
2123

@@ -98,11 +100,24 @@ public void testDynamicAttribute() throws Exception {
98100
ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr(new DynamicHrefAttribute());
99101
assertThat(testTagWithAttrValueNull.render(), is("<a href=\"/\"></a>"));
100102
}
101-
103+
102104
@Test
103105
public void testDynamicAttributeReplacement() throws Exception {
104106
ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr("href", "/link").attr(new DynamicHrefAttribute());
105107
assertThat(testTagWithAttrValueNull.render(), is("<a href=\"/\"></a>"));
106108
}
107109

110+
@Test
111+
public void testParameterNameReflectionAttributes() throws Exception {
112+
String expectedAnchor = "<a href=\"http://example.com\">example.com</a>";
113+
String actualAnchor = a("example.com").withAttrs(href -> "http://example.com").render();
114+
assertThat(actualAnchor, is(expectedAnchor));
115+
String expectedForm = "<form method=\"post\" action=\"/form-path\"><input name=\"email\" type=\"email\"><input name=\"password\" type=\"password\"></form>";
116+
String actualForm = form().withAttrs(method -> "post", action -> "/form-path").with(
117+
input().withAttrs(name -> "email", type -> "email"),
118+
input().withAttrs(name -> "password", type -> "password")
119+
).render();
120+
assertThat(actualForm, is(expectedForm));
121+
}
122+
108123
}

0 commit comments

Comments
 (0)