1 /* 2 * Copyright (c) 2017, 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 package build.tools.depend; 26 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.lang.annotation.Documented; 34 import java.nio.charset.Charset; 35 import java.security.MessageDigest; 36 import java.security.NoSuchAlgorithmException; 37 import java.util.Arrays; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Set; 44 import java.util.stream.Collectors; 45 46 import javax.lang.model.element.AnnotationMirror; 47 import javax.lang.model.element.AnnotationValue; 48 import javax.lang.model.element.AnnotationValueVisitor; 49 import javax.lang.model.element.Element; 50 import javax.lang.model.element.ElementVisitor; 51 import javax.lang.model.element.ExecutableElement; 52 import javax.lang.model.element.Modifier; 53 import javax.lang.model.element.ModuleElement; 54 import javax.lang.model.element.ModuleElement.DirectiveVisitor; 55 import javax.lang.model.element.ModuleElement.ExportsDirective; 56 import javax.lang.model.element.ModuleElement.OpensDirective; 57 import javax.lang.model.element.ModuleElement.ProvidesDirective; 58 import javax.lang.model.element.ModuleElement.RequiresDirective; 59 import javax.lang.model.element.ModuleElement.UsesDirective; 60 import javax.lang.model.element.PackageElement; 61 import javax.lang.model.element.QualifiedNameable; 62 import javax.lang.model.element.TypeElement; 63 import javax.lang.model.element.TypeParameterElement; 64 import javax.lang.model.element.VariableElement; 65 import javax.lang.model.type.ArrayType; 66 import javax.lang.model.type.DeclaredType; 67 import javax.lang.model.type.ErrorType; 68 import javax.lang.model.type.ExecutableType; 69 import javax.lang.model.type.IntersectionType; 70 import javax.lang.model.type.NoType; 71 import javax.lang.model.type.NullType; 72 import javax.lang.model.type.PrimitiveType; 73 import javax.lang.model.type.TypeMirror; 74 import javax.lang.model.type.TypeVariable; 75 import javax.lang.model.type.TypeVisitor; 76 import javax.lang.model.type.UnionType; 77 import javax.lang.model.type.WildcardType; 78 import javax.lang.model.util.ElementFilter; 79 import javax.tools.JavaFileObject; 80 81 import com.sun.source.util.JavacTask; 82 import com.sun.source.util.Plugin; 83 import com.sun.source.util.TaskEvent; 84 import com.sun.source.util.TaskEvent.Kind; 85 import com.sun.source.util.TaskListener; 86 import com.sun.source.util.TreePath; 87 import com.sun.source.util.Trees; 88 import javax.lang.model.element.ElementKind; 89 90 public class Depend implements Plugin { 91 92 @Override getName()93 public String getName() { 94 return "depend"; 95 } 96 97 @Override init(JavacTask jt, String... args)98 public void init(JavacTask jt, String... args) { 99 jt.addTaskListener(new TaskListener() { 100 private final Map<ModuleElement, Set<PackageElement>> apiPackages = new HashMap<>(); 101 private final MessageDigest apiHash; 102 { 103 try { 104 apiHash = MessageDigest.getInstance("MD5"); 105 } catch (NoSuchAlgorithmException ex) { 106 throw new IllegalStateException(ex); 107 } 108 } 109 @Override 110 public void started(TaskEvent te) { 111 } 112 113 @Override 114 public void finished(TaskEvent te) { 115 if (te.getKind() == Kind.ANALYZE) { 116 if (te.getSourceFile().isNameCompatible("module-info", JavaFileObject.Kind.SOURCE)) { 117 ModuleElement mod = (ModuleElement) Trees.instance(jt).getElement(new TreePath(te.getCompilationUnit())); 118 new APIVisitor(apiHash).visit(mod); 119 } else if (te.getSourceFile().isNameCompatible("package-info", JavaFileObject.Kind.SOURCE)) { 120 //ignore - cannot contain important changes (?) 121 } else { 122 TypeElement clazz = te.getTypeElement(); 123 ModuleElement mod = jt.getElements().getModuleOf(clazz); 124 Set<PackageElement> thisModulePackages = apiPackages.computeIfAbsent(mod, m -> { 125 return ElementFilter.exportsIn(mod.getDirectives()) 126 .stream() 127 .map(ed -> ed.getPackage()) 128 .collect(Collectors.toSet()); 129 }); 130 if (thisModulePackages.contains(jt.getElements().getPackageOf(clazz))) { 131 new APIVisitor(apiHash).visit(clazz); 132 } 133 } 134 } 135 if (te.getKind() == Kind.COMPILATION) { 136 String previousSignature = null; 137 File digestFile = new File(args[0]); 138 try (InputStream in = new FileInputStream(digestFile)) { 139 previousSignature = new String(in.readAllBytes(), "UTF-8"); 140 } catch (IOException ex) { 141 //ignore 142 } 143 String currentSignature = Depend.this.toString(apiHash.digest()); 144 if (!Objects.equals(previousSignature, currentSignature)) { 145 digestFile.getParentFile().mkdirs(); 146 try (OutputStream out = new FileOutputStream(digestFile)) { 147 out.write(currentSignature.getBytes("UTF-8")); 148 } catch (IOException ex) { 149 throw new IllegalStateException(ex); 150 } 151 } 152 } 153 } 154 }); 155 } 156 toString(byte[] digest)157 private String toString(byte[] digest) { 158 StringBuilder result = new StringBuilder(); 159 160 for (byte b : digest) { 161 result.append(String.format("%X", b)); 162 } 163 164 return result.toString(); 165 } 166 167 private static final class APIVisitor implements ElementVisitor<Void, Void>, 168 TypeVisitor<Void, Void>, 169 AnnotationValueVisitor<Void, Void>, 170 DirectiveVisitor<Void, Void> { 171 172 private final MessageDigest apiHash; 173 private final Charset utf8; 174 APIVisitor(MessageDigest apiHash)175 public APIVisitor(MessageDigest apiHash) { 176 this.apiHash = apiHash; 177 utf8 = Charset.forName("UTF-8"); 178 } 179 visit(Iterable<? extends Element> list, Void p)180 public Void visit(Iterable<? extends Element> list, Void p) { 181 list.forEach(e -> visit(e, p)); 182 return null; 183 } 184 update(CharSequence data)185 private void update(CharSequence data) { 186 apiHash.update(data.toString().getBytes(utf8)); 187 } 188 visit(Iterable<? extends TypeMirror> types)189 private void visit(Iterable<? extends TypeMirror> types) { 190 for (TypeMirror type : types) { 191 visit(type); 192 } 193 } 194 updateAnnotation(AnnotationMirror am)195 private void updateAnnotation(AnnotationMirror am) { 196 update("@"); 197 visit(am.getAnnotationType()); 198 am.getElementValues() 199 .keySet() 200 .stream() 201 .sorted((ee1, ee2) -> ee1.getSimpleName().toString().compareTo(ee2.getSimpleName().toString())) 202 .forEach(ee -> { 203 visit(ee); 204 visit(am.getElementValues().get(ee)); 205 }); 206 } 207 updateAnnotations(Iterable<? extends AnnotationMirror> annotations)208 private void updateAnnotations(Iterable<? extends AnnotationMirror> annotations) { 209 for (AnnotationMirror am : annotations) { 210 if (am.getAnnotationType().asElement().getAnnotation(Documented.class) == null) 211 continue; 212 updateAnnotation(am); 213 } 214 } 215 216 @Override visit(Element e, Void p)217 public Void visit(Element e, Void p) { 218 if (e.getKind() != ElementKind.MODULE && 219 !e.getModifiers().contains(Modifier.PUBLIC) && 220 !e.getModifiers().contains(Modifier.PROTECTED)) { 221 return null; 222 } 223 update(e.getKind().name()); 224 update(e.getModifiers().stream() 225 .map(mod -> mod.name()) 226 .collect(Collectors.joining(",", "[", "]"))); 227 update(e.getSimpleName()); 228 updateAnnotations(e.getAnnotationMirrors()); 229 return e.accept(this, p); 230 } 231 232 @Override visit(Element e)233 public Void visit(Element e) { 234 return visit(e, null); 235 } 236 237 @Override visitModule(ModuleElement e, Void p)238 public Void visitModule(ModuleElement e, Void p) { 239 update(String.valueOf(e.isOpen())); 240 update(e.getQualifiedName()); 241 e.getDirectives() 242 .stream() 243 .forEach(d -> d.accept(this, null)); 244 return null; 245 } 246 247 @Override visitPackage(PackageElement e, Void p)248 public Void visitPackage(PackageElement e, Void p) { 249 throw new UnsupportedOperationException("Should not get here."); 250 } 251 252 @Override visitType(TypeElement e, Void p)253 public Void visitType(TypeElement e, Void p) { 254 visit(e.getTypeParameters(), p); 255 visit(e.getSuperclass()); 256 visit(e.getInterfaces()); 257 visit(e.getEnclosedElements(), p); 258 return null; 259 } 260 261 @Override visitVariable(VariableElement e, Void p)262 public Void visitVariable(VariableElement e, Void p) { 263 visit(e.asType()); 264 update(String.valueOf(e.getConstantValue())); 265 return null; 266 } 267 268 @Override visitExecutable(ExecutableElement e, Void p)269 public Void visitExecutable(ExecutableElement e, Void p) { 270 update("<"); 271 visit(e.getTypeParameters(), p); 272 update(">"); 273 visit(e.getReturnType()); 274 update("("); 275 visit(e.getParameters(), p); 276 update(")"); 277 visit(e.getThrownTypes()); 278 update(String.valueOf(e.getDefaultValue())); 279 update(String.valueOf(e.isVarArgs())); 280 return null; 281 } 282 283 @Override visitTypeParameter(TypeParameterElement e, Void p)284 public Void visitTypeParameter(TypeParameterElement e, Void p) { 285 visit(e.getBounds()); 286 return null; 287 } 288 289 @Override visitUnknown(Element e, Void p)290 public Void visitUnknown(Element e, Void p) { 291 throw new UnsupportedOperationException("Not supported."); 292 } 293 294 @Override visit(TypeMirror t, Void p)295 public Void visit(TypeMirror t, Void p) { 296 if (t == null) { 297 update("null"); 298 return null; 299 } 300 update(t.getKind().name()); 301 updateAnnotations(t.getAnnotationMirrors()); 302 t.accept(this, p); 303 return null; 304 } 305 306 @Override visitPrimitive(PrimitiveType t, Void p)307 public Void visitPrimitive(PrimitiveType t, Void p) { 308 return null; //done 309 } 310 311 @Override visitNull(NullType t, Void p)312 public Void visitNull(NullType t, Void p) { 313 return null; //done 314 } 315 316 @Override visitArray(ArrayType t, Void p)317 public Void visitArray(ArrayType t, Void p) { 318 visit(t.getComponentType()); 319 update("[]"); 320 return null; 321 } 322 323 @Override visitDeclared(DeclaredType t, Void p)324 public Void visitDeclared(DeclaredType t, Void p) { 325 update(((QualifiedNameable) t.asElement()).getQualifiedName()); 326 update("<"); 327 visit(t.getTypeArguments()); 328 update(">"); 329 return null; 330 } 331 332 @Override visitError(ErrorType t, Void p)333 public Void visitError(ErrorType t, Void p) { 334 return visitDeclared(t, p); 335 } 336 337 private final Set<Element> seenVariables = new HashSet<>(); 338 339 @Override visitTypeVariable(TypeVariable t, Void p)340 public Void visitTypeVariable(TypeVariable t, Void p) { 341 Element decl = t.asElement(); 342 if (!seenVariables.add(decl)) { 343 return null; 344 } 345 visit(decl, null); 346 visit(t.getLowerBound(), null); 347 visit(t.getUpperBound(), null); 348 seenVariables.remove(decl); 349 return null; 350 } 351 352 @Override visitWildcard(WildcardType t, Void p)353 public Void visitWildcard(WildcardType t, Void p) { 354 visit(t.getSuperBound()); 355 visit(t.getExtendsBound()); 356 return null; 357 } 358 359 @Override visitExecutable(ExecutableType t, Void p)360 public Void visitExecutable(ExecutableType t, Void p) { 361 throw new UnsupportedOperationException("Not supported."); 362 } 363 364 @Override visitNoType(NoType t, Void p)365 public Void visitNoType(NoType t, Void p) { 366 return null;//done 367 } 368 369 @Override visitUnknown(TypeMirror t, Void p)370 public Void visitUnknown(TypeMirror t, Void p) { 371 throw new UnsupportedOperationException("Not supported."); 372 } 373 374 @Override visitUnion(UnionType t, Void p)375 public Void visitUnion(UnionType t, Void p) { 376 update("("); 377 visit(t.getAlternatives()); 378 update(")"); 379 return null; 380 } 381 382 @Override visitIntersection(IntersectionType t, Void p)383 public Void visitIntersection(IntersectionType t, Void p) { 384 update("("); 385 visit(t.getBounds()); 386 update(")"); 387 return null; 388 } 389 390 @Override visit(AnnotationValue av, Void p)391 public Void visit(AnnotationValue av, Void p) { 392 return av.accept(this, p); 393 } 394 395 @Override visitBoolean(boolean b, Void p)396 public Void visitBoolean(boolean b, Void p) { 397 update(String.valueOf(b)); 398 return null; 399 } 400 401 @Override visitByte(byte b, Void p)402 public Void visitByte(byte b, Void p) { 403 update(String.valueOf(b)); 404 return null; 405 } 406 407 @Override visitChar(char c, Void p)408 public Void visitChar(char c, Void p) { 409 update(String.valueOf(c)); 410 return null; 411 } 412 413 @Override visitDouble(double d, Void p)414 public Void visitDouble(double d, Void p) { 415 update(String.valueOf(d)); 416 return null; 417 } 418 419 @Override visitFloat(float f, Void p)420 public Void visitFloat(float f, Void p) { 421 update(String.valueOf(f)); 422 return null; 423 } 424 425 @Override visitInt(int i, Void p)426 public Void visitInt(int i, Void p) { 427 update(String.valueOf(i)); 428 return null; 429 } 430 431 @Override visitLong(long i, Void p)432 public Void visitLong(long i, Void p) { 433 update(String.valueOf(i)); 434 return null; 435 } 436 437 @Override visitShort(short s, Void p)438 public Void visitShort(short s, Void p) { 439 update(String.valueOf(s)); 440 return null; 441 } 442 443 @Override visitString(String s, Void p)444 public Void visitString(String s, Void p) { 445 update(s); 446 return null; 447 } 448 449 @Override visitType(TypeMirror t, Void p)450 public Void visitType(TypeMirror t, Void p) { 451 return visit(t); 452 } 453 454 @Override visitEnumConstant(VariableElement c, Void p)455 public Void visitEnumConstant(VariableElement c, Void p) { 456 return visit(c); 457 } 458 459 @Override visitAnnotation(AnnotationMirror a, Void p)460 public Void visitAnnotation(AnnotationMirror a, Void p) { 461 updateAnnotation(a); 462 return null; 463 } 464 465 @Override visitArray(List<? extends AnnotationValue> vals, Void p)466 public Void visitArray(List<? extends AnnotationValue> vals, Void p) { 467 update("("); 468 for (AnnotationValue av : vals) { 469 visit(av); 470 } 471 update(")"); 472 return null; 473 } 474 475 @Override visitUnknown(AnnotationValue av, Void p)476 public Void visitUnknown(AnnotationValue av, Void p) { 477 throw new UnsupportedOperationException("Not supported."); 478 } 479 480 @Override visitRequires(RequiresDirective d, Void p)481 public Void visitRequires(RequiresDirective d, Void p) { 482 update("RequiresDirective"); 483 update(String.valueOf(d.isStatic())); 484 update(String.valueOf(d.isTransitive())); 485 update(d.getDependency().getQualifiedName()); 486 return null; 487 } 488 489 @Override visitExports(ExportsDirective d, Void p)490 public Void visitExports(ExportsDirective d, Void p) { 491 update("ExportsDirective"); 492 update(d.getPackage().getQualifiedName()); 493 if (d.getTargetModules() != null) { 494 for (ModuleElement me : d.getTargetModules()) { 495 update(me.getQualifiedName()); 496 } 497 } else { 498 update("<none>"); 499 } 500 return null; 501 } 502 503 @Override visitOpens(OpensDirective d, Void p)504 public Void visitOpens(OpensDirective d, Void p) { 505 update("OpensDirective"); 506 update(d.getPackage().getQualifiedName()); 507 if (d.getTargetModules() != null) { 508 for (ModuleElement me : d.getTargetModules()) { 509 update(me.getQualifiedName()); 510 } 511 } else { 512 update("<none>"); 513 } 514 return null; 515 } 516 517 @Override visitUses(UsesDirective d, Void p)518 public Void visitUses(UsesDirective d, Void p) { 519 update("UsesDirective"); 520 update(d.getService().getQualifiedName()); 521 return null; 522 } 523 524 @Override visitProvides(ProvidesDirective d, Void p)525 public Void visitProvides(ProvidesDirective d, Void p) { 526 update("ProvidesDirective"); 527 update(d.getService().getQualifiedName()); 528 update("("); 529 for (TypeElement impl : d.getImplementations()) { 530 update(impl.getQualifiedName()); 531 } 532 update(")"); 533 return null; 534 } 535 536 } 537 } 538