    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}