Files
libadalang/extensions/java_api/main_class
Pierre-Marie de Rodat 859f5701dd C/Python/OCaml/Java: bind all GPR loading options
(cherry picked from commit f39ebe979e)
2025-10-23 12:23:52 +00:00

780 lines
25 KiB
Plaintext

## vim: ft=makojava
<%
api = java_api
nat = c_api.get_name
%>
// ==========
// Define classes to handle project loading
// ==========
/**
* Exception to represent errors during project manipulation
*/
public static final class ProjectManagerException extends RuntimeException {
ProjectManagerException(
final String message
) {
super(message);
}
}
/**
* Enum to represent the source file mode for a GPR project
*/
public static enum SourceFileMode {
// ----- Enum values -----
DEFAULT(0),
ROOT_PROJECT(1),
WHOLE_PROJECT(2),
WHOLE_PROJECT_WITH_RUNTIME(3),
;
// ----- Class attributes -----
/** Singleton that represents the none source file mode */
public static final SourceFileMode NONE = DEFAULT;
/** The map from int to enum values */
private static final Map<Integer, SourceFileMode> map = new HashMap<>();
// ----- Instance attributes -----
/** The value of the instance */
private final int value;
// ----- Constructors -----
/**
* The private constructor
*/
private SourceFileMode(
final int value
) {
this.value = value;
}
static {
for(SourceFileMode elem : SourceFileMode.values()) {
map.put(elem.value, elem);
}
}
// ----- Enum methods -----
/**
* Get a source file mode from a native integer value.
*
* @param cValue The native value of the enum.
* @return The Java source file mode.
* @throws EnumException If the given native value doesn't correspond
* to an actual enum value.
*/
public static SourceFileMode fromC(
final int cValue
) throws EnumException {
if(!map.containsKey(cValue))
throw new EnumException(
"Cannot get SourceFileMode from " + cValue
);
return (SourceFileMode) map.get(cValue);
}
/**
* Get the native integer value of the enum instance.
*
* @return The native C value.
*/
public int toC() {
return this.value;
}
}
/**
* Enum to represent a given project loading option.
*/
public static enum ProjectOption {
// ----- Enum values -----
AP(0),
AUTOCONF(1),
CONFIG(2),
DB(3),
DB_MINUS(4),
IMPLICIT_WITH(5),
RESOLVE_LINKS(6),
NO_PROJECT(7),
P(8),
PRINT_GPR_REGISTRY(9),
RELOCATE_BUILD_TREE(10),
ROOT_DIR(11),
RTS(12),
SRC_SUBDIRS(13),
SUBDIRS(14),
TARGET(15),
X(16),
;
// ----- Class attributes -----
/** The map from int to enum values */
private static final Map<Integer, ProjectOption> map = new HashMap<>();
// ----- Instance attributes -----
/** The value of the instance */
private final int value;
// ----- Constructors -----
/**
* The private constructor
*/
private ProjectOption(
final int value
) {
this.value = value;
}
static {
for(ProjectOption elem : ProjectOption.values()) {
map.put(elem.value, elem);
}
}
// ----- Enum methods -----
/**
* Get a project option from a native integer value.
*
* @param cValue The native value of the enum.
* @return The corresponding Java enum value.
* @throws EnumException If the given native value doesn't correspond
* to an actual enum value.
*/
public static ProjectOption fromC(
final int cValue
) throws EnumException {
if(!map.containsKey(cValue))
throw new EnumException(
"Cannot get ProjectOption from " + cValue
);
return (ProjectOption) map.get(cValue);
}
/**
* Get the native integer value of the enum instance.
*
* @return The native C value.
*/
public int toC() {
return this.value;
}
}
${java_doc("libadalang.gpr_options", 4)}
public static final class ProjectOptions implements AutoCloseable {
// ----- Instance attributes -----
/**
* Reference to the native value.
*
* It is package visible so that binding code in the ProjectManager can
* convert ProjectOptions arguments down to the C value. */
final PointerWrapper reference;
// ----- Constructors -----
/**
* Create a new project loading options holder.
*
* @param reference The reference to the native options holder.
*/
private ProjectOptions(
final PointerWrapper reference
) {
this.reference = reference;
}
/**
* Create a new project loading options holder.
*/
public ProjectOptions() {
if(ImageInfo.inImageCode()) {
ProjectOptionsNative res =
NI_LIB.${nat("gpr_options_create")}();
this.reference = new PointerWrapper(res);
} else {
this.reference = JNI_LIB.${nat("gpr_options_create")}();
}
}
/**
* Add a switch to the given options.
*
* See the Ada API for `GPR2.Options.Add_Switch`.
*/
public void addSwitch(
ProjectOption switch_,
String param,
String index,
boolean overwrite
) {
if(ImageInfo.inImageCode()) {
final CCharPointer paramNative =
param == null ?
WordFactory.nullPointer() :
toCString(param);
final CCharPointer indexNative =
index == null ?
WordFactory.nullPointer() :
toCString(index);
NI_LIB.${nat("gpr_options_add_switch")}(
this.reference.ni(),
switch_.toC(),
paramNative,
indexNative,
overwrite ? 1 : 0
);
if (param != null) {
UnmanagedMemory.free(paramNative);
}
if (index != null) {
UnmanagedMemory.free(indexNative);
}
} else {
JNI_LIB.${nat("gpr_options_add_switch")}(
this,
switch_.toC(),
param,
index,
overwrite
);
}
try {
checkException();
} catch (LangkitException e) {
throw new ProjectManagerException(e.getMessage());
}
}
/**
* Add a switch to the given options.
*
* See the Ada API for `GPR2.Options.Add_Switch`.
*/
public void addSwitch(
ProjectOption switch_,
String param,
String index
) {
this.addSwitch(switch_, param, index, false);
}
/**
* Add a switch to the given options.
*
* See the Ada API for `GPR2.Options.Add_Switch`.
*/
public void addSwitch(
ProjectOption switch_,
String param
) {
this.addSwitch(switch_, param, null, false);
}
/**
* Add a switch to the given options.
*
* See the Ada API for `GPR2.Options.Add_Switch`.
*/
public void addSwitch(
ProjectOption switch_
) {
this.addSwitch(switch_, null, null, false);
}
/** @see java.lang.AutoCloseable#close() */
@Override
public void close() {
if(ImageInfo.inImageCode()) {
NI_LIB.${nat("gpr_options_free")}(this.reference.ni());
} else {
JNI_LIB.${nat("gpr_options_free")}(this);
}
}
}
/**
* This class is used for the GPR project loading.
*/
public static final class ProjectManager implements AutoCloseable {
// ----- Class attributes -----
/** Singleton that represents the none project manager. */
public static final ProjectManager NONE =
new ProjectManager(PointerWrapper.nullPointer());
// ----- Instance attributes -----
/** Reference to the native value. */
private final PointerWrapper reference;
/**
* List of diagnostics emitted by the native API during the project
* opening.
*/
private final List<String> diagnostics;
// ----- Constructors -----
/**
* Create a new project manager from its native reference.
*
* @param reference The reference to the native project manager.
*/
ProjectManager(
final PointerWrapper reference
) {
this.reference = reference;
this.diagnostics = new ArrayList<>();
}
/**
* Create a new project manager.
*
* @param options The GPR loading options.
* @param adaOnly Restrict project loading to the Ada language.
*/
public ProjectManager(
final ProjectOptions options,
final boolean adaOnly
) {
if (options == null) {
throw new ProjectManagerException(
"non-null `options` argument required"
);
}
if(ImageInfo.inImageCode()) {
// Declare native values
final ProjectOptionsNative optionsPointer;
final Pointer errorsPointer;
// Prepare the result pointer
final Pointer projectPointer = StackValue.get(SizeOf.get(VoidPointer.class));
projectPointer.writeWord(0, WordFactory.nullPointer());
optionsPointer = options.reference.ni();
errorsPointer = StackValue.get(SizeOf.get(WordPointer.class));
errorsPointer.writeWord(0, WordFactory.nullPointer());
NI_LIB.${nat("gpr_project_load")}(
optionsPointer,
adaOnly ? 1 : 0,
projectPointer,
errorsPointer
);
// Check the langkit exception and cast it into a project manager error
try {
checkException();
} catch (LangkitException e) {
throw new ProjectManagerException(e.getMessage());
}
// Get the result of modified values
final ProjectManagerNative projectManagerNative = (ProjectManagerNative) projectPointer.readWord(0);
final StringArrayNative errorArrayNative = (StringArrayNative) errorsPointer.readWord(0);
// `errorsPointer` is not allocated if an exception was raised during project file loading
String[] errors = new String[0];
if (errorArrayNative.isNonNull()) {
// Translate the error native array into a Java array
errors = toJStringArray(errorArrayNative);
// Free the error array
NI_LIB.${nat("free_string_array")}(errorArrayNative);
}
// Check the langkit exception and cast it into a project manager error
try {
checkException();
} catch (LangkitException e) {
throw new ProjectManagerException(e.getMessage());
}
// Create the result project manager and add diagnostics if any
this.reference = new PointerWrapper(projectManagerNative);
this.diagnostics = List.of(errors);
} else {
// Call the native function from the stubs
final List<String> diagnostics = new ArrayList<>();
final PointerWrapper reference =
JNI_LIB.${nat("gpr_project_load")}(
options,
adaOnly,
diagnostics
);
// Check the langkit exceptions
try {
checkException();
} catch (LangkitException e) {
throw new ProjectManagerException(e.getMessage());
}
// Return the project manager
this.reference = reference;
this.diagnostics = diagnostics;
}
}
// ----- Graal C API methods -----
/**
* Wrap a native project manager in the Java class.
*
* @param pointer The pointer to the native project manager.
* @return The newly wrapped project manager.
*/
static ProjectManager wrap(
final Pointer pointer
) {
return wrap((ProjectManagerNative) pointer.readWord(0));
}
/**
* Wrap a native project manager in the Java class.
*
* @param projectManagerNative The native project manager to wrap.
* @return The newly wrapped project manager.
*/
static ProjectManager wrap(
final ProjectManagerNative projectManagerNative
) {
return new ProjectManager(new PointerWrapper(projectManagerNative));
}
/**
* Unwrap the project manager inside the given pointer.
*
* @param pointer The pointer to write in.
*/
public void unwrap(
final Pointer pointer
) {
pointer.writeWord(0, this.unwrap());
}
/**
* Get the native value of the project manager.
*
* @return The native project manager.
*/
public ProjectManagerNative unwrap() {
return (ProjectManagerNative) this.reference.ni();
}
// ----- Class methods -----
/**
* Translate a native string array structure into a Java string
* array.
*
* @param stringArrayNative The native string array structure.
* @return The Java string array.
*/
private static String[] toJStringArray(
final StringArrayNative stringArrayNative
) {
final String[] res = new String[stringArrayNative.get_length()];
final CCharPointerPointer nativeFilesPointer = stringArrayNative.get_c_ptr();
for(int i = 0 ; i < res.length ; i++) {
final CCharPointer nativeFile = nativeFilesPointer.read(i);
res[i] = toJString(nativeFile);
}
return res;
}
// ----- Instance methods -----
public List<String> getDiagnostics() {
return this.diagnostics;
}
/**
* Create a unit provider for the given subproject.
*
* @param subproject The subproject for which to create a unit provider.
* @return The unit provider for the project manager.
*/
public UnitProvider getProvider(final String subproject) {
final UnitProvider result;
if(ImageInfo.inImageCode()) {
final CCharPointer subprojectNative =
subproject == null ?
WordFactory.nullPointer() :
toCString(subproject);
UnitProviderNative unitProviderNative = NI_LIB.${nat('gpr_project_create_unit_provider')}(
this.reference.ni(),
subprojectNative
);
result = UnitProvider.wrap(unitProviderNative);
if (subproject != null) {
UnmanagedMemory.free(subprojectNative);
}
} else {
result = JNI_LIB.${nat("gpr_project_create_unit_provider")}(
this,
subproject
);
}
return result;
}
/**
* Create a unit provider for root project.
*/
public UnitProvider getProvider() {
return this.getProvider(null);
}
${java_doc("libadalang.gpr_project_create_context", 8)}
public AnalysisContext createContext(
String subproject,
EventHandler eventHandler,
boolean withTrivia,
int tabStop
) {
if(ImageInfo.inImageCode()) {
// Prepare C values for native calls
final CCharPointer subproject_c =
subproject == null
? WordFactory.nullPointer()
: toCString(subproject);
EventHandlerNative eventHandler_c =
eventHandler == null
? WordFactory.nullPointer()
: eventHandler.reference.ni();
// Manually allocate a C-level analysis context so that we can
// initialize it ourselves.
final PointerWrapper context = new PointerWrapper(
NI_LIB.${nat("allocate_analysis_context")}()
);
// Create the Java wrapper, so that we have one ready for
// event handler callbacks triggered during context
// initialization.
AnalysisContext result =
AnalysisContext.fromReference(context, eventHandler, true);
// "result" has its own ownership share: release ours
NI_LIB.${nat("context_decref")}(context.ni());
// TODO: attach "this" to "result" so that the former lives at
// least as long as the former.
// Finally, initialize the analysis context. Note that this
// step may raise an exception: in that case, the analysis
// context is considered not initialized: release it.
NI_LIB.${nat("gpr_project_initialize_context")}(
this.reference.ni(),
context.ni(),
subproject_c,
eventHandler_c,
withTrivia ? 1 : 0,
tabStop
);
UnmanagedMemory.free(subproject_c);
final LangkitExceptionNative exc_c =
NI_LIB.${nat("get_last_exception")}();
if (exc_c.isNonNull()) {
LangkitException exc = wrapException(exc_c);
NI_LIB.${nat("context_decref")}(context.ni());
throw exc;
}
return result;
} else {
return JNI_LIB.${nat("gpr_project_create_context")}(
this,
subproject,
eventHandler,
withTrivia,
tabStop
);
}
}
/**
* Get the files for the given subprojects in a string array.
*
* @param mode The file getting mode.
* @param subprojects The subprojects to consider.
* @return The array that contains the project files.
*/
public String[] getFiles(
final SourceFileMode mode,
final String[] subprojects
) {
// Verify if the project is null
if(this.reference.isNull())
return new String[0];
String[] result = null;
LangkitException exc;
if(ImageInfo.inImageCode()) {
// Convert the Java array of subprojects ("subprojects"
// argument) into the required C array
// (subprojectCount/subprojectsNative).
final int subprojectCount;
final CCharPointerPointer subprojectsNative;
if (subprojects != null && subprojects.length > 0) {
subprojectCount = subprojects.length;
subprojectsNative =
UnmanagedMemory.calloc(
subprojectCount * SizeOf.get(CCharPointerPointer.class)
);
} else {
subprojectCount = 0;
subprojectsNative = WordFactory.nullPointer();
}
for (int i = 0; i < subprojectCount; ++i) {
subprojectsNative.write(i, toCString(subprojects[i]));
}
// Call the C API. Keep track of a potential native exception.
StringArrayNative sourceFileArray =
NI_LIB.${nat('gpr_project_source_files')}(
this.reference.ni(),
mode.toC(),
subprojectsNative,
subprojectCount
);
exc = getLastException();
// If the call was successful, create the Java array result and
// initialize it, and free the C array result.
if(exc == null) {
result = toJStringArray(sourceFileArray);
NI_LIB.${nat("free_string_array")}(sourceFileArray);
}
// Release subprojectsNative
for (int i = 0; i < subprojectCount; ++i) {
UnmanagedMemory.free(subprojectsNative.read(i));
}
if (subprojectCount > 0) {
UnmanagedMemory.free(subprojectsNative);
}
} else {
result = JNI_LIB.${nat("gpr_project_source_files")}(
this,
mode.toC(),
subprojects
);
exc = getLastException();
}
// If we got an exception, turn it into a ProjectManagerException
// one.
if(exc != null) {
throw new ProjectManagerException(exc.getMessage());
}
return result;
}
/**
* Get the files of the root project in a string array.
*
* @param mode The file getting mode.
* @return The array that contains the project files.
*/
public String[] getFiles(final SourceFileMode mode) {
return this.getFiles(mode, null);
}
/** @see java.lang.AutoCloseable#close() */
@Override
public void close() {
if(ImageInfo.inImageCode()) {
NI_LIB.${nat("gpr_project_free")}(this.reference.ni());
} else {
JNI_LIB.${nat("gpr_project_free")}(this);
}
}
}
${java_doc("libadalang.create_auto_provider", 4)}
public static UnitProvider createAutoProvider(
final String[] sourceFiles,
final String charset
) {
if (ImageInfo.inImageCode()) {
final CCharPointer charsetNative =
charset == null ?
WordFactory.nullPointer() :
toCString(charset);
// Allocate the C array of C strings that will contain decoded
// source file names. Make room for one additional null pointer
// to mark the end of the array.
final CCharPointerPointer sourceFilesNative =
UnmanagedMemory.calloc(
(sourceFiles.length + 1) * SizeOf.get(CCharPointerPointer.class)
);
for (int i = 0; i < sourceFiles.length; ++i) {
sourceFilesNative.write(i, toCString(sourceFiles[i]));
}
// Create the auto provider
final UnitProviderNative unitProviderNative =
NI_LIB.${nat('create_auto_provider')}(
sourceFilesNative,
charsetNative
);
// Release all temporarily allocated memory
for (int i = 0; i < sourceFiles.length; ++i) {
UnmanagedMemory.free(sourceFilesNative.read(i));
}
UnmanagedMemory.free(sourceFilesNative);
if (charset != null) {
UnmanagedMemory.free(charsetNative);
}
return UnitProvider.wrap(unitProviderNative);
} else {
return JNI_LIB.${nat("create_auto_provider")}(
sourceFiles,
charset
);
}
}