@eclipse-glsp/protocol
Version:
The protocol definition for client-server communication in GLSP
120 lines (108 loc) • 5.07 kB
text/typescript
/********************************************************************************
* Copyright (c) 2023-2024 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { ContainerModule, interfaces } from 'inversify';
import { MaybeArray, asArray } from '../utils/array-util';
import { BindingContext } from './inversify-util';
/**
* Optional constructor options for {@link FeatureModule}s.
*/
export interface FeatureModuleOptions {
/**
* The set of feature modules that is required in order for this module to load.
*/
requires?: MaybeArray<FeatureModule>;
/**
* Optional `featureId` that should be used. If omitted an id will be autogenerated
*/
featureId?: symbol;
}
/**
* A `FeatureModule` is a specialized {@link ContainerModule} that can declare dependencies to other {@link FeatureModule}.
* A feature module will only be loaded into a container if all of its required modules haven been loaded before. T
* Each feature module binds its `featureId` be default. This enables querying of existing container to check wether a
* feature module has been loaded into this container.
*/
export class FeatureModule extends ContainerModule {
/**
* Global flag to enable/disable additional debug log output when loading feature modules
* Default is `false`.
*/
public static DEBUG_LOG_ENABLED = false;
readonly featureId: symbol;
readonly requires?: MaybeArray<FeatureModule>;
constructor(registry: interfaces.ContainerModuleCallBack, options: FeatureModuleOptions = {}) {
super((bind, unbind, isBound, ...rest) => {
if (this.configure(bind, isBound)) {
registry(bind, unbind, isBound, ...rest);
this.debugLog(`Loading of feature module with id '${this.featureId.toString()}' completed`);
}
});
this.featureId = options.featureId ?? this.createFeatureId();
this.requires = options.requires;
}
protected createFeatureId(): symbol {
return Symbol(this.id);
}
/**
* Configures the feature module i.e. checks if the requirements are met.
* If this is the case the {@link FeatureModule.featureId} will be bound and the module will be loaded
* @param bind container bind function
* @param isBound container isBound function
* @returns `true` if all requirements are met and the module is loaded. `false` otherwise
*/
configure(bind: interfaces.Bind, isBound: interfaces.IsBound): boolean {
this.debugLog(`Trying to load feature module with id '${this.featureId.toString()}'`);
if (this.isLoaded({ isBound })) {
const message = `Could not load feature module. Another module with id '${this.featureId.toString()}' is already loaded`;
this.debugLog(message);
throw new Error(message);
}
if (this.checkRequirements(isBound)) {
this.debugLog(`Requirements are met, continue loading of feature module with id '${this.featureId.toString()}'`);
bind(this.featureId).toConstantValue(this.featureId);
return true;
}
return false;
}
protected debugLog(message?: any, ...optionalParams: any[]): void {
if (FeatureModule.DEBUG_LOG_ENABLED) {
console.log(message, ...optionalParams);
}
}
/**
* Checks if all required {@link FeatureModule}s are already loaded/bound in the container.
* @param isBound The `isBound` property of the module callback. Used to check the required modules.
* @returns `true` if all requirements are met, `false` otherwise
*/
protected checkRequirements(isBound: interfaces.IsBound): boolean {
const requires = asArray(this.requires ?? []);
if (requires.length === 0) {
return true;
}
const missing = requires.filter(module => !module.isLoaded({ isBound }));
if (missing.length > 0) {
this.debugLog(
// eslint-disable-next-line max-len
`Could not load feature module. Required modules are not loaded. Feature ids: ${missing.map(m => m.featureId.toString()).join(', ')}`
);
return false;
}
return true;
}
isLoaded(context: Pick<BindingContext, 'isBound'>): boolean {
return context.isBound(this.featureId);
}
}