Skip to content

Commit

Permalink
Add parameter name reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
tipsy committed Jan 8, 2018
1 parent b42ba41 commit 1789ba0
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 10 deletions.
3 changes: 3 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
<source>1.8</source>
<target>1.8</target>
<optimize>true</optimize>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/j2html/attributes/LambdaAttribute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package j2html.attributes;

// Written by Benjamin Weber (http://benjiweber.co.uk/blog/author/benji/)

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 also need java 8u60 or newer for parameter reflection to work");
}
}

}
48 changes: 48 additions & 0 deletions src/main/java/j2html/reflection/MethodFinder.java
Original file line number Diff line number Diff line change
@@ -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 {
}
}
21 changes: 15 additions & 6 deletions src/main/java/j2html/tags/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/j2html/reflection/NamedValueTest.java
Original file line number Diff line number Diff line change
@@ -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()));
}

}
23 changes: 19 additions & 4 deletions src/test/java/j2html/tags/TagTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -98,11 +100,24 @@ public void testDynamicAttribute() throws Exception {
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));
}

}

0 comments on commit 1789ba0

Please sign in to comment.