001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.datatypes;
011:
012: import java.util.*;
013:
014: import java.util.regex.Pattern;
015: import org.mmbase.bridge.*;
016: import org.mmbase.util.Casting;
017: import org.mmbase.util.LocalizedString;
018: import org.mmbase.util.logging.*;
019:
020: /**
021: * The datatype for String fields. Strings can be constrained by a regular expression, and have a
022: * property 'password' which indicates that the contents should not be shown.
023: *
024: * @author Pierre van Rooden
025: * @author Michiel Meeuwissen
026: * @version $Id: StringDataType.java,v 1.46 2007/09/16 17:55:28 michiel Exp $
027: * @since MMBase-1.8
028: */
029: public class StringDataType extends ComparableDataType<String>
030: implements LengthDataType<String> {
031: private static final Logger log = Logging
032: .getLoggerInstance(StringDataType.class);
033:
034: private static final long serialVersionUID = 1L; // increase this if object serialization changes (which we shouldn't do!)
035:
036: protected PatternRestriction patternRestriction = new PatternRestriction(
037: Pattern.compile("(?s)\\A.*\\z"));
038: private boolean isPassword = false;
039: protected AbstractLengthDataType.MinRestriction minLengthRestriction = new AbstractLengthDataType.MinRestriction(
040: this , 0);
041: protected AbstractLengthDataType.MaxRestriction maxLengthRestriction = new AbstractLengthDataType.MaxRestriction(
042: this , Integer.MAX_VALUE);
043:
044: /**
045: * Constructor for string data type.
046: * @param name the name of the data type
047: */
048: public StringDataType(String name) {
049: super (name, String.class);
050: }
051:
052: protected void inheritProperties(BasicDataType<String> origin) {
053: super .inheritProperties(origin);
054: if (origin instanceof StringDataType) {
055: StringDataType dataType = (StringDataType) origin;
056: isPassword = dataType.isPassword();
057: }
058: }
059:
060: public static final Pattern DOUBLE_PATTERN;
061: static {
062: // copied from javadoc of Double: http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#valueOf(java.lang.String)
063: final String Digits = "(\\p{Digit}+)";
064: final String HexDigits = "(\\p{XDigit}+)";
065: // an exponent is 'e' or 'E' followed by an optionally
066: // signed decimal integer.
067: final String Exp = "[eE][+-]?" + Digits;
068: final String fpRegex = ("[\\x00-\\x20]*" + // Optional leading "whitespace"
069: "[+-]?(" + // Optional sign character
070: "NaN|" + // "NaN" string
071: "Infinity|" + // "Infinity" string
072:
073: // A decimal floating-point string representing a finite positive
074: // number without a leading sign has at most five basic pieces:
075: // Digits . Digits ExponentPart FloatTypeSuffix
076: //
077: // Since this method allows integer-only strings as input
078: // in addition to strings of floating-point literals, the
079: // two sub-patterns below are simplifications of the grammar
080: // productions from the Java Language Specification, 2nd
081: // edition, section 3.10.2.
082:
083: // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
084: "(((" + Digits + "(\\.)?(" + Digits + "?)(" + Exp
085: + ")?)|" +
086:
087: // . Digits ExponentPart_opt FloatTypeSuffix_opt
088: "(\\.(" + Digits + ")(" + Exp + ")?)|" +
089:
090: // Hexadecimal strings
091: "((" +
092: // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
093: "(0[xX]" + HexDigits + "(\\.)?)|" +
094:
095: // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
096: "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +
097:
098: ")[pP][+-]?" + Digits + "))" + "[fFdD]?))" + "[\\x00-\\x20]*");// Optional trailing "whitespace"
099:
100: DOUBLE_PATTERN = Pattern.compile(fpRegex);
101: }
102: public static final Pattern BOOLEAN_PATTERN = Pattern
103: .compile("\\A(1|0|true|false)\\z");
104: public static final Pattern INTEGER_PATTERN = Pattern
105: .compile("\\A-?[0-9]+\\z");
106: public static final Pattern NON_NEGATIVE_INTEGER_PATTERN = Pattern
107: .compile("\\A[0-9]+\\z");
108: public static final Pattern LONG_PATTERN = INTEGER_PATTERN;
109:
110: protected void inheritRestrictions(BasicDataType origin) {
111: super .inheritRestrictions(origin);
112: if (origin instanceof StringDataType) {
113: StringDataType dataType = (StringDataType) origin;
114: patternRestriction.inherit(dataType.patternRestriction);
115: minLengthRestriction.inherit(dataType.minLengthRestriction);
116: maxLengthRestriction.inherit(dataType.maxLengthRestriction);
117: } else if (origin instanceof BooleanDataType) {
118: patternRestriction.setValue(BOOLEAN_PATTERN);
119: } else if (origin instanceof IntegerDataType) {
120: PatternRestriction parent = new PatternRestriction(
121: INTEGER_PATTERN);
122: parent.setEnforceStrength(ENFORCE_ABSOLUTE);
123: patternRestriction = new PatternRestriction(parent);
124: } else if (origin instanceof LongDataType) {
125: PatternRestriction parent = new PatternRestriction(
126: LONG_PATTERN);
127: parent.setEnforceStrength(ENFORCE_ABSOLUTE);
128: patternRestriction = new PatternRestriction(parent);
129: } else if (origin instanceof FloatDataType) {
130: PatternRestriction parent = new PatternRestriction(
131: DOUBLE_PATTERN);
132: parent.setEnforceStrength(ENFORCE_ABSOLUTE);
133: patternRestriction = new PatternRestriction(parent);
134: } else if (origin instanceof DoubleDataType) {
135: PatternRestriction parent = new PatternRestriction(
136: DOUBLE_PATTERN);
137: parent.setEnforceStrength(ENFORCE_ABSOLUTE);
138: patternRestriction = new PatternRestriction(parent);
139: }
140: if (origin instanceof NumberDataType) {
141: // number datatypes intrinsicly have a minimal and a maximal value, so these would have been interhited.
142: // but on a string they would never work (strings are compared alphabeticly), so remove those restrictions:
143: setMin(null, true);
144: setMax(null, true);
145: }
146: }
147:
148: protected void cloneRestrictions(BasicDataType origin) {
149: super .cloneRestrictions(origin);
150: if (origin instanceof StringDataType) {
151: StringDataType dataType = (StringDataType) origin;
152: patternRestriction = new PatternRestriction(
153: dataType.patternRestriction);
154: minLengthRestriction = new AbstractLengthDataType.MinRestriction(
155: this , dataType.minLengthRestriction);
156: maxLengthRestriction = new AbstractLengthDataType.MaxRestriction(
157: this , dataType.maxLengthRestriction);
158: }
159: }
160:
161: public long getLength(Object value) {
162: if (value == null)
163: return 0;
164: return ((String) value).length();
165: }
166:
167: /**
168: * {@inheritDoc}
169: */
170: public long getMinLength() {
171: return Casting.toLong(minLengthRestriction.getValue());
172: }
173:
174: /**
175: * {@inheritDoc}
176: */
177: public DataType.Restriction<Long> getMinLengthRestriction() {
178: return minLengthRestriction;
179: }
180:
181: /**
182: * {@inheritDoc}
183: */
184: public void setMinLength(long value) {
185: getMinLengthRestriction().setValue(Long.valueOf(value));
186: }
187:
188: /**
189: * {@inheritDoc}
190: */
191: public long getMaxLength() {
192: return Casting.toLong(getMaxLengthRestriction().getValue());
193: }
194:
195: /**
196: * {@inheritDoc}
197: */
198: public DataType.Restriction<Long> getMaxLengthRestriction() {
199: return maxLengthRestriction;
200: }
201:
202: /**
203: * {@inheritDoc}
204: */
205: public void setMaxLength(long value) {
206: getMaxLengthRestriction().setValue(Long.valueOf(value));
207: }
208:
209: /**
210: * Returns the regular expression pattern used to validate values for this datatype.
211: * @return the pattern.
212: */
213: public Pattern getPattern() {
214: return patternRestriction.getPattern();
215: }
216:
217: /**
218: * Returns the 'pattern' restriction, containing the value, error messages, and fixed status of this attribute.
219: * @return the restriction as a {@link DataType.Restriction}
220: */
221: public DataType.Restriction getPatternRestriction() {
222: return patternRestriction;
223: }
224:
225: /**
226: * Sets the regular expression pattern used to validate values for this datatype.
227: * @param value the pattern as a <code>Pattern</code>, or <code>null</code> if no pattern should be applied.
228: * @throws java.lang.UnsupportedOperationException if this datatype is read-only (i.e. defined by MMBase)
229: */
230: public void setPattern(Pattern value) {
231: getPatternRestriction().setValue(value);
232: }
233:
234: /**
235: * Whether or not the data represents sensitive information, in which case e.g. an input
236: * interface may present asterisks in stead of letters.
237: */
238: public boolean isPassword() {
239: return isPassword;
240: }
241:
242: public void setPassword(boolean pw) {
243: edit();
244: isPassword = pw;
245: }
246:
247: public void toXml(org.w3c.dom.Element parent) {
248: super .toXml(parent);
249: addRestriction(
250: parent,
251: "minLength",
252: "description,class,property,default,unique,required,(minInclusive|minExclusive),(maxInclusive|maxExclusive),minLength",
253: minLengthRestriction);
254: addRestriction(
255: parent,
256: "maxLength",
257: "description,class,property,default,unique,required,(minInclusive|minExclusive),(maxInclusive|maxExclusive),minLength,maxLength",
258: maxLengthRestriction);
259: addRestriction(
260: parent,
261: "pattern",
262: "description,class,property,default,unique,required,(minInclusive|minExclusive),(maxInclusive|maxExclusive),minLength,maxLength,length,pattern",
263: patternRestriction);
264: }
265:
266: public int getEnforceStrength() {
267: int enforceStrength = Math.max(super .getEnforceStrength(),
268: minLengthRestriction.getEnforceStrength());
269: enforceStrength = Math.max(enforceStrength,
270: maxLengthRestriction.getEnforceStrength());
271: return Math.max(enforceStrength, patternRestriction
272: .getEnforceStrength());
273: }
274:
275: protected Collection<LocalizedString> validateCastValueOrNull(
276: Collection<LocalizedString> errors, Object castValue,
277: Object value, Node node, Field field) {
278: errors = super .validateCastValueOrNull(errors, castValue,
279: value, node, field);
280: errors = minLengthRestriction.validate(errors, castValue, node,
281: field);
282: return errors;
283:
284: }
285:
286: protected Collection<LocalizedString> validateCastValue(
287: Collection<LocalizedString> errors, Object castValue,
288: Object value, Node node, Field field) {
289: errors = super .validateCastValue(errors, castValue, value,
290: node, field);
291: errors = patternRestriction.validate(errors, castValue, node,
292: field);
293: errors = maxLengthRestriction.validate(errors, castValue, node,
294: field);
295: return errors;
296: }
297:
298: protected StringBuilder toStringBuilder() {
299: StringBuilder buf = super .toStringBuilder();
300: Pattern p = getPattern();
301: if (p != null && !(p.pattern().equals(".*"))) {
302: buf.append(" pattern:").append(p.pattern());
303: }
304: if (isPassword()) {
305: buf.append(" password");
306: }
307: return buf;
308: }
309:
310: protected class PatternRestriction extends
311: AbstractRestriction<Pattern> {
312: PatternRestriction(PatternRestriction source) {
313: super (source);
314: }
315:
316: PatternRestriction(Pattern v) {
317: super ("pattern", v);
318: }
319:
320: Pattern getPattern() {
321: return value;
322: }
323:
324: protected boolean simpleValid(Object v, Node node, Field field) {
325: String s = Casting.toString(v);
326: boolean res = value == null ? true : value.matcher(s)
327: .matches();
328: //log.info("VALIDATING " + v + " with " + getPattern() + " -> " + res);
329: return res;
330: }
331: }
332:
333: public static void main(String[] argv) {
334: Pattern p = Pattern.compile(argv[0]);
335: System.out.println(p.matcher(argv[1]).matches());
336: }
337:
338: }
|