001/** 002 * Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com> 003 */ 004package com.typesafe.config.impl; 005 006import java.io.DataOutputStream; 007import java.io.File; 008import java.io.IOException; 009import java.io.ObjectInputStream; 010import java.io.ObjectOutputStream; 011import java.net.URISyntaxException; 012import java.net.URL; 013import java.util.ArrayList; 014import java.util.List; 015 016import com.typesafe.config.ConfigException; 017import com.typesafe.config.ConfigOrigin; 018import com.typesafe.config.ConfigSyntax; 019 020/** 021 * Internal implementation detail, not ABI stable, do not touch. 022 * For use only by the {@link com.typesafe.config} package. 023 */ 024final public class ConfigImplUtil { 025 static boolean equalsHandlingNull(Object a, Object b) { 026 if (a == null && b != null) 027 return false; 028 else if (a != null && b == null) 029 return false; 030 else if (a == b) // catches null == null plus optimizes identity case 031 return true; 032 else 033 return a.equals(b); 034 } 035 036 static boolean isC0Control(int codepoint) { 037 return (codepoint >= 0x0000 && codepoint <= 0x001F); 038 } 039 040 public static String renderJsonString(String s) { 041 StringBuilder sb = new StringBuilder(); 042 sb.append('"'); 043 for (int i = 0; i < s.length(); ++i) { 044 char c = s.charAt(i); 045 switch (c) { 046 case '"': 047 sb.append("\\\""); 048 break; 049 case '\\': 050 sb.append("\\\\"); 051 break; 052 case '\n': 053 sb.append("\\n"); 054 break; 055 case '\b': 056 sb.append("\\b"); 057 break; 058 case '\f': 059 sb.append("\\f"); 060 break; 061 case '\r': 062 sb.append("\\r"); 063 break; 064 case '\t': 065 sb.append("\\t"); 066 break; 067 default: 068 if (isC0Control(c)) 069 sb.append(String.format("\\u%04x", (int) c)); 070 else 071 sb.append(c); 072 } 073 } 074 sb.append('"'); 075 return sb.toString(); 076 } 077 078 static String renderStringUnquotedIfPossible(String s) { 079 // this can quote unnecessarily as long as it never fails to quote when 080 // necessary 081 if (s.length() == 0) 082 return renderJsonString(s); 083 084 // if it starts with a hyphen or number, we have to quote 085 // to ensure we end up with a string and not a number 086 int first = s.codePointAt(0); 087 if (Character.isDigit(first) || first == '-') 088 return renderJsonString(s); 089 090 if (s.startsWith("include") || s.startsWith("true") || s.startsWith("false") 091 || s.startsWith("null") || s.contains("//")) 092 return renderJsonString(s); 093 094 // only unquote if it's pure alphanumeric 095 for (int i = 0; i < s.length(); ++i) { 096 char c = s.charAt(i); 097 if (!(Character.isLetter(c) || Character.isDigit(c) || c == '-')) 098 return renderJsonString(s); 099 } 100 101 return s; 102 } 103 104 static boolean isWhitespace(int codepoint) { 105 switch (codepoint) { 106 // try to hit the most common ASCII ones first, then the nonbreaking 107 // spaces that Java brokenly leaves out of isWhitespace. 108 case ' ': 109 case '\n': 110 case '\u00A0': 111 case '\u2007': 112 case '\u202F': 113 // this one is the BOM, see 114 // http://www.unicode.org/faq/utf_bom.html#BOM 115 // we just accept it as a zero-width nonbreaking space. 116 case '\uFEFF': 117 return true; 118 default: 119 return Character.isWhitespace(codepoint); 120 } 121 } 122 123 public static String unicodeTrim(String s) { 124 // this is dumb because it looks like there aren't any whitespace 125 // characters that need surrogate encoding. But, points for 126 // pedantic correctness! It's future-proof or something. 127 // String.trim() actually is broken, since there are plenty of 128 // non-ASCII whitespace characters. 129 final int length = s.length(); 130 if (length == 0) 131 return s; 132 133 int start = 0; 134 while (start < length) { 135 char c = s.charAt(start); 136 if (c == ' ' || c == '\n') { 137 start += 1; 138 } else { 139 int cp = s.codePointAt(start); 140 if (isWhitespace(cp)) 141 start += Character.charCount(cp); 142 else 143 break; 144 } 145 } 146 147 int end = length; 148 while (end > start) { 149 char c = s.charAt(end - 1); 150 if (c == ' ' || c == '\n') { 151 --end; 152 } else { 153 int cp; 154 int delta; 155 if (Character.isLowSurrogate(c)) { 156 cp = s.codePointAt(end - 2); 157 delta = 2; 158 } else { 159 cp = s.codePointAt(end - 1); 160 delta = 1; 161 } 162 if (isWhitespace(cp)) 163 end -= delta; 164 else 165 break; 166 } 167 } 168 return s.substring(start, end); 169 } 170 171 172 public static ConfigException extractInitializerError(ExceptionInInitializerError e) { 173 Throwable cause = e.getCause(); 174 if (cause != null && cause instanceof ConfigException) { 175 return (ConfigException) cause; 176 } else { 177 throw e; 178 } 179 } 180 181 static File urlToFile(URL url) { 182 // this isn't really right, clearly, but not sure what to do. 183 try { 184 // this will properly handle hex escapes, etc. 185 return new File(url.toURI()); 186 } catch (URISyntaxException e) { 187 // this handles some stuff like file:///c:/Whatever/ 188 // apparently but mangles handling of hex escapes 189 return new File(url.getPath()); 190 } catch (IllegalArgumentException e) { 191 // file://foo with double slash causes 192 // IllegalArgumentException "url has an authority component" 193 return new File(url.getPath()); 194 } 195 } 196 197 public static String joinPath(String... elements) { 198 return (new Path(elements)).render(); 199 } 200 201 public static String joinPath(List<String> elements) { 202 return joinPath(elements.toArray(new String[0])); 203 } 204 205 public static List<String> splitPath(String path) { 206 Path p = Path.newPath(path); 207 List<String> elements = new ArrayList<String>(); 208 while (p != null) { 209 elements.add(p.first()); 210 p = p.remainder(); 211 } 212 return elements; 213 } 214 215 public static ConfigOrigin readOrigin(ObjectInputStream in) throws IOException { 216 return SerializedConfigValue.readOrigin(in, null); 217 } 218 219 public static void writeOrigin(ObjectOutputStream out, ConfigOrigin origin) throws IOException { 220 SerializedConfigValue.writeOrigin(new DataOutputStream(out), (SimpleConfigOrigin) origin, 221 null); 222 } 223 224 static String toCamelCase(String originalName) { 225 String[] words = originalName.split("-+"); 226 StringBuilder nameBuilder = new StringBuilder(originalName.length()); 227 for (String word : words) { 228 if (nameBuilder.length() == 0) { 229 nameBuilder.append(word); 230 } else { 231 nameBuilder.append(word.substring(0, 1).toUpperCase()); 232 nameBuilder.append(word.substring(1)); 233 } 234 } 235 return nameBuilder.toString(); 236 } 237 238 private static char underscoreMappings(int num) { 239 // Rationale on name mangling: 240 // 241 // Most shells (e.g. bash, sh, etc.) doesn't support any character other 242 // than alphanumeric and `_` in environment variables names. 243 // In HOCON the default separator is `.` so it is directly translated to a 244 // single `_` for convenience; `-` and `_` are less often present in config 245 // keys but they have to be representable and the only possible mapping is 246 // `_` repeated. 247 switch (num) { 248 case 1: return '.'; 249 case 2: return '-'; 250 case 3: return '_'; 251 default: return 0; 252 } 253 } 254 255 static String envVariableAsProperty(String variable, String prefix) throws ConfigException { 256 StringBuilder builder = new StringBuilder(); 257 258 String strippedPrefix = variable.substring(prefix.length(), variable.length()); 259 260 int underscores = 0; 261 for (char c : strippedPrefix.toCharArray()) { 262 if (c == '_') { 263 underscores++; 264 } else { 265 if (underscores > 0 && underscores < 4) { 266 builder.append(underscoreMappings(underscores)); 267 } else if (underscores > 3) { 268 throw new ConfigException.BadPath(variable, "Environment variable contains an un-mapped number of underscores."); 269 } 270 underscores = 0; 271 builder.append(c); 272 } 273 } 274 275 if (underscores > 0 && underscores < 4) { 276 builder.append(underscoreMappings(underscores)); 277 } else if (underscores > 3) { 278 throw new ConfigException.BadPath(variable, "Environment variable contains an un-mapped number of underscores."); 279 } 280 281 return builder.toString(); 282 } 283 284 /** 285 * Guess configuration syntax from given filename. 286 * 287 * @param filename configuration filename 288 * @return configuration syntax if a match is found. Otherwise, null. 289 */ 290 public static ConfigSyntax syntaxFromExtension(String filename) { 291 if (filename == null) 292 return null; 293 else if (filename.endsWith(".json")) 294 return ConfigSyntax.JSON; 295 else if (filename.endsWith(".conf")) 296 return ConfigSyntax.CONF; 297 else if (filename.endsWith(".properties")) 298 return ConfigSyntax.PROPERTIES; 299 else 300 return null; 301 } 302}