How to add to List in nested flatMaps using different publishers with Reactor - project-reactor

I'm new to Reactor and I would like to know what is the correct way to implement this simple logic. I want to compare two Collections of items and if items are matched I want to add them to Mono<List>. Items are matched if they are equal or if item is parent of other item.
My test method sometimes passes and other times fails with incorrect number of items in Mono<List>. I can't figure out what is the problem. It seems that when I run the junit tests for class with this single test method it passes. However, when I put other test methods, to test different inputs/outputs, in the same class then it sometimes fails with incorrect items in the list. As if the test method finished sooner, before the list got updated with all added items.
package test;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
public class ReactorBasicsTests {
#Test
public void testMatching() {
Mono<List<String>> matched = matching(
Arrays.asList("fruit", "vegetable", "meat"),
Arrays.asList("apple", "orange", "carrot", "meat"));
StepVerifier.create(matched)
.consumeNextWith(list -> {
assertThat(list).containsExactlyInAnyOrder(
"fruit+apple", "fruit+orange", "vegetable+carrot", "meat+meat");
})
.expectComplete()
.verify();
}
/**
* Items are matched when:
* 1) they are equal (meat equals to meat)
* 2) item is parent of child item (fruit is parent of apple)
*/
public Mono<List<String>> matching(List<String> list1, List<String> list2) {
Flux<String> items1 = Flux.fromIterable(list1);
Flux<String> items2 = Flux.fromIterable(list2);
List<String> matched = new ArrayList<>();
return items1
.flatMap(item1 -> items2
.flatMap(item2 -> {
// Check if item1 is equal to item2
Mono<Void> isEqual = Mono.fromSupplier(() -> {
if (item1.equals(item2)) {
matched.add(item1 + "+" + item2);
}
return null; // I have to return something to compile the code
}).then();
// Check if item1 is parent of item2
Mono<Void> isParent = this.getParents(item2).hasElement(item1)
.flatMap(booleanValue -> {
if (booleanValue) {
matched.add(item1 + "+" + item2);
}
return Mono.empty();
});
return isEqual.then(isParent).then();
})
).then(Flux.fromIterable(matched).collectList());
}
/** This method simulates reactive database repository request that returns Flux<String> **/
public Flux<String> getParents(String item) {
if (item.equals("apple") || item.equals("orange")) return Flux.just("fruit", "food");
else if (item.equals("carrot")) return Flux.just("vegetable", "food");
return Flux.empty();
}
}

Related

How to sort a CharmListView with a specific order?

Is it possible to sort a CharmListView in a specific order ?
The list contains the names of the BLE bluetooth devices in range. How to do a sort in the following way ? :
If the device contains a character string :1- then this device belongs to Category XYZ;
if the character string contains :2- then this device belongs to Category XXX. etc ...
When displaying the CharmListView we should see firstly Category XYZ , then Category ABC
In CharmListView example The sorting is done with this code snippet :
if(ascending){
charmListView.setHeaderComparator((d1,d2) -> d1.compareTo(d2));
charmListView.setComparator((s1,s2) -> Double.compare(s1.getDensity(), s2.getDensity()));
ascending = false;
} else {
charmListView.setHeaderComparator((d1,d2) -> d2.compareTo(d1));
charmListView.setComparator((s1,s2) -> Double.compare(s2.getDensity(), s1.getDensity()));
ascending = true;
}
I do not understand how charmListView.setHeaderComparator((d1,d2) -> d2.compareTo(d1)); and
CharmListView.setComparator((s1,s2) -> Double.compare(s2.getDensity(), s1.getDensity())); are working.
By default, the headers are sorted in natural order (alphabetically). How to not sort alphabetically but just with a specific order that I provide ?
Category XYZ should be on top
This is the source code of the class where the CharmListView is created.
package com.hacare.views;
import com.gluonhq.attach.ble.BleDevice;
import com.gluonhq.attach.ble.BleService;
import com.gluonhq.charm.glisten.application.MobileApplication;
import com.gluonhq.charm.glisten.control.AppBar;
import com.gluonhq.charm.glisten.control.CharmListCell;
import com.gluonhq.charm.glisten.control.CharmListView;
import com.gluonhq.charm.glisten.control.ListTile;
import com.gluonhq.charm.glisten.mvc.View;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import com.hacare.objects.MyBleDevice;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class PrimaryPresenter {
/**
* List of ble devices nearby
*/
ObservableList<BleDevice> ble_device_list = FXCollections.observableArrayList();
#FXML
private View primary;
#FXML
private CharmListView<BleDevice, String> ble_device_charmlistview;
/**
* Controller init function
*/
public void initialize() {
primary.showingProperty().addListener((obs, oldValue, newValue) -> {
if (newValue) {
AppBar appBar = MobileApplication.getInstance().getAppBar();
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
MobileApplication.getInstance().getDrawer().open()));
appBar.setTitleText("Primary");
appBar.getActionItems().add(MaterialDesignIcon.BLUETOOTH.button(e ->
buttonClick()));
}
});
ble_device_charmlistview.setHeadersFunction(MyBleDevice::getO_device_type);
ble_device_charmlistview.setComparator((o1, o2) -> {
if(o2.getName().contains(":1-")){
//test statement
return 1;
}
//test statement
return 0;
});
ble_device_charmlistview.setCellFactory(bleDeviceStringCharmListView -> new CharmListCell<BleDevice>(){
#Override
public void updateItem(BleDevice item, boolean empty) {
super.updateItem(item, empty);
if(item!=null && !empty){
ListTile tile = new ListTile();
tile.textProperty().add(item.getName());
Image image = new Image("/icon.png");
if(item.getName().contains("HAcare_Y4")){
image = new Image("imgs/y-type-y4_64x64.jpg");
}
else{
image = new Image("/icon.png");
}
tile.setPrimaryGraphic(new ImageView(image));
setText(null);
setGraphic(tile);
}
}
});
ble_device_charmlistview.selectedItemProperty().addListener((observableValue, bleDevice, t1) ->
{
System.out.println(ble_device_charmlistview.getSelectedItem().getAddress());
}
);
}
#FXML
void buttonClick() {
ble_device_list.clear();
//dummy devices
BleDevice device1 = new BleDevice();
device1.setAddress("AB:CD:EF:GH:IJ:KL");
device1.setName("HAcare_Y4:1-CW71");
BleDevice device2 = new BleDevice();
device2.setAddress("MN:CD:EZ:AH:FJ:CL");
device2.setName("dummy device");
BleDevice device3 = new BleDevice();
device3.setAddress("AK:CD:ED:FE:AJ:KL");
device3.setName("HAcare_Y4:1-AZ5K");
ble_device_list.add(device1);
ble_device_list.add(device2);
ble_device_list.add(device3);
ble_device_charmlistview.setItems(ble_device_list);
BleService.create().ifPresent(ble ->
ble.startScanningDevices().addListener((ListChangeListener.Change<? extends BleDevice> c) -> {
while (c.next()) {
if (c.wasAdded()) {
for (BleDevice device : c.getAddedSubList()) {
System.out.println("Device found: " + device.getName());
ble_device_list.add(device);
ble_device_charmlistview.setItems(ble_device_list);
}
}
}
}));
}
}

How to generate Dart code for annotations at fields?

I'm writing a code generator for Dart using the build_runner, but my builder is not being called for annotations at fields, although it does work for annotations at classes.
Is it possible to also call the generator for annotations at fields (or at any place for that matter)?
For example, the builder is called for the following file:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
int number;
}
But not for this one:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
class Fruit {
#MyAnnotation()
int number;
}
Here's the definition of the annotation:
class MyAnnotation {
const MyAnnotation();
}
And this is how the generator is defined. For now, it just aborts whenever it's called, causing an error message to be printed.
library my_annotation_generator;
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:my_annotation/my_annotation.dart';
import 'package:source_gen/source_gen.dart';
Builder generateAnnotation(BuilderOptions options) =>
SharedPartBuilder([MyAnnotationGenerator()], 'my_annotation');
class MyAnnotationGenerator extends GeneratorForAnnotation<MyAnnotation> {
#override
generateForAnnotatedElement(Element element, ConstantReader annotation, _) {
throw CodeGenError('Generating code for annotation is not implemented yet.');
}
Here's the build.yaml configuration:
targets:
$default:
builders:
my_annotation_generator|my_annotation:
enabled: true
builders:
my_annotation:
target: ":my_annotation_generator"
import: "package:my_annotation/my_annotation.dart"
builder_factories: ["generateAnnotation"]
build_extensions: { ".dart": [".my_annotation.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]
At least from my experience, your file 'example.dart' would need at least one annotation above the class definition to be parsed by GeneratorForAnnotation.
example.dart:
import 'package:my_annotation/my_annotation.dart';
part 'example.g.dart';
#MyAnnotation()
class Fruit {
#MyFieldAnnotation()
int number;
}
To access annotations above class fields or class methods you could use a visitor to "visit" each child element and extract the source code information.
For example, to get information about the class fields you could override the method visitFieldElement and then access any annotations using the getter: element.metadata.
builder.dart:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/src/builder/build_step.dart';
import 'package:source_gen/source_gen.dart';
import 'package:my_annotation/my_annotation.dart';
class MyAnnotationGenerator extends
GeneratorForAnnotation<MyAnnotation> {
#override
FutureOr<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,){
return _generateSource(element);
}
String _generateSource(Element element) {
var visitor = ModelVisitor();
element.visitChildren(visitor);
return '''
// ${visitor.className}
// ${visitor.fields}
// ${visitor.metaData}
''';
}
}
class ModelVisitor extends SimpleElementVisitor {
DartType className;
Map<String, DartType> fields = {};
Map<String, dynamic> metaData = {};
#override
visitConstructorElement(ConstructorElement element) {
className = element.type.returnType;
}
#override
visitFieldElement(FieldElement element) {
fields[element.name] = element.type;
metaData[element.name] = element.metadata;
}
}
Note: In this example, _generateSource returns a commented statement. Without comments you would need to return well-formed dart source code, otherwise, the builder will terminate with an error.
For more information see:
Source Generation and Writing Your Own Package (The Boring Flutter Development Show, Ep. 22) https://www.youtube.com/watch?v=mYDFOdl-aWM&t=459s
The built-in GeneratorForAnnotation uses the LibraryElement's annotatedWith(...) method, which only checks for top-level annotations.
To also detect annotations on fields, you'll need to write something custom.
Here's the Generator I wrote for my project:
abstract class GeneratorForAnnotatedField<AnnotationType> extends Generator {
/// Returns the annotation of type [AnnotationType] of the given [element],
/// or [null] if it doesn't have any.
DartObject getAnnotation(Element element) {
final annotations =
TypeChecker.fromRuntime(AnnotationType).annotationsOf(element);
if (annotations.isEmpty) {
return null;
}
if (annotations.length > 1) {
throw Exception(
"You tried to add multiple #$AnnotationType() annotations to the "
"same element (${element.name}), but that's not possible.");
}
return annotations.single;
}
#override
String generate(LibraryReader library, BuildStep buildStep) {
final values = <String>{};
for (final element in library.allElements) {
if (element is ClassElement && !element.isEnum) {
for (final field in element.fields) {
final annotation = getAnnotation(field);
if (annotation != null) {
values.add(generateForAnnotatedField(
field,
ConstantReader(annotation),
));
}
}
}
}
return values.join('\n\n');
}
String generateForAnnotatedField(
FieldElement field, ConstantReader annotation);
}
I had a very similar issue trying to target specific methods within my annotated classes. Inspired by your answers I slightly modified the class annotation model_visitor to check the method annotation before selecting elements.
class ClassAnnotationModelVisitor extends SimpleElementVisitor<dynamic> {
String className;
Map<String, String> methods = <String, String>{};
Map<String, String> parameters = <String, String>{};
#override
dynamic visitConstructorElement(ConstructorElement element) {
final elementReturnType = element.type.returnType.toString();
className = elementReturnType.replaceFirst('*', '');
}
#override
dynamic visitMethodElement(MethodElement element) {
if (methodHasAnnotation(MethodAnnotation, element)) {
final functionReturnType = element.type.returnType.toString();
methods[element.name] = functionReturnType.replaceFirst('*', '');
parameters[element.name] = element.parameters.map((e) => e.name).join(' ,');
}
}
bool methodHasAnnotation(Type annotationType, MethodElement element) {
final annotations = TypeChecker.fromRuntime(annotationType).annotationsOf(element);
return !annotations.isEmpty;
}
}
Then, I can use the basic GeneratorForAnnotation class and generate for class and methodsArray.

Java 8 Streams: List to Map with mapped values

I'm trying to create a Map from a List using Streams.
The key should be the name of the original item,
The value should be some derived data.
After .map() the stream consists of Integers and at the time of .collect() I can't access "foo" from the previous lambda. How do I get the original item in .toMap()?
Can this be done with Streams or do I need .forEach()?
(The code below is only for demonstration, the real code is of course much more complex and I can't make doSomething() a method of Foo).
import java.util.ArrayList;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class StreamTest {
public class Foo {
public String getName() {
return "FOO";
}
public Integer getValue() {
return 42;
}
}
public Integer doSomething(Foo foo) {
return foo.getValue() + 23;
}
public Map<String, Integer> run() {
return new ArrayList<Foo>().stream().map(foo -> doSomething(foo)).collect(Collectors.toMap(foo.getName, Function.identity()));
}
public static void main(String[] args) {
StreamTest streamTest = new StreamTest();
streamTest.run();
}
}
It appears to me it’s not that complicated. Am I missing something?
return Stream.of(new Foo())
.collect(Collectors.toMap(Foo::getName, this::doSomething));
I’m rather much into method references. If you prefer the -> notation, use
return Stream.of(new Foo())
.collect(Collectors.toMap(foo -> foo.getName(), foo -> doSomething(foo)));
Either will break (throw an exception) if there’s more than one Foo with the same name in your stream.

Xtext. Can't add HyperlinkHelper

I try to customize HyperlinkHelper. So I have override HypertextDetector
package org.xtext.example.mydsl.ui;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.xtext.ui.editor.hyperlinking.DefaultHyperlinkDetector;
import org.eclipse.xtext.ui.editor.hyperlinking.IHyperlinkHelper;
public class MyHyperlinkDetector extends DefaultHyperlinkDetector {
private static final String PREFERENCES = ".hyper";
#Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
IDocument document = textViewer.getDocument();
int offset = region.getOffset();
// extract relevant characters
IRegion lineRegion;
String candidate;
try {
lineRegion = document.getLineInformationOfOffset(offset);
candidate = document.get(lineRegion.getOffset(), lineRegion.getLength());
} catch (BadLocationException ex) {
return null;
}
// look for keyword
int index = candidate.indexOf(PREFERENCES);
if (index != -1) {
// detect region containing keyword
IRegion targetRegion = new Region(lineRegion.getOffset() + index, PREFERENCES.length());
if ((targetRegion.getOffset() <= offset)
&& ((targetRegion.getOffset() + targetRegion.getLength()) > offset))
// create link
return new IHyperlink[] { new PreferencesHyperlink(targetRegion, candidate) };
}
return null;
}
#Override
public IHyperlinkHelper getHelper() {
// TODO Auto-generated method stub
return new MyHyperlinkHelper();
}
}
Hyperlink detector is worked, but MyHyperlinkHelper is never created. Even if I comment method detectHyperlinks.
My goal is to open file with name what I have click in my edited dsl grammar. That's why I need HyperlinkHelper. I.e. I need to check does my substring is correct file name.
How to solve it?
Regards,
Vladimir.
dont override the method. simply use guice and call the method from the superclass in your impl
public Class<? extends IHyperlinkHelper> bindIHyperlinkHelper() {
return DomainmodelHyperlinkHelper.class;
}
or in Xtend
def Class<? extends IHyperlinkHelper> bindIHyperlinkHelper() {
return DomainmodelHyperlinkHelper;
}

Displaying Many Jira issues using Issue Navigator

My question is similar to "Displaying Jira issues using Issue Navigator" But my concern is that sometimes my list of issues is quite long and providing the user with a link to the Issue Navigator only works if my link is shorter than the max length of URLs.
Is there another way? Cookies? POST data? Perhaps programmatically creating and sharing a filter on the fly, and returning a link to the Issue Navigator that uses this filter? (But at some point I'd want to delete these filters so I don't have so many lying around.)
I do have the JQL query for getting the same list of issues, but it takes a very (very) long time to run and my code has already done the work of finding out what the result is -- I don't want the user to wait twice for the same thing (once while I'm generating my snazzy graphical servlet view and a second time when they want to see the same results in the Issue Navigator).
The easiest way to implement this is to ensure that the list of issues is cached somewhere within one of your application components. Each separate list of issues should be identified by its own unique ID (the magicKey below) that you define yourself.
You would then write your own JQL function that looks up your pre-calculated list of issues by that magic key, which then converts the list of issues into the format that the Issue Navigator requires.
The buildUriEncodedJqlQuery() method can be used to create such an example JQL query dynamically. For example, if the magic key is 1234, it would yield this JQL query: issue in myJqlFunction(1234).
You'd then feed the user a URL that looks like this:
String url = "/secure/IssueNavigator.jspa?mode=hide&reset=true&jqlQuery=" + MyJqlFunction.buildUriEncodedJqlQuery(magicKey);
The end result is that the user will be placed in the Issue Navigator looking at exactly the list of issues your code has provided.
This code also specifically prevents the user from saving your JQL function as part of a saved filter, since it's assumed that the issue cache in your application will not be permanent. If that's not correct, you will want to empty out the sanitiseOperand part.
In atlassian-plugins.xml:
<jql-function key="myJqlFunction" name="My JQL Function"
class="com.mycompany.MyJqlFunction">
</jql-function>
JQL function:
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.JiraDataType;
import com.atlassian.jira.JiraDataTypes;
import com.atlassian.jira.jql.operand.QueryLiteral;
import com.atlassian.jira.jql.query.QueryCreationContext;
import com.atlassian.jira.plugin.jql.function.ClauseSanitisingJqlFunction;
import com.atlassian.jira.plugin.jql.function.JqlFunction;
import com.atlassian.jira.plugin.jql.function.JqlFunctionModuleDescriptor;
import com.atlassian.jira.util.MessageSet;
import com.atlassian.jira.util.MessageSetImpl;
import com.atlassian.jira.util.NotNull;
import com.atlassian.query.clause.TerminalClause;
import com.atlassian.query.operand.FunctionOperand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MyJqlFunction implements JqlFunction, ClauseSanitisingJqlFunction
{
private static final String JQL_FUNCTION_NAME = "myJqlFunctionName";
private static final int JQL_FUNCTION_MIN_ARG_COUNT = 1;
private static final int JQL_FUNCTION_MAGIC_KEY_INDEX = 0;
public MyJqlFunction()
{
// inject your app's other components here
}
#Override
public void init(#NotNull JqlFunctionModuleDescriptor moduleDescriptor)
{
}
#Override
public JiraDataType getDataType()
{
return JiraDataTypes.ISSUE;
}
#Override
public String getFunctionName()
{
return JQL_FUNCTION_NAME;
}
#Override
public int getMinimumNumberOfExpectedArguments()
{
return JQL_FUNCTION_MIN_ARG_COUNT;
}
#Override
public boolean isList()
{
return true;
}
/**
* This function generates a URL-escaped JQL query that corresponds to the supplied magic key.
*
* #param magicKey
* #return
*/
public static String buildUriEncodedJqlQuery(String magicKey)
{
return "issue%20in%20" + JQL_FUNCTION_NAME + "(%22"
+ magicKey + "%22%)";
}
#Override
public List<QueryLiteral> getValues(#NotNull QueryCreationContext queryCreationContext,
#NotNull FunctionOperand operand,
#NotNull TerminalClause terminalClause)
{
User searchUser = queryCreationContext.getUser();
MessageSet messages = new MessageSetImpl();
List<QueryLiteral> values = internalGetValues(searchUser,
operand,
terminalClause,
messages,
!queryCreationContext.isSecurityOverriden());
return values;
}
private List<QueryLiteral> internalGetValues(#NotNull User searchUser,
#NotNull FunctionOperand operand,
#NotNull TerminalClause terminalClause,
#NotNull MessageSet messages,
#NotNull Boolean checkSecurity)
{
List<QueryLiteral> result = new ArrayList<QueryLiteral>();
if (searchUser==null)
{
// handle anon user
}
List<String> args = operand.getArgs();
if (wasSanitised(args))
{
messages.addErrorMessage("this function can't be used as part of a saved filter etc");
return result;
}
if (args.size() < getMinimumNumberOfExpectedArguments())
{
messages.addErrorMessage("too few arguments, etc");
return result;
}
final String magicKey = args.get(JQL_FUNCTION_MAGIC_KEY_INDEX);
// You need to implement this part yourself! This is where you use the supplied
// magicKey to fetch a list of issues from your own internal data source.
List<String> myIssueKeys = myCache.get(magicKey);
for (String id : myIssueKeys)
{
result.add(new QueryLiteral(operand, id));
}
return result;
}
#Override
public MessageSet validate(User searcher, #NotNull FunctionOperand operand, #NotNull TerminalClause terminalClause)
{
MessageSet messages = new MessageSetImpl();
internalGetValues(searcher, operand, terminalClause, messages, true);
return messages;
}
#Override
public FunctionOperand sanitiseOperand(User paramUser, #NotNull FunctionOperand operand)
{
// prevent the user from saving this as a filter, since the results are presumed to be dynamic
return new FunctionOperand(operand.getName(), Arrays.asList(""));
}
private boolean wasSanitised(List<String> args)
{
return (args.size() == 0 || args.get(JQL_FUNCTION_MAGIC_KEY_INDEX).isEmpty());
}
}

Resources