Skip to content
92 changes: 92 additions & 0 deletions documentation/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,95 @@ All plugin-actions are by default only enabled, i.e. the menuitem and toolbar-bu
a model is loaded. A plugin-action can change this behaviour by overriding the default method
```boolean shouldBeEnabled(IPluginAction pluginAction)```.
This is especially useful, if an action does not require a model to be loaded.

## Autocompletion for OCL statements

### Design

```mermaid
classDiagram
class Autocompletion {
- model: MModel
- state: MSystemState
+ getSuggestions(input: String, OCLOnly boolean): SuggestionResult
}

class AutocompletionParser {
- resultType ResultTypeRoot
+ AutoCompletionParser(model: MModel, input: String): AutoCompletionparser
+ getResult(): ResultTypeRoot
+ parse(model: MModel, input: String, err: PrintWriter): void
- processResult(): void
}

class ResultTypeRoot {
// Base class for result types
}

class SuggestionResult {
+ suggestions: List<String>
+ prefix: String
}

Autocompletion -- AutocompletionParser: uses
AutocompletionParser -- ResultTypeRoot: creates
AutocompletionParser --|> Suggestion: creates

```

The autocompletion process involves a lexer, a parser and a suggester.

The 'Autocompletion' serves as the suggester, while the 'AutoCompletionParser' acts as both lexer and parser.

An instance of 'Autocompletion' is held by the Main Window, ensuring only one instance exists when the GUI is active. Upon loading a new model, the existing instance is replaced with a new one.

```mermaid
sequenceDiagram
participant Autocompletion
participant AutocompletionParser
participant ResultTypeRoot
participant Suggestion

EvalOCLDialog->>Autocompletion: getSuggestions(input, OCLOnly)
Autocompletion->>AutocompletionParser: create instance
AutocompletionParser->>AutocompletionParser: parse()
AutocompletionParser->>AutocompletionParser: processResult()
AutocompletionParser-->>ResultTypeRoot: create instance
Autocompletion->>AutocompletionParser: getResult()
AutocompletionParser-->>SuggestionResult: create instance
```

In the 'getSuggestions' method of 'Autocompletion,' a new 'AutoCompletionParser' instance is created. The constructor of 'AutoCompletionParser' involves parsing the input string and processing the found types.

Processing is essential as the 'parse' method only collects types found in the input string. In 'processResult,' an instance of a subtype of 'ResultTypeRoot' is created.

The 'getSuggestions' method then calls 'getResult' of the 'AutoCompletionParser' object, returning a 'SuggestionResult' object containing suggestion strings and a possible prefix for colored input.

### Usage in GUI

1. **Autocompletion suggestions:** Suggestions are updated in real-time as characters are typed in the text area.

2. **Navigation:** Use the 'ArrowDown' and 'ArrowUp' keys to move through the suggested list.

3. **Selection:** Press 'Enter' to choose an item from the suggestion list.

4. **Focus:** The text area retains focus for all other keyboard inputs, even when the suggestion list is active.

### Usage in Code

To obtain suggestions for an OCL statement:

- Call the 'getSuggestions' method of the 'AutocompleteManager' instance.

For obtaining only a subtype of 'ResultTypeRoot' without suggestions:
- Instantiate a new 'AutocompleteParser' object.
- Call the 'getResult' method of the new object.

### Adding autocompletion support for new operation

When adding a new operation for a collection type in USE these steps have to be completed:

- Create a new subclass of 'OpGeneric,' e.g., 'MyOperation.'
- Register it through the given 'registerOperation' in 'StandardOperationsMyCollection.'

To add autocompletion support for 'MyOperation' no additional steps are required, as both autocompletion and the OCL compiler rely on the same data structure ('opmap' in 'ExpStdOp'). The new operation seamlessly becomes part of the suggestion list, e.g., when typing 'MyCollection{MyValues}->MyOp.'
35 changes: 35 additions & 0 deletions use-core/src/main/java/org/tzi/use/uml/ocl/expr/ParamInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.tzi.use.uml.ocl.expr;

import java.util.List;

/**
* The ParamInfo class is responsible for managing parameter information of operations
* <p>
* Each operation has its own ParamManager object.
* It consists of a {@link ParamInfo#paramMethodList list of functional interfaces} used in OpGeneric's matches method
* and a {@link ParamNamesAndTypes}.
*/
public class ParamInfo {
/**
* Parameter information containing types and names.
*/
public ParamNamesAndTypes paramNamesAndTypes;

/**
* List of parameter methods.
*/
public List<ParamMethod> paramMethodList;

/**
* Constructs a new ParamManager with the provided parameter methods,
* types, and names.
*
* @param paramMethods A list of ParamMethod objects.
* @param paramTypes A list of parameter types.
* @param paramNames A list of parameter names.
*/
public ParamInfo(List<ParamMethod> paramMethods, List<String> paramTypes, List<String> paramNames) {
this.paramNamesAndTypes = new ParamNamesAndTypes(paramTypes, paramNames);
this.paramMethodList = paramMethods;
}
}
21 changes: 21 additions & 0 deletions use-core/src/main/java/org/tzi/use/uml/ocl/expr/ParamMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.tzi.use.uml.ocl.expr;

import org.tzi.use.uml.ocl.type.Type;

/**
* The ParamMethod interface is a functional interface.
*/
public interface ParamMethod {
/**
* The SAM implemented when a new operation is defined
* <p>
* This method is called in the context of {@link org.tzi.use.uml.ocl.expr.operations.OpGeneric OpGeneric}'s matches method.
*
* @implSpec
* The implementation typically checks whether the parameter is a subtype of a certain type.
*
* @param param The parameter on which the method is executed.
* @return true if the method execution is successful; false otherwise.
*/
boolean method(Type param);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.tzi.use.uml.ocl.expr;

import java.util.List;

/**
* The ParamNamesAndTypes class represents information about method parameters, including
* their types and names.
*/
public class ParamNamesAndTypes {
/**
* List of parameter names.
*/
List<String> names;

/**
* List of parameter types.
*/
List<String> types;

/**
* Constructs a new ParamInfo with the provided parameter types and names.
*
* @param types List of parameter types.
* @param names List of parameter names.
*/
public ParamNamesAndTypes(List<String> types, List<String> names) {
this.types = types;
this.names = names;
}

/**
* Generates a formatted string representation of parameter types and names.
*
* @return A string representing parameter types and names.
*/
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 1; i < names.size(); i++) {//ignore first param since it is the collection itself
stringBuilder.append(types.get(i)).append(" ").append(names.get(i));
if (i < names.size() - 1) {
stringBuilder.append(", ");
}
}
return stringBuilder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package org.tzi.use.uml.ocl.expr.operations;

import org.tzi.use.uml.ocl.expr.EvalContext;
import org.tzi.use.uml.ocl.expr.Expression;
import org.tzi.use.uml.ocl.expr.*;
import org.tzi.use.uml.ocl.type.Type;
import org.tzi.use.uml.ocl.type.Type.VoidHandling;
import org.tzi.use.uml.ocl.value.Value;
import org.tzi.use.util.OperationType;
import org.tzi.use.util.StringUtil;

import com.google.common.collect.Multimap;

import java.util.stream.IntStream;

/**
* OpGeneric is the base class of a large group of individual operations. Each
* operation is implemented by its own class deriving from OpGeneric. New
Expand All @@ -35,8 +37,64 @@ public abstract class OpGeneric {

public static final int SPECIAL = 3;

/**
* Parameter information containing type checks, types and names.
*/
private ParamInfo paramInfo;

/**
* Checks if the given array of parameters matches the expected method parameters.
* <p>
* Uses the List of functions located in {@link this#paramInfo}
*
* @param params An array of Type objects representing the method parameters to be checked.
* @return true if the parameters match the expected method parameters, false otherwise.
*/
public boolean match(Type[] params) {
if (paramInfo.paramMethodList.size() != params.length) {
return false;
}

return IntStream.range(0, params.length)
.allMatch(i -> paramInfo.paramMethodList.get(i).method(params[i]));
}

public abstract Type matches(Type[] params);

public abstract String name();


/**
* Retrieves information about the operation type and returns an OperationType object.
*
* @return An OperationType object containing information about the operation.
*/
public final OperationType getOperationType() {
// Extracting class name information
String[] fullClassName = getClass().toString().split("\\.");
String[] className = fullClassName[fullClassName.length - 1].split("_");

// Creating OperationType object
OperationType operationType = new OperationType();

// Setting operationName and collectionType based on class name
if (className.length > 2) { // collections
operationType.operationName = className[2];
operationType.collectionType = className[1];
} else { // everything else
operationType.operationName = className[1];
}

// Setting param information if ParamManager is not null
if (paramInfo != null) {
operationType.param = paramInfo.paramNamesAndTypes.toString();
} else {
operationType.param = "";
}

return operationType;
}

public boolean isBooleanOperation() {
return false;
}
Expand All @@ -45,8 +103,6 @@ public boolean isBooleanOperation() {

public abstract boolean isInfixOrPrefix();

public abstract Type matches(Type params[]);

public String checkWarningUnrelatedTypes(Expression args[]) { return null; }

public abstract Value eval(EvalContext ctx, Value args[], Type resultType);
Expand Down Expand Up @@ -115,4 +171,8 @@ public static void registerOperation(OpGeneric op, Multimap<String, OpGeneric> o
public static void registerOperation(String name, OpGeneric op, Multimap<String, OpGeneric> opmap) {
opmap.put(name, op);
}

public void setParamInfo(ParamInfo paramInfo) {
this.paramInfo = paramInfo;
}
}
Loading