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; 018 019/** 020 * Internal implementation detail, not ABI stable, do not touch. 021 * For use only by the {@link com.typesafe.config} package. 022 */ 023final public class ConfigImplUtil { 024 static boolean equalsHandlingNull(Object a, Object b) { 025 if (a == null && b != null) 026 return false; 027 else if (a != null && b == null) 028 return false; 029 else if (a == b) // catches null == null plus optimizes identity case 030 return true; 031 else 032 return a.equals(b); 033 } 034 035 static boolean isC0Control(int codepoint) { 036 return (codepoint >= 0x0000 && codepoint <= 0x001F); 037 } 038 039 public static String renderJsonString(String s) { 040 StringBuilder sb = new StringBuilder(); 041 sb.append('"'); 042 for (int i = 0; i < s.length(); ++i) { 043 char c = s.charAt(i); 044 switch (c) { 045 case '"': 046 sb.append("\\\""); 047 break; 048 case '\\': 049 sb.append("\\\\"); 050 break; 051 case '\n': 052 sb.append("\\n"); 053 break; 054 case '\b': 055 sb.append("\\b"); 056 break; 057 case '\f': 058 sb.append("\\f"); 059 break; 060 case '\r': 061 sb.append("\\r"); 062 break; 063 case '\t': 064 sb.append("\\t"); 065 break; 066 default: 067 if (isC0Control(c)) 068 sb.append(String.format("\\u%04x", (int) c)); 069 else 070 sb.append(c); 071 } 072 } 073 sb.append('"'); 074 return sb.toString(); 075 } 076 077 static String renderStringUnquotedIfPossible(String s) { 078 // this can quote unnecessarily as long as it never fails to quote when 079 // necessary 080 if (s.length() == 0) 081 return renderJsonString(s); 082 083 // if it starts with a hyphen or number, we have to quote 084 // to ensure we end up with a string and not a number 085 int first = s.codePointAt(0); 086 if (Character.isDigit(first) || first == '-') 087 return renderJsonString(s); 088 089 if (s.startsWith("include") || s.startsWith("true") || s.startsWith("false") 090 || s.startsWith("null") || s.contains("//")) 091 return renderJsonString(s); 092 093 // only unquote if it's pure alphanumeric 094 for (int i = 0; i < s.length(); ++i) { 095 char c = s.charAt(i); 096 if (!(Character.isLetter(c) || Character.isDigit(c) || c == '-')) 097 return renderJsonString(s); 098 } 099 100 return s; 101 } 102 103 static boolean isWhitespace(int codepoint) { 104 switch (codepoint) { 105 // try to hit the most common ASCII ones first, then the nonbreaking 106 // spaces that Java brokenly leaves out of isWhitespace. 107 case ' ': 108 case '\n': 109 case '\u00A0': 110 case '\u2007': 111 case '\u202F': 112 // this one is the BOM, see 113 // http://www.unicode.org/faq/utf_bom.html#BOM 114 // we just accept it as a zero-width nonbreaking space. 115 case '\uFEFF': 116 return true; 117 default: 118 return Character.isWhitespace(codepoint); 119 } 120 } 121 122 public static String unicodeTrim(String s) { 123 // this is dumb because it looks like there aren't any whitespace 124 // characters that need surrogate encoding. But, points for 125 // pedantic correctness! It's future-proof or something. 126 // String.trim() actually is broken, since there are plenty of 127 // non-ASCII whitespace characters. 128 final int length = s.length(); 129 if (length == 0) 130 return s; 131 132 int start = 0; 133 while (start < length) { 134 char c = s.charAt(start); 135 if (c == ' ' || c == '\n') { 136 start += 1; 137 } else { 138 int cp = s.codePointAt(start); 139 if (isWhitespace(cp)) 140 start += Character.charCount(cp); 141 else 142 break; 143 } 144 } 145 146 int end = length; 147 while (end > start) { 148 char c = s.charAt(end - 1); 149 if (c == ' ' || c == '\n') { 150 --end; 151 } else { 152 int cp; 153 int delta; 154 if (Character.isLowSurrogate(c)) { 155 cp = s.codePointAt(end - 2); 156 delta = 2; 157 } else { 158 cp = s.codePointAt(end - 1); 159 delta = 1; 160 } 161 if (isWhitespace(cp)) 162 end -= delta; 163 else 164 break; 165 } 166 } 167 return s.substring(start, end); 168 } 169 170 171 public static ConfigException extractInitializerError(ExceptionInInitializerError e) { 172 Throwable cause = e.getCause(); 173 if (cause != null && cause instanceof ConfigException) { 174 return (ConfigException) cause; 175 } else { 176 throw e; 177 } 178 } 179 180 static File urlToFile(URL url) { 181 // this isn't really right, clearly, but not sure what to do. 182 try { 183 // this will properly handle hex escapes, etc. 184 return new File(url.toURI()); 185 } catch (URISyntaxException e) { 186 // this handles some stuff like file:///c:/Whatever/ 187 // apparently but mangles handling of hex escapes 188 return new File(url.getPath()); 189 } catch (IllegalArgumentException e) { 190 // file://foo with double slash causes 191 // IllegalArgumentException "url has an authority component" 192 return new File(url.getPath()); 193 } 194 } 195 196 public static String joinPath(String... elements) { 197 return (new Path(elements)).render(); 198 } 199 200 public static String joinPath(List<String> elements) { 201 return joinPath(elements.toArray(new String[0])); 202 } 203 204 public static List<String> splitPath(String path) { 205 Path p = Path.newPath(path); 206 List<String> elements = new ArrayList<String>(); 207 while (p != null) { 208 elements.add(p.first()); 209 p = p.remainder(); 210 } 211 return elements; 212 } 213 214 public static ConfigOrigin readOrigin(ObjectInputStream in) throws IOException { 215 return SerializedConfigValue.readOrigin(in, null); 216 } 217 218 public static void writeOrigin(ObjectOutputStream out, ConfigOrigin origin) throws IOException { 219 SerializedConfigValue.writeOrigin(new DataOutputStream(out), (SimpleConfigOrigin) origin, 220 null); 221 } 222 223 static String toCamelCase(String originalName) { 224 String[] words = originalName.split("-+"); 225 StringBuilder nameBuilder = new StringBuilder(originalName.length()); 226 for (String word : words) { 227 if (nameBuilder.length() == 0) { 228 nameBuilder.append(word); 229 } else { 230 nameBuilder.append(word.substring(0, 1).toUpperCase()); 231 nameBuilder.append(word.substring(1)); 232 } 233 } 234 return nameBuilder.toString(); 235 } 236}