diff --git a/pom.xml b/pom.xml index 3b2fc5ab..1bb81ee0 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,9 @@ 1.8 1.8 true + + -parameters + diff --git a/src/main/java/j2html/attributes/LambdaAttribute.java b/src/main/java/j2html/attributes/LambdaAttribute.java new file mode 100644 index 00000000..54aecef4 --- /dev/null +++ b/src/main/java/j2html/attributes/LambdaAttribute.java @@ -0,0 +1,24 @@ +package j2html.attributes; + +import j2html.reflection.MethodFinder; +import java.util.function.Function; + +public interface LambdaAttribute extends MethodFinder, Function { + + default String name() { + checkParametersEnabled(); + return parameter(0).getName(); + } + + default String value() { + checkParametersEnabled(); + return String.valueOf(this.apply(name())); + } + + default void checkParametersEnabled() { + if ("arg0".equals(parameter(0).getName())) { + throw new IllegalStateException("You need java 8u60 or newer for parameter reflection to work"); + } + } + +} diff --git a/src/main/java/j2html/reflection/MethodFinder.java b/src/main/java/j2html/reflection/MethodFinder.java new file mode 100644 index 00000000..493bcb65 --- /dev/null +++ b/src/main/java/j2html/reflection/MethodFinder.java @@ -0,0 +1,48 @@ +package j2html.reflection; + +// Written by Benjamin Weber (http://benjiweber.co.uk/blog/author/benji/) + +import java.io.Serializable; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Objects; + +public interface MethodFinder extends Serializable { + + default SerializedLambda serialized() { + try { + Method replaceMethod = getClass().getDeclaredMethod("writeReplace"); + replaceMethod.setAccessible(true); + return (SerializedLambda) replaceMethod.invoke(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + default Class getContainingClass() { + try { + String className = serialized().getImplClass().replaceAll("/", "."); + return Class.forName(className); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + default Method method() { + SerializedLambda lambda = serialized(); + Class containingClass = getContainingClass(); + return Arrays.stream(containingClass.getDeclaredMethods()) + .filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName())) + .findFirst() + .orElseThrow(UnableToGuessMethodException::new); + } + + default Parameter parameter(int n) { + return method().getParameters()[n]; + } + + class UnableToGuessMethodException extends RuntimeException { + } +} diff --git a/src/main/java/j2html/tags/Tag.java b/src/main/java/j2html/tags/Tag.java index ab1ff291..6cad4c0a 100644 --- a/src/main/java/j2html/tags/Tag.java +++ b/src/main/java/j2html/tags/Tag.java @@ -2,6 +2,7 @@ import j2html.attributes.Attr; import j2html.attributes.Attribute; +import j2html.attributes.LambdaAttribute; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; @@ -79,7 +80,7 @@ public T attr(String attribute, Object value) { setAttribute(attribute, value == null ? null : String.valueOf(value)); return (T) this; } - + /** * Adds the specified attribute. If the Tag previously contained an attribute with the same name, the old attribute is replaced by the specified attribute. * @@ -134,11 +135,6 @@ public boolean equals(Object obj) { return ((Tag) obj).render().equals(this.render()); } - /** - * Convenience methods that call attr with predefined attributes - * - * @return itself for easy chaining - */ public T withClasses(String... classes) { StringBuilder sb = new StringBuilder(); for (String s : classes) { @@ -147,6 +143,19 @@ public T withClasses(String... classes) { return attr(Attr.CLASS, sb.toString().trim()); } + public T withAttrs(LambdaAttribute... lambdaAttributes) { + for (LambdaAttribute attr : lambdaAttributes) { + attr(attr.name(), attr.value()); + } + return (T) this; + } + + /** + * Convenience methods that call attr with predefined attributes + * + * @return itself for easy chaining + */ + public T isAutoComplete() { return attr(Attr.AUTOCOMPLETE, null); } diff --git a/src/test/java/j2html/reflection/NamedValueTest.java b/src/test/java/j2html/reflection/NamedValueTest.java new file mode 100644 index 00000000..dcfbc0b0 --- /dev/null +++ b/src/test/java/j2html/reflection/NamedValueTest.java @@ -0,0 +1,17 @@ +package j2html.reflection; + +import j2html.attributes.LambdaAttribute; +import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class NamedValueTest { + + @Test + public void testNamedValueWorks() { + LambdaAttribute pair = five -> 5; + assertThat("five", is(pair.name())); + assertThat("5", is(pair.value())); + } + +} diff --git a/src/test/java/j2html/tags/TagTest.java b/src/test/java/j2html/tags/TagTest.java index e82b734a..8c821c15 100644 --- a/src/test/java/j2html/tags/TagTest.java +++ b/src/test/java/j2html/tags/TagTest.java @@ -1,8 +1,13 @@ package j2html.tags; +import j2html.Config; +import j2html.model.DynamicHrefAttribute; +import org.junit.Test; +import static j2html.TagCreator.a; import static j2html.TagCreator.body; import static j2html.TagCreator.div; import static j2html.TagCreator.footer; +import static j2html.TagCreator.form; import static j2html.TagCreator.header; import static j2html.TagCreator.html; import static j2html.TagCreator.iff; @@ -13,9 +18,6 @@ import static j2html.TagCreator.tag; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import j2html.Config; -import j2html.model.DynamicHrefAttribute; -import org.junit.Test; public class TagTest { @@ -98,11 +100,24 @@ public void testDynamicAttribute() throws Exception { ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr(new DynamicHrefAttribute()); assertThat(testTagWithAttrValueNull.render(), is("")); } - + @Test public void testDynamicAttributeReplacement() throws Exception { ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr("href", "/link").attr(new DynamicHrefAttribute()); assertThat(testTagWithAttrValueNull.render(), is("")); } + @Test + public void testParameterNameReflectionAttributes() throws Exception { + String expectedAnchor = "example.com"; + String actualAnchor = a("example.com").withAttrs(href -> "http://example.com").render(); + assertThat(actualAnchor, is(expectedAnchor)); + String expectedForm = "
"; + String actualForm = form().withAttrs(method -> "post", action -> "/form-path").with( + input().withAttrs(name -> "email", type -> "email"), + input().withAttrs(name -> "password", type -> "password") + ).render(); + assertThat(actualForm, is(expectedForm)); + } + }