aboutsummaryrefslogtreecommitdiffstats
path: root/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java
blob: d73bad5e4fb459d67eeafe0010bd885aed12af71 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2000-2016. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */
package com.ericsson.otp.erlang;

/**
 * Provides a Java representation of Erlang atoms. Atoms can be created from
 * strings whose length is not more than {@link #maxAtomLength maxAtomLength}
 * characters.
 */
public class OtpErlangAtom extends OtpErlangObject {
    // don't change this!
    private static final long serialVersionUID = -3204386396807876641L;

    /** The maximun allowed length of an atom, in characters */
    public static final int maxAtomLength = 0xff; // one byte length

    private final String atom;

    /**
     * Create an atom from the given string.
     *
     * @param atom
     *            the string to create the atom from.
     *
     * @exception java.lang.IllegalArgumentException
     *                if the string is null or contains more than
     *                {@link #maxAtomLength maxAtomLength} characters.
     */
    public OtpErlangAtom(final String atom) {
        if (atom == null) {
            throw new java.lang.IllegalArgumentException("null string value");
        }

        if (atom.codePointCount(0, atom.length()) > maxAtomLength) {
            throw new java.lang.IllegalArgumentException("Atom may not exceed "
                    + maxAtomLength + " characters: " + atom);
        }
        this.atom = atom;
    }

    /**
     * Create an atom from a stream containing an atom encoded in Erlang
     * external format.
     *
     * @param buf
     *            the stream containing the encoded atom.
     *
     * @exception OtpErlangDecodeException
     *                if the buffer does not contain a valid external
     *                representation of an Erlang atom.
     */
    public OtpErlangAtom(final OtpInputStream buf)
            throws OtpErlangDecodeException {
        atom = buf.read_atom();
    }

    /**
     * Create an atom whose value is "true" or "false".
     */
    public OtpErlangAtom(final boolean t) {
        atom = String.valueOf(t);
    }

    /**
     * Get the actual string contained in this object.
     *
     * @return the raw string contained in this object, without regard to Erlang
     *         quoting rules.
     *
     * @see #toString
     */
    public String atomValue() {
        return atom;
    }

    /**
     * The boolean value of this atom.
     *
     * @return the value of this atom expressed as a boolean value. If the atom
     *         consists of the characters "true" (independent of case) the value
     *         will be true. For any other values, the value will be false.
     *
     */
    public boolean booleanValue() {
        return Boolean.valueOf(atomValue()).booleanValue();
    }

    /**
     * Get the printname of the atom represented by this object. The difference
     * between this method and {link #atomValue atomValue()} is that the
     * printname is quoted and escaped where necessary, according to the Erlang
     * rules for atom naming.
     *
     * @return the printname representation of this atom object.
     *
     * @see #atomValue
     */
    @Override
    public String toString() {
        if (atomNeedsQuoting(atom)) {
            return "'" + escapeSpecialChars(atom) + "'";
        }
        return atom;
    }

    /**
     * Determine if two atoms are equal.
     *
     * @param o
     *            the other object to compare to.
     *
     * @return true if the atoms are equal, false otherwise.
     */
    @Override
    public boolean equals(final Object o) {

        if (!(o instanceof OtpErlangAtom)) {
            return false;
        }

        final OtpErlangAtom other = (OtpErlangAtom) o;
        return atom.compareTo(other.atom) == 0;
    }

    @Override
    protected int doHashCode() {
        return atom.hashCode();
    }

    /**
     * Convert this atom to the equivalent Erlang external representation.
     *
     * @param buf
     *            an output stream to which the encoded atom should be written.
     */
    @Override
    public void encode(final OtpOutputStream buf) {
        buf.write_atom(atom);
    }

    /* the following four predicates are helpers for the toString() method */
    private boolean isErlangDigit(final char c) {
        return c >= '0' && c <= '9';
    }

    private boolean isErlangUpper(final char c) {
        return c >= 'A' && c <= 'Z' || c == '_';
    }

    private boolean isErlangLower(final char c) {
        return c >= 'a' && c <= 'z';
    }

    private boolean isErlangLetter(final char c) {
        return isErlangLower(c) || isErlangUpper(c);
    }

    // true if the atom should be displayed with quotation marks
    private boolean atomNeedsQuoting(final String s) {
        char c;

        if (s.length() == 0) {
            return true;
        }
        if (!isErlangLower(s.charAt(0))) {
            return true;
        }

        final int len = s.length();
        for (int i = 1; i < len; i++) {
            c = s.charAt(i);

            if (!isErlangLetter(c) && !isErlangDigit(c) && c != '@') {
                return true;
            }
        }
        return false;
    }

    /*
     * Get the atom string, with special characters escaped. Note that this
     * function currently does not consider any characters above 127 to be
     * printable.
     */
    private String escapeSpecialChars(final String s) {
        char c;
        final StringBuffer so = new StringBuffer();

        final int len = s.length();
        for (int i = 0; i < len; i++) {
            c = s.charAt(i);

            /*
             * note that some of these escape sequences are unique to Erlang,
             * which is why the corresponding 'case' values use octal. The
             * resulting string is, of course, in Erlang format.
             */

            switch (c) {
            // some special escape sequences
            case '\b':
                so.append("\\b");
                break;

            case 0177:
                so.append("\\d");
                break;

            case 033:
                so.append("\\e");
                break;

            case '\f':
                so.append("\\f");
                break;

            case '\n':
                so.append("\\n");
                break;

            case '\r':
                so.append("\\r");
                break;

            case '\t':
                so.append("\\t");
                break;

            case 013:
                so.append("\\v");
                break;

            case '\\':
                so.append("\\\\");
                break;

            case '\'':
                so.append("\\'");
                break;

            case '\"':
                so.append("\\\"");
                break;

            default:
                // some other character classes
                if (c < 027) {
                    // control chars show as "\^@", "\^A" etc
                    so.append("\\^" + (char) ('A' - 1 + c));
                } else if (c > 126) {
                    // 8-bit chars show as \345 \344 \366 etc
                    so.append("\\" + Integer.toOctalString(c));
                } else {
                    // character is printable without modification!
                    so.append(c);
                }
            }
        }
        return new String(so);
    }

}