0001: /*
0002:
0003: This software is OSI Certified Open Source Software.
0004: OSI Certified is a certification mark of the Open Source Initiative.
0005:
0006: The license (Mozilla version 1.0) can be read at the MMBase site.
0007: See http://www.MMBase.org/license
0008:
0009: */
0010:
0011: package org.mmbase.datatypes;
0012:
0013: import java.io.*;
0014: import java.util.*;
0015:
0016: import org.mmbase.bridge.*;
0017: import org.mmbase.bridge.util.Queries;
0018: import org.mmbase.core.AbstractDescriptor;
0019: import org.mmbase.core.util.Fields;
0020: import org.mmbase.datatypes.processors.*;
0021: import org.mmbase.security.Rank;
0022: import org.mmbase.storage.search.Constraint;
0023: import org.mmbase.storage.search.FieldCompareConstraint;
0024: import org.mmbase.util.*;
0025: import org.mmbase.util.logging.Logger;
0026: import org.mmbase.util.logging.Logging;
0027: import org.mmbase.util.xml.DocumentReader;
0028: import org.w3c.dom.Element;
0029:
0030: /**
0031: * Every DataType extends this one. It's extensions can however implement several extensions of the
0032: * DataType interface (e.g. some datatypes (at least {@link StringDataType}) are both {@link LengthDataType}
0033: * and {@link ComparableDataType}, and some are only one ({@link BinaryDataType}, {@link
0034: * NumberDataType}). In other words, this arrangement is like this, because java does not support
0035: * Multipible inheritance.
0036: *
0037: * @author Pierre van Rooden
0038: * @author Michiel Meeuwissen
0039: * @since MMBase-1.8
0040: * @version $Id: BasicDataType.java,v 1.83 2008/02/16 22:13:53 nklasens Exp $
0041: */
0042:
0043: public class BasicDataType<C> extends AbstractDescriptor implements
0044: DataType<C>, Cloneable, Comparable<DataType<C>>, Descriptor {
0045: /**
0046: * The bundle used by datatype to determine default prompts for error messages when a
0047: * validation fails.
0048: */
0049: public static final String DATATYPE_BUNDLE = "org.mmbase.datatypes.resources.datatypes";
0050: private static final Logger log = Logging
0051: .getLoggerInstance(BasicDataType.class);
0052:
0053: protected RequiredRestriction requiredRestriction = new RequiredRestriction(
0054: false);
0055: protected UniqueRestriction uniqueRestriction = new UniqueRestriction(
0056: false);
0057: protected TypeRestriction typeRestriction = new TypeRestriction();
0058: protected EnumerationRestriction enumerationRestriction = new EnumerationRestriction(
0059: (LocalizedEntryListFactory<C>) null);
0060:
0061: /**
0062: * The datatype from which this datatype originally inherited it's properties.
0063: */
0064: protected BasicDataType<?> origin = null;
0065:
0066: private Object owner;
0067: private Class<C> classType;
0068: private C defaultValue;
0069:
0070: private CommitProcessor commitProcessor = EmptyCommitProcessor
0071: .getInstance();
0072: private Processor[] getProcessors;
0073: private Processor[] setProcessors;
0074:
0075: private Element xml = null;
0076:
0077: /**
0078: * Create a data type object of unspecified class type
0079: * @param name the name of the data types
0080: */
0081: public BasicDataType(String name) {
0082: this (name, (Class<C>) Object.class);
0083: }
0084:
0085: /**
0086: * Create a data type object
0087: * @param name the name of the data type
0088: * @param classType the class of the data type's possible value
0089: */
0090: protected BasicDataType(String name, Class<C> classType) {
0091: super (name);
0092: this .classType = classType;
0093: owner = null;
0094: }
0095:
0096: private static final long serialVersionUID = 1L; // increase this if object serialization changes (which we shouldn't do!)
0097:
0098: // implementation of serializable
0099: private void writeObject(ObjectOutputStream out) throws IOException {
0100: out.writeUTF(key);
0101: out.writeObject(description);
0102: out.writeObject(guiName);
0103: out.writeObject(requiredRestriction);
0104: out.writeObject(uniqueRestriction);
0105: out.writeObject(enumerationRestriction.getEnumerationFactory());
0106: if (owner instanceof Serializable) {
0107: out.writeObject(owner);
0108: } else {
0109: out.writeObject(owner == null ? null : "OWNER");
0110: }
0111: out.writeObject(classType);
0112: if (defaultValue instanceof Serializable
0113: || defaultValue == null) {
0114: out.writeObject(defaultValue);
0115: } else {
0116: log
0117: .warn("Default value "
0118: + defaultValue.getClass()
0119: + " '"
0120: + defaultValue
0121: + "' is not serializable, taking it null, which may not be correct.");
0122: out.writeObject(null);
0123: }
0124: out.writeObject(commitProcessor);
0125: out.writeObject(getProcessors);
0126: out.writeObject(setProcessors);
0127: }
0128:
0129: // implementation of serializable
0130: private void readObject(ObjectInputStream in) throws IOException,
0131: ClassNotFoundException {
0132: key = in.readUTF();
0133: description = (LocalizedString) in.readObject();
0134: guiName = (LocalizedString) in.readObject();
0135: requiredRestriction = (RequiredRestriction) in.readObject();
0136: uniqueRestriction = (UniqueRestriction) in.readObject();
0137: enumerationRestriction = new EnumerationRestriction(
0138: (LocalizedEntryListFactory<C>) in.readObject());
0139: typeRestriction = new TypeRestriction(); // its always the same, so no need actually persisting it.
0140: owner = in.readObject();
0141: try {
0142: classType = (Class<C>) in.readObject();
0143: } catch (Throwable t) {
0144: // if some unknown class, simply fall back
0145: classType = (Class<C>) Object.class;
0146: }
0147: defaultValue = (C) in.readObject();
0148: commitProcessor = (CommitProcessor) in.readObject();
0149: getProcessors = (Processor[]) in.readObject();
0150: setProcessors = (Processor[]) in.readObject();
0151: }
0152:
0153: public String getBaseTypeIdentifier() {
0154: return Fields.getTypeDescription(getBaseType()).toLowerCase();
0155: }
0156:
0157: public int getBaseType() {
0158: return Fields.classToType(classType);
0159: }
0160:
0161: /**
0162: * {@inheritDoc}
0163: * Calls both {@link #inheritProperties} and {@link #inheritRestrictions}.
0164: */
0165: public final void inherit(BasicDataType<C> origin) {
0166: edit();
0167: inheritProperties(origin);
0168: inheritRestrictions(origin);
0169: }
0170:
0171: /**
0172: * Properties are members of the datatype that can easily be copied/clones.
0173: */
0174: protected void inheritProperties(BasicDataType<C> origin) {
0175: this .origin = origin;
0176:
0177: defaultValue = origin.getDefaultValue();
0178:
0179: commitProcessor = (CommitProcessor) (origin.commitProcessor instanceof PublicCloneable ? ((PublicCloneable) origin.commitProcessor)
0180: .clone()
0181: : origin.commitProcessor);
0182: if (origin.getProcessors == null) {
0183: getProcessors = null;
0184: } else {
0185: getProcessors = origin.getProcessors.clone();
0186: }
0187: if (origin.setProcessors == null) {
0188: setProcessors = null;
0189: } else {
0190: setProcessors = origin.setProcessors.clone();
0191: }
0192: }
0193:
0194: /**
0195: * If a datatype is cloned, the restrictions of it (normally implemented as inner classes), must be reinstantiated.
0196: */
0197: protected void cloneRestrictions(BasicDataType<C> origin) {
0198: enumerationRestriction = new EnumerationRestriction(
0199: origin.enumerationRestriction);
0200: requiredRestriction = new RequiredRestriction(
0201: origin.requiredRestriction);
0202: uniqueRestriction = new UniqueRestriction(
0203: origin.uniqueRestriction);
0204: }
0205:
0206: /**
0207: * If a datatype inherits from another datatype all its restrictions inherit too.
0208: */
0209: protected void inheritRestrictions(BasicDataType<C> origin) {
0210: if (!origin.getEnumerationFactory().isEmpty()) {
0211: enumerationRestriction
0212: .inherit(origin.enumerationRestriction);
0213: if (enumerationRestriction.value != null) {
0214: LocalizedEntryListFactory<C> fact = enumerationRestriction
0215: .getEnumerationFactory();
0216: if (!origin.getTypeAsClass().equals(getTypeAsClass())) {
0217: // Reevaluate XML configuration, because it was done with a 'wrong' suggestion for the wrapper class.
0218: Element elm = fact.toXml();
0219: if (elm == null) {
0220: log
0221: .warn("Did not get XML from Factory "
0222: + fact);
0223: } else {
0224: // need to clone the actual factory,
0225: // since it will otherwise change the original restrictions.
0226: fact = new LocalizedEntryListFactory<C>();
0227: fact.fillFromXml(elm, getTypeAsClass());
0228: enumerationRestriction.setValue(fact);
0229: }
0230: }
0231: }
0232: }
0233:
0234: requiredRestriction.inherit(origin.requiredRestriction);
0235: uniqueRestriction.inherit(origin.uniqueRestriction);
0236: }
0237:
0238: /**
0239: * {@inheritDoc}
0240: */
0241: public BasicDataType<?> getOrigin() {
0242: return origin;
0243: }
0244:
0245: /**
0246: * {@inheritDoc}
0247: */
0248: public Class<C> getTypeAsClass() {
0249: return classType;
0250: }
0251:
0252: /**
0253: * Checks if the passed object is of the correct class (compatible with the type of this DataType),
0254: * and throws an IllegalArgumentException if it doesn't.
0255: * @param value teh value whose type (class) to check
0256: * @throws IllegalArgumentException if the type is not compatible
0257: */
0258: protected boolean isCorrectType(Object value) {
0259: return (value == null && !isRequired())
0260: || Casting.isType(classType, value);
0261: }
0262:
0263: /**
0264: * {@inheritDoc}
0265: */
0266: public void checkType(Object value) {
0267: if (!isCorrectType(value)) {
0268: // customize this?
0269: throw new IllegalArgumentException("DataType of '" + value
0270: + "' for '" + getName() + "' must be of type "
0271: + classType + " (but is "
0272: + (value == null ? value : value.getClass()) + ")");
0273: }
0274: }
0275:
0276: /**
0277: * {@inheritDoc}
0278: *
0279: * Tries to determin cloud by node and field if possible and wraps {@link #preCast(Object, Cloud, Node, Field)}.
0280: */
0281: public final <D> D preCast(D value, Node node, Field field) {
0282: //public final Object preCast(Object value, Node node, Field field) {
0283: return preCast(value, getCloud(node, field), node, field);
0284: }
0285:
0286: /**
0287: * This method is as yet unused, but can be anticipated
0288: */
0289: //public final <D> D preCast(D value, Cloud cloud) {
0290: public final Object preCast(Object value, Cloud cloud) {
0291: return preCast(value, cloud, null, null);
0292: }
0293:
0294: /**
0295: * This method implements 'precasting', which can be seen as a kind of datatype specific
0296: * casting. It should anticipate that every argument can be <code>null</code>. It should not
0297: * change the actual type of the value.
0298: */
0299: protected <D> D preCast(D value, Cloud cloud, Node node, Field field) {
0300: if (value == null)
0301: return null;
0302: D preCast = enumerationRestriction.preCast(value, cloud);
0303: return preCast;
0304: }
0305:
0306: /**
0307: * {@inheritDoc}
0308: *
0309: * No need to override this. It is garantueed by javadoc that cast should work out of preCast
0310: * using Casting.toType. So that is what this final implementation is doing.
0311: *
0312: * Override {@link #preCast(Object, Cloud, Node, Field)}
0313: */
0314: public final C cast(Object value, final Node node, final Field field) {
0315: if (origin != null
0316: && (!origin.getClass().isAssignableFrom(getClass()))) {
0317: // if inherited from incompatible type, then first try to cast in the way of origin.
0318: // e.g. if origin is Date, but actual type is integer, then casting of 'today' works now.
0319: value = origin.cast(value, node, field);
0320: }
0321: Cloud cloud = getCloud(node, field);
0322: try {
0323: return cast(value, cloud, node, field);
0324: } catch (CastException ce) {
0325: log.error(ce);
0326: return Casting.toType(classType, cloud, preCast(value,
0327: cloud, node, field));
0328: }
0329: }
0330:
0331: /**
0332: * Utility to avoid repetitive calling of getCloud
0333: */
0334: protected C cast(Object value, Cloud cloud, Node node, Field field)
0335: throws CastException {
0336: Object preCast = preCast(value, cloud, node, field);
0337: if (preCast == null)
0338: return null;
0339: C cast = Casting.toType(classType, cloud, preCast);
0340: return cast;
0341: }
0342:
0343: protected final Cloud getCloud(Node node, Field field) {
0344: if (node != null)
0345: return node.getCloud();
0346: if (field != null)
0347: return field.getNodeManager().getCloud();
0348: return null;
0349: }
0350:
0351: /**
0352: * Before validating the value, the value will be 'cast', on default this will be to the
0353: * 'correct' type, but it can be a more generic type sometimes. E.g. for numbers this wil simply
0354: * cast to Number.
0355: */
0356: protected Object castToValidate(Object value, Node node, Field field)
0357: throws CastException {
0358: return cast(value, getCloud(node, field), node, field);
0359: }
0360:
0361: /**
0362: * {@inheritDoc}
0363: */
0364: public C getDefaultValue() {
0365: if (defaultValue == null)
0366: return null;
0367: return cast(defaultValue, null, null);
0368: }
0369:
0370: /**
0371: * {@inheritDoc}
0372: */
0373: public void setDefaultValue(C def) {
0374: edit();
0375: defaultValue = def;
0376: }
0377:
0378: protected Element getElement(Element parent, String name,
0379: String path) {
0380: return getElement(parent, name, name, path);
0381: }
0382:
0383: protected Element getElement(Element parent, String pattern,
0384: String name, String path) {
0385: java.util.regex.Pattern p = java.util.regex.Pattern
0386: .compile("\\A" + pattern + "\\z");
0387: org.w3c.dom.NodeList nl = parent.getChildNodes();
0388: Element el = null;
0389: for (int i = 0; i < nl.getLength(); i++) {
0390: org.w3c.dom.Node child = nl.item(i);
0391: if (child instanceof Element) {
0392: if (p.matcher(child.getLocalName()).matches()) {
0393: el = (Element) child;
0394: break;
0395: }
0396: }
0397: }
0398: if (el == null) {
0399: el = parent.getOwnerDocument().createElementNS(XMLNS, name);
0400: DocumentReader.appendChild(parent, el, path);
0401: } else if (!el.getLocalName().equals(name)) {
0402: Element newChild = parent.getOwnerDocument()
0403: .createElementNS(XMLNS, name);
0404: parent.replaceChild(newChild, el);
0405: el = newChild;
0406: }
0407: return el;
0408: }
0409:
0410: protected String getEnforceString(int enforce) {
0411: switch (enforce) {
0412: case DataType.ENFORCE_ABSOLUTE:
0413: return "absolute";
0414: case DataType.ENFORCE_ALWAYS:
0415: return "always";
0416: case DataType.ENFORCE_ONCHANGE:
0417: return "onchange";
0418: case DataType.ENFORCE_ONCREATE:
0419: return "oncreate";
0420: case DataType.ENFORCE_NEVER:
0421: return "never";
0422: default:
0423: return "???";
0424: }
0425: }
0426:
0427: protected Element addRestriction(Element parent, String name,
0428: String path, Restriction restriction) {
0429: return addRestriction(parent, name, name, path, restriction);
0430: }
0431:
0432: protected Element addRestriction(Element parent, String pattern,
0433: String name, String path, Restriction restriction) {
0434: Element el = addErrorDescription(getElement(parent, pattern,
0435: name, path), restriction);
0436: xmlValue(el, restriction.getValue());
0437: el.setAttribute("enforce", getEnforceString(restriction
0438: .getEnforceStrength()));
0439: return el;
0440: }
0441:
0442: protected Element addErrorDescription(Element el, Restriction r) {
0443: r.getErrorDescription().toXml("description", DataType.XMLNS,
0444: el, "");
0445: return el;
0446: }
0447:
0448: public boolean isFinished() {
0449: return owner != null;
0450: }
0451:
0452: /**
0453: * {@inheritDoc}
0454: */
0455: public void finish() {
0456: finish(new Object());
0457: }
0458:
0459: /**
0460: * {@inheritDoc}
0461: */
0462: public void finish(Object owner) {
0463: this .owner = owner;
0464: }
0465:
0466: /**
0467: * {@inheritDoc}
0468: */
0469: public DataType<C> rewrite(Object owner) {
0470: if (this .owner != null) {
0471: if (this .owner != owner) {
0472: throw new IllegalArgumentException(
0473: "Cannot rewrite this datatype - specified owner is not correct");
0474: }
0475: this .owner = null;
0476: }
0477: return this ;
0478: }
0479:
0480: /**
0481: * @javadoc
0482: */
0483: protected void edit() {
0484: if (isFinished()) {
0485: throw new IllegalStateException("This data type '"
0486: + getName()
0487: + "' is finished and can no longer be changed.");
0488: }
0489: }
0490:
0491: /**
0492: * {@inheritDoc}
0493: */
0494: public final Collection<LocalizedString> validate(C value) {
0495: return validate(value, null, null);
0496: }
0497:
0498: public final Collection<LocalizedString> validate(final C value,
0499: final Node node, final Field field) {
0500: return validate(value, node, field, true);
0501: }
0502:
0503: /**
0504: * {@inheritDoc}
0505: */
0506: private final Collection<LocalizedString> validate(
0507: final Object value, final Node node, final Field field,
0508: boolean testEnum) {
0509: Collection<LocalizedString> errors = VALID;
0510: Object castValue;
0511: try {
0512: castValue = castToValidate(value, node, field);
0513: errors = typeRestriction.validate(errors, castValue, node,
0514: field);
0515: } catch (CastException ce) {
0516: log.debug(ce);
0517: errors = typeRestriction.addError(errors, value, node,
0518: field);
0519: castValue = value;
0520: }
0521:
0522: if (errors.size() > 0) {
0523: // no need continuing, restrictions will probably not know how to handle this value any way.
0524: return errors;
0525: }
0526:
0527: errors = requiredRestriction.validate(errors, value, node,
0528: field);
0529:
0530: errors = validateCastValueOrNull(errors, castValue, value,
0531: node, field);
0532:
0533: if (castValue == null) {
0534: return errors; // null is valid, unless required.
0535: }
0536: if (testEnum) {
0537: errors = enumerationRestriction.validate(errors, value,
0538: node, field);
0539: }
0540: errors = uniqueRestriction.validate(errors, castValue, node,
0541: field);
0542: errors = validateCastValue(errors, castValue, value, node,
0543: field);
0544: return errors;
0545: }
0546:
0547: public int getEnforceStrength() {
0548: int enforceStrength = Math.max(typeRestriction
0549: .getEnforceStrength(), requiredRestriction
0550: .getEnforceStrength());
0551: enforceStrength = Math.max(enforceStrength,
0552: enumerationRestriction.getEnforceStrength());
0553: return Math.max(enforceStrength, uniqueRestriction
0554: .getEnforceStrength());
0555: }
0556:
0557: protected Collection<LocalizedString> validateCastValue(
0558: Collection<LocalizedString> errors, Object castValue,
0559: Object value, Node node, Field field) {
0560: return errors;
0561: }
0562:
0563: /**
0564: * @since MMBase-1.8.4
0565: */
0566: protected Collection<LocalizedString> validateCastValueOrNull(
0567: Collection<LocalizedString> errors, Object castValue,
0568: Object value, Node node, Field field) {
0569: return errors;
0570: }
0571:
0572: protected StringBuilder toStringBuilder() {
0573: StringBuilder buf = new StringBuilder();
0574: buf.append(getName() + " (" + getTypeAsClass()
0575: + (defaultValue != null ? ":" + defaultValue : "")
0576: + ")");
0577: buf
0578: .append(commitProcessor == null
0579: || EmptyCommitProcessor.getInstance() == commitProcessor ? ""
0580: : " commit: " + commitProcessor + "");
0581: if (getProcessors != null) {
0582: for (int i = 0; i < 13; i++) {
0583: buf.append(getProcessors[i] == null ? "" : ("; get ["
0584: + Fields.typeToClass(i) + "]:"
0585: + getProcessors[i] + " "));
0586: }
0587: }
0588: if (setProcessors != null) {
0589: for (int i = 0; i < 13; i++) {
0590: buf.append(setProcessors[i] == null ? "" : ("; set ["
0591: + Fields.typeToClass(i) + "]:"
0592: + setProcessors[i] + " "));
0593: }
0594: }
0595: if (isRequired()) {
0596: buf.append(" required");
0597: }
0598: if (isUnique()) {
0599: buf.append(" unique");
0600: }
0601: if (enumerationRestriction.getValue() != null
0602: && !enumerationRestriction.getEnumerationFactory()
0603: .isEmpty()) {
0604: buf.append(" " + enumerationRestriction);
0605: }
0606: return buf;
0607:
0608: }
0609:
0610: public final String toString() {
0611: StringBuilder buf = toStringBuilder();
0612: if (isFinished()) {
0613: buf.append(".");
0614: }
0615: return buf.toString();
0616: }
0617:
0618: /**
0619: * {@inheritDoc}
0620: *
0621: * This method is final, override {@link #clone(String)} in stead.
0622: */
0623: public final Object clone() {
0624: return clone(null);
0625: }
0626:
0627: /**
0628: * {@inheritDoc}
0629: *
0630: * Besides super.clone, it calls {@link #inheritProperties(BasicDataType)} and {@link
0631: * #cloneRestrictions(BasicDataType)}. A clone is not finished. See {@link #isFinished()}.
0632: */
0633: public DataType clone(String name) {
0634: try {
0635: BasicDataType<C> clone = (BasicDataType<C>) super
0636: .clone(name);
0637: // reset owner if it was set, so this datatype can be changed
0638: clone.owner = null;
0639: // properly inherit from this datatype (this also clones properties and processor arrays)
0640: clone.inheritProperties(this );
0641: clone.cloneRestrictions(this );
0642: if (log.isTraceEnabled()) {
0643: log.trace("Cloned " + this + " -> " + clone);
0644: }
0645: return clone;
0646: } catch (CloneNotSupportedException cnse) {
0647: // should not happen
0648: log.error("Cannot clone this DataType: " + name);
0649: throw new RuntimeException("Cannot clone this DataType: "
0650: + name, cnse);
0651: }
0652: }
0653:
0654: public Element toXml() {
0655: if (xml == null) {
0656: xml = DocumentReader.getDocumentBuilder().newDocument()
0657: .createElementNS(XMLNS, "datatype");
0658: xml.getOwnerDocument().appendChild(xml);
0659: }
0660: return xml;
0661: }
0662:
0663: public void setXml(Element element) {
0664: xml = DocumentReader.toDocument(element).getDocumentElement();
0665: if (origin != null) {
0666: xml.setAttribute("base", origin.getName());
0667: }
0668: // remove 'specialization' childs (they don't say anything about this datatype itself)
0669: org.w3c.dom.Node child = xml.getFirstChild();
0670: while (child != null) {
0671: org.w3c.dom.Node next = child.getNextSibling();
0672: switch (child.getNodeType()) {
0673: case org.w3c.dom.Node.ELEMENT_NODE:
0674: if (child.getLocalName().equals("specialization")
0675: || child.getLocalName().equals("datatype")) {
0676: // fall through and remove
0677: } else {
0678: break;
0679: }
0680: case org.w3c.dom.Node.TEXT_NODE:
0681: xml.removeChild(child);
0682: }
0683: child = next;
0684:
0685: }
0686: }
0687:
0688: protected void xmlValue(Element el, Object value) {
0689: el.setAttribute("value", Casting.toString(value));
0690: }
0691:
0692: /**
0693:
0694: */
0695: public void toXml(Element parent) {
0696: parent.setAttribute("id", getName());
0697:
0698: description.toXml("description", XMLNS, parent, "description");
0699:
0700: {
0701: Element classElement = getElement(parent, "class",
0702: "description,class");
0703: classElement.setAttribute("name", getClass().getName());
0704:
0705: StringBuilder extend = new StringBuilder();
0706: Class<?> sup = getClass().getSuperclass();
0707: while (DataType.class.isAssignableFrom(sup)) {
0708: if (extend.length() > 0)
0709: extend.append(',');
0710: extend.append(sup.getName());
0711: sup = sup.getSuperclass();
0712: }
0713: for (Class<?> c : getClass().getInterfaces()) {
0714: if (DataType.class.isAssignableFrom(c)) {
0715: if (extend.length() > 0)
0716: extend.append(',');
0717: extend.append(c.getName());
0718: }
0719: }
0720: classElement.setAttribute("extends", extend.toString());
0721: }
0722:
0723: xmlValue(getElement(parent, "default",
0724: "description,class,property,default"), defaultValue);
0725:
0726: addRestriction(parent, "unique",
0727: "description,class,property,default,unique",
0728: uniqueRestriction);
0729: addRestriction(parent, "required",
0730: "description,class,property,default,unique,required",
0731: requiredRestriction);
0732: getElement(parent, "enumeration",
0733: "description,class,property,default,unique,required,enumeration");
0734: /// set this here...
0735:
0736: /**
0737: End up in the wrong place, and not needed for javascript, so commented out for the moment.
0738:
0739: if (getCommitProcessor() != EmptyCommitProcessor.getInstance()) {
0740: org.w3c.dom.NodeList nl = parent.getElementsByTagName("commitprocessor");
0741: Element element;
0742: if (nl.getLength() == 0) {
0743: element = parent.getOwnerDocument().createElementNS(XMLNS, "commitprocessor");
0744: Element clazz = parent.getOwnerDocument().createElementNS(XMLNS, "class");
0745: clazz.setAttribute("name", getCommitProcessor().getClass().getName());
0746: DocumentReader.appendChild(parent, element, "description,class,property");
0747: element.appendChild(clazz);
0748: } else {
0749: element = (Element) nl.item(0);
0750: }
0751:
0752: //element.setAttribute("value", Casting.toString(defaultValue));
0753: }
0754: */
0755:
0756: }
0757:
0758: public int compareTo(DataType<C> a) {
0759: int compared = getName().compareTo(a.getName());
0760: if (compared == 0)
0761: compared = getTypeAsClass().getName().compareTo(
0762: a.getTypeAsClass().getName());
0763: return compared;
0764: }
0765:
0766: /**
0767: * Whether data type equals to other data type. Only key and type are consided. DefaultValue and
0768: * required properties are only 'utilities'.
0769: * @return true if o is a DataType of which key and type equal to this' key and type.
0770: */
0771: public boolean equals(Object o) {
0772: if (o instanceof DataType) {
0773: DataType<?> a = (DataType<?>) o;
0774: return getName().equals(a.getName())
0775: && getTypeAsClass().equals(a.getTypeAsClass());
0776: }
0777: return false;
0778: }
0779:
0780: public int hashCode() {
0781: return getName().hashCode() * 13 + getTypeAsClass().hashCode();
0782: }
0783:
0784: /**
0785: * {@inheritDoc}
0786: */
0787: public boolean isRequired() {
0788: return requiredRestriction.isRequired();
0789: }
0790:
0791: /**
0792: * {@inheritDoc}
0793: */
0794: public DataType.Restriction<Boolean> getRequiredRestriction() {
0795: return requiredRestriction;
0796: }
0797:
0798: /**
0799: * {@inheritDoc}
0800: */
0801: public void setRequired(boolean required) {
0802: getRequiredRestriction().setValue(Boolean.valueOf(required));
0803: }
0804:
0805: /**
0806: * {@inheritDoc}
0807: */
0808: public boolean isUnique() {
0809: return uniqueRestriction.isUnique();
0810: }
0811:
0812: /**
0813: * {@inheritDoc}
0814: */
0815: public DataType.Restriction<Boolean> getUniqueRestriction() {
0816: return uniqueRestriction;
0817: }
0818:
0819: /**
0820: * {@inheritDoc}
0821: */
0822: public void setUnique(boolean unique) {
0823: getUniqueRestriction().setValue(Boolean.valueOf(unique));
0824: }
0825:
0826: /**
0827: * {@inheritDoc}
0828: */
0829: public String getEnumerationValue(Locale locale, Cloud cloud,
0830: Node node, Field field, Object key) {
0831: String value = null;
0832: if (key != null) {
0833: // cast to the appropriate datatype value.
0834: // Note that for now it is assumed that the keys are of the same type.
0835: // I'm not 100% sure that this is always the case.
0836: C keyValue = cast(key, node, field);
0837: if (keyValue != null) {
0838: for (Iterator<Map.Entry<C, String>> i = new RestrictedEnumerationIterator(
0839: locale, cloud, node, field); value == null
0840: && i.hasNext();) {
0841: Map.Entry<C, String> entry = i.next();
0842: if (keyValue.equals(entry.getKey())) {
0843: value = entry.getValue();
0844: }
0845: }
0846: }
0847: }
0848: return value;
0849: }
0850:
0851: /**
0852: * {@inheritDoc}
0853: */
0854: public Iterator<Map.Entry<C, String>> getEnumerationValues(
0855: Locale locale, Cloud cloud, Node node, Field field) {
0856: Iterator<Map.Entry<C, String>> i = new RestrictedEnumerationIterator(
0857: locale, cloud, node, field);
0858: return i.hasNext() ? i : null;
0859: }
0860:
0861: /**
0862: * {@inheritDoc}
0863: */
0864: public LocalizedEntryListFactory<C> getEnumerationFactory() {
0865: return enumerationRestriction.getEnumerationFactory();
0866: }
0867:
0868: /**
0869: * {@inheritDoc}
0870: */
0871: public DataType.Restriction<LocalizedEntryListFactory<C>> getEnumerationRestriction() {
0872: return enumerationRestriction;
0873: }
0874:
0875: public CommitProcessor getCommitProcessor() {
0876: return commitProcessor == null ? EmptyCommitProcessor
0877: .getInstance() : commitProcessor;
0878: }
0879:
0880: public void setCommitProcessor(CommitProcessor cp) {
0881: commitProcessor = cp;
0882: }
0883:
0884: /**
0885: * {@inheritDoc}
0886: */
0887: public Processor getProcessor(int action) {
0888: Processor processor;
0889: if (action == PROCESS_GET) {
0890: processor = getProcessors == null ? null : getProcessors[0];
0891: } else {
0892: processor = setProcessors == null ? null : setProcessors[0];
0893: }
0894: return processor == null ? CopyProcessor.getInstance()
0895: : processor;
0896: }
0897:
0898: /**
0899: * {@inheritDoc}
0900: */
0901: public Processor getProcessor(int action, int processingType) {
0902: if (processingType == Field.TYPE_UNKNOWN) {
0903: return getProcessor(action);
0904: } else {
0905: Processor processor;
0906: if (action == PROCESS_GET) {
0907: processor = getProcessors == null ? null
0908: : getProcessors[processingType];
0909: } else {
0910: processor = setProcessors == null ? null
0911: : setProcessors[processingType];
0912: }
0913: return processor == null ? getProcessor(action) : processor;
0914: }
0915: }
0916:
0917: /**
0918: * {@inheritDoc}
0919: */
0920: public void setProcessor(int action, Processor processor) {
0921: setProcessor(action, processor, Field.TYPE_UNKNOWN);
0922: }
0923:
0924: private Processor[] newProcessorsArray() {
0925: return new Processor[] { null /* object */,
0926: null /* string */, null /* integer */,
0927: null /* not used */, null /* byte */,
0928: null /* float */, null /* double */,
0929: null /* long */, null /* xml */,
0930: null /* node */, null /* datetime */,
0931: null /* boolean */, null /* list */
0932: };
0933: }
0934:
0935: /**
0936: * {@inheritDoc}
0937: */
0938: public void setProcessor(int action, Processor processor,
0939: int processingType) {
0940: if (processingType == Field.TYPE_UNKNOWN) {
0941: processingType = 0;
0942: }
0943: if (action == PROCESS_GET) {
0944: if (getProcessors == null)
0945: getProcessors = newProcessorsArray();
0946: getProcessors[processingType] = processor;
0947: } else {
0948: if (setProcessors == null)
0949: setProcessors = newProcessorsArray();
0950: setProcessors[processingType] = processor;
0951: }
0952: }
0953:
0954: // ================================================================================
0955: // Follow implementations of the basic restrictions.
0956:
0957: // ABSTRACT
0958:
0959: /**
0960: * Abstract inner class Restriction. Based on static StaticAbstractRestriction
0961: */
0962: protected abstract class AbstractRestriction<D extends Serializable>
0963: extends StaticAbstractRestriction<D> {
0964: protected AbstractRestriction(AbstractRestriction source) {
0965: super (BasicDataType.this , source);
0966: }
0967:
0968: protected AbstractRestriction(String name, D value) {
0969: super (BasicDataType.this , name, value);
0970: }
0971: }
0972:
0973: /**
0974: * A Restriction is represented by these kind of objects.
0975: * When you override this class, take care of cloning of outer class!
0976: * This class itself is not cloneable. Cloning is hard when you have inner classes.
0977: *
0978: * All restrictions extend from this.
0979: *
0980: * See <a href="http://www.adtmag.com/java/articleold.asp?id=364">article about inner classes,
0981: * cloning in java</a>
0982: */
0983: protected static abstract class StaticAbstractRestriction<D extends Serializable>
0984: implements DataType.Restriction<D> {
0985: protected final String name;
0986: protected final BasicDataType parent;
0987: protected LocalizedString errorDescription;
0988: protected D value;
0989: protected boolean fixed = false;
0990: protected int enforceStrength = DataType.ENFORCE_ALWAYS;
0991:
0992: /**
0993: * If a restriction has an 'absolute' parent restriction, then also that restriction must be
0994: * valid (because it was 'absolute'). A restriction gets an absolute parent if its
0995: * surrounding DataType is clone of DataType in which the same restriction is marked with
0996: * {@link DataType#ENFORCE_ABSOLUTE}.
0997: */
0998: protected StaticAbstractRestriction absoluteParent = null;
0999:
1000: /**
1001: * Instantaties new restriction for a clone of the parent DataType. If the source
1002: * restriction is 'absolute' it will remain to be enforced even if the clone gets a new
1003: * value.
1004: */
1005: protected StaticAbstractRestriction(BasicDataType parent,
1006: StaticAbstractRestriction source) {
1007: this .name = source.getName();
1008: this .parent = parent;
1009: if (source.enforceStrength == DataType.ENFORCE_ABSOLUTE) {
1010: absoluteParent = source;
1011: } else {
1012: absoluteParent = source.absoluteParent;
1013: }
1014: inherit(source);
1015: if (source.enforceStrength == DataType.ENFORCE_ABSOLUTE) {
1016: enforceStrength = DataType.ENFORCE_ALWAYS;
1017: }
1018: }
1019:
1020: protected StaticAbstractRestriction(BasicDataType parent,
1021: String name, D value) {
1022: this .name = name;
1023: this .parent = parent;
1024: this .value = value;
1025: }
1026:
1027: public String getName() {
1028: return name;
1029: }
1030:
1031: public D getValue() {
1032: return value;
1033: }
1034:
1035: public void setValue(D v) {
1036: parent.edit();
1037: if (fixed) {
1038: throw new IllegalStateException("Restriction '" + name
1039: + "' is fixed, cannot be changed");
1040: }
1041:
1042: this .value = v;
1043: }
1044:
1045: public LocalizedString getErrorDescription() {
1046: if (errorDescription == null) {
1047: // this is postponsed to first use, because otherwise 'getBaseTypeIdentifier' give correct value only after constructor of parent.
1048: String key = parent.getBaseTypeIdentifier() + "."
1049: + name + ".error";
1050: errorDescription = new LocalizedString(key);
1051: errorDescription.setBundle(DATATYPE_BUNDLE);
1052: }
1053: return errorDescription;
1054: }
1055:
1056: public void setErrorDescription(LocalizedString errorDescription) {
1057: this .errorDescription = errorDescription;
1058: }
1059:
1060: public boolean isFixed() {
1061: return fixed;
1062: }
1063:
1064: public void setFixed(boolean fixed) {
1065: if (this .fixed && !fixed) {
1066: throw new IllegalStateException("Restriction '" + name
1067: + "' is fixed, cannot be changed");
1068: }
1069: this .fixed = fixed;
1070: }
1071:
1072: /**
1073: * Utility method to add a new error message to the errors collection, based on this
1074: * Restriction. If this error-collection is unmodifiable (VALID), it is replaced with a new
1075: * empty one first.
1076: */
1077: protected final Collection<LocalizedString> addError(
1078: Collection<LocalizedString> errors, Object v,
1079: Node node, Field field) {
1080: if (errors == VALID)
1081: errors = new ArrayList<LocalizedString>();
1082: ReplacingLocalizedString error = new ReplacingLocalizedString(
1083: getErrorDescription());
1084: error.replaceAll("\\$\\{NAME\\}", ReplacingLocalizedString
1085: .makeLiteral(getName()));
1086: error.replaceAll("\\$\\{CONSTRAINT\\}",
1087: ReplacingLocalizedString.makeLiteral(toString(node,
1088: field)));
1089: error.replaceAll("\\$\\{CONSTRAINTVALUE\\}",
1090: ReplacingLocalizedString.makeLiteral(valueString(
1091: node, field)));
1092: error.replaceAll("\\$\\{VALUE\\}", ReplacingLocalizedString
1093: .makeLiteral(Casting.toString(v)));
1094: errors.add(error);
1095: return errors;
1096: }
1097:
1098: /**
1099: * If value of a a restriction depends on node, field, then you can override this
1100: */
1101: protected String valueString(Node node, Field field) {
1102: return Casting.toString(value);
1103: }
1104:
1105: /**
1106: * Whether {@link #validate} must enforce this condition
1107: */
1108: protected final boolean enforce(Object v, Node node, Field field) {
1109: switch (enforceStrength) {
1110: case DataType.ENFORCE_ABSOLUTE:
1111: case DataType.ENFORCE_ALWAYS:
1112: return true;
1113: case DataType.ENFORCE_ONCHANGE:
1114: if (node == null || field == null
1115: || node.isChanged(field.getName()))
1116: return true;
1117: case DataType.ENFORCE_ONCREATE:
1118: if (node == null || node.isNew())
1119: return true;
1120: case DataType.ENFORCE_NEVER:
1121: return false;
1122: default:
1123: return true;
1124: }
1125: }
1126:
1127: /**
1128: * This method is called by {@link BasicDataType#validate(Object, Node, Field)} for each of its conditions.
1129: */
1130: protected Collection<LocalizedString> validate(
1131: Collection<LocalizedString> errors, Object v,
1132: Node node, Field field) {
1133: if (absoluteParent != null
1134: && !absoluteParent.valid(v, node, field)) {
1135: int sizeBefore = errors.size();
1136: Collection<LocalizedString> res = absoluteParent
1137: .addError(errors, v, node, field);
1138: if (res.size() > sizeBefore) {
1139: return res;
1140: }
1141: }
1142: if ((!enforce(v, node, field)) || valid(v, node, field)) {
1143: // no new error to add.
1144: return errors;
1145: } else {
1146: return addError(errors, v, node, field);
1147: }
1148: }
1149:
1150: public final boolean valid(Object v, Node node, Field field) {
1151: try {
1152: if (absoluteParent != null) {
1153: if (!absoluteParent.valid(v, node, field))
1154: return false;
1155: }
1156: return simpleValid(parent
1157: .castToValidate(v, node, field), node, field);
1158: } catch (Throwable t) {
1159: if (log.isServiceEnabled()) {
1160: log.service(
1161: "Not valid because cast-to-validate threw exception "
1162: + t.getClass(), t);
1163: }
1164: return false;
1165: }
1166: }
1167:
1168: protected abstract boolean simpleValid(Object v, Node node,
1169: Field field);
1170:
1171: protected final void inherit(
1172: StaticAbstractRestriction<D> source, boolean cast) {
1173: // perhaps this value must be cloned?, but how?? Cloneable has no public methods....
1174: D inheritedValue = source.getValue();
1175: if (cast)
1176: inheritedValue = (D) parent.cast(inheritedValue, null,
1177: null);
1178: setValue(inheritedValue);
1179: enforceStrength = source.getEnforceStrength();
1180: errorDescription = (LocalizedString) source
1181: .getErrorDescription().clone();
1182: }
1183:
1184: protected final void inherit(StaticAbstractRestriction source) {
1185: inherit(source, false);
1186: }
1187:
1188: public int getEnforceStrength() {
1189: return enforceStrength;
1190: }
1191:
1192: public void setEnforceStrength(int e) {
1193: enforceStrength = e;
1194: }
1195:
1196: public final String toString() {
1197: return toString(null, null);
1198: }
1199:
1200: public final String toString(Node node, Field field) {
1201: return name
1202: + ": "
1203: + (enforceStrength == DataType.ENFORCE_NEVER ? "*"
1204: : "") + valueString(node, field)
1205: + (fixed ? "." : "");
1206: }
1207:
1208: }
1209:
1210: // REQUIRED
1211: protected class RequiredRestriction extends
1212: AbstractRestriction<Boolean> {
1213: private static final long serialVersionUID = 1L;
1214:
1215: RequiredRestriction(RequiredRestriction source) {
1216: super (source);
1217: }
1218:
1219: RequiredRestriction(boolean b) {
1220: super ("required", Boolean.valueOf(b));
1221: }
1222:
1223: final boolean isRequired() {
1224: return Boolean.TRUE.equals(value);
1225: }
1226:
1227: protected boolean simpleValid(Object v, Node node, Field field) {
1228: if (!isRequired())
1229: return true;
1230: return v != null;
1231: }
1232: }
1233:
1234: // UNIQUE
1235: protected class UniqueRestriction extends
1236: AbstractRestriction<Boolean> {
1237: private static final long serialVersionUID = 1L;
1238:
1239: UniqueRestriction(UniqueRestriction source) {
1240: super (source);
1241: }
1242:
1243: UniqueRestriction(boolean b) {
1244: super ("unique", Boolean.valueOf(b));
1245: }
1246:
1247: final boolean isUnique() {
1248: return Boolean.TRUE.equals(value);
1249: }
1250:
1251: protected boolean simpleValid(Object v, Node node, Field field) {
1252: if (!isUnique())
1253: return true;
1254: if (field != null && v != null && value != null) {
1255:
1256: if (field.isVirtual()) {
1257: log.warn("Cannot check uniqueness on field "
1258: + field + " because it is virtual");
1259: return true; // e.g. if the field was defined in XML but not present in DB (after upgrade?)
1260: }
1261:
1262: if (node != null && !node.isNew()) {
1263: if (field.getName().equals("number")) {
1264: // on 'number' there is a unique constraint, if it is checked for a non-new node
1265: // we can simply avoid all quering because it will result in a query number == <number> and number <> <number>
1266: if (Casting.toInt(v) == node.getNumber()) {
1267: return true;
1268: } else {
1269: // changing
1270: log.warn("Odd, changing number of node "
1271: + node + " ?!", new Exception());
1272: }
1273: }
1274: }
1275:
1276: NodeManager nodeManager = field.getNodeManager();
1277: Cloud cloud = nodeManager.getCloud();
1278: if (cloud.getUser().getRank().getInt() < Rank.ADMIN_INT) {
1279: // This will test for uniqueness using bridge, so you'll miss objects you can't
1280: // see (and database doesn't know that!)
1281: // So try using an administrator for that! That would probably work ok.
1282: Cloud adminCloud = cloud.getCloudContext()
1283: .getCloud("mmbase", "class", null);
1284: if (adminCloud.getUser().getRank().getInt() > cloud
1285: .getUser().getRank().getInt()) {
1286: cloud = adminCloud;
1287: nodeManager = adminCloud
1288: .getNodeManager(nodeManager.getName());
1289: }
1290: }
1291: // create a query and query for the value
1292: NodeQuery query = nodeManager.createQuery();
1293: Constraint constraint = Queries.createConstraint(query,
1294: field.getName(), FieldCompareConstraint.EQUAL,
1295: v);
1296: Queries.addConstraint(query, constraint);
1297: if (node != null && !node.isNew()) {
1298: constraint = Queries.createConstraint(query,
1299: "number", FieldCompareConstraint.NOT_EQUAL,
1300: node.getNumber());
1301: Queries.addConstraint(query, constraint);
1302: }
1303: if (log.isDebugEnabled()) {
1304: log.debug(query);
1305: }
1306: return Queries.count(query) == 0;
1307: } else {
1308: if (field == null)
1309: log.warn("Cannot check uniqueness without field");
1310: return true;
1311: }
1312: }
1313: }
1314:
1315: // TYPE
1316:
1317: protected class TypeRestriction extends
1318: AbstractRestriction<Class<?>> {
1319: private static final long serialVersionUID = 1L;
1320:
1321: TypeRestriction(TypeRestriction source) {
1322: super (source);
1323: }
1324:
1325: TypeRestriction() {
1326: super ("type", BasicDataType.this .getClass());
1327: }
1328:
1329: protected boolean simpleValid(Object v, Node node, Field field) {
1330: try {
1331: BasicDataType.this .cast(v, node, field);
1332: return true;
1333: } catch (Throwable e) {
1334: log.error(e);
1335: return false;
1336: }
1337: }
1338: }
1339:
1340: // ENUMERATION
1341: protected class EnumerationRestriction extends
1342: AbstractRestriction<LocalizedEntryListFactory<C>> {
1343: private static final long serialVersionUID = 1L;
1344:
1345: EnumerationRestriction(EnumerationRestriction source) {
1346: super (source);
1347: value = value != null ? (LocalizedEntryListFactory<C>) value
1348: .clone()
1349: : null;
1350: }
1351:
1352: EnumerationRestriction(LocalizedEntryListFactory<C> entries) {
1353: super ("enumeration", entries);
1354: }
1355:
1356: final LocalizedEntryListFactory<C> getEnumerationFactory() {
1357: if (value == null) {
1358: value = new LocalizedEntryListFactory<C>();
1359: }
1360: return value;
1361: }
1362:
1363: public Collection<Map.Entry<C, String>> getEnumeration(
1364: Locale locale, Cloud cloud, Node node, Field field) {
1365: if (value == null)
1366: return Collections.emptyList();
1367: if (cloud == null) {
1368: if (node != null) {
1369: cloud = node.getCloud();
1370: } else if (field != null) {
1371: cloud = field.getNodeManager().getCloud();
1372: }
1373: }
1374: return value.get(locale, cloud);
1375: }
1376:
1377: /**
1378: * @see BasicDataType#preCast
1379: */
1380: protected <D> D preCast(D v, Cloud cloud) {
1381: if (getValue() == null)
1382: return v;
1383: try {
1384: return (D) value.castKey(v, cloud);
1385: //return v != null ? Casting.toType(v.getClass(), cloud, res) : res;
1386: } catch (NoClassDefFoundError ncdfe) {
1387: log.error("Could not find class " + ncdfe.getMessage()
1388: + " while casting " + v.getClass() + " " + v,
1389: ncdfe);
1390: return v;
1391: }
1392:
1393: }
1394:
1395: protected boolean simpleValid(Object v, Node node, Field field) {
1396: if (value == null || value.isEmpty()) {
1397: return true;
1398: }
1399: Cloud cloud = BasicDataType.this .getCloud(node, field);
1400: Collection<Map.Entry<C, String>> validValues = getEnumeration(
1401: null, cloud, node, field);
1402: if (validValues.size() == 0) {
1403: return true;
1404: }
1405: Object candidate;
1406: try {
1407: candidate = BasicDataType.this .cast(v, cloud, node,
1408: field);
1409: } catch (CastException ce) {
1410: log.info(ce);
1411: return false;
1412: }
1413: for (Map.Entry<C, String> e : validValues) {
1414: Object valid = e.getKey();
1415: if (valid.equals(candidate)) {
1416: return true;
1417: }
1418: }
1419: return false;
1420: }
1421:
1422: protected String valueString(Node node, Field field) {
1423: Collection<Map.Entry<C, String>> col = getEnumeration(null,
1424: null, node, field);
1425: if (col.size() == 0)
1426: return "";
1427: StringBuffer buf = new StringBuffer();
1428: Iterator<Map.Entry<C, String>> it = col.iterator();
1429: int i = 0;
1430: while (it.hasNext() && ++i < 10) {
1431: Map.Entry<C, String> ent = it.next();
1432: buf.append(Casting.toString(ent));
1433: if (it.hasNext())
1434: buf.append(", ");
1435: }
1436: if (i < col.size())
1437: buf.append(".(" + (col.size() - i) + " more ..");
1438: return buf.toString();
1439: }
1440:
1441: }
1442:
1443: /**
1444: * Iterates over the collection provided by the EnumerationRestriction, but skips the values
1445: * which are invalid because of the other restrictions on this DataType.
1446: */
1447: //Also, it 'preCasts' the * keys to the right type.
1448: protected class RestrictedEnumerationIterator implements
1449: Iterator<Map.Entry<C, String>> {
1450: private final Iterator<Map.Entry<C, String>> baseIterator;
1451: private final Node node;
1452: private final Field field;
1453: private Map.Entry<C, String> next = null;
1454:
1455: RestrictedEnumerationIterator(Locale locale, Cloud cloud,
1456: Node node, Field field) {
1457: Collection<Map.Entry<C, String>> col = enumerationRestriction
1458: .getEnumeration(locale, cloud, node, field);
1459: if (log.isDebugEnabled()) {
1460: log.debug("Restricted iterator on " + col);
1461: }
1462: baseIterator = col.iterator();
1463: this .node = node;
1464: this .field = field;
1465: determineNext();
1466: }
1467:
1468: protected void determineNext() {
1469: next = null;
1470: while (baseIterator.hasNext()) {
1471: final Map.Entry<C, String> entry = baseIterator.next();
1472: C value = entry.getKey();
1473: Collection<LocalizedString> validationResult = BasicDataType.this
1474: .validate(value, node, field, false);
1475: if (validationResult == VALID) {
1476: next = entry;
1477: /*
1478: new Map.Entry() {
1479: public Object getKey() {
1480: return BasicDataType.this.preCast(entry.getKey(), node, field);
1481: }
1482: public Object getValue() {
1483: return entry.getValue();
1484: }
1485: public Object setValue(Object v) {
1486: return entry.setValue(v);
1487: }
1488: };
1489: */
1490: break;
1491: } else if (log.isDebugEnabled()) {
1492: String errors = "";
1493: for (LocalizedString localizedString : validationResult) {
1494: errors += localizedString.get(null);
1495: }
1496: log.debug("Value " + value.getClass() + " " + value
1497: + " does not validate : " + errors);
1498: }
1499: }
1500: }
1501:
1502: public boolean hasNext() {
1503: return next != null;
1504: }
1505:
1506: public Map.Entry<C, String> next() {
1507: if (next == null) {
1508: throw new NoSuchElementException();
1509: }
1510: Map.Entry<C, String> n = next;
1511: determineNext();
1512: return n;
1513: }
1514:
1515: public void remove() {
1516: throw new UnsupportedOperationException(
1517: "Cannot remove entries from " + getClass());
1518: }
1519:
1520: public String toString() {
1521: return "restricted iterator(" + enumerationRestriction
1522: + ")";
1523: }
1524: }
1525:
1526: }
|