├── .gitignore
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── org
│ └── skife
│ └── config
│ ├── Bully.java
│ ├── CaseInsensitiveEnumCoercible.java
│ ├── Coercer.java
│ ├── Coercible.java
│ ├── CommonsConfigSource.java
│ ├── Config.java
│ ├── ConfigReplacements.java
│ ├── ConfigSource.java
│ ├── ConfigurationObjectFactory.java
│ ├── DataAmount.java
│ ├── DataAmountUnit.java
│ ├── Default.java
│ ├── DefaultCoercibles.java
│ ├── DefaultNull.java
│ ├── Description.java
│ ├── ExactMatchEnumCoercible.java
│ ├── Param.java
│ ├── Separator.java
│ ├── ServletFilterConfigSource.java
│ ├── SimplePropertyConfigSource.java
│ └── TimeSpan.java
└── test
└── java
└── org
└── skife
└── config
├── BadConfig.java
├── CoercionConfig.java
├── Config1.java
├── Config2.java
├── Config3.java
├── Config4.java
├── Config5.java
├── ConfigEnum.java
├── EnumeratedConfig1.java
├── MultiConfig.java
├── Props.java
├── ReplacementConfig1.java
├── TestArrays.java
├── TestBadConfig.java
├── TestCaseInsensitiveEnumCoercible.java
├── TestClasses.java
├── TestCoercion.java
├── TestCollections.java
├── TestCommonsConfig.java
├── TestConfigurationObjectFactory.java
├── TestCustomCoercion.java
├── TestDataAmount.java
├── TestDefaultCoercibles.java
├── TestDefaultNull.java
├── TestDefaultsPresent.java
├── TestEmptyValue.java
├── TestEnums.java
├── TestExposeMappedReplacements.java
├── TestFile.java
├── TestMultiConfig.java
├── TestNoFinal.java
├── TestServletFilterConfigSource.java
├── TestTimeSpan.java
├── TestVariousPropertyTypes.java
├── Wibble.java
└── WibbleConfig.java
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | report
3 | *.ipr
4 | *.iws
5 | *.iml
6 | .clover
7 | .idea
8 | build
9 | out
10 | .classpath
11 | .project
12 | .settings
13 | *~
14 | .java-version
15 | test-output
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Example
2 |
3 | Create an interface for your config object:
4 |
5 | public interface MyConfig
6 | {
7 | @Config("foo")
8 | String getFoo();
9 |
10 | @Config("blah")
11 | int getBlah();
12 |
13 | @Config("what")
14 | @Default("none")
15 | String getWhat();
16 | }
17 |
18 | Set the properties that we mapped with `@Config` above (or simply call `System.getProperties()`):
19 |
20 | Properties props = new Properties();
21 | props.setProperty("foo", "hello");
22 | props.setProperty("blah", "123");
23 |
24 | Then create the config object from the properties:
25 |
26 | ConfigurationObjectFactory factory = new ConfigurationObjectFactory(props);
27 | MyConfig conf = factory.build(MyConfig.class);
28 |
29 | # Default values
30 |
31 | Using `@Default()` can set arbitrary default values. To set `null` as the default value, use the `@DefaultNull`annotation.
32 |
33 | # Advanced usage
34 |
35 | @Config({"what1", "what2"})
36 | @Default("none")
37 | String getWhat();
38 |
39 | will look at `what1` first, then at `what2` and finally fall back to the default.
40 |
41 | # Parameterized configs
42 |
43 | enum StateType {
44 | FILESYSTEM,
45 | MEMORY
46 | }
47 |
48 | @Config("${state_type}.size")
49 | @DefaultNull
50 | String getParameterizedConfig(@Param("state_type") StateType e);
51 |
52 | @Config("${state_type}.${source}.path")
53 | @DefaultNull
54 | String getDoubleParameterizedConfig(@Param("state_type") StateType e, @Param("source") String source);
55 |
56 |
57 | Use it like the following:
58 |
59 | myConfig.getParameterizedConfig(StateType.MEMORY); // reads from MEMORY.size
60 | myConfig.getDoubleParameterizedConfig(StateType.FILESYSTEM, "backend"); // reads from FILESYSTEM.backend.path
61 |
62 |
63 | # Type support
64 |
65 | Config-magic supports these types:
66 |
67 | * Primitive types: `boolean`, `byte`, `short`, `integer`, `long`, `float`, `double`.
68 | * Enums. Note that config-magic by default ignores the case for enum values.
69 | * `java.lang.String`.
70 | * `java.net.URI`.
71 | * `java.lang.Class` and simple wildcard extensions (`java.lang.Class>`, `java.lang.Class extends Foo>` - config-magic will type check that the type passed as a property conforms to the wildcard type), but not more complex wildcard or parameterized types (e.g. `java.lang.Class super Bar>` or `java.lang.Class extends List super Bar>>`).
72 | * `org.skipe.config.TimeSpan`: constructed from short textual representation like "5d" (or alias "5 days"); units supported are:
73 | * ms (alias 'milliseconds')
74 | * s ('seconds')
75 | * m ('minutes')
76 | * h ('hours')
77 | * d ('days')
78 | * Any instantiable class that has a public constructor with a single `Object` parameter. This is useful for instance for [joda-time](http://joda-time.sourceforge.net/)'s `DateTime` objects.
79 | * Any instantiable class that has a public constructor with a single `String` parameter. This is useful for instance for `java.lang.File`.
80 | * Any class that has a static `valueOf` method with a single `String` parameter and the class as its return type.
81 |
82 | # Maven dependency
83 |
84 | To use config-magic in Maven projects:
85 |
86 |
87 | org.skife.config
88 | config-magic
89 | 0.11
90 |
91 |
92 | # Mailing List
93 |
94 | We have a [mailing list](http://groups.google.com/group/config-magic) for development and users.
95 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | org.skife.config
4 | config-magic
5 | jar
6 | 0.23-SNAPSHOT
7 | config-magic
8 | http://github.com/brianm/config-magic
9 |
10 | A configuration object convenience library.
11 |
12 |
13 | UTF-8
14 |
15 |
16 |
17 |
18 | Apache License 2.0
19 | http://www.apache.org/licenses/LICENSE-2.0.html
20 | repo
21 |
22 |
23 |
24 |
25 | scm:git:https://github.com/brianm/config-magic.git
26 | https://github.com/brianm/config-magic
27 | HEAD
28 |
29 |
30 |
31 |
32 |
33 | sonatype-nexus-snapshots
34 | Sonatype Nexus Snapshots
35 | http://oss.sonatype.org/content/repositories/snapshots
36 |
37 |
38 |
39 | sonatype-nexus-staging
40 | Nexus Release Repository
41 | http://oss.sonatype.org/service/local/staging/deploy/maven2/
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | commons-configuration
50 | commons-configuration
51 | 1.6
52 | true
53 |
54 |
55 |
56 | cglib
57 | cglib-nodep
58 | 2.2
59 |
60 |
61 |
62 | org.slf4j
63 | slf4j-api
64 | 1.5.6
65 |
66 |
67 |
68 | org.slf4j
69 | slf4j-simple
70 | 1.5.6
71 | true
72 |
73 |
74 |
75 | javax.servlet
76 | servlet-api
77 | 2.5
78 | provided
79 | true
80 |
81 |
82 |
83 | junit
84 | junit
85 | 4.13.1
86 | test
87 |
88 |
89 |
90 | joda-time
91 | joda-time
92 | 1.6.2
93 | test
94 |
95 |
96 |
97 |
98 |
99 |
100 | org.apache.maven.plugins
101 | maven-compiler-plugin
102 | 2.3.2
103 |
104 | 1.6
105 | 1.6
106 |
107 |
108 |
109 |
110 | org.apache.maven.plugins
111 | maven-resources-plugin
112 | 2.5
113 |
114 | UTF-8
115 |
116 |
117 |
118 |
119 | org.apache.maven.plugins
120 | maven-assembly-plugin
121 | 2.2.2
122 |
123 | gnu
124 |
125 |
126 |
127 |
128 | org.apache.maven.plugins
129 | maven-source-plugin
130 | 2.2.1
131 |
132 |
133 | attach-sources
134 |
135 | jar-no-fork
136 |
137 |
138 |
139 |
140 |
141 | org.apache.maven.plugins
142 | maven-javadoc-plugin
143 | 2.9.1
144 |
145 |
146 | attach-javadocs
147 |
148 | jar
149 |
150 |
151 |
152 |
153 |
154 |
155 | org.apache.maven.plugins
156 | maven-shade-plugin
157 | 1.5
158 |
159 |
160 | package
161 |
162 | shade
163 |
164 |
165 |
166 |
167 | cglib:cglib-nodep
168 |
169 |
170 |
171 |
172 | net.sf.cglib
173 | org.skife.config.cglib
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | org.apache.maven.plugins
182 | maven-release-plugin
183 | 2.5.3
184 |
185 | forked-path
186 |
187 |
188 |
189 |
190 | org.sonatype.plugins
191 | nexus-staging-maven-plugin
192 | 1.6.7
193 | true
194 |
195 | sonatype-nexus-staging
196 | https://oss.sonatype.org/
197 | true
198 |
199 |
200 |
201 |
202 | org.apache.maven.plugins
203 | maven-gpg-plugin
204 | 1.5
205 |
206 |
207 | sign-artifacts
208 | verify
209 |
210 | sign
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 | release-sign-artifacts
221 |
222 |
223 | performRelease
224 | true
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 | brianm
238 | Brian McCallister
239 | brianm@skife.org
240 |
241 |
242 | martint
243 | Martin Traverso
244 |
245 |
246 | arosien
247 | Adam Rosien
248 |
249 |
250 | hgschmie
251 | Henning Schmiedehausen
252 |
253 |
254 | cowtowncoder
255 | Tatu Saloranta
256 | tatu@fasterxml.com
257 |
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------
/src/main/java/org/skife/config/Bully.java:
--------------------------------------------------------------------------------
1 | package org.skife.config;
2 |
3 | import java.lang.reflect.Array;
4 | import java.lang.reflect.Constructor;
5 | import java.lang.reflect.ParameterizedType;
6 | import java.lang.reflect.Type;
7 | import java.lang.reflect.WildcardType;
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.Collections;
11 | import java.util.HashMap;
12 | import java.util.LinkedHashSet;
13 | import java.util.List;
14 | import java.util.Map;
15 | import java.util.Set;
16 |
17 | class Bully
18 | {
19 | /** All explicit type conversions that config magic knows about. Every new bully will know about those. */
20 | private static final List> TYPE_COERCIBLES;
21 |
22 | /** Catchall converters. These will be run if no specific type coercer was found. */
23 | private static final List> DEFAULT_COERCIBLES;
24 |
25 | static {
26 | final List> typeCoercibles = new ArrayList>();
27 | final List> defaultCoercibles = new ArrayList>();
28 |
29 | typeCoercibles.add(DefaultCoercibles.BOOLEAN_COERCIBLE);
30 | typeCoercibles.add(DefaultCoercibles.BYTE_COERCIBLE);
31 | typeCoercibles.add(DefaultCoercibles.SHORT_COERCIBLE);
32 | typeCoercibles.add(DefaultCoercibles.INTEGER_COERCIBLE);
33 | typeCoercibles.add(DefaultCoercibles.LONG_COERCIBLE);
34 | typeCoercibles.add(DefaultCoercibles.FLOAT_COERCIBLE);
35 | typeCoercibles.add(DefaultCoercibles.DOUBLE_COERCIBLE);
36 | typeCoercibles.add(DefaultCoercibles.STRING_COERCIBLE);
37 |
38 | // Look Brian, now it groks URIs. ;-)
39 | typeCoercibles.add(DefaultCoercibles.URI_COERCIBLE);
40 |
41 | defaultCoercibles.add(DefaultCoercibles.CASE_INSENSITIVE_ENUM_COERCIBLE);
42 | defaultCoercibles.add(DefaultCoercibles.VALUE_OF_COERCIBLE);
43 | defaultCoercibles.add(DefaultCoercibles.STRING_CTOR_COERCIBLE);
44 | defaultCoercibles.add(DefaultCoercibles.OBJECT_CTOR_COERCIBLE);
45 |
46 | TYPE_COERCIBLES = Collections.unmodifiableList(typeCoercibles);
47 | DEFAULT_COERCIBLES = Collections.unmodifiableList(defaultCoercibles);
48 | }
49 |
50 | /**
51 | * The instance specific mappings from a given type to its coercer. This needs to be two-level because the
52 | * catchall converters will generate specific instances of their coercers based on the type.
53 | */
54 | private final Map, Coercer>> mappings = new HashMap, Coercer>>();
55 |
56 | /**
57 | * All the coercibles that this instance knows about. This list can be extended with user mappings.
58 | */
59 | private final List> coercibles = new ArrayList>();
60 |
61 | public Bully()
62 | {
63 | coercibles.addAll(TYPE_COERCIBLES);
64 | }
65 |
66 | /**
67 | * Adds a new Coercible to the list of known coercibles. This also resets the current mappings in this bully.
68 | */
69 | public void addCoercible(final Coercible> coercible)
70 | {
71 | coercibles.add(coercible);
72 | mappings.clear();
73 | }
74 |
75 | public synchronized Object coerce(Type type, String value, Separator separator) {
76 | if (type instanceof Class) {
77 | Class> clazz = (Class>)type;
78 |
79 | if (clazz.isArray()) {
80 | return coerceArray(clazz.getComponentType(), value, separator);
81 | }
82 | else if (Class.class.equals(clazz)) {
83 | return coerceClass(type, null, value);
84 | }
85 | else {
86 | return coerce(clazz, value);
87 | }
88 | }
89 | else if (type instanceof ParameterizedType) {
90 | ParameterizedType parameterizedType = (ParameterizedType)type;
91 | Type rawType = parameterizedType.getRawType();
92 |
93 | if (rawType instanceof Class>) {
94 | Type[] args = parameterizedType.getActualTypeArguments();
95 |
96 | if (args != null && args.length == 1) {
97 | if (args[0] instanceof Class>) {
98 | return coerceCollection((Class>)rawType, (Class>)args[0], value, separator);
99 | }
100 | else if (args[0] instanceof WildcardType) {
101 | return coerceClass(type, (WildcardType)args[0], value);
102 | }
103 | }
104 | }
105 | }
106 | throw new IllegalStateException(String.format("Don't know how to handle a '%s' type for value '%s'", type, value));
107 | }
108 |
109 | private boolean isAssignableFrom(Type targetType, Class> assignedClass) {
110 | if (targetType instanceof Class) {
111 | return ((Class>)targetType).isAssignableFrom(assignedClass);
112 | }
113 | else if (targetType instanceof WildcardType) {
114 | WildcardType wildcardType = (WildcardType)targetType;
115 |
116 | // Class extends Foo>
117 | for (Type upperBoundType : wildcardType.getUpperBounds()) {
118 | if (!Object.class.equals(upperBoundType)) {
119 | if ((upperBoundType instanceof Class>) && !((Class>)upperBoundType).isAssignableFrom(assignedClass)) {
120 | return false;
121 | }
122 | }
123 | }
124 | }
125 | return true;
126 | }
127 |
128 | private Class> coerceClass(Type type, WildcardType wildcardType, String value) {
129 | if (value == null) {
130 | return null;
131 | }
132 | else {
133 | try {
134 | Class> clazz = Class.forName(value);
135 |
136 | if (!isAssignableFrom(wildcardType, clazz)) {
137 | throw new IllegalArgumentException("Specified class " + clazz + " is not compatible with required type " + type);
138 | }
139 | return clazz;
140 | }
141 | catch (Exception ex) {
142 | throw new IllegalArgumentException(ex);
143 | }
144 | }
145 | }
146 |
147 | private Object coerceArray(Class> elemType, String value, Separator separator) {
148 | if (value == null) {
149 | return null;
150 | }
151 | else if (value.length() == 0) {
152 | return Array.newInstance(elemType, 0);
153 | }
154 | else {
155 | String[] tokens = value.split(separator == null ? Separator.DEFAULT : separator.value());
156 | Object targetArray = Array.newInstance(elemType, tokens.length);
157 |
158 | for (int idx = 0; idx < tokens.length; idx++) {
159 | Array.set(targetArray, idx, coerce(elemType, tokens[idx]));
160 | }
161 | return targetArray;
162 | }
163 | }
164 |
165 | @SuppressWarnings({ "rawtypes", "unchecked" })
166 | private Object coerceCollection(Class> containerType, Class> elemType, String value, Separator separator) {
167 | if (value == null) {
168 | return null;
169 | }
170 | else {
171 | Collection result = null;
172 |
173 | if (Set.class.equals(containerType)) {
174 | result = new LinkedHashSet();
175 | }
176 | else if (Collection.class.equals(containerType) || List.class.equals(containerType)) {
177 | result = new ArrayList();
178 | }
179 | else if (Collection.class.isAssignableFrom(containerType)) {
180 | try {
181 | final Constructor> ctor = containerType.getConstructor();
182 |
183 | if (ctor != null) {
184 | result = (Collection)ctor.newInstance();
185 | }
186 | }
187 | catch (Exception ex) {
188 | // handled below
189 | }
190 | }
191 | if (result == null) {
192 | throw new IllegalStateException(String.format("Don't know how to handle a '%s' container type for value '%s'", containerType, value));
193 | }
194 | if (value.length() > 0) {
195 | for (String token : value.split(separator == null ? Separator.DEFAULT : separator.value())) {
196 | result.add(coerce(elemType, token));
197 | }
198 | }
199 | return result;
200 | }
201 | }
202 |
203 | private Object coerce(Class> clazz, String value) {
204 | Coercer> coercer = getCoercerFor(coercibles, clazz);
205 | if (coercer == null) {
206 | coercer = getCoercerFor(DEFAULT_COERCIBLES, clazz);
207 |
208 | if (coercer == null) {
209 | throw new IllegalStateException(String.format("Don't know how to handle a '%s' type for value '%s'", clazz, value));
210 | }
211 | }
212 | return coercer.coerce(value);
213 | }
214 |
215 | private Coercer> getCoercerFor(final List> coercibles, final Class> type)
216 | {
217 | Coercer> typeCoercer = mappings.get(type);
218 | if (typeCoercer == null) {
219 | for (Coercible> coercible : coercibles) {
220 | final Coercer> coercer = coercible.accept(type);
221 | if (coercer != null) {
222 | mappings.put(type, coercer);
223 | typeCoercer = coercer;
224 | break;
225 | }
226 | }
227 | }
228 | return typeCoercer;
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/main/java/org/skife/config/CaseInsensitiveEnumCoercible.java:
--------------------------------------------------------------------------------
1 | package org.skife.config;
2 |
3 | import java.util.Arrays;
4 |
5 | /**
6 | * Do case insensitive string comparisons for determination of enum value matches.
7 | */
8 | public class CaseInsensitiveEnumCoercible implements Coercible