Skip to content

Instantly share code, notes, and snippets.

@blueskyfish
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blueskyfish/f6fc640025ae2058059c to your computer and use it in GitHub Desktop.
Save blueskyfish/f6fc640025ae2058059c to your computer and use it in GitHub Desktop.
NumberModel with Command Pattern
/*
* Copyright (c) 2014. BlueSkyFish <blueskyfish@kirchnerei.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package kirchnerei.number.model;
/**
* Interface for a command
*/
public interface Command {
/**
* Executes the command
*/
void doIt();
/**
* Undo the command
*/
void undo();
/**
* Creates a exact copy from the command with a new cursor position.
*
* @param cursor the new cursor position
* @return a copy of the command
*/
Command clone(int cursor);
}
/*
* Copyright (c) 2014. BlueSkyFish <blueskyfish@kirchnerei.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package kirchnerei.number.model;
/**
* Deletes an sign on the cursor position.
*/
public class DeleteCommand implements Command {
private final StringBuilder text;
private final int cursor;
private char sign;
public DeleteCommand(int cursor, StringBuilder text) {
this.text = text;
this.cursor = cursor;
}
@Override
public void doIt() {
int start = cursor - 1;
sign = text.charAt(start);
text.deleteCharAt(start);
}
@Override
public void undo() {
text.insert(cursor - 1, sign);
}
@Override
public Command clone(int cursor) {
return new DeleteCommand(cursor, text);
}
}
/*
* Copyright (c) 2014. BlueSkyFish <blueskyfish@kirchnerei.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package kirchnerei.number.model;
/**
* Insert a digit sign.
*/
public class InsertCommand implements Command {
private final char sign;
private final int cursor;
private final StringBuilder text;
public InsertCommand(char sign, int cursor, StringBuilder text) {
this.sign = sign;
this.cursor = cursor;
this.text = text;
}
@Override
public void doIt() {
text.insert(cursor, sign);
}
@Override
public void undo() {
text.deleteCharAt(cursor);
}
@Override
public Command clone(int cursor) {
return new InsertCommand(sign, cursor, text);
}
}
/*
* Copyright (c) 2014. Mulder3 BlueSkyFish <blueskyfish@kirchnerei.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package kirchnerei.number.model;
import java.util.Stack;
/**
* The number model contains a string with digit signs. Also an undo stack is available.
*/
public class NumberModel {
private static final int DEFAULT_CAPACITY = 32;
private final Stack<Command> commands;
private final StringBuilder text;
private int cursor = 0;
public NumberModel() {
this(DEFAULT_CAPACITY);
}
public NumberModel(int capacity) {
this.commands = new Stack<Command>();
this.text = new StringBuilder(capacity);
}
public NumberModel(String template, int capacity) {
this(capacity);
this.setTemplate(template);
}
/**
* Set the template into the internal text. Only digit signs will be insert.
*
* @param template the template (e.g: "27.08.2012")
*/
public final void setTemplate(String template) {
text.setLength(0);
for (char ch : template.toCharArray()) {
if (Character.isDigit(ch)) {
text.append(ch);
}
}
}
/**
* Move the cursor to the given position. If the new position is outside of the range, then correct the position.
*
* @param cursor the new position
*/
public void cursorMove(int cursor) {
this.cursor = cursor;
adjustCursorPosition();
}
/**
* Returns the current position
* @return the position
*/
public int getCursorPosition() {
return cursor;
}
/**
* Insert the digit sign at the cursor position.
*
* @param sign the digit
* @return the current cursor position after insert, or -1 when the sign is not a digit
*/
public int insert(char sign) {
if (Character.isDigit(sign)) {
InsertCommand cmd = new InsertCommand(sign, cursor, text);
commands.push(cmd);
cmd.doIt();
cursor++;
adjustCursorPosition();
return cursor;
}
return -1;
}
/**
* Delete the sign on the current cursor position. If the cursor is on the position 0 then returns -1.
*
* @return the current cursor position after deleting (may be the same position as before), or -1 if the cursor is at 0.
*/
public int delete() {
if (cursor > 0) {
DeleteCommand cmd = new DeleteCommand(cursor, text);
commands.push(cmd);
cmd.doIt();
adjustCursorPosition();
return cursor;
}
return -1;
}
/**
* Takes the last change and update the text again.
*
* @return the current cursor position or -1 when the command stack is empty.
*/
public int repeat() {
if (!commands.isEmpty()) {
Command cmd = commands.peek();
Command repCmd = cmd.clone(cursor);
commands.push(repCmd);
repCmd.doIt();
if (repCmd instanceof InsertCommand) {
cursor++;
}
adjustCursorPosition();
return cursor;
}
return -1;
}
/**
* Undo the last change.
*/
public int undo() {
if (!commands.isEmpty()) {
Command cmd = commands.pop();
cmd.undo();
adjustCursorPosition();
return cursor;
}
return -1;
}
@Override
public String toString() {
return text.toString();
}
/**
* Copy the digit text into a template. If the sign in the template is <code>#</code> then set the sign from
* the digit text.
*
* <pre><code>
*
* NumberModel model = new NumberModel();
*
* model.insert("2");
* model.insert("1");
* model.insert("0");
* model.insert("8");
* model.insert("2");
* model.insert("0");
* model.insert("1");
* model.insert("4");
*
* assertEquals("21082014", model.toString());
*
* assertEquals("21.08.2014", model.toString("##.##.####"));
*
* </code></pre>
*
* @param template the template
* @return the merged text.
*/
public String toString(String template) {
StringBuilder sb = new StringBuilder(template);
int index = 0;
for (int i = 0; i < template.length() && index < text.length() ; i++) {
char ch = sb.charAt(i);
if (ch == '#') {
char sign = text.charAt(index++);
sb.deleteCharAt(i).insert(i, sign);
}
}
return sb.toString();
}
private void adjustCursorPosition() {
if (cursor < 0) {
cursor = 0;
}
if (cursor > text.length()) {
cursor = text.length();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment