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}