diff --git a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java index 47d60a6d2..c37574897 100644 --- a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java +++ b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java @@ -18,6 +18,7 @@ import static com.hubspot.jinjava.lib.fn.Functions.DEFAULT_RANGE_LIMIT; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.hubspot.jinjava.el.JinjavaInterpreterResolver; import com.hubspot.jinjava.el.JinjavaNodePreProcessor; import com.hubspot.jinjava.el.JinjavaObjectUnwrapper; @@ -44,6 +45,7 @@ import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; +import javax.annotation.Nullable; import javax.el.ELResolver; public class JinjavaConfig { @@ -135,11 +137,21 @@ private JinjavaConfig(Builder builder) { legacyOverrides = builder.legacyOverrides; dateTimeProvider = builder.dateTimeProvider; enablePreciseDivideFilter = builder.enablePreciseDivideFilter; - objectMapper = builder.objectMapper; + objectMapper = setupObjectMapper(builder.objectMapper); objectUnwrapper = builder.objectUnwrapper; nodePreProcessor = builder.nodePreProcessor; } + private ObjectMapper setupObjectMapper(@Nullable ObjectMapper objectMapper) { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + if (legacyOverrides.isUseSnakeCasePropertyNaming()) { + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + } + } + return objectMapper; + } + public Charset getCharset() { return charset; } @@ -298,7 +310,7 @@ public static class Builder { private ExecutionMode executionMode = DefaultExecutionMode.instance(); private LegacyOverrides legacyOverrides = LegacyOverrides.NONE; private boolean enablePreciseDivideFilter = false; - private ObjectMapper objectMapper = new ObjectMapper(); + private ObjectMapper objectMapper = null; private ObjectUnwrapper objectUnwrapper = new JinjavaObjectUnwrapper(); private BiConsumer nodePreProcessor = new JinjavaNodePreProcessor(); diff --git a/src/main/java/com/hubspot/jinjava/LegacyOverrides.java b/src/main/java/com/hubspot/jinjava/LegacyOverrides.java index 9fb46b03d..5ff01314f 100644 --- a/src/main/java/com/hubspot/jinjava/LegacyOverrides.java +++ b/src/main/java/com/hubspot/jinjava/LegacyOverrides.java @@ -9,6 +9,7 @@ public class LegacyOverrides { private final boolean evaluateMapKeys; private final boolean iterateOverMapKeys; private final boolean usePyishObjectMapper; + private final boolean useSnakeCasePropertyNaming; private final boolean whitespaceRequiredWithinTokens; private final boolean useNaturalOperatorPrecedence; private final boolean parseWhitespaceControlStrictly; @@ -17,6 +18,7 @@ private LegacyOverrides(Builder builder) { evaluateMapKeys = builder.evaluateMapKeys; iterateOverMapKeys = builder.iterateOverMapKeys; usePyishObjectMapper = builder.usePyishObjectMapper; + useSnakeCasePropertyNaming = builder.useSnakeCasePropertyNaming; whitespaceRequiredWithinTokens = builder.whitespaceRequiredWithinTokens; useNaturalOperatorPrecedence = builder.useNaturalOperatorPrecedence; parseWhitespaceControlStrictly = builder.parseWhitespaceControlStrictly; @@ -38,6 +40,10 @@ public boolean isUsePyishObjectMapper() { return usePyishObjectMapper; } + public boolean isUseSnakeCasePropertyNaming() { + return useSnakeCasePropertyNaming; + } + public boolean isWhitespaceRequiredWithinTokens() { return whitespaceRequiredWithinTokens; } @@ -54,6 +60,7 @@ public static class Builder { private boolean evaluateMapKeys = false; private boolean iterateOverMapKeys = false; private boolean usePyishObjectMapper = false; + private boolean useSnakeCasePropertyNaming = false; private boolean whitespaceRequiredWithinTokens = false; private boolean useNaturalOperatorPrecedence = false; private boolean parseWhitespaceControlStrictly = false; @@ -69,6 +76,7 @@ public static Builder from(LegacyOverrides legacyOverrides) { .withEvaluateMapKeys(legacyOverrides.evaluateMapKeys) .withIterateOverMapKeys(legacyOverrides.iterateOverMapKeys) .withUsePyishObjectMapper(legacyOverrides.usePyishObjectMapper) + .withUseSnakeCasePropertyNaming(legacyOverrides.useSnakeCasePropertyNaming) .withWhitespaceRequiredWithinTokens( legacyOverrides.whitespaceRequiredWithinTokens ) @@ -93,6 +101,11 @@ public Builder withUsePyishObjectMapper(boolean usePyishObjectMapper) { return this; } + public Builder withUseSnakeCasePropertyNaming(boolean useSnakeCasePropertyNaming) { + this.useSnakeCasePropertyNaming = useSnakeCasePropertyNaming; + return this; + } + public Builder withWhitespaceRequiredWithinTokens( boolean whitespaceRequiredWithinTokens ) { diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/AllowSnakeCaseFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/AllowSnakeCaseFilter.java new file mode 100644 index 000000000..3b9868968 --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/lib/filter/AllowSnakeCaseFilter.java @@ -0,0 +1,43 @@ +package com.hubspot.jinjava.lib.filter; + +import com.hubspot.jinjava.doc.annotations.JinjavaDoc; +import com.hubspot.jinjava.doc.annotations.JinjavaParam; +import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; +import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.objects.collections.PyMap; +import com.hubspot.jinjava.objects.collections.SizeLimitingPyMap; +import com.hubspot.jinjava.objects.collections.SnakeCaseAccessibleMap; +import java.util.Map; + +@JinjavaDoc( + value = "Allow keys on the provided camelCase map to be accessed using snake_case", + input = @JinjavaParam( + value = "map", + type = "dict", + desc = "The dict to make keys accessible using snake_case", + required = true + ), + snippets = { @JinjavaSnippet(code = "{{ {'fooBar': 'baz'}|allow_snake_case }}") } +) +public class AllowSnakeCaseFilter implements Filter { + public static final String NAME = "allow_snake_case"; + + @Override + public String getName() { + return NAME; + } + + @Override + public Object filter(Object var, JinjavaInterpreter interpreter, String... args) { + if (!(var instanceof Map)) { + return var; + } + Map map = (Map) var; + if (map instanceof PyMap) { + map = ((PyMap) map).toMap(); + } + return new SnakeCaseAccessibleMap( + new SizeLimitingPyMap(map, interpreter.getConfig().getMaxMapSize()) + ); + } +} diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java index 216a1d2bd..f5dfc30e3 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java @@ -32,6 +32,7 @@ protected void registerDefaults() { registerClasses( AbsFilter.class, AddFilter.class, + AllowSnakeCaseFilter.class, AttrFilter.class, Base64DecodeFilter.class, Base64EncodeFilter.class, diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/FromJsonFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/FromJsonFilter.java index fbea6fa8d..bacee747d 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/FromJsonFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/FromJsonFilter.java @@ -1,6 +1,5 @@ package com.hubspot.jinjava.lib.filter; -import com.fasterxml.jackson.databind.ObjectMapper; import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; @@ -19,7 +18,6 @@ snippets = { @JinjavaSnippet(code = "{{object|fromJson}}") } ) public class FromJsonFilter implements Filter { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Override public Object filter(Object var, JinjavaInterpreter interpreter, String... args) { @@ -31,7 +29,10 @@ public Object filter(Object var, JinjavaInterpreter interpreter, String... args) throw new InvalidInputException(interpreter, this, InvalidReason.STRING); } try { - return OBJECT_MAPPER.readValue((String) var, Object.class); + return interpreter + .getConfig() + .getObjectMapper() + .readValue((String) var, Object.class); } catch (IOException e) { throw new InvalidInputException(interpreter, this, InvalidReason.JSON_READ); } diff --git a/src/main/java/com/hubspot/jinjava/objects/collections/SnakeCaseAccessibleMap.java b/src/main/java/com/hubspot/jinjava/objects/collections/SnakeCaseAccessibleMap.java new file mode 100644 index 000000000..1c4b68c90 --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/objects/collections/SnakeCaseAccessibleMap.java @@ -0,0 +1,43 @@ +package com.hubspot.jinjava.objects.collections; + +import com.google.common.base.CaseFormat; +import com.hubspot.jinjava.lib.filter.AllowSnakeCaseFilter; +import com.hubspot.jinjava.objects.serialization.PyishSerializable; +import java.io.IOException; +import java.util.Map; + +public class SnakeCaseAccessibleMap extends PyMap implements PyishSerializable { + + public SnakeCaseAccessibleMap(Map map) { + super(map); + } + + @Override + public Object get(Object key) { + Object result = super.get(key); + if (result == null && key instanceof String) { + return getWithCamelCase((String) key); + } + return result; + } + + private Object getWithCamelCase(String key) { + if (key == null) { + return null; + } + if (key.indexOf('_') == -1) { + return null; + } + return super.get(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, key)); + } + + @SuppressWarnings("unchecked") + @Override + public T appendPyishString(T appendable) + throws IOException { + return (T) appendable + .append(PyishSerializable.writeValueAsString(toMap())) + .append('|') + .append(AllowSnakeCaseFilter.NAME); + } +} diff --git a/src/main/java/com/hubspot/jinjava/objects/serialization/BothCasingBeanSerializer.java b/src/main/java/com/hubspot/jinjava/objects/serialization/BothCasingBeanSerializer.java new file mode 100644 index 000000000..f966065be --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/objects/serialization/BothCasingBeanSerializer.java @@ -0,0 +1,46 @@ +package com.hubspot.jinjava.objects.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.hubspot.jinjava.lib.filter.AllowSnakeCaseFilter; +import java.io.IOException; + +public class BothCasingBeanSerializer extends JsonSerializer { + private final JsonSerializer orignalSerializer; + + private BothCasingBeanSerializer(JsonSerializer jsonSerializer) { + this.orignalSerializer = jsonSerializer; + } + + public static BothCasingBeanSerializer wrapping( + JsonSerializer jsonSerializer + ) { + return new BothCasingBeanSerializer<>(jsonSerializer); + } + + @Override + public void serialize( + T value, + JsonGenerator gen, + SerializerProvider serializerProvider + ) + throws IOException { + if ( + Boolean.TRUE.equals( + serializerProvider.getAttribute(PyishObjectMapper.ALLOW_SNAKE_CASE_ATTRIBUTE) + ) + ) { + // if it's directly for output, then we don't want to add the additional filter characters, + // as doing so would make the "|allow_snake_case" appear in the final output. + StringBuilder sb = new StringBuilder(); + sb + .append(PyishSerializable.writeValueAsString(value)) + .append('|') + .append(AllowSnakeCaseFilter.NAME); + gen.writeRawValue(sb.toString()); + } else { + orignalSerializer.serialize(value, gen, serializerProvider); + } + } +} diff --git a/src/main/java/com/hubspot/jinjava/objects/serialization/MapEntrySerializer.java b/src/main/java/com/hubspot/jinjava/objects/serialization/MapEntrySerializer.java index dd354bb46..e67cfd32a 100644 --- a/src/main/java/com/hubspot/jinjava/objects/serialization/MapEntrySerializer.java +++ b/src/main/java/com/hubspot/jinjava/objects/serialization/MapEntrySerializer.java @@ -26,11 +26,16 @@ public void serialize( ); String key; String value; + ObjectWriter objectWriter = PyishObjectMapper.PYISH_OBJECT_WRITER.withAttribute( + PyishObjectMapper.ALLOW_SNAKE_CASE_ATTRIBUTE, + serializerProvider.getAttribute(PyishObjectMapper.ALLOW_SNAKE_CASE_ATTRIBUTE) + ); if (remainingLength != null) { - ObjectWriter objectWriter = PyishObjectMapper.PYISH_OBJECT_WRITER.withAttribute( - LengthLimitingWriter.REMAINING_LENGTH_ATTRIBUTE, - remainingLength - ); + objectWriter = + objectWriter.withAttribute( + LengthLimitingWriter.REMAINING_LENGTH_ATTRIBUTE, + remainingLength + ); key = objectWriter.writeValueAsString(entry.getKey()); LengthLimitingWriter lengthLimitingWriter = new LengthLimitingWriter( new CharArrayWriter(), @@ -39,8 +44,8 @@ public void serialize( objectWriter.writeValue(lengthLimitingWriter, entry.getValue()); value = lengthLimitingWriter.toString(); } else { - key = PyishObjectMapper.PYISH_OBJECT_WRITER.writeValueAsString(entry.getKey()); - value = PyishObjectMapper.PYISH_OBJECT_WRITER.writeValueAsString(entry.getValue()); + key = objectWriter.writeValueAsString(entry.getKey()); + value = objectWriter.writeValueAsString(entry.getValue()); } jsonGenerator.writeRawValue(String.format("fn:map_entry(%s, %s)", key, value)); } diff --git a/src/main/java/com/hubspot/jinjava/objects/serialization/PyishBeanSerializerModifier.java b/src/main/java/com/hubspot/jinjava/objects/serialization/PyishBeanSerializerModifier.java index a55f0bf36..a3d84cdb1 100644 --- a/src/main/java/com/hubspot/jinjava/objects/serialization/PyishBeanSerializerModifier.java +++ b/src/main/java/com/hubspot/jinjava/objects/serialization/PyishBeanSerializerModifier.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.ser.BeanSerializer; import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import java.util.Map; @@ -23,6 +24,9 @@ public JsonSerializer modifySerializer( if (Map.Entry.class.isAssignableFrom(beanDesc.getBeanClass())) { return MapEntrySerializer.INSTANCE; } + if (serializer instanceof BeanSerializer) { + return BothCasingBeanSerializer.wrapping(serializer); + } return serializer; } else { return PyishSerializer.INSTANCE; diff --git a/src/main/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapper.java b/src/main/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapper.java index 14da54606..c7628aaf6 100644 --- a/src/main/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapper.java +++ b/src/main/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapper.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; import com.hubspot.jinjava.interpret.JinjavaInterpreter; @@ -19,8 +20,23 @@ public class PyishObjectMapper { public static final ObjectWriter PYISH_OBJECT_WRITER; + public static final ObjectWriter SNAKE_CASE_PYISH_OBJECT_WRITER; + public static final String ALLOW_SNAKE_CASE_ATTRIBUTE = "allowSnakeCase"; static { + PYISH_OBJECT_WRITER = + getPyishObjectMapper() + .writer(PyishPrettyPrinter.INSTANCE) + .with(PyishCharacterEscapes.INSTANCE); + + SNAKE_CASE_PYISH_OBJECT_WRITER = + getPyishObjectMapper() + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE) + .writer(PyishPrettyPrinter.INSTANCE) + .with(PyishCharacterEscapes.INSTANCE); + } + + private static ObjectMapper getPyishObjectMapper() { ObjectMapper mapper = new ObjectMapper( new JsonFactoryBuilder().quoteChar('\'').build() ) @@ -30,20 +46,23 @@ public class PyishObjectMapper { .addSerializer(PyishSerializable.class, PyishSerializer.INSTANCE) ); mapper.getSerializerProvider().setNullKeySerializer(new NullKeySerializer()); - PYISH_OBJECT_WRITER = - mapper.writer(PyishPrettyPrinter.INSTANCE).with(PyishCharacterEscapes.INSTANCE); + return mapper; } public static String getAsUnquotedPyishString(Object val) { if (val != null) { - return WhitespaceUtils.unquoteAndUnescape(getAsPyishString(val)); + return WhitespaceUtils.unquoteAndUnescape(getAsPyishString(val, true)); } return ""; } public static String getAsPyishString(Object val) { + return getAsPyishString(val, false); + } + + private static String getAsPyishString(Object val, boolean forOutput) { try { - return getAsPyishStringOrThrow(val); + return getAsPyishStringOrThrow(val, forOutput); } catch (IOException e) { if (e instanceof LengthLimitingJsonProcessingException) { throw new OutputTooBigException( @@ -56,7 +75,21 @@ public static String getAsPyishString(Object val) { } public static String getAsPyishStringOrThrow(Object val) throws IOException { - ObjectWriter objectWriter = PYISH_OBJECT_WRITER; + return getAsPyishStringOrThrow(val, false); + } + + public static String getAsPyishStringOrThrow(Object val, boolean forOutput) + throws IOException { + boolean useSnakeCaseMappingOverride = JinjavaInterpreter + .getCurrentMaybe() + .map( + interpreter -> + interpreter.getConfig().getLegacyOverrides().isUseSnakeCasePropertyNaming() + ) + .orElse(false); + ObjectWriter objectWriter = useSnakeCaseMappingOverride + ? SNAKE_CASE_PYISH_OBJECT_WRITER + : PYISH_OBJECT_WRITER; Writer writer; Optional maxOutputSize = JinjavaInterpreter .getCurrentMaybe() @@ -75,7 +108,11 @@ public static String getAsPyishStringOrThrow(Object val) throws IOException { } else { writer = new CharArrayWriter(); } + if (!useSnakeCaseMappingOverride) { + objectWriter = objectWriter.withAttribute(ALLOW_SNAKE_CASE_ATTRIBUTE, !forOutput); + } objectWriter.writeValue(writer, val); + return writer.toString(); } diff --git a/src/test/java/com/hubspot/jinjava/lib/filter/AllowSnakeCaseFilterTest.java b/src/test/java/com/hubspot/jinjava/lib/filter/AllowSnakeCaseFilterTest.java new file mode 100644 index 000000000..5db6305b5 --- /dev/null +++ b/src/test/java/com/hubspot/jinjava/lib/filter/AllowSnakeCaseFilterTest.java @@ -0,0 +1,28 @@ +package com.hubspot.jinjava.lib.filter; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hubspot.jinjava.BaseInterpretingTest; +import com.hubspot.jinjava.objects.serialization.PyishObjectMapper; +import org.junit.Test; + +public class AllowSnakeCaseFilterTest extends BaseInterpretingTest { + + @Test + public void itDoesNotChangeNonMaps() { + assertThat(interpreter.render("{{ 'fooBar'|allow_snake_case }}")).isEqualTo("fooBar"); + } + + @Test + public void itMakesMapKeysAccessibleWithSnakeCase() { + assertThat(interpreter.render("{{ ({'fooBar': 'foo'}|allow_snake_case).foo_bar }}")) + .isEqualTo("foo"); + } + + @Test + public void itReserializesAsSnakeCaseAccessibleMap() { + interpreter.render("{% set map = {'fooBar': 'foo'}|allow_snake_case %}"); + assertThat(PyishObjectMapper.getAsPyishString(interpreter.getContext().get("map"))) + .isEqualTo("{'fooBar': 'foo'} |allow_snake_case"); + } +} diff --git a/src/test/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapperTest.java b/src/test/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapperTest.java index cadcb8be9..39b03e3c5 100644 --- a/src/test/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapperTest.java +++ b/src/test/java/com/hubspot/jinjava/objects/serialization/PyishObjectMapperTest.java @@ -7,8 +7,10 @@ import com.google.common.collect.ImmutableMap; import com.hubspot.jinjava.Jinjava; import com.hubspot.jinjava.JinjavaConfig; +import com.hubspot.jinjava.LegacyOverrides; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.objects.collections.SizeLimitingPyMap; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -88,4 +90,71 @@ public void itLimitsDepth() { JinjavaInterpreter.popCurrent(); } } + + @Test + public void itSerializesToSnakeCaseAccessibleMap() { + assertThat(PyishObjectMapper.getAsPyishString(new Foo("bar"))) + .isEqualTo("{'fooBar': 'bar'} |allow_snake_case"); + } + + @Test + public void itSerializesToSnakeCaseAccessibleMapWhenInMapEntry() { + assertThat( + PyishObjectMapper.getAsPyishString( + new AbstractMap.SimpleImmutableEntry<>("foo", new Foo("bar")) + ) + ) + .isEqualTo("fn:map_entry('foo', {'fooBar': 'bar'} |allow_snake_case)"); + } + + @Test + public void itDoesNotConvertToSnakeCaseMapWhenResultIsForOutput() { + Jinjava jinjava = new Jinjava( + JinjavaConfig + .newBuilder() + .withLegacyOverrides( + LegacyOverrides.newBuilder().withUsePyishObjectMapper(true).build() + ) + .build() + ); + JinjavaInterpreter interpreter = jinjava.newInterpreter(); + interpreter.getContext().put("foo", new Foo("bar")); + assertThat(interpreter.render("{{ foo }}")).isEqualTo("{'fooBar': 'bar'}"); + } + + @Test + public void itSerializesToSnakeCaseWhenLegacyOverrideIsSet() { + Jinjava jinjava = new Jinjava( + JinjavaConfig + .newBuilder() + .withLegacyOverrides( + LegacyOverrides + .newBuilder() + .withUsePyishObjectMapper(true) + .withUseSnakeCasePropertyNaming(true) + .build() + ) + .build() + ); + JinjavaInterpreter interpreter = jinjava.newInterpreter(); + try { + JinjavaInterpreter.pushCurrent(interpreter); + interpreter.getContext().put("foo", new Foo("bar")); + assertThat(interpreter.render("{{ foo }}")).isEqualTo("{'foo_bar': 'bar'}"); + } finally { + JinjavaInterpreter.popCurrent(); + } + } + + static class Foo { + private final String bar; + + public Foo(String bar) { + this.bar = bar; + } + + public String getFooBar() { + return bar; + } + } }