I have a question while using gridgain 8.8.9.
I have CityKey and City objects,when i specified the CityKey.COUNTRYCODE as a affinity key,
The Expiry Policy didn't work.
I put one thousand pieces of data to the cache,the key is CityKey object and the value is City object ,and the expiry time is one minute.one minute later,only two pieces of data were removed, then i used cache -scan command in visor ,all the data were removed ,but if i used cache command , the data would always exist.
IF I removed the #AffinityKeyMapped annotation on CityKey.COUNTRYCODE,the expiry policy could work.
I did the same test with gridgain 8.8.18,and the expiry policy worked all the time.
So I wonder to know if this is a bug in gridgain 8.8.9,
If it’s not a bug, what’s the reason and how to solve it.
CityKey
import org.apache.ignite.cache.affinity.AffinityKeyMapped;
public class CityKey {
/** */
private int ID;
/** */
#AffinityKeyMapped
private String COUNTRYCODE;
public CityKey(int ID, String COUNTRYCODE) {
this.ID = ID;
this.COUNTRYCODE = COUNTRYCODE;
}
public CityKey() {
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
#Override
public String toString() {
return "CityKey{" +
"ID=" + ID +
", COUNTRYCODE='" + COUNTRYCODE + '\'' +
'}';
}
public String getCOUNTRYCODE() {
return COUNTRYCODE;
}
public void setCOUNTRYCODE(String COUNTRYCODE) {
this.COUNTRYCODE = COUNTRYCODE;
}
public void assssssss(String sssss){
System.out.println(sssss);
}
}
City
public class City {
private String NAME;
private String DISTRICT;
private int POPULATION;
public City(String NAME, String DISTRICT, int POPULATION) {
this.NAME = NAME;
this.DISTRICT = DISTRICT;
this.POPULATION = POPULATION;
}
public String getNAME() {
return NAME;
}
public void setNAME(String NAME) {
this.NAME = NAME;
}
public String getDISTRICT() {
return DISTRICT;
}
public void setDISTRICT(String DISTRICT) {
this.DISTRICT = DISTRICT;
}
public int getPOPULATION() {
return POPULATION;
}
public void setPOPULATION(int POPULATION) {
this.POPULATION = POPULATION;
}
#Override
public String toString() {
return "City{" +
"NAME='" + NAME + '\'' +
", DISTRICT='" + DISTRICT + '\'' +
", POPULATION=" + POPULATION +
'}';
}
}
GridGainTest
public class GridGainTest {
public static void main(String[] args) throws Exception {
ClientConfiguration clientConfiguration = new ClientConfiguration().setAddresses("127.0.0.1:10800");
IgniteClient client = Ignition.startClient(clientConfiguration);
// client.destroyCache("testcity");
ClientCacheConfiguration cacheConfiguration = new ClientCacheConfiguration().setName("testcity");
cacheConfiguration.setExpiryPolicy(new CreatedExpiryPolicy(new Duration(TimeUnit.MINUTES, 1)));
ClientCache<CityKey, City> city = client.getOrCreateCache(cacheConfiguration);
for (int i = 1000000; i < 1001000; ++i) {
City value = new City("henan", "a place", 1000000);
CityKey key = new CityKey(i, "hn" + i);
city.put(key, value);
System.out.println(i);
}
client.close();
}
}
Here is the gridgain visor output.
visor> cache
Time of the snapshot: 2022-05-12 15:24:33
| Name(#) | Mode | Nodes | Total entries (Heap / Off-heap) | Primar
y entries (Heap / Off-heap) | Hits | Misses | Reads | Writes |
| testcity(#c0) | PARTITIONED | 1 | 1000 (0 / 1000) | min: 1
000 (0 / 1000) | min: 0 | min: 0 | min: 0 | min: 0 |
| | | | | avg: 1
000.00 (0.00 / 1000.00) | avg: 0.00 | avg: 0.00 | avg: 0.00 | avg: 0.00 |
| | | | | max: 1
000 (0 / 1000) | max: 0 | max: 0 | max: 0 | max: 0 |
+-------------------------------------------------------------------------------
----------------------------------------------------------------------------+
Use "-a" flag to see detailed statistics.
visor> cache
Time of the snapshot: 2022-05-12 15:26:50
| Name(#) | Mode | Nodes | Total entries (Heap / Off-heap) | Primar
y entries (Heap / Off-heap) | Hits | Misses | Reads | Writes |
| testcity(#c0) | PARTITIONED | 1 | 998 (0 / 998) | min: 9
98 (0 / 998) | min: 0 | min: 0 | min: 0 | min: 0 |
| | | | | avg: 9
98.00 (0.00 / 998.00) | avg: 0.00 | avg: 0.00 | avg: 0.00 | avg: 0.00 |
| | | | | max: 9
98 (0 / 998) | max: 0 | max: 0 | max: 0 | max: 0 |
+-------------------------------------------------------------------------------
----------------------------------------------------------------------------+
Use "-a" flag to see detailed statistics.
visor> cache -scan
Time of the snapshot: 2022-05-12 15:28:42
+===============================================================+
| # | Name(#) | Mode | Size (Heap / Off-heap) |
+===============================================================+
| 0 | testcity(#c0) | PARTITIONED | min: 998 (0 / 998) |
| | | | avg: 998.00 (0.00 / 998.00) |
| | | | max: 998 (0 / 998) |
+---------------------------------------------------------------+
Choose cache number ('c' to cancel) [c]: 0
Cache: testcity is empty
Most likely it's a known issue. It could be related to the improper handling of tombstones because of a corner case of faulty affinity and assignments. Try the latest GridGain 8.8.18. The issue is GG-34306 - Fixed an issue with clearing tombstones.
Related
I have a dataset that lists different activities done in each day in a string array, similar concept asked here. Each activity is delimited and can easily be separated into columns, as I've had no problem doing in Excel.
activites
Work | family | date | gaming | relax | good sleep | shopping
Work | family | date | Nature | Crusin | reading | gaming | relax | good sleep | cooking | laundry
family | date | movies & tv | gaming | sport | relax | medium sleep | cooking
Work | family | date | Photography | gaming | relax | good sleep | medium sleep | cooking
Work | family | date | Nature | reading | gaming | relax | good sleep | cleaning
What I am trying to do is make each activity into a boolean variable which has its own column as such, so it indicates 0 for not having done the activity on that day and 1 for having done the activity. It would look something like this:
Work Family Date Gaming Relax
1 1 0 1 0
1 1 1 0 0
0 0 1 0 1
So, what I ended up doing was using my knowledge of Java to reformat data. I first separated the activities into their own variables, each containing a numerical (binary) value to indicate whether or not that activity had been done that day. I had to treat sleep quality separately, so that part looks a little wonky. Here's the code, which produced the correct output:
public static void main(String[] args) throws FileNotFoundException {
Scanner scan = new Scanner(new FileReader("activities.txt"));
String[] actList = { "Work", "school", "family", "friends", "date", "nature", "crusin", "photography",
"making music/piano", "movies & tv", "reading", "gaming", "sport", "relax", "sleep", "shopping", "cleaning",
"cooking", "laundry" };
int row = 0;
while (scan.hasNextLine()) {
row++;
int col = 0;
int activityNo = 0;
int[] actValue = new int[actList.length];
String pipeDelim = scan.nextLine();
String[] actName = pipeDelim.split(" \\| ");
int sleepTagsUsed = 0;
while (activityNo < actName.length) {
col = 0;
for (String a : actName) {
if (a.contains("sleep")) {
col = 14;
if (a.equalsIgnoreCase("bad sleep") || a.equalsIgnoreCase("bad sleep\t")) {
if (col < actList.length) {
actValue[col] = 0;
if (sleepTagsUsed == 0) {
col++;
}
sleepTagsUsed++;
} else {
break;
}
if (!(activityNo > actName.length)) {
activityNo++;
}
} else if (a.equalsIgnoreCase("medium sleep") || a.equalsIgnoreCase("medium sleep\t")) {
if (col < actList.length) {
actValue[col] = 1;
if (sleepTagsUsed == 0) {
col++;
}
sleepTagsUsed++;
} else {
break;
}
if (!(activityNo > actName.length)) {
activityNo++;
}
} else if (a.equalsIgnoreCase("good sleep") || a.equalsIgnoreCase("good sleep\t")) {
if (col < actList.length) {
actValue[col] = 2;
if (sleepTagsUsed == 0) {
col++;
}
sleepTagsUsed++;
} else {
break;
}
if (!(activityNo > actName.length)) {
activityNo++;
}
} else if (a.equalsIgnoreCase("sleep early") || a.equalsIgnoreCase("sleep early\t")) {
if (col < actList.length) {
actValue[col] = 3;
if (sleepTagsUsed == 0) {
col++;
}
sleepTagsUsed++;
} else {
break;
}
if (!(activityNo > actName.length)) {
activityNo++;
}
} else {
if (col < actList.length) {
actValue[col] = -1;
} else {
break;
}
System.out.println("No sleep logged error");
}
} else {
int j = 0;
for (String i : actList) {
if (a.equalsIgnoreCase(i) || a.equalsIgnoreCase(i + "\t")) {
actValue[col] = 1;
if (activityNo > actName.length) {
break;
} else {
activityNo++;
break;
}
} else {
if (col < actList.length) {
j++;
if (j > col) {
actValue[col] = 0;
col++;
}
} else {
break;
}
}
}
col++;
}
}
}
for (int p : actValue) {
System.out.print(p + "\t");
}
System.out.println();
}
scan.close();
}
Why ANTLR don't recognize the tokens correctly?
I think that the problem is something to do with the "MemberAccessor" has an ID and the "CustomMethodCall" rule too.
Grammar
**statement
: varDeclaration
| customMethodCall;
customMethodCall : (memberAccessor '.)? ID L_PAREN expressionList R_PAREN;
memberAcessorPart
: ID
| 'this'
| ID '.getAt' L_PAREN expression R_PAREN
| ID arrayGet:
| ID L_PAREN expressionList R_PAREN;
memberAccessor
: memberAcessorPart (ACESSAR memberAcessorPart)*;
**
protected override void ReportNoViableAlternative (Parser recognizer, NoViableAltException e) {
string[] expected = new String[e.GetExpectedTokens ().Count];
int[] expectedInts = e.GetExpectedTokens ().ToArray ();
for (int i = 0; i < expected.Length; i++) {
if (expectedInts[i] == -1) expected[i] = "<EOF>";
else expected[i] = PortuProParser.DefaultVocabulary.GetDisplayName (expectedInts[i]);
}
string c = PortuProParser.DefaultVocabulary.GetDisplayName (e.StartToken.Type);
if (expected.ToList ().IndexOf (c) == -1)
throw new PortuProException (e.StartToken.Line, e.StartToken.Column, "Entrada desconhecida. '" + e.StartToken.Text + "'\n\nEsperado: '" + string.Join (", ", expected) + "'");
}
Well i've got on 'c' the 'ID' and the 'ID' is expected as token.
I have been looking all over the web site, Internet, and of course SO and can't seem to find a description or specification for the markup language being used in umlet.
In the example sequence diagram for example:
title: sample
_alpha:A~id1_|_beta:B~id2_|_gamma:G~id3_
id1->>id2:id1,id2
id2-/>id1:async Msg.
id3->>>id1:id1,id3
id1.>id3:id1,id3:async return Msg
id1->id1:id1:self
iframe{:interaction frame
id2->id3:id1,id3:async Msg.
iframe}
The ->, -->, etc are fairly obvious, but what the colons do?
Why are underlines needed, etc. Inquiring minds would like to know as this looks like a useful tool for sketching.
Found an annotated script (that doesn't seem valid as of 14.2):
//UML2 style titles are optional.
title:sequence diagram
//the participating objects are separated using the pipe symbol "|". Their names may be underlined using underscores.
_alpha:A_|_beta:B_|_gamma:G_
//The following line describes a (synchonuous) message originating from the first object sent to the second object, while both objects are active.
1->>2:1,2
//This message is asynchronous being sent from the second object to the first object. The messages is named.
2-/>1:async Msg.
//A message from the third object to the first object; both objects are active.
3->>>1:1,3
//A named asynchronous return message in UML2 style (using a stick arrow head).
1.>3:1,3:async return Msg
//This is a self call of the first object.
1->1:1:self
//Interaction frames may be used to show optional or conditional blocks within sequence diagrams.
iframe{:interaction frame
2->3:2,3:async Msg.
iframe}
RE: BNF, there's umlet-elements/src/main/javacc/SequenceAllInOneParser.jj in the source:
options {
static = false;
IGNORE_CASE = false;
JAVA_UNICODE_ESCAPE = true;
JDK_VERSION = "1.6";
// Not yet detected as a valid option by the Eclipse plugin, but works nonetheless. This is essential for GWT compatible generated code.
JAVA_TEMPLATE_TYPE = "modern";
}
PARSER_BEGIN(SequenceAllInOneParser)
package com.baselet.element.facet.specific.sequence_aio.gen;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import com.baselet.control.enums.LineType;
import com.baselet.element.facet.specific.sequence_aio.Lifeline;
import com.baselet.element.facet.specific.sequence_aio.Message.ArrowType;
import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramBuilder;
import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramException;
public class SequenceAllInOneParser {
private static final Logger log = LoggerFactory.getLogger(SequenceAllInOneParser.class);
private boolean autoTick;
/**
* Replaces "\\\\" with "\\" in the output so that any further specified match replace pair can't match the replaced "\\".
* #param matchReplacePairs
* #return the string with the replacements
*/
public static String backslashReplace(String input, String... matchReplacePairs) {
if (matchReplacePairs.length % 2 == 1) {
throw new IllegalArgumentException("matchReplacePairs must have an even number of elements.");
}
String split = "\\\\";
StringBuilder strBuilder = new StringBuilder(input.length());
int firstIndex = 0;
// use the indexOf function instead of a split, because split uses regex and regex is not 100% supported by GWT
int foundIndex = input.indexOf(split, firstIndex);
while (firstIndex < input.length()) {
int lastIndex = foundIndex == -1 ? input.length() : foundIndex;
String tmp = input.substring(firstIndex, lastIndex);
for (int j = 0; j < matchReplacePairs.length - 1; j += 2) {
tmp = tmp.replace(matchReplacePairs[j], matchReplacePairs[j + 1]);
}
strBuilder.append(tmp);
if (foundIndex != -1) {
strBuilder.append('\\');
}
firstIndex = lastIndex + split.length();
foundIndex = input.indexOf(split, firstIndex);
}
return strBuilder.toString();
}
/**
* Small data container to pass all informations.
*/
private class MessageArrowInfo {
boolean fromLeftToRight;
LineType lineType;
ArrowType arrowType;
}
private class InteractionConstraint {
private String lifelineId = null;
private String text = "";
}
private class LifelineInterval {
private String startId;
private String endId;
}
}
PARSER_END(SequenceAllInOneParser)
/* defines input to be ignored */
< * > SKIP:
{
" "
| "\t"
}
< DIAGRAM_DEF_TEXT > SKIP:
{
"\n" : DEFAULT
| "\r\n" : DEFAULT
| "\r" : DEFAULT
}
< DEFAULT > TOKEN:
{
< DIAGRAM_OPTION_OVERRIDE_ID :"overrideIds=" >
| < DIAGRAM_OPTION_AUTO_TICK: "autoTick=" >
| < TRUE: "true" >
| < FALSE: "false" >
| < DIAGRAM_TITLE: "title=" > : DIAGRAM_DEF_TEXT
| < DIAGRAM_DESC: "desc=" > : DIAGRAM_DEF_TEXT
| < LIFELINE_DEFINITIONS: "obj=" > : LIFELINE_DEF
}
< DIAGRAM_DEF_TEXT > TOKEN:
{
< DIAGRAM_DEFINITION_TEXT:
(
~ ["\\","\r","\n"]
| ("\\" ["\\", "n"] )
)+> : DIAGRAM_DEF_TEXT
}
< LIFELINE_DEF > TOKEN :
{
< LL_DEF_NEW_LINE: "\n" | "\r\n" | "\r" > : DEFAULT
| < LIFELINE_DEF_DELIMITER: "|" >
/*| < LIFELINE_TITLE_DELIMITER: "~" > integrated in the title */
| < LIFELINE_ACTOR: "ACTOR" >
| < LIFELINE_ACTIVE: "ACTIVE" >
| < LIFELINE_CREATED_LATER :"CREATED_LATER" >
| < LIFELINE_EXEC_SPEC_FROM_START: "EXECUTION" >
| < LIFELINE_TITLE: /* since | and ~ are special characters which delimit the title they need to be escaped */
(
~["~","\\","\n","\r","|"]
| ("\\" ["\\","~","|","n"])
)+
"~" >
}
< DEFAULT > TOKEN :
{
< TEXT_DELIMITER: ":" > : DIAGRAM_SEQ_TEXT
| < LIST_DELIMITER: "," >
| < OPEN_CURLY_BRACKET: "{" >
| < CLOSE_CURLY_BRACKET: "}" >
| < COMMAND_DELIMITER: ";" >
| < T_DASH: "-" >
| < T_DOT: "." >
| < T_DDOT: ".." >
| < T_EQ: "=" >
| < MESSAGE_ARROW_LEFT_OPEN: "<" >
| < MESSAGE_ARROW_LEFT_FILLED: "<<<" >
| < MESSAGE_ARROW_RIGHT_OPEN: ">" >
| < MESSAGE_ARROW_RIGHT_FILLED: ">>>" >
| < MESSAGE_DURATION_INC: "+" >
| < LOST: "lost" >
| < FOUND: "found" >
| < GATE: "gate" >
| < START_COREGION: "coregionStart=" >
| < END_COREGION: "coregionEnd=" >
| < INVARIANT: "invariant=" >
| < STATE_INVARIANT: "stateInvariant=" >
| < EXEC_SPEC_START: "on=" >
| < EXEC_SPEC_END: "off=" >
| < LL_DESTROY: "destroy=" >
| < REF: "ref=" >
| < CONTINUATION: "continuation=" >
| < TEXT_ON_LIFELINE: "text=" >
| < TICK: "tick=" >
| < COMBINED_FRAGMENT: "combinedFragment=" > : CF_OPERATOR
| < INTERACTION_CONSTRAINT: "constraint=" >
| < UNSIGNED_INT_CONSTANT: ( ["0" - "9"] ) + >
| < DEFAULT_NEW_LINE: "\n" | "\r\n" | "\r" >
}
< LIFELINE_DEF, DEFAULT > TOKEN :
{
< LIFELINE_ID: ["a"-"z","A"-"Z"] (["a"-"z","A"-"Z","0"-"9"])* >
}
< DIAGRAM_SEQ_TEXT > TOKEN :
{
< TEXT_UNTIL_NEXT_COMMAND : ( /* since ; is special characters which delimit the text it must be escaped */
~["\\","\n","\r",";"]
| ("\\" ["\\",";","n"])
)+ > : DEFAULT
}
< CF_OPERATOR > TOKEN :
{
< COMBINED_FRAGMENT_OPERATOR: /* since ; and ~ are special characters which delimit the operator they need to be escaped */
(
~["~","\\","\n","\r",";"]
| ("\\" ["\\","~","|","n",";"])
)+
"~" > : DEFAULT
}
/* general Tokens */
/* skip comments */
< * > SKIP :
{
<"//"(~["\n","\r"])*>
}
< * > TOKEN :
{
<LAST_LINE_COMMENT: "//"(~["\n","\r"])*>
}
/**
* The main function which parses the whole diagram.
* Line comments are skipped (see Tokens)
*/
SequenceDiagramBuilder start() :
{
String titleText = "";
String descText = "";
SequenceDiagramBuilder diagram = new SequenceDiagramBuilder();
autoTick = true;
}
{
(
(
titleText=DiagramTitle() /* NL skip and change state to DEFAULT */
| descText = DiagramDescription() /* NL skip and change state to DEFAULT */
| Option(diagram) < DEFAULT_NEW_LINE >
| LifelineDefinitions(diagram) /* NL part of the nonterminal */
)+//diagram definition as new NT followed by NL alternating with sequence so that the sequence= can be removed
Sequence(diagram)
)
(< LAST_LINE_COMMENT >)?
<EOF>
{
diagram.setTitle(titleText);
diagram.setText(descText);
return diagram;
}
}
String DiagramTitle() :
{
String text = "";
}
{
< DIAGRAM_TITLE >
(
< DIAGRAM_DEFINITION_TEXT > { text = backslashReplace(token.image, "\\n","\n");}
)?
{return text;}
}
String DiagramDescription() :
{
String desc = "";
}
{
< DIAGRAM_DESC >
< DIAGRAM_DEFINITION_TEXT > { desc = backslashReplace(token.image, "\\n","\n");}
{return desc;}
}
/**
* Options for the whole diagram
*/
void Option(SequenceDiagramBuilder diagram) :
{
boolean overrideIds;
}
{
< DIAGRAM_OPTION_OVERRIDE_ID > overrideIds = booleanConstant()
{
diagram.setOverrideDefaultIds(overrideIds);
}
| < DIAGRAM_OPTION_AUTO_TICK > autoTick = booleanConstant()
}
/**
* Defines all Lifelines
*/
void LifelineDefinitions(SequenceDiagramBuilder diagram) :
{}
{
< LIFELINE_DEFINITIONS > LifelineDef(diagram) (< LIFELINE_DEF_DELIMITER > LifelineDef(diagram))* < LL_DEF_NEW_LINE >
}
/**
* Defines one Lifeline, the id can't be LIFELINE_ACTOR, LIFELINE_ACTIVE
* or LIFELINE_CREATED_LATER because these are keywords.
*/
void LifelineDef(SequenceDiagramBuilder diagram) :
{
String name = "";
String id = null;
boolean createdOnStart = true;
Lifeline.LifelineHeadType headType = Lifeline.LifelineHeadType.STANDARD;
boolean execSpecFromStart = false;
}
{
name = LifelineDefTitleText() (id=LifelineId())?
(
< LIFELINE_ACTOR > { headType = Lifeline.LifelineHeadType.ACTOR; }
| < LIFELINE_ACTIVE > { headType = Lifeline.LifelineHeadType.ACTIVE_CLASS; }
| < LIFELINE_CREATED_LATER > { createdOnStart = false; }
| < LIFELINE_EXEC_SPEC_FROM_START > { execSpecFromStart = true; }
) *
{
if("lost".equals(id) || "found".equals(id)) {
throw new SequenceDiagramException("'lost' and 'found' are keywords and can not be used as lifeline identifiers.");
}
diagram.addLiveline(name, id, headType, createdOnStart, execSpecFromStart);
}
}
/** can could be multiple lines */
String LifelineDefTitleText() :
{}
{
< LIFELINE_TITLE >
{
/* remove trailing ~ and handle the escaping of \, |, n and ~ */
return backslashReplace(token.image.substring(0, token.image.length() - 1), "\\n", "\n", "\\~", "~", "\\|", "|");
}
}
/**
* Can't be one of the following, because these are keywords in the lifeline definition!
* < LIFELINE_ACTOR: "ACTOR" >
* < LIFELINE_ACTIVE: "ACTIVE" >
* < LIFELINE_CREATED_LATER :"CREATED_LATER" >
*/
String LifelineId() :
{}
{
< LIFELINE_ID >
{
return token.image;
}
}
void Sequence(SequenceDiagramBuilder diagram) :
{}
{
(
SequenceTick(diagram) < DEFAULT_NEW_LINE >
| (
SequenceElement(diagram)
( LOOKAHEAD(2) < COMMAND_DELIMITER > SequenceElement(diagram))*
(< COMMAND_DELIMITER >)?
< DEFAULT_NEW_LINE >
{ if(autoTick) { diagram.tick(); } }
)
| < DEFAULT_NEW_LINE >
)*
}
void SequenceTick(SequenceDiagramBuilder diagram) :
{
int tickCount = 1;
}
{
< TICK > (tickCount = unsignedIntConstant())?
{ diagram.tick(tickCount); }
}
void SequenceElement(SequenceDiagramBuilder diagram) :
{}
{
(
MessageOrGeneralOrderingOrText(diagram) /* Message, GeneralOrdering have a common prefix for more clarity a new nonterminal was created*/
| Coregion(diagram)
| DestroyLL(diagram)
| ExecutionSpecification(diagram)
| StateInvariant(diagram)
//| (diagram) //general ordering, TimeConstraint TimeObservation and DurationConstraint Duration Observation missing
| InteractionUse(diagram)
| Continuation(diagram)
| CombinedFragment(diagram)
)
}
void MessageOrGeneralOrderingOrText(SequenceDiagramBuilder diagram) :
{}
{
/*
* (Message and GeneralOrdering) A common prefix is: <LIFELINE_ID> "." <LIFELINE_ID> "<"
* All three have <LIFELINE_ID > as common prefix
* therefore use LOOKAHEAD(2) if it is a text, if not use a LOOKAHEAD(5) to determine if it is a message or a general ordering
*/
LOOKAHEAD(2) TextOnLifeline(diagram)
| LOOKAHEAD(5) Message(diagram)
| GeneralOrdering(diagram)
}
void CombinedFragment(SequenceDiagramBuilder diagram) :
{
LifelineInterval interval = new LifelineInterval();
String operator = "";
String id = null;
}
{
(
< COMBINED_FRAGMENT >
(
< COMBINED_FRAGMENT_OPERATOR >
{ operator = backslashReplace(token.image.substring(0, token.image.length() - 1), "\\n", "\n", "\\;", ";", "\\~", "~"); }
)
[
id = LifelineId()
[interval = LifelineInterval()]
]
{ diagram.beginCombinedFragment(interval.startId, interval.endId, id, operator); }
)
| (
< T_DASH > < T_DASH > (< T_EQ > id=LifelineId())?
{ diagram.endCombinedFragment(id); }
)
| (
< T_DDOT > (< T_EQ > id=LifelineId())?
{ diagram.endAndBeginOperand(id); }
)
}
void Message(SequenceDiagramBuilder diagram) :
{
String leftLifelineId = null;
String rightLifelineId = null;
String leftLifelineLocalId = null;
String rightLifelineLocalId = null;
MessageArrowInfo messageArrowInfo;
String msgText = "";
int lostCount = 0;
int foundCount = 0;
int gateCount = 0;
int duration = 0;
}
{
(
(
leftLifelineId = LifelineId() [LOOKAHEAD(2)< T_DOT > leftLifelineLocalId = LifelineId()]
| < LOST > { leftLifelineId = "lost"; lostCount++; }
| < FOUND > { leftLifelineId = "found"; foundCount++; }
| < GATE > { leftLifelineId = "gate"; gateCount++; }
)
messageArrowInfo = MessageArrow()
(
rightLifelineId = LifelineId() [< T_DOT > rightLifelineLocalId = LifelineId()]
| < LOST > { rightLifelineId = "lost"; lostCount++; }
| < FOUND > { rightLifelineId = "found"; foundCount++; }
| < GATE > { rightLifelineId = "gate"; gateCount++; }
)
(duration = MessageDuration())?
(msgText = TextUntilNewLine())?
)
{
if(lostCount + foundCount + gateCount > 1) {
throw new SequenceDiagramException("Error: 'lost', 'found' and 'gate' can only occur once per message.");
}
String send;
String receive;
String sendLocalId;
String receiveLocalId;
if(messageArrowInfo.fromLeftToRight) {
send = leftLifelineId;
receive = rightLifelineId;
sendLocalId = leftLifelineLocalId;
receiveLocalId = rightLifelineLocalId;
}
else {
send = rightLifelineId;
receive = leftLifelineId;
sendLocalId = rightLifelineLocalId;
receiveLocalId = leftLifelineLocalId;
}
if(gateCount > 0) {
if(duration != 0) {
throw new SequenceDiagramException("Error: a messages with a gate can only have a duration of 0, but the duration was " + duration + ".");
}
if(send.equals("gate")) {
diagram.addSendGateMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
}
else {
diagram.addReceiveGateMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
}
}
else if(send.equals("lost")) {
throw new SequenceDiagramException("Error: 'lost' can only be on the receiving end of a message.");
}
else if(send.equals("found")) {
if(duration != 0) {
throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
}
diagram.addFoundMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
}
else
{
if(receive.equals("lost")) {
if(duration != 0) {
throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
}
diagram.addLostMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
}
else if(receive.equals("found")) {
throw new SequenceDiagramException("Error: 'found' can only be on the sending end of a message.");
}
else {
diagram.addMessage(send, receive, duration, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId, receiveLocalId);
}
}
}
}
MessageArrowInfo MessageArrow():
{
MessageArrowInfo messageArrowInfo = new MessageArrowInfo();
}
{
(
(
messageArrowInfo.lineType = MessageArrowLineType()
(
< MESSAGE_ARROW_RIGHT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
| < MESSAGE_ARROW_RIGHT_FILLED > { messageArrowInfo.arrowType = ArrowType.FILLED; }
)
)
{
messageArrowInfo.fromLeftToRight = true;
}
| (
(
< MESSAGE_ARROW_LEFT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
| < MESSAGE_ARROW_LEFT_FILLED > { messageArrowInfo.arrowType = ArrowType.FILLED; }
)
messageArrowInfo.lineType = MessageArrowLineType()
)
{
messageArrowInfo.fromLeftToRight = false;
}
)
{ return messageArrowInfo; }
}
LineType MessageArrowLineType() :
{
LineType lineType;
}
{
(
< T_DASH > { lineType = LineType.SOLID; }
| < T_DOT > { lineType = LineType.DASHED; }
)
{
return lineType;
}
}
int MessageDuration() :
{
int duration;
}
{
(
(
< MESSAGE_DURATION_INC > { duration = 1; }
(
duration = unsignedIntConstant()
| (< MESSAGE_DURATION_INC > { duration++; })*
)
)
| (
< T_DASH > { duration = -1; }
(
duration = unsignedIntConstant() { duration = -duration; }
| (< T_DASH > { duration--; })*
)
)
)
{
return duration;
}
}
void GeneralOrdering(SequenceDiagramBuilder diagram):
{
String leftLifelineId = null;
String rightLifelineId = null;
String leftLifelineLocalId = null;
String rightLifelineLocalId = null;
boolean leftEarlier;
}
{
leftLifelineId = LifelineId()
< T_DOT >
leftLifelineLocalId = LifelineId()
(
< MESSAGE_ARROW_RIGHT_OPEN > { leftEarlier = true; }
| < MESSAGE_ARROW_LEFT_OPEN > { leftEarlier = false; }
)
rightLifelineId = LifelineId()
< T_DOT >
rightLifelineLocalId = LifelineId()
{
if(leftEarlier) {
diagram.addGeneralOrdering(leftLifelineId, leftLifelineLocalId, rightLifelineId, rightLifelineLocalId);
}
else {
diagram.addGeneralOrdering(rightLifelineId, rightLifelineLocalId, leftLifelineId, leftLifelineLocalId);
}
}
}
void Coregion(SequenceDiagramBuilder diagram) :
{
String lifelineId;
boolean start;
}
{
(
< START_COREGION > { start = true; }
| < END_COREGION > { start = false; }
)
lifelineId = LifelineId()
{
diagram.addCoregion(lifelineId, start);
}
}
void DestroyLL(SequenceDiagramBuilder diagram) :
{
String lifelineId;
}
{
< LL_DESTROY >
lifelineId = LifelineId()
{
diagram.destroyLifeline(lifelineId);
}
}
void ExecutionSpecification(SequenceDiagramBuilder diagram) :
{
String lifelineId;
boolean on;
}
{
(
< EXEC_SPEC_START > { on = true; }
| < EXEC_SPEC_END > { on = false; }
)
lifelineId = LifelineId()
{
diagram.changeExecutionSpecification(lifelineId, on);
}
( (< LIST_DELIMITER >)? lifelineId = LifelineId()
{
diagram.changeExecutionSpecification(lifelineId, on);
}
)*
}
void StateInvariant(SequenceDiagramBuilder diagram) :
{
String lifelineId;
String text = "";
boolean stateStyle;
}
{
(
< INVARIANT > { stateStyle = false; }
| < STATE_INVARIANT > { stateStyle = true; }
)
lifelineId = LifelineId()
(text = TextUntilNewLine())?
{
diagram.addStateInvariant(lifelineId, text, stateStyle);
}
}
void TextOnLifeline(SequenceDiagramBuilder diagram) :
{
String lifelineId;
String text = "";
}
{
[< TEXT_ON_LIFELINE >]
lifelineId = LifelineId()
text = TextUntilNewLine() /* text is mandatory, if not lookahead with message and general ordering would not work*/
{
diagram.addTextOnLifeline(lifelineId, text);
}
}
String TextUntilNewLine() :
{
}
{
< TEXT_DELIMITER >
< TEXT_UNTIL_NEXT_COMMAND >
{
return backslashReplace(token.image, "\\n", "\n", "\\;", ";");
}
}
void InteractionUse(SequenceDiagramBuilder diagram):
{
LifelineInterval interval;
String text = "";
}
{
(
< REF >
interval = LifelineInterval()
(text = TextUntilNewLine())?
)
{
diagram.addInteractionUse(interval.startId, interval.endId, text);
}
}
void Continuation(SequenceDiagramBuilder diagram):
{
LifelineInterval interval;
String text = "";
}
{
(
< CONTINUATION >
interval = LifelineInterval()
(text = TextUntilNewLine())?
)
{
diagram.addContinuation(interval.startId, interval.endId, text);
}
}
/**
* #return the start and end of the interval.
*/
LifelineInterval LifelineInterval():
{
LifelineInterval interval = new LifelineInterval();
}
{
interval.startId = LifelineId()
(< LIST_DELIMITER >)?
interval.endId = LifelineId()
{
return interval;
}
}
boolean booleanConstant() :
{ boolean value;}
{
(
<FALSE> { value = false;}
| <TRUE> { value = true;}
) {return value; }
}
int unsignedIntConstant() :
{}
{
<UNSIGNED_INT_CONSTANT> {
int value;
try {
value = Integer.parseInt(token.image);
} catch(NumberFormatException e) {
// only digits are accepted by the gramer, so the only reason for a NumberFormatException should be that the number is too big for int
throw (ParseException) new ParseException("Error: The string '" + token.image + "' couldn't be parsed as integer. The most probable reason is that the number is too big.").initCause(e);
}
return value;
}
}
Having issues trying to decipher the principal variation (PV) results.
"The principal variation is a path from the root to a leaf node, in which every node has the same value. This leaf node, whose value determines the minimax value of the root, is called the principal leaf."
The game demo below PV (move,eval) shows this line:
4,0 7,0 6,0 5,0 2,1
How can this be a valid PV since not ALL eval nodes have the same value? The AI never loses, but since the dizzying PV seems bogus, it casts a dark shadow on the AI logic. :( Hopefully, it's just a PV bug!
| | 0 | 1 | 2
---|---|--- ---|---|---
| | 3 | 4 | 5
---|---|--- ---|---|---
| | 6 | 7 | 8
Your move: 8
Thinking Cycles....: 2784300
Boards Generated...: 3956
Principal Variation: 4,0 7,0 6,0 5,0 2,1
Alpha-Beta Cutoffs.: 931
Computer Evaluation: 0
Computer Move......: 4
| | 0 | 1 | 2
---|---|--- ---|---|---
| X | 3 | 4 | 5
---|---|--- ---|---|---
| | O 6 | 7 | 8
Your move: 7
Thinking Cycles....: 410484
Boards Generated...: 575
Principal Variation: 6,0 5,0 2,1
Alpha-Beta Cutoffs.: 63
Computer Evaluation: 0
Computer Move......: 6
| | 0 | 1 | 2
---|---|--- ---|---|---
| X | 3 | 4 | 5
---|---|--- ---|---|---
X | O | O 6 | 7 | 8
Your move: 2
Thinking Cycles....: 42808
Boards Generated...: 45
Principal Variation: 5,0 3,0 1,0 0,0
Alpha-Beta Cutoffs.: 1
Computer Evaluation: 0
Computer Move......: 5
| | O 0 | 1 | 2
---|---|--- ---|---|---
| X | X 3 | 4 | 5
---|---|--- ---|---|---
X | O | O 6 | 7 | 8
Your move: 3
Thinking Cycles....: 6892
Boards Generated...: 4
Principal Variation: 0,0 1,0
Alpha-Beta Cutoffs.: 0
Computer Evaluation: 0
Computer Move......: 0
X | | O 0 | 1 | 2
---|---|--- ---|---|---
O | X | X 3 | 4 | 5
---|---|--- ---|---|---
X | O | O 6 | 7 | 8
Your move: 1
X | O | O 0 | 1 | 2
---|---|--- ---|---|---
O | X | X 3 | 4 | 5
---|---|--- ---|---|---
X | O | O 6 | 7 | 8
A draw! (*_*)
If anyone sees a bug in my code, please let me know. Thanks.
// Tic-Tac-Toe - Iterative implementation of alpha beta tree search.
// Built with Microsoft Visual Studio Professional 2013.
#include "stdafx.h"
#include <windows.h>
#include <intrin.h>
#include <stdint.h>
#define INFINITY 9999
#define NO_MOVE 9
#define NO_EVAL 2
#define X 1
#define O -1
#define Empty 0
struct values
{
int nodeMove;
int nodeEval;
int alpha;
int beta;
int player;
int board[9];
};
struct line
{
int nodeMove;
int nodeEval;
};
struct values moves[9];
int bestMove, bestEval;
int nodesCreated;
int abCutoffs;
int pvDepth, pvBestDepth;
// The principal variation pv[9] is a path from the root to a leaf node, in which every node
// has the same value. This leaf node, whose value determines the minimax value of the root,
// is called the principal leaf.
struct line pv[9] = {
{ NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL },
{ NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL },
{ NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }
};
struct line bestPV[9] = {
{ NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL },
{ NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL },
{ NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }, { NO_MOVE, NO_EVAL }
};
int board_eval(int *b)
{
// Rows.
if (b[0] && b[0] == b[1] && b[1] == b[2]) return b[0];
if (b[3] && b[3] == b[4] && b[4] == b[5]) return b[3];
if (b[6] && b[6] == b[7] && b[7] == b[8]) return b[6];
// Cols.
if (b[0] && b[0] == b[3] && b[3] == b[6]) return b[0];
if (b[1] && b[1] == b[4] && b[4] == b[7]) return b[1];
if (b[2] && b[2] == b[5] && b[5] == b[8]) return b[2];
// Center is empty.
if (!b[4]) return 0;
// Diags.
if (b[0] == b[4] && b[4] == b[8]) return b[0];
if (b[2] == b[4] && b[4] == b[6]) return b[2];
return 0;
}
void displayboard(int depth)
{
const char *t = "O X";
printf("\n\t %c | %c | %c\t\t 0 | 1 | 2\n", t[moves[depth].board[0] + 1], t[moves[depth].board[1] + 1], t[moves[depth].board[2] + 1]);
printf("\t---|---|---\t\t---|---|---\n");
printf("\t %c | %c | %c\t\t 3 | 4 | 5\n", t[moves[depth].board[3] + 1], t[moves[depth].board[4] + 1], t[moves[depth].board[5] + 1]);
printf("\t---|---|---\t\t---|---|---\n");
printf("\t %c | %c | %c\t\t 6 | 7 | 8\n\n", t[moves[depth].board[6] + 1], t[moves[depth].board[7] + 1], t[moves[depth].board[8] + 1]);
}
int find_move(int *board_arr, int nodeMove)
{
int i;
// Speedup loop using nodeMove instead of 0.
for (i = nodeMove; i < 9; i++) {
if (board_arr[i] == Empty)
return i;
}
return NO_MOVE;
}
int move_up_tree(int depth)
{
depth--;
if (depth == 0 && (moves[depth + 1].nodeEval > moves[depth].nodeEval))
{
bestMove = moves[depth].nodeMove;
bestEval = moves[depth + 1].nodeEval;
pvBestDepth = pvDepth;
pv[depth] = { bestMove, bestEval };
for (int i = 0; i < pvDepth; ++i)
{
bestPV[i].nodeMove = pv[i].nodeMove;
bestPV[i].nodeEval = pv[i].nodeEval;
pv[i] = { NO_MOVE, NO_EVAL };
}
}
if (moves[depth].player == X)
{
moves[depth].nodeEval = max(moves[depth].nodeEval, moves[depth + 1].nodeEval);
moves[depth].alpha = max(moves[depth].alpha, moves[depth].nodeEval);
}
else
{
moves[depth].nodeEval = min(moves[depth].nodeEval, moves[depth + 1].nodeEval);
moves[depth].beta = min(moves[depth].beta, moves[depth].nodeEval);
}
pv[depth] = { moves[depth].nodeMove, moves[depth].nodeEval };
moves[depth].nodeMove++;
moves[depth].nodeMove = find_move(moves[depth].board, moves[depth].nodeMove);
return depth;
}
int move_down_tree(int depth)
{
int eval;
depth++;
moves[depth] = moves[depth - 1];
nodesCreated++;
if (moves[depth].player == X)
{
moves[depth].board[moves[depth].nodeMove] = X;
moves[depth].player = O;
moves[depth].nodeEval = INFINITY;
}
else
{
moves[depth].board[moves[depth].nodeMove] = O;
moves[depth].player = X;
moves[depth].nodeEval = -INFINITY;
}
eval = board_eval(moves[depth].board);
// Leaf node.
if (eval || find_move(moves[depth].board, 0) == NO_MOVE)
{
moves[depth].nodeEval = eval;
moves[depth].nodeMove = NO_MOVE;
pvDepth = depth;
}
else
{
moves[depth].nodeMove = find_move(moves[depth].board, 0);
}
return depth;
}
void computer_move()
{
int depth = 0;
uint64_t c1, c2;
nodesCreated = 0;
abCutoffs = 0;
bestMove = NO_MOVE;
bestEval = -INFINITY;
moves[0].nodeMove = find_move(moves[0].board, 0);
moves[0].nodeEval = -INFINITY;
moves[0].alpha = -INFINITY;
moves[0].beta = INFINITY;
moves[0].player = X;
if (moves[0].nodeMove != NO_MOVE)
{
c1 = __rdtsc();
while (TRUE)
{
if (moves[depth].nodeMove == NO_MOVE)
{
if (depth == 0) break;
depth = move_up_tree(depth);
}
else if (moves[depth].alpha >= moves[depth].beta)
{
abCutoffs++;
moves[depth].nodeMove = NO_MOVE;
}
else
{
depth = move_down_tree(depth);
}
}
c2 = __rdtsc();
moves[0].board[bestMove] = X;
printf("\n");
printf("Thinking Cycles....: %d\n", c2 - c1);
printf("Boards Generated...: %d\n", nodesCreated);
printf("Principal Variation: ");
for (int i = 0; i < pvBestDepth; ++i) printf("%d,%d ", bestPV[i].nodeMove,bestPV[i].nodeEval);
printf("\n");
printf("Alpha-Beta Cutoffs.: %d\n", abCutoffs);
printf("Computer Evaluation: %d\n", bestEval);
printf("Computer Move......: %d\n", bestMove);
}
}
void init_board()
{
moves[0].board[0] = Empty;
moves[0].board[1] = Empty;
moves[0].board[2] = Empty;
moves[0].board[3] = Empty;
moves[0].board[4] = Empty;
moves[0].board[5] = Empty;
moves[0].board[6] = Empty;
moves[0].board[7] = Empty;
moves[0].board[8] = Empty;
}
void human_move()
{
int move;
char *p, s[100];
printf("Your move: ");
while (fgets(s, sizeof(s), stdin)) {
move = strtol(s, &p, 10);
if (p == s || *p != '\n') {
printf("Your move: ");
}
else break;
}
moves[0].board[move] = O;
}
int main(int argc, char **argv)
{
init_board();
displayboard(0);
while (1)
{
human_move();
computer_move();
displayboard(0);
if (board_eval(moves[0].board))
{
printf("Computer Wins! (-_-)\n");
init_board();
displayboard(0);
}
else if (find_move(moves[0].board, 0) == NO_MOVE)
{
printf("A draw! (*_*)\n");
init_board();
displayboard(0);
}
}
return 0;
}
Trying to make a simple number clicker control for BlackBerry 6/7, like this:
At heart it's just a text field and two buttons, with a Manager to space them out.
I know about the unsupported add-on TableManager, but it doesn't support column scans. And, the notion of using deeply-nested Managers I find... disturbing.
And, this will come up multiple times, so I wanted a simple, reusable component.
So, I built a simple Manager to contain these three components, even allowing you to provide your own textfield or buttons for stylistic reasons. The code is attached below. Obviously fancier than it needs to be but the work is all done in sublayout.
What actually happens is that the upper right of each of the 3 components appears in the correct place, but the 3 components are "shrink wrapped" to the minimum size needed to display their contents, ignoring the requested USE_ALL_WIDTH and USE_ALL_HEIGHT. This is probably a minor goofup, but how can I make these components actually USE ALL WIDTH and USE ALL HEIGHT? I have tried several variations on USE_ALL_* but not found the winning one yet. Of course any other improvements would also be welcome.
Thanks.
package layout;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.XYEdges;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.EditField;
/**
* XXX BROKEN DO NOT USE YET - layout fail, components get shrink-wrapped.
*
* NumberClicker Makes a layout with three components, like this:
* <pre>
* +-------------------+ +-------------------+
* | | | + |
* | 3 | |-------------------|
* | | |-------------------|
* | | | - |
* |-------------------| |-------------------|
* </pre>
* Note that by default, the buttons are set to increment and decrement the number in the textfield!
* #author Ian Darwin
*/
public class NumberClicker extends Manager {
private static final long SUBCOMPONENT_STYLE = Field.USE_ALL_HEIGHT | Field.USE_ALL_WIDTH;
private static final long MANAGER_STYLE = Field.FIELD_HCENTER | Field.FIELD_VCENTER;
final XYEdges MARGINS = new XYEdges(10,10,10,10);
EditField number = new EditField(SUBCOMPONENT_STYLE);
ButtonField plus = new ButtonField("+", SUBCOMPONENT_STYLE);
ButtonField minus = new ButtonField("-", SUBCOMPONENT_STYLE);
public NumberClicker() {
this(MANAGER_STYLE);
}
public NumberClicker(long style)
{
this(null, null, null, style);
}
/** Constructor allows you to provide your own three fields */
public NumberClicker(EditField number, ButtonField plus, ButtonField minus) {
this(number, plus, minus, MANAGER_STYLE);
}
/** Constructor allows you to provide your own three fields ANd override style.
* If any of the fields is null, the default value is used.
*/
public NumberClicker(EditField number, ButtonField plus, ButtonField minus, long style) {
super(style);
if (number != null) {
this.number = number;
} else {
this.number.setMargin(MARGINS); // set margins on our default, constructed above.
}
setValue(1);
add(this.number); // Nulls allowed, so must be careful to use "this." throughout this method.
if (plus != null) {
this.plus = plus;
} else {
this.plus.setMargin(MARGINS);
}
add(this.plus);
if (minus != null) {
this.minus = minus;
} else {
this.minus.setMargin(MARGINS);
}
add(this.minus);
this.plus.setRunnable(new Runnable() {
public void run() {
increment();
}
});
this.minus.setRunnable(new Runnable() {
public void run() {
decrement();
}
});
}
public void increment() {
number.setText(Integer.toString(Integer.parseInt(number.getText().trim()) + 1));
}
public void decrement() {
number.setText(Integer.toString(Integer.parseInt(number.getText().trim()) - 1));
}
/** Return the integer value of the clicker. Do not call if you are re-using this as a three-component layout manager! */
public int getValue() {
return Integer.parseInt(number.getText().trim());
}
public void setValue(int value) {
number.setText(Integer.toString(value));
}
/**
* Compute sizes and positions of subfields.
*
* Required by Manager
*/
public void sublayout(int width, int height) {
int layoutWidth = width;
int layoutHeight = Math.min(height, Display.getHeight()); // no scrolling here
System.err.println("Display:" + Display.getWidth() + "x" + Display.getHeight());
int halfX = layoutWidth / 2;
int halfY = layoutHeight / 2;
System.err.println("sublayout:" + width + "," + height + "; " + halfX + "," + halfY);
int numberWidth = halfX - number.getMarginLeft() - number.getMarginRight();
int numberHeight = layoutHeight - number.getMarginTop() - number.getMarginBottom();
layoutChild(number, numberWidth, numberHeight);
setPositionChild(number, 0 + number.getMarginLeft(), 0 + number.getMarginTop());
System.err.println(number + " " + numberWidth + "," + numberHeight + " " +number.getMarginLeft());
int plusWidth = halfX - plus.getMarginLeft() - plus.getMarginRight();
int plusHeight = halfY - plus.getMarginTop() - plus.getMarginBottom();
layoutChild(plus, plusWidth, plusHeight);
setPositionChild( plus, halfX + plus.getMarginLeft(), plus.getMarginTop());
int minusWidth = halfX - minus.getMarginLeft() - minus.getMarginRight();
int minusHeight = halfY - minus.getMarginTop() - minus.getMarginBottom();
layoutChild(minus, minusWidth, minusHeight);
// Use plus.getMarginHeight() for better alignment.
setPositionChild( minus, halfX + plus.getMarginLeft(), halfY + minus.getMarginTop() );
//setVirtualExtent(layoutWidth, height);
setExtent(layoutWidth, height);
}
public EditField getNumberField() {
return number;
}
public void setNumberField(EditField number) {
this.number = number;
}
public ButtonField getPlusField() {
return plus;
}
public void setPlusField(ButtonField plus) {
this.plus = plus;
}
public Field getMinusField() {
return minus;
}
public void setMinusField(ButtonField minus) {
this.minus = minus;
}
}
The closest thing to what you are trying to achieve is
Few notes:
EditField always use USE_ALL_WIDTH. It doesn't matter if you requested it or not. Therefore, if you want to limit its width you have override its layout() method. In my code snippet, its width is limited by the maximum chars allowed for this field's value (see CustomEditField).
ButtonField ignores USE_ALL_WIDTH and USE_ALL_HEIGHT. Its extent depends only on the text within the button. In order to achieve the effect of USE_ALL_WIDTH, you have to add horizontal padding to it.
Unfortunately, the padding trick won't work if you want to achieve the USE_ALL_HEIGHT effect. When you add vertical padding to a button, at some stage it will repeat its background vertically. If it is required, you will have to write a custom button field for it.
Also check BlackBerry's advanced UI components at this page.
Here is the code:
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.FontMetrics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.XYEdges;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.decor.Border;
import net.rim.device.api.ui.decor.BorderFactory;
import net.rim.device.api.ui.text.NumericTextFilter;
public class NumberClicker extends Manager {
private class CustomEditField extends EditField {
public int getPreferredWidth() {
FontMetrics fontMetrics = new FontMetrics();
getFont().getMetrics(fontMetrics);
return getMaxSize()*fontMetrics.getMaxCharWidth();
};
public int getPreferredHeight() {
// forcing the field to be single lined
return getFont().getHeight();
}
protected void layout(int width, int height) {
super.layout(
Math.min(width, getPreferredWidth()),
Math.min(height, getPreferredHeight())
);
}
}
final XYEdges MARGINS = new XYEdges(2,2,2,2);
EditField _number;
Manager _numberManager;
ButtonField _plus;
ButtonField _minus;
public NumberClicker() {
super(0);
Font font = getFont();
font = font.derive(Font.BOLD, font.getHeight() + 10);
_number = new CustomEditField();
_number.setFilter(new NumericTextFilter());
_number.setMaxSize(1);
_number.setFont(font);
setValue(1);
_numberManager = new Manager(0) {
protected void sublayout(int width, int height) {
layoutChild(_number, width, height);
setPositionChild(_number,
Math.max(0, (width - _number.getWidth())/2),
Math.max(0, (height - _number.getHeight())/2)
);
setExtent(width, height);
}
};
_numberManager.setBorder(BorderFactory.createRoundedBorder(new XYEdges()));
_numberManager.setMargin(MARGINS);
_numberManager.add(_number);
add(_numberManager);
_plus = new ButtonField("+", 0);
_plus.setMargin(MARGINS);
add(_plus);
_minus = new ButtonField("-");
_minus.setMargin(MARGINS);
add(_minus);
_plus.setRunnable(new Runnable() {
public void run() {
increment();
}
});
_minus.setRunnable(new Runnable() {
public void run() {
decrement();
}
});
}
private void increment() {
synchronized (UiApplication.getEventLock()) { //probably not needed here. overkill.
_number.setText(Integer.toString(Integer.parseInt(_number.getText().trim()) + 1));
}
}
private void decrement() {
if (Integer.parseInt(_number.getText()) <= 0) {
return;
}
synchronized (UiApplication.getEventLock()) { //probably not needed here. overkill.
_number.setText(Integer.toString(Integer.parseInt(_number.getText().trim()) - 1));
}
}
public void setValue(int value) {
if (value < 0) {
return;
}
synchronized (UiApplication.getEventLock()) { // MUST. can be called from non UI thread.
_number.setText(Integer.toString(value));
}
}
/**
* Compute sizes and positions of subfields.
*/
public void sublayout(int width, int height) {
int heightUsed = 0;
int halfX = width / 2;
Border border = _plus.getBorder();
int plusWidth = halfX - _plus.getMarginLeft() - _plus.getMarginRight();
int plusHeight = height - _plus.getMarginTop() - _plus.getMarginBottom();
// calculate horizontal padding so the button will look like USE_ALL_WIDTH
int plusHPadding = (Math.max(0, plusWidth - _plus.getPreferredWidth() - border.getLeft() - border.getRight()))/2;
_plus.setPadding(0, plusHPadding, 0, plusHPadding);
layoutChild(_plus, plusWidth, plusHeight);
setPositionChild( _plus, halfX + _plus.getMarginLeft(), _plus.getMarginTop());
heightUsed += _plus.getHeight() + _plus.getMarginTop() + _plus.getMarginBottom();
border = _minus.getBorder();
int minusWidth = halfX - _minus.getMarginLeft() - _minus.getMarginRight();
int minusHeight = height - _plus.getHeight() - _minus.getMarginTop() - _minus.getMarginBottom();
// calculate horizontal padding so the button will look like USE_ALL_WIDTH
int minusHPadding = (Math.max(0, minusWidth - _minus.getPreferredWidth() - border.getLeft() - border.getRight()))/2;
_minus.setPadding(0, minusHPadding, 0, minusHPadding);
layoutChild(_minus, minusWidth, minusHeight);
setPositionChild( _minus, halfX + _plus.getMarginLeft(), heightUsed + _minus.getMarginTop());
heightUsed += _minus.getHeight() + _minus.getMarginTop() + _minus.getMarginBottom();
int numberWidth = halfX - _numberManager.getMarginLeft() - _numberManager.getMarginRight();
int numberHeight = heightUsed - _numberManager.getMarginTop() - _numberManager.getMarginBottom();
layoutChild(_numberManager, numberWidth, numberHeight);
setPositionChild(_numberManager, _numberManager.getMarginLeft(), _numberManager.getMarginTop());
setExtent(width, heightUsed);
}
}