Skip to content

Commit

Permalink
Allow array access via indices in configuration paths
Browse files Browse the repository at this point in the history
- Fixes lightbend#30
- Adds and modifies peek and find methods to allow indexing of ConfigList objects
- Enables nested array and object access using indices in paths:
        listConfig.getString("ofObject.0.byteObj.byteVal")
        listConfig.getString("ofArray.2.2")
  • Loading branch information
Eric committed Feb 18, 2023
1 parent 6e035f2 commit 52b58fb
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Map;

import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigMergeable;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
Expand Down Expand Up @@ -104,6 +105,8 @@ private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path
} else {
if (v instanceof AbstractConfigObject) {
return peekPath((AbstractConfigObject) v, next);
} else if (v instanceof SimpleConfigList) {
return peekListPath((SimpleConfigList) v, next);
} else {
return null;
}
Expand All @@ -113,6 +116,37 @@ private static AbstractConfigValue peekPath(AbstractConfigObject self, Path path
}
}

private static AbstractConfigValue peekListPath(SimpleConfigList self, Path path)
{
try {
Integer current = Integer.valueOf(path.first());
if(path.remainder() == null) {
return self.get(current);
}

AbstractConfigValue v =
self.get(current) instanceof AbstractConfigObject ?
((AbstractConfigObject) self.get(current)).attemptPeekWithPartialResolve(path.remainder().first()) :
peekListPath((SimpleConfigList) self.get(current), path.remainder());

Path next = path.remainder().remainder();
if (next == null) {
return v;
} else {
if (v instanceof AbstractConfigObject) {
return peekPath((AbstractConfigObject) v, next);
} else if (v instanceof SimpleConfigList) {
return peekListPath((SimpleConfigList) v, next);
} else {
return next.remainder() == null ? v : null;
}
}
} catch (ConfigException.NotResolved e) {
throw ConfigImpl.improveNotResolved(path, e);
}
}


@Override
public ConfigValueType valueType() {
return ConfigValueType.OBJECT;
Expand Down
57 changes: 52 additions & 5 deletions config/src/main/java/com/typesafe/config/impl/SimpleConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigException.BadPath;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigMemorySize;
import com.typesafe.config.ConfigMergeable;
Expand Down Expand Up @@ -174,11 +175,21 @@ static private AbstractConfigValue findOrNull(AbstractConfigObject self, Path pa
if (next == null) {
return findKeyOrNull(self, key, expected, originalPath);
} else {
AbstractConfigObject o = (AbstractConfigObject) findKey(self, key,
ConfigValueType.OBJECT,
originalPath.subPath(0, originalPath.length() - next.length()));
assert (o != null); // missing was supposed to throw
return findOrNull(o, next, expected, originalPath);

AbstractConfigValue nextValue =
findKey(self,
key,
null,
originalPath.subPath(0, originalPath.length() - next.length()));

if (nextValue instanceof AbstractConfigObject) {
AbstractConfigObject o = (AbstractConfigObject) nextValue;
return findOrNull(o, next, expected, originalPath);
} else if (nextValue instanceof SimpleConfigList) {
SimpleConfigList o = (SimpleConfigList) nextValue;
return findOrNull(o, next, expected, originalPath);
}
return nextValue;
}
} catch (ConfigException.NotResolved e) {
throw ConfigImpl.improveNotResolved(path, e);
Expand All @@ -194,6 +205,42 @@ AbstractConfigValue find(String pathExpression, ConfigValueType expected) {
return find(path, expected, path);
}

static private AbstractConfigValue findOrNull(SimpleConfigList self,
Path path,
ConfigValueType expected,
Path originalPath)
{
try {
Integer index = Integer.valueOf(path.first());
Path next = path.remainder();
if (next == null) {
try {
return self.get(index);
} catch (IndexOutOfBoundsException e) {
throw new BadPath(originalPath.render(),
String.format("Path index %d exceeds the configuration list length %d",
index, self.size()));
}
}

if (self.get(index) instanceof AbstractConfigObject) {

return findOrNull((AbstractConfigObject) self.get(index), next, expected, originalPath);

} else if (self.get(index) instanceof SimpleConfigList) {

return findOrNull((SimpleConfigList) self.get(index), next, expected, originalPath);
}
throw new BadPath(originalPath.render(),
String.format("Attempted to retrieve key %s on non-object %s",
self.get(index),
self));
} catch (ConfigException.NotResolved e) {
throw ConfigImpl.improveNotResolved(path, e);
}
}


private AbstractConfigValue findOrNull(Path pathExpression, ConfigValueType expected, Path originalPath) {
return findOrNull(object, pathExpression, expected, originalPath);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
*/
package com.typesafe.config.impl

import beanconfig.EnumsConfig.{ Solution, Problem }
import beanconfig.EnumsConfig.{ Problem, Solution }
import com.typesafe.config._

import java.io.{ InputStream, InputStreamReader }
import java.time.Duration

import beanconfig._
import com.typesafe.config.ConfigException.BadPath
import org.junit.Assert._
import org.junit._

import java.util
import java.util.Collections
import scala.collection.JavaConverters._
import scala.collection.mutable.ArrayBuffer

Expand Down Expand Up @@ -131,6 +133,32 @@ class ConfigBeanFactoryTest extends TestUtils {

assertEquals(List(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean)
}
@Test
def testCanAccessListByElement() {
val listConfig: Config = loadConfig().getConfig("arrays")
Assert.assertTrue(listConfig.getList("empty").isEmpty)
Assert.assertTrue(listConfig.hasPath("ofObject.0"))
Assert.assertEquals(2, listConfig.getInt("ofInt.1"))
Assert.assertEquals("c", listConfig.getString("ofString.2"))
Assert.assertEquals("c", listConfig.getString("ofArray.2.2"))
Assert.assertEquals(listConfig.getList("ofArray.2").unwrapped(), List("a", "b", "c").asJava);
Assert.assertTrue(listConfig.hasPath("ofArray.2"))
Assert.assertTrue(listConfig.hasPath("ofArray.2.2"))
Assert.assertTrue(listConfig.hasPath("ofObject.0.byteObj.byteVal"))
Assert.assertEquals("1", listConfig.getString("ofObject.0.byteObj.byteVal"))
Assert.assertFalse(listConfig.hasPath("ofObject.0.byteObj.byteVal.non"))
Assert.assertTrue(listConfig.getIsNull("ofNull.0"))
Assert.assertFalse(listConfig.hasPath("ofNull.0"))

try {
listConfig.getString("empty.1")
Assert.fail();
} catch {
case e: BadPath => {}
case e: Exception => Assert.fail()
}

}

@Test
def testCreateSet() {
Expand Down

0 comments on commit 52b58fb

Please sign in to comment.