Class: Squash::Java::Namespace

Inherits:
Object
  • Object
show all
Defined in:
lib/squash/java/namespace.rb

Overview

A Ruby representation of the packages, classes, methods, and fields of a Java project, with their full and obfuscated names. The RenameLog class loads a Namespace from a yGuard or ProGuard rename log file.

Note: Some of the finder methods of this class and its enclosed classes are find-or-create-type methods. Read the method documentation for each method carefully.

**Another note:** A distinction is made between full and partial names (e.g., “MyClass” vs. “com.mycompany.MyClass”), and cleartext and obfuscated names (e.g., “com.mycompany.MyClass” vs. “com.A.B”). Note that there are four possible combinations of these states. Method docs will state what naming format is expected for each parameter.

Constant Summary collapse

METHOD_REGEX =
/^([a-z0-9_.$\[\]]+) ([a-z0-9_$]+)\(([a-z$0-9_.\[\] ,]*)\)/i

Instance Method Summary collapse

Constructor Details

#initializeNamespace

Creates a new empty Namespace.



39
40
41
# File 'lib/squash/java/namespace.rb', line 39

def initialize
  @package_roots = Set.new
end

Instance Method Details

#add_class_alias(name, obfuscation) ⇒ Squash::Java::Class

Associates a full class name with an obfuscated name.

Parameters:

  • name (String)

    The full class name (e.g., “com.foo.bar.Baz”).

  • obfuscation (String)

    The obfuscated name for just the last segment of the full name (e.g., “A”).

Returns:



114
115
116
117
118
# File 'lib/squash/java/namespace.rb', line 114

def add_class_alias(name, obfuscation)
  cl             = klass(name)
  cl.obfuscation = obfuscation
  return cl
end

#add_method_alias(class_or_name, method_name, obfuscation) ⇒ Squash::Java::Method

Associates a method name with an obfuscated alias.

Parameters:

  • class_or_name (String, Squash::Java::Class)

    A full class name (e.g., “com.foo.bar.Baz”), or a class object.

  • method_name (String)

    A method name, with return value and argument types (e.g., “com.foo.Type1 methodName(com.foo.Type2, int[])”).

Returns:



128
129
130
131
132
133
# File 'lib/squash/java/namespace.rb', line 128

def add_method_alias(class_or_name, method_name, obfuscation)
  cl               = (class_or_name.kind_of?(Squash::Java::Class) ? class_or_name : klass(class_or_name))
  meth             = java_method(cl, method_name)
  meth.obfuscation = obfuscation
  return meth
end

#add_package_alias(name, obfuscation) ⇒ Squash::Java::Package

Associates a full package name with an obfuscated name.

Parameters:

  • name (String)

    The full package name (e.g., “com.foo.bar”).

  • obfuscation (String)

    The obfuscated name for just the last segment of the full name (e.g., “A”).

Returns:



101
102
103
104
105
# File 'lib/squash/java/namespace.rb', line 101

def add_package_alias(name, obfuscation)
  pkg             = package(name)
  pkg.obfuscation = obfuscation
  return pkg
end

#argument(type_descriptor) ⇒ Squash::Java::Argument

Creates a new Argument for a given type descriptor. This can be a primitive (e.g., “float”) or a full class name (e.g., “com.foo.Bar”), and can be a scalar or an array (e.g., “float[]” or “com.foo.Bar[]”). **Finds or creates** the Type, and **always creates** a new Argument.

Parameters:

  • type_descriptor (String)

    The type description.

Returns:



261
262
263
264
265
# File 'lib/squash/java/namespace.rb', line 261

def argument(type_descriptor)
  dimensionality = type_descriptor.scan(/\[\]/).size
  type_name      = type_descriptor.gsub(/\[\]/, '')
  Squash::Java::Argument.new type(type_name), dimensionality
end

#find_files(root) ⇒ Object

Attempts to locate the paths to the Classes defined in this Namespace. Classes must be defined in files named after the class, organized into folders structured after the classes’ packages. For example, the source of the ‘com.foo.bar.Baz` class should be defined in a file located at “com/foo/bar/Baz.java”, somewhere inside your project root. This is a “best guess” attempt and will not work every time.

Once this method is complete, any Class objects under this namespace that were successfully matched will have their path attributes set.

Parameters:

  • root (String)

    The project root. All source directories must be under this root.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/squash/java/namespace.rb', line 58

def find_files(root, package_or_class=nil)
  case package_or_class
    when nil
      root.sub! /\/$/, ''
      @package_roots.each { |pkg| find_files root, pkg }
    when Squash::Java::Package
      package_or_class.classes.each { |cl| find_files root, cl }
      package_or_class.children.each { |pkg| find_files root, pkg }
    when Squash::Java::Class
      class_subpath = package_or_class.subpath
      Find.find(root) do |project_path|
        if project_path[0, root.length + 2] == root + '/.'
          Find.prune
          next
        end
        if project_path[-class_subpath.length, class_subpath.length] == class_subpath
          package_or_class.path = project_path.sub(/^#{Regexp.escape root}\//, '')
        end
      end
  end
end

#java_method(klass, name) ⇒ Squash::Java::Method

**Finds or creates** a method by its name and parent class. Polymorphism is supported: Two methods can share the same name so long as their argument count or types are different.

Parameters:

  • klass (Squash::Java::Class)

    The class containing the method.

  • name (String)

    The method name, with return type and arguments as full, unobfuscated types (e.g., “com.foo.Bar myMethod(com.foo.Baz, int[])”.

Returns:



222
223
224
225
226
227
228
229
230
231
# File 'lib/squash/java/namespace.rb', line 222

def java_method(klass, name)
  matches = name.match(METHOD_REGEX) or raise "Invalid method name #{name.inspect}"
  return_type = argument(matches[1])
  method_name = matches[2]
  args        = matches[3].split(/,\s*/).map { |arg| argument(arg) }
  args = [] if matches[3].empty?

  klass.java_methods.detect { |meth| meth.name == method_name && meth.arguments == args } ||
      Squash::Java::Method.new(klass, method_name, return_type, *args)
end

#obfuscated_argument(type_descriptor) ⇒ Squash::Java::Argument?

Creates a new Argument for a given type descriptor, which can be fully or partially obfuscated. This can be an unobfuscated primitive (e.g., “float”) or a possibly-obfuscated full class name (e.g., “com.foo.A”), and can be a scalar or an array (e.g., “float[]” or “com.foo.A[]”). Finds the Type, and **always creates** a new Argument. Returns ‘nil` for unknown types.

Parameters:

  • type_descriptor (String)

    The type description.

Returns:



278
279
280
281
282
283
284
# File 'lib/squash/java/namespace.rb', line 278

def obfuscated_argument(type_descriptor)
  dimensionality = type_descriptor.scan(/\[\]/).size
  type_name      = type_descriptor.gsub(/\[\]/, '')
  type           = obfuscated_type(type_name)
  return nil unless type
  Squash::Java::Argument.new type, dimensionality
end

#obfuscated_class(identifier) ⇒ Squash::Java::Class?

Finds a class by its obfuscated (or partially obfuscated) full name. (Technically it also works as a find-only variant of #klass since all some, or none of the name need be obfuscated.)

Parameters:

  • identifier (String)

    An obfuscated full class name (e.g., “com.foo.A.B”).

Returns:



184
185
186
187
188
189
190
191
# File 'lib/squash/java/namespace.rb', line 184

def obfuscated_class(identifier)
  parts      = identifier.split('.')
  class_name = parts.pop
  pkg        = obfuscated_package(parts.join('.'))
  return nil unless pkg

  pkg.classes.detect { |cl| cl.obfuscation == class_name || cl.name == class_name }
end

#obfuscated_method(klass, name) ⇒ Squash::Java::Method?

Finds a method by its obfuscated name and parent class. Polymorphism is supported: Two methods can share the same name so long as their argument count or types are different.

Parameters:

  • klass (Squash::Java::Class)

    The class containing the method.

  • name (String)

    The obfuscated method name, with return type and arguments as full, obfuscated types (e.g., “com.foo.A myMethod(com.foo.B, int[])”.

Returns:



242
243
244
245
246
247
248
249
# File 'lib/squash/java/namespace.rb', line 242

def obfuscated_method(klass, name)
  matches = name.match(METHOD_REGEX) or raise "Invalid method name #{name.inspect}"
  return_type = obfuscated_type(matches[1])
  method_name = matches[2]
  args        = matches[3].split(/,\s*/).map { |arg| obfuscated_argument(arg) }
  args = [] if matches[3].empty?
  klass.java_methods.detect { |m| m.obfuscation == method_name && m.arguments == args }
end

#obfuscated_package(identifier) ⇒ Squash::Java::Package?

Finds a package by its obfuscated (or partially obfuscated) full name. (Technically it also works as a find-only variant of #package since all, some, or none of the name need be obfuscated.)

Parameters:

  • identifier (String)

    An obfuscated full package name (e.g., “com.foo.A”).

Returns:



165
166
167
168
169
170
171
172
173
174
# File 'lib/squash/java/namespace.rb', line 165

def obfuscated_package(identifier)
  parts     = identifier.split('.')
  root_name = parts.shift
  root      = @package_roots.detect { |pkg| pkg.name == root_name }
  if parts.empty?
    root
  else
    root ? root.find_obfuscated(parts.join('.')) : nil
  end
end

#obfuscated_type(name) ⇒ Squash::Java::Type?

Finds a class or primitive type by its obfuscated name. Primitives are never obfuscated.

Parameters:

  • name (String)

    The obfuscated full class name (e.g., “com.squareup.A.B”) or full primitive name

Returns:



209
210
211
# File 'lib/squash/java/namespace.rb', line 209

def obfuscated_type(name)
  Squash::Java::PRIMITIVES.detect { |prim| prim.name == name } || obfuscated_class(name)
end

#package(identifier) ⇒ Squash::Java::Package Also known as: klass

**Finds or creates** a package by its full name.

Parameters:

  • identifier (String)

    A full package name (e.g., “com.foo.bar”).

Returns:



140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/squash/java/namespace.rb', line 140

def package(identifier)
  parts     = identifier.split('.')
  root_name = parts.shift
  root      = @package_roots.detect { |pkg| pkg.name == root_name } || begin
    pkg = Squash::Java::Package.new(root_name)
    @package_roots << pkg
    pkg
  end
  if parts.empty?
    root
  else
    root.find_or_create(parts.join('.'))
  end
end

#path_for_class(klass) ⇒ String?

Returns the path to a Class‘s source .java file, relative to the project root, if a) the class exists in the namespace, and b) the class has a known path.

Parameters:

  • klass (String)

    The full class name (parts of which can be obfuscated), e.g., “com.foo.A.Baz”.

Returns:

  • (String, nil)

    The path to the class’s source file, if known.

See Also:



89
90
91
92
# File 'lib/squash/java/namespace.rb', line 89

def path_for_class(klass)
  cl = obfuscated_class(klass)
  cl ? cl.path : nil
end

#type(name) ⇒ Squash::Java::Type

**Finds or creates** a primitive or class type by its name.

Parameters:

  • name (String)

    The type name (e.g., “int” or “FooClass”).

Returns:



198
199
200
# File 'lib/squash/java/namespace.rb', line 198

def type(name)
  Squash::Java::PRIMITIVES.detect { |prim| prim.name == name } || klass(name)
end