JavaBlobIndex.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Carsten Hammer
 *******************************************************************************/
package org.eclipse.jgit.storage.hibernate.entity;

import java.time.Instant;

import org.eclipse.jgit.storage.hibernate.search.EmbeddingService;
import org.hibernate.annotations.Nationalized;
import org.hibernate.search.engine.backend.types.Searchable;
import org.hibernate.search.engine.backend.types.VectorSimilarity;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.VectorField;

import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;

/**
 * Entity for indexing structural metadata extracted from Java source blobs.
 * <p>
 * Fields are populated by parsing {@code .java} files with JDT's AST parser
 * (without bindings). This enables searching by type name, method name, field
 * name, fully qualified name, and inheritance relationships without requiring
 * a full classpath.
 * </p>
 */
@Indexed
@Entity
@Table(name = "java_blob_index", indexes = {
		@Index(name = "idx_blob_repo", columnList = "repository_name"),
		@Index(name = "idx_blob_blob_oid", columnList = "blob_object_id"),
		@Index(name = "idx_blob_commit_oid", columnList = "commit_object_id"),
		@Index(name = "idx_blob_repo_commit", columnList = "repository_name, commit_object_id") })
public class JavaBlobIndex {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@KeywordField
	@Column(name = "repository_name", nullable = false)
	private String repositoryName;

	@KeywordField
	@Column(name = "blob_object_id", length = 40, nullable = false)
	private String blobObjectId;

	@KeywordField
	@Column(name = "commit_object_id", length = 40, nullable = false)
	private String commitObjectId;

	@KeywordField
	@Column(name = "file_type", length = 32)
	private String fileType;

	@FullTextField(analyzer = "javaPath")
	@Nationalized
	@Column(name = "file_path", length = 1024)
	private String filePath;

	@Nationalized
	@KeywordField
	@Column(name = "package_name", length = 512)
	private String packageName;

	@FullTextField(analyzer = "dotQualifiedName")
	@Nationalized
	@Column(name = "declared_types", length = 65535)
	private String declaredTypes;

	@FullTextField(analyzer = "dotQualifiedName")
	@Nationalized
	@Column(name = "fully_qualified_names", length = 65535)
	private String fullyQualifiedNames;

	@FullTextField(analyzer = "javaIdentifier")
	@Nationalized
	@Column(name = "declared_methods", length = 65535)
	private String declaredMethods;

	@FullTextField(analyzer = "javaIdentifier")
	@Nationalized
	@Column(name = "declared_fields", length = 65535)
	private String declaredFields;

	@FullTextField(analyzer = "dotQualifiedName")
	@Nationalized
	@Column(name = "extends_types", length = 65535)
	private String extendsTypes;

	@FullTextField(analyzer = "dotQualifiedName")
	@Nationalized
	@Column(name = "implements_types", length = 65535)
	private String implementsTypes;

	@FullTextField(analyzer = "dotQualifiedName")
	@Nationalized
	@Column(name = "import_statements", length = 65535)
	private String importStatements;

	@FullTextField(analyzer = "javaSourceEcj")
	@Nationalized
	@Column(name = "source_snippet", length = 65535)
	private String sourceSnippet;

	@KeywordField
	@Nationalized
	@Column(name = "project_name", length = 512)
	private String projectName;

	@FullTextField(analyzer = "javaIdentifier")
	@Nationalized
	@Column(name = "simple_class_name", length = 512)
	private String simpleClassName;

	@KeywordField
	@Column(name = "type_kind", length = 32)
	private String typeKind;

	@KeywordField
	@Column(name = "visibility", length = 64)
	private String visibility;

	@FullTextField(analyzer = "dotQualifiedName")
	@Nationalized
	@Column(name = "annotations", length = 65535)
	private String annotations;

	@GenericField
	@Column(name = "line_count")
	private int lineCount;

	@FullTextField(analyzer = "commitMessage")
	@Nationalized
	@Column(name = "type_documentation", length = 2000)
	private String typeDocumentation;

	@FullTextField(analyzer = "javaIdentifier")
	@Nationalized
	@Column(name = "method_signatures", length = 65535)
	private String methodSignatures;

	@FullTextField(analyzer = "dotQualifiedName")
	@Nationalized
	@Column(name = "referenced_types", length = 65535)
	private String referencedTypes;

	@FullTextField(analyzer = "genericContent")
	@Nationalized
	@Column(name = "string_literals", length = 65535)
	private String stringLiterals;

	@GenericField
	@Column(name = "has_main_method")
	private boolean hasMainMethod;

	@KeywordField
	@Nationalized
	@Column(name = "commit_author")
	private String commitAuthor;

	@GenericField
	@Column(name = "commit_date")
	private Instant commitDate;

	@VectorField(dimension = EmbeddingService.EMBEDDING_DIMENSION,
			searchable = Searchable.YES,
			vectorSimilarity = VectorSimilarity.COSINE)
	@Lob
	@Convert(converter = FloatArrayConverter.class)
	@Column(name = "semantic_embedding")
	private float[] semanticEmbedding;

	@GenericField
	@Column(name = "has_embedding")
	private boolean hasEmbedding;

	/** Default constructor for JPA. */
	public JavaBlobIndex() {
	}

	/**
	 * Get the primary key.
	 *
	 * @return the id
	 */
	public Long getId() {
		return id;
	}

	/**
	 * Set the primary key.
	 *
	 * @param id
	 *            the id
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Get the repository name.
	 *
	 * @return the repositoryName
	 */
	public String getRepositoryName() {
		return repositoryName;
	}

	/**
	 * Set the repository name.
	 *
	 * @param repositoryName
	 *            the repository name
	 */
	public void setRepositoryName(String repositoryName) {
		this.repositoryName = repositoryName;
	}

	/**
	 * Get the blob SHA-1.
	 *
	 * @return the blobObjectId
	 */
	public String getBlobObjectId() {
		return blobObjectId;
	}

	/**
	 * Set the blob SHA-1.
	 *
	 * @param blobObjectId
	 *            the SHA-1 hex string
	 */
	public void setBlobObjectId(String blobObjectId) {
		this.blobObjectId = blobObjectId;
	}

	/**
	 * Get the commit SHA-1 that introduced this blob.
	 *
	 * @return the commitObjectId
	 */
	public String getCommitObjectId() {
		return commitObjectId;
	}

	/**
	 * Set the commit SHA-1.
	 *
	 * @param commitObjectId
	 *            the SHA-1 hex string
	 */
	public void setCommitObjectId(String commitObjectId) {
		this.commitObjectId = commitObjectId;
	}

	/**
	 * Get the file type (e.g., "java", "xml", "properties").
	 *
	 * @return the fileType
	 */
	public String getFileType() {
		return fileType;
	}

	/**
	 * Set the file type.
	 *
	 * @param fileType
	 *            the file type identifier
	 */
	public void setFileType(String fileType) {
		this.fileType = fileType;
	}

	/**
	 * Get the file path within the repository.
	 *
	 * @return the filePath
	 */
	public String getFilePath() {
		return filePath;
	}

	/**
	 * Set the file path.
	 *
	 * @param filePath
	 *            the relative path
	 */
	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}

	/**
	 * Get the package name.
	 *
	 * @return the packageName
	 */
	public String getPackageName() {
		return packageName;
	}

	/**
	 * Set the package name.
	 *
	 * @param packageName
	 *            the Java package name
	 */
	public void setPackageName(String packageName) {
		this.packageName = packageName;
	}

	/**
	 * Get the newline-separated declared type names.
	 *
	 * @return the declaredTypes
	 */
	public String getDeclaredTypes() {
		return declaredTypes;
	}

	/**
	 * Set the newline-separated declared type names.
	 *
	 * @param declaredTypes
	 *            newline-separated type names
	 */
	public void setDeclaredTypes(String declaredTypes) {
		this.declaredTypes = declaredTypes;
	}

	/**
	 * Get the newline-separated fully qualified names.
	 *
	 * @return the fullyQualifiedNames
	 */
	public String getFullyQualifiedNames() {
		return fullyQualifiedNames;
	}

	/**
	 * Set the newline-separated fully qualified names.
	 *
	 * @param fullyQualifiedNames
	 *            newline-separated FQNs
	 */
	public void setFullyQualifiedNames(String fullyQualifiedNames) {
		this.fullyQualifiedNames = fullyQualifiedNames;
	}

	/**
	 * Get the newline-separated declared method names.
	 *
	 * @return the declaredMethods
	 */
	public String getDeclaredMethods() {
		return declaredMethods;
	}

	/**
	 * Set the newline-separated declared method names.
	 *
	 * @param declaredMethods
	 *            newline-separated method names
	 */
	public void setDeclaredMethods(String declaredMethods) {
		this.declaredMethods = declaredMethods;
	}

	/**
	 * Get the newline-separated declared field names.
	 *
	 * @return the declaredFields
	 */
	public String getDeclaredFields() {
		return declaredFields;
	}

	/**
	 * Set the newline-separated declared field names.
	 *
	 * @param declaredFields
	 *            newline-separated field names
	 */
	public void setDeclaredFields(String declaredFields) {
		this.declaredFields = declaredFields;
	}

	/**
	 * Get the newline-separated supertypes.
	 *
	 * @return the extendsTypes
	 */
	public String getExtendsTypes() {
		return extendsTypes;
	}

	/**
	 * Set the newline-separated supertypes.
	 *
	 * @param extendsTypes
	 *            newline-separated type names
	 */
	public void setExtendsTypes(String extendsTypes) {
		this.extendsTypes = extendsTypes;
	}

	/**
	 * Get the newline-separated implemented interfaces.
	 *
	 * @return the implementsTypes
	 */
	public String getImplementsTypes() {
		return implementsTypes;
	}

	/**
	 * Set the newline-separated implemented interfaces.
	 *
	 * @param implementsTypes
	 *            newline-separated type names
	 */
	public void setImplementsTypes(String implementsTypes) {
		this.implementsTypes = implementsTypes;
	}

	/**
	 * Get the newline-separated import statements.
	 *
	 * @return the importStatements
	 */
	public String getImportStatements() {
		return importStatements;
	}

	/**
	 * Set the newline-separated import statements.
	 *
	 * @param importStatements
	 *            newline-separated imports
	 */
	public void setImportStatements(String importStatements) {
		this.importStatements = importStatements;
	}

	/**
	 * Get the source code snippet (first 64KB).
	 *
	 * @return the sourceSnippet
	 */
	public String getSourceSnippet() {
		return sourceSnippet;
	}

	/**
	 * Set the source code snippet.
	 *
	 * @param sourceSnippet
	 *            the source text
	 */
	public void setSourceSnippet(String sourceSnippet) {
		this.sourceSnippet = sourceSnippet;
	}

	/**
	 * Get the project name.
	 *
	 * @return the projectName
	 */
	public String getProjectName() {
		return projectName;
	}

	/**
	 * Set the project name.
	 *
	 * @param projectName
	 *            the project name
	 */
	public void setProjectName(String projectName) {
		this.projectName = projectName;
	}

	/**
	 * Get the simple class name.
	 *
	 * @return the simpleClassName
	 */
	public String getSimpleClassName() {
		return simpleClassName;
	}

	/**
	 * Set the simple class name.
	 *
	 * @param simpleClassName
	 *            the simple class name
	 */
	public void setSimpleClassName(String simpleClassName) {
		this.simpleClassName = simpleClassName;
	}

	/**
	 * Get the type kind (class, interface, enum, annotation).
	 *
	 * @return the typeKind
	 */
	public String getTypeKind() {
		return typeKind;
	}

	/**
	 * Set the type kind.
	 *
	 * @param typeKind
	 *            the type kind
	 */
	public void setTypeKind(String typeKind) {
		this.typeKind = typeKind;
	}

	/**
	 * Get the visibility modifier string.
	 *
	 * @return the visibility
	 */
	public String getVisibility() {
		return visibility;
	}

	/**
	 * Set the visibility modifier string.
	 *
	 * @param visibility
	 *            the visibility
	 */
	public void setVisibility(String visibility) {
		this.visibility = visibility;
	}

	/**
	 * Get the newline-separated annotation names.
	 *
	 * @return the annotations
	 */
	public String getAnnotations() {
		return annotations;
	}

	/**
	 * Set the newline-separated annotation names.
	 *
	 * @param annotations
	 *            newline-separated annotation names
	 */
	public void setAnnotations(String annotations) {
		this.annotations = annotations;
	}

	/**
	 * Get the line count.
	 *
	 * @return the lineCount
	 */
	public int getLineCount() {
		return lineCount;
	}

	/**
	 * Set the line count.
	 *
	 * @param lineCount
	 *            the line count
	 */
	public void setLineCount(int lineCount) {
		this.lineCount = lineCount;
	}

	/**
	 * Get the type documentation (Javadoc on primary type).
	 *
	 * @return the typeDocumentation
	 */
	public String getTypeDocumentation() {
		return typeDocumentation;
	}

	/**
	 * Set the type documentation.
	 *
	 * @param typeDocumentation
	 *            the type documentation, truncated to 2000 chars
	 */
	public void setTypeDocumentation(String typeDocumentation) {
		this.typeDocumentation = typeDocumentation;
	}

	/**
	 * Get the newline-separated method signatures.
	 *
	 * @return the methodSignatures
	 */
	public String getMethodSignatures() {
		return methodSignatures;
	}

	/**
	 * Set the newline-separated method signatures.
	 *
	 * @param methodSignatures
	 *            newline-separated method signatures
	 */
	public void setMethodSignatures(String methodSignatures) {
		this.methodSignatures = methodSignatures;
	}

	/**
	 * Get the newline-separated referenced types.
	 *
	 * @return the referencedTypes
	 */
	public String getReferencedTypes() {
		return referencedTypes;
	}

	/**
	 * Set the newline-separated referenced types.
	 *
	 * @param referencedTypes
	 *            newline-separated referenced type names
	 */
	public void setReferencedTypes(String referencedTypes) {
		this.referencedTypes = referencedTypes;
	}

	/**
	 * Get the newline-separated string literals.
	 *
	 * @return the stringLiterals
	 */
	public String getStringLiterals() {
		return stringLiterals;
	}

	/**
	 * Set the newline-separated string literals.
	 *
	 * @param stringLiterals
	 *            newline-separated string literal values
	 */
	public void setStringLiterals(String stringLiterals) {
		this.stringLiterals = stringLiterals;
	}

	/**
	 * Check if a main method was detected.
	 *
	 * @return true if a main method was found
	 */
	public boolean isHasMainMethod() {
		return hasMainMethod;
	}

	/**
	 * Set whether a main method was detected.
	 *
	 * @param hasMainMethod
	 *            true if a main method was found
	 */
	public void setHasMainMethod(boolean hasMainMethod) {
		this.hasMainMethod = hasMainMethod;
	}

	/**
	 * Get the commit author name.
	 *
	 * @return the commitAuthor
	 */
	public String getCommitAuthor() {
		return commitAuthor;
	}

	/**
	 * Set the commit author name.
	 *
	 * @param commitAuthor
	 *            the commit author
	 */
	public void setCommitAuthor(String commitAuthor) {
		this.commitAuthor = commitAuthor;
	}

	/**
	 * Get the commit date.
	 *
	 * @return the commitDate
	 */
	public Instant getCommitDate() {
		return commitDate;
	}

	/**
	 * Set the commit date.
	 *
	 * @param commitDate
	 *            the commit date
	 */
	public void setCommitDate(Instant commitDate) {
		this.commitDate = commitDate;
	}

	/**
	 * Get the semantic embedding vector.
	 *
	 * @return the 384-dimensional embedding, or {@code null} if not computed
	 */
	public float[] getSemanticEmbedding() {
		return semanticEmbedding;
	}

	/**
	 * Set the semantic embedding vector.
	 *
	 * @param semanticEmbedding
	 *            the 384-dimensional embedding
	 */
	public void setSemanticEmbedding(float[] semanticEmbedding) {
		this.semanticEmbedding = semanticEmbedding;
	}

	/**
	 * Check if a semantic embedding has been computed for this blob.
	 *
	 * @return {@code true} if an embedding is available
	 */
	public boolean isHasEmbedding() {
		return hasEmbedding;
	}

	/**
	 * Set whether a semantic embedding has been computed.
	 *
	 * @param hasEmbedding
	 *            {@code true} if an embedding is available
	 */
	public void setHasEmbedding(boolean hasEmbedding) {
		this.hasEmbedding = hasEmbedding;
	}
}