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 */
25
26package jdk.jshell;
27
28import java.util.Set;
29import java.util.stream.Collectors;
30import java.util.stream.Stream;
31
32/**
33 * Within a String, mask code comments and ignored modifiers (within context).
34 *
35 * @author Robert Field
36 */
37class MaskCommentsAndModifiers {
38
39    private final static Set<String> IGNORED_MODIFIERS =
40            Stream.of( "public", "protected", "private", "static", "final" )
41                    .collect( Collectors.toSet() );
42
43    private final static Set<String> OTHER_MODIFIERS =
44            Stream.of( "abstract", "strictfp", "transient", "volatile", "synchronized", "native", "default" )
45                    .collect( Collectors.toSet() );
46
47    // Builder to accumulate non-masked characters
48    private final StringBuilder sbCleared = new StringBuilder();
49
50    // Builder to accumulate masked characters
51    private final StringBuilder sbMask = new StringBuilder();
52
53    // The input string
54    private final String str;
55
56    // Entire input string length
57    private final int length;
58
59    // The next character position
60    private int next = 0;
61
62    // The current character
63    private int c;
64
65    // Do we mask-off ignored modifiers?  Set by parameter and turned off after
66    // initial modifier section
67    private boolean maskModifiers;
68
69    // Does the string end with an unclosed '/*' style comment?
70    private boolean openComment = false;
71
72    MaskCommentsAndModifiers(String s, boolean maskModifiers) {
73        this.str = s;
74        this.length = s.length();
75        this.maskModifiers = maskModifiers;
76        read();
77        while (c >= 0) {
78            next();
79            read();
80        }
81    }
82
83    String cleared() {
84        return sbCleared.toString();
85    }
86
87    String mask() {
88        return sbMask.toString();
89    }
90
91    boolean endsWithOpenComment() {
92        return openComment;
93    }
94
95    /****** private implementation methods ******/
96
97    /**
98     * Read the next character
99     */
100    private int read() {
101        return c = (next >= length)
102                ? -1
103                : str.charAt(next++);
104    }
105
106    private void unread() {
107        if (c >= 0) {
108            --next;
109        }
110    }
111
112    private void writeTo(StringBuilder sb, int ch) {
113        sb.append((char)ch);
114    }
115
116    private void write(int ch) {
117        if (ch != -1) {
118            writeTo(sbCleared, ch);
119            writeTo(sbMask, Character.isWhitespace(ch) ? ch : ' ');
120        }
121    }
122
123    private void writeMask(int ch) {
124        if (ch != -1) {
125            writeTo(sbMask, ch);
126            writeTo(sbCleared, Character.isWhitespace(ch) ? ch : ' ');
127        }
128    }
129
130    private void write(CharSequence s) {
131        for (int cp : s.chars().toArray()) {
132            write(cp);
133        }
134    }
135
136    private void writeMask(CharSequence s) {
137        for (int cp : s.chars().toArray()) {
138            writeMask(cp);
139        }
140    }
141
142    private void next() {
143        switch (c) {
144            case '\'':
145            case '"':
146                maskModifiers = false;
147                write(c);
148                int match = c;
149                while (read() >= 0 && c != match && c != '\n' && c != '\r') {
150                    write(c);
151                    if (c == '\\') {
152                        write(read());
153                    }
154                }
155                write(c); // write match // line-end
156                break;
157            case '/':
158                read();
159                switch (c) {
160                    case '*':
161                        writeMask('/');
162                        writeMask(c);
163                        int prevc = 0;
164                        while (read() >= 0 && (c != '/' || prevc != '*')) {
165                            writeMask(c);
166                            prevc = c;
167                        }
168                        writeMask(c);
169                        openComment = c < 0;
170                        break;
171                    case '/':
172                        writeMask('/');
173                        writeMask(c);
174                        while (read() >= 0 && c != '\n' && c != '\r') {
175                            writeMask(c);
176                        }
177                        writeMask(c);
178                        break;
179                    default:
180                        maskModifiers = false;
181                        write('/');
182                        unread();
183                        break;
184                }
185                break;
186            case '@':
187                do {
188                    write(c);
189                    read();
190                } while (Character.isJavaIdentifierPart(c));
191                while (Character.isWhitespace(c)) {
192                    write(c);
193                    read();
194                }
195                // if this is an annotation with arguments, process those recursively
196                if (c == '(') {
197                    write(c);
198                    boolean prevMaskModifiers = maskModifiers;
199                    int parenCnt = 1;
200                    while (read() >= 0) {
201                        if (c == ')') {
202                            if (--parenCnt == 0) {
203                                break;
204                            }
205                        } else if (c == '(') {
206                            ++parenCnt;
207                        }
208                        next(); // recurse to handle quotes and comments
209                    }
210                    write(c);
211                    // stuff in annotation arguments doesn't effect inside determination
212                    maskModifiers = prevMaskModifiers;
213                } else {
214                    unread();
215                }
216                break;
217            default:
218                if (Character.isJavaIdentifierStart(c)) {
219                    StringBuilder sb = new StringBuilder();
220                    do {
221                        writeTo(sb, c);
222                        read();
223                    } while (Character.isJavaIdentifierPart(c));
224                    unread();
225                    String id = sb.toString();
226                    if (maskModifiers && IGNORED_MODIFIERS.contains(id)) {
227                        writeMask(sb);
228                    } else {
229                        write(sb);
230                        if (maskModifiers && !OTHER_MODIFIERS.contains(id)) {
231                            maskModifiers = false;
232                        }
233                    }
234                } else {
235                    if (!Character.isWhitespace(c)) {
236                        maskModifiers = false;
237                    }
238                    write(c);
239                }
240                break;
241        }
242    }
243}
244