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.



254
255
256
257
258
259
# File 'lib/tree_haver/backends/java.rb', line 254

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

Returns:

  • (Symbol)


243
244
245
# File 'lib/tree_haver/backends/java.rb', line 243

def backend
  @backend
end

#implObject (readonly)

Returns the value of attribute impl.



239
240
241
# File 'lib/tree_haver/backends/java.rb', line 239

def impl
  @impl
end

#pathString? (readonly)

The path this language was loaded from (if known)

Returns:

  • (String, nil)


247
248
249
# File 'lib/tree_haver/backends/java.rb', line 247

def path
  @path
end

#symbolString? (readonly)

The symbol name (if known)

Returns:

  • (String, nil)


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

def symbol
  @symbol
end

Class Method Details

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



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
379
380
381
382
383
384
385
386
# File 'lib/tree_haver/backends/java.rb', line 316

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

  # Use shared utility for consistent symbol derivation across backends
  # If symbol not provided, derive from name or path
  sym = symbol || LibraryPathUtils.derive_symbol_from_path(path)
  # If name was provided, use it to override the derived symbol
  sym = "tree_sitter_#{name}" if name && !symbol

  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")

Parameters:

  • name (String)

    the language name (e.g., “java”, “python”, “toml”)

Returns:

Raises:



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/tree_haver/backends/java.rb', line 402

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.

Parameters:

  • other (Object)

    object to compare with

Returns:

  • (Integer, nil)

    -1, 0, 1, or nil if not comparable



268
269
270
271
272
273
274
275
276
277
# File 'lib/tree_haver/backends/java.rb', line 268

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)

Returns:

  • (Integer)


281
282
283
# File 'lib/tree_haver/backends/java.rb', line 281

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