001 /* 002 @license.text@ 003 */ 004 package biz.hammurapi.config; 005 006 import java.util.HashSet; 007 import java.util.Set; 008 import java.util.StringTokenizer; 009 010 /** 011 * Expands property value entries like ${myProperty} to property values. 012 * Nested properties are supportd, i.e. if myProperty value contains ${someOtherPropery} 013 * then it will also be expanded. ${ escaping is not supported. 014 * @author Pavel Vlasov 015 * @version $Revision: 1.3 $ 016 */ 017 public class PropertyParser { 018 public static final String TOKEN_CLOSING_CHAR = "}"; 019 public static final String TOKEN_SECOND_OPENING_CHAR = "{"; 020 public static final String TOKEN_FIRST_OPENING_CHAR = "$"; 021 022 private String tokenClosingChar = TOKEN_CLOSING_CHAR; 023 private String tokenSecondOpeningChar = TOKEN_SECOND_OPENING_CHAR; 024 private String tokenFirstOpeningChar = TOKEN_FIRST_OPENING_CHAR; 025 026 private Context context; 027 private boolean useNameAsDefault; 028 029 /** 030 * Creates a new instance of PropertyParser with system 031 * properties. 032 * @param useNameAsDefault If true then property name will be used 033 * as property default value. 034 */ 035 public PropertyParser(boolean useNameAsDefault) { 036 context=new Context() { 037 public Object get(String name) { 038 return System.getProperty(name); 039 } 040 }; 041 042 this.useNameAsDefault=useNameAsDefault; 043 } 044 045 /** 046 * Creates a new instance of PropertyParser 047 * @param properties Properties 048 * @param useNameAsDefault If true then property name will be used 049 * as property default value. 050 */ 051 public PropertyParser(Context context, boolean useNameAsDefault) { 052 if (context==null) { 053 throw new NullPointerException("Context is null"); 054 } 055 this.context=context; 056 this.useNameAsDefault=useNameAsDefault; 057 } 058 059 /** 060 * Creates a new instance of PropertyParser with system 061 * properties. 062 * @param useNameAsDefault If true then property name will be used 063 * as property default value. 064 */ 065 public PropertyParser(boolean useNameAsDefault, char tokenFirstOpeningChar, char tokenSecondOpeningChar, char tokenClosingChar) { 066 context=new Context() { 067 public Object get(String name) { 068 return System.getProperty(name); 069 } 070 }; 071 072 this.useNameAsDefault=useNameAsDefault; 073 this.tokenClosingChar=String.valueOf(tokenClosingChar); 074 this.tokenFirstOpeningChar=String.valueOf(tokenFirstOpeningChar); 075 this.tokenSecondOpeningChar=String.valueOf(tokenSecondOpeningChar); 076 } 077 078 /** 079 * Creates a new instance of PropertyParser 080 * @param properties Properties 081 * @param useNameAsDefault If true then property name will be used 082 * as property default value. 083 */ 084 public PropertyParser(Context context, boolean useNameAsDefault, char tokenFirstOpeningChar, char tokenSecondOpeningChar, char tokenClosingChar) { 085 if (context==null) { 086 throw new NullPointerException("Context is null"); 087 } 088 this.context=context; 089 this.useNameAsDefault=useNameAsDefault; 090 this.tokenClosingChar=String.valueOf(tokenClosingChar); 091 this.tokenFirstOpeningChar=String.valueOf(tokenFirstOpeningChar); 092 this.tokenSecondOpeningChar=String.valueOf(tokenSecondOpeningChar); 093 } 094 095 096 /** 097 * Property parsing. 098 * Replaces string ${<property name>} with property value. 099 * If property value contains ${<other property name>} it 100 * will be parsed. 101 */ 102 public String getParsedProperty(String key) { 103 return getParsedProperty(key, new HashSet()); 104 } 105 106 /** 107 * Parses a string by replacing occurences of ${<property name>} 108 * with property values. 109 * If property value contains ${<other property name>} it 110 * will be parsed. 111 */ 112 public String parse(String str) { 113 return parse(str, null); 114 } 115 116 private String getParsedProperty(String key, Set stack) { 117 Object o=context.get(key); 118 String pr; 119 if (o==null) { 120 if (useNameAsDefault) { 121 pr=key; 122 } else { 123 return null; 124 } 125 } else { 126 pr=o.toString(); 127 } 128 129 if (stack.contains(key)) { 130 throw new RuntimeConfigurationException("Circular reference in property substitution, property: "+key); 131 } 132 133 stack.add(key); 134 135 return parse(pr, stack); 136 } 137 138 private String parse(String str, Set stack) { 139 if (str==null) { 140 return null; 141 } 142 143 StringTokenizer st=new StringTokenizer(str,tokenFirstOpeningChar+tokenSecondOpeningChar+tokenClosingChar,true); 144 145 StringBuffer ret=new StringBuffer(); 146 /** 147 * Parser state: 148 * 0: Text 149 * 1: $ 150 * 2: ${ 151 */ 152 int state=0; 153 154 String propName=null; 155 while (st.hasMoreTokens()) { 156 String tkn=st.nextToken(); 157 switch (state) { 158 case 0: 159 if (tokenFirstOpeningChar.equals(tkn)) 160 state=1; 161 else 162 ret.append(tkn); 163 break; 164 case 1: 165 if (tokenSecondOpeningChar.equals(tkn)) 166 state=2; 167 else { 168 state=0; 169 ret.append(tokenFirstOpeningChar); 170 ret.append(tkn); 171 } 172 break; 173 case 2: 174 if (tokenClosingChar.equals(tkn)) { 175 String propVal=getParsedProperty(propName, stack==null ? new HashSet() : stack); 176 ret.append(propVal==null ? tokenFirstOpeningChar+tokenSecondOpeningChar+propName+tokenClosingChar : propVal); 177 propName=null; 178 state=0; 179 } else { 180 if (propName==null) 181 propName=tkn; 182 else 183 propName+=tkn; 184 } 185 break; 186 default: 187 throw new IllegalStateException("Illegal parser state: "+state); 188 } 189 } 190 191 if (state==2) 192 ret.append(tokenFirstOpeningChar+tokenSecondOpeningChar+propName); 193 194 return ret.toString(); 195 } 196 }