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