Skip to content

Commit

Permalink
Fix NumericParsingUtils and add more functionality to Java library (#10)
Browse files Browse the repository at this point in the history
* Add more builders to the Java library

* Fix issue #9
  • Loading branch information
rafaelbfs authored Jun 2, 2024
1 parent b7c1292 commit afefe25
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@

package systems.terranatal.omnijfx.internationalization;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -67,12 +70,21 @@ static String stripGroupingSymbols(String text, String decimalSeparator) {
* @return true if the string can be parsed to a number, false otherwise
*/
static boolean isParseable(String text, Locale locale) {
var separator = Character.toString(DecimalFormatSymbols.getInstance(locale).getDecimalSeparator());
var str = stripGroupingSymbols(text, separator);
var localizedRational = "[\\+\\-]?\\d+(\\%s\\d+)?".formatted(separator);
var finalRegex = "%s([Ee]%s)?".formatted(localizedRational, localizedRational);
var symbols = DecimalFormatSymbols.getInstance(locale);
var separator = Character.toString(symbols.getDecimalSeparator());
var grouping = Character.toString(DecimalFormatSymbols.getInstance(locale).getGroupingSeparator());
final var scientific = "%s([Ee]%s)?";

return str.matches(finalRegex);
if (!text.contains(grouping)) {
var localizedRational = "[\\+\\-]?\\d+(\\%s\\d+)?".formatted(separator);
var finalRegex = scientific.formatted(localizedRational, localizedRational);
return text.matches(finalRegex);
}
var gsize = ((DecimalFormat) DecimalFormat.getInstance(locale)).getGroupingSize();
var rationalWithGrouping = "[\\+\\-]?\\d{1,%d}(\\%s\\d{%d})*(\\%s\\d+)?"
.formatted(gsize, grouping, gsize, separator);
var finalRegex = scientific.formatted(rationalWithGrouping, rationalWithGrouping);
return text.matches(finalRegex);
}

/**
Expand All @@ -94,4 +106,23 @@ static boolean isParseable(String text) {
static String stripGroupingSymbols(String text) {
return stripGroupingSymbols(text, Character.toString(DecimalFormatSymbols.getInstance().getDecimalSeparator()));
}

static boolean hasGrouping(String text, Locale locale) {
var symbols = DecimalFormatSymbols.getInstance(locale);
var grouping = Character.toString(symbols.getGroupingSeparator());

return text.contains(grouping);
}

static boolean hasGrouping(String text) {
return hasGrouping(text, Locale.getDefault());
}

static Number parseUnchecked(NumberFormat formatter, String text) {
try {
return formatter.parse(text);
} catch (ParseException e) {
throw new IllegalArgumentException("Could not parse " + text + " as a number.", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,32 @@
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Stream;

public class TestParsingUtils {
private static final Random RANDOM = new Random();

@ParameterizedTest
@ArgumentsSource(ParsingStringsProvider.class)
public void testNumericParsingUtils(Optional<Locale> maybeLocale, String parameter, String expectedResult,
public void testNumericParsingUtils(Optional<Locale> maybeLocale, String parameter, boolean hasGrouping,
boolean expectedParseability) {
var result = "";
var result = false;
var isParseable = false;

if (maybeLocale.isPresent()) {
result = NumericParsingUtils.stripGroupingSymbols(parameter,
Character.toString(DecimalFormatSymbols.getInstance(maybeLocale.get()).getDecimalSeparator()));
result = NumericParsingUtils.hasGrouping(parameter, maybeLocale.get());
isParseable = NumericParsingUtils.isParseable(parameter, maybeLocale.get());
} else {
result = NumericParsingUtils.stripGroupingSymbols(parameter);
result = NumericParsingUtils.hasGrouping(parameter);
isParseable = NumericParsingUtils.isParseable(parameter);
}

Assertions.assertEquals(expectedResult, result);
Assertions.assertEquals(hasGrouping, result);
Assertions.assertEquals(expectedParseability, isParseable);
}

Expand All @@ -69,24 +71,49 @@ public static class ParsingStringsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) throws Exception {
var platformDecimalSeparator = Character.toString(DecimalFormatSymbols.getInstance().getDecimalSeparator());

var sampleNr1 = "123" + platformDecimalSeparator + "456";
var badNr1 = "123" + platformDecimalSeparator + "456" + platformDecimalSeparator + "789";
var scientificNr = "456%s321E+0%s987".formatted(platformDecimalSeparator, platformDecimalSeparator);
return Stream.of(
Arguments.of(Optional.of(Locale.GERMANY), "123.123,004", "123123,004", true),
Arguments.of(Optional.empty(), "123 456 789", "123456789", true),
Arguments.of(Optional.empty(), sampleNr1, sampleNr1, true),
Arguments.of(Optional.empty(), badNr1, badNr1, false),
Arguments.of(Optional.of(Locale.US), "123,456,789.002", "123456789.002", true),
Arguments.of(Optional.of(Locale.US), "123'456'789", "123456789", true),
Arguments.of(Optional.empty(), scientificNr, scientificNr, true),
Arguments.of(Optional.empty(), scientificNr.toLowerCase(), scientificNr.toLowerCase(), true),
Arguments.of(Optional.of(Locale.GERMANY), "123.986,009E-3,14", "123986,009E-3,14", true),
Arguments.of(Optional.of(Locale.US), "123,986.009e-3.14", "123986.009e-3.14", true),
Arguments.of(Optional.of(Locale.GERMANY), "123,123,123e-3,55,5", "123,123,123e-3,55,5", false),
Arguments.of(Optional.of(Locale.US), "123.123.123e-3.55.5", "123.123.123e-3.55.5", false)
Arguments.of(Optional.of(Locale.GERMANY), "123.123,004", true, true),
Arguments.of(Optional.of(Locale.CHINA), generateRandom(Locale.CHINA, 3), true, true),
Arguments.of(Optional.empty(), "123 456 789", false, false),
Arguments.of(Optional.empty(), sampleNr1, false, true),
Arguments.of(Optional.empty(), badNr1, false, false),
Arguments.of(Optional.of(Locale.US), "123,456,789.002", true, true),
Arguments.of(Optional.of(Locale.US), "123'456'789", false, false),
Arguments.of(Optional.empty(), scientificNr, false, true),
Arguments.of(Optional.empty(), scientificNr.toLowerCase(), false, true),
Arguments.of(Optional.of(Locale.GERMANY), "123.986,009E-3,14", true, true),
Arguments.of(Optional.of(Locale.US), "12,986.009e-3.14", true, true),
Arguments.of(Optional.of(Locale.GERMANY), "123,123,123e-3,55,5", false, false),
Arguments.of(Optional.of(Locale.US), "123.123.123e-3.55.5", false, false),
Arguments.of(Optional.of(Locale.GERMANY), "12.45.89", true, false)
);
}
}


public static int randomGroup(Locale locale) {
var platformGSize = ((DecimalFormat) DecimalFormat.getInstance(locale)).getGroupingSize();
var number = Double.valueOf(Math.pow(10.0, platformGSize)).intValue();

return RANDOM.nextInt(number/10, number);
}

public static String generateRandom(Locale locale, int numberOfGroups) {
if (numberOfGroups < 1) {
return String.valueOf(RANDOM.nextInt(1, 10000000));
}

var sb = new StringBuilder();
sb.append(String.valueOf(randomGroup(locale)));
var grouping = String.valueOf(DecimalFormatSymbols.getInstance(locale).getGroupingSeparator());

for (int i = 1; i < numberOfGroups; i++) {
sb.append(grouping + randomGroup(locale));
}
return sb.toString();
}
}
3 changes: 2 additions & 1 deletion jfx/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
module omnijfx {
requires javafx.controls;
requires javafx.graphics;
requires java.logging;
requires omnijfx.internationalization;
requires java.logging;
//requires org.junit.jupiter.engine;

opens systems.terranatal.omnijfx.jfx.builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,8 @@
package systems.terranatal.omnijfx.jfx.builder;

import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.control.*;
import javafx.scene.layout.*;

/**
* Utility class containing methods to create Node and Pane builders
Expand Down Expand Up @@ -109,6 +103,23 @@ static Initializer<Slider> slider(double minimum, double maximum, double initial
return node(new Slider(minimum, maximum, initial));
}

/**
* Creates an {@link Initializer} containing a {@link RadioButton} with a label
* @param label the label
* @return an {@link Initializer} containing a {@link RadioButton} with a label
*/
static Initializer<RadioButton> radioButton(String label) {
return node(new RadioButton(label));
}

/**
* Creates an {@link Initializer} containing a {@link RadioButton}
* @return an {@link Initializer} containing a {@link RadioButton}
*/
static Initializer<RadioButton> radioButton() {
return node(new RadioButton());
}

/**
* Specialized method from {@link Builders#pane(Pane)} to create a {@link HBox} initializer
* @return an initializer for {@link HBox}
Expand Down Expand Up @@ -143,4 +154,11 @@ static PaneInitializer<StackPane> stackPane() {
return pane(new StackPane());
}

/**
* Creates a {@link GridInitializer}.
* @return a {@link GridInitializer}
*/
static GridInitializer gridPane() {
return new GridInitializer(new GridPane());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2024, Rafael Barros Felix de Sousa @ Terranatal Systems
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of omnijfx nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package systems.terranatal.omnijfx.jfx.builder;

import javafx.scene.Node;
import javafx.scene.layout.GridPane;

import java.util.Arrays;
import java.util.Collections;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class GridInitializer extends PaneInitializer<GridPane> {
public GridInitializer(GridPane instance) {
super(instance);
}

public GridInitializer(Supplier<GridPane> supplier) {
super(supplier);
}

public static GridInitializer wrap(GridPane instance) {
return new GridInitializer(instance);
}

@SafeVarargs
public final <N extends Node> GridInitializer addColumn(int colIndex, N... nodes) {
if (colIndex < 0 || nodes.length == 0) return this;
instance.addColumn(colIndex, nodes);
return this;
}

@SafeVarargs
public final GridInitializer addColumn(int colIndex, Supplier<? extends Node>... nodes) {
var stream = Stream.of(nodes).map(Supplier::get);
return addColumn(colIndex, stream.toArray(Node[]::new));
}


@SafeVarargs
public final <N extends Node> GridInitializer addRow(int rowIndex, N... nodes) {
if (rowIndex < 0 || nodes.length == 0) {
return this;
}
instance.addRow(rowIndex, nodes);
return this;
}

@SafeVarargs
public final GridInitializer addRow(int rowIndex, Supplier<? extends Node>... nodes) {
var stream = Stream.of(nodes).map(Supplier::get);
return addRow(rowIndex, stream.toArray(Node[]::new));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2024, Rafael Barros Felix de Sousa @ Terranatal Systems
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of omnijfx nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package systems.terranatal.omnijfx.jfx.builder;

import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;

import java.util.function.Supplier;

/**
* Allows the user to build a {@link ToggleGroup}
* @param <T> any implementation of {@link Toggle} interface
*/
public class ToggleGroupBuilder<T extends Toggle> implements Supplier<ToggleGroup> {
private final ToggleGroup group;

/**
* Default and only constructor
*/
public ToggleGroupBuilder() {
this.group = new ToggleGroup();
}

/**
* Returns the {@link ToggleGroup} that this builder initialized
* @return the {@link ToggleGroup} that this builder initialized
*/
@Override
public ToggleGroup get() {
return group;
}

/**
* Adds a component that implements {@link Toggle} interface to the {@link ToggleGroup}
* @param toggle a component that implements {@link Toggle}
* @return this builder
*/
public ToggleGroupBuilder addToggle(Toggle toggle) {
toggle.setToggleGroup(group);
return this;
}

/**
* Overloaded method that accepts any {@link Supplier} of a {@link Toggle} component
* and adds its result to the {@link ToggleGroup}.
* @param toggle a {@link Supplier} of a component that implements {@link Toggle}
* @return this builder
*/
public ToggleGroupBuilder addToggle(Supplier<T> toggle) {
return addToggle(toggle.get());
}
}
Loading

0 comments on commit afefe25

Please sign in to comment.