1 Commits

Author SHA1 Message Date
David
92aa3310db Add parameter name reflection 2018-01-10 16:06:52 +01:00
6 changed files with 126 additions and 10 deletions

View File

@@ -93,6 +93,9 @@
<source>1.8</source>
<target>1.8</target>
<optimize>true</optimize>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>

View File

@@ -0,0 +1,24 @@
package j2html.attributes;
import j2html.reflection.MethodFinder;
import java.util.function.Function;
public interface LambdaAttribute extends MethodFinder, Function<String, Object> {
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");
}
}
}

View File

@@ -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 {
}
}

View File

@@ -2,6 +2,7 @@ package j2html.tags;
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 abstract class Tag<T extends Tag<T>> extends DomContent {
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 abstract class Tag<T extends Tag<T>> extends DomContent {
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 abstract class Tag<T extends Tag<T>> extends DomContent {
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);
}

View File

@@ -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()));
}
}

View File

@@ -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.p;
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 class TagTest {
ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr(new DynamicHrefAttribute());
assertThat(testTagWithAttrValueNull.render(), is("<a href=\"/\"></a>"));
}
@Test
public void testDynamicAttributeReplacement() throws Exception {
ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr("href", "/link").attr(new DynamicHrefAttribute());
assertThat(testTagWithAttrValueNull.render(), is("<a href=\"/\"></a>"));
}
@Test
public void testParameterNameReflectionAttributes() throws Exception {
String expectedAnchor = "<a href=\"http://example.com\">example.com</a>";
String actualAnchor = a("example.com").withAttrs(href -> "http://example.com").render();
assertThat(actualAnchor, is(expectedAnchor));
String expectedForm = "<form method=\"post\" action=\"/form-path\"><input name=\"email\" type=\"email\"><input name=\"password\" type=\"password\"></form>";
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));
}
}