KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > datatypes > BasicDataType


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10
11 package org.mmbase.datatypes;
12
13 import java.io.*;
14 import java.util.*;
15
16 import org.mmbase.bridge.*;
17 import org.mmbase.bridge.util.Queries;
18 import org.mmbase.core.AbstractDescriptor;
19 import org.mmbase.core.util.Fields;
20 import org.mmbase.datatypes.processors.*;
21 import org.mmbase.security.Rank;
22 import org.mmbase.storage.search.Constraint;
23 import org.mmbase.storage.search.FieldCompareConstraint;
24 import org.mmbase.util.*;
25 import org.mmbase.util.logging.Logger;
26 import org.mmbase.util.logging.Logging;
27 import org.mmbase.util.xml.DocumentReader;
28 import org.w3c.dom.Element JavaDoc;
29
30
31 /**
32  * Every DataType extends this one. It's extensions can however implement several extensions of the
33  * DataType interface (e.g. some datatypes (at least {@link StringDataType}) are both {@link LengthDataType}
34  * and {@link ComparableDataType}, and some are only one ({@link BinaryDataType}, {@link
35  * NumberDataType}). In other words, this arrangement is like this, because java does not support
36  * Multipible inheritance.
37  *
38  * @author Pierre van Rooden
39  * @author Michiel Meeuwissen
40  * @since MMBase-1.8
41  * @version $Id: BasicDataType.java,v 1.61 2006/07/18 12:58:40 michiel Exp $
42  */

43
44 public class BasicDataType extends AbstractDescriptor implements DataType, Cloneable JavaDoc, Comparable JavaDoc, Descriptor {
45     /**
46      * The bundle used by datatype to determine default prompts for error messages when a
47      * validation fails.
48      */

49     public static final String JavaDoc DATATYPE_BUNDLE = "org.mmbase.datatypes.resources.datatypes";
50     private static final Logger log = Logging.getLoggerInstance(BasicDataType.class);
51
52     protected RequiredRestriction requiredRestriction = new RequiredRestriction(false);
53     protected UniqueRestriction uniqueRestriction = new UniqueRestriction(false);
54     protected TypeRestriction typeRestriction = new TypeRestriction();
55     protected EnumerationRestriction enumerationRestriction = new EnumerationRestriction((LocalizedEntryListFactory) null);
56
57     /**
58      * The datatype from which this datatype originally inherited it's properties.
59      */

60     protected DataType origin = null;
61
62     private Object JavaDoc owner;
63     private Class JavaDoc classType;
64     private Object JavaDoc defaultValue;
65
66     private CommitProcessor commitProcessor = EmptyCommitProcessor.getInstance();
67     private Processor[] getProcessors;
68     private Processor[] setProcessors;
69
70     private Element JavaDoc xml = null;
71
72     /**
73      * Create a data type object of unspecified class type
74      * @param name the name of the data type
75 s */

76     public BasicDataType(String JavaDoc name) {
77         this(name, Object JavaDoc.class);
78     }
79
80     /**
81      * Create a data type object
82      * @param name the name of the data type
83      * @param classType the class of the data type's possible value
84      */

85     protected BasicDataType(String JavaDoc name, Class JavaDoc classType) {
86         super(name);
87         this.classType = classType;
88         owner = null;
89     }
90
91     private static final long serialVersionUID = 1L; // increase this if object serialization changes (which we shouldn't do!)
92

93     // implementation of serializable
94
private void writeObject(ObjectOutputStream out) throws IOException {
95         out.writeUTF(key);
96         out.writeObject(description);
97         out.writeObject(guiName);
98         out.writeObject(requiredRestriction);
99         out.writeObject(uniqueRestriction);
100         out.writeObject(enumerationRestriction.getEnumerationFactory());
101         if (owner instanceof Serializable) {
102             out.writeObject(owner);
103         } else {
104             out.writeObject(owner == null ? null : "OWNER");
105         }
106         out.writeObject(classType);
107         if (defaultValue instanceof Serializable || defaultValue == null) {
108             out.writeObject(defaultValue);
109         } else {
110             log.warn("Default value " + defaultValue.getClass() + " '" + defaultValue + "' is not serializable, taking it null, which may not be correct.");
111             out.writeObject(null);
112         }
113         out.writeObject(commitProcessor);
114         out.writeObject(getProcessors);
115         out.writeObject(setProcessors);
116     }
117     // implementation of serializable
118
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException JavaDoc {
119         key = in.readUTF();
120         description = (LocalizedString) in.readObject();
121         guiName = (LocalizedString) in.readObject();
122         requiredRestriction = (RequiredRestriction) in.readObject();
123         uniqueRestriction = (UniqueRestriction) in.readObject();
124         enumerationRestriction = new EnumerationRestriction((LocalizedEntryListFactory) in.readObject());
125         typeRestriction = new TypeRestriction(); // its always the same, so no need actually persisting it.
126
owner = in.readObject();
127         try {
128             classType = (Class JavaDoc) in.readObject();
129         } catch (Throwable JavaDoc t) {
130             // if some unknown class, simply fall back
131
classType = Object JavaDoc.class;
132         }
133         defaultValue = in.readObject();
134         commitProcessor = (CommitProcessor) in.readObject();
135         getProcessors = (Processor[]) in.readObject();
136         setProcessors = (Processor[]) in.readObject();
137     }
138
139     public String JavaDoc getBaseTypeIdentifier() {
140         return Fields.getTypeDescription(getBaseType()).toLowerCase();
141     }
142
143     public int getBaseType() {
144         return Fields.classToType(classType);
145     }
146
147     /**
148      * {@inheritDoc}
149      * Calls both {@link #inheritProperties} and {@link #inheritRestrictions}.
150      */

151     public final void inherit(BasicDataType origin) {
152         edit();
153         inheritProperties(origin);
154         inheritRestrictions(origin);
155     }
156
157     /**
158      * Properties are members of the datatype that can easily be copied/clones.
159      */

160     protected void inheritProperties(BasicDataType origin) {
161         this.origin = origin;
162
163         defaultValue = origin.getDefaultValue();
164
165         commitProcessor = (CommitProcessor) ( origin.commitProcessor instanceof PublicCloneable ? ((PublicCloneable) origin.commitProcessor).clone() : origin.commitProcessor);
166         if (origin.getProcessors == null) {
167             getProcessors = null;
168         } else {
169             getProcessors = (Processor[]) origin.getProcessors.clone();
170         }
171         if (origin.setProcessors == null) {
172             setProcessors = null;
173         } else {
174             setProcessors = (Processor[]) origin.setProcessors.clone();
175         }
176     }
177
178     /**
179      * If a datatype is cloned, the restrictions of it (normally implemented as inner classes), must be reinstantiated.
180      */

181     protected void cloneRestrictions(BasicDataType origin) {
182         enumerationRestriction = new EnumerationRestriction(origin.enumerationRestriction);
183         requiredRestriction = new RequiredRestriction(origin.requiredRestriction);
184         uniqueRestriction = new UniqueRestriction(origin.uniqueRestriction);
185     }
186
187     /**
188      * If a datatype inherits from another datatype all its restrictions inherit too.
189      */

190     protected void inheritRestrictions(BasicDataType origin) {
191         if (! origin.getEnumerationFactory().isEmpty()) {
192             enumerationRestriction.inherit(origin.enumerationRestriction);
193             if (enumerationRestriction.value != null) {
194                 LocalizedEntryListFactory fact = enumerationRestriction.getEnumerationFactory();
195                 if (! origin.getTypeAsClass().equals(getTypeAsClass())) {
196                     // Reevaluate XML configuration, because it was done with a 'wrong' suggestion for the wrapper class.
197
Element JavaDoc elm = fact.toXml();
198                     if (elm == null) {
199                         log.warn("Did not get XML from Factory " + fact);
200                     } else {
201                         // need to clone the actual factory,
202
// since it will otherwise change the original restrictions.
203
fact = new LocalizedEntryListFactory();
204                         fact.fillFromXml(elm, getTypeAsClass());
205                         enumerationRestriction.setValue(fact);
206                     }
207                 }
208             }
209         }
210
211         requiredRestriction.inherit(origin.requiredRestriction);
212         uniqueRestriction.inherit(origin.uniqueRestriction);
213     }
214
215     /**
216      * {@inheritDoc}
217      */

218     public DataType getOrigin() {
219         return origin;
220     }
221
222     /**
223      * {@inheritDoc}
224      */

225     public Class JavaDoc getTypeAsClass() {
226         return classType;
227     }
228
229    /**
230      * Checks if the passed object is of the correct class (compatible with the type of this DataType),
231      * and throws an IllegalArgumentException if it doesn't.
232      * @param value teh value whose type (class) to check
233      * @throws IllegalArgumentException if the type is not compatible
234      */

235     protected boolean isCorrectType(Object JavaDoc value) {
236         return Casting.isType(classType, value);
237     }
238
239     /**
240      * {@inheritDoc}
241      */

242     public void checkType(Object JavaDoc value) {
243         if (!isCorrectType(value)) {
244             // customize this?
245
throw new IllegalArgumentException JavaDoc("DataType of '" + value + "' for '" + getName() + "' must be of type " + classType + " (but is " + (value == null ? value : value.getClass()) + ")");
246         }
247     }
248
249
250     /**
251      * {@inheritDoc}
252      *
253      * Tries to determin cloud by node and field if possible and wraps {@link #preCast(Object, Cloud, Node, Field)}.
254      */

255     public final Object JavaDoc preCast(Object JavaDoc value, Node node, Field field) {
256         return preCast(value, getCloud(node, field), node, field);
257     }
258
259     /**
260      * This method is as yet unused, but can be anticipated
261      */

262     public final Object JavaDoc preCast(Object JavaDoc value, Cloud cloud) {
263         return preCast(value, cloud, null, null);
264     }
265
266     /**
267      * This method implements 'precasting', which can be seen as a kind of datatype specific
268      * casting. It should anticipate that every argument can be <code>null</code>. It should not
269      * change the actual type of the value.
270      */

271     protected Object JavaDoc preCast(Object JavaDoc value, Cloud cloud, Node node, Field field) {
272         if (value == null) return null;
273         Object JavaDoc preCast = enumerationRestriction.preCast(value, cloud);
274         return preCast;
275     }
276
277
278     /**
279      * {@inheritDoc}
280      *
281      * No need to override this. It is garantueed by javadoc that cast should work out of preCast
282      * using Casting.toType. So that is what this final implementation is doing.
283      *
284      * Override {@link #preCast(Object, Cloud, Node, Field)}
285      */

286     public final Object JavaDoc cast(Object JavaDoc value, final Node node, final Field field) {
287         if (origin != null && (! origin.getClass().isAssignableFrom(getClass()))) {
288             // if inherited from incompatible type, then first try to cast in the way of origin.
289
// e.g. if origin is Date, but actual type is integer, then casting of 'today' works now.
290
value = origin.cast(value, node, field);
291         }
292         Cloud cloud = getCloud(node, field);
293         try {
294             return cast(value, cloud, node, field);
295         } catch (CastException ce) {
296             log.error(ce.getMessage());
297             return Casting.toType(classType, cloud, preCast(value, cloud, node, field));
298         }
299     }
300
301     /**
302      * Utility to avoid repetitive calling of getCloud
303      */

304     protected Object JavaDoc cast(Object JavaDoc value, Cloud cloud, Node node, Field field) throws CastException {
305         Object JavaDoc preCast = preCast(value, cloud, node, field);
306         if (preCast == null) return null;
307         Object JavaDoc cast = Casting.toType(classType, cloud, preCast);
308         return cast;
309     }
310
311     protected final Cloud getCloud(Node node, Field field) {
312         if (node != null) return node.getCloud();
313         if (field != null) return field.getNodeManager().getCloud();
314         return null;
315     }
316
317     /**
318      * Before validating the value, the value will be 'cast', on default this will be to the
319      * 'correct' type, but it can be a more generic type sometimes. E.g. for numbers this wil simply
320      * cast to Number.
321      */

322     protected Object JavaDoc castToValidate(Object JavaDoc value, Node node, Field field) throws CastException {
323         return cast(value, getCloud(node, field), node, field);
324     }
325
326     /**
327      * {@inheritDoc}
328      */

329     public Object JavaDoc getDefaultValue() {
330         if (defaultValue == null) return null;
331         return cast(defaultValue, null, null);
332     }
333
334     /**
335      * {@inheritDoc}
336      */

337     public void setDefaultValue(Object JavaDoc def) {
338         edit();
339         defaultValue = def;
340     }
341
342     protected Element JavaDoc getElement(Element JavaDoc parent, String JavaDoc name, String JavaDoc path) {
343         return getElement(parent, name, name, path);
344     }
345     protected Element JavaDoc getElement(Element JavaDoc parent, String JavaDoc pattern, String JavaDoc name, String JavaDoc path) {
346         java.util.regex.Pattern JavaDoc p = java.util.regex.Pattern.compile("\\A" + pattern + "\\z");
347         org.w3c.dom.NodeList JavaDoc nl = parent.getChildNodes();
348         Element JavaDoc el = null;
349         for (int i = 0; i < nl.getLength(); i++) {
350             org.w3c.dom.Node JavaDoc child = nl.item(i);
351             if (child instanceof Element JavaDoc) {
352                 if (p.matcher(child.getNodeName()).matches()) {
353                     el = (Element JavaDoc) child;
354                     break;
355                 }
356             }
357         }
358         if (el == null) {
359             el = parent.getOwnerDocument().createElementNS(XMLNS, name);
360             DocumentReader.appendChild(parent, el, path);
361         }
362         return el;
363     }
364
365     protected String JavaDoc xmlValue(Object JavaDoc value) {
366         return Casting.toString(value);
367     }
368
369     public void toXml(Element JavaDoc parent) {
370         parent.setAttribute("id", getName());
371         description.toXml("description", XMLNS, parent, "description");
372         getElement(parent, "class", "description,class").setAttribute("name", getClass().getName());
373         getElement(parent, "default", "description,class,property,default").setAttribute("value", xmlValue(defaultValue));
374         getElement(parent, "unique", "description,class,property,default,unique").setAttribute("value", "" + uniqueRestriction.isUnique());
375         getElement(parent, "required", "description,class,property,default,unique,required").setAttribute("value", "" + requiredRestriction.isRequired());
376
377         getElement(parent, "enumeration", "description,class,property,default,unique,required,enumeration");
378         /// set this here...
379

380         if (getCommitProcessor() != EmptyCommitProcessor.getInstance()) {
381             org.w3c.dom.NodeList JavaDoc nl = parent.getElementsByTagName("commitprocessor");
382             Element JavaDoc element;
383             if (nl.getLength() == 0) {
384                 element = parent.getOwnerDocument().createElementNS(XMLNS, "commitprocessor");
385                 Element JavaDoc clazz = parent.getOwnerDocument().createElementNS(XMLNS, "class");
386                 clazz.setAttribute("name", getCommitProcessor().getClass().getName());
387                 DocumentReader.appendChild(parent, element, "description,class,property");
388                 element.appendChild(clazz);
389             } else {
390                 element = (Element JavaDoc) nl.item(0);
391             }
392
393             //element.setAttribute("value", Casting.toString(defaultValue));
394
}
395
396     }
397
398     public boolean isFinished() {
399         return owner != null;
400     }
401
402     /**
403      * @javadoc
404      */

405     public void finish() {
406         finish(new Object JavaDoc());
407     }
408
409     /**
410      * @javadoc
411      */

412     public void finish(Object JavaDoc owner) {
413         this.owner = owner;
414     }
415
416     /**
417      * @javadoc
418      */

419     public DataType rewrite(Object JavaDoc owner) {
420         if (this.owner != null) {
421             if (this.owner != owner) {
422                 throw new IllegalArgumentException JavaDoc("Cannot rewrite this datatype - specified owner is not correct");
423             }
424             this.owner = null;
425         }
426         return this;
427     }
428
429
430
431     /**
432      * @javadoc
433      */

434     protected void edit() {
435         if (isFinished()) {
436             throw new IllegalStateException JavaDoc("This data type '" + getName() + "' is finished and can no longer be changed.");
437         }
438     }
439
440
441     /**
442      * {@inheritDoc}
443      */

444     public final Collection /*<LocalizedString>*/ validate(Object JavaDoc value) {
445         return validate(value, null, null);
446     }
447
448
449     public final Collection /*<LocalizedString> */ validate(final Object JavaDoc value, final Node node, final Field field) {
450         return validate(value, node, field, true);
451     }
452     /**
453      * {@inheritDoc}
454      */

455     private final Collection /*<LocalizedString> */ validate(final Object JavaDoc value, final Node node, final Field field, boolean testEnum) {
456         Collection errors = VALID;
457         Object JavaDoc castValue;
458         try {
459             castValue = castToValidate(value, node, field);
460             errors = typeRestriction.validate(errors, castValue, node, field);
461         } catch (CastException ce) {
462             errors = typeRestriction.addError(errors, value, node, field);
463             castValue = value;
464         }
465
466         if (errors.size() > 0) {
467             // no need continuing, restrictions will probably not know how to handle this value any way.
468
return errors;
469         }
470
471         errors = requiredRestriction.validate(errors, value, node, field);
472
473         if (value == null) {
474             return errors; // null is valid, unless required.
475
}
476         if (testEnum) {
477             errors = enumerationRestriction.validate(errors, value, node, field);
478         }
479         errors = uniqueRestriction.validate(errors, castValue, node, field);
480         errors = validateCastValue(errors, castValue, value, node, field);
481         return errors;
482     }
483
484     protected Collection validateCastValue(Collection errors, Object JavaDoc castValue, Object JavaDoc value, Node node, Field field) {
485         return errors;
486     }
487
488     protected StringBuffer JavaDoc toStringBuffer() {
489         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
490         buf.append(getName() + " (" + getTypeAsClass() + (defaultValue != null ? ":" + defaultValue : "") + ")");
491         buf.append(commitProcessor == null ? "" : " commit: " + commitProcessor + "");
492         if (getProcessors != null) {
493             for (int i = 0; i < 13; i++) {
494                 buf.append(getProcessors[i] == null ? "" : ("; get [" + Fields.typeToClass(i) + "]:" + getProcessors[i] + " "));
495             }
496         }
497         if (setProcessors != null) {
498             for (int i =0; i < 13; i++) {
499                 buf.append(setProcessors[i] == null ? "" : ("; set [" + Fields.typeToClass(i) + "]:" + setProcessors[i] + " "));
500             }
501         }
502         if (isRequired()) {
503             buf.append(" required");
504         }
505         if (isUnique()) {
506             buf.append(" unique");
507         }
508         if (enumerationRestriction.getValue() != null) {
509             buf.append(" " + enumerationRestriction);
510         }
511         return buf;
512
513     }
514     public final String JavaDoc toString() {
515         StringBuffer JavaDoc buf = toStringBuffer();
516         if (isFinished()) {
517             buf.append(".");
518         }
519         return buf.toString();
520     }
521
522
523     /**
524      * {@inheritDoc}
525      *
526      * This method is final, override {@link #clone(String)} in stead.
527      */

528     public final Object JavaDoc clone() {
529         return clone(null);
530     }
531
532     /**
533      * {@inheritDoc}
534      *
535      * Besides super.clone, it calls {@link #inheritProperties(BasicDataType)} and {@link
536      * #cloneRestrictions(BasicDataType)}. A clone is not finished. See {@link #isFinished()}.
537      */

538     public Object JavaDoc clone(String JavaDoc name) {
539         try {
540             BasicDataType clone = (BasicDataType) super.clone(name);
541             // reset owner if it was set, so this datatype can be changed
542
clone.owner = null;
543             // properly inherit from this datatype (this also clones properties and processor arrays)
544
clone.inheritProperties(this);
545             clone.cloneRestrictions(this);
546             if (log.isTraceEnabled()) {
547                 log.trace("Cloned " + this + " -> " + clone);
548             }
549             return clone;
550         } catch (CloneNotSupportedException JavaDoc cnse) {
551             // should not happen
552
log.error("Cannot clone this DataType: " + name);
553             throw new RuntimeException JavaDoc("Cannot clone this DataType: " + name, cnse);
554         }
555     }
556
557     public Element JavaDoc toXml() {
558         if (xml == null) {
559             xml = DocumentReader.getDocumentBuilder().newDocument().createElementNS(XMLNS, "datatype");
560             xml.getOwnerDocument().appendChild(xml);
561         }
562         return xml;
563     }
564
565     public void setXml(Element JavaDoc element) {
566         xml = DocumentReader.toDocument(element).getDocumentElement();
567         if (origin != null) {
568             xml.setAttribute("base", origin.getName());
569         }
570         // remove 'specialization' childs (they don't say anything about this datatype itself)
571
org.w3c.dom.NodeList JavaDoc childNodes = xml.getChildNodes();
572         for (int i = 0; i < childNodes.getLength(); i++) {
573             if (childNodes.item(i) instanceof Element JavaDoc) {
574                 Element JavaDoc childElement = (Element JavaDoc) childNodes.item(i);
575                 if (childElement.getLocalName().equals("specialization")
576                     ||childElement.getLocalName().equals("datatype")
577                     ) {
578                     xml.removeChild(childElement);
579                 }
580             }
581         }
582     }
583
584     public int compareTo(Object JavaDoc o) {
585         if (o instanceof DataType) {
586             DataType a = (DataType) o;
587             int compared = getName().compareTo(a.getName());
588             if (compared == 0) compared = getTypeAsClass().getName().compareTo(a.getTypeAsClass().getName());
589             return compared;
590         } else {
591             throw new ClassCastException JavaDoc("Object is not of type DataType");
592         }
593     }
594
595     /**
596      * Whether data type equals to other data type. Only key and type are consided. DefaultValue and
597      * required properties are only 'utilities'.
598      * @return true if o is a DataType of which key and type equal to this' key and type.
599      */

600     public boolean equals(Object JavaDoc o) {
601         if (o instanceof DataType) {
602             DataType a = (DataType) o;
603             return getName().equals(a.getName()) && getTypeAsClass().equals(a.getTypeAsClass());
604         }
605         return false;
606     }
607
608     public int hashCode() {
609         return getName().hashCode() * 13 + getTypeAsClass().hashCode();
610     }
611
612     /**
613      * {@inheritDoc}
614      */

615     public boolean isRequired() {
616         return requiredRestriction.isRequired();
617     }
618
619     /**
620      * {@inheritDoc}
621      */

622     public DataType.Restriction getRequiredRestriction() {
623         return requiredRestriction;
624     }
625
626     /**
627      * {@inheritDoc}
628      */

629     public void setRequired(boolean required) {
630         getRequiredRestriction().setValue(Boolean.valueOf(required));
631     }
632
633     /**
634      * {@inheritDoc}
635      */

636     public boolean isUnique() {
637         return uniqueRestriction.isUnique();
638     }
639
640     /**
641      * {@inheritDoc}
642      */

643     public DataType.Restriction getUniqueRestriction() {
644         return uniqueRestriction;
645     }
646
647     /**
648      * {@inheritDoc}
649      */

650     public void setUnique(boolean unique) {
651         getUniqueRestriction().setValue(Boolean.valueOf(unique));
652     }
653
654     /**
655      * {@inheritDoc}
656      */

657     public Object JavaDoc getEnumerationValue(Locale locale, Cloud cloud, Node node, Field field, Object JavaDoc key) {
658         Object JavaDoc value = null;
659         if (key != null) {
660             // cast to the appropriate datatype value.
661
// Note that for now it is assumed that the keys are of the same type.
662
// I'm not 100% sure that this is always the case.
663
Object JavaDoc keyValue = cast(key, node, field);
664             if (keyValue != null) {
665                 for (Iterator i = new RestrictedEnumerationIterator(locale, cloud, node, field); value == null && i.hasNext(); ) {
666                     Map.Entry entry = (Map.Entry) i.next();
667                     if (keyValue.equals(entry.getKey()) ) {
668                         value = entry.getValue();
669                     }
670                 }
671             }
672         }
673         return value;
674     }
675
676     /**
677      * {@inheritDoc}
678      */

679     public Iterator getEnumerationValues(Locale locale, Cloud cloud, Node node, Field field) {
680         Iterator i = new RestrictedEnumerationIterator(locale, cloud, node, field);
681         return i.hasNext() ? i : null;
682     }
683
684     /**
685      * {@inheritDoc}
686      */

687     public LocalizedEntryListFactory getEnumerationFactory() {
688         return enumerationRestriction.getEnumerationFactory();
689     }
690
691     /**
692      * {@inheritDoc}
693      */

694     public DataType.Restriction getEnumerationRestriction() {
695         return enumerationRestriction;
696     }
697
698
699
700     public CommitProcessor getCommitProcessor() {
701         return commitProcessor == null ? EmptyCommitProcessor.getInstance() : commitProcessor;
702     }
703     public void setCommitProcessor(CommitProcessor cp) {
704         commitProcessor = cp;
705     }
706
707     /**
708      * {@inheritDoc}
709      */

710     public Processor getProcessor(int action) {
711         Processor processor;
712         if (action == PROCESS_GET) {
713             processor = getProcessors == null ? null : getProcessors[0];
714         } else {
715             processor = setProcessors == null ? null : setProcessors[0];
716         }
717         return processor == null ? CopyProcessor.getInstance() : processor;
718     }
719
720     /**
721      * {@inheritDoc}
722      */

723     public Processor getProcessor(int action, int processingType) {
724         if (processingType == Field.TYPE_UNKNOWN) {
725             return getProcessor(action);
726         } else {
727             Processor processor;
728             if (action == PROCESS_GET) {
729                 processor = getProcessors == null ? null : getProcessors[processingType];
730             } else {
731                 processor = setProcessors == null ? null : setProcessors[processingType];
732             }
733             return processor == null ? getProcessor(action) : processor;
734         }
735     }
736
737     /**
738      * {@inheritDoc}
739      */

740     public void setProcessor(int action, Processor processor) {
741         setProcessor(action, processor, Field.TYPE_UNKNOWN);
742     }
743
744     private Processor[] newProcessorsArray() {
745         return new Processor[] {
746              null /* object */, null /* string */, null /* integer */, null /* not used */, null /* byte */,
747              null /* float */, null /* double */, null /* long */, null /* xml */, null /* node */,
748              null /* datetime */, null /* boolean */, null /* list */
749         };
750     }
751
752     /**
753      * {@inheritDoc}
754      */

755     public void setProcessor(int action, Processor processor, int processingType) {
756         if (processingType == Field.TYPE_UNKNOWN) {
757             processingType = 0;
758         }
759         if (action == PROCESS_GET) {
760             if (getProcessors == null) getProcessors = newProcessorsArray();
761             getProcessors[processingType] = processor;
762         } else {
763             if (setProcessors == null) setProcessors = newProcessorsArray();
764             setProcessors[processingType] = processor;
765         }
766     }
767
768
769     // ================================================================================
770
// Follow implementations of the basic restrictions.
771

772
773     // ABSTRACT
774

775     /**
776      * Abstract inner class Restriction. Based on static StaticAbstractRestriction
777      */

778     protected abstract class AbstractRestriction extends StaticAbstractRestriction {
779         protected AbstractRestriction(AbstractRestriction source) {
780             super(BasicDataType.this, source);
781         }
782         protected AbstractRestriction(String JavaDoc name, Serializable value) {
783             super(BasicDataType.this, name, value);
784         }
785     }
786     /**
787      * A Restriction is represented by these kind of objects.
788      * When you override this class, take care of cloning of outer class!
789      * This class itself is not cloneable. Cloning is hard when you have inner classes.
790      *
791      * All restrictions extend from this.
792      *
793      * See <a HREF="http://www.adtmag.com/java/articleold.asp?id=364">article about inner classes,
794      * cloning in java</a>
795      */

796     protected static abstract class StaticAbstractRestriction implements DataType.Restriction {
797         protected final String JavaDoc name;
798         protected final BasicDataType parent;
799         protected LocalizedString errorDescription;
800         protected Serializable value;
801         protected boolean fixed = false;
802         protected int enforceStrength = DataType.ENFORCE_ALWAYS;
803
804         /**
805          * If a restriction has an 'absolute' parent restriction, then also that restriction must be
806          * valid (because it was 'absolute'). A restriction gets an absolute parent if its
807          * surrounding DataType is clone of DataType in which the same restriction is marked with
808          * {@link DataType#ENFORCE_ABSOLUTE}.
809          */

810         protected StaticAbstractRestriction absoluteParent = null;
811
812         /**
813          * Instantaties new restriction for a clone of the parent DataType. If the source
814          * restriction is 'absolute' it will remain to be enforced even if the clone gets a new
815          * value.
816          */

817         protected StaticAbstractRestriction(BasicDataType parent, StaticAbstractRestriction source) {
818             this.name = source.getName();
819             this.parent = parent;
820             if (source.enforceStrength == DataType.ENFORCE_ABSOLUTE) {
821                 absoluteParent = source;
822             } else {
823                 absoluteParent = source.absoluteParent;
824             }
825             inherit(source);
826             if (source.enforceStrength == DataType.ENFORCE_ABSOLUTE) {
827                 enforceStrength = DataType.ENFORCE_ALWAYS;
828             }
829         }
830
831         protected StaticAbstractRestriction(BasicDataType parent, String JavaDoc name, Serializable value) {
832             this.name = name;
833             this.parent = parent;
834             this.value = value;
835         }
836
837         public String JavaDoc getName() {
838             return name;
839         }
840
841         public Serializable getValue() {
842             return value;
843         }
844
845         public void setValue(Serializable v) {
846             parent.edit();
847             if (fixed) {
848                 throw new IllegalStateException JavaDoc("Restriction '" + name + "' is fixed, cannot be changed");
849             }
850
851             this.value = v;
852         }
853
854         public LocalizedString getErrorDescription() {
855             if (errorDescription == null) {
856                 // this is postponsed to first use, because otherwis 'getBaesTypeIdentifier' give correct value only after constructor of parent.
857
String JavaDoc key = parent.getBaseTypeIdentifier() + "." + name + ".error";
858                 errorDescription = new LocalizedString(key);
859                 errorDescription.setBundle(DATATYPE_BUNDLE);
860             }
861             return errorDescription;
862         }
863
864         public void setErrorDescription(LocalizedString errorDescription) {
865             this.errorDescription = errorDescription;
866         }
867
868         public boolean isFixed() {
869             return fixed;
870         }
871
872         public void setFixed(boolean fixed) {
873             if (this.fixed && !fixed) {
874                 throw new IllegalStateException JavaDoc("Restriction '" + name + "' is fixed, cannot be changed");
875             }
876             this.fixed = fixed;
877         }
878
879         /**
880          * Utility method to add a new error message to the errors collection, based on this
881          * Restriction. If this error-collection is unmodifiable (VALID), it is replaced with a new
882          * empty one first.
883          */

884         protected final Collection addError(Collection errors, Object JavaDoc v, Node node, Field field) {
885             if (errors == VALID) errors = new ArrayList();
886             ReplacingLocalizedString error = new ReplacingLocalizedString(getErrorDescription());
887             error.replaceAll("\\$\\{NAME\\}", ReplacingLocalizedString.makeLiteral(getName()));
888             error.replaceAll("\\$\\{CONSTRAINT\\}", ReplacingLocalizedString.makeLiteral(toString(node, field)));
889             error.replaceAll("\\$\\{CONSTRAINTVALUE\\}", ReplacingLocalizedString.makeLiteral(valueString(node, field)));
890             error.replaceAll("\\$\\{VALUE\\}", ReplacingLocalizedString.makeLiteral("" + v));
891             errors.add(error);
892             return errors;
893         }
894
895         /**
896          * If value of a a restriction depends on node, field, then you can override this
897          */

898         protected String JavaDoc valueString(Node node, Field field) {
899             return "" + value;
900         }
901
902         /**
903          * Whether {@link #validate} must enforce this condition
904          */

905         protected final boolean enforce(Node node, Field field) {
906             switch(enforceStrength) {
907             case DataType.ENFORCE_ABSOLUTE:
908             case DataType.ENFORCE_ALWAYS: return true;
909             case DataType.ENFORCE_ONCHANGE: if (node == null || field == null || node.isChanged(field.getName())) return true;
910             case DataType.ENFORCE_ONCREATE: if (node == null || node.isNew()) return true;
911             case DataType.ENFORCE_NEVER: return false;
912             default: return true;
913             }
914         }
915
916         /**
917          * This method is called by {@link BasicDataType#validate(Object, Node, Field)} for each of its conditions.
918          */

919         protected Collection validate(Collection errors, Object JavaDoc v, Node node, Field field) {
920             if (absoluteParent != null && ! absoluteParent.valid(v, node, field)) {
921                 int sizeBefore = errors.size();
922                 Collection res = absoluteParent.addError(errors, v, node, field);
923                 if (res.size() > sizeBefore) {
924                     return res;
925                 }
926             }
927             if ((! enforce(node, field)) || valid(v, node, field)) {
928                 // no new error to add.
929
return errors;
930             } else {
931                 return addError(errors, v, node, field);
932             }
933         }
934
935         public final boolean valid(Object JavaDoc v, Node node, Field field) {
936             try {
937                 if (absoluteParent != null) {
938                     if (! absoluteParent.valid(v, node, field)) return false;
939                 }
940                 return simpleValid(parent.castToValidate(v, node, field), node, field);
941             } catch (Throwable JavaDoc t) {
942                 if (log.isServiceEnabled()) {
943                     log.service("Not valid because cast-to-validate threw exception " + t.getClass(), t);
944                 }
945                 return false;
946             }
947         }
948
949         protected abstract boolean simpleValid(Object JavaDoc v, Node node, Field field);
950
951         protected final void inherit(StaticAbstractRestriction source, boolean cast) {
952             // perhaps this value must be cloned?, but how?? Cloneable has no public methods....
953
Serializable inheritedValue = source.getValue();
954             if (cast) inheritedValue = (Serializable) parent.cast(inheritedValue, null, null);
955             setValue(inheritedValue);
956             enforceStrength = source.getEnforceStrength();
957             errorDescription = (LocalizedString) source.getErrorDescription().clone();
958         }
959
960         protected final void inherit(StaticAbstractRestriction source) {
961             inherit(source, false);
962         }
963
964         public int getEnforceStrength() {
965             return enforceStrength;
966         }
967
968         public void setEnforceStrength(int e) {
969             enforceStrength = e;
970         }
971
972         public final String JavaDoc toString() {
973             return toString(null, null);
974         }
975
976         public final String JavaDoc toString(Node node, Field field) {
977             return name + ": " +
978                 (enforceStrength == DataType.ENFORCE_NEVER ? "*" : "") +
979                 valueString(node, field) + ( fixed ? "." : "");
980         }
981
982     }
983
984     // REQUIRED
985
protected class RequiredRestriction extends AbstractRestriction {
986         private static final long serialVersionUID = 1L;
987
988         RequiredRestriction(RequiredRestriction source) {
989             super(source);
990         }
991
992         RequiredRestriction(boolean b) {
993             super("required", Boolean.valueOf(b));
994         }
995
996         final boolean isRequired() {
997             return Boolean.TRUE.equals(value);
998         }
999
1000        protected boolean simpleValid(Object JavaDoc v, Node node, Field field) {
1001            if(!isRequired()) return true;
1002            return v != null;
1003        }
1004    }
1005
1006    // UNIQUE
1007
protected class UniqueRestriction extends AbstractRestriction {
1008        private static final long serialVersionUID = 1L;
1009        UniqueRestriction(UniqueRestriction source) {
1010            super(source);
1011        }
1012
1013        UniqueRestriction(boolean b) {
1014            super("unique", Boolean.valueOf(b));
1015        }
1016
1017        final boolean isUnique() {
1018            return Boolean.TRUE.equals(value);
1019        }
1020
1021        protected boolean simpleValid(Object JavaDoc v, Node node, Field field) {
1022            if (! isUnique()) return true;
1023            if (node != null && field != null && v != null && value != null ) {
1024
1025                if (field.isVirtual()) return true; // e.g. if the field was defined in XML but not present in DB (after upgrade?)
1026

1027                if (!node.isNew()) {
1028                    if (field.getName().equals("number")) {
1029                        // on 'number' there is a unique constraint, if it is checked for a non-new node
1030
// we can simply avoid all quering because it will result in a query number == <number> and number <> <number>
1031
if (Casting.toInt(v) == node.getNumber()) {
1032                            return true;
1033                        } else {
1034                            // changing
1035
log.warn("Odd, changing number of node " + node + " ?!", new Exception JavaDoc());
1036                        }
1037                    }
1038                }
1039
1040                NodeManager nodeManager = field.getNodeManager();
1041                Cloud cloud = nodeManager.getCloud();
1042                if (cloud.getUser().getRank().getInt() < Rank.ADMIN_INT) {
1043                    // This will test for uniqueness using bridge, so you'll miss objects you can't
1044
// see (and database doesn't know that!)
1045
// So try using an administrator for that! That would probably work ok.
1046
Cloud adminCloud = cloud.getCloudContext().getCloud("mmbase", "class", null);
1047                    if (adminCloud.getUser().getRank().getInt() > cloud.getUser().getRank().getInt()) {
1048                        cloud = adminCloud;
1049                        nodeManager = adminCloud.getNodeManager(nodeManager.getName());
1050                    }
1051                }
1052                // create a query and query for the value
1053
NodeQuery query = nodeManager.createQuery();
1054                Constraint constraint = Queries.createConstraint(query, field.getName(), FieldCompareConstraint.EQUAL, v);
1055                Queries.addConstraint(query, constraint);
1056                if (!node.isNew()) {
1057                    constraint = Queries.createConstraint(query, "number", FieldCompareConstraint.NOT_EQUAL, new Integer JavaDoc(node.getNumber()));
1058                    Queries.addConstraint(query, constraint);
1059                }
1060                if(log.isDebugEnabled()) {
1061                    log.debug(query);
1062                }
1063                return Queries.count(query) == 0;
1064            } else {
1065                // TODO needs to work without Node too.
1066
return true;
1067            }
1068        }
1069    }
1070
1071
1072    // TYPE
1073

1074    protected class TypeRestriction extends AbstractRestriction {
1075        private static final long serialVersionUID = 1L;
1076        TypeRestriction(TypeRestriction source) {
1077            super(source);
1078        }
1079
1080        TypeRestriction() {
1081            super("type", BasicDataType.this.getClass());
1082        }
1083
1084        protected boolean simpleValid(Object JavaDoc v, Node node, Field field) {
1085            try {
1086                BasicDataType.this.cast(v, node, field);
1087                return true;
1088            } catch (Throwable JavaDoc e) {
1089                return false;
1090            }
1091        }
1092    }
1093
1094    // ENUMERATION
1095
protected class EnumerationRestriction extends AbstractRestriction {
1096        private static final long serialVersionUID = 1L;
1097
1098        EnumerationRestriction(EnumerationRestriction source) {
1099            super(source);
1100            value = value != null ? (Serializable) ((LocalizedEntryListFactory) value).clone() : null;
1101        }
1102
1103        EnumerationRestriction(LocalizedEntryListFactory entries) {
1104            super("enumeration", entries);
1105        }
1106
1107        final LocalizedEntryListFactory getEnumerationFactory() {
1108            if(value == null) {
1109                value = new LocalizedEntryListFactory();
1110            }
1111            return (LocalizedEntryListFactory) value;
1112        }
1113
1114        public Collection getEnumeration(Locale locale, Cloud cloud, Node node, Field field) {
1115            if (value == null) return Collections.EMPTY_LIST;
1116            LocalizedEntryListFactory ef = (LocalizedEntryListFactory) value;
1117            if (cloud == null) {
1118                if (node != null) {
1119                    cloud = node.getCloud();
1120                } else if (field != null) {
1121                    cloud = field.getNodeManager().getCloud();
1122                }
1123            }
1124            return ef.get(locale, cloud);
1125        }
1126
1127        /**
1128         * @see BasicDataType#preCast
1129         */

1130        protected Object JavaDoc preCast(Object JavaDoc v, Cloud cloud) {
1131            if (getValue() == null) return v;
1132            try {
1133                return ((LocalizedEntryListFactory) value).castKey(v, cloud);
1134                //return v != null ? Casting.toType(v.getClass(), cloud, res) : res;
1135
} catch (NoClassDefFoundError JavaDoc ncdfe) {
1136                log.error("Could not find class " + ncdfe.getMessage() + " while casting " + v.getClass() + " " + v, ncdfe);
1137                return v;
1138            }
1139
1140        }
1141
1142        protected boolean simpleValid(Object JavaDoc v, Node node, Field field) {
1143            if (value == null || ((LocalizedEntryListFactory) value).isEmpty()) {
1144                return true;
1145            }
1146            Cloud cloud = BasicDataType.this.getCloud(node, field);
1147            Collection validValues = getEnumeration(null, cloud, node, field);
1148            if (validValues.size() == 0) {
1149                return true;
1150            }
1151            Object JavaDoc candidate;
1152            try {
1153                candidate = BasicDataType.this.cast(v, cloud, node, field);
1154            } catch (CastException ce) {
1155                return false;
1156            }
1157            Iterator i = validValues.iterator();
1158            while (i.hasNext()) {
1159                Map.Entry e = (Map.Entry) i.next();
1160                Object JavaDoc valid = e.getKey();
1161                if (valid.equals(candidate)) {
1162                    return true;
1163                }
1164            }
1165            return false;
1166        }
1167
1168        protected String JavaDoc valueString(Node node, Field field) {
1169            Collection col = getEnumeration(null, null, node, field);
1170            if(col.size() == 0) return "";
1171            StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
1172            Iterator it = col.iterator();
1173            int i = 0;
1174            while (it.hasNext() && ++i < 10) {
1175                Map.Entry ent = (Map.Entry)it.next();
1176                buf.append(Casting.toString(ent));
1177                if (it.hasNext()) buf.append(", ");
1178            }
1179            if (i < col.size()) buf.append(".(" + (col.size() - i) + " more ..");
1180            return buf.toString();
1181        }
1182
1183    }
1184
1185
1186
1187    /**
1188     * Iterates over the collection provided by the EnumerationRestriction, but skips the values
1189     * which are invalid because of the other restrictions on this DataType.
1190     */

1191    //Also, it 'preCasts' the * keys to the right type.
1192

1193    protected class RestrictedEnumerationIterator implements Iterator {
1194        private final Iterator baseIterator;
1195        private final Node node;
1196        private final Field field;
1197        private Map.Entry next = null;
1198
1199        RestrictedEnumerationIterator(Locale locale, Cloud cloud, Node node, Field field) {
1200            Collection col = enumerationRestriction.getEnumeration(locale, cloud, node, field);
1201            if (log.isDebugEnabled()) {
1202                log.debug("Restricted iterator on " + col);
1203            }
1204            baseIterator = col.iterator();
1205            this.node = node;
1206            this.field = field;
1207            determineNext();
1208        }
1209
1210        protected void determineNext() {
1211            next = null;
1212            while (baseIterator.hasNext()) {
1213                final Map.Entry entry = (Map.Entry) baseIterator.next();
1214                Object JavaDoc value = entry.getKey();
1215                Collection validationResult = BasicDataType.this.validate(value, node, field, false);
1216                if (validationResult == VALID) {
1217                    next = entry;
1218                    /*
1219                    new Map.Entry() {
1220                            public Object getKey() {
1221                                return BasicDataType.this.preCast(entry.getKey(), node, field);
1222                            }
1223                            public Object getValue() {
1224                                return entry.getValue();
1225                            }
1226                            public Object setValue(Object v) {
1227                                return entry.setValue(v);
1228                            }
1229                        };
1230                    */

1231                    break;
1232                } else if (log.isDebugEnabled()) {
1233                    String JavaDoc errors = "";
1234                    for (Iterator i = validationResult.iterator(); i.hasNext();) {
1235                        errors += ((LocalizedString)i.next()).get(null);
1236                    }
1237                    log.debug("Value " + value.getClass() + " " + value + " does not validate : " + errors);
1238                }
1239            }
1240        }
1241
1242        public boolean hasNext() {
1243            return next != null;
1244        }
1245
1246        public Object JavaDoc next() {
1247            if (next == null) {
1248                throw new NoSuchElementException();
1249            }
1250            Object JavaDoc n = next;
1251            determineNext();
1252            return n;
1253        }
1254
1255        public void remove() {
1256            throw new UnsupportedOperationException JavaDoc("Cannot remove entries from " + getClass());
1257        }
1258
1259        public String JavaDoc toString() {
1260            return "restricted iterator(" + enumerationRestriction + ")";
1261        }
1262    }
1263
1264}
1265
Popular Tags