Class: TreeHaver::Backends::Java::Language

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/tree_haver/backends/java.rb

Overview

Wrapper for java-tree-sitter Language

:nocov: All Java backend implementation classes require JRuby and cannot be tested on MRI/CRuby. JRuby-specific CI jobs would test this code.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(impl, path: nil, symbol: nil) ⇒ Language

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Language.



248
249
250
251
252
253
# File 'lib/tree_haver/backends/java.rb', line 248

def initialize(impl, path: nil, symbol: nil)
  @impl = impl
  @backend = :java
  @path = path
  @symbol = symbol
end

Instance Attribute Details

#backendSymbol (readonly)

The backend this language is for



237
238
239
# File 'lib/tree_haver/backends/java.rb', line 237

def backend
  @backend
end

#implObject (readonly)

Returns the value of attribute impl.



233
234
235
# File 'lib/tree_haver/backends/java.rb', line 233

def impl
  @impl
end

#pathString? (readonly)

The path this language was loaded from (if known)



241
242
243
# File 'lib/tree_haver/backends/java.rb', line 241

def path
  @path
end

#symbolString? (readonly)

The symbol name (if known)



245
246
247
# File 'lib/tree_haver/backends/java.rb', line 245

def symbol
  @symbol
end

Class Method Details

.from_library(path, symbol: nil, name: nil) ⇒ Object Also known as: from_path



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/tree_haver/backends/java.rb', line 310

def from_library(path, symbol: nil, name: nil)
  raise TreeHaver::NotAvailable, "Java backend not available" unless Java.available?

  # Derive symbol from name or path if not provided
  base_name = File.basename(path, ".*").sub(/^lib/, "")
  sym = symbol || "tree_sitter_#{name || base_name.sub(/^tree-sitter-/, "")}"

  begin
    arena = ::Java::JavaLangForeign::Arena.global
    symbol_lookup_class = ::Java::JavaLangForeign::SymbolLookup

    # IMPORTANT: Load libtree-sitter.so FIRST by name so its symbols are available
    # Grammar libraries need symbols like ts_language_version from the runtime
    # We cache this lookup at the module level
    unless Java.runtime_lookup
      # Use libraryLookup(String, Arena) to search LD_LIBRARY_PATH
      Java.runtime_lookup = symbol_lookup_class.libraryLookup("libtree-sitter.so", arena)
    end

    # Now load the grammar library
    if File.exist?(path)
      # Explicit path provided - use libraryLookup(Path, Arena)
      java_path = ::Java::JavaNioFile::Paths.get(path)
      grammar_lookup = symbol_lookup_class.libraryLookup(java_path, arena)
    else
      # Library name provided - use libraryLookup(String, Arena) to search
      # LD_LIBRARY_PATH / DYLD_LIBRARY_PATH / PATH
      grammar_lookup = symbol_lookup_class.libraryLookup(path, arena)
    end

    # Chain the lookups: grammar first, then runtime library for ts_* symbols
    # This makes ts_language_version available when Language.load() needs it
    combined_lookup = grammar_lookup.or(Java.runtime_lookup)

    java_lang = Java.java_classes[:Language].load(combined_lookup, sym)
    new(java_lang, path: path, symbol: symbol)
  rescue ::Java::JavaLang::RuntimeException => e
    cause = e.cause
    root_cause = cause&.cause || cause

    error_msg = "Failed to load language '#{sym}' from #{path}: #{e.message}"
    if root_cause.is_a?(::Java::JavaLang::UnsatisfiedLinkError)
      unresolved = root_cause.message.to_s
      if unresolved.include?("ts_language_version")
        # This specific symbol was renamed in tree-sitter 0.24
        error_msg += "\n\nVersion mismatch detected: The grammar was built against " \
          "tree-sitter < 0.24 (uses ts_language_version), but your runtime library " \
          "is tree-sitter >= 0.24 (uses ts_language_abi_version).\n\n" \
          "Solutions:\n" \
          "1. Rebuild the grammar against your version of tree-sitter\n" \
          "2. Install a matching version of tree-sitter (< 0.24)\n" \
          "3. Find a pre-built grammar compatible with tree-sitter 0.24+"
      elsif unresolved.include?("ts_language") || unresolved.include?("ts_parser")
        error_msg += "\n\nThe grammar library has unresolved tree-sitter symbols. " \
          "Ensure libtree-sitter.so is in LD_LIBRARY_PATH and version-compatible " \
          "with the grammar."
      end
    end
    raise TreeHaver::NotAvailable, error_msg
  rescue ::Java::JavaLang::UnsatisfiedLinkError => e
    raise TreeHaver::NotAvailable,
      "Native library error loading #{path}: #{e.message}. " \
        "Ensure the library is in LD_LIBRARY_PATH."
  rescue ::Java::JavaLang::IllegalArgumentException => e
    raise TreeHaver::NotAvailable,
      "Could not find library '#{path}': #{e.message}. " \
        "Ensure it's in LD_LIBRARY_PATH or provide an absolute path."
  end
end

.load_by_name(name) ⇒ Language

Load a language by name from java-tree-sitter grammar JARs

This method loads grammars that are packaged as java-tree-sitter JARs from Maven Central. These JARs include the native grammar library pre-built for Java’s Foreign Function API.

Examples:

# First, add the grammar JAR to TREE_SITTER_JAVA_JARS_DIR:
# tree-sitter-toml-0.23.2.jar from Maven Central
lang = TreeHaver::Backends::Java::Language.load_by_name("toml")

Raises:



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/tree_haver/backends/java.rb', line 394

def load_by_name(name)
  raise TreeHaver::NotAvailable, "Java backend not available" unless Java.available?

  begin
    # java-tree-sitter's Language.load(String) searches for the language
    # in the classpath using standard naming conventions
    java_lang = Java.java_classes[:Language].load(name)
    new(java_lang, symbol: "tree_sitter_#{name}")
  rescue ::Java::JavaLang::RuntimeException => e
    raise TreeHaver::NotAvailable,
      "Failed to load language '#{name}': #{e.message}. " \
        "Ensure the grammar JAR (e.g., tree-sitter-#{name}-X.Y.Z.jar) " \
        "is in TREE_SITTER_JAVA_JARS_DIR."
  end
end

Instance Method Details

#<=>(other) ⇒ Integer?

Compare languages for equality

Java languages are equal if they have the same backend, path, and symbol. Path and symbol uniquely identify a loaded language.



262
263
264
265
266
267
268
269
270
271
# File 'lib/tree_haver/backends/java.rb', line 262

def <=>(other)
  return unless other.is_a?(Language)
  return unless other.backend == @backend

  # Compare by path first, then symbol
  cmp = (@path || "") <=> (other.path || "")
  return cmp if cmp.nonzero?

  (@symbol || "") <=> (other.symbol || "")
end

#hashInteger

Hash value for this language (for use in Sets/Hashes)



275
276
277
# File 'lib/tree_haver/backends/java.rb', line 275

def hash
  [@backend, @path, @symbol].hash
end