1 /* 2 * Copyright (c) 2014, 2019, 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 26 package com.sun.tools.sjavac.pubapi; 27 28 29 import static com.sun.tools.sjavac.Util.union; 30 31 import java.io.Serializable; 32 import java.util.ArrayList; 33 import java.util.Collection; 34 import java.util.Collections; 35 import java.util.Comparator; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Optional; 41 import java.util.Set; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 import java.util.stream.Collectors; 45 import java.util.stream.Stream; 46 47 import javax.lang.model.element.Modifier; 48 49 import com.sun.tools.javac.util.Assert; 50 import com.sun.tools.javac.util.StringUtils; 51 52 public class PubApi implements Serializable { 53 54 private static final long serialVersionUID = 5926627347801986850L; 55 56 // Used to have Set here. Problem is that the objects are mutated during 57 // javac_state loading, causing them to change hash codes. We could probably 58 // change back to Set once javac_state loading is cleaned up. 59 public final Map<String, PubType> types = new HashMap<>(); 60 public final Map<String, PubVar> variables = new HashMap<>(); 61 public final Map<String, PubMethod> methods = new HashMap<>(); 62 public final Map<String, PubVar> recordComponents = new HashMap<>(); 63 PubApi()64 public PubApi() { 65 } 66 PubApi(Collection<PubType> types, Collection<PubVar> variables, Collection<PubMethod> methods)67 public PubApi(Collection<PubType> types, 68 Collection<PubVar> variables, 69 Collection<PubMethod> methods) { 70 types.forEach(this::addPubType); 71 variables.forEach(this::addPubVar); 72 methods.forEach(this::addPubMethod); 73 } 74 75 // Currently this is implemented as equality. This is far from optimal. It 76 // should preferably make sure that all previous methods are still available 77 // and no abstract methods are added. It should also be aware of inheritance 78 // of course. isBackwardCompatibleWith(PubApi older)79 public boolean isBackwardCompatibleWith(PubApi older) { 80 return equals(older); 81 } 82 typeLine(PubType type)83 private static String typeLine(PubType type) { 84 if (type.fqName.isEmpty()) 85 throw new RuntimeException("empty class name " + type); 86 return String.format("TYPE %s%s", asString(type.modifiers), type.fqName); 87 } 88 varLine(PubVar var)89 private static String varLine(PubVar var) { 90 return String.format("VAR %s%s %s%s", 91 asString(var.modifiers), 92 TypeDesc.encodeAsString(var.type), 93 var.identifier, 94 var.getConstValue().map(v -> " = " + v).orElse("")); 95 } 96 methodLine(PubMethod method)97 private static String methodLine(PubMethod method) { 98 return String.format("METHOD %s%s%s %s(%s)%s", 99 asString(method.modifiers), 100 method.typeParams.isEmpty() ? "" : ("<" + method.typeParams.stream().map(PubApiTypeParam::asString).collect(Collectors.joining(",")) + "> "), 101 TypeDesc.encodeAsString(method.returnType), 102 method.identifier, 103 commaSeparated(method.paramTypes), 104 method.throwDecls.isEmpty() 105 ? "" 106 : " throws " + commaSeparated(method.throwDecls)); 107 } 108 asListOfStrings()109 public List<String> asListOfStrings() { 110 List<String> lines = new ArrayList<>(); 111 112 // Types 113 types.values() 114 .stream() 115 .sorted(Comparator.comparing(PubApi::typeLine)) 116 .forEach(type -> { 117 lines.add(typeLine(type)); 118 for (String subline : type.pubApi.asListOfStrings()) 119 lines.add(" " + subline); 120 }); 121 122 // Variables 123 variables.values() 124 .stream() 125 .map(PubApi::varLine) 126 .sorted() 127 .forEach(lines::add); 128 129 // Methods 130 methods.values() 131 .stream() 132 .map(PubApi::methodLine) 133 .sorted() 134 .forEach(lines::add); 135 136 return lines; 137 } 138 139 @Override equals(Object obj)140 public boolean equals(Object obj) { 141 if (getClass() != obj.getClass()) 142 return false; 143 PubApi other = (PubApi) obj; 144 return types.equals(other.types) 145 && variables.equals(other.variables) 146 && methods.equals(other.methods); 147 } 148 149 @Override hashCode()150 public int hashCode() { 151 return types.keySet().hashCode() 152 ^ variables.keySet().hashCode() 153 ^ methods.keySet().hashCode(); 154 } 155 commaSeparated(List<TypeDesc> typeDescs)156 private static String commaSeparated(List<TypeDesc> typeDescs) { 157 return typeDescs.stream() 158 .map(TypeDesc::encodeAsString) 159 .collect(Collectors.joining(",")); 160 } 161 162 // Create space separated list of modifiers (with a trailing space) asString(Set<Modifier> modifiers)163 private static String asString(Set<Modifier> modifiers) { 164 return modifiers.stream() 165 .map(mod -> mod + " ") 166 .sorted() 167 .collect(Collectors.joining()); 168 } 169 170 // Used to combine class PubApis to package level PubApis mergeTypes(PubApi api1, PubApi api2)171 public static PubApi mergeTypes(PubApi api1, PubApi api2) { 172 Assert.check(api1.methods.isEmpty(), "Can only merge types."); 173 Assert.check(api2.methods.isEmpty(), "Can only merge types."); 174 Assert.check(api1.variables.isEmpty(), "Can only merge types."); 175 Assert.check(api2.variables.isEmpty(), "Can only merge types."); 176 PubApi merged = new PubApi(); 177 merged.types.putAll(api1.types); 178 merged.types.putAll(api2.types); 179 return merged; 180 } 181 182 183 // Used for line-by-line parsing 184 private PubType lastInsertedType = null; 185 186 private final static String MODIFIERS = Stream.of(Modifier.values()) 187 .map(Modifier::name) 188 .map(StringUtils::toLowerCase) 189 .collect(Collectors.joining("|", "(", ")")); 190 191 private final static Pattern MOD_PATTERN = Pattern.compile("(" + MODIFIERS + " )*"); 192 private final static Pattern METHOD_PATTERN = Pattern.compile("(?<ret>.+?) (?<name>\\S+)\\((?<params>.*)\\)( throws (?<throws>.*))?"); 193 private final static Pattern VAR_PATTERN = Pattern.compile("VAR (?<modifiers>("+MODIFIERS+" )*)(?<type>.+?) (?<id>\\S+)( = (?<val>.*))?"); 194 private final static Pattern TYPE_PATTERN = Pattern.compile("TYPE (?<modifiers>("+MODIFIERS+" )*)(?<fullyQualified>\\S+)"); 195 appendItem(String l)196 public void appendItem(String l) { 197 try { 198 if (l.startsWith(" ")) { 199 lastInsertedType.pubApi.appendItem(l.substring(2)); 200 return; 201 } 202 203 if (l.startsWith("METHOD")) { 204 l = l.substring("METHOD ".length()); 205 Set<Modifier> modifiers = new HashSet<>(); 206 Matcher modMatcher = MOD_PATTERN.matcher(l); 207 if (modMatcher.find()) { 208 String modifiersStr = modMatcher.group(); 209 modifiers.addAll(parseModifiers(modifiersStr)); 210 l = l.substring(modifiersStr.length()); 211 } 212 List<PubApiTypeParam> typeParams = new ArrayList<>(); 213 if (l.startsWith("<")) { 214 int closingPos = findClosingTag(l, 0); 215 String str = l.substring(1, closingPos); 216 l = l.substring(closingPos+1); 217 typeParams.addAll(parseTypeParams(splitOnTopLevelCommas(str))); 218 } 219 Matcher mm = METHOD_PATTERN.matcher(l); 220 if (!mm.matches()) 221 throw new AssertionError("Could not parse return type, identifier, parameter types or throws declaration of method: " + l); 222 223 List<String> params = splitOnTopLevelCommas(mm.group("params")); 224 String th = Optional.ofNullable(mm.group("throws")).orElse(""); 225 List<String> throwz = splitOnTopLevelCommas(th); 226 PubMethod m = new PubMethod(modifiers, 227 typeParams, 228 TypeDesc.decodeString(mm.group("ret")), 229 mm.group("name"), 230 parseTypeDescs(params), 231 parseTypeDescs(throwz)); 232 addPubMethod(m); 233 return; 234 } 235 236 Matcher vm = VAR_PATTERN.matcher(l); 237 if (vm.matches()) { 238 addPubVar(new PubVar(parseModifiers(vm.group("modifiers")), 239 TypeDesc.decodeString(vm.group("type")), 240 vm.group("id"), 241 vm.group("val"))); 242 return; 243 } 244 245 Matcher tm = TYPE_PATTERN.matcher(l); 246 if (tm.matches()) { 247 addPubType(new PubType(parseModifiers(tm.group("modifiers")), 248 tm.group("fullyQualified"), 249 new PubApi())); 250 return; 251 } 252 253 throw new AssertionError("No matching line pattern."); 254 } catch (Throwable e) { 255 throw new AssertionError("Could not parse API line: " + l, e); 256 } 257 } 258 addPubType(PubType t)259 public void addPubType(PubType t) { 260 types.put(t.fqName, t); 261 lastInsertedType = t; 262 } 263 addPubVar(PubVar v)264 public void addPubVar(PubVar v) { 265 variables.put(v.identifier, v); 266 } 267 addPubMethod(PubMethod m)268 public void addPubMethod(PubMethod m) { 269 methods.put(m.asSignatureString(), m); 270 } 271 parseTypeDescs(List<String> strs)272 private static List<TypeDesc> parseTypeDescs(List<String> strs) { 273 return strs.stream() 274 .map(TypeDesc::decodeString) 275 .collect(Collectors.toList()); 276 } 277 parseTypeParams(List<String> strs)278 private static List<PubApiTypeParam> parseTypeParams(List<String> strs) { 279 return strs.stream().map(PubApi::parseTypeParam).collect(Collectors.toList()); 280 } 281 282 // Parse a type parameter string. Example input: 283 // identifier 284 // identifier extends Type (& Type)* parseTypeParam(String typeParamString)285 private static PubApiTypeParam parseTypeParam(String typeParamString) { 286 int extPos = typeParamString.indexOf(" extends "); 287 if (extPos == -1) 288 return new PubApiTypeParam(typeParamString, Collections.emptyList()); 289 String identifier = typeParamString.substring(0, extPos); 290 String rest = typeParamString.substring(extPos + " extends ".length()); 291 List<TypeDesc> bounds = parseTypeDescs(splitOnTopLevelChars(rest, '&')); 292 return new PubApiTypeParam(identifier, bounds); 293 } 294 parseModifiers(String modifiers)295 public Set<Modifier> parseModifiers(String modifiers) { 296 if (modifiers == null) 297 return Collections.emptySet(); 298 return Stream.of(modifiers.split(" ")) 299 .map(String::trim) 300 .map(StringUtils::toUpperCase) 301 .filter(s -> !s.isEmpty()) 302 .map(Modifier::valueOf) 303 .collect(Collectors.toSet()); 304 } 305 306 // Find closing tag of the opening tag at the given 'pos'. findClosingTag(String l, int pos)307 private static int findClosingTag(String l, int pos) { 308 while (true) { 309 pos = pos + 1; 310 if (l.charAt(pos) == '>') 311 return pos; 312 if (l.charAt(pos) == '<') 313 pos = findClosingTag(l, pos); 314 } 315 } 316 splitOnTopLevelCommas(String s)317 public List<String> splitOnTopLevelCommas(String s) { 318 return splitOnTopLevelChars(s, ','); 319 } 320 splitOnTopLevelChars(String s, char split)321 public static List<String> splitOnTopLevelChars(String s, char split) { 322 if (s.isEmpty()) 323 return Collections.emptyList(); 324 List<String> result = new ArrayList<>(); 325 StringBuilder buf = new StringBuilder(); 326 int depth = 0; 327 for (char c : s.toCharArray()) { 328 if (c == split && depth == 0) { 329 result.add(buf.toString().trim()); 330 buf = new StringBuilder(); 331 } else { 332 if (c == '<') depth++; 333 if (c == '>') depth--; 334 buf.append(c); 335 } 336 } 337 result.add(buf.toString().trim()); 338 return result; 339 } 340 isEmpty()341 public boolean isEmpty() { 342 return types.isEmpty() && variables.isEmpty() && methods.isEmpty(); 343 } 344 345 // Used for descriptive debug messages when figuring out what triggers 346 // recompilation. diff(PubApi prevApi)347 public List<String> diff(PubApi prevApi) { 348 return diff("", prevApi); 349 } diff(String scopePrefix, PubApi prevApi)350 private List<String> diff(String scopePrefix, PubApi prevApi) { 351 352 List<String> diffs = new ArrayList<>(); 353 354 for (String typeKey : union(types.keySet(), prevApi.types.keySet())) { 355 PubType type = types.get(typeKey); 356 PubType prevType = prevApi.types.get(typeKey); 357 if (prevType == null) { 358 diffs.add("Type " + scopePrefix + typeKey + " was added"); 359 } else if (type == null) { 360 diffs.add("Type " + scopePrefix + typeKey + " was removed"); 361 } else { 362 // Check modifiers 363 if (!type.modifiers.equals(prevType.modifiers)) { 364 diffs.add("Modifiers for type " + scopePrefix + typeKey 365 + " changed from " + prevType.modifiers + " to " 366 + type.modifiers); 367 } 368 369 // Recursively check types pub API 370 diffs.addAll(type.pubApi.diff(prevType.pubApi)); 371 } 372 } 373 374 for (String varKey : union(variables.keySet(), prevApi.variables.keySet())) { 375 PubVar var = variables.get(varKey); 376 PubVar prevVar = prevApi.variables.get(varKey); 377 if (prevVar == null) { 378 diffs.add("Variable " + scopePrefix + varKey + " was added"); 379 } else if (var == null) { 380 diffs.add("Variable " + scopePrefix + varKey + " was removed"); 381 } else { 382 if (!var.modifiers.equals(prevVar.modifiers)) { 383 diffs.add("Modifiers for var " + scopePrefix + varKey 384 + " changed from " + prevVar.modifiers + " to " 385 + var.modifiers); 386 } 387 if (!var.type.equals(prevVar.type)) { 388 diffs.add("Type of " + scopePrefix + varKey 389 + " changed from " + prevVar.type + " to " 390 + var.type); 391 } 392 if (!var.getConstValue().equals(prevVar.getConstValue())) { 393 diffs.add("Const value of " + scopePrefix + varKey 394 + " changed from " + prevVar.getConstValue().orElse("<none>") 395 + " to " + var.getConstValue().orElse("<none>")); 396 } 397 } 398 } 399 400 for (String methodKey : union(methods.keySet(), prevApi.methods.keySet())) { 401 PubMethod method = methods.get(methodKey); 402 PubMethod prevMethod = prevApi.methods.get(methodKey); 403 if (prevMethod == null) { 404 diffs.add("Method " + scopePrefix + methodKey + " was added"); 405 } else if (method == null) { 406 diffs.add("Method " + scopePrefix + methodKey + " was removed"); 407 } else { 408 if (!method.modifiers.equals(prevMethod.modifiers)) { 409 diffs.add("Modifiers for method " + scopePrefix + methodKey 410 + " changed from " + prevMethod.modifiers + " to " 411 + method.modifiers); 412 } 413 if (!method.typeParams.equals(prevMethod.typeParams)) { 414 diffs.add("Type parameters for method " + scopePrefix 415 + methodKey + " changed from " + prevMethod.typeParams 416 + " to " + method.typeParams); 417 } 418 if (!method.throwDecls.equals(prevMethod.throwDecls)) { 419 diffs.add("Throw decl for method " + scopePrefix + methodKey 420 + " changed from " + prevMethod.throwDecls + " to " 421 + " to " + method.throwDecls); 422 } 423 } 424 } 425 426 return diffs; 427 } 428 toString()429 public String toString() { 430 return String.format("%s[types: %s, variables: %s, methods: %s]", 431 getClass().getSimpleName(), 432 types.values(), 433 variables.values(), 434 methods.values()); 435 } 436 } 437