646 } |
678 } |
647 |
679 |
648 private String pkg = ""; |
680 private String pkg = ""; |
649 @Override |
681 @Override |
650 public void visitDependence(String origin, Archive source, |
682 public void visitDependence(String origin, Archive source, |
651 String target, Archive archive, String profile) { |
683 String target, Archive archive, Profile profile) { |
652 if (!origin.equals(pkg)) { |
684 if (!origin.equals(pkg)) { |
653 pkg = origin; |
685 pkg = origin; |
654 writer.format(" %s (%s)%n", origin, source.getFileName()); |
686 writer.format(" %s (%s)%n", origin, source.getFileName()); |
655 } |
687 } |
656 String name = (options.showProfile && !profile.isEmpty()) |
688 writer.format(" -> %-50s %s%n", target, getProfileArchiveInfo(archive, profile)); |
657 ? profile |
|
658 : getArchiveName(archive, profile); |
|
659 writer.format(" -> %-50s %s%n", target, name); |
|
660 } |
689 } |
661 |
690 |
662 @Override |
691 @Override |
663 public void visitArchiveDependence(Archive origin, Archive target, String profile) { |
692 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { |
664 writer.format("%s -> %s", origin, target); |
693 writer.format("%s -> %s", origin.getPathName(), target.getPathName()); |
665 if (options.showProfile && !profile.isEmpty()) { |
694 if (options.showProfile && profile != null) { |
666 writer.format(" (%s)%n", profile); |
695 writer.format(" (%s)%n", profile); |
667 } else { |
696 } else { |
668 writer.format("%n"); |
697 writer.format("%n"); |
669 } |
698 } |
670 } |
699 } |
671 } |
700 } |
672 |
701 |
673 class DotFileFormatter implements Analyzer.Visitor, AutoCloseable { |
702 class DotFileFormatter extends DotGraph<String> implements AutoCloseable { |
674 private final PrintWriter writer; |
703 private final PrintWriter writer; |
675 private final String name; |
704 private final String name; |
676 DotFileFormatter(PrintWriter writer, String name) { |
|
677 this.writer = writer; |
|
678 this.name = name; |
|
679 writer.format("digraph \"%s\" {%n", name); |
|
680 } |
|
681 DotFileFormatter(PrintWriter writer, Archive archive) { |
705 DotFileFormatter(PrintWriter writer, Archive archive) { |
682 this.writer = writer; |
706 this.writer = writer; |
683 this.name = archive.getFileName(); |
707 this.name = archive.getFileName(); |
684 writer.format("digraph \"%s\" {%n", name); |
708 writer.format("digraph \"%s\" {%n", name); |
685 writer.format(" // Path: %s%n", archive.toString()); |
709 writer.format(" // Path: %s%n", archive.getPathName()); |
686 } |
710 } |
687 |
711 |
688 @Override |
712 @Override |
689 public void close() { |
713 public void close() { |
690 writer.println("}"); |
714 writer.println("}"); |
691 } |
715 } |
692 |
716 |
693 private final Set<String> edges = new HashSet<>(); |
|
694 private String node = ""; |
|
695 @Override |
717 @Override |
696 public void visitDependence(String origin, Archive source, |
718 public void visitDependence(String origin, Archive source, |
697 String target, Archive archive, String profile) { |
719 String target, Archive archive, Profile profile) { |
698 if (!node.equals(origin)) { |
|
699 edges.clear(); |
|
700 node = origin; |
|
701 } |
|
702 // if -P option is specified, package name -> profile will |
720 // if -P option is specified, package name -> profile will |
703 // be shown and filter out multiple same edges. |
721 // be shown and filter out multiple same edges. |
704 if (!edges.contains(target)) { |
722 String name = getProfileArchiveInfo(archive, profile); |
705 StringBuilder sb = new StringBuilder(); |
723 writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile))); |
706 String name = options.showProfile && !profile.isEmpty() |
724 } |
707 ? profile |
|
708 : getArchiveName(archive, profile); |
|
709 writer.format(" %-50s -> %s;%n", |
|
710 String.format("\"%s\"", origin), |
|
711 name.isEmpty() ? String.format("\"%s\"", target) |
|
712 : String.format("\"%s (%s)\"", target, name)); |
|
713 edges.add(target); |
|
714 } |
|
715 } |
|
716 |
|
717 @Override |
725 @Override |
718 public void visitArchiveDependence(Archive origin, Archive target, String profile) { |
726 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { |
719 String name = options.showProfile && !profile.isEmpty() |
727 throw new UnsupportedOperationException(); |
720 ? profile : ""; |
728 } |
721 writer.format(" %-30s -> \"%s\";%n", |
729 } |
722 String.format("\"%s\"", origin.getFileName()), |
730 |
723 name.isEmpty() |
731 class DotSummaryForArchive extends DotGraph<Archive> { |
724 ? target.getFileName() |
732 @Override |
725 : String.format("%s (%s)", target.getFileName(), name)); |
733 public void visitDependence(String origin, Archive source, |
|
734 String target, Archive archive, Profile profile) { |
|
735 Edge e = findEdge(source, archive); |
|
736 assert e != null; |
|
737 // add the dependency to the label if enabled and not compact1 |
|
738 if (profile == Profile.COMPACT1) { |
|
739 return; |
|
740 } |
|
741 e.addLabel(origin, target, profileName(archive, profile)); |
|
742 } |
|
743 @Override |
|
744 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { |
|
745 // add an edge with the archive's name with no tag |
|
746 // so that there is only one node for each JDK archive |
|
747 // while there may be edges to different profiles |
|
748 Edge e = addEdge(origin, target, ""); |
|
749 if (target instanceof JDKArchive) { |
|
750 // add a label to print the profile |
|
751 if (profile == null) { |
|
752 e.addLabel("JDK internal API"); |
|
753 } else if (options.showProfile && !options.showLabel) { |
|
754 e.addLabel(profile.toString()); |
|
755 } |
|
756 } |
|
757 } |
|
758 } |
|
759 |
|
760 // DotSummaryForPackage generates the summary.dot file for verbose mode |
|
761 // (-v or -verbose option) that includes all class dependencies. |
|
762 // The summary.dot file shows package-level dependencies. |
|
763 class DotSummaryForPackage extends DotGraph<String> { |
|
764 private String packageOf(String cn) { |
|
765 int i = cn.lastIndexOf('.'); |
|
766 return i > 0 ? cn.substring(0, i) : "<unnamed>"; |
|
767 } |
|
768 @Override |
|
769 public void visitDependence(String origin, Archive source, |
|
770 String target, Archive archive, Profile profile) { |
|
771 // add a package dependency edge |
|
772 String from = packageOf(origin); |
|
773 String to = packageOf(target); |
|
774 Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile)); |
|
775 |
|
776 // add the dependency to the label if enabled and not compact1 |
|
777 if (!options.showLabel || profile == Profile.COMPACT1) { |
|
778 return; |
|
779 } |
|
780 |
|
781 // trim the package name of origin to shorten the label |
|
782 int i = origin.lastIndexOf('.'); |
|
783 String n1 = i < 0 ? origin : origin.substring(i+1); |
|
784 e.addLabel(n1, target, profileName(archive, profile)); |
|
785 } |
|
786 @Override |
|
787 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { |
|
788 // nop |
|
789 } |
|
790 } |
|
791 abstract class DotGraph<T> implements Analyzer.Visitor { |
|
792 private final Set<Edge> edges = new LinkedHashSet<>(); |
|
793 private Edge curEdge; |
|
794 public void writeTo(PrintWriter writer) { |
|
795 writer.format("digraph \"summary\" {%n"); |
|
796 for (Edge e: edges) { |
|
797 writeEdge(writer, e); |
|
798 } |
|
799 writer.println("}"); |
|
800 } |
|
801 |
|
802 void writeEdge(PrintWriter writer, Edge e) { |
|
803 writer.format(" %-50s -> \"%s\"%s;%n", |
|
804 String.format("\"%s\"", e.from.toString()), |
|
805 e.tag.isEmpty() ? e.to |
|
806 : String.format("%s (%s)", e.to, e.tag), |
|
807 getLabel(e)); |
|
808 } |
|
809 |
|
810 Edge addEdge(T origin, T target, String tag) { |
|
811 Edge e = new Edge(origin, target, tag); |
|
812 if (e.equals(curEdge)) { |
|
813 return curEdge; |
|
814 } |
|
815 |
|
816 if (edges.contains(e)) { |
|
817 for (Edge e1 : edges) { |
|
818 if (e.equals(e1)) { |
|
819 curEdge = e1; |
|
820 } |
|
821 } |
|
822 } else { |
|
823 edges.add(e); |
|
824 curEdge = e; |
|
825 } |
|
826 return curEdge; |
|
827 } |
|
828 |
|
829 Edge findEdge(T origin, T target) { |
|
830 for (Edge e : edges) { |
|
831 if (e.from.equals(origin) && e.to.equals(target)) { |
|
832 return e; |
|
833 } |
|
834 } |
|
835 return null; |
|
836 } |
|
837 |
|
838 String getLabel(Edge e) { |
|
839 String label = e.label.toString(); |
|
840 return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label); |
|
841 } |
|
842 |
|
843 class Edge { |
|
844 final T from; |
|
845 final T to; |
|
846 final String tag; // optional tag |
|
847 final StringBuilder label = new StringBuilder(); |
|
848 Edge(T from, T to, String tag) { |
|
849 this.from = from; |
|
850 this.to = to; |
|
851 this.tag = tag; |
|
852 } |
|
853 void addLabel(String s) { |
|
854 label.append(s).append("\\n"); |
|
855 } |
|
856 void addLabel(String origin, String target, String profile) { |
|
857 label.append(origin).append(" -> ").append(target); |
|
858 if (!profile.isEmpty()) { |
|
859 label.append(" (" + profile + ")"); |
|
860 } |
|
861 label.append("\\n"); |
|
862 } |
|
863 @Override @SuppressWarnings("unchecked") |
|
864 public boolean equals(Object o) { |
|
865 if (o instanceof DotGraph<?>.Edge) { |
|
866 DotGraph<?>.Edge e = (DotGraph<?>.Edge)o; |
|
867 return this.from.equals(e.from) && |
|
868 this.to.equals(e.to) && |
|
869 this.tag.equals(e.tag); |
|
870 } |
|
871 return false; |
|
872 } |
|
873 @Override |
|
874 public int hashCode() { |
|
875 int hash = 7; |
|
876 hash = 67 * hash + Objects.hashCode(this.from) + |
|
877 Objects.hashCode(this.to) + Objects.hashCode(this.tag); |
|
878 return hash; |
|
879 } |
726 } |
880 } |
727 } |
881 } |
728 } |
882 } |