1
\$\begingroup\$

Background

A subclass of java.util.Properties attempts to recursively resolve configured properties:

prop_a=Prop A is ${prop_b} 
prop_b=Prop B is ${prop_c} 
prop_c=Prop C.

After reading the file, prop_a should be Prop A is Prop B is Prop C.

Property Reference Semantics

Nest properties are not allowed, making the following invalid:

${property_name_a ${property_name_b}}

A valid property is:

name=Text for ${property_a}, followed by ${property_b}.

Source Code

Code that handles parsing:

/**
 * This will throw an IllegalArgumentException if there are mismatched
 * braces.
 *
 * @param s - The string with zero or more ${} references to resolve.
 * @return The value of the string, with all substituted values made.
 */
public String resolve( String s ) {
  StringBuffer sb = new StringBuffer( 256 );
  Stack<StringBuffer> stack = new Stack<StringBuffer>();
  int len = s.length();

  for( int i = 0; i < len; i++ ) {
    char c = s.charAt( i );

    switch( c ) {
      case '$': {
        if( i + 1 < len && s.charAt( i + 1 ) == '{' ) {
          stack.push( sb );
          sb = new StringBuffer( 256 );
          i++;
        }
        break;
      }

      case '}': {
        if( stack.isEmpty() ) {
          throw new IllegalArgumentException( "unexpected '}'" );
        }

        String name = sb.toString();

        sb = stack.pop();
        sb.append( super.getProperty( name, null ) );
        break;
      }

      default: {
        sb.append( c );
        break;
      }
    }
  }

  if( !stack.isEmpty() ) {
    throw new IllegalArgumentException( "missing '}'" );
  }

  return sb.toString();
}

Question

How would you simplify the code and detect/avoid infinite recursion?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
  • You say "recursively resolve", but in fact resolve is not recursive. It returns Prop A is Prop B is ${prop_c} instead of Prop A is Prop B is Prop C, if you call it with Prop A is ${prop_b}. If you call it with prop_a, as advertised, it returns prop_a.

  • You allow nested ${} in you syntax. What is your semantics of it?

  • Do you have any test cases for this. The bug and Unclear semantics of nested parens for instance, strongly indicate you should have.

  • Since you access the super class through its public interface, namely the the singe call to the super.getProperty you could instead pass the properties as a parameter. It will improve usability. Then you can make the static, if you wish. Since the method does not refer to any instance members otherwise.

  • StringBuffer and Stack have built in synchronization, whose overhead you can avoid by using StringBuilder and ArrayList.

Here is the test code to save others some effort:

import java.util.Properties;
import java.util.Stack;


public class Main {
    public static String resolve(Properties props,  String s ) {
          StringBuffer sb = new StringBuffer( 256 );
          Stack<StringBuffer> stack = new Stack<StringBuffer>();
          int len = s.length();

          for( int i = 0; i < len; i++ ) {
            char c = s.charAt( i );

            switch( c ) {
              case '$': {
                if( i + 1 < len && s.charAt( i + 1 ) == '{' ) {
                  stack.push( sb );
                  sb = new StringBuffer( 256 );
                  i++;
                }
                break;
              }

              case '}': {
                if( stack.isEmpty() ) {
                  throw new IllegalArgumentException( "unexpected '}'" );
                }

                String name = sb.toString();

                sb = stack.pop();
                sb.append( props.get(name) );
                break;
              }

              default: {
                sb.append( c );
                break;
              }
            }
          }

          if( !stack.isEmpty() ) {
            throw new IllegalArgumentException( "missing '}'" );
          }

          return sb.toString();
        }

    public static void main(String[] args) {
        Properties props = new Properties();

        props.setProperty("prop_a", "Prop A is ${prop_b}"); 
        props.setProperty("prop_b", "Prop B is ${prop_c}"); 
        props.setProperty("prop_c", "Prop C.");

        System.out.println(resolve(props, "Prop A is ${prop_b}"));
        System.out.println(resolve(props, "prop_a"));
        System.out.println(resolve(props, "${prop_a}"));
        System.out.println(resolve(props, "${${prop_b}}"));
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ A bug exists in both of our implementations: ${prop_a} {0} should resolve successfully. \$\endgroup\$ Dec 1, 2016 at 6:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.