Incremental score calculation bug? - optaplanner

I've been dealing with a score corruption error for few days with no apparent reason. The error appears only on FULL_ASSERT mode and it is not related to the constraints defined on the drools file.
Following is the error :
014-07-02 14:51:49,037 [SwingWorker-pool-1-thread-4] TRACE Move index (0), score (-4/-2450/-240/-170), accepted (false) for move (EMP4#START => EMP2).
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the workingScore (-3/-1890/-640/-170) is not the uncorruptedScore (-3/-1890/-640/-250) after completedAction (EMP3#EMP4 => EMP4):
The corrupted scoreDirector has 1 ConstraintMatch(s) which are in excess (and should not be there):
com.abcdl.be.solver/MinimizeTotalTime/level3/[org.drools.core.reteoo.InitialFactImpl#4dde85f0]=-170
The corrupted scoreDirector has 1 ConstraintMatch(s) which are missing:
com.abcdl.be.solver/MinimizeTotalTime/level3/[org.drools.core.reteoo.InitialFactImpl#4dde85f0]=-250
Check your score constraints.
The error appears every time after several steps are completed for no apparent reason.
I'm developing a software to schedule several tasks considering time and resources constraints.
The whole process is represented by a directed tree diagram such that the nodes of the graph represent the tasks and the edges, the dependencies between the tasks.
To do this, the planner change the parent node of each node until he finds the best solution.
The node is the planning entity and its parent the planning variable :
#PlanningEntity(difficultyComparatorClass = NodeDifficultyComparator.class)
public class Node extends ProcessChain {
private Node parent; // Planning variable: changes during planning, between score calculations.
private String delay; // Used to display the delay for nodes of type "And"
private int id; // Used as an identifier for each node. Different nodes cannot have the same id
public Node(String name, String type, int time, int resources, String md, int id)
{
super(name, "", time, resources, type, md);
this.id = id;
}
public Node()
{
super();
this.delay = "";
}
public String getDelay() {
return delay;
}
public void setDelay(String delay) {
this.delay = delay;
}
#PlanningVariable(valueRangeProviderRefs = {"parentRange"}, strengthComparatorClass = ParentStrengthComparator.class, nullable = false)
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
/*public String toString()
{
if(this.type.equals("AND"))
return delay;
if(!this.md.isEmpty())
return Tools.excerpt(name+" : "+this.md);
return Tools.excerpt(name);
}*/
public String toString()
{
if(parent!= null)
return Tools.excerpt(name) +"#"+parent;
else
return Tools.excerpt(name);
}
public boolean equals( Object o ) {
if (this == o) {
return true;
} else if (o instanceof Node) {
Node other = (Node) o;
return new EqualsBuilder()
.append(name, other.name)
.append(id, other.id)
.isEquals();
} else {
return false;
}
}
public int hashCode() {
return new HashCodeBuilder()
.append(name)
.append(id)
.toHashCode();
}
// ************************************************************************
// Complex methods
// ************************************************************************
public int getStartTime()
{
try{
return Graph.getInstance().getNode2times().get(this).getFirst();
}
catch(NullPointerException e)
{
System.out.println("getStartTime() is null for " + this);
}
return 10;
}
public int getEndTime()
{ try{
return Graph.getInstance().getNode2times().get(this).getSecond();
}
catch(NullPointerException e)
{
System.out.println("getEndTime() is null for " + this);
}
return 10;
}
#ValueRangeProvider(id = "parentRange")
public Collection<Node> getPossibleParents()
{
Collection<Node> nodes = new ArrayList<Node>(Graph.getInstance().getNodes());
nodes.remove(this); // We remove this node from the list
if(Graph.getInstance().getParentsCount(this) > 0)
nodes.remove(Graph.getInstance().getParents(this)); // We remove its parents from the list
if(Graph.getInstance().getChildCount(this) > 0)
nodes.remove(Graph.getInstance().getChildren(this)); // We remove its children from the list
if(!nodes.contains(Graph.getInstance().getNt()))
nodes.add(Graph.getInstance().getNt());
return nodes;
}
/**
* The normal methods {#link #equals(Object)} and {#link #hashCode()} cannot be used because the rule engine already
* requires them (for performance in their original state).
* #see #solutionHashCode()
*/
public boolean solutionEquals(Object o) {
if (this == o) {
return true;
} else if (o instanceof Node) {
Node other = (Node) o;
return new EqualsBuilder()
.append(name, other.name)
.append(id, other.id)
.isEquals();
} else {
return false;
}
}
/**
* The normal methods {#link #equals(Object)} and {#link #hashCode()} cannot be used because the rule engine already
* requires them (for performance in their original state).
* #see #solutionEquals(Object)
*/
public int solutionHashCode() {
return new HashCodeBuilder()
.append(name)
.append(id)
.toHashCode();
}
}
Each move must update the graph by removing the previous edge and adding the new edge from the node to its parent, so i'm using a custom change move :
public class ParentChangeMove implements Move{
private Node node;
private Node parent;
private Graph g = Graph.getInstance();
public ParentChangeMove(Node node, Node parent) {
this.node = node;
this.parent = parent;
}
public boolean isMoveDoable(ScoreDirector scoreDirector) {
List<Dependency> dep = new ArrayList<Dependency>(g.getDependencies());
dep.add(new Dependency(parent.getName(), node.getName()));
return !ObjectUtils.equals(node.getParent(), parent) && !g.detectCycles(dep) && !g.getParents(node).contains(parent);
}
public Move createUndoMove(ScoreDirector scoreDirector) {
return new ParentChangeMove(node, node.getParent());
}
public void doMove(ScoreDirector scoreDirector) {
scoreDirector.beforeVariableChanged(node, "parent"); // before changes are made
//The previous edge is removed from the graph
if(node.getParent() != null)
{
Dependency d = new Dependency(node.getParent().getName(), node.getName());
g.removeEdge(g.getDep2link().get(d));
g.getDependencies().remove(d);
g.getDep2link().remove(d);
}
node.setParent(parent); // the move
//The new edge is added on the graph (parent ==> node)
Link link = new Link();
Dependency d = new Dependency(parent.getName(), node.getName());
g.addEdge(link, parent, node);
g.getDependencies().add(d);
g.getDep2link().put(d, link);
g.setStepTimes();
scoreDirector.afterVariableChanged(node, "parent"); // after changes are made
}
public Collection<? extends Object> getPlanningEntities() {
return Collections.singletonList(node);
}
public Collection<? extends Object> getPlanningValues() {
return Collections.singletonList(parent);
}
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o instanceof ParentChangeMove) {
ParentChangeMove other = (ParentChangeMove) o;
return new EqualsBuilder()
.append(node, other.node)
.append(parent, other.parent)
.isEquals();
} else {
return false;
}
}
public int hashCode() {
return new HashCodeBuilder()
.append(node)
.append(parent)
.toHashCode();
}
public String toString() {
return node + " => " + parent;
}
}
The graph does define multiple methods that are used by the constraints to calculate the score for each solution like the following :
rule "MinimizeTotalTime" // Minimize the total process time
when
eval(true)
then
scoreHolder.addSoftConstraintMatch(kcontext, 1, -Graph.getInstance().totalTime());
end
On other environment modes, the error does not appear but the best score calculated is not equal to the actual score.
I don't have any clue as to where the problem could come from. Note that i already checked all my equals and hashcode methods.
EDIT : Following ge0ffrey's proposition, I used collect CE in "MinimizeTotalTime" rule to check if the error comes again :
rule "MinimizeTotalTime" // Minimize the total process time
when
ArrayList() from collect(Node())
then
scoreHolder.addSoftConstraintMatch(kcontext, 0, -Graph.getInstance().totalTime());
end
At this point, no error appears and everything seems ok. But when I use "terminate early", I get the following error :
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the solution's score (-9133) is not the uncorruptedScore (-9765).
Also, I have a rule that doesn't use any method from the Graph class and seems to respect the incremental score calculation but returns another score corruption error.
The purpose of the rule is to make sure that we don't use more resources that available:
rule "addMarks" //insert a Mark each time a task starts or ends
when
Node($startTime : getStartTime(), $endTime : getEndTime())
then
insertLogical(new Mark($startTime));
insertLogical(new Mark($endTime));
end
rule "resourcesLimit" // At any time, The number of resources used must not exceed the total number of resources available
when
Mark($startTime: time)
Mark(time > $startTime, $endTime : time)
not Mark(time > $startTime, time < $endTime)
$total : Number(intValue > Global.getInstance().getAvailableResources() ) from
accumulate(Node(getEndTime() >=$endTime, getStartTime()<= $startTime, $res : resources), sum($res))
then
scoreHolder.addHardConstraintMatch(kcontext, 0, (Global.getInstance().getAvailableResources() - $total.intValue()) * ($endTime - $startTime) );
end
Following is the error :
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the workingScore (-193595) is not the uncorruptedScore (-193574) after completedAction (DWL_CM_XX_101#DWL_PA_XX_180 => DWL_PA_XX_180):
The corrupted scoreDirector has 4 ConstraintMatch(s) which are in excess (and should not be there):
com.abcdl.be.solver/resourcesLimit/level0/[43.0, 2012, 1891]=-2783
com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1870, 1805]=-1625
com.abcdl.be.solver/resourcesLimit/level0/[46.0, 1805, 1774]=-806
com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1774, 1762]=-300
The corrupted scoreDirector has 3 ConstraintMatch(s) which are missing:
com.abcdl.be.solver/resourcesLimit/level0/[43.0, 2012, 1901]=-2553
com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1870, 1762]=-2700
com.abcdl.be.solver/resourcesLimit/level0/[44.0, 1901, 1891]=-240
Check your score constraints.

A score rule that has a LHS of just "eval(true)" is inherently broken. Either that constraint is always broken, for the exact same weight, and there really is no reason to evaluate it. Or it is sometimes broken (or always broken but for different weights) and then the rule needs to refire accordingly.
Problem: the return value of Graph.getInstance().totalTime() changes as the planning variables change value. But Drools just looks at the LHS as planning variables change and it sees that nothing in the LHS has changed so there's no need to re-evaluate that score rule, when the planning variables change. Note: this is called incremental score calculation (see docs), which is a huge performance speedup.
Subproblem: The method Graph.getInstance().totalTime() is inherently not incremental.
Fix: translate that totalTime() function into a DRL function based on Node selections. You 'll probably need to use accumulate. If that's too hard (because it's a complex calculation of the critical path or so), try it anyway (for incremental score calculation's sake) or try a LHS that does a collect over all Nodes (which is like eval(true) but it will be refired every time.

Related

Custom shadow variables are not updating when visit change to one chain(vehicle) to another while planning

We have a scenario like delivery the new order and pickup the old order from same visit. For this scenario we have added pickCapacity and deliveryCapacity and also calculating accumulated capacity(weightUsedAtEndOfVisit) at end of visit in ArrivalTimeUpdatingVariableListener class.
This is working fine, But some times weightUsedAtEndOfVisit values are not getting updated in previous chain when visit change one chain to another.
TimeWindowedVisit
#PlanningEntity
public class TimeWindowedVisit{
protected Long dropWeight;
protected Long pickupWeight;
private Long weightUsedAtEndOfVisit;
#CustomShadowVariable(variableListenerRef = #PlanningVariableReference(variableName = "arrivalTime"))
public Long getWeightUsedAtEndOfVisit() {
return weightUsedAtEndOfVisit;
}
public void setWeightUsedAtEndOfVisit(Long weightUsedAtEndOfVisit) {
this.weightUsedAtEndOfVisit = weightUsedAtEndOfVisit;
}
}
ArrivalTimeUpdatingVariableListener
public class ArrivalTimeUpdatingVariableListener implements VariableListener<TimeWindowedPlanningVisit> {
#Override
public void afterEntityAdded(ScoreDirector scoreDirector, TimeWindowedPlanningVisit planningVisit) {
updateUsedCapacityAtVisit(scoreDirector, planningVisit);
}
#Override
public void afterVariableChanged(ScoreDirector scoreDirector, TimeWindowedPlanningVisit planningVisit) {
updateUsedCapacityAtVisit(scoreDirector, planningVisit);
}
...
protected void updateUsedCapacityAtVisit(ScoreDirector<?> scoreDirector, TimeWindowedPlanningVisit sourcePlanningVisit) {
TimeWindowedPlanningVisit shadowPlanningVisit;
if (sourcePlanningVisit.getPreviousStandstill() != null) {
shadowPlanningVisit = sourcePlanningVisit;
while (shadowPlanningVisit != null) {
if (shadowPlanningVisit.getPreviousStandstill() instanceof TimeWindowedPlanningVisit) {
shadowPlanningVisit = (TimeWindowedPlanningVisit) shadowPlanningVisit.getPreviousStandstill();
} else {
break;
}
}
//calculate the first stop in the chain, will be needed to get a running capacity total at each stop
//we broadcast the running total so that we can determine if we ever exceed vehicle capacity
TimeWindowedPlanningVisit firstVisit = shadowPlanningVisit;
// begin at the first stop and evaluate all of the stops to see what capacity we will be leaving the depot with
// the capacity will be a sum of all the items that need to be dropped (delivered) at the location.
long runningCapacity = 0;
while (shadowPlanningVisit != null) {
if (shadowPlanningVisit.isWarehousePickup()) {
runningCapacity += shadowPlanningVisit.getDropWeight();
}
shadowPlanningVisit = shadowPlanningVisit.getNextPlanningVisit();
}
shadowPlanningVisit = firstVisit;
// now that we know the full capcity we will be leaving the depot with, calculate the capacity at each
// stop, accounting for capacity being dropped (delivered) and picked up (returned) along the way
while (shadowPlanningVisit != null) {
runningCapacity -= shadowPlanningVisit.getDropWeight();
runningCapacity += shadowPlanningVisit.getPickupWeight();
if (!Objects.equals(runningCapacity, shadowPlanningVisit.getWeightUsedAtEndOfVisit())) {
scoreDirector.beforeVariableChanged(shadowPlanningVisit, "weightUsedAtEndOfVisit");
shadowPlanningVisit.setWeightUsedAtEndOfVisit(runningCapacity);
scoreDirector.afterVariableChanged(shadowPlanningVisit, "weightUsedAtEndOfVisit");
}
shadowPlanningVisit = shadowPlanningVisit.getNextPlanningVisit();
}
}
}
}
Constraint Class
protected Constraint vehicleCapacityStopLevel(ConstraintFactory factory) {
System.out.println("vehicleCapacityStopLevel...");
return factory.from(TimeWindowedPlanningVisit.class)
.filter(visit -> visit.getWeightUsedAtEndOfVisit() > visit.getVehicle().getWeight())
.penalizeLong("vehicle capacity exceeded at stop",
BendableLongScore.ofHard(2, 1, 0, 1),
visit -> visit.getWeightUsedAtEndOfVisit() - visit.getVehicle().getWeight());
}
Your #CustomShadowVariable should have a sources field, and not a variableListenerRef value. The latter is to piggy-back on another custom shadow variable's variable listener.
This is also an issue in optaplanner, because a variableListenerRef should fail fast if it doesn't lead to a custom shadow variable. We'll confirm and fix that.

In Linked list how to produce value

I would like to repeatedly enter a number that is added to a linked list.
But there's an error in the code at line x = new Node():
No enclosing instance of type Main is accessible. Must qualify the allocation with an enclosing instance of type Main (e.g. x.new A() where x is an instance of Main).
Is there a way to fix my code?
static Node head;
static Node p;
static Node q;
static Node x;
class Node {
int data;
Node next;
public Node link;
// Constructor to create a new node
// Next is by default initialized
// as null
Node(int d) {
data = d;
next = null;
}
public Node() {
// TODO Auto-generated constructor stub
}
}
Two issues:
class Node should either be declared as static, or be moved to a separate file.
p = x should happen outside the else block, since it should get this value also when the if condition was true:
if(head == null) {
head = x;
} else {
p.link = x;
}
p = x;
Some remarks:
If you really want to use the Node constructor without arguments, then it is better to define explicitly what the new Node's properties should be:
public Node() {
data = 0;
next = null;
}
However, it would be better to not have this constructor signature at all, and only construct the object using the data as argument:
if(num != -999){
x = new Node(num);
...and now you don't need to do any of this any more:
x.data = num;
x.link = null;
The inner Node class is not static, meaning it belongs to a specific instance of the enclosing Main class. Since it doesn't refer to any instance methods this seems like it was not done intentionally. Make the class itself static (i.e., static class Node {) and you should be fine.
First, the class Node cannot be referenced from a static context. To fix this, make it static or move it to its own file. Second, the null pointer exception happens since you don't assign p in the special case where the list is empty.
Overall, I suggest that you clean up your class and use a more structured approach. Rename p to last to make it clear that this is a reference to the last element of the list. Move the functionality to add a node into its own method to make the code more readable. Use next in the Node class to point to the next node instead of link. Create an instance of the class where your head and last reference is defined and make them private. Use break inside the loop to only define the magic number (-999) once and exit the loop when it is entered.
The whole class could look like this:
public class CustomLinkedList {
private Node head = null;
private Node last = null;
static class Node {
int data;
Node next = null;
}
public void add(int num) {
Node x = new Node();
x.data = num;
if (this.head == null) {
this.head = x;
} else {
this.last.next = x;
}
this.last = x;
}
public static void main(String[] args) {
CustomLinkedList list = new CustomLinkedList();
int count = 0;
do {
try {
BufferedReader dataIn = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter number " + (++count) + ": ");
String strNum = dataIn.readLine();
int num = Integer.parseInt(strNum);
if (num != -999) {
list.add(num);
} else {
break; // exit loop
}
} catch (IOException e) {
System.out.print(e.getMessage());
}
} while (true);
}
}

Simple IAffinityFunction implementation leads to crash

I'm using Ignite.NET 2.7.6. I did a very simple implementation of IAffinityFunction interface:
sealed class AffinityFunction : IAffinityFunction
{
public int Partitions { get { return 1; } }
public int GetPartition(object key) { return 0; }
public void RemoveNode(Guid nodeId) {}
public IEnumerable<IEnumerable<IClusterNode> > AssignPartitions(AffinityFunctionContext context)
{
List<IClusterNode> servers = new List<IClusterNode>();
foreach(IClusterNode node in context.CurrentTopologySnapshot)
{
if(!node.IsClient)
{
servers.Add(node);
}
}
m_Partitions = new List<IEnumerable<IClusterNode> >();
if(servers.Count != 0)
{
m_Partitions.Add(servers);
}
return m_Partitions;
}
private List<IEnumerable<IClusterNode> > m_Partitions;
}
Then I assign it to my cache's CacheConfiguration. I must mention that the cache is in the Replicated mode.
Firstly I start server node, then the client node and this leads to server crash with the following error in its log file:
java.lang.IndexOutOfBoundsException: index 0
at java.util.concurrent.atomic.AtomicReferenceArray.checkedByteOffset(Unknown Source)
at java.util.concurrent.atomic.AtomicReferenceArray.get(Unknown Source)
at org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionTopologyImpl.localPartition0(GridDhtPartitionTopologyImpl.java:911)
at org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionTopologyImpl.localPartition(GridDhtPartitionTopologyImpl.java:825)
As I understand this AffinityFunction must collect all the servers in one partition and returns this partition as the one to search in.
public int Partitions { get { return (m_Partitions != null ? m_Partitions.Count : 0); } }
0 is not a valid value for Partitions property (should be positive)
Partitions property should return a constant value not based on any condition. This property is evaluated once per cache per node.
The error message and documentation should be better, I'll file a ticket.

How can I use Lucene's PriorityQueue when I don't know the max size at create time?

I built a custom collector for Lucene.Net, but I can't figure out how to order (or page) the results. Everytime Collect gets called, I can add the result to an internal PriorityQueue, which I understand is the correct way to do this.
I extended the PriorityQueue, but it requires a size parameter on creation. You have to call Initialize in the constructor and pass in the max size.
However, in a collector, the searcher just calls Collect when it gets a new result, so I don't know how many results I have when I create the PriorityQueue. Based on this, I can't figure out how to make the PriorityQueue work.
I realize I'm probably missing something simple here...
PriorityQueue is not SortedList or SortedDictionary.
It is a kind of sorting implementation where it returns the top M results(your PriorityQueue's size) of N elements. You can add with InsertWithOverflow as many items as you want, but it will only hold only the top M elements.
Suppose your search resulted in 1000000 hits. Would you return all of the results to user?
A better way would be to return the top 10 elements to the user(using PriorityQueue(10)) and
if the user requests for the next 10 result, you can make a new search with PriorityQueue(20) and return the next 10 elements and so on.
This is the trick most search engines like google uses.
Everytime Commit gets called, I can add the result to an internal PriorityQueue.
I can not undestand the relationship between Commit and search, Therefore I will append a sample usage of PriorityQueue:
public class CustomQueue : Lucene.Net.Util.PriorityQueue<Document>
{
public CustomQueue(int maxSize): base()
{
Initialize(maxSize);
}
public override bool LessThan(Document a, Document b)
{
//a.GetField("field1")
//b.GetField("field2");
return //compare a & b
}
}
public class MyCollector : Lucene.Net.Search.Collector
{
CustomQueue _queue = null;
IndexReader _currentReader;
public MyCollector(int maxSize)
{
_queue = new CustomQueue(maxSize);
}
public override bool AcceptsDocsOutOfOrder()
{
return true;
}
public override void Collect(int doc)
{
_queue.InsertWithOverflow(_currentReader.Document(doc));
}
public override void SetNextReader(IndexReader reader, int docBase)
{
_currentReader = reader;
}
public override void SetScorer(Scorer scorer)
{
}
}
searcher.Search(query,new MyCollector(10)) //First page.
searcher.Search(query,new MyCollector(20)) //2nd page.
searcher.Search(query,new MyCollector(30)) //3rd page.
EDIT for #nokturnal
public class MyPriorityQueue<TObj, TComp> : Lucene.Net.Util.PriorityQueue<TObj>
where TComp : IComparable<TComp>
{
Func<TObj, TComp> _KeySelector;
public MyPriorityQueue(int size, Func<TObj, TComp> keySelector) : base()
{
_KeySelector = keySelector;
Initialize(size);
}
public override bool LessThan(TObj a, TObj b)
{
return _KeySelector(a).CompareTo(_KeySelector(b)) < 0;
}
public IEnumerable<TObj> Items
{
get
{
int size = Size();
for (int i = 0; i < size; i++)
yield return Pop();
}
}
}
var pq = new MyPriorityQueue<Document, string>(3, doc => doc.GetField("SomeField").StringValue);
foreach (var item in pq.Items)
{
}
The reason Lucene's Priority Queue is size limited is because it uses a fixed size implementation that is very fast.
Think about what is the reasonable maximum number of results to get back at a time and use that number, the "waste" for when the results are few is not that bad for the benefit it gains.
On the other hand, if you have such a huge number of results that you cannot hold them, then how are you going to be serving/displaying them? Keep in mind that this is for "top" hits so as you iterate through the results you will be hitting less and less relevant ones anyway.

What is the recommended way to make a numeric TextField in JavaFX?

I need to restrict input into a TextField to integers. Any advice?
Very old thread, but this seems neater and strips out non-numeric characters if pasted.
// force the field to be numeric only
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
if (!newValue.matches("\\d*")) {
textField.setText(newValue.replaceAll("[^\\d]", ""));
}
}
});
javafx.scene.control.TextFormatter
Updated Apr 2016
This answer was created some years ago and the original answer is largely obsolete now.
Since Java 8u40, Java has a TextFormatter which is usually best for enforcing input of specific formats such as numerics on JavaFX TextFields:
Numeric TextField for Integers in JavaFX 8 with TextFormatter and/or UnaryOperator
Java 8 U40 TextFormatter (JavaFX) to restrict user input only for decimal number
String with numbers and letters to double javafx
See also other answers to this question which specifically mention TextFormatter.
Original Answer
There are some examples of this in this gist, I have duplicated one of the examples below:
// helper text field subclass which restricts text input to a given range of natural int numbers
// and exposes the current numeric int value of the edit box as a value property.
class IntField extends TextField {
final private IntegerProperty value;
final private int minValue;
final private int maxValue;
// expose an integer value property for the text field.
public int getValue() { return value.getValue(); }
public void setValue(int newValue) { value.setValue(newValue); }
public IntegerProperty valueProperty() { return value; }
IntField(int minValue, int maxValue, int initialValue) {
if (minValue > maxValue)
throw new IllegalArgumentException(
"IntField min value " + minValue + " greater than max value " + maxValue
);
if (!((minValue <= initialValue) && (initialValue <= maxValue)))
throw new IllegalArgumentException(
"IntField initialValue " + initialValue + " not between " + minValue + " and " + maxValue
);
// initialize the field values.
this.minValue = minValue;
this.maxValue = maxValue;
value = new SimpleIntegerProperty(initialValue);
setText(initialValue + "");
final IntField intField = this;
// make sure the value property is clamped to the required range
// and update the field's text to be in sync with the value.
value.addListener(new ChangeListener<Number>() {
#Override public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
if (newValue == null) {
intField.setText("");
} else {
if (newValue.intValue() < intField.minValue) {
value.setValue(intField.minValue);
return;
}
if (newValue.intValue() > intField.maxValue) {
value.setValue(intField.maxValue);
return;
}
if (newValue.intValue() == 0 && (textProperty().get() == null || "".equals(textProperty().get()))) {
// no action required, text property is already blank, we don't need to set it to 0.
} else {
intField.setText(newValue.toString());
}
}
}
});
// restrict key input to numerals.
this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent keyEvent) {
if(intField.minValue<0) {
if (!"-0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
}
else {
if (!"0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
}
}
});
// ensure any entered values lie inside the required range.
this.textProperty().addListener(new ChangeListener<String>() {
#Override public void changed(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
if (newValue == null || "".equals(newValue) || (intField.minValue<0 && "-".equals(newValue))) {
value.setValue(0);
return;
}
final int intValue = Integer.parseInt(newValue);
if (intField.minValue > intValue || intValue > intField.maxValue) {
textProperty().setValue(oldValue);
}
value.set(Integer.parseInt(textProperty().get()));
}
});
}
}
I know this is a rather old thread, but for future readers here is another solution I found quite intuitive:
public class NumberTextField extends TextField
{
#Override
public void replaceText(int start, int end, String text)
{
if (validate(text))
{
super.replaceText(start, end, text);
}
}
#Override
public void replaceSelection(String text)
{
if (validate(text))
{
super.replaceSelection(text);
}
}
private boolean validate(String text)
{
return text.matches("[0-9]*");
}
}
Edit: Thanks none_ and SCBoy for your suggested improvements.
Starting with JavaFX 8u40, you can set a TextFormatter object on a text field:
UnaryOperator<Change> filter = change -> {
String text = change.getText();
if (text.matches("[0-9]*")) {
return change;
}
return null;
};
TextFormatter<String> textFormatter = new TextFormatter<>(filter);
fieldNport = new TextField();
fieldNport.setTextFormatter(textFormatter);
This avoids both subclassing and duplicate change events which you will get when you add a change listener to the text property and modify the text in that listener.
The TextInput has a TextFormatter which can be used to format, convert and limit the types of text that can be input.
The TextFormatter has a filter which can be used to reject input. We need to set this to reject anything that's not a valid integer. It also has a converter which we need to set to convert the string value to an integer value which we can bind later on.
Lets create a reusable filter:
public class IntegerFilter implements UnaryOperator<TextFormatter.Change> {
private final static Pattern DIGIT_PATTERN = Pattern.compile("\\d*");
#Override
public Change apply(TextFormatter.Change aT) {
return DIGIT_PATTERN.matcher(aT.getText()).matches() ? aT : null;
}
}
The filter can do one of three things, it can return the change unmodified to accept it as it is, it can alter the change in some way it deems fit or it can return null to reject the change all together.
We will use the standard IntegerStringConverter as a converter.
Putting it all together we have:
TextField textField = ...;
TextFormatter<Integer> formatter = new TextFormatter<>(
new IntegerStringConverter(), // Standard converter form JavaFX
defaultValue,
new IntegerFilter());
formatter.valueProperty().bindBidirectional(myIntegerProperty);
textField.setTextFormatter(formatter);
If you want don't need a reusable filter you can do this fancy one-liner instead:
TextFormatter<Integer> formatter = new TextFormatter<>(
new IntegerStringConverter(),
defaultValue,
c -> Pattern.matches("\\d*", c.getText()) ? c : null );
I don't like exceptions thus I used the matches function from String-Class
text.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
if (newValue.matches("\\d*")) {
int value = Integer.parseInt(newValue);
} else {
text.setText(oldValue);
}
}
});
Starting from Java SE 8u40, for such need you can use an "integer" Spinner allowing to safely select a valid integer by using the keyboard's up arrow/down arrow keys or the up arrow/down arrow provided buttons.
You can also define a min, a max and an initial value to limit the allowed values and an amount to increment or decrement by, per step.
For example
// Creates an integer spinner with 1 as min, 10 as max and 2 as initial value
Spinner<Integer> spinner1 = new Spinner<>(1, 10, 2);
// Creates an integer spinner with 0 as min, 100 as max and 10 as initial
// value and 10 as amount to increment or decrement by, per step
Spinner<Integer> spinner2 = new Spinner<>(0, 100, 10, 10);
Example of result with an "integer" spinner and a "double" spinner
A spinner is a single-line text field control that lets the user
select a number or an object value from an ordered sequence of such
values. Spinners typically provide a pair of tiny arrow buttons for
stepping through the elements of the sequence. The keyboard's up
arrow/down arrow keys also cycle through the elements. The user may
also be allowed to type a (legal) value directly into the spinner.
Although combo boxes provide similar functionality, spinners are
sometimes preferred because they don't require a drop-down list that
can obscure important data, and also because they allow for features
such as wrapping from the maximum value back to the minimum value
(e.g., from the largest positive integer to 0).
More details about the Spinner control
The preffered answer can be even smaller if you make use of Java 1.8 Lambdas
textfield.textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.matches("\\d*")) return;
textfield.setText(newValue.replaceAll("[^\\d]", ""));
});
I want to help with my idea from combining Evan Knowles answer with TextFormatter from JavaFX 8
textField.setTextFormatter(new TextFormatter<>(c -> {
if (!c.getControlNewText().matches("\\d*"))
return null;
else
return c;
}
));
so good luck ;) keep calm and code java
TextField text = new TextField();
text.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
try {
Integer.parseInt(newValue);
if (newValue.endsWith("f") || newValue.endsWith("d")) {
manualPriceInput.setText(newValue.substring(0, newValue.length()-1));
}
} catch (ParseException e) {
text.setText(oldValue);
}
}
});
The if clause is important to handle inputs like 0.5d or 0.7f which are correctly parsed by Int.parseInt(), but shouldn't appear in the text field.
Try this simple code it will do the job.
DecimalFormat format = new DecimalFormat( "#.0" );
TextField field = new TextField();
field.setTextFormatter( new TextFormatter<>(c ->
{
if ( c.getControlNewText().isEmpty() )
{
return c;
}
ParsePosition parsePosition = new ParsePosition( 0 );
Object object = format.parse( c.getControlNewText(), parsePosition );
if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
{
return null;
}
else
{
return c;
}
}));
If you want to apply the same listener to more than one TextField here is the simplest solution:
TextField txtMinPrice, txtMaxPrice = new TextField();
ChangeListener<String> forceNumberListener = (observable, oldValue, newValue) -> {
if (!newValue.matches("\\d*"))
((StringProperty) observable).set(oldValue);
};
txtMinPrice.textProperty().addListener(forceNumberListener);
txtMaxPrice.textProperty().addListener(forceNumberListener);
This one worked for me.
public void RestrictNumbersOnly(TextField tf){
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
if (!newValue.matches("|[-\\+]?|[-\\+]?\\d+\\.?|[-\\+]?\\d+\\.?\\d+")){
tf.setText(oldValue);
}
}
});
}
Here is a simple class that handles some basic validations on TextField, using TextFormatter introduced in JavaFX 8u40
EDIT:
(Code added regarding Floern's comment)
import java.text.DecimalFormatSymbols;
import java.util.regex.Pattern;
import javafx.beans.NamedArg;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
public class TextFieldValidator {
private static final String CURRENCY_SYMBOL = DecimalFormatSymbols.getInstance().getCurrencySymbol();
private static final char DECIMAL_SEPARATOR = DecimalFormatSymbols.getInstance().getDecimalSeparator();
private final Pattern INPUT_PATTERN;
public TextFieldValidator(#NamedArg("modus") ValidationModus modus, #NamedArg("countOf") int countOf) {
this(modus.createPattern(countOf));
}
public TextFieldValidator(#NamedArg("regex") String regex) {
this(Pattern.compile(regex));
}
public TextFieldValidator(Pattern inputPattern) {
INPUT_PATTERN = inputPattern;
}
public static TextFieldValidator maxFractionDigits(int countOf) {
return new TextFieldValidator(maxFractionPattern(countOf));
}
public static TextFieldValidator maxIntegers(int countOf) {
return new TextFieldValidator(maxIntegerPattern(countOf));
}
public static TextFieldValidator integersOnly() {
return new TextFieldValidator(integersOnlyPattern());
}
public TextFormatter<Object> getFormatter() {
return new TextFormatter<>(this::validateChange);
}
private Change validateChange(Change c) {
if (validate(c.getControlNewText())) {
return c;
}
return null;
}
public boolean validate(String input) {
return INPUT_PATTERN.matcher(input).matches();
}
private static Pattern maxFractionPattern(int countOf) {
return Pattern.compile("\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?");
}
private static Pattern maxCurrencyFractionPattern(int countOf) {
return Pattern.compile("^\\" + CURRENCY_SYMBOL + "?\\s?\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?\\s?\\" +
CURRENCY_SYMBOL + "?");
}
private static Pattern maxIntegerPattern(int countOf) {
return Pattern.compile("\\d{0," + countOf + "}");
}
private static Pattern integersOnlyPattern() {
return Pattern.compile("\\d*");
}
public enum ValidationModus {
MAX_CURRENCY_FRACTION_DIGITS {
#Override
public Pattern createPattern(int countOf) {
return maxCurrencyFractionPattern(countOf);
}
},
MAX_FRACTION_DIGITS {
#Override
public Pattern createPattern(int countOf) {
return maxFractionPattern(countOf);
}
},
MAX_INTEGERS {
#Override
public Pattern createPattern(int countOf) {
return maxIntegerPattern(countOf);
}
},
INTEGERS_ONLY {
#Override
public Pattern createPattern(int countOf) {
return integersOnlyPattern();
}
};
public abstract Pattern createPattern(int countOf);
}
}
You can use it like this:
textField.setTextFormatter(new TextFieldValidator(ValidationModus.INTEGERS_ONLY).getFormatter());
or you can instantiate it in a fxml file, and apply it to a customTextField with the according properties.
app.fxml:
<fx:define>
<TextFieldValidator fx:id="validator" modus="INTEGERS_ONLY"/>
</fx:define>
CustomTextField.class:
public class CustomTextField {
private TextField textField;
public CustomTextField(#NamedArg("validator") TextFieldValidator validator) {
this();
textField.setTextFormatter(validator.getFormatter());
}
}
Code on github
This is what I use:
private TextField textField;
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if(!newValue.matches("[0-9]*")){
textField.setText(oldValue);
}
}
});
The same in lambda notation would be:
private TextField textField;
textField.textProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue.matches("[0-9]*")){
textField.setText(oldValue);
}
});
This method lets TextField to finish all processing (copy/paste/undo safe).
Does not require to extend classes and allows you to decide what to do with new text after every change
(to push it to logic, or turn back to previous value, or even to modify it).
// fired by every text property change
textField.textProperty().addListener(
(observable, oldValue, newValue) -> {
// Your validation rules, anything you like
// (! note 1 !) make sure that empty string (newValue.equals(""))
// or initial text is always valid
// to prevent inifinity cycle
// do whatever you want with newValue
// If newValue is not valid for your rules
((StringProperty)observable).setValue(oldValue);
// (! note 2 !) do not bind textProperty (textProperty().bind(someProperty))
// to anything in your code. TextProperty implementation
// of StringProperty in TextFieldControl
// will throw RuntimeException in this case on setValue(string) call.
// Or catch and handle this exception.
// If you want to change something in text
// When it is valid for you with some changes that can be automated.
// For example change it to upper case
((StringProperty)observable).setValue(newValue.toUpperCase());
}
);
For your case just add this logic inside. Works perfectly.
if (newValue.equals("")) return;
try {
Integer i = Integer.valueOf(newValue);
// do what you want with this i
} catch (Exception e) {
((StringProperty)observable).setValue(oldValue);
}
Mmmm. I ran into that problem weeks ago. As the API doesn't provide a control to achieve that,
you may want to use your own one. I used something like:
public class IntegerBox extends TextBox {
public-init var value : Integer = 0;
protected function apply() {
try {
value = Integer.parseInt(text);
} catch (e : NumberFormatException) {}
text = "{value}";
}
override var focused = false on replace {apply()};
override var action = function () {apply()}
}
It's used the same way that a normal TextBox,
but has also a value attribute which stores the entered integer.
When the control looses the focus, it validates the value and reverts it (if isn't valid).
this Code Make your textField Accept only Number
textField.lengthProperty().addListener((observable, oldValue, newValue) -> {
if(newValue.intValue() > oldValue.intValue()){
char c = textField.getText().charAt(oldValue.intValue());
/** Check if the new character is the number or other's */
if( c > '9' || c < '0'){
/** if it's not number then just setText to previous one */
textField.setText(textField.getText().substring(0,textField.getText().length()-1));
}
}
});
This code works fine for me even if you try to copy/paste.
myTextField.textProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue.matches("\\d*")) {
myTextField.setText(oldValue);
}
});
In recent updates of JavaFX, you have to set new text in Platform.runLater method just like this:
private void set_normal_number(TextField textField, String oldValue, String newValue) {
try {
int p = textField.getCaretPosition();
if (!newValue.matches("\\d*")) {
Platform.runLater(() -> {
textField.setText(newValue.replaceAll("[^\\d]", ""));
textField.positionCaret(p);
});
}
} catch (Exception e) {
}
}
It's a good idea to set caret position too.
I would like to improve Evan Knowles answer: https://stackoverflow.com/a/30796829/2628125
In my case I had class with handlers for UI Component part. Initialization:
this.dataText.textProperty().addListener((observable, oldValue, newValue) -> this.numericSanitization(observable, oldValue, newValue));
And the numbericSanitization method:
private synchronized void numericSanitization(ObservableValue<? extends String> observable, String oldValue, String newValue) {
final String allowedPattern = "\\d*";
if (!newValue.matches(allowedPattern)) {
this.dataText.setText(oldValue);
}
}
Keyword synchronized is added to prevent possible render lock issue in javafx if setText will be called before old one is finished execution. It is easy to reproduce if you will start typing wrong chars really fast.
Another advantage is that you keep only one pattern to match and just do rollback. It is better because you can easily abstragate solution for different sanitization patterns.
rate_text.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String s="";
for(char c : newValue.toCharArray()){
if(((int)c >= 48 && (int)c <= 57 || (int)c == 46)){
s+=c;
}
}
rate_text.setText(s);
}
});
This works fine as it allows you to enter only integer value and decimal value (having ASCII code 46).
Another very simple solution would be:
TextField tf = new TextField();
tf.addEventFilter(KeyEvent.ANY, event -> {
if (!event.getCharacter().trim().matches("\\d?")) {
event.consume();
}
});
A little late, but if you also what to include decimals:
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!newValue.matches("\\d{0,7}([\\.]\\d{0,4})?")) {
textField.setText(oldValue);
}
}