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