javaclass 0.0.3 → 0.0.4

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 (175) hide show
  1. data/Rakefile +67 -56
  2. data/Readme.txt +42 -39
  3. data/example_task.rb +172 -0
  4. data/examples/check_interface_names.rb +44 -0
  5. data/examples/corpus.rb +19 -0
  6. data/examples/count_classes_in_modules.rb +38 -0
  7. data/examples/cumulative_dependencies.rb +39 -0
  8. data/examples/find_all_imported_types.rb +44 -0
  9. data/examples/find_referenced_modules.rb +53 -0
  10. data/examples/find_unreferenced_classes.rb +65 -0
  11. data/examples/generate_class_lists.rb +67 -43
  12. data/examples/profiler_scratchpad.rb +33 -0
  13. data/examples/simple_usage.rb +42 -0
  14. data/history.txt +29 -7
  15. data/javaclass.gemspec +31 -0
  16. data/lib/javaclass/adder_tree.rb +92 -0
  17. data/lib/javaclass/analyse/dependencies.rb +52 -0
  18. data/lib/javaclass/{metric/metrics.txt → analyse/ideas.txt} +2 -2
  19. data/lib/javaclass/analyse/transitive_dependencies.rb +52 -0
  20. data/lib/javaclass/classfile/access_flag_constants.rb +24 -0
  21. data/lib/javaclass/classfile/access_flags.rb +49 -26
  22. data/lib/javaclass/classfile/class_format_error.rb +37 -0
  23. data/lib/javaclass/classfile/class_magic.rb +16 -8
  24. data/lib/javaclass/classfile/class_version.rb +19 -25
  25. data/lib/javaclass/classfile/constant_pool.rb +110 -45
  26. data/lib/javaclass/classfile/constants/base.rb +33 -12
  27. data/lib/javaclass/classfile/constants/double_reference.rb +55 -41
  28. data/lib/javaclass/classfile/constants/single_reference.rb +29 -21
  29. data/lib/javaclass/classfile/constants/value.rb +43 -33
  30. data/lib/javaclass/classfile/java_class_header.rb +72 -46
  31. data/lib/javaclass/classfile/java_class_header_as_java_name.rb +33 -0
  32. data/lib/javaclass/classfile/java_class_header_shortcuts.rb +19 -0
  33. data/lib/javaclass/classfile/references.rb +21 -19
  34. data/lib/javaclass/classlist/class_entry.rb +26 -27
  35. data/lib/javaclass/classlist/jar_searcher.rb +34 -25
  36. data/lib/javaclass/classlist/list.rb +31 -31
  37. data/lib/javaclass/classlist/package_entry.rb +25 -24
  38. data/lib/javaclass/classpath/any_classpath.rb +48 -0
  39. data/lib/javaclass/classpath/class_not_found_error.rb +20 -0
  40. data/lib/javaclass/classpath/classpaths.txt +2 -2
  41. data/lib/javaclass/classpath/composite_classpath.rb +56 -54
  42. data/lib/javaclass/classpath/convention_classpath.rb +38 -0
  43. data/lib/javaclass/classpath/eclipse_classpath.rb +74 -0
  44. data/lib/javaclass/classpath/factory.rb +65 -0
  45. data/lib/javaclass/classpath/file_classpath.rb +47 -0
  46. data/lib/javaclass/classpath/folder_classpath.rb +42 -44
  47. data/lib/javaclass/classpath/jar_classpath.rb +91 -52
  48. data/lib/javaclass/classpath/java_home_classpath.rb +24 -13
  49. data/lib/javaclass/classpath/maven_classpath.rb +44 -0
  50. data/lib/javaclass/classpath/temporary_unpacker.rb +111 -0
  51. data/lib/javaclass/classpath/tracking_classpath.rb +144 -0
  52. data/lib/javaclass/classscanner/ideas.txt +3 -0
  53. data/lib/javaclass/classscanner/imported_types.rb +29 -0
  54. data/lib/javaclass/classscanner/scanners.rb +29 -0
  55. data/lib/javaclass/delegate_directive.rb +15 -0
  56. data/lib/javaclass/dsl/caching_classpath.rb +38 -0
  57. data/lib/javaclass/dsl/classpath_analysers.rb +27 -0
  58. data/lib/javaclass/dsl/java_name_factory.rb +79 -0
  59. data/lib/javaclass/dsl/loader.rb +42 -0
  60. data/lib/javaclass/dsl/loading_classpath.rb +53 -0
  61. data/lib/javaclass/dsl/mixin.rb +54 -0
  62. data/lib/javaclass/gems/zip_file.rb +154 -0
  63. data/lib/javaclass/java_language.rb +50 -0
  64. data/lib/javaclass/java_name.rb +329 -55
  65. data/lib/javaclass/java_name_scanner.rb +95 -0
  66. data/lib/javaclass/resources/iso_3166_countries.txt +240 -0
  67. data/lib/javaclass/resources/jdk0_packages.txt +6 -0
  68. data/lib/javaclass/resources/jdk1_packages.txt +6 -0
  69. data/lib/javaclass/resources/jdk2_packages.txt +4 -0
  70. data/lib/javaclass/resources/jdk3_packages.txt +6 -0
  71. data/lib/javaclass/resources/jdk4_packages.txt +22 -0
  72. data/lib/javaclass/resources/jdk5_packages.txt +5 -0
  73. data/lib/javaclass/resources/jdk6_packages.txt +7 -0
  74. data/lib/javaclass/resources/jdk7_packages.txt +0 -0
  75. data/lib/javaclass/resources/jdk_packages.txt +53 -0
  76. data/lib/javaclass/resources/reserved_words.txt +50 -0
  77. data/lib/javaclass/string_hexdump.rb +76 -0
  78. data/lib/javaclass/string_ux.rb +21 -10
  79. data/lib/javaclass.rb +16 -41
  80. data/license.txt +28 -0
  81. data/planned.txt +13 -0
  82. data/test/data/Object_102.class +0 -0
  83. data/test/data/Runnable_102.class +0 -0
  84. data/test/data/access_flags/AccessFlagsTestAnnotation.class +0 -0
  85. data/test/data/access_flags/AccessFlagsTestAnnotation.java +3 -0
  86. data/test/data/access_flags/AccessFlagsTestEnum$1.class +0 -0
  87. data/test/data/access_flags/AccessFlagsTestEnum.class +0 -0
  88. data/test/data/access_flags/AccessFlagsTestEnum.java +6 -0
  89. data/test/data/access_flags/AccessFlagsTestInner$1.class +0 -0
  90. data/test/data/access_flags/AccessFlagsTestInner$2.class +0 -0
  91. data/test/data/access_flags/AccessFlagsTestInner.class +0 -0
  92. data/test/data/access_flags/AccessFlagsTestInner.java +13 -0
  93. data/test/data/access_flags/AccessFlagsTestPackage.class +0 -0
  94. data/test/data/access_flags/AccessFlagsTestPackage.java +1 -1
  95. data/test/data/api/packagename/AccessFlagsTestPublic.class +0 -0
  96. data/test/data/class_version/ClassVersionTest17.class +0 -0
  97. data/test/data/class_version/make.bat +6 -2
  98. data/test/data/eclipse_classpath/classes/ClassVersionTest12.class +0 -0
  99. data/test/data/eclipse_classpath/lib/JarClasspathTest.jar +0 -0
  100. data/test/data/eclipse_classpath/test-classes/ClassVersionTest13.class +0 -0
  101. data/test/data/folder_classpath/{JarClasspathTestFolder → classes}/ClassVersionTest10.class +0 -0
  102. data/test/data/folder_classpath/{JarClasspathTestFolder → classes}/package/ClassVersionTest11.class +0 -0
  103. data/test/data/jar_classpath/JarClasspathTest.jar +0 -0
  104. data/test/data/jar_classpath/JarClasspathTest.zip +0 -0
  105. data/test/data/jar_classpath/JarClasspathTestManifest.jar +0 -0
  106. data/test/data/jar_classpath/JarClasspathTestMultiManifest.jar +0 -0
  107. data/test/data/jar_classpath/make.bat +6 -2
  108. data/test/data/jar_searcher/BrokenRunnable_102.class +0 -0
  109. data/test/data/java_home_classpath/jdk118/lib/classes.zip +0 -0
  110. data/test/data/java_name_scanner/META-INF/MANIFEST.MF +12 -0
  111. data/test/data/java_name_scanner/plugin.xml +18 -0
  112. data/test/data/maven_classpath/module/pom.xml +8 -0
  113. data/test/data/maven_classpath/module/target/classes/ClassVersionTest12.class +0 -0
  114. data/test/data/maven_classpath/pom.xml +8 -0
  115. data/test/data/maven_classpath/target/classes/ClassVersionTest10.class +0 -0
  116. data/test/data/maven_classpath/target/test-classes/ClassVersionTest11.class +0 -0
  117. data/test/data/transitive_dependencies/A.class +0 -0
  118. data/test/data/transitive_dependencies/A.java +5 -0
  119. data/test/data/transitive_dependencies/B.class +0 -0
  120. data/test/data/transitive_dependencies/B.java +3 -0
  121. data/test/data/transitive_dependencies/C.class +0 -0
  122. data/test/data/transitive_dependencies/C.java +3 -0
  123. data/test/data/transitive_dependencies/Start.class +0 -0
  124. data/test/data/transitive_dependencies/Start.java +4 -0
  125. data/test/data/transitive_dependencies/make.bat +3 -0
  126. data/test/data/zip_file/commons-math-2.2-broken.zip +0 -0
  127. data/test/data/zip_file/regenerated-with-7zip.zip +0 -0
  128. data/test/data/zip_file/regenerated-with-jar.zip +0 -0
  129. data/test/dot_classpath.rb +33 -0
  130. data/test/logging_folder_classpath.rb +19 -0
  131. data/test/setup.rb +1 -1
  132. data/test/test_access_flags.rb +58 -32
  133. data/test/test_adder_tree.rb +78 -0
  134. data/test/test_any_classpath.rb +39 -0
  135. data/test/test_base.rb +9 -7
  136. data/test/test_caching_classpath.rb +41 -0
  137. data/test/test_class_entry.rb +60 -60
  138. data/test/test_class_magic.rb +31 -0
  139. data/test/test_class_version.rb +25 -25
  140. data/test/test_composite_classpath.rb +22 -23
  141. data/test/test_constant_pool.rb +37 -13
  142. data/test/test_convention_classpath.rb +39 -0
  143. data/test/test_eclipse_classpath.rb +73 -0
  144. data/test/test_factory.rb +61 -0
  145. data/test/test_folder_classpath.rb +26 -10
  146. data/test/test_imported_types.rb +34 -0
  147. data/test/test_jar_classpath.rb +29 -14
  148. data/test/test_jar_searcher.rb +27 -14
  149. data/test/test_java_class_header.rb +22 -10
  150. data/test/test_java_class_header_as_java_name.rb +41 -0
  151. data/test/test_java_home_classpath.rb +17 -11
  152. data/test/test_java_name.rb +204 -64
  153. data/test/test_java_name_factory.rb +52 -0
  154. data/test/test_java_name_scanner.rb +24 -0
  155. data/test/test_javaclass_api.rb +43 -0
  156. data/test/test_list.rb +58 -44
  157. data/test/test_load_directive.rb +34 -0
  158. data/test/test_maven_classpath.rb +46 -0
  159. data/test/test_package_entry.rb +27 -22
  160. data/test/test_references.rb +14 -14
  161. data/test/test_string_hexdump.rb +24 -0
  162. data/test/test_string_ux.rb +18 -106
  163. data/test/test_tracking_classpath.rb +112 -0
  164. data/test/test_transitive_dependencies.rb +31 -0
  165. data/test/test_unpacking_jar_classpath.rb +43 -0
  166. data/test/test_zip_file.rb +33 -0
  167. data/test/ts_all_tests.rb +80 -18
  168. data/thanks.txt +2 -0
  169. metadata +151 -22
  170. data/lib/javaclass/classpath/port_ClassPathEntry.java +0 -202
  171. data/lib/javaclass/classpath/port_ClassPathEntryFactory.java +0 -311
  172. data/lib/javaclass/classpath/port_DirectoryRepository.java +0 -24
  173. data/lib/javaclass/metric/ccd.rb +0 -68
  174. data/lib/javaclass/metric/class_usage.rb +0 -41
  175. data/test/test_javaclass.rb +0 -22
@@ -1,32 +1,51 @@
1
- require 'zip/zipfilesystem'
1
+ require 'fileutils'
2
+ require 'javaclass/gems/zip_file'
3
+ require 'javaclass/classpath/temporary_unpacker'
4
+ require 'javaclass/classpath/file_classpath'
5
+ require 'javaclass/java_language'
2
6
  require 'javaclass/java_name'
3
7
 
4
8
  module JavaClass
5
- module Classpath
6
-
7
- # Abstraction of a ZIP or JAR on the CLASSPATH.
9
+
10
+ # Activate temporary unpacking of all JARs. This speeds up loading of classes later.
11
+ def self.unpack_jars!(flag=:unpack)
12
+ @@unpack_jars = flag
13
+ end
14
+
15
+ # Return +true+ if JARs should be temporarily unpacked
16
+ def self.unpack_jars?
17
+ defined?(@@unpack_jars) && @@unpack_jars
18
+ end
19
+
20
+ module Classpath
21
+
22
+ # Abstraction of a ZIP or JAR on the CLASSPATH. May return additional classpath
23
+ # elements for referenced libs. This is a leaf in the classpath tree.
8
24
  # Author:: Peter Kofler
9
- class JarClasspath
25
+ class JarClasspath < FileClasspath
26
+
27
+ # Check if the _file_ is a valid location for a jar classpath.
28
+ def self.valid_location?(file)
29
+ FileTest.exist?(file) && FileTest.file?(file) && FileTest.size(file) > 0 && file =~ /\.jar$|\.zip$/
30
+ end
10
31
 
11
- # Return the list of classnames found in this _jarfile_ .
32
+ # Create a classpath with this _jarfile_ .
12
33
  def initialize(jarfile)
13
- @jarfile = jarfile
14
- raise IOError, "jarfile #{@jarfile} not found" if !FileTest.exist? @jarfile
15
- raise "#{@jarfile} is no file" if !FileTest.file? @jarfile
16
- @classes = list_classes.collect { |cl| cl.to_javaname }
17
- @manifest =
18
- begin
19
- Zip::ZipFile.open(@jarfile) { |zipfile| zipfile.file.read("META-INF/MANIFEST.MF") }
20
- rescue
21
- nil
34
+ super(jarfile)
35
+ unless JarClasspath::valid_location?(jarfile)
36
+ raise IOError, "jarfile #{jarfile} not found/no file"
22
37
  end
38
+ @jarfile = jarfile
39
+ init_classes
40
+ @manifest = JavaClass::Gems::ZipFile.new(@jarfile).read('META-INF/MANIFEST.MF')
41
+ setup_cache if JavaClass.unpack_jars?
23
42
  end
24
43
 
25
- # Return if the given classpath element is a jar.
44
+ # Return +true+ as this classpath element is a jar. Zip files return +false+ as well.
26
45
  def jar?
27
46
  @manifest != nil
28
47
  end
29
-
48
+
30
49
  # Return list of additional classpath elements defined in the manifest of this jarfile.
31
50
  def additional_classpath
32
51
  if @manifest
@@ -40,61 +59,81 @@ module JavaClass
40
59
  []
41
60
  end
42
61
  end
43
-
44
- # Return the list of class names found in this jar.
45
- def names
46
- @classes.dup
62
+
63
+ # Return the list of class names found in this jar. An additional block is used as _filter_ on class names.
64
+ def names(&filter)
65
+ if block_given?
66
+ @class_names.find_all { |n| filter.call(n) }
67
+ else
68
+ @class_names.dup
69
+ end
47
70
  end
48
-
71
+
49
72
  # Return if _classname_ is included in this jar.
50
73
  def includes?(classname)
51
- @classes.include?(normalize(classname))
74
+ @class_lookup[to_key(classname).file_name]
52
75
  end
53
-
76
+
54
77
  # Load the binary data of the file name or class name _classname_ from this jar.
55
78
  def load_binary(classname)
56
- raise "class #{classname} not found in #{@jarfile}" unless includes?(classname)
57
- Zip::ZipFile.open(@jarfile) do |zipfile|
58
- zipfile.file.read(normalize(classname))
79
+ key = to_key(classname)
80
+ if JavaClass.unpack_jars?
81
+ @delegate.load_binary(key)
82
+ else
83
+ unless includes?(key)
84
+ raise ClassNotFoundError.new(key, @jarfile)
85
+ end
86
+ JavaClass::Gems::ZipFile.new(@jarfile).read(key).freeze
59
87
  end
60
88
  end
61
-
89
+
62
90
  # Return the number of classes in this jar.
63
91
  def count
64
- @classes.size
65
- end
66
-
67
- def to_s
68
- @jarfile
69
- end
70
-
71
- def ==(other)
72
- other.class == JarClasspath && other.to_s == self.to_s
92
+ @class_names.size
73
93
  end
74
-
75
- private
76
-
77
- # Return the list of classnames (in fact file names) found in this jarfile.
94
+
95
+ private
96
+
97
+ # Return the list of classnames (in fact file names) found in this jarfile.
78
98
  def list_classes
79
99
  list = []
80
- Zip::ZipFile.foreach(@jarfile) do |entry|
100
+ JavaClass::Gems::ZipFile.new(@jarfile).entries do |entry|
81
101
  name = entry.name
82
- next unless entry.file? and name =~ /\.class$/ # class file
83
- list << name
102
+ next unless entry.file? and name =~ JavaLanguage::CLASS_REGEX # class file
103
+ list << name
84
104
  end
85
- list.sort
105
+ list
106
+ end
107
+
108
+ # Set up the class names.
109
+ def init_classes
110
+ @class_names = list_classes.
111
+ reject { |n| n =~ /package-info\.class$/ }.
112
+ find_all { |n| valid_java_name?(n) }.
113
+ sort.
114
+ collect { |cl| JavaClassFileName.new(cl) }
115
+ pairs = @class_names.map { |name| [name.file_name, 1] }.flatten
116
+ @class_lookup = Hash[ *pairs ] # file_name (String) => anything
86
117
  end
87
118
 
88
- # Normalize the file name or class name _classname_ to be a file name in the jar.
89
- def normalize(classname)
90
- if classname !~ /\.class$/
91
- classname.gsub(/\./,'/') + '.class'
119
+ def valid_java_name?(name)
120
+ if JavaClassFileName.valid?(name)
121
+ true
92
122
  else
93
- classname
123
+ warn("skipping invalid class file name #{name} in classpath #{@jarfile}")
124
+ false
94
125
  end
95
126
  end
96
127
 
128
+ # Set up the temporary unpacking. This sets the delegate field for future use.
129
+ def setup_cache
130
+ unpacker = TemporaryUnpacker.new(@jarfile)
131
+ unpacker.create_temporary_folder
132
+ unpacker.unpack!
133
+ @delegate = FolderClasspath.new(unpacker.folder)
134
+ end
135
+
97
136
  end
98
-
137
+
99
138
  end
100
- end
139
+ end
@@ -1,40 +1,51 @@
1
1
  require 'javaclass/classpath/jar_classpath'
2
2
 
3
3
  module JavaClass
4
- module Classpath
5
-
6
- # Abstraction of the Java boot CLASSPATH. May return additional classpath elements for endorsed libs.
4
+ module Classpath
5
+
6
+ # Abstraction of the Java boot CLASSPATH. May return additional classpath
7
+ # elements for endorsed libs. This is a leaf in the classpath tree.
7
8
  # Author:: Peter Kofler
8
9
  class JavaHomeClasspath < JarClasspath
10
+
11
+ RT_JAR = 'rt.jar'
9
12
 
10
- # Return the list of classnames found in this _javahome_ .
13
+ # Create a classpath from this _javahome_ directory.
11
14
  def initialize(javahome)
12
- if FileTest.exist? rtjar=File.join(javahome, 'lib', 'rt.jar')
15
+ if exist?(rtjar=File.join(javahome, 'lib', RT_JAR))
16
+ super(rtjar)
17
+ elsif exist?(rtjar=File.join(javahome, 'jre', 'lib', RT_JAR))
13
18
  super(rtjar)
14
- elsif FileTest.exist? rtjar=File.join(javahome, 'jre', 'lib', 'rt.jar')
19
+ elsif exist?(rtjar=File.join(javahome, 'lib', 'classes.zip')) # Java 1.1 home with lib/classes.zip
15
20
  super(rtjar)
16
21
  else
17
- raise IOError, "rt.jar not found in java home #{javahome}"
22
+ raise IOError, "#{RT_JAR} not found in java home #{javahome}"
18
23
  end
19
24
  @lib = File.dirname(rtjar)
20
25
  end
21
-
26
+
22
27
  # Return list of additional classpath elements, e.g. endorsed libs found in this Java Home.
23
28
  def additional_classpath
24
29
  list = super
25
-
26
- if FileTest.exist? ext=File.join(@lib, 'ext')
30
+
31
+ if FileTest.exist? ext=File.join(@lib, 'ext')
27
32
  current = Dir.getwd
28
33
  Dir.chdir ext
29
-
34
+
30
35
  list += Dir['*.jar'].collect { |jar| File.join(ext, jar) }
31
-
36
+
32
37
  Dir.chdir current
33
38
  end
34
39
  list
35
40
  end
41
+
42
+ private
43
+
44
+ def exist?(file)
45
+ FileTest.exist?(file) && FileTest.file?(file)
46
+ end
36
47
 
37
48
  end
38
-
49
+
39
50
  end
40
51
  end
@@ -0,0 +1,44 @@
1
+ require 'javaclass/classpath/composite_classpath'
2
+
3
+ module JavaClass
4
+ module Classpath
5
+
6
+ # A Maven folder structure aware classpath. Maven submodules are supported.
7
+ # Author:: Peter Kofler
8
+ class MavenClasspath < CompositeClasspath
9
+
10
+ POM_XML = 'pom.xml'
11
+
12
+ # Check if the _file_ is a valid location for a Maven classpath.
13
+ def self.valid_location?(file)
14
+ FileTest.exist?(file) && FileTest.directory?(file) && FileTest.exist?(File.join(file, POM_XML))
15
+ end
16
+
17
+ # Create a classpath for a Maven base project _folder_
18
+ def initialize(folder)
19
+ unless MavenClasspath::valid_location?(folder)
20
+ raise IOError, "folder #{folder} not a Maven project"
21
+ end
22
+ pom = File.join(folder, POM_XML)
23
+ super(pom)
24
+ add_if_exist(File.join(folder, 'target/classes'))
25
+ add_if_exist(File.join(folder, 'target/test-classes'))
26
+
27
+ # look for submodules
28
+ Dir.entries(folder).each do |dir|
29
+ next if dir =~ /^(?:\.|\.\.|src|target|pom.xml|\.settings)$/
30
+ folder = File.join(folder, dir)
31
+ add_element(MavenClasspath.new(folder)) if MavenClasspath::valid_location?(folder)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def add_if_exist(folder)
38
+ add_file_name(folder) if FileTest.exist?(folder) && FileTest.directory?(folder)
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,111 @@
1
+ require 'fileutils'
2
+ require 'javaclass/gems/zip_file'
3
+ require 'javaclass/java_language'
4
+
5
+ module JavaClass
6
+ module Classpath
7
+
8
+ # Unpack a JAR (ZIP) into a temporary folder.
9
+ # Author:: Peter Kofler
10
+ class TemporaryUnpacker
11
+
12
+ # Command templates for external too like 7zip or zip.
13
+ COMMANDS = [
14
+ # 7zip 9.20
15
+ '7za x -bd -o<folder> -y <jar> 2>&1',
16
+ # Unzip 5.42
17
+ 'unzip -o -qq <jar> -d <folder> 2>&1',
18
+ # WinZip 8.1
19
+ 'WinZip32.exe -min -e -o <jar> <folder>',
20
+ ]
21
+
22
+ # The temporary folder. This folder will be deleted after Ruby shuts down.
23
+ attr_reader :folder
24
+
25
+ # Set the given _jarfile_ to unpack.
26
+ def initialize(jarfile)
27
+ @jarfile = jarfile
28
+
29
+ if !defined?(@@unpack_strategies)
30
+ # use unzip first, fallback by hand
31
+ @@unpack_strategies = COMMANDS.map{ |c| Proc.new{ |jar, folder| TemporaryUnpacker::unpack_shell(c, jar, folder) } } +
32
+ [ Proc.new{ |jar, folder| TemporaryUnpacker::unpack_ruby(jar, folder) } ]
33
+ end
34
+ end
35
+
36
+ # Create the temporary folder where it will be unpacked to.
37
+ def create_temporary_folder
38
+ folder = File.join(find_temp_folder, "temp_#{File.basename(@jarfile)}_#{Time.now.to_i.to_s}")
39
+ FileUtils.mkdir_p(folder)
40
+ at_exit { FileUtils.rm_r(folder) }
41
+ @folder = folder
42
+ end
43
+
44
+ # Unpack the given jar file.
45
+ def unpack!
46
+ unless defined?(@folder) && @folder
47
+ raise IOError, 'no temporary folder created'
48
+ end
49
+
50
+ # Find the first working strategy and keep it
51
+ if ! @@unpack_strategies.first.call(@jarfile, @folder)
52
+ warn("Dropping unpacker for #{@jarfile}. Install 7zip or unzip!")
53
+ @@unpack_strategies.delete_at(0)
54
+ if @@unpack_strategies.empty?
55
+ raise 'no suitable unpack strategy found'
56
+ end
57
+ unpack!
58
+ end
59
+ end
60
+
61
+ # Return the temp folder if a variable is set, else returm /tmp.
62
+ def find_temp_folder
63
+ TemporaryUnpacker::escape_folder(
64
+ if ENV['TEMP']
65
+ ENV['TEMP'] # Windows
66
+ elsif ENV['TMP']
67
+ ENV['TMP']
68
+ else
69
+ '/tmp'
70
+ end
71
+ )
72
+ end
73
+
74
+ private
75
+
76
+ # Escape _folder_ if it contains blanks.
77
+ def self.escape_folder(folder)
78
+ if folder =~ / / then "\"#{folder}\"" else folder end
79
+ end
80
+
81
+ # Unpack _jarfile_ into _folder_ using external executeable using the _command_ string. Return +true+ for success.
82
+ def self.unpack_shell(command, jarfile, folder)
83
+ begin
84
+ `#{command.gsub(/<folder>/, escape_folder(folder)).gsub(/<jar>/, escape_folder(jarfile))}`
85
+ $?.to_i == 0
86
+ rescue
87
+ false
88
+ end
89
+ end
90
+
91
+ # Unpack _jarfile_ into _folder_ using Ruby's Rubyzip gem. This is very slow. Return +true+ for success.
92
+ def self.unpack_ruby(jarfile, folder)
93
+ # warn('unpacking with slow ruby unpacker')
94
+ zip_file = JavaClass::Gems::ZipFile.new(jarfile)
95
+ zip_file.entries do |entry|
96
+ name = entry.name
97
+ next unless entry.file? and name =~ JavaLanguage::CLASS_REGEX # class file
98
+
99
+ f_path = File.join(folder, entry.name)
100
+ FileUtils.mkdir_p(File.dirname(f_path))
101
+ unless File.exist?(f_path)
102
+ File.open(f_path, 'wb') { |file| file.write(zip_file.read(name)) }
103
+ end
104
+ end
105
+ true
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,144 @@
1
+ require 'delegate'
2
+ require 'javaclass/classpath/composite_classpath'
3
+
4
+ module JavaClass
5
+ module Classpath
6
+
7
+ # A delegator classpath that tracks which classes have been accessed. For an example see
8
+ # {how to find (un)referenced JARs}[link:/files/lib/generated/examples/find_referenced_modules_txt.html].
9
+ # Author:: Peter Kofler
10
+ class TrackingClasspath < SimpleDelegator
11
+
12
+ # Create a tracked instance of the _classpath_ .
13
+ def initialize(classpath)
14
+ unless classpath.respond_to?(:load_binary) || classpath.respond_to?(:load)
15
+ raise ArgumentError, "wrong type of delegatee #{classpath.class}"
16
+ end
17
+ @classpath = classpath
18
+ reset_access
19
+ super(classpath)
20
+ end
21
+
22
+ # Returns the wrapped classpath element (+self+) of this decorated classpath.
23
+ def elements
24
+ if [FolderClasspath, JarClasspath].include?(@classpath.class)
25
+ [self]
26
+ else
27
+ @classpath.elements
28
+ end
29
+ end
30
+
31
+ # Reset all prior marked access.
32
+ def reset_access
33
+ @accessed = Hash.new(0) # class_file (JavaClassFileName) => cnt
34
+ end
35
+
36
+ # Load the binary and mark the _classname_ as accessed.
37
+ def load_binary(classname)
38
+ key = to_key(classname)
39
+ mark_accessed(key)
40
+ @classpath.load_binary(key)
41
+ end
42
+
43
+ # Read and disassemble the given class _classname_ and mark as accessed.
44
+ def load(classname)
45
+ key = to_key(classname)
46
+ mark_accessed(key)
47
+ @classpath.load(key)
48
+ end
49
+
50
+ # Mark the _classname_ as accessed. Return the number of accesses so far.
51
+ def mark_accessed(classname)
52
+ key = to_key(classname)
53
+
54
+ if @classpath.includes?(key)
55
+
56
+ # hash keys need to be frozen to keep state
57
+ if !@accessed.include?(key)
58
+ key = key.freeze
59
+ end
60
+
61
+ @accessed[key] += 1
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ # Was the _classname_ accessed then return the count? If _classname_ is +nil+ then check if any class was accessed.
68
+ def accessed(classname=nil)
69
+ if classname
70
+ key = to_key(classname)
71
+ @accessed[key]
72
+ else
73
+ @accessed.values.inject(0) {|s,e| s + e }
74
+ end
75
+ end
76
+
77
+ # Return the classnames of all accessed classes. This is a list of frozen JavaClassFileName.
78
+ def all_accessed
79
+ @accessed.keys.sort
80
+ end
81
+
82
+ private
83
+
84
+ def to_key(classname)
85
+ classname.to_javaname.to_class_file
86
+ end
87
+
88
+ end
89
+
90
+ class CompositeClasspath
91
+
92
+ # Wrap the _elem_ classpath with a new TrackingClasspath and add it to the list of elements.
93
+ alias __old__add_element__ add_element
94
+
95
+ def add_element(elem)
96
+ unless @elements.find { |cpe| cpe == elem }
97
+ if [FolderClasspath, JarClasspath].include?(elem.class)
98
+ __old__add_element__(TrackingClasspath.new(elem))
99
+ else
100
+ __old__add_element__(elem)
101
+ end
102
+ end
103
+ end
104
+
105
+ # Reset all prior marked access in child elements. (See TrackingClasspath)
106
+ def reset_access
107
+ @elements.each { |e| e.reset_access }
108
+ end
109
+
110
+ # Mark the _classname_ as accessed. Return the number of accesses so far. (See TrackingClasspath)
111
+ def mark_accessed(classname)
112
+ key = to_key(classname)
113
+ found = find_element_for(key)
114
+ if found
115
+ found.mark_accessed(key)
116
+ else
117
+ nil
118
+ end
119
+ end
120
+
121
+ # Was the _classname_ accessed then return the count? If _classname_ is +nil+
122
+ # then check if any class was accessed. (See TrackingClasspath)
123
+ def accessed(classname=nil)
124
+ if classname
125
+ key = to_key(classname)
126
+ found = find_element_for(key)
127
+ if found then found.accessed(key) else 0 end
128
+ else
129
+ @elements.inject(0) do |s,e|
130
+ accessed = e.accessed
131
+ if accessed then s + accessed else s end
132
+ end
133
+ end
134
+ end
135
+
136
+ # Return the classnames of all accessed classes in child elements. (See TrackingClasspath)
137
+ def all_accessed
138
+ @elements.map { |cp| cp.all_accessed }.flatten.sort
139
+ end
140
+
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,3 @@
1
+ === Class Analysis (working on a single class)
2
+
3
+ * count outgoing references (usage) per method, similar to general imported types
@@ -0,0 +1,29 @@
1
+ require 'delegate'
2
+
3
+ module JavaClass
4
+ module ClassScanner
5
+
6
+ # Add analysis for imported types to ClassFile::JavaClassHeader.
7
+ # Author:: Peter Kofler
8
+ class ImportedTypes < SimpleDelegator
9
+
10
+ # Decorate JavaClassHeader _header_ to add imported types lazy scanner.
11
+ def initialize(header)
12
+ super(header)
13
+ @imported_types = nil
14
+ end
15
+
16
+ # Determine the imported types of this class and return their names. This does not contain the name if this class itself.
17
+ def imported_types
18
+ @imported_types ||= references.used_classes.collect { |c| c.class_name.to_classname }.sort
19
+ end
20
+
21
+ # Determine the imported types of this class which are not from the JDK. This are all imported_types - all jdk types.
22
+ def imported_3rd_party_types
23
+ imported_types.reject { |name| name.in_jdk? }
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require 'javaclass/classscanner/imported_types'
2
+
3
+ module JavaClass
4
+
5
+ # The module ClassScanner is for separating namespaces. It contains various
6
+ # decorators that scan and analyse single class files and provide additional
7
+ # information about the class, e.g. Cyclomatic Complexity. For analysis across
8
+ # whole code bases ("classpaths") see module Analyse.
9
+ # Author:: Peter Kofler
10
+ module ClassScanner
11
+
12
+ # ClassScanner factory methods to create different kind of scanner decorators.
13
+ # Author:: Peter Kofler
14
+ module Scanners
15
+
16
+ # Scan parsed _header_ for (selected) _features_ . This ties together all scanners.
17
+ def analyse(header, features=:all)
18
+ # later add feature selection if needed, argument determines delegators, :none, :some, :all
19
+ ImportedTypes.new(
20
+ header
21
+ )
22
+ end
23
+
24
+ alias :decorate analyse
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # Delegation directives.
2
+ # Author:: Peter Kofler
3
+ module DelegateDirective # :nodoc:
4
+
5
+ # Directive to create a delegating method _method_name_ to the result of my own method _delegate_ without arguments.
6
+ def delegate(method_name, delegate)
7
+ self.module_eval("def #{method_name}(*obj) #{delegate}.#{method_name}(*obj) end")
8
+ end
9
+
10
+ # Directive to create a delegating method _method_name_ to the field _field_name_ .
11
+ def delegate_field(method_name, field_name)
12
+ self.module_eval("def #{method_name}(*obj) @#{field_name}.#{method_name}(*obj) end")
13
+ end
14
+
15
+ end
@@ -0,0 +1,38 @@
1
+ require 'delegate'
2
+
3
+ module JavaClass
4
+ module Dsl
5
+
6
+ # A delegator classpath that caches loaded class files in a map by full qualified class names.
7
+ # Author:: Peter Kofler
8
+ class CachingClasspath < SimpleDelegator
9
+
10
+ # Create a cached instance of the _classpath_ (a LoadingClasspath).
11
+ def initialize(classpath)
12
+ unless classpath.respond_to? :load
13
+ raise ArgumentError, "wrong type of delegatee #{classpath.class}"
14
+ end
15
+ @classpath = classpath
16
+ @cache = Hash.new # full_name (String) => ClassEntryHeader
17
+ super(classpath)
18
+ end
19
+
20
+ # Ask the cache for the _classname_ and return it. Else delegate loading.
21
+ def load(classname)
22
+ key = classname.to_javaname.full_name
23
+ if !@cache.include?(key)
24
+ @cache[key] = @classpath.load(classname)
25
+ end
26
+ @cache[key]
27
+ end
28
+
29
+ # Load _listed_ or all classes. Duplicate method to use the cache of decorator.
30
+ def values(listed=nil, &filter)
31
+ listed ||= names(&filter)
32
+ listed.collect { |name| load(name) }
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end