    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}