UNPKG

@specs-feup/kadabra

Version:

A Java source-to-source compiler written in Typescript

229 lines (206 loc) 7.01 kB
import Query from "@specs-feup/lara/api/weaver/Query.js"; import { Method, InterfaceType, Class, Call, Field, App, FileJp, } from "../Joinpoints.js"; import { generateFunctionalInterface } from "./Factory.js"; /** * Prepares a given method call by: * - Extracting a functional interface. * - Creating a field of that type. * - Initializing the field with the called method. * - Replacing the call with invocation of the field method. * * @param call - The method call join point. * @param method - The method join point (optional). * @param fieldLocation - The location to insert the field (optional). * @param newFile - Whether to create the interface in a new file (default: true). * @param funcInterface - The functional interface join point (optional). * @returns An object containing the extracted field, interface, and related information. */ export function extractToField( call: Call | undefined | null, method?: Method, fieldLocation?: Class, newFile: boolean = true, funcInterface: InterfaceType | undefined | null = null ): { $field: Field | undefined | null; $interface: InterfaceType | null; $interfaceMethod: Method | undefined; defaultMethod: string | undefined; } { if (call === undefined || call === null) { return { $field: null, $interface: funcInterface, $interfaceMethod: undefined, defaultMethod: undefined, }; } if (method === undefined) { const ancestor = call.getAncestor("method") as Method | undefined; if (ancestor == undefined) { throw new Error("No method found for the given call."); } method = ancestor; } if (funcInterface === undefined || funcInterface === null) { const extracted = generateFunctionalInterface( call.name, call.declarator, undefined, undefined, newFile ); funcInterface = extracted.$interface; console.log( `[LOG] Extracted a functional interface "${funcInterface.name}" based on method "${call.name}"` ); } const defaultMethod = `${call.qualifiedDecl}::${call.name}`; fieldLocation ??= Query.search(Class, { qualifiedName: method.declarator, }).getFirst(); if (fieldLocation === undefined) { throw new Error( "Could not get a location to insert new field. Please verify the input arguments of extractToField." ); } let field: Field | undefined = undefined; let interfaceMethod: Method | undefined = undefined; for (const m of Query.searchFrom(funcInterface, Method, call.name)) { interfaceMethod = m; field = fieldLocation.newField( method.isStatic ? ["static"] : [], funcInterface.qualifiedName, interfaceMethod.name, defaultMethod ); console.log( `[LOG] Extracted a field "${field.name}", from call "${call.name}", to ${field.declarator}` ); call.setTarget(field.name); call.setExecutable(interfaceMethod); } if (field !== undefined) { console.log( `[LOG] Call to "${call.name}" (in method "${method.name}") is ready!` ); } return { $field: field, $interface: funcInterface, $interfaceMethod: interfaceMethod, defaultMethod, }; } const DEFAULT_PACKAGE = "pt.up.fe.specs.lara.kadabra.utils"; /** * Generates a new mapping class for functional mapping. * * @param interfaceJp - The functional interface join point. * @param methodName - The name of the method. * @param getterType - The type of the getter. * @param target - The target join point (optional). * @returns An object containing the mapping class and related methods. */ export function newMappingClass( interfaceJp: InterfaceType, methodName: string, getterType: string, target: Class | App | FileJp = Query.root() as App ): { $mapClass: Class; put: (key: string, value: string) => string; contains: (key: string) => string; get: (param: string, defaultMethod?: string) => string; } { const targetMethodFirstCap = methodName.charAt(0).toUpperCase() + methodName.slice(1); const mapClassName = `${DEFAULT_PACKAGE}.${targetMethodFirstCap}Caller`; console.log(`[LOG] Creating new functional mapping class: ${mapClassName}`); let mapClass = undefined; if ( target instanceof App || target instanceof FileJp || target instanceof Class ) { mapClass = target.mapVersions( mapClassName, getterType, interfaceJp, methodName ); } else { throw new Error( "Target join point for new functional method caller has to be: app, file, class, or interface." ); } return { $mapClass: mapClass, put: (key: string, value: string) => `${mapClass.qualifiedName}.put(${key}, ${value})`, contains: (key: string) => `${mapClass.qualifiedName}.contains(${key})`, get: (param: string, defaultMethod?: string) => defaultMethod ? `${mapClass.qualifiedName}.get(${param}, ${defaultMethod})` : `${mapClass.qualifiedName}.get(${param})`, }; } /** * Generates a new functional method caller. * * @param interfaceJp - The functional interface join point. * @param methodName - The name of the method. * @param getterType - The type of the getter. * @param defaultMethodStr - The default method string. * @returns An object containing the mapping class and related methods. */ export function newFunctionalMethodCaller( interfaceJp: InterfaceType | null = null, methodName: string | null = null, getterType: string | null = null, defaultMethodStr: string | null = null ): { $mapClass: Class | undefined; put: string; contains: string; get: ((param: string) => string) | undefined; } { if ( interfaceJp === null || methodName === null || getterType === null || defaultMethodStr === null ) { return { $mapClass: undefined, put: "put", contains: "contains", get: undefined, }; } const targetMethodFirstCap = methodName.charAt(0).toUpperCase() + methodName.slice(1); const mapClassName = `${DEFAULT_PACKAGE}.${targetMethodFirstCap}Caller`; console.log(`[LOG] Creating new functional mapping class: ${mapClassName}`); const mapClass = (Query.root() as App).mapVersions( mapClassName, getterType, interfaceJp, methodName ); return { $mapClass: mapClass, put: "put", contains: "contains", get: (param: string) => `${mapClass.qualifiedName}.get(${param}, ${defaultMethodStr})`, }; }