http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java ---------------------------------------------------------------------- diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java deleted file mode 100755 index c34126c..0000000 --- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T6.java +++ /dev/null @@ -1,38 +0,0 @@ -// *************************************************************************************************************************** -// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * -// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * -// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * -// * with the License. You may obtain a copy of the License at * -// * * -// * http://www.apache.org/licenses/LICENSE-2.0 * -// * * -// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * -// * specific language governing permissions and limitations under the License. * -// *************************************************************************************************************************** -package org.apache.juneau.xml.xml1b; - -import org.apache.juneau.annotation.*; -import org.apache.juneau.xml.annotation.*; - -@Xml(prefix="foo",namespace="http://foo") -@Bean(sort=true) -@SuppressWarnings("javadoc") -public class T6 { - - public int f1 = 1; - - @Xml(prefix="bar",namespace="http://bar") public int f2 = 2; - - private int f3 = 3; - public int getF3() { return f3; } - public void setF3(int f3) { this.f3 = f3; } - - private int f4 = 4; - @Xml(prefix="baz",namespace="http://baz") public int getF4() { return f4; } - public void setF4(int f4) { this.f4 = f4; } - - public boolean equals(T6 x) { - return x.f1 == f1 && x.f2 == f2 && x.f3 == f3 && x.f4 == f4; - } -}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java ---------------------------------------------------------------------- diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java deleted file mode 100755 index f2960aa..0000000 --- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/T7.java +++ /dev/null @@ -1,37 +0,0 @@ -// *************************************************************************************************************************** -// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * -// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * -// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * -// * with the License. You may obtain a copy of the License at * -// * * -// * http://www.apache.org/licenses/LICENSE-2.0 * -// * * -// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * -// * specific language governing permissions and limitations under the License. * -// *************************************************************************************************************************** -package org.apache.juneau.xml.xml1b; - -import org.apache.juneau.annotation.*; -import org.apache.juneau.xml.annotation.*; - -@Bean(sort=true) -@SuppressWarnings("javadoc") -public class T7 { - - @BeanProperty(name="g1") public int f1 = 1; - - @Xml(prefix="bar",namespace="http://bar") @BeanProperty(name="g2") public int f2 = 2; - - private int f3 = 3; - @BeanProperty(name="g3") public int getF3() { return f3; } - @BeanProperty(name="g3") public void setF3(int f3) { this.f3 = f3; } - - private int f4 = 4; - @BeanProperty(name="g4") @Xml(prefix="baz",namespace="http://baz") public int getF4() { return f4; } - @BeanProperty(name="g4") public void setF4(int f4) { this.f4 = f4; } - - public boolean equals(T7 x) { - return x.f1 == f1 && x.f2 == f2 && x.f3 == f3 && x.f4 == f4; - } -} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java ---------------------------------------------------------------------- diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java deleted file mode 100755 index 306db1b..0000000 --- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1b/package-info.java +++ /dev/null @@ -1,16 +0,0 @@ -// *************************************************************************************************************************** -// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * -// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * -// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * -// * with the License. You may obtain a copy of the License at * -// * * -// * http://www.apache.org/licenses/LICENSE-2.0 * -// * * -// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * -// * specific language governing permissions and limitations under the License. * -// *************************************************************************************************************************** -@XmlSchema(prefix="p1",namespace="http://p1") -package org.apache.juneau.xml.xml1b; -import org.apache.juneau.xml.annotation.*; - http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java ---------------------------------------------------------------------- diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java deleted file mode 100755 index 0c383eb..0000000 --- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T8.java +++ /dev/null @@ -1,33 +0,0 @@ -// *************************************************************************************************************************** -// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * -// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * -// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * -// * with the License. You may obtain a copy of the License at * -// * * -// * http://www.apache.org/licenses/LICENSE-2.0 * -// * * -// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * -// * specific language governing permissions and limitations under the License. * -// *************************************************************************************************************************** -package org.apache.juneau.xml.xml1c; - -import org.apache.juneau.xml.annotation.*; - -@Xml(prefix="p2") -@SuppressWarnings("javadoc") -public class T8 { - - public int f1 = 1; - - @Xml(prefix="p1") public int f2 = 2; - - @Xml(prefix="c1") public int f3 = 3; - - @Xml(prefix="f1") - public int f4 = 4; - - public boolean equals(T8 x) { - return x.f1 == f1 && x.f2 == f2 && x.f3 == f3 && x.f4 == f4; - } -} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java ---------------------------------------------------------------------- diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java deleted file mode 100755 index 7009626..0000000 --- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/T9.java +++ /dev/null @@ -1,23 +0,0 @@ -// *************************************************************************************************************************** -// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * -// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * -// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * -// * with the License. You may obtain a copy of the License at * -// * * -// * http://www.apache.org/licenses/LICENSE-2.0 * -// * * -// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * -// * specific language governing permissions and limitations under the License. * -// *************************************************************************************************************************** -package org.apache.juneau.xml.xml1c; - -@SuppressWarnings("javadoc") -public class T9 { - - public int f1 = 1; - - public boolean equals(T9 x) { - return x.f1 == f1; - } -} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java ---------------------------------------------------------------------- diff --git a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java b/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java deleted file mode 100755 index e054388..0000000 --- a/juneau-core-test/src/test/java/org/apache/juneau/xml/xml1c/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -// *************************************************************************************************************************** -// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * -// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * -// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * -// * with the License. You may obtain a copy of the License at * -// * * -// * http://www.apache.org/licenses/LICENSE-2.0 * -// * * -// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * -// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * -// * specific language governing permissions and limitations under the License. * -// *************************************************************************************************************************** -@XmlSchema( - prefix="p1", - xmlNs={ - @XmlNs(prefix="p1",namespaceURI="http://p1"), - @XmlNs(prefix="p2",namespaceURI="http://p2"), - @XmlNs(prefix="p3",namespaceURI="http://p3(unused)"), - @XmlNs(prefix="c1",namespaceURI="http://c1"), - @XmlNs(prefix="f1",namespaceURI="http://f1") - } -) -package org.apache.juneau.xml.xml1c; -import org.apache.juneau.xml.annotation.*; - http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/.classpath ---------------------------------------------------------------------- diff --git a/juneau-core/juneau-config/.classpath b/juneau-core/juneau-config/.classpath new file mode 100644 index 0000000..2fd4a04 --- /dev/null +++ b/juneau-core/juneau-config/.classpath @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" output="target/classes" path="src/main/java"> + <attributes> + <attribute name="optional" value="true"/> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"> + <attributes> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="src" output="target/test-classes" path="src/test/java"> + <attributes> + <attribute name="optional" value="true"/> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> + <attributes> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="output" path="target/classes"/> +</classpath> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/.gitignore ---------------------------------------------------------------------- diff --git a/juneau-core/juneau-config/.gitignore b/juneau-core/juneau-config/.gitignore new file mode 100644 index 0000000..d274d47 --- /dev/null +++ b/juneau-core/juneau-config/.gitignore @@ -0,0 +1,3 @@ +/target/ +/.settings/ +/.DS_Store http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/.project ---------------------------------------------------------------------- diff --git a/juneau-core/juneau-config/.project b/juneau-core/juneau-config/.project new file mode 100644 index 0000000..23dc42c --- /dev/null +++ b/juneau-core/juneau-config/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>juneau-config</name> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.m2e.core.maven2Builder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.m2e.core.maven2Nature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + <nature>edu.umd.cs.findbugs.plugin.eclipse.findbugsNature</nature> + </natures> +</projectDescription> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/pom.xml ---------------------------------------------------------------------- diff --git a/juneau-core/juneau-config/pom.xml b/juneau-core/juneau-config/pom.xml new file mode 100644 index 0000000..516b07d --- /dev/null +++ b/juneau-core/juneau-config/pom.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + *************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations under the License. * + *************************************************************************************************************************** +--> +<project + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.juneau</groupId> + <artifactId>juneau-core</artifactId> + <version>6.3.2-incubating-SNAPSHOT</version> + </parent> + + <artifactId>juneau-config</artifactId> + <name>Apache Juneau Config File API</name> + <description>Dynamic configuration files.</description> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.apache.juneau</groupId> + <artifactId>juneau-marshall</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.juneau</groupId> + <artifactId>juneau-svl</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + + <properties> + <!-- Skip javadoc generation since we generate them in the aggregate pom --> + <maven.javadoc.skip>true</maven.javadoc.skip> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>3.2.0</version> + <extensions>true</extensions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <includes> + <include>**/*Test.class</include> + </includes> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <executions> + <execution> + <id>attach-sources</id> + <phase>verify</phase> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.7.2.201409121644</version> + <executions> + <execution> + <id>default-prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>default-report</id> + <phase>prepare-package</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java ---------------------------------------------------------------------- diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java new file mode 100644 index 0000000..5a956a5 --- /dev/null +++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFile.java @@ -0,0 +1,1215 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.ini; + +import static java.lang.reflect.Modifier.*; +import static org.apache.juneau.ini.ConfigFileFormat.*; +import static org.apache.juneau.ini.ConfigUtils.*; +import static org.apache.juneau.internal.ThrowableUtils.*; +import static org.apache.juneau.internal.StringUtils.*; +import static org.apache.juneau.internal.ArrayUtils.*; + +import java.beans.*; +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.svl.*; + +/** + * Implements the API for accessing the contents of a config file. + * + * <p> + * Refer to <a class='doclink' href='package-summary.html#TOC'>org.apache.juneau.ini</a> for usage information. + */ +public abstract class ConfigFile implements Map<String,Section> { + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + /** + * Retrieves an entry value from this config file. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @return The value, or the default value if the section or value doesn't exist. + */ + public abstract String get(String sectionName, String sectionKey); + + /** + * Sets an entry value in this config file. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @param value The new value. + * @param serializer + * The serializer to use for serializing the object. + * If <jk>null</jk>, then uses the predefined serializer on the config file. + * @param encoded If <jk>true</jk>, then encode the value using the encoder associated with this config file. + * @param newline If <jk>true</jk>, then put serialized output on a separate line from the key. + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws SerializeException If the object value could not be converted to a JSON string for some reason. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract String put(String sectionName, String sectionKey, Object value, Serializer serializer, + boolean encoded, boolean newline) throws SerializeException; + + /** + * Identical to {@link #put(String, String, Object, Serializer, boolean, boolean)} except used when the value is a + * simple string to avoid having to catch a {@link SerializeException}. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @param value The new value. + * @param encoded + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract String put(String sectionName, String sectionKey, String value, boolean encoded); + + + /** + * Removes an entry from this config file. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract String remove(String sectionName, String sectionKey); + + /** + * Returns the current set of keys in the specified section. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @return The list of keys in the specified section, or <jk>null</jk> if section does not exist. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract Set<String> getSectionKeys(String sectionName); + + /** + * Reloads this config file object from the persisted file contents if the modified timestamp on the file has changed. + * + * @return This object (for method chaining). + * @throws IOException If file could not be read, or file is not associated with this object. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile loadIfModified() throws IOException; + + /** + * Loads this config file object from the persisted file contents. + * + * @return This object (for method chaining). + * @throws IOException If file could not be read, or file is not associated with this object. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile load() throws IOException; + + /** + * Loads this config file object from the specified reader. + * + * @param r The reader to read from. + * @return This object (for method chaining). + * @throws IOException If file could not be read, or file is not associated with this object. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile load(Reader r) throws IOException; + + /** + * Adds arbitrary lines to the specified config file section. + * + * <p> + * The lines can be any of the following.... + * <ul class='spaced-list'> + * <li> + * <js>"# comment"</js> - A comment line. + * <li> + * <js>"key=val"</js> - A key/value pair (equivalent to calling {@link #put(String,Object)}. + * <li> + * <js>" foobar "</js> - Anything else (interpreted as a comment). + * </ul> + * + * <p> + * If the section does not exist, it will automatically be created. + * + * @param section The name of the section to add lines to, or <jk>null</jk> to add to the beginning unnamed section. + * @param lines The lines to add to the section. + * @return This object (for method chaining). + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile addLines(String section, String...lines); + + /** + * Adds header comments to the specified section. + * + * <p> + * Header comments are defined as lines that start with <jk>"#"</jk> immediately preceding a section header + * <jk>"[section]"</jk>. + * These are handled as part of the section itself instead of being interpreted as comments in the previous section. + * + * <p> + * Header comments can be of the following formats... + * <ul class='spaced-list'> + * <li> + * <js>"# comment"</js> - A comment line. + * <li> + * <js>"comment"</js> - Anything else (will automatically be prefixed with <js>"# "</js>). + * </ul> + * + * <p> + * If the section does not exist, it will automatically be created. + * + * @param section The name of the section to add lines to, or <jk>null</jk> to add to the default section. + * @param headerComments The comment lines to add to the section. + * @return This object (for method chaining). + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile addHeaderComments(String section, String...headerComments); + + /** + * Removes any header comments from the specified section. + * + * @param section The name of the section to remove header comments from. + * @return This object (for method chaining). + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile clearHeaderComments(String section); + + /** + * Returns the reusable bean session associated with this config file. + * + * <p> + * Used for performing simple datatype conversions. + * + * @return The reusable bean session associated with this config file. + */ + protected abstract BeanSession getBeanSession(); + + /** + * Converts the specified object to a string. + * + * <p> + * The serialized output is identical to LAX JSON (JSON with unquoted attributes) except for the following + * exceptions: + * <ul> + * <li>Top level strings are not quoted. + * </ul> + * + * @param o The object to serialize. + * @param serializer + * The serializer to use for serializing the object. + * If <jk>null</jk>, then uses the predefined serializer on the config file. + * @param newline If <jk>true</jk>, add a newline at the beginning of the value. + * @return The serialized object. + * @throws SerializeException + */ + protected abstract String serialize(Object o, Serializer serializer, boolean newline) throws SerializeException; + + /** + * Converts the specified string to an object of the specified type. + * + * @param s The string to parse. + * @param parser + * The parser to use for parsing the object. + * If <jk>null</jk>, then uses the predefined parser on the config file. + * @param type The data type to create. + * @param args The generic type arguments if the type is a {@link Collection} or {@link Map} + * @return The parsed object. + * @throws ParseException + */ + protected abstract <T> T parse(String s, Parser parser, Type type, Type...args) throws ParseException; + + /** + * Places a read lock on this config file. + */ + protected abstract void readLock(); + + /** + * Removes the read lock on this config file. + */ + protected abstract void readUnlock(); + + + //-------------------------------------------------------------------------------- + // API methods + //-------------------------------------------------------------------------------- + + /** + * Returns the specified value as a string from the config file. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param def The default value if the section or value does not exist. + * @return The value, or the default value if the section or value doesn't exist. + */ + public final String getString(String key, String def) { + assertFieldNotNull(key, "key"); + String s = get(getSectionName(key), getSectionKey(key)); + return (StringUtils.isEmpty(s) && def != null ? def : s); + } + + /** + * Removes an entry with the specified key. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws UnsupportedOperationException If config file is read only. + */ + public final String removeString(String key) { + assertFieldNotNull(key, "key"); + return remove(getSectionName(key), getSectionKey(key)); + } + + /** + * Gets the entry with the specified key and converts it to the specified value. + * + * <p> + * The key can be in one of the following formats... + * <ul class='spaced-list'> + * <li> + * <js>"key"</js> - A value in the default section (i.e. defined above any <code>[section]</code> header). + * <li> + * <js>"section/key"</js> - A value from the specified section. + * </ul> + * + * <p> + * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). + * + * <h5 class='section'>Examples:</h5> + * <p class='bcode'> + * ConfigFile cf = <jk>new</jk> ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>); + * + * <jc>// Parse into a linked-list of strings.</jc> + * List l = cf.getObject(<js>"MySection/myListOfStrings"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); + * + * <jc>// Parse into a linked-list of beans.</jc> + * List l = cf.getObject(<js>"MySection/myListOfBeans"</js>, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); + * + * <jc>// Parse into a linked-list of linked-lists of strings.</jc> + * List l = cf.getObject(<js>"MySection/my2dListOfStrings"</js>, LinkedList.<jk>class</jk>, + * LinkedList.<jk>class</jk>, String.<jk>class</jk>); + * + * <jc>// Parse into a map of string keys/values.</jc> + * Map m = cf.getObject(<js>"MySection/myMap"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, + * String.<jk>class</jk>); + * + * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> + * Map m = cf.getObject(<js>"MySection/myMapOfListsOfBeans"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, + * List.<jk>class</jk>, MyBean.<jk>class</jk>); + * </p> + * + * <p> + * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type. + * + * <p> + * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value + * types. + * + * <p> + * The array can be arbitrarily long to indicate arbitrarily complex data structures. + * + * <h5 class='section'>Notes:</h5> + * <ul> + * <li>Use the {@link #getObject(String, Class)} method instead if you don't need a parameterized map/collection. + * </ul> + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param type + * The object type to create. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * @param args + * The type arguments of the class if it's a collection or map. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObject(String key, Type type, Type...args) throws ParseException { + return getObject(key, (Parser)null, type, args); + } + + /** + * Same as {@link #getObject(String, Type, Type...)} but allows you to specify the parser to use to parse the value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param parser + * The parser to use for parsing the object. + * If <jk>null</jk>, then uses the predefined parser on the config file. + * @param type + * The object type to create. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * @param args + * The type arguments of the class if it's a collection or map. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObject(String key, Parser parser, Type type, Type...args) throws ParseException { + assertFieldNotNull(key, "key"); + assertFieldNotNull(type, "type"); + return parse(getString(key), parser, type, args); + } + + /** + * Same as {@link #getObject(String, Type, Type...)} except optimized for a non-parameterized class. + * + * <p> + * This is the preferred parse method for simple types since you don't need to cast the results. + * + * <h5 class='section'>Examples:</h5> + * <p class='bcode'> + * ConfigFile cf = <jk>new</jk> ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>); + * + * <jc>// Parse into a string.</jc> + * String s = cf.getObject(<js>"MySection/mySimpleString"</js>, String.<jk>class</jk>); + * + * <jc>// Parse into a bean.</jc> + * MyBean b = cf.getObject(<js>"MySection/myBean"</js>, MyBean.<jk>class</jk>); + * + * <jc>// Parse into a bean array.</jc> + * MyBean[] b = cf.getObject(<js>"MySection/myBeanArray"</js>, MyBean[].<jk>class</jk>); + * + * <jc>// Parse into a linked-list of objects.</jc> + * List l = cf.getObject(<js>"MySection/myList"</js>, LinkedList.<jk>class</jk>); + * + * <jc>// Parse into a map of object keys/values.</jc> + * Map m = cf.getObject(<js>"MySection/myMap"</js>, TreeMap.<jk>class</jk>); + * </p> + * + * @param <T> The class type of the object being created. + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param type The object type to create. + * @return The parsed object. + * @throws ParseException + * If the input contains a syntax error or is malformed, or is not valid for the specified type. + * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. + */ + public final <T> T getObject(String key, Class<T> type) throws ParseException { + return getObject(key, (Parser)null, type); + } + + /** + * Same as {@link #getObject(String, Class)} but allows you to specify the parser to use to parse the value. + * + * @param <T> The class type of the object being created. + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param parser + * The parser to use for parsing the object. + * If <jk>null</jk>, then uses the predefined parser on the config file. + * @param type The object type to create. + * @return The parsed object. + * @throws ParseException + * If the input contains a syntax error or is malformed, or is not valid for the specified type. + * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. + */ + public final <T> T getObject(String key, Parser parser, Class<T> type) throws ParseException { + assertFieldNotNull(key, "key"); + assertFieldNotNull(type, "c"); + return parse(getString(key), parser, type); + } + + /** + * Gets the entry with the specified key and converts it to the specified value. + * + * <p> + * Same as {@link #getObject(String, Class)}, but with a default value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param def The default value if section or key does not exist. + * @param type The class to convert the value to. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObjectWithDefault(String key, T def, Class<T> type) throws ParseException { + return getObjectWithDefault(key, null, def, type); + } + + /** + * Same as {@link #getObjectWithDefault(String, Object, Class)} but allows you to specify the parser to use to parse + * the value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param parser + * The parser to use for parsing the object. + * If <jk>null</jk>, then uses the predefined parser on the config file. + * @param def The default value if section or key does not exist. + * @param type The class to convert the value to. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObjectWithDefault(String key, Parser parser, T def, Class<T> type) throws ParseException { + assertFieldNotNull(key, "key"); + assertFieldNotNull(type, "c"); + T t = parse(getString(key), parser, type); + return (t == null ? def : t); + } + + /** + * Gets the entry with the specified key and converts it to the specified value. + * + * <p> + * Same as {@link #getObject(String, Type, Type...)}, but with a default value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param def The default value if section or key does not exist. + * @param type + * The object type to create. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * @param args + * The type arguments of the class if it's a collection or map. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObjectWithDefault(String key, T def, Type type, Type...args) throws ParseException { + return getObjectWithDefault(key, null, def, type, args); + } + + /** + * Same as {@link #getObjectWithDefault(String, Object, Type, Type...)} but allows you to specify the parser to use + * to parse the value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param parser + * The parser to use for parsing the object. + * If <jk>null</jk>, then uses the predefined parser on the config file. + * @param def The default value if section or key does not exist. + * @param type + * The object type to create. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * @param args + * The type arguments of the class if it's a collection or map. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObjectWithDefault(String key, Parser parser, T def, Type type, Type...args) throws ParseException { + assertFieldNotNull(key, "key"); + assertFieldNotNull(type, "type"); + T t = parse(getString(key), parser, type, args); + return (t == null ? def : t); + } + + /** + * Gets the entry with the specified key and converts it to the specified value. + * + * <p> + * Same as {@link #getObject(String, Class)}, but used when key is already broken into section/key. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @param c The class to convert the value to. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or the default value if the section or value doesn't exist. + */ + public final <T> T getObject(String sectionName, String sectionKey, Class<T> c) throws ParseException { + return getObject(sectionName, sectionKey, null, c); + } + + /** + * Same as {@link #getObject(String, String, Class)} but allows you to specify the parser to use to parse the value. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @param parser + * The parser to use for parsing the object. + * If <jk>null</jk>, then uses the predefined parser on the config file. + * @param c The class to convert the value to. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or the default value if the section or value doesn't exist. + */ + public final <T> T getObject(String sectionName, String sectionKey, Parser parser, Class<T> c) throws ParseException { + assertFieldNotNull(sectionName, "sectionName"); + assertFieldNotNull(sectionKey, "sectionKey"); + return parse(get(sectionName, sectionKey), parser, c); + } + + /** + * Gets the entry with the specified key and converts it to the specified value. + * + * <p> + * Same as {@link #getObject(String, Type, Type...)}, but used when key is already broken into section/key. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @param type + * The object type to create. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * @param args + * The type arguments of the class if it's a collection or map. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObject(String sectionName, String sectionKey, Type type, Type...args) throws ParseException { + return getObject(sectionName, sectionKey, null, type, args); + } + + /** + * Same as {@link #getObject(String, String, Type, Type...)} but allows you to specify the parser to use to parse + * the value. + * + * @param sectionName The section name. Must not be <jk>null</jk>. + * @param sectionKey The section key. Must not be <jk>null</jk>. + * @param parser + * The parser to use for parsing the object. + * If <jk>null</jk>, then uses the predefined parser on the config file. + * @param type + * The object type to create. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * @param args + * The type arguments of the class if it's a collection or map. + * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, + * {@link GenericArrayType} + * <br>Ignored if the main type is not a map or collection. + * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final <T> T getObject(String sectionName, String sectionKey, Parser parser, Type type, Type...args) + throws ParseException { + assertFieldNotNull(sectionName, "sectionName"); + assertFieldNotNull(sectionKey, "sectionKey"); + return parse(get(sectionName, sectionKey), parser, type, args); + } + + /** + * Gets the entry with the specified key. + * + * <p> + * The key can be in one of the following formats... + * <ul class='spaced-list'> + * <li> + * <js>"key"</js> - A value in the default section (i.e. defined above any <code>[section]</code> header). + * <li> + * <js>"section/key"</js> - A value from the specified section. + * </ul> + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @return The value, or <jk>null</jk> if the section or key does not exist. + */ + public final String getString(String key) { + return getString(key, null); + } + + /** + * Gets the entry with the specified key, splits the value on commas, and returns the values as trimmed strings. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @return The value, or an empty list if the section or key does not exist. + */ + public final String[] getStringArray(String key) { + return getStringArray(key, new String[0]); + } + + /** + * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param def The default value if section or key does not exist. + * @return The value, or an empty list if the section or key does not exist. + */ + public final String[] getStringArray(String key, String[] def) { + String s = getString(key); + if (s == null) + return def; + String[] r = StringUtils.isEmpty(s) ? new String[0] : split(s); + return r.length == 0 ? def : r; + } + + /** + * Convenience method for getting int config values. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @return The value, or <code>0</code> if the section or key does not exist or cannot be parsed as an integer. + */ + public final int getInt(String key) { + return getInt(key, 0); + } + + /** + * Convenience method for getting int config values. + * + * <p> + * <js>"M"</js> and <js>"K"</js> can be used to identify millions and thousands. + * + * <h5 class='section'>Example:</h5> + * <ul class='spaced-list'> + * <li> + * <code><js>"100K"</js> => 1024000</code> + * <li> + * <code><js>"100M"</js> => 104857600</code> + * </ul> + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param def The default value if config file or value does not exist. + * @return The value, or the default value if the section or key does not exist or cannot be parsed as an integer. + */ + public final int getInt(String key, int def) { + String s = getString(key); + if (StringUtils.isEmpty(s)) + return def; + return parseIntWithSuffix(s); + } + + /** + * Convenience method for getting boolean config values. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @return The value, or <jk>false</jk> if the section or key does not exist or cannot be parsed as a boolean. + */ + public final boolean getBoolean(String key) { + return getBoolean(key, false); + } + + /** + * Convenience method for getting boolean config values. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param def The default value if config file or value does not exist. + * @return The value, or the default value if the section or key does not exist or cannot be parsed as a boolean. + */ + public final boolean getBoolean(String key, boolean def) { + String s = getString(key); + return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s); + } + + /** + * Adds or replaces an entry with the specified key with a POJO serialized to a string using the registered + * serializer. + * + * <p> + * Equivalent to calling <code>put(key, value, isEncoded(key))</code>. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param value The new value POJO. + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws SerializeException + * If serializer could not serialize the value or if a serializer is not registered with this config file. + * @throws UnsupportedOperationException If config file is read only. + */ + public final String put(String key, Object value) throws SerializeException { + return put(key, value, null, isEncoded(key), false); + } + + /** + * Same as {@link #put(String, Object)} but allows you to specify the serializer to use to serialize the value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param value The new value POJO. + * @param serializer + * The serializer to use for serializing the object. + * If <jk>null</jk>, then uses the predefined serializer on the config file. + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws SerializeException + * If serializer could not serialize the value or if a serializer is not registered with this config file. + * @throws UnsupportedOperationException If config file is read only. + */ + public final String put(String key, Object value, Serializer serializer) throws SerializeException { + return put(key, value, serializer, isEncoded(key), false); + } + + /** + * Adds or replaces an entry with the specified key with the specified value. + * + * <p> + * The format of the entry depends on the data type of the value. + * <ul class='spaced-list'> + * <li> + * Simple types (<code>String</code>, <code>Number</code>, <code>Boolean</code>, primitives) + * are serialized as plain strings. + * <li> + * Arrays and collections of simple types are serialized as comma-delimited lists of plain strings. + * <li> + * Other types (e.g. beans) are serialized using the serializer registered with this config file. + * <li + * >Arrays and collections of other types are serialized as comma-delimited lists of serialized strings of + * each entry. + * </ul> + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param value The new value. + * @param encoded + * If <jk>true</jk>, value is encoded by the registered encoder when the config file is persisted to disk. + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws SerializeException + * If serializer could not serialize the value or if a serializer is not registered with this config file. + * @throws UnsupportedOperationException If config file is read only. + */ + public final String put(String key, Object value, boolean encoded) throws SerializeException { + return put(key, value, null, encoded, false); + } + + /** + * Same as {@link #put(String, Object, boolean)} but allows you to specify the serializer to use to serialize the + * value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @param value The new value. + * @param serializer + * The serializer to use for serializing the object. + * If <jk>null</jk>, then uses the predefined serializer on the config file. + * @param encoded + * If <jk>true</jk>, value is encoded by the registered encoder when the config file is persisted to disk. + * @param newline If <jk>true</jk>, a newline is added to the beginning of the input. + * @return The previous value, or <jk>null</jk> if the section or key did not previously exist. + * @throws SerializeException + * If serializer could not serialize the value or if a serializer is not registered with this config file. + * @throws UnsupportedOperationException If config file is read only. + */ + public final String put(String key, Object value, Serializer serializer, boolean encoded, boolean newline) + throws SerializeException { + assertFieldNotNull(key, "key"); + return put(getSectionName(key), getSectionKey(key), serialize(value, serializer, newline), encoded); + } + + /** + * Returns the specified section as a map of key/value pairs. + * + * @param sectionName The section name to retrieve. + * @return A map of the section, or <jk>null</jk> if the section was not found. + */ + public final ObjectMap getSectionMap(String sectionName) { + readLock(); + try { + Set<String> keys = getSectionKeys(sectionName); + if (keys == null) + return null; + ObjectMap m = new ObjectMap(); + for (String key : keys) + m.put(key, get(sectionName, key)); + return m; + } finally { + readUnlock(); + } + } + + /** + * Copies the entries in a section to the specified bean by calling the public setters on that bean. + * + * @param sectionName The section name to write from. + * @param bean The bean to set the properties on. + * @param ignoreUnknownProperties + * If <jk>true</jk>, don't throw an {@link IllegalArgumentException} if this section contains a key that doesn't + * correspond to a setter method. + * @param permittedPropertyTypes + * If specified, only look for setters whose property types are those listed. + * If not specified, use all setters. + * @return An object map of the changes made to the bean. + * @throws ParseException If parser was not set on this config file or invalid properties were found in the section. + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public final ObjectMap writeProperties(String sectionName, Object bean, boolean ignoreUnknownProperties, + Class<?>...permittedPropertyTypes) throws ParseException, IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + assertFieldNotNull(bean, "bean"); + ObjectMap om = new ObjectMap(); + readLock(); + try { + Set<String> keys = getSectionKeys(sectionName); + if (keys == null) + throw new IllegalArgumentException("Section not found"); + keys = new LinkedHashSet<String>(keys); + for (Method m : bean.getClass().getMethods()) { + int mod = m.getModifiers(); + if (isPublic(mod) && (!isStatic(mod)) && m.getName().startsWith("set") && m.getParameterTypes().length == 1) { + Class<?> pt = m.getParameterTypes()[0]; + if (permittedPropertyTypes == null || permittedPropertyTypes.length == 0 || contains(pt, permittedPropertyTypes)) { + String propName = Introspector.decapitalize(m.getName().substring(3)); + Object value = getObject(sectionName, propName, pt); + if (value != null) { + m.invoke(bean, value); + om.put(propName, value); + keys.remove(propName); + } + } + } + } + if (! (ignoreUnknownProperties || keys.isEmpty())) + throw new ParseException("Invalid properties found in config file section ''{0}'': {1}", sectionName, keys); + return om; + } finally { + readUnlock(); + } + } + + /** + * Shortcut for calling <code>getSectionAsBean(sectionName, c, <jk>false</jk>)</code>. + * + * @param sectionName The section name to write from. + * @param c The bean class to create. + * @return A new bean instance. + * @throws ParseException + */ + public final <T> T getSectionAsBean(String sectionName, Class<T>c) throws ParseException { + return getSectionAsBean(sectionName, c, false); + } + + /** + * Converts this config file section to the specified bean instance. + * + * <p> + * Key/value pairs in the config file section get copied as bean property values to the specified bean class. + * + * <h6 class='figure'>Example config file</h6> + * <p class='bcode'> + * <cs>[MyAddress]</cs> + * <ck>name</ck> = <cv>John Smith</cv> + * <ck>street</ck> = <cv>123 Main Street</cv> + * <ck>city</ck> = <cv>Anywhere</cv> + * <ck>state</ck> = <cv>NY</cv> + * <ck>zip</ck> = <cv>12345</cv> + * </p> + * + * <h6 class='figure'>Example bean</h6> + * <p class='bcode'> + * <jk>public class</jk> Address { + * public String name, street, city; + * public StateEnum state; + * public int zip; + * } + * </p> + * + * <h6 class='figure'>Example usage</h6> + * <p class='bcode'> + * ConfigFile cf = <jk>new</jk> ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>); + * Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>, Address.<jk>class</jk>); + * </p> + * + * @param sectionName The section name to write from. + * @param c The bean class to create. + * @param ignoreUnknownProperties + * If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property + * name. + * @return A new bean instance. + * @throws ParseException + */ + public final <T> T getSectionAsBean(String sectionName, Class<T> c, boolean ignoreUnknownProperties) + throws ParseException { + assertFieldNotNull(c, "c"); + readLock(); + try { + BeanMap<T> bm = getBeanSession().newBeanMap(c); + for (String k : getSectionKeys(sectionName)) { + BeanPropertyMeta bpm = bm.getPropertyMeta(k); + if (bpm == null) { + if (! ignoreUnknownProperties) + throw new ParseException("Unknown property {0} encountered", k); + } else { + bm.put(k, getObject(sectionName + '/' + k, bpm.getClassMeta().getInnerClass())); + } + } + return bm.getBean(); + } finally { + readUnlock(); + } + } + + /** + * Wraps a config file section inside a Java interface so that values in the section can be read and + * write using getters and setters. + * + * <h6 class='figure'>Example config file</h6> + * <p class='bcode'> + * <cs>[MySection]</cs> + * <ck>string</ck> = <cv>foo</cv> + * <ck>int</ck> = <cv>123</cv> + * <ck>enum</ck> = <cv>ONE</cv> + * <ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv> + * <ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv> + * <ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv> + * </p> + * + * <h6 class='figure'>Example interface</h6> + * <p class='bcode'> + * <jk>public interface</jk> MyConfigInterface { + * + * String getString(); + * <jk>void</jk> setString(String x); + * + * <jk>int</jk> getInt(); + * <jk>void</jk> setInt(<jk>int</jk> x); + * + * MyEnum getEnum(); + * <jk>void</jk> setEnum(MyEnum x); + * + * MyBean getBean(); + * <jk>void</jk> setBean(MyBean x); + * + * <jk>int</jk>[][][] getInt3dArray(); + * <jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x); + * + * Map<String,List<MyBean[][][]>> getBean1d3dListMap(); + * <jk>void</jk> setBean1d3dListMap(Map<String,List<MyBean[][][]>> x); + * } + * </p> + * + * <h6 class='figure'>Example usage</h6> + * <p class='bcode'> + * ConfigFile cf = <jk>new</jk> ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>); + * + * MyConfigInterface ci = cf.getSectionAsInterface(<js>"MySection"</js>, MyConfigInterface.<jk>class</jk>); + * + * <jk>int</jk> myInt = ci.getInt(); + * + * ci.setBean(<jk>new</jk> MyBean()); + * + * cf.save(); + * </p> + * + * @param sectionName The section name to retrieve as an interface proxy. + * @param c The proxy interface class. + * @return The proxy interface. + */ + @SuppressWarnings("unchecked") + public final <T> T getSectionAsInterface(final String sectionName, final Class<T> c) { + assertFieldNotNull(c, "c"); + + if (! c.isInterface()) + throw new UnsupportedOperationException("Class passed to getSectionAsInterface is not an interface."); + + InvocationHandler h = new InvocationHandler() { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + BeanInfo bi = Introspector.getBeanInfo(c, null); + for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { + Method rm = pd.getReadMethod(), wm = pd.getWriteMethod(); + if (method.equals(rm)) + return ConfigFile.this.getObject(sectionName, pd.getName(), rm.getGenericReturnType()); + if (method.equals(wm)) + return ConfigFile.this.put(sectionName, pd.getName(), args[0], null, false, false); + } + throw new UnsupportedOperationException("Unsupported interface method. method=[ " + method + " ]"); + } + }; + + return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, h); + } + + /** + * Returns <jk>true</jk> if this section contains the specified key and the key has a non-blank value. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @return <jk>true</jk> if this section contains the specified key and the key has a non-blank value. + */ + public final boolean containsNonEmptyValue(String key) { + return ! StringUtils.isEmpty(getString(key, null)); + } + + /** + * Gets the section with the specified name. + * + * @param name The section name. + * @return The section, or <jk>null</jk> if section does not exist. + */ + protected abstract Section getSection(String name); + + /** + * Gets the section with the specified name and optionally creates it if it's not there. + * + * @param name The section name. + * @param create Create the section if it's not there. + * @return The section, or <jk>null</jk> if section does not exist. + * @throws UnsupportedOperationException + * If config file is read only and section doesn't exist and <code>create</code> is <jk>true</jk>. + */ + protected abstract Section getSection(String name, boolean create); + + /** + * Appends a section to this config file if it does not already exist. + * + * <p> + * Returns the existing section if it already exists. + * + * @param name The section name, or <jk>null</jk> for the default section. + * @return The appended or existing section. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile addSection(String name); + + /** + * Creates or overwrites the specified section. + * + * @param name The section name, or <jk>null</jk> for the default section. + * @param contents The contents of the new section. + * @return The appended or existing section. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile setSection(String name, Map<String,String> contents); + + /** + * Removes the section with the specified name. + * + * @param name The name of the section to remove, or <jk>null</jk> for the default section. + * @return The removed section, or <jk>null</jk> if named section does not exist. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile removeSection(String name); + + /** + * Returns <jk>true</jk> if the encoding flag is set on the specified entry. + * + * @param key The key. See {@link #getString(String)} for a description of the key. + * @return <jk>true</jk> if the encoding flag is set on the specified entry. + */ + public abstract boolean isEncoded(String key); + + /** + * Saves this config file to disk. + * + * @return This object (for method chaining). + * @throws IOException If a problem occurred trying to save file to disk, or file is not associated with this object. + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile save() throws IOException; + + /** + * Saves this config file to the specified writer as an INI file. + * + * <p> + * The writer will automatically be closed. + * + * @param out The writer to send the output to. + * @return This object (for method chaining). + * @throws IOException If a problem occurred trying to send contents to the writer. + */ + public final ConfigFile serializeTo(Writer out) throws IOException { + return serializeTo(out, INI); + } + + /** + * Same as {@link #serializeTo(Writer)}, except allows you to explicitly specify a format. + * + * @param out The writer to send the output to. + * @param format The {@link ConfigFileFormat} of the output. + * @return This object (for method chaining). + * @throws IOException If a problem occurred trying to send contents to the writer. + */ + public abstract ConfigFile serializeTo(Writer out, ConfigFileFormat format) throws IOException; + + /** + * Add a listener to this config file to react to modification events. + * + * @param listener The new listener to add. + * @return This object (for method chaining). + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile addListener(ConfigFileListener listener); + + /** + * Merges the contents of the specified config file into this config file. + * + * <p> + * Pretty much identical to just replacing this config file, but causes the + * {@link ConfigFileListener#onChange(ConfigFile, Set)} method to be invoked on differences between the file. + * + * @param cf The config file whose values should be copied into this config file. + * @return This object (for method chaining). + * @throws UnsupportedOperationException If config file is read only. + */ + public abstract ConfigFile merge(ConfigFile cf); + + /** + * Returns the config file contents as a string. + * + * <p> + * The contents of the string are the same as the contents that would be serialized to disk. + */ + @Override /* Object */ + public abstract String toString(); + + /** + * Returns a wrapped instance of this config file where calls to getters have their values first resolved by the + * specified {@link VarResolver}. + * + * @param vr The {@link VarResolver} for resolving variables in values. + * @return This config file wrapped in an instance of {@link ConfigFileWrapped}. + */ + public abstract ConfigFile getResolving(VarResolver vr); + + /** + * Returns a wrapped instance of this config file where calls to getters have their values first resolved by the + * specified {@link VarResolverSession}. + * + * @param vs The {@link VarResolverSession} for resolving variables in values. + * @return This config file wrapped in an instance of {@link ConfigFileWrapped}. + */ + public abstract ConfigFile getResolving(VarResolverSession vs); + + /** + * Returns a wrapped instance of this config file where calls to getters have their values first resolved by a + * default {@link VarResolver}. + * + * The default {@link VarResolver} is registered with the following {@link Var StringVars}: + * <ul class='spaced-list'> + * <li> + * <code>$S{key}</code>,<code>$S{key,default}</code> - System properties. + * <li> + * <code>$E{key}</code>,<code>$E{key,default}</code> - Environment variables. + * <li> + * <code>$C{key}</code>,<code>$C{key,default}</code> - Values in this configuration file. + * </ul> + * + * @return A new config file that resolves string variables. + */ + public abstract ConfigFile getResolving(); + + /** + * Wraps this config file in a {@link Writable} interface that renders it as plain text. + * + * @return This config file wrapped in a {@link Writable}. + */ + public abstract Writable toWritable(); + + /** + * @return The string var resolver associated with this config file. + */ + protected VarResolver getVarResolver() { + // Only ConfigFileWrapped returns a value. + return null; + } + + private static int parseIntWithSuffix(String s) { + assertFieldNotNull(s, "s"); + int m = 1; + if (s.endsWith("M")) { + m = 1024*1024; + s = s.substring(0, s.length()-1).trim(); + } else if (s.endsWith("K")) { + m = 1024; + s = s.substring(0, s.length()-1).trim(); + } + return Integer.parseInt(s) * m; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/ab15d45b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java ---------------------------------------------------------------------- diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java new file mode 100644 index 0000000..63c6b38 --- /dev/null +++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/ini/ConfigFileBuilder.java @@ -0,0 +1,333 @@ +// *************************************************************************************************************************** +// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * +// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * +// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * +// * with the License. You may obtain a copy of the License at * +// * * +// * http://www.apache.org/licenses/LICENSE-2.0 * +// * * +// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * +// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * +// * specific language governing permissions and limitations under the License. * +// *************************************************************************************************************************** +package org.apache.juneau.ini; + +import static org.apache.juneau.ini.ConfigFileFormat.*; +import static org.apache.juneau.internal.FileUtils.*; + +import java.io.*; +import java.nio.charset.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.json.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.utils.*; + +/** + * Builder for creating instances of {@link ConfigFile ConfigFiles}. + * + * <h5 class='section'>Example:</h5> + * <p class='bcode'> + * ConfigFile cf = <jk>new</jk> ConfigFileBuilder().build(<js>"MyConfig.cfg"</js>); + * String setting = cf.get(<js>"MySection/mysetting"</js>); + * </p> + */ +@SuppressWarnings("hiding") +public class ConfigFileBuilder { + + private WriterSerializer serializer = JsonSerializer.DEFAULT_LAX; + private ReaderParser parser = JsonParser.DEFAULT; + private Encoder encoder = new XorEncoder(); + private boolean readOnly = false, createIfNotExists = false; + private Charset charset = Charset.defaultCharset(); + private List<File> searchPaths = new AList<File>().append(new File(".")); + + /** + * Specify the encoder to use for encoded config file entries (e.g. <js>"mySecret*={...}"</js>). + * + * <p> + * The default value for this setting is an instance of {@link XorEncoder}. + * + * @param encoder The new value for this setting. + * @return This object (for method chaining). + */ + public ConfigFileBuilder encoder(Encoder encoder) { + this.encoder = encoder; + return this; + } + + /** + * Specify the serializer to use for serializing POJOs when using {@link ConfigFile#put(String, Object)}. + * + * <p> + * The default value for this setting is {@link JsonSerializer#DEFAULT_LAX}. + * + * @param serializer The new value for this setting. + * @return This object (for method chaining). + */ + public ConfigFileBuilder serializer(WriterSerializer serializer) { + this.serializer = serializer; + return this; + } + + /** + * Specify the parser to use for parsing POJOs when using {@link ConfigFile#getObject(String,Class)}. + * + * <p> + * The default value for this setting is {@link JsonParser#DEFAULT} + * + * @param parser The new value for this setting. + * @return This object (for method chaining). + */ + public ConfigFileBuilder parser(ReaderParser parser) { + this.parser = parser; + return this; + } + + /** + * Specify the config file character encoding. + * + * <p> + * The default value for this setting is {@link Charset#defaultCharset()}. + * + * @param charset The new value for this setting. + * @return This object (for method chaining). + */ + public ConfigFileBuilder charset(Charset charset) { + this.charset = charset; + return this; + } + + /** + * Specify the search paths for config files. + * + * <p> + * Can contain relative or absolute paths. + * + * <p> + * The default value for this setting is <code>[<js>"."</js>]</code>. + * + * @param searchPaths The new value for this setting. + * @return This object (for method chaining). + */ + public ConfigFileBuilder paths(String...searchPaths) { + this.searchPaths = new LinkedList<File>(); + for (String p : searchPaths) + this.searchPaths.add(new File(p)); + return this; + } + + /** + * Make {@link ConfigFile ConfigFiles} read-only. + * + * <p> + * The default value of this setting is <jk>false</jk>. + * + * @return This object (for method chaining). + */ + public ConfigFileBuilder readOnly() { + this.readOnly = true; + return this; + } + + /** + * Create config files if they cannot be found on the file system. + * + * <p> + * The default value for this setting is <jk>false</jk>. + * + * @return This object (for method chaining). + */ + public ConfigFileBuilder createIfNotExists() { + this.createIfNotExists = true; + return this; + } + + /** + * Returns the config file with the specified absolute or relative path. + * + * @param path The absolute or relative path of the config file. + * @return The config file. + * @throws IOException If config file could not be parsed. + * @throws FileNotFoundException If config file could not be found. + */ + public ConfigFile build(String path) throws IOException { + return new ConfigFileImpl(resolve(path), readOnly, encoder, serializer, parser, charset); + } + + /** + * Create a new empty config file not backed by any file. + * + * @return A new config file. + * @throws IOException + */ + public ConfigFile build() throws IOException { + return new ConfigFileImpl(null, false, encoder, serializer, parser, charset); + } + + /** + * Create a new config file backed by the specified file. + * + * <p> + * This method is provided primarily for testing purposes. + * + * @param f The file to create a config file from. + * @return A new config file. + * @throws IOException + */ + public ConfigFile build(File f) throws IOException { + return new ConfigFileImpl(f, false, encoder, serializer, parser, charset); + } + + /** + * Create a new config file not backed by a file. + * + * @param r The reader containing an INI-formatted file to initialize the config file from. + * @return A new config file. + * @throws IOException + */ + public ConfigFile build(Reader r) throws IOException { + return new ConfigFileImpl(null, false, encoder, serializer, parser, charset).load(r); + } + + private File resolve(String path) throws IOException { + + // Handle absolute file. + File f = new File(path); + if (f.isAbsolute()) { + if (createIfNotExists) + create(f); + if (f.exists()) + return f; + throw new FileNotFoundException("Could not find config file '"+path+"'"); + } + + if (searchPaths.isEmpty()) + throw new FileNotFoundException("No search paths specified in ConfigFileBuilder."); + + // Handle paths relative to search paths. + for (File sf : searchPaths) { + f = new File(sf.getAbsolutePath() + "/" + path); + if (f.exists()) + return f; + } + + if (createIfNotExists) { + f = new File(searchPaths.get(0).getAbsolutePath() + "/" + path); + create(f); + return f; + } + + throw new FileNotFoundException("Could not find config file '"+path+"'"); + } + + /** + * Implements command-line features for working with INI configuration files. + * + * <p> + * Invoke as a normal Java program... + * <p class='bcode'> + * java org.apache.juneau.ini.ConfigFileBuilder [args] + * </p> + * + * <p> + * Arguments can be any of the following... + * <ul class='spaced-list'> + * <li> + * No arguments + * <br>Prints usage message. + * <li> + * <code>createBatchEnvFile -configfile <configFile> -envfile <batchFile> [-verbose]</code> + * <br>Creates a batch file that will set each config file entry as an environment variable. + * <br>Characters in the keys that are not valid as environment variable names (e.g. <js>'/'</js> and <js>'.'</js>) + * will be converted to underscores. + * <li> + * <code>createShellEnvFile -configFile <configFile> -envFile <configFile> [-verbose]</code> + * Creates a shell script that will set each config file entry as an environment variable. + * <br>Characters in the keys that are not valid as environment variable names (e.g. <js>'/'</js> and <js>'.'</js>) + * will be converted to underscores. + * <li> + * <code>setVals -configFile <configFile> -vals [var1=val1 [var2=val2...]] [-verbose]</code> + * Sets values in config files. + * </ul> + * + * <p> + * For example, the following command will create the file <code>'MyConfig.bat'</code> from the contents of the + * file <code>'MyConfig.cfg'</code>. + * <p class='bcode'> + * java org.apache.juneau.ini.ConfigFileBuilder createBatchEnvFile -configfile C:\foo\MyConfig.cfg + * -batchfile C:\foo\MyConfig.bat + * </p> + * + * @param args Command-line arguments + */ + public static void main(String[] args) { + + Args a = new Args(args); + String command = a.getArg(0); + String configFile = a.getArg("configFile"); + String envFile = a.getArg("envFile"); + List<String> vals = a.getArgs("vals"); + + if (command == null || ! (command.equals("createBatchEnvFile") || command.equals("createShellEnvFile") || command.equals("setVals"))) + printUsageAndExit(); + else if (configFile.isEmpty()) + printUsageAndExit(); + else if (command.equals("setVals") && vals.isEmpty()) + printUsageAndExit(); + else if ((command.equals("createBatchEnvFile") || command.equals("createShellEnvFile")) && envFile.isEmpty()) + printUsageAndExit(); + else { + try { + ConfigFile cf = new ConfigFileBuilder().build(configFile); + + if (command.equalsIgnoreCase("setVals")) { + for (String val : vals) { + String[] x = val.split("\\="); + if (x.length != 2) + throw new FormattedRuntimeException( + "Invalid format for value: ''{0}''. Must be in the format 'key=value'", + val + ); + cf.put(x[0], x[1]); + } + cf.save(); + return; + + } else if (command.equalsIgnoreCase("createBatchEnvFile")) { + Writer fw = new OutputStreamWriter(new FileOutputStream(envFile), Charset.defaultCharset()); + try { + cf.serializeTo(fw, BATCH); + } finally { + fw.close(); + } + return; + + } else if (command.equalsIgnoreCase("createShellEnvFile")) { + Writer fw = new OutputStreamWriter(new FileOutputStream(envFile), Charset.defaultCharset()); + try { + cf.serializeTo(fw, SHELL); + } finally { + fw.close(); + } + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private static void printUsageAndExit() { + System.err.println("---Usage---"); // NOT DEBUG + System.err.println("java -cp juneau.jar org.apache.juneau.ini.ConfigFile createBatchEnvFile -configFile <configFile> -envFile <envFile> [-verbose]"); // NOT DEBUG + System.err.println("java -cp juneau.jar org.apache.juneau.ini.ConfigFile createShellEnvFile -configFile <configFile> -envFile <envFile> [-verbose]"); // NOT DEBUG + System.err.println("java -cp juneau.jar org.apache.juneau.ini.ConfigFile setVals -configFile <configFile> -vals [var1 val1 [var2 val2...]] [-verbose]"); // NOT DEBUG + int rc = Integer.getInteger("exit.2", 2); + if (rc != 0) + System.exit(rc); + } +}
