javaclass 0.4.1 → 0.4.2
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.
- checksums.yaml +7 -0
- data/Rakefile +188 -194
- data/Readme.txt +33 -29
- data/examples/generate_class_lists.rb +16 -9
- data/history.txt +15 -4
- data/javaclass.gemspec +10 -8
- data/lib/generated/examples/chart_class_dependencies.txt +35 -0
- data/lib/generated/examples/chart_module_dependencies.txt +43 -0
- data/lib/generated/examples/check_interface_names.txt +36 -0
- data/lib/generated/examples/count_classes_in_modules.txt +31 -0
- data/lib/generated/examples/cumulative_dependencies.txt +28 -0
- data/lib/generated/examples/find_all_imported_types.txt +38 -0
- data/lib/generated/examples/find_incoming_dependency_graph.txt +73 -0
- data/lib/generated/examples/find_layers_of_modules.txt +70 -0
- data/lib/generated/examples/find_referenced_modules.txt +41 -0
- data/lib/generated/examples/find_unreferenced_classes.txt +66 -0
- data/lib/generated/examples/generate_class_lists.txt +53 -0
- data/lib/generated/examples/show_jar_api.txt +64 -0
- data/lib/generated/examples/simple_usage.txt +38 -0
- data/lib/javaclass/classfile/access_flag_constants.rb +32 -5
- data/lib/javaclass/classfile/access_flags.rb +39 -20
- data/lib/javaclass/classfile/attributes/attributes.rb +134 -0
- data/lib/javaclass/classfile/class_access_flags.rb +37 -0
- data/lib/javaclass/classfile/class_file_attributes.rb +62 -0
- data/lib/javaclass/classfile/class_version.rb +0 -1
- data/lib/javaclass/classfile/constant_pool.rb +17 -11
- data/lib/javaclass/classfile/constants/single_reference.rb +53 -0
- data/lib/javaclass/classfile/fields.rb +37 -0
- data/lib/javaclass/classfile/java_class_header.rb +22 -13
- data/lib/javaclass/classfile/java_class_header_shortcuts.rb +6 -2
- data/lib/javaclass/classfile/methods.rb +37 -0
- data/lib/javaclass/classlist/jar_searcher.rb +38 -4
- data/lib/javaclass/classpath/temporary_unpacker.rb +1 -1
- data/lib/javaclass/string_hexdump.rb +1 -1
- data/license.txt +7 -7
- data/test/data/access_flags/{AccessFlagsTestInner$1.class → AccessFlagsTestAnonym$1.class} +0 -0
- data/test/data/access_flags/{AccessFlagsTestInner.class → AccessFlagsTestAnonym.class} +0 -0
- data/test/data/access_flags/AccessFlagsTestAnonym.java +9 -0
- data/test/data/access_flags/AccessFlagsTestPublic_javap.txt +1 -0
- data/test/data/constant_pool/Java8_JavaFX_Animation$1_Tag15.class +0 -0
- data/test/data/constant_pool/Java9_Activation_module-info_Tag20.class +0 -0
- data/test/data/jar_searcher/JarClassListTest.jar +0 -0
- data/test/data/jar_searcher/PublicClass.java +44 -1
- data/test/data/jar_searcher/make.bat +1 -2
- data/test/{test_access_flags.rb → test_class_access_flags.rb} +91 -93
- data/test/test_class_file_attributes.rb +57 -0
- data/test/test_constant_pool.rb +31 -0
- data/test/test_jar_searcher.rb +40 -7
- data/test/test_javaclass_api.rb +2 -2
- data/test/test_string_hexdump.rb +4 -1
- data/test/ts_all_tests.rb +4 -2
- metadata +100 -116
- data/test/data/access_flags/AccessFlagsTestInner$2.class +0 -0
- data/test/data/access_flags/AccessFlagsTestInner.java +0 -13
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'javaclass/classfile/access_flags'
|
2
|
+
require 'javaclass/classfile/class_format_error'
|
3
|
+
|
4
|
+
module JavaClass
|
5
|
+
module ClassFile
|
6
|
+
|
7
|
+
# The access flags of a class or interface.
|
8
|
+
# Author:: Peter Kofler
|
9
|
+
class ClassAccessFlags < AccessFlags
|
10
|
+
|
11
|
+
# Bitmask for unknown/not supported flags on classes.
|
12
|
+
ACC_OTHER = 0xffff ^ ACC_PUBLIC ^ ACC_STATIC ^ ACC_FINAL ^ ACC_SUPER ^ ACC_INTERFACE ^ ACC_ABSTRACT ^ ACC_SYNTHETIC ^ ACC_ENUM ^ ACC_ANNOTATION ^ ACC_MODULE
|
13
|
+
|
14
|
+
def initialize(flags)
|
15
|
+
super
|
16
|
+
assert_flags
|
17
|
+
end
|
18
|
+
|
19
|
+
def assert_flags
|
20
|
+
raise ClassFormatError, "inconsistent flags #{flags} (other value #{flags & ACC_OTHER})" if (flags & ACC_OTHER) != 0
|
21
|
+
end
|
22
|
+
private :assert_flags
|
23
|
+
|
24
|
+
# Is this class a purely abstract class (and not an interface)?
|
25
|
+
def abstract_class?
|
26
|
+
abstract? && !interface_class?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Is this class an interface (and not an annotation)?
|
30
|
+
def interface_class?
|
31
|
+
interface? && !annotation?
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'javaclass/java_name'
|
2
|
+
|
3
|
+
module JavaClass
|
4
|
+
module ClassFile
|
5
|
+
|
6
|
+
# Class file attributes.
|
7
|
+
# Author:: Peter Kofler
|
8
|
+
class ClassFileAttributes
|
9
|
+
|
10
|
+
def initialize(attributes, this_class)
|
11
|
+
@attributes = attributes
|
12
|
+
@this_class = this_class
|
13
|
+
end
|
14
|
+
|
15
|
+
# Name of the source file.
|
16
|
+
def source_file
|
17
|
+
a = @attributes.with('SourceFile')
|
18
|
+
if a then a.source_file else '<not set>' end
|
19
|
+
end
|
20
|
+
|
21
|
+
# List of inner classes +Attributes::InnerClass+ with name and access flags.
|
22
|
+
def inner_classes
|
23
|
+
a = @attributes.with('InnerClasses')
|
24
|
+
if a then a.inner_classes else [] end
|
25
|
+
end
|
26
|
+
|
27
|
+
def inner_class?
|
28
|
+
if inner_classes.find { |inner| inner.class_name == @this_class }
|
29
|
+
true
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Defines an accessible inner class, which is a static inner class which is not synthetic.
|
36
|
+
def static_inner_class?
|
37
|
+
if inner_classes.find { |inner| inner.class_name == @this_class &&
|
38
|
+
inner.access_flags.static? &&
|
39
|
+
(!inner.access_flags.private? || inner.access_flags.protected?) }
|
40
|
+
true
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def anonymous?
|
47
|
+
inner_class? && @this_class =~ /\$\d+$/
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return outer class name for inner classes, or the current class name.
|
51
|
+
def outer_class
|
52
|
+
if inner_class?
|
53
|
+
JavaVMName.new(@this_class[/^[^$]+/])
|
54
|
+
else
|
55
|
+
@this_class
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -13,19 +13,25 @@ module JavaClass
|
|
13
13
|
|
14
14
|
# Types of constants by their +tag+.
|
15
15
|
CONSTANT_TYPE_TAGS = {
|
16
|
-
CLASS_TAG
|
17
|
-
FIELD_TAG
|
18
|
-
METHOD_TAG
|
16
|
+
CLASS_TAG = 7 => Constants::ConstantClass,
|
17
|
+
FIELD_TAG = 9 => Constants::ConstantField,
|
18
|
+
METHOD_TAG = 10 => Constants::ConstantMethod,
|
19
19
|
INTERFACE_METHOD_TAG = 11 => Constants::ConstantInterfaceMethod,
|
20
|
-
STRING_TAG
|
21
|
-
INT_TAG
|
22
|
-
FLOAT_TAG
|
23
|
-
LONG_TAG
|
24
|
-
DOUBLE_TAG
|
25
|
-
NAME_AND_TYPE_TAG
|
26
|
-
ASCIZ_TAG
|
20
|
+
STRING_TAG = 8 => Constants::ConstantString,
|
21
|
+
INT_TAG = 3 => Constants::ConstantInt,
|
22
|
+
FLOAT_TAG = 4 => Constants::ConstantFloat,
|
23
|
+
LONG_TAG = 5 => Constants::ConstantLong,
|
24
|
+
DOUBLE_TAG = 6 => Constants::ConstantDouble,
|
25
|
+
NAME_AND_TYPE_TAG = 12 => Constants::ConstantNameAndType,
|
26
|
+
ASCIZ_TAG = 1 => Constants::ConstantAsciz,
|
27
|
+
# Java 1.7
|
28
|
+
METHOD_HANDLE_TAG = 15 => Constants::ConstantMethodHandle,
|
29
|
+
METHOD_TYPE_TAG = 16 => Constants::ConstantMethodType,
|
30
|
+
INVOKE_DYNAMIC_TAG = 18 => Constants::ConstantInvokeDynamic,
|
31
|
+
# Java 9
|
32
|
+
MODULE_TAG = 19 => Constants::ConstantModule,
|
33
|
+
PACKAGE_TAG = 20 => Constants::ConstantPackage,
|
27
34
|
}
|
28
|
-
# TODO Java7/Java8 JDK, implement 3 new tags, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
|
29
35
|
|
30
36
|
# Size of the whole constant pool in bytes.
|
31
37
|
attr_reader :size
|
@@ -68,6 +68,59 @@ module JavaClass
|
|
68
68
|
alias string_value first_value
|
69
69
|
end
|
70
70
|
|
71
|
+
class ConstantMethodHandle < SingleReference # ZenTest SKIP
|
72
|
+
alias method_handle_index first_index
|
73
|
+
|
74
|
+
def initialize(pool, data, start)
|
75
|
+
super(pool, data, start)
|
76
|
+
|
77
|
+
@kind = data.u1(start+1)
|
78
|
+
@first_index = data.u2(start+2)
|
79
|
+
@size = 4
|
80
|
+
end
|
81
|
+
alias method_handle_value first_value
|
82
|
+
end
|
83
|
+
|
84
|
+
class ConstantMethodType < SingleReference # ZenTest SKIP
|
85
|
+
alias method_type_index first_index
|
86
|
+
|
87
|
+
def initialize(pool, data, start)
|
88
|
+
super(pool, data, start)
|
89
|
+
end
|
90
|
+
alias method_type_value first_value
|
91
|
+
end
|
92
|
+
|
93
|
+
class ConstantInvokeDynamic < SingleReference # ZenTest SKIP
|
94
|
+
alias name_and_type_index first_index
|
95
|
+
|
96
|
+
def initialize(pool, data, start)
|
97
|
+
super(pool, data, start)
|
98
|
+
|
99
|
+
@bootstrap_method_attr_index = data.u2(start+1)
|
100
|
+
@first_index = data.u2(start+3)
|
101
|
+
@size = 5
|
102
|
+
end
|
103
|
+
alias name_and_type_value first_value
|
104
|
+
end
|
105
|
+
|
106
|
+
class ConstantModule < SingleReference # ZenTest SKIP
|
107
|
+
alias name_index first_index
|
108
|
+
|
109
|
+
def initialize(pool, data, start)
|
110
|
+
super(pool, data, start)
|
111
|
+
end
|
112
|
+
alias name_value first_value
|
113
|
+
end
|
114
|
+
|
115
|
+
class ConstantPackage < SingleReference # ZenTest SKIP
|
116
|
+
alias name_index first_index
|
117
|
+
|
118
|
+
def initialize(pool, data, start)
|
119
|
+
super(pool, data, start)
|
120
|
+
end
|
121
|
+
alias name_value first_value
|
122
|
+
end
|
123
|
+
|
71
124
|
end
|
72
125
|
end
|
73
126
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'javaclass/classfile/attributes/attributes'
|
2
|
+
|
3
|
+
module JavaClass
|
4
|
+
module ClassFile
|
5
|
+
|
6
|
+
# Container of the fields - skips the fields for now.
|
7
|
+
# Author:: Peter Kofler
|
8
|
+
class Fields # :nodoc:
|
9
|
+
|
10
|
+
# Size of the whole fields structure in bytes.
|
11
|
+
attr_reader :size
|
12
|
+
|
13
|
+
# Parse the field structure from the bytes _data_ beginning at position _start_.
|
14
|
+
def initialize(data, start, constant_pool)
|
15
|
+
count = data.u2(start)
|
16
|
+
@size = 2
|
17
|
+
|
18
|
+
(1..count).each do |i|
|
19
|
+
# TODO Implement parsing of fields into Field class
|
20
|
+
|
21
|
+
# access_flags = data.u2(start + @size) # later ... FieldAccessFlag.new(data, start + @size)
|
22
|
+
# @size += 2
|
23
|
+
# name_index = data.u2(start + @size) # later ... get from ConstantPool
|
24
|
+
# @size += 2
|
25
|
+
# descriptor_index = data.u2(start + @size) # later ... get from ConstantPool
|
26
|
+
# @size += 2
|
27
|
+
@size += 6
|
28
|
+
|
29
|
+
attributes = Attributes::Attributes.new(data, start + @size, constant_pool)
|
30
|
+
@size += attributes.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -2,8 +2,12 @@ require 'javaclass/string_ux'
|
|
2
2
|
require 'javaclass/classfile/class_magic'
|
3
3
|
require 'javaclass/classfile/class_version'
|
4
4
|
require 'javaclass/classfile/constant_pool'
|
5
|
+
require 'javaclass/classfile/class_access_flags'
|
6
|
+
require 'javaclass/classfile/fields'
|
7
|
+
require 'javaclass/classfile/methods'
|
8
|
+
require 'javaclass/classfile/attributes/attributes'
|
9
|
+
require 'javaclass/classfile/class_file_attributes'
|
5
10
|
require 'javaclass/classfile/references'
|
6
|
-
require 'javaclass/classfile/access_flags'
|
7
11
|
require 'javaclass/java_name'
|
8
12
|
|
9
13
|
module JavaClass
|
@@ -19,7 +23,7 @@ module JavaClass
|
|
19
23
|
# Parse and disassemble Java class files, similar to the +javap+ command.
|
20
24
|
# Provides all information of a Java class file. This is just a container for all kind of
|
21
25
|
# specialised elements. The constuctor parses and creates all contained elements.
|
22
|
-
# See::
|
26
|
+
# See:: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
|
23
27
|
# See:: {en.wikipedia.org/wiki/Class}[http://en.wikipedia.org/wiki/Class_(file_format)]
|
24
28
|
# Author:: Peter Kofler
|
25
29
|
class JavaClassHeader
|
@@ -30,25 +34,29 @@ module JavaClass
|
|
30
34
|
attr_reader :access_flags
|
31
35
|
attr_reader :references
|
32
36
|
attr_reader :interfaces
|
37
|
+
attr_reader :attributes
|
33
38
|
|
34
39
|
# Create a header with the binary _data_ from the class file.
|
35
40
|
def initialize(data)
|
41
|
+
pos = 0
|
36
42
|
|
37
43
|
# ClassFile {
|
38
44
|
# u4 magic;
|
39
45
|
@magic = ClassMagic.new(data)
|
46
|
+
pos += 4
|
40
47
|
|
41
48
|
# u2 minor_version;
|
42
49
|
# u2 major_version;
|
43
50
|
@version = ClassVersion.new(data)
|
51
|
+
pos += 4
|
44
52
|
|
45
53
|
# u2 constant_pool_count;
|
46
54
|
# cp_info constant_pool[constant_pool_count-1];
|
47
55
|
@constant_pool = ConstantPool.new(data)
|
48
|
-
pos
|
56
|
+
pos += @constant_pool.size
|
49
57
|
|
50
58
|
# u2 access_flags;
|
51
|
-
@access_flags =
|
59
|
+
@access_flags = ClassAccessFlags.new(data.u2(pos))
|
52
60
|
pos += 2
|
53
61
|
|
54
62
|
# u2 this_class;
|
@@ -65,21 +73,22 @@ module JavaClass
|
|
65
73
|
@interfaces = data.u2rep(count, pos + 2).collect { |i| @constant_pool.class_item(i) }
|
66
74
|
pos += 2 + count*2
|
67
75
|
|
68
|
-
# TODO Implement parsing of fields and methods of the JVM spec
|
69
76
|
# u2 fields_count;
|
70
77
|
# field_info fields[fields_count];
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
78
|
+
@fields = Fields.new(data, pos, @constant_pool)
|
79
|
+
pos += @fields.size
|
80
|
+
|
75
81
|
# u2 methods_count;
|
76
82
|
# method_info methods[methods_count];
|
77
|
-
|
78
|
-
|
79
|
-
# pos += 2 + count*2
|
83
|
+
@methods = Methods.new(data, pos, @constant_pool)
|
84
|
+
pos += @methods.size
|
80
85
|
|
81
86
|
# u2 attributes_count;
|
82
87
|
# attribute_info attributes[attributes_count];
|
88
|
+
attr = Attributes::Attributes.new(data, pos, @constant_pool)
|
89
|
+
pos += attr.size
|
90
|
+
@attributes = ClassFileAttributes.new(attr, this_class)
|
91
|
+
|
83
92
|
# }
|
84
93
|
@references = References.new(@constant_pool, @this_class_idx)
|
85
94
|
|
@@ -113,7 +122,7 @@ module JavaClass
|
|
113
122
|
ext = super_class ? " extends #{super_class.to_classname}" : ''
|
114
123
|
int = !@interfaces.empty? ? " implements #{@interfaces.join(',')}" : ''
|
115
124
|
d << "#{mod}class #{this_class.to_classname}#{ext}#{int}"
|
116
|
-
|
125
|
+
d << " SourceFile: \"#{attributes.source_file}\""
|
117
126
|
d += @version.dump
|
118
127
|
d += @constant_pool.dump
|
119
128
|
d << ''
|
@@ -5,14 +5,18 @@ module JavaClass
|
|
5
5
|
|
6
6
|
# Is this class an interface (and not an annotation)?
|
7
7
|
def interface?
|
8
|
-
access_flags.
|
8
|
+
access_flags.interface_class?
|
9
9
|
end
|
10
10
|
|
11
11
|
# Is this class an abstract class (and not an interface)?
|
12
12
|
def abstract_class?
|
13
|
-
access_flags.
|
13
|
+
access_flags.abstract_class?
|
14
14
|
end
|
15
15
|
|
16
|
+
def inner?
|
17
|
+
attributes.inner?
|
18
|
+
end
|
19
|
+
|
16
20
|
end
|
17
21
|
|
18
22
|
# TODO add tests for this
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'javaclass/classfile/attributes/attributes'
|
2
|
+
|
3
|
+
module JavaClass
|
4
|
+
module ClassFile
|
5
|
+
|
6
|
+
# Container of the methods - skips the fields for now.
|
7
|
+
# Author:: Peter Kofler
|
8
|
+
class Methods # :nodoc:
|
9
|
+
|
10
|
+
# Size of the whole methods structure in bytes.
|
11
|
+
attr_reader :size
|
12
|
+
|
13
|
+
# Parse the method structure from the bytes _data_ beginning at position _start_.
|
14
|
+
def initialize(data, start, constant_pool)
|
15
|
+
count = data.u2(start)
|
16
|
+
@size = 2
|
17
|
+
|
18
|
+
(1..count).each do |i|
|
19
|
+
# TODO Implement parsing of methods into Method class
|
20
|
+
|
21
|
+
# access_flags = data.u2(start + @size) # later ... MethodAccessFlag.new(data, start + @size)
|
22
|
+
# @size += 2
|
23
|
+
# name_index = data.u2(start + @size) # later ... get from ConstantPool
|
24
|
+
# @size += 2
|
25
|
+
# descriptor_index = data.u2(start + @size) # later ... get from ConstantPool
|
26
|
+
# @size += 2
|
27
|
+
@size += 6
|
28
|
+
|
29
|
+
attributes = Attributes::Attributes.new(data, start + @size, constant_pool)
|
30
|
+
@size += attributes.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -47,16 +47,49 @@ module JavaClass
|
|
47
47
|
# Return true if the _classfile_ in the given _classpath_ is public. This is expensive because the JAR file is opened and the
|
48
48
|
# _classfile_ is extracted and read.
|
49
49
|
def public?(classpath, classfile)
|
50
|
+
# temporal hack, store @classpath and @header
|
51
|
+
@classpath = classpath
|
50
52
|
begin
|
51
|
-
header = ClassFile::JavaClassHeader.new(classpath.load_binary(classfile))
|
53
|
+
@header = ClassFile::JavaClassHeader.new(classpath.load_binary(classfile))
|
52
54
|
rescue JavaClass::ClassFile::ClassFormatError => ex
|
53
55
|
ex.add_classname(classfile, classpath.to_s)
|
54
56
|
raise ex
|
55
57
|
end
|
56
|
-
header.magic.check("invalid java class #{classfile}")
|
57
|
-
header.access_flags.accessible?
|
58
|
+
@header.magic.check("invalid java class #{classfile}")
|
59
|
+
@header.access_flags.accessible?
|
58
60
|
end
|
59
61
|
|
62
|
+
def accessible?
|
63
|
+
if @skip_package_classes && !@header.access_flags.accessible?
|
64
|
+
# not public and we only want public
|
65
|
+
false
|
66
|
+
|
67
|
+
elsif @skip_inner_classes || !@header.attributes.inner_class?
|
68
|
+
# no inner classes have been collected, everything is accessible due its public flag
|
69
|
+
# or this is not an inner class, so everything is OK
|
70
|
+
true
|
71
|
+
|
72
|
+
# it is an inner class
|
73
|
+
|
74
|
+
elsif @header.access_flags.synthetic? || @header.attributes.anonymous?
|
75
|
+
# the inner class is anonymous or synthetic, not accessible
|
76
|
+
false
|
77
|
+
|
78
|
+
elsif !@header.attributes.static_inner_class?
|
79
|
+
# must be static (and not private nor protected) to be accessible
|
80
|
+
false
|
81
|
+
|
82
|
+
elsif !@skip_package_classes
|
83
|
+
# inner class is public or package access, OK
|
84
|
+
true
|
85
|
+
|
86
|
+
else
|
87
|
+
# inner class is public, but parent class might be package only
|
88
|
+
public?(@classpath, @header.attributes.outer_class.to_class_file)
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
60
93
|
# Compile the class list for the given _version_ of Java. This searches the _path_ for zips and JARs
|
61
94
|
# and adds them to the given _list_ of found classes. _version_ is a number >= 0, e.g. 2 for JDK 1.2.
|
62
95
|
# _list_ must provide a <code>add_class(entry, is_public, version)</code> method.
|
@@ -72,7 +105,8 @@ module JavaClass
|
|
72
105
|
def add_list_from_classpath(version, classpath, list)
|
73
106
|
filter_classes(classpath.names).each do |entry|
|
74
107
|
is_public = public?(classpath, entry)
|
75
|
-
next if
|
108
|
+
next if !accessible?
|
109
|
+
|
76
110
|
list.add_class(entry, is_public, version) if list
|
77
111
|
yield(entry, is_public, version) if block_given?
|
78
112
|
end
|