Skip to content

Commit

Permalink
Added pre processing feature for properties. Resolves #120
Browse files Browse the repository at this point in the history
  • Loading branch information
Luigi R. Viggiano committed Apr 16, 2015
1 parent 79124c2 commit 4d39966
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 19 deletions.
7 changes: 7 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
TODO LIST
=========

Web site:
- [ ] Write documentation for pre processing feature
- [ ] Write documentation for JMX support
- [ ] Update the release note.
13 changes: 13 additions & 0 deletions owner/src/main/java/org/aeonbits/owner/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,17 @@ enum DisableableFeature {
Class<? extends Converter> value();
}

/**
* Specifies a <tt>{@link Preprocessor}</tt> class to allow the user to define a custom logic to pre-process
* the property value before being used by the library.
*
* @since 1.0.9
*/
@Retention(RUNTIME)
@Target({METHOD, TYPE})
@Documented
@interface PreprocessorClasses {
Class<? extends Preprocessor>[] value();
}

}
20 changes: 20 additions & 0 deletions owner/src/main/java/org/aeonbits/owner/Preprocessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2012-2015, Luigi R. Viggiano
* All rights reserved.
*
* This software is distributable under the BSD license.
* See the terms of the BSD license in the documentation provided with this software.
*/

package org.aeonbits.owner;

/**
* Preprocessor interface specifies how to pre-process an input string coming from a property value before being used by
* OWNER.
*
* @author Luigi R. Viggiano
* @since 1.0.9
*/
public interface Preprocessor {
String process(String input);
}
48 changes: 48 additions & 0 deletions owner/src/main/java/org/aeonbits/owner/PreprocessorResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2012-2015, Luigi R. Viggiano
* All rights reserved.
*
* This software is distributable under the BSD license.
* See the terms of the BSD license in the documentation provided with this software.
*/

package org.aeonbits.owner;

import org.aeonbits.owner.Config.PreprocessorClasses;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import static java.util.Collections.emptyList;
import static org.aeonbits.owner.Util.newInstance;

/**
* @author Luigi R. Viggiano
*/
final class PreprocessorResolver {

/** Don't let anyone instantiate this class */
private PreprocessorResolver() {}

public static List<Preprocessor> resolvePreprocessors(Method method) {
List<Preprocessor> result = new ArrayList<Preprocessor>();
List<Preprocessor> preprocessorsOnMethod = getPreprocessor(method.getAnnotation(PreprocessorClasses.class));
result.addAll(preprocessorsOnMethod);

List<Preprocessor> preprocessorsOnClass = getPreprocessor(method.getDeclaringClass().getAnnotation(PreprocessorClasses.class));
result.addAll(preprocessorsOnClass);

return result;
}

private static List<Preprocessor> getPreprocessor(PreprocessorClasses preprocessorClassesAnnotation) {
if (preprocessorClassesAnnotation == null) return emptyList();
Class<? extends Preprocessor>[] preprocessorClasses = preprocessorClassesAnnotation.value();
if (preprocessorClasses == null) return emptyList();
List<Preprocessor> result = new LinkedList<Preprocessor>();
return newInstance(preprocessorClassesAnnotation.value(), result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.aeonbits.owner.Config.DisableableFeature.PARAMETER_FORMATTING;
import static org.aeonbits.owner.Config.DisableableFeature.VARIABLE_EXPANSION;
import static org.aeonbits.owner.Converters.convert;
import static org.aeonbits.owner.PreprocessorResolver.resolvePreprocessors;
import static org.aeonbits.owner.PropertiesMapper.key;
import static org.aeonbits.owner.Util.isFeatureDisabled;
import static org.aeonbits.owner.util.Reflection.invokeDefaultMethod;
Expand Down Expand Up @@ -79,11 +80,20 @@ private Object resolveProperty(Method method, Object... args) {
}
if (value == null)
return null;
value = preProcess(method, value);
Object result = convert(method, method.getReturnType(), format(method, expandVariables(method, value), args));
if (result == Converters.NULL) return null;
return result;
}

private String preProcess(Method method, String value) {
List<Preprocessor> preprocessors = resolvePreprocessors(method);
String result = value;
for (Preprocessor preprocessor : preprocessors)
result = preprocessor.process(result);
return result;
}

private String expandKey(Method method) {
String key = key(method);
if (isFeatureDisabled(method, VARIABLE_EXPANSION))
Expand Down
15 changes: 3 additions & 12 deletions owner/src/main/java/org/aeonbits/owner/TokenizerResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import java.lang.reflect.Method;

import static org.aeonbits.owner.Util.newInstance;
import static org.aeonbits.owner.Util.unsupported;

/**
Expand Down Expand Up @@ -50,7 +51,7 @@ private static Tokenizer resolveTokenizerOnClassLevel(Class<?> declaringClass) {
return new SplitAndTrimTokenizer(separatorAnnotationOnClassLevel.value());

if (tokenizerClassAnnotationOnClassLevel != null)
return createTokenizer(tokenizerClassAnnotationOnClassLevel.value());
return newInstance(tokenizerClassAnnotationOnClassLevel.value());

return null;
}
Expand All @@ -68,19 +69,9 @@ private static Tokenizer resolveTokenizerOnMethodLevel(Method targetMethod) {
return new SplitAndTrimTokenizer(separatorAnnotationOnMethodLevel.value());

if (tokenizerClassAnnotationOnMethodLevel != null)
return createTokenizer(tokenizerClassAnnotationOnMethodLevel.value());
return newInstance(tokenizerClassAnnotationOnMethodLevel.value());

return null;
}

private static Tokenizer createTokenizer(Class<? extends Tokenizer> tokenizerClass) {
try {
return tokenizerClass.newInstance();
} catch (Exception e) {
throw unsupported(e,
"Tokenizer class '%s' cannot be instantiated; see the cause below in the stack trace",
tokenizerClass.getCanonicalName());
}
}

}
15 changes: 15 additions & 0 deletions owner/src/main/java/org/aeonbits/owner/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,19 @@ private static byte[] toBytes(Properties props) throws IOException {
}
}

public static <T> T newInstance(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
throw unsupported(e,
"Class '%s' cannot be instantiated; see the cause below in the stack trace",
clazz.getCanonicalName());
}
}

public static <T> List<T> newInstance(Class<? extends T>[] classes, List<T> result) {
for (Class<? extends T> clazz : classes)
result.add(newInstance(clazz));
return result;
}
}
2 changes: 1 addition & 1 deletion owner/src/test/java/org/aeonbits/owner/ConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class ConfigTest {
@Mock
private ScheduledExecutorService scheduler;

public static interface SampleConfig extends Config {
public interface SampleConfig extends Config {
String hello(String param);

@DefaultValue("Bohemian Rapsody - Queen")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
public class ConfigWithSubstitutionTest {

public static interface ConfigWithSubstitutionFile extends Config {
public interface ConfigWithSubstitutionFile extends Config {
String story();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @author Luigi R. Viggiano
*/
public class DisableFeatureTest {
public static interface ConfigWithSubstitutionDisabledOnMethod extends Config {
public interface ConfigWithSubstitutionDisabledOnMethod extends Config {
@DefaultValue("Earth")
String world();

Expand Down
96 changes: 96 additions & 0 deletions owner/src/test/java/org/aeonbits/owner/PreprocessorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2012-2015, Luigi R. Viggiano
* All rights reserved.
*
* This software is distributable under the BSD license.
* See the terms of the BSD license in the documentation provided with this software.
*/

package org.aeonbits.owner;

import org.aeonbits.owner.Config.PreprocessorClasses;
import org.junit.Test;

import static java.lang.Math.max;
import static java.lang.Math.min;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

/**
* Issue #120
*
* @author Luigi R. Viggiano
*/
public class PreprocessorTest {

@PreprocessorClasses({ SkipInlineComments.class, Trim.class })
public interface ConfigWithPreprocessors extends Config {
@DefaultValue(" 300 ")
Integer pollingPeriod();

@PreprocessorClasses(ToLowerCase.class)
@DefaultValue(" HelloWorld ")
String helloWorld();

@DefaultValue(" This is the value ! this is an inline comment # this is the inline comment too")
String skipsInlineComments();

@PreprocessorClasses(ToLowerCase.class)
String nullValue();
}

@Test
public void shouldReturnTrimmedValue() {
ConfigWithPreprocessors cfg = ConfigFactory.create(ConfigWithPreprocessors.class);
assertEquals(new Integer(300), cfg.pollingPeriod());
}

@Test
public void shouldReturnLowercasedTrimmedHelloWorld() {
ConfigWithPreprocessors cfg = ConfigFactory.create(ConfigWithPreprocessors.class);
assertEquals("helloworld", cfg.helloWorld());
}

@Test
public void shouldSkipInlineComments() {
ConfigWithPreprocessors cfg = ConfigFactory.create(ConfigWithPreprocessors.class);
assertEquals("This is the value", cfg.skipsInlineComments());
}

@Test
public void shouldNotThrowExceptions() {
ConfigWithPreprocessors cfg = ConfigFactory.create(ConfigWithPreprocessors.class);
assertNull(cfg.nullValue());
}


// preprocessors implementation

public static class Trim implements Preprocessor {
public String process(String input) {
return input.trim();
}
}

public static class ToLowerCase implements Preprocessor {
public String process(String input) {
return input.toLowerCase();
}
}

public static class SkipInlineComments implements Preprocessor {
public String process(String input) {
int hashTagIndex = input.indexOf('#');
int exclamationMarkIndex = input.indexOf("!");
if (hashTagIndex == -1 && exclamationMarkIndex == -1)
return input; // comments not present.

int commentIndex = min(hashTagIndex, exclamationMarkIndex);
if (commentIndex == -1)
commentIndex = max(hashTagIndex, exclamationMarkIndex);

return input.substring(0, commentIndex);
}
}

}
2 changes: 1 addition & 1 deletion owner/src/test/java/org/aeonbits/owner/TestConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
* @author luigi
*/
public interface TestConstants {
static final String RESOURCES_DIR = "target/test-generated-resources";
String RESOURCES_DIR = "target/test-generated-resources";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
public class UndefinedPropertiesTest {

public static interface Person extends Config {
public interface Person extends Config {
String name();
int age();
Double weight();
Expand Down
4 changes: 2 additions & 2 deletions owner/src/test/java/org/aeonbits/owner/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ public static Map<String, String> getenv() {
return Util.system().getenv();
}

public static interface MyCloneable extends Cloneable {
public interface MyCloneable extends Cloneable {
// for some stupid reason java.lang.Cloneable doesn't define this method...
public Object clone() throws CloneNotSupportedException;
Object clone() throws CloneNotSupportedException;
}

@SuppressWarnings("unchecked")
Expand Down

0 comments on commit 4d39966

Please sign in to comment.