/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui.html;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.plaf.UIManager;
import java.util.Enumeration;
import java.util.Vector;
/**
* This class implements HTML's input format restrictions.
* These restrictions can be provided in the FORMAT attribute of the INPUT tag and are relevant for textfields.
* However as the FORMAT tag was deprectaed it is more standard to supply them in the '-wap-input-format' of the input field CSS or Style.
*
* @author Ofir Leitner
*/
class HTMLInputFormat {
/**
* The allowed literals in an input format defintion
*/
private static char[] literals = {'a','A','n','N','x','X','m','M'};
/**
* The matching allowed character set for each literal
*/
private static int[] literalConstraints =
{FormatConstraint.TYPE_LOWERCASE|FormatConstraint.TYPE_SYMBOL,
FormatConstraint.TYPE_UPPERCASE|FormatConstraint.TYPE_SYMBOL,
FormatConstraint.TYPE_NUMERIC|FormatConstraint.TYPE_SYMBOL,
FormatConstraint.TYPE_NUMERIC,
FormatConstraint.TYPE_LOWERCASE|FormatConstraint.TYPE_NUMERIC|FormatConstraint.TYPE_SYMBOL,
FormatConstraint.TYPE_UPPERCASE|FormatConstraint.TYPE_NUMERIC|FormatConstraint.TYPE_SYMBOL,
FormatConstraint.TYPE_LOWERCASE|FormatConstraint.TYPE_ANY,
FormatConstraint.TYPE_UPPERCASE|FormatConstraint.TYPE_ANY
};
private int minLength;
private int maxLength;
private Vector formatConstraints = new Vector();
/**
* This static method is used to create an HTMLInputFormat
*
* @param formatString The string representing the format defintion (As taken from the HTML/CSS)
* @return An HTMLInputFormat object containing all the constraints or null if there are none or if the string is invalid
*/
static HTMLInputFormat getInputFormat(String formatString) {
if (formatString==null) {
return null;
}
try {
HTMLInputFormat format=new HTMLInputFormat(formatString);
if (format.formatConstraints.size()==0) {
return null;
}
return format;
} catch (Exception e) {
System.out.println(e.getMessage()+" at input format string "+formatString);
return null;
}
}
/**
* A private constructor, to obtain an HTMLInputFormat object use HTMLInputFormat.getInputFormat
*
* @param formatString The string representing the format defintion (As taken from the HTML/CSS)
*/
private HTMLInputFormat(String formatString) {
String count="";
for(int i=0;i<formatString.length();i++) {
char c=formatString.charAt(i);
if (c=='*') {
if (!count.equals("")) {
throw new IllegalArgumentException("Malformed format string. The wildcard * can't appear after any other count indicator.");
} else {
count+=c;
}
} else if ((c>='0') && (c<='9')) {
if (count.equals("*")) {
throw new IllegalArgumentException("Malformed format string. Count indicators cannot appear after the wildcard *");
} else {
count+=c;
}
} else {
int constraint=-1;
for(int j=0;j<literals.length;j++) {
if (c==literals[j]) {
constraint=literalConstraints[j];
break;
}
}
if (constraint==-1) {
throw new IllegalArgumentException("Malformed format string. Unrecognized literal "+c);
}
FormatConstraint fc=new FormatConstraint(constraint, count);
formatConstraints.addElement(fc);
if (maxLength!=Integer.MAX_VALUE) {
if (fc.count==FormatConstraint.COUNT_EXACTLY_ONE) {
maxLength++;
} else if (fc.count==FormatConstraint.COUNT_NO_LIMIT) {
maxLength=Integer.MAX_VALUE;
} else {
maxLength+=fc.count;
}
}
if (fc.count==FormatConstraint.COUNT_EXACTLY_ONE) {
minLength++;
}
count="";
}
}
}
/**
* Applies the constrains represented by this object to the given TextArea.
* After invoking this method the returned TextArea should be used as restrictions are made sometimes on a new object.
* In case this is a TextField, this method will also set the input modes as needed.
*
* @param ta The TextArea to apply the constraints on.
* @return An instance of TextArea (Either the given one or a new one) with the constraints.
*/
TextArea applyConstraints(TextArea ta) {
int widestConstraint=0;
for (Enumeration e=formatConstraints.elements();e.hasMoreElements();) {
FormatConstraint constraint=(FormatConstraint)e.nextElement();
for(int i=1;i<=16;i*=2) {
if ((constraint.type & i)!=0) {
widestConstraint|=i;
}
}
}
if (maxLength!=Integer.MAX_VALUE) {
ta.setMaxSize(maxLength);
}
if (widestConstraint==FormatConstraint.TYPE_NUMERIC) {
ta.setConstraint(ta.getConstraint()|TextArea.NUMERIC);
}
if (ta instanceof TextField) {
TextField tf= (TextField)ta;
if (((widestConstraint & FormatConstraint.TYPE_SYMBOL)==0) && ((widestConstraint & FormatConstraint.TYPE_ANY)==0)) { // No symbols allowed
tf=new TextField(ta.getText()) {
protected void showSymbolDialog() { // Block symbols dialog
}
};
tf.setConstraint(ta.getConstraint());
ta=tf;
}
if ((widestConstraint & FormatConstraint.TYPE_ANY)!=0) {
if ((widestConstraint & FormatConstraint.TYPE_UPPERCASE)!=0) {
tf.setInputMode("ABC");
} else {
tf.setInputMode("abc");
}
} else {
if ((widestConstraint & FormatConstraint.TYPE_LOWERCASE)==0) {
excludeInputMode(tf, "abc");
excludeInputMode(tf, "Abc");
}
if ((widestConstraint & FormatConstraint.TYPE_UPPERCASE)==0) {
excludeInputMode(tf, "ABC");
excludeInputMode(tf, "Abc");
}
if ((widestConstraint & FormatConstraint.TYPE_NUMERIC)==0) {
excludeInputMode(tf, "123");
}
}
}
return ta;
}
/**
* Excludes the given input mode from the given TextField
*
* @param tf The TextField to work on
* @param modeToExclude The mode to exclude
*/
private void excludeInputMode(TextField tf,String modeToExclude) {
String[] curModes=tf.getInputModeOrder();
String[] newModes=new String[curModes.length-1];
int j=0;
for(int i=0;i<curModes.length;i++) {
if (!curModes[i].equals(modeToExclude)) {
if (j<newModes.length) {
newModes[j]=curModes[i];
j++;
} else {
return; //Mode was not there in the first place
}
}
}
tf.setInputModeOrder(newModes);
}
/**
* Verifies that the given String conforms to the constraints represented by this object.
*
* @param str The string to verify
* @return true if the string is valid, false otherwise.
*/
boolean verifyString(String str) {
if ((str.length()>maxLength) || (str.length()<minLength)) {
return false;
}
int i=0;
Enumeration e=formatConstraints.elements();
if (!str.equals("")) {
char c=str.charAt(i);
for (;e.hasMoreElements();) {
FormatConstraint constraint=(FormatConstraint)e.nextElement();
if (constraint.count==FormatConstraint.COUNT_EXACTLY_ONE) {
if (!verifyChar(c, constraint.type)) {
return false;
}
i++;
if (i<str.length()) {
c=str.charAt(i);
} else {
break;
}
} else {
int charNum=0;
while ((i<str.length()) && (charNum<constraint.count)) {
if (!verifyChar(c, constraint.type)) {
// Note that if a char doesn't apply to the current constraint it might conform to the next one (since we are in an
// "up to X chars" count, so perhaps from the current constraint segment there are less chars, this is why we don't
// fail, but break out, and let the char be compared against the next constraint
break;
}
i++;
charNum++;
if (i<str.length()) {
c=str.charAt(i);
} else {
break;
}
}
if (i>=str.length()) {
break;
}
}
}
if (i<str.length()) { // Chars left that are not covered by any constraint
return false;
}
}
// All chars covered, but more perhaps more constraints are still available
// They will be checked to see if anyone forces to have one more char and if so fail the verification
while(e.hasMoreElements()) {
FormatConstraint constraint=(FormatConstraint)e.nextElement();
if (constraint.count==FormatConstraint.COUNT_EXACTLY_ONE) {
return false;
}
}
return true;
}
/**
* Verifies the given character. THis method is used by verifyString on each char
*
* @param c The char to verify
* @param constraint The constraint to verify againts
* @return true if the char conforms to the given constraint, false otherwise
*/
private boolean verifyChar(char c,int constraint) {
if (((constraint & FormatConstraint.TYPE_ANY)!=0) ||
(((constraint & FormatConstraint.TYPE_NUMERIC)!=0) && (c>='0') && (c<='9')) ||
(((constraint & FormatConstraint.TYPE_UPPERCASE)!=0) && (c>='A') && (c<='Z')) ||
(((constraint & FormatConstraint.TYPE_LOWERCASE)!=0) && (c>='a') && (c<='z'))) {
return true;
}
if ((constraint & FormatConstraint.TYPE_SYMBOL)!=0) {
char[] symbols=TextField.getSymbolTable();
for(int i=0;i<symbols.length;i++) {
if (symbols[i]==c) {
return true;
}
}
}
return false;
}
/**
* A printout of a user-friendly string describing the format
*
* @return a printout of a user-friendly string describing the format
*/
public String toString() {
String str="";
String followedBy="";
int lastType=-1;
String lastString="";
int singlesCount=0;
for (Enumeration e=formatConstraints.elements();e.hasMoreElements();) {
FormatConstraint constraint=(FormatConstraint)e.nextElement();
if (constraint.count==FormatConstraint.COUNT_EXACTLY_ONE) {
if (lastType!=-1) {
if (lastType!=constraint.type) {
str+=followedBy+singlesCount+lastString;
followedBy=" followed by ";
singlesCount=1;
lastType=constraint.type;
lastString=constraint.toString();
} else {
singlesCount++;
}
} else { //lastType==-1
lastType=constraint.type;
lastString=constraint.toString();
singlesCount=1;
}
} else {
if (lastType!=-1) {
str+=followedBy+singlesCount+lastString;
followedBy=" followed by ";
lastType=-1;
singlesCount=0;
lastString="";
}
str+=followedBy+constraint.toString();
followedBy=" followed by ";
}
}
if (lastType!=-1) {
str+=followedBy+singlesCount+lastString;
}
return str;
}
// Inner classes:
/**
* This class reprensents a single constraint on an input field
* The HTMLInputFormat breaks down the format string into 1 or more FormatConstraint objects
*
* Each constraint is represented by a type and a count.
* The type indicated which typs of characters are allows and can be one or more of the TYPE_* constants ORed together.
* The count is either a number that indicates we allow up to this count or one of the COUNT_* constants (See below)
*
* @author Ofir Leitner
*/
class FormatConstraint {
/**
* All lowercase english letters are allowed
*/
static final int TYPE_LOWERCASE = 1;
/**
* All uppercase english letters are allowed
*/
static final int TYPE_UPPERCASE = 2;
/**
* All numbers are allowed
*/
static final int TYPE_NUMERIC = 4;
/**
* Symbols according to the TextField symbols table are allowed
*/
static final int TYPE_SYMBOL = 8;
/**
* All characters are allowed
*/
static final int TYPE_ANY = 16;
/**
* A constant representing that there must be one and one only of this type
*/
static final int COUNT_EXACTLY_ONE=Integer.MIN_VALUE;
/**
* A constant representing that there can be 0 or more of this type
*/
static final int COUNT_NO_LIMIT=Integer.MAX_VALUE;
int type;
int count;
/**
* The constructor which converts the count string into an integer or one of the COUNT_* constants above
*
* @param type The constraint type (one or more of the TYPE_* constants ORed together)
* @param countStr A string representing the constraint count
*/
FormatConstraint(int type,String countStr) {
if (countStr.equals("*")) {
count=COUNT_NO_LIMIT;
} else if (countStr.equals("")) {
count=COUNT_EXACTLY_ONE;
} else {
try {
count=Integer.parseInt(countStr);
} catch (NumberFormatException nfe) {
System.out.println("Invalid FormatConstraint count "+countStr);
}
}
this.type=type;
}
/**
* A printout of a user-friendly string describing this constraint
*
* @return a printout of a user-friendly string describing this constraint
*/
public String toString() {
String str="";
if (count==COUNT_EXACTLY_ONE) {
//str+="";
} else if (count==COUNT_NO_LIMIT) {
str+=UIManager.getInstance().localize("html.format.anynumber", "any number of");
} else {
str+=UIManager.getInstance().localize("html.format.upto", "up to")+" "+count;
}
str+=" ";
String orString=" "+UIManager.getInstance().localize("html.format.or", "or")+" ";
String or="";
if ((type & TYPE_ANY)!=0) {
str+="any";
} else {
if ((type & TYPE_LOWERCASE)!=0) {
str+=UIManager.getInstance().localize("html.format.lowercase", "lowercase");
or=orString;
}
if ((type & TYPE_UPPERCASE)!=0) {
str+=or+UIManager.getInstance().localize("html.format.uppercase", "uppercase");
or=orString;
}
if ((type & TYPE_NUMERIC)!=0) {
str+=or+UIManager.getInstance().localize("html.format.numeric", "numeric");
or=orString;
}
if ((type & TYPE_SYMBOL)!=0) {
str+=or+UIManager.getInstance().localize("html.format.symbol", "symbol");
}
}
str+=" ";
if ((count!=COUNT_EXACTLY_ONE) && (count!=1)) {
str+=UIManager.getInstance().localize("html.format.chars", "characters");
} else {
str+=UIManager.getInstance().localize("html.format.char", "character");
}
return str;
}
}
}