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