Skip to main content
Glama
JavaFieldParser.ts5.63 kB
import { ParsedEntity, ParsedRelationship } from '../../../types.js'; import { EntityFactory } from '../../base/EntityFactory.js'; import { RelationshipBuilder } from '../../base/RelationshipBuilder.js'; import { JavaContentExtractor } from '../../extractors/java/JavaContentExtractor.js'; import { JavaDocExtractor } from '../../extractors/java/JavaDocExtractor.js'; import { JavaFrameworkDetector } from '../../framework-detection/java/JavaFrameworkDetector.js'; export class JavaFieldParser { private contentExtractor = new JavaContentExtractor(); private docExtractor = new JavaDocExtractor(); private frameworkDetector = new JavaFrameworkDetector(); parseFields( content: string, filePath: string, packageName: string, entities: ParsedEntity[], relationships: ParsedRelationship[], addEntity: (entity: Omit<ParsedEntity, 'project_id'>) => void, addRelationship: (rel: Omit<ParsedRelationship, 'project_id'>) => void ): void { const extractionResult = this.contentExtractor.extractContent(content, filePath); for (const parsedField of extractionResult.fields) { // Determine the containing class const containingClass = this.findContainingClass(content, parsedField.startLine || 1, entities); if (!containingClass) continue; // Skip if not in a class const fieldId = `${containingClass.qualified_name}.${parsedField.name}`; const qualifiedName = `${containingClass.qualified_name}.${parsedField.name}`; // Extract documentation const documentation = parsedField.startLine ? this.docExtractor.extractDocumentation(content, this.getPositionFromLine(content, parsedField.startLine)) : undefined; // Extract annotations const annotations = this.extractAnnotations(content, parsedField.startLine || 1); // Create field entity const fieldEntity = EntityFactory.createField( fieldId, parsedField.name, qualifiedName, filePath, parsedField.startLine, parsedField.endLine, parsedField.modifiers, documentation, annotations ); addEntity(fieldEntity); // Create containment relationship addRelationship(RelationshipBuilder.createContains(containingClass.id, fieldId, filePath)); // Create type reference if the field has a custom type if (parsedField.type) { const referencedType = this.resolveType(parsedField.type, packageName, extractionResult.imports); if (referencedType !== parsedField.type && !this.isBuiltInType(referencedType)) { addRelationship(RelationshipBuilder.createReferences(fieldId, referencedType, filePath)); } } } } private findContainingClass(content: string, fieldLine: number, entities: ParsedEntity[]): ParsedEntity | null { // Find the class that contains this field based on line numbers for (const entity of entities) { if (entity.type === 'class' && entity.start_line && entity.end_line && fieldLine >= entity.start_line && fieldLine <= entity.end_line) { return entity; } } return null; } private resolveType(typeName: string, packageName: string, imports: any[]): string { // Remove generic type parameters and array brackets const baseType = typeName.replace(/[<>\[\]]/g, '').split(/[<,\s]/)[0].trim(); // Check if it's a fully qualified name if (baseType.includes('.')) { return baseType; } // Check imports for the type for (const imp of imports) { if (imp.items?.includes(baseType) || imp.module.endsWith(`.${baseType}`)) { return imp.module.includes('.') ? imp.module : `${packageName}.${baseType}`; } } // Default to same package return `${packageName}.${baseType}`; } private isBuiltInType(typeName: string): boolean { const builtInTypes = [ 'int', 'long', 'short', 'byte', 'float', 'double', 'boolean', 'char', 'Integer', 'Long', 'Short', 'Byte', 'Float', 'Double', 'Boolean', 'Character', 'String', 'Object', 'void', 'Void', 'List', 'Set', 'Map', 'Collection', 'ArrayList', 'HashMap', 'HashSet' ]; return builtInTypes.some(type => typeName.includes(type)); } private extractAnnotations(content: string, startLine: number): any[] { const annotations: any[] = []; const lines = content.split('\n'); // Look backwards from the field declaration for annotations for (let i = startLine - 2; i >= 0; i--) { const line = lines[i].trim(); if (!line || line.startsWith('//') || line.startsWith('/*')) continue; const annotationMatch = line.match(/@([A-Za-z_][A-Za-z0-9_]*)/); if (annotationMatch) { const annotationName = annotationMatch[1]; const framework = this.frameworkDetector.detectFramework(annotationName) || 'Unknown'; const category = this.frameworkDetector.categorizeAnnotation(annotationName) || 'unknown'; annotations.unshift({ name: annotationName, framework, category }); } else if (line && !line.startsWith('@')) { break; // Stop at non-annotation content } } return annotations; } private getPositionFromLine(content: string, lineNumber: number): number { const lines = content.split('\n'); let position = 0; for (let i = 0; i < Math.min(lineNumber - 1, lines.length); i++) { position += lines[i].length + 1; // +1 for newline } return position; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JonnoC/CodeRAG'

If you have feedback or need assistance with the MCP directory API, please join our Discord server