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}