javaclass 0.0.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. data/Rakefile +18 -71
  2. data/Readme.txt +13 -22
  3. data/{example_task.rb → dev/example_task.rb} +1 -1
  4. data/dev/saikuro_task.rb +120 -0
  5. data/examples/chart_class_dependencies.rb +43 -0
  6. data/examples/chart_module_dependencies.rb +64 -0
  7. data/examples/check_interface_names.rb +3 -2
  8. data/examples/corpus.rb +52 -12
  9. data/examples/count_classes_in_modules.rb +2 -1
  10. data/examples/cumulative_dependencies.rb +4 -3
  11. data/examples/find_all_imported_types.rb +11 -7
  12. data/examples/find_incoming_dependency_graph.rb +81 -0
  13. data/examples/find_layers_of_modules.rb +79 -0
  14. data/examples/find_referenced_modules.rb +4 -6
  15. data/examples/find_unreferenced_classes.rb +14 -4
  16. data/examples/generate_class_lists.rb +1 -0
  17. data/examples/show_jar_api.rb +93 -0
  18. data/examples/test_corpus.rb +32 -0
  19. data/history.txt +17 -5
  20. data/javaclass.gemspec +9 -8
  21. data/lib/javaclass/analyse/dependencies.rb +1 -1
  22. data/lib/javaclass/analyse/transitive_dependencies.rb +2 -2
  23. data/lib/javaclass/classfile/class_magic.rb +1 -1
  24. data/lib/javaclass/classfile/constant_pool.rb +1 -0
  25. data/lib/javaclass/classfile/java_class_header_as_java_name.rb +4 -0
  26. data/lib/javaclass/classfile/java_class_header_shortcuts.rb +2 -0
  27. data/lib/javaclass/classlist/jar_searcher.rb +11 -5
  28. data/lib/javaclass/classlist/list.rb +27 -14
  29. data/lib/javaclass/classpath/any_classpath.rb +1 -1
  30. data/lib/javaclass/classpath/eclipse_classpath.rb +45 -25
  31. data/lib/javaclass/classpath/factory.rb +23 -13
  32. data/lib/javaclass/classpath/maven_artefact.rb +62 -0
  33. data/lib/javaclass/classpath/tracking_classpath.rb +3 -1
  34. data/lib/javaclass/dependencies/class_node.rb +36 -0
  35. data/lib/javaclass/dependencies/classpath_node.rb +37 -0
  36. data/lib/javaclass/dependencies/edge.rb +41 -0
  37. data/lib/javaclass/dependencies/graph.rb +53 -0
  38. data/lib/javaclass/dependencies/graphml_serializer.rb +102 -0
  39. data/lib/javaclass/dependencies/node.rb +82 -0
  40. data/lib/javaclass/dependencies/yaml_serializer.rb +103 -0
  41. data/lib/javaclass/dsl/java_name_factory.rb +2 -2
  42. data/lib/javaclass/gems/zip_file.rb +41 -33
  43. data/lib/javaclass/java_name.rb +1 -1
  44. data/lib/javaclass/java_name_scanner.rb +9 -7
  45. data/lib/javaclass/string_20.rb +40 -0
  46. data/lib/javaclass/string_hexdump.rb +18 -2
  47. data/lib/javaclass/string_ux.rb +3 -7
  48. data/planned.txt +50 -4
  49. data/rake_analysis.rb +46 -0
  50. data/test/data/api/packagename/Broken.class +0 -0
  51. data/test/logging_folder_classpath.rb +2 -2
  52. data/test/{test_adder_tree.rb → test_adder_tree_node.rb} +1 -1
  53. data/test/test_class_magic.rb +1 -1
  54. data/test/test_eclipse_classpath.rb +1 -1
  55. data/test/test_edge.rb +60 -0
  56. data/test/test_factory.rb +1 -1
  57. data/test/test_graph.rb +28 -0
  58. data/test/test_java_name.rb +13 -1
  59. data/test/test_javaclass_api.rb +18 -3
  60. data/test/test_maven_artefact.rb +37 -0
  61. data/test/test_node.rb +56 -0
  62. data/test/test_yaml_serializer.rb +105 -0
  63. data/test/ts_all_tests.rb +16 -3
  64. metadata +46 -35
  65. data/examples/profiler_scratchpad.rb +0 -33
  66. data/lib/javaclass/analyse/ideas.txt +0 -15
  67. data/lib/javaclass/classpath/classpaths.txt +0 -2
  68. data/lib/javaclass/classscanner/ideas.txt +0 -3
@@ -22,6 +22,7 @@ module JavaClass
22
22
  # Returns the wrapped classpath element (+self+) of this decorated classpath.
23
23
  def elements
24
24
  if [FolderClasspath, JarClasspath].include?(@classpath.class)
25
+ # TODO check for any subclass of FileClasspath instead
25
26
  [self]
26
27
  else
27
28
  @classpath.elements
@@ -90,11 +91,12 @@ module JavaClass
90
91
  class CompositeClasspath
91
92
 
92
93
  # Wrap the _elem_ classpath with a new TrackingClasspath and add it to the list of elements.
93
- alias __old__add_element__ add_element
94
+ alias :__old__add_element__ :add_element unless method_defined?('__old__add_element__')
94
95
 
95
96
  def add_element(elem)
96
97
  unless @elements.find { |cpe| cpe == elem }
97
98
  if [FolderClasspath, JarClasspath].include?(elem.class)
99
+ # TODO check for any subclass of FileClasspath instead -> fail a test with ConventionClasspath and Tracker
98
100
  __old__add_element__(TrackingClasspath.new(elem))
99
101
  else
100
102
  __old__add_element__(elem)
@@ -0,0 +1,36 @@
1
+ require 'javaclass/dependencies/node'
2
+ require 'javaclass/dependencies/edge'
3
+
4
+ module JavaClass
5
+ module Dependencies
6
+
7
+ # A concrete Node which contains a ClassFile and its dependencies.
8
+ # This models a Node as a Java class.
9
+ # Author:: Peter Kofler
10
+ class ClassNode < Node
11
+
12
+ def initialize(java_class)
13
+ super(java_class.to_classname)
14
+ @java_class = java_class
15
+ end
16
+
17
+ # Iterate on a list of Edge dependencies this node has.
18
+ def outgoing_dependencies
19
+ @java_class.imported_3rd_party_types.each do |import|
20
+ yield Edge.new(@java_class.to_classname, import.to_classname)
21
+ end
22
+ # later iterate all types/fields/methods and create an edge from the method to the target type.
23
+ # So Edges make sense and multiplicity in dependencies is possible.
24
+ end
25
+
26
+ # Does this Node satisfy the dependency to _class_name_ ?
27
+ def satisfies?(class_name)
28
+ @java_class.to_classname == class_name.to_classname
29
+ # later class name will be a full qualified class#method or field name.
30
+ end
31
+
32
+ end
33
+ # TODO add tests
34
+
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ require 'javaclass/dependencies/node'
2
+ require 'javaclass/dependencies/edge'
3
+
4
+ module JavaClass
5
+ module Dependencies
6
+
7
+ # A concrete Node which contains a Classpath and its dependencies.
8
+ # This models a Node as a component, maybe an Eclipse plugin, a Maven module or a library.
9
+ # Dependencies (Edge) contain all references imported by any class of this component.
10
+ # Author:: Peter Kofler
11
+ class ClasspathNode < Node
12
+
13
+ def initialize(name, classpath)
14
+ super(name, classpath.count)
15
+ @classpath = classpath
16
+ end
17
+
18
+ # Iterate on a list of Edge dependencies this node has.
19
+ def outgoing_dependencies
20
+ @classpath.values.each do |clazz|
21
+ clazz.imported_3rd_party_types.each do |import|
22
+ unless satisfies?(import)
23
+ yield Edge.new(clazz.to_classname, import)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # Does this Node satisfy the dependency _dependency_name_ .
30
+ def satisfies?(dependency_name)
31
+ @classpath.includes?(dependency_name)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module JavaClass
2
+ module Dependencies
3
+
4
+ # An edge in the Graph of dependencies. An edge knows it's source and destination details.
5
+ # Author:: Peter Kofler
6
+ class Edge
7
+
8
+ attr_reader :source
9
+ attr_reader :target
10
+
11
+ def initialize(source, target)
12
+ @source = source
13
+ @target = target
14
+ end
15
+
16
+ def to_s
17
+ "#{@target} (#{@source})"
18
+ end
19
+
20
+ def ==(other)
21
+ @source == other.source && @target == other.target
22
+ end
23
+
24
+ alias eql? ==
25
+
26
+ def hash
27
+ [@source, @target].hash
28
+ end
29
+
30
+ def <=>(other)
31
+ res = @target <=> other.target
32
+ if res == 0
33
+ res = @source <=> other.source
34
+ end
35
+ res
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ module JavaClass
2
+
3
+ # The module Dependencies is for separating namespaces. It contains logic
4
+ # to analyse and structure general directed dependency graphs. A set of
5
+ # dependencies build a Graph which can be analysed.
6
+ # Author:: Peter Kofler
7
+ module Dependencies
8
+
9
+ # A graph contains a list of Node
10
+ # Author:: Peter Kofler
11
+ class Graph
12
+
13
+ def initialize
14
+ @nodes = []
15
+ end
16
+
17
+ # Add a _node_ to this graph.
18
+ def add(node)
19
+ @nodes << node
20
+ end
21
+
22
+ def to_a
23
+ @nodes.dup
24
+ end
25
+
26
+ # Iterates all nodes and fills the dependency fields of the Node.
27
+ def resolve_dependencies
28
+ @nodes.each do |node|
29
+ puts "processing #{node}"
30
+
31
+ node.outgoing_dependencies do |dependency|
32
+ providers = nodes_satisfying(dependency.target)
33
+ node.add_dependency(dependency, providers)
34
+ end
35
+
36
+ node.dependencies.values.each { |vals| vals.sort! }
37
+ end
38
+ end
39
+
40
+ # Find the nodes that satisfy the given _dependency_
41
+ def nodes_satisfying(dependency)
42
+ @nodes.find_all { |n| n.satisfies?(dependency) }
43
+ end
44
+
45
+ # Iterate all nodes in this Graph and call _block_ for each Node
46
+ def each_node(&block)
47
+ @nodes.each { |node| block.call(node) }
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,102 @@
1
+ require 'rexml/document'
2
+
3
+ module JavaClass
4
+ module Dependencies
5
+
6
+ # Serializes a Graph of Node to GraphML (XML).
7
+ # To see the graph, use yED, http://www.yworks.com/en/products_yed_about.html to
8
+ # * load the graphml file.
9
+ # * Then select all nodes and apply Tools/Fit Node to Label.
10
+ # * Finally apply the Layout/Hierarchical or maybe Layout/Organic/Smart.
11
+ # Author:: Peter Kofler
12
+ class GraphmlSerializer
13
+ include REXML
14
+
15
+ # Create a serializer with _options_ hash:
16
+ # edges:: how to chart edge labes, either :no_text or :with_counts
17
+ def initialize(options = { :edges => :with_counts })
18
+ @options = options
19
+ end
20
+
21
+ # Save the _graph_ to _filename_ .
22
+ def save(filename, graph)
23
+ File.open(filename + '.graphml', 'w') do |f|
24
+ doc = graph_to_xml(graph)
25
+ doc.write(out_string = '', 2)
26
+ f.print out_string
27
+ end
28
+ end
29
+
30
+ # Return an XML document of the GraphML serialized _graph_ .
31
+ def graph_to_xml(graph)
32
+ doc = create_xml_doc
33
+ container = add_graph_element(doc)
34
+ graph.to_a.each { |node| add_node_as_xml(container, node) }
35
+ doc
36
+ end
37
+
38
+ private
39
+
40
+ def create_xml_doc
41
+ REXML::Document.new
42
+ end
43
+
44
+ def add_graph_element(doc)
45
+ root = doc.add_element('graphml',
46
+ 'xmlns' => 'http://graphml.graphdrawing.org/xmlns',
47
+ 'xmlns:y' => 'http://www.yworks.com/xml/graphml')
48
+ root.add_element('key', 'id' => 'n1', 'for' => 'node', 'yfiles.type' => 'nodegraphics')
49
+ root.add_element('key', 'id' => 'e1', 'for' => 'edge', 'yfiles.type' => 'edgegraphics')
50
+
51
+ root.add_element('graph', 'edgedefault' => 'directed')
52
+ end
53
+
54
+ public
55
+
56
+ # Add the _node_ as XML to the _container_ .
57
+ def add_node_as_xml(container, node)
58
+ add_node_element(container, node)
59
+
60
+ node.dependencies.keys.each do |dep|
61
+ add_edge_element(container, node, dep)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def add_node_element(container, node)
68
+ elem = container.add_element('node', 'id' => node.name)
69
+ elem.add_element('data', 'key' => 'n1').
70
+ add_element('y:ShapeNode').
71
+ add_element('y:NodeLabel').
72
+ add_text(node.to_s)
73
+ end
74
+
75
+ def add_edge_element(container, node, dep)
76
+ edge = container.add_element('edge')
77
+ edge.add_attribute('id', node.name + '.' + dep.name)
78
+ edge.add_attribute('source', node.name)
79
+ edge.add_attribute('target', dep.name)
80
+
81
+ add_edge_label(edge, node.dependencies[dep])
82
+ end
83
+
84
+ def add_edge_label(edge, dependencies_2_dep)
85
+ if @options[:edges] == :with_counts
86
+ number_total_dependencies = dependencies_2_dep.size.to_s
87
+ number_unique_dependencies = dependencies_2_dep.collect { |d| d.target }.uniq.size.to_s
88
+ edge.add_element('data', 'key' => 'e1').
89
+ add_element('y:PolyLineEdge').
90
+ add_element('y:EdgeLabel').
91
+ add_text("#{number_total_dependencies} (#{number_unique_dependencies})")
92
+ elsif @options[:edges] == :no_text
93
+ # do nothing
94
+ else
95
+ raise "unknown option for edge labels #{@options[:edges]}"
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,82 @@
1
+ module JavaClass
2
+ module Dependencies
3
+
4
+ # A node in a Graph of dependencies. A node contains a map of dependencies (Edge) to other nodes.
5
+ # Author:: Peter Kofler
6
+ class Node
7
+
8
+ attr_reader :name
9
+ attr_reader :size
10
+ attr_reader :dependencies
11
+ ### attr_reader :incoming
12
+
13
+ def initialize(name, size=0)
14
+ @name = name
15
+ @size = size
16
+ @dependencies = Hash.new([])
17
+ ### @incoming = Hash.new([])
18
+ end
19
+
20
+ def to_s
21
+ if @size > 0
22
+ "#{@name} (#{@size.to_s})"
23
+ else
24
+ @name
25
+ end
26
+ end
27
+
28
+ def ==(other)
29
+ other.respond_to?(:name) && @name == other.name
30
+ end
31
+
32
+ def hash
33
+ @name.hash
34
+ end
35
+
36
+ def <=>(other)
37
+ @name <=> other.name
38
+ end
39
+
40
+ # Add a _dependency_ Edge for a list of _provider_ Node.
41
+ def add_dependency(dependency, providers)
42
+ if providers.size == 0
43
+ # external dependency, skip this
44
+ elsif providers.size == 1
45
+ # add dependency to this provider
46
+ provider = providers[0]
47
+ # unless @dependencies[provider].include? dependency
48
+ @dependencies[provider] = @dependencies[provider] + [dependency]
49
+ ### provider.incoming[self] = provider.incoming[self] + [dependency]
50
+ # end
51
+ else
52
+ warn "dependency to \"#{dependency}\" found more than once: #{providers.join(', ')}"
53
+ end
54
+ end
55
+
56
+ # Add a list _dependencies_ of _provider_ .
57
+ def add_dependencies(dependencies, providers)
58
+ dependencies.each do |dependency|
59
+ add_dependency(dependency, providers)
60
+ end
61
+ end
62
+
63
+ # Iterate all providers of dependencies of this Node and call _block_ for each provider with its list of dependencies (Edge).
64
+ def each_dependency_provider(&block)
65
+ @dependencies.keys.each do |provider|
66
+ block.call(provider, @dependencies[provider])
67
+ end
68
+ end
69
+
70
+ # Iterate all dependencies of this Node and call _block_ for each provider with each of its dependencies.
71
+ def each_edge(&block)
72
+ each_dependency_provider do |provider, edges|
73
+ edges.each do |edge|
74
+ block.call(provider, edge)
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,103 @@
1
+ require 'yaml'
2
+ require 'javaclass/dependencies/edge'
3
+ require 'javaclass/dependencies/node'
4
+ require 'javaclass/dependencies/graph'
5
+
6
+ module JavaClass
7
+ module Dependencies
8
+
9
+ # Serializes a Graph of Nodes to YAML.
10
+ # Author:: Peter Kofler
11
+ class YamlSerializer
12
+
13
+ # Create a serializer with _options_ hash:
14
+ # outgoing:: how to persist outgoing dependencies, either :detailed, :summary or :none
15
+ def initialize(options = {:outgoing => :detailed })
16
+ @options = options
17
+ end
18
+
19
+ # Exists a YAML serialized _graph_ ?
20
+ def has_yaml?(filename)
21
+ File.exist? yaml_file(filename)
22
+ end
23
+
24
+ # Save the _graph_ to YAML _filename_ .
25
+ def save(filename, graph)
26
+ File.open(yaml_file(filename), 'w') { |f| f.print graph_to_yaml(graph) }
27
+ end
28
+
29
+ # Return a String of the YAML serialized _graph_ .
30
+ def graph_to_yaml(graph)
31
+ "---\n" +
32
+ graph.to_a.map { |node| node_to_yaml(node) }.join("\n")
33
+ end
34
+
35
+ # Return a String of the YAML serialized _node_ .
36
+ def node_to_yaml(node)
37
+ node.name + ":\n" +
38
+ node.dependencies.keys.map { |dep|
39
+ ' ' + dep.name + dependencies_to_yaml(node.dependencies[dep])
40
+ }.join("\n")
41
+ end
42
+
43
+ private
44
+
45
+ def yaml_file(filename)
46
+ filename + '.yaml'
47
+ end
48
+
49
+ def dependencies_to_yaml(dependencies)
50
+ if @options[:outgoing] == :detailed
51
+ ":\n" + dependencies.map { |dep| " - #{dep.source}->#{dep.target}" }.join("\n")
52
+ elsif @options[:outgoing] == :summary
53
+ ":\n" + dependencies.map { |dep| dep.target }.uniq.sort.map { |dep| " - #{dep}" }.join("\n")
54
+ elsif @options[:outgoing] == :none
55
+ ''
56
+ else
57
+ raise "unknown option for outgoing dependencies #{@options[:outgoing]}"
58
+ end
59
+ end
60
+
61
+ def node_with(name)
62
+ node = @nodes_by_name[name]
63
+ if node
64
+ node
65
+ else
66
+ @nodes_by_name[name] = Node.new(name)
67
+ end
68
+ end
69
+
70
+ public
71
+
72
+ # Load the Graph from YAML _filename_ .
73
+ def load(filename)
74
+ yaml = File.open(yaml_file(filename)) { |f| YAML.load(f) }
75
+ # TODO support compressed yaml files, e.g. inside zip
76
+ yaml_to_graph(yaml)
77
+ end
78
+
79
+ # Return a Graph from the YAML data _yaml_ .
80
+ def yaml_to_graph(yaml)
81
+ graph = Graph.new
82
+ @nodes_by_name = {}
83
+
84
+ yaml.keys.each do |name|
85
+ node = node_with(name)
86
+ graph.add(node)
87
+
88
+ dependency_map = yaml[name] || {}
89
+ dependency_map.keys.each do |dependency_name|
90
+ depending_node = node_with(dependency_name)
91
+ dependencies = dependency_map[dependency_name].collect { |d| Edge.new(*d.split(/->/)) }
92
+ node.add_dependencies(dependencies, [depending_node])
93
+ end
94
+ end
95
+
96
+ @nodes_by_name = {}
97
+ graph
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+ end