MaskCommentsAndModifiers.java revision 3062:15bdc18525ff
1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package jdk.jshell;
26
27import java.util.Set;
28import java.util.stream.Collectors;
29import java.util.stream.Stream;
30
31/**
32 * Within a String, mask code comments and ignored modifiers (within context).
33 *
34 * @author Robert Field
35 */
36class MaskCommentsAndModifiers {
37
38    private final static Set<String> IGNORED_MODIFERS =
39            Stream.of( "public", "protected", "private", "static", "final" )
40                    .collect( Collectors.toSet() );
41
42    private final StringBuilder sbCleared = new StringBuilder();
43    private final StringBuilder sbMask = new StringBuilder();
44    private final String str;
45    private final int length;
46    private final boolean maskModifiers;
47    private int next = 0;
48    private boolean wasMasked = false;
49    private boolean inside = false;
50
51    @SuppressWarnings("empty-statement")
52    public MaskCommentsAndModifiers(String s, boolean maskModifiers) {
53        this.str = s;
54        this.length = s.length();
55        this.maskModifiers = maskModifiers;
56        do { } while (next());
57    }
58
59    public String cleared() {
60        return sbCleared.toString();
61    }
62
63    public String mask() {
64        return sbMask.toString();
65    }
66
67    public boolean wasMasked() {
68        return wasMasked;
69    }
70
71    /**
72     * Read the next character
73     */
74    private int read() {
75        if (next >= length) {
76            return -1;
77        }
78        return str.charAt(next++);
79    }
80
81    private void write(StringBuilder sb, int ch) {
82        sb.append((char)ch);
83    }
84
85    private void write(int ch) {
86        write(sbCleared, ch);
87        write(sbMask, Character.isWhitespace(ch) ? ch : ' ');
88    }
89
90    private void writeMask(int ch) {
91        wasMasked = true;
92        write(sbMask, ch);
93        write(sbCleared, Character.isWhitespace(ch) ? ch : ' ');
94    }
95
96    private void write(CharSequence s) {
97        for (int cp : s.chars().toArray()) {
98            write(cp);
99        }
100    }
101
102    private void writeMask(CharSequence s) {
103        for (int cp : s.chars().toArray()) {
104            writeMask(cp);
105        }
106    }
107
108    private boolean next() {
109        return next(read());
110    }
111
112    private boolean next(int c) {
113        if (c < 0) {
114            return false;
115        }
116
117        if (c == '\'' || c == '"') {
118            inside = true;
119            write(c);
120            int match = c;
121            c = read();
122            while (c != match) {
123                if (c < 0) {
124                    return false;
125                }
126                if (c == '\n' || c == '\r') {
127                    write(c);
128                    return true;
129                }
130                if (c == '\\') {
131                    write(c);
132                    c = read();
133                }
134                write(c);
135                c = read();
136            }
137            write(c);
138            return true;
139        }
140
141        if (c == '/') {
142            c = read();
143            if (c == '*') {
144                writeMask('/');
145                writeMask(c);
146                int prevc = 0;
147                while ((c = read()) != '/' || prevc != '*') {
148                    if (c < 0) {
149                        return false;
150                    }
151                    writeMask(c);
152                    prevc = c;
153                }
154                writeMask(c);
155                return true;
156            } else if (c == '/') {
157                writeMask('/');
158                writeMask(c);
159                while ((c = read()) != '\n' && c != '\r') {
160                    if (c < 0) {
161                        return false;
162                    }
163                    writeMask(c);
164                }
165                writeMask(c);
166                return true;
167            } else {
168                inside = true;
169                write('/');
170                // read character falls through
171            }
172        }
173
174        if (Character.isJavaIdentifierStart(c)) {
175            if (maskModifiers && !inside) {
176                StringBuilder sb = new StringBuilder();
177                do {
178                    write(sb, c);
179                    c = read();
180                } while (Character.isJavaIdentifierPart(c));
181                String id = sb.toString();
182                if (IGNORED_MODIFERS.contains(id)) {
183                    writeMask(sb);
184                } else {
185                    write(sb);
186                    if (id.equals("import")) {
187                        inside = true;
188                    }
189                }
190                return next(c); // recurse to handle left-over character
191            }
192        } else if (!Character.isWhitespace(c)) {
193            inside = true;
194        }
195
196        if (c < 0) {
197            return false;
198        }
199        write(c);
200        return true;
201    }
202}
203