1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.objects

File BaseCollection.java

 

Coverage histogram

../../../../img/srcFileCovDistChart6.png
72% of files have more coverage

Code metrics

148
314
60
1
952
668
147
0.47
5.23
60
2.45

Classes

Class Line # Actions
BaseCollection 64 314 0% 147 214
0.590038359%
 

Contributing tests

This file is covered by 273 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.objects;
21   
22    import java.util.ArrayList;
23    import java.util.Arrays;
24    import java.util.Collection;
25    import java.util.Collections;
26    import java.util.Date;
27    import java.util.HashMap;
28    import java.util.HashSet;
29    import java.util.Iterator;
30    import java.util.LinkedHashMap;
31    import java.util.List;
32    import java.util.Map;
33    import java.util.Set;
34   
35    import org.apache.commons.lang3.StringUtils;
36    import org.dom4j.Element;
37    import org.slf4j.Logger;
38    import org.slf4j.LoggerFactory;
39    import org.xwiki.model.EntityType;
40    import org.xwiki.model.reference.DocumentReference;
41    import org.xwiki.model.reference.DocumentReferenceResolver;
42    import org.xwiki.model.reference.EntityReference;
43    import org.xwiki.model.reference.EntityReferenceResolver;
44   
45    import com.xpn.xwiki.XWikiContext;
46    import com.xpn.xwiki.XWikiException;
47    import com.xpn.xwiki.doc.XWikiDocument;
48    import com.xpn.xwiki.doc.merge.MergeConfiguration;
49    import com.xpn.xwiki.doc.merge.MergeResult;
50    import com.xpn.xwiki.objects.classes.BaseClass;
51    import com.xpn.xwiki.objects.classes.PropertyClass;
52    import com.xpn.xwiki.web.Utils;
53   
54    /**
55    * Base class for representing an element having a collection of properties. For example:
56    * <ul>
57    * <li>an XClass definition (composed of XClass properties)</li>
58    * <li>an XObject definition (composed of XObject properties)</li>
59    * <li>an XWikiStats object (composed of stats properties)</li>
60    * </ul>
61    *
62    * @version $Id: 73b5d1732bf364e971fc8687167f70d802ee721f $
63    */
 
64    public abstract class BaseCollection<R extends EntityReference> extends BaseElement<R>
65    implements ObjectInterface, Cloneable
66    {
67    private static final Logger LOGGER = LoggerFactory.getLogger(BaseCollection.class);
68   
69    /**
70    * The meaning of this reference fields depends on the element represented. Examples:
71    * <ul>
72    * <li>If this BaseCollection instance represents an XObject then refers to the document where the XObject's XClass
73    * is defined.</li>
74    * <li>If this BaseCollection instance represents an XClass then it's not used.</li>
75    * </ul>
76    */
77    private EntityReference xClassReference;
78   
79    /**
80    * Cache the XClass reference resolved as an absolute reference for improved performance (so that we don't have to
81    * resolve the relative reference every time getXClassReference() is called.
82    */
83    private DocumentReference xClassReferenceCache;
84   
85    /**
86    * List of properties (eg XClass properties, XObject properties, etc).
87    */
88    protected Map<String, Object> fields = new LinkedHashMap<String, Object>();
89   
90    protected List<Object> fieldsToRemove = new ArrayList<>();
91   
92    /**
93    * The meaning of this reference fields depends on the element represented. Examples:
94    * <ul>
95    * <li>When the BaseCollection represents an XObject, this number is the position of this XObject in the document
96    * where it's located. The first XObject of a given XClass type is at position 0, and other XObject of the same
97    * XClass type are at position 1, etc.</li>
98    * </ul>
99    */
100    protected int number;
101   
102    /**
103    * Used to resolve XClass references in the way they are stored externally (database, xml, etc), ie relative or
104    * absolute.
105    */
106    protected EntityReferenceResolver<String> relativeEntityReferenceResolver;
107   
108    /**
109    * Used to normalize references.
110    */
111    protected DocumentReferenceResolver<EntityReference> currentReferenceDocumentReferenceResolver;
112   
113    /**
114    * @return the component used to resolve XClass references in the way they are stored externally (database, xml,
115    * etc), ie relative or absolute
116    */
 
117  30819 toggle protected EntityReferenceResolver<String> getRelativeEntityReferenceResolver()
118    {
119  30819 if (this.relativeEntityReferenceResolver == null) {
120  30817 this.relativeEntityReferenceResolver = Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "relative");
121    }
122   
123  30821 return this.relativeEntityReferenceResolver;
124    }
125   
126    /**
127    * @return the component used to normalize references
128    */
 
129  1179195 toggle protected DocumentReferenceResolver<EntityReference> getCurrentReferenceDocumentReferenceResolver()
130    {
131  1179202 if (this.currentReferenceDocumentReferenceResolver == null) {
132  71541 this.currentReferenceDocumentReferenceResolver =
133    Utils.getComponent(DocumentReferenceResolver.TYPE_REFERENCE, "current");
134    }
135   
136  1179210 return this.currentReferenceDocumentReferenceResolver;
137    }
138   
 
139  1139954 toggle public int getNumber()
140    {
141  1139970 return this.number;
142    }
143   
 
144  1360276 toggle public void setNumber(int number)
145    {
146  1360235 this.number = number;
147    }
148   
149    /**
150    * Marks a field as scheduled for removal when saving this entity. Should only be used internally, use
151    * {@link #removeField(String)} to actually remove a field.
152    *
153    * @param field the field to remove, must belong to this entity
154    * @see #removeField(String)
155    */
 
156  5 toggle public void addPropertyForRemoval(PropertyInterface field)
157    {
158  5 getFieldsToRemove().add(field);
159    }
160   
161    /**
162    * Get the absolute reference of the XClass.
163    *
164    * @since 2.2M2
165    */
 
166  3095591 toggle public DocumentReference getXClassReference()
167    {
168  3095626 if (this.xClassReferenceCache == null && getRelativeXClassReference() != null) {
169  1179213 this.xClassReferenceCache = getCurrentReferenceDocumentReferenceResolver()
170    .resolve(getRelativeXClassReference(), getDocumentReference());
171    }
172   
173  3095598 return this.xClassReferenceCache;
174    }
175   
176    /**
177    * Get the actual reference to the XClass as stored in this instance.
178    *
179    * @since 4.0M2
180    */
 
181  3651169 toggle public EntityReference getRelativeXClassReference()
182    {
183  3651181 return this.xClassReference;
184    }
185   
186    /**
187    * Note that this method cannot be removed for now since it's used by Hibernate for saving an XObject.
188    *
189    * @deprecated since 2.2M2 use {@link #getXClassReference()} instead
190    */
 
191  39609 toggle @Deprecated
192    public String getClassName()
193    {
194  39609 String xClassAsString;
195  39609 if (getXClassReference() != null) {
196  39609 xClassAsString = getLocalEntityReferenceSerializer().serialize(getXClassReference());
197    } else {
198  0 xClassAsString = "";
199    }
200  39609 return xClassAsString;
201    }
202   
203    /**
204    * Set the reference to the XClass (used for an XObject).
205    * <p>
206    * Note that absolute reference are not supported for xclasses which mean that the wiki part (whatever the wiki is)
207    * of the reference will be systematically removed.
208    *
209    * @param xClassReference the reference to the XClass of this XObject.
210    * @since 2.2.3
211    */
 
212  1256338 toggle public void setXClassReference(EntityReference xClassReference)
213    {
214    // Ensure that the reference to the XClass is always relative to the document wiki.
215  1256351 EntityReference ref = xClassReference;
216   
217  1256339 if (ref != null) {
218  634756 EntityReference wiki = xClassReference.extractReference(EntityType.WIKI);
219  634732 if (wiki != null) {
220  33417 ref = xClassReference.removeParent(wiki);
221    }
222    }
223   
224  1256331 this.xClassReference = ref;
225  1256330 this.xClassReferenceCache = null;
226    }
227   
228    /**
229    * Note that this method cannot be removed for now since it's used by Hibernate for loading an XObject.
230    *
231    * @deprecated since 2.2.3 use {@link #setXClassReference(EntityReference)} ()} instead
232    */
 
233  30710 toggle @Deprecated
234    public void setClassName(String name)
235    {
236  30710 EntityReference xClassReference = null;
237  30712 if (!StringUtils.isEmpty(name)) {
238    // Handle backward compatibility: In the past, for statistics objects we used to use a special class name
239    // of "internal". We now check for a null Class Reference instead wherever we were previously checking for
240    // "internal".
241  30712 if (!"internal".equals(name)) {
242  30713 xClassReference = getRelativeEntityReferenceResolver().resolve(name, EntityType.DOCUMENT);
243    }
244    }
245  30711 setXClassReference(xClassReference);
246    }
247   
 
248  11494956 toggle @Override
249    public PropertyInterface safeget(String name)
250    {
251  11496236 return (PropertyInterface) getFields().get(name);
252    }
253   
 
254  660771 toggle @Override
255    public PropertyInterface get(String name) throws XWikiException
256    {
257  660774 return safeget(name);
258    }
259   
 
260  3321401 toggle @Override
261    public void safeput(String name, PropertyInterface property)
262    {
263  3321399 addField(name, property);
264  3321372 if (property instanceof BaseProperty) {
265  3167514 ((BaseProperty) property).setObject(this);
266  3167500 ((BaseProperty) property).setName(name);
267    }
268    }
269   
 
270  0 toggle @Override
271    public void put(String name, PropertyInterface property) throws XWikiException
272    {
273  0 safeput(name, property);
274    }
275   
276    /**
277    * @since 2.2M1
278    */
 
279  864810 toggle @Override
280    public BaseClass getXClass(XWikiContext context)
281    {
282  864823 BaseClass baseClass = null;
283   
284  864809 if ((context == null) || (context.getWiki() == null)) {
285  60 return baseClass;
286    }
287   
288  864753 DocumentReference classReference = getXClassReference();
289   
290  864766 if (classReference != null) {
291  864554 try {
292  864562 baseClass = context.getWiki().getXClass(classReference, context);
293    } catch (Exception e) {
294  1 LOGGER.error("Failed to get class [" + classReference + "]", e);
295    }
296    }
297   
298  864755 return baseClass;
299    }
300   
 
301  4703426 toggle public String getStringValue(String name)
302    {
303  4704836 BaseProperty prop = (BaseProperty) safeget(name);
304  4705843 if (prop == null || prop.getValue() == null) {
305  53024 return "";
306    } else {
307  4652903 return prop.getValue().toString();
308    }
309    }
310   
 
311  6483 toggle public String getLargeStringValue(String name)
312    {
313  6482 return getStringValue(name);
314    }
315   
 
316  1101307 toggle public void setStringValue(String name, String value)
317    {
318  1101306 BaseStringProperty property = (BaseStringProperty) safeget(name);
319   
320  1101305 if (!(property instanceof StringProperty)) {
321  454240 if (property != null) {
322    // Make sure to delete the property if it's not the right type
323  4 removeField(name);
324    }
325   
326  454241 property = new StringProperty();
327    }
328   
329  1101303 property.setName(name);
330  1101301 property.setValue(value);
331   
332  1101301 safeput(name, property);
333    }
334   
 
335  1148 toggle public void setLargeStringValue(String name, String value)
336    {
337  1148 BaseStringProperty property = (BaseStringProperty) safeget(name);
338   
339  1148 if (!(property instanceof LargeStringProperty)) {
340  1145 if (property != null) {
341    // Make sure to delete the property if it's not the right type
342  0 removeField(name);
343    }
344   
345  1145 property = new LargeStringProperty();
346    }
347   
348  1148 property.setName(name);
349  1148 property.setValue(value);
350   
351  1148 safeput(name, property);
352    }
353   
 
354  1064528 toggle public int getIntValue(String name)
355    {
356  1064530 return getIntValue(name, 0);
357    }
358   
 
359  1100210 toggle public int getIntValue(String name, int default_value)
360    {
361  1100210 try {
362  1100214 NumberProperty prop = (NumberProperty) safeget(name);
363  1100206 if (prop == null) {
364  60670 return default_value;
365    } else {
366  1039533 return ((Number) prop.getValue()).intValue();
367    }
368    } catch (Exception e) {
369  13891 return default_value;
370    }
371    }
372   
 
373  1126263 toggle public void setIntValue(String name, int value)
374    {
375  1126262 NumberProperty property = new IntegerProperty();
376  1126259 property.setName(name);
377  1126259 property.setValue(value);
378  1126259 safeput(name, property);
379    }
380   
 
381  0 toggle public long getLongValue(String name)
382    {
383  0 try {
384  0 NumberProperty prop = (NumberProperty) safeget(name);
385  0 if (prop == null) {
386  0 return 0;
387    } else {
388  0 return ((Number) prop.getValue()).longValue();
389    }
390    } catch (Exception e) {
391  0 return 0;
392    }
393    }
394   
 
395  1 toggle public void setLongValue(String name, long value)
396    {
397  1 NumberProperty property = new LongProperty();
398  1 property.setName(name);
399  1 property.setValue(value);
400  1 safeput(name, property);
401    }
402   
 
403  0 toggle public float getFloatValue(String name)
404    {
405  0 try {
406  0 NumberProperty prop = (NumberProperty) safeget(name);
407  0 if (prop == null) {
408  0 return 0;
409    } else {
410  0 return ((Number) prop.getValue()).floatValue();
411    }
412    } catch (Exception e) {
413  0 return 0;
414    }
415    }
416   
 
417  0 toggle public void setFloatValue(String name, float value)
418    {
419  0 NumberProperty property = new FloatProperty();
420  0 property.setName(name);
421  0 property.setValue(Float.valueOf(value));
422  0 safeput(name, property);
423    }
424   
 
425  0 toggle public double getDoubleValue(String name)
426    {
427  0 try {
428  0 NumberProperty prop = (NumberProperty) safeget(name);
429  0 if (prop == null) {
430  0 return 0;
431    } else {
432  0 return ((Number) prop.getValue()).doubleValue();
433    }
434    } catch (Exception e) {
435  0 return 0;
436    }
437    }
438   
 
439  0 toggle public void setDoubleValue(String name, double value)
440    {
441  0 NumberProperty property = new DoubleProperty();
442  0 property.setName(name);
443  0 property.setValue(Double.valueOf(value));
444  0 safeput(name, property);
445    }
446   
 
447  3017 toggle public Date getDateValue(String name)
448    {
449  3017 try {
450  3017 DateProperty prop = (DateProperty) safeget(name);
451  3017 if (prop == null) {
452  5 return null;
453    } else {
454  3012 return (Date) prop.getValue();
455    }
456    } catch (Exception e) {
457  0 return null;
458    }
459    }
460   
 
461  20 toggle public void setDateValue(String name, Date value)
462    {
463  20 DateProperty property = new DateProperty();
464  20 property.setName(name);
465  20 property.setValue(value);
466  20 safeput(name, property);
467    }
468   
 
469  0 toggle public Set<?> getSetValue(String name)
470    {
471  0 ListProperty prop = (ListProperty) safeget(name);
472  0 if (prop == null) {
473  0 return new HashSet<Object>();
474    } else {
475  0 return new HashSet<Object>((Collection<?>) prop.getValue());
476    }
477    }
478   
 
479  0 toggle public void setSetValue(String name, Set<?> value)
480    {
481  0 ListProperty property = new ListProperty();
482  0 property.setValue(value);
483  0 safeput(name, property);
484    }
485   
 
486  78233 toggle public List getListValue(String name)
487    {
488  78233 ListProperty prop = (ListProperty) safeget(name);
489  78233 if (prop == null) {
490  71305 return new ArrayList();
491    } else {
492  6928 return (List) prop.getValue();
493    }
494    }
495   
 
496  119 toggle public void setStringListValue(String name, List value)
497    {
498  119 ListProperty property = (ListProperty) safeget(name);
499  119 if (property == null) {
500  119 property = new StringListProperty();
501    }
502  119 property.setValue(value);
503  119 safeput(name, property);
504    }
505   
 
506  14 toggle public void setDBStringListValue(String name, List value)
507    {
508  14 ListProperty property = (ListProperty) safeget(name);
509  14 if (property == null) {
510  0 property = new DBStringListProperty();
511    }
512  14 property.setValue(value);
513  14 safeput(name, property);
514    }
515   
516    // These functions should not be used
517    // but instead our own implementation
 
518  13064796 toggle private Map<String, Object> getFields()
519    {
520  13065099 return this.fields;
521    }
522   
 
523  1151123 toggle public void setFields(Map fields)
524    {
525  1151106 this.fields = fields;
526    }
527   
 
528  40105183 toggle public PropertyInterface getField(String name)
529    {
530  40105241 return (PropertyInterface) this.fields.get(name);
531    }
532   
 
533  3526781 toggle public void addField(String name, PropertyInterface element)
534    {
535  3526899 this.fields.put(name, element);
536   
537  3526973 if (element instanceof BaseElement) {
538  3526976 ((BaseElement) element).setOwnerDocument(getOwnerDocument());
539    }
540    }
541   
 
542  89 toggle public void removeField(String name)
543    {
544  89 Object field = safeget(name);
545  89 if (field != null) {
546  89 this.fields.remove(name);
547  89 this.fieldsToRemove.add(field);
548    }
549    }
550   
 
551  91128 toggle public Collection getFieldList()
552    {
553  91128 return this.fields.values();
554    }
555   
 
556  4459027 toggle public Set<String> getPropertyList()
557    {
558  4459037 return this.fields.keySet();
559    }
560   
 
561  214383 toggle public Object[] getProperties()
562    {
563  214383 return getFields().values().toArray();
564    }
565   
 
566  368 toggle public String[] getPropertyNames()
567    {
568  368 return getFields().keySet().toArray(new String[0]);
569    }
570   
571    /**
572    * Return an iterator that will operate on a collection of values (as would be returned by getProperties or
573    * getFieldList) sorted by their name (ElementInterface.getName()).
574    */
 
575  213852 toggle public Iterator getSortedIterator()
576    {
577  213852 Iterator it = null;
578  213852 try {
579    // Use getProperties to get the values in list form (rather than as generic collection)
580  213852 List propList = Arrays.asList(getProperties());
581   
582    // Use the element comparator to sort the properties by name (based on ElementInterface)
583  213852 Collections.sort(propList, new ElementComparator());
584   
585    // Iterate over the sorted property list
586  213852 it = propList.iterator();
587    } catch (ClassCastException ccex) {
588    // If sorting by the comparator resulted in a ClassCastException (possible),
589    // iterate over the generic collection of values.
590  0 it = getFieldList().iterator();
591    }
592   
593  213851 return it;
594    }
595   
 
596  18705 toggle @Override
597    public boolean equals(Object coll)
598    {
599    // Same Java object, they sure are equal
600  18705 if (this == coll) {
601  0 return true;
602    }
603   
604  18705 if (!super.equals(coll)) {
605  115 return false;
606    }
607  18590 BaseCollection collection = (BaseCollection) coll;
608  18590 if (collection.getXClassReference() == null) {
609  16716 if (getXClassReference() != null) {
610  0 return false;
611    }
612  1874 } else if (!collection.getXClassReference().equals(getXClassReference())) {
613  0 return false;
614    }
615   
616  18590 if (getFields().size() != collection.getFields().size()) {
617  154 return false;
618    }
619   
620  18436 for (Map.Entry<String, Object> entry : getFields().entrySet()) {
621  148046 Object prop = entry.getValue();
622  148046 Object prop2 = collection.getFields().get(entry.getKey());
623  148046 if (!prop.equals(prop2)) {
624  277 return false;
625    }
626    }
627   
628  18159 return true;
629    }
630   
 
631  1151081 toggle @Override
632    public BaseCollection clone()
633    {
634  1151082 BaseCollection collection = (BaseCollection) super.clone();
635  1151041 collection.setXClassReference(getRelativeXClassReference());
636  1151057 collection.setNumber(getNumber());
637  1151057 Map fields = getFields();
638  1151046 Map cfields = new HashMap();
639  1151055 for (Object objEntry : fields.entrySet()) {
640  10824381 Map.Entry entry = (Map.Entry) objEntry;
641  10824435 PropertyInterface prop = (PropertyInterface) ((BaseElement) entry.getValue()).clone();
642  10824096 prop.setObject(collection);
643  10824076 cfields.put(entry.getKey(), prop);
644    }
645  1151094 collection.setFields(cfields);
646   
647  1151098 return collection;
648    }
649   
 
650  0 toggle public void merge(BaseObject object)
651    {
652  0 Iterator itfields = object.getPropertyList().iterator();
653  0 while (itfields.hasNext()) {
654  0 String name = (String) itfields.next();
655  0 if (safeget(name) == null) {
656  0 safeput(name, (PropertyInterface) ((BaseElement) object.safeget(name)).clone());
657    }
658    }
659    }
660   
 
661  0 toggle public List<ObjectDiff> getDiff(Object oldObject, XWikiContext context)
662    {
663  0 ArrayList<ObjectDiff> difflist = new ArrayList<ObjectDiff>();
664  0 BaseCollection oldCollection = (BaseCollection) oldObject;
665    // Iterate over the new properties first, to handle changed and added objects
666  0 for (Object key : this.getFields().keySet()) {
667  0 String propertyName = (String) key;
668  0 BaseProperty newProperty = (BaseProperty) this.getFields().get(propertyName);
669  0 BaseProperty oldProperty = (BaseProperty) oldCollection.getFields().get(propertyName);
670  0 BaseClass bclass = getXClass(context);
671  0 PropertyClass pclass = (PropertyClass) ((bclass == null) ? null : bclass.getField(propertyName));
672  0 String propertyType = (pclass == null) ? "" : pclass.getClassType();
673   
674  0 if (oldProperty == null) {
675    // The property exist in the new object, but not in the old one
676  0 if ((newProperty != null) && (!newProperty.toText().equals(""))) {
677  0 if (pclass != null) {
678  0 String newPropertyValue = (newProperty.getValue() instanceof String) ? newProperty.toText()
679    : pclass.displayView(propertyName, this, context);
680  0 difflist.add(new ObjectDiff(getXClassReference(), getNumber(), "",
681    ObjectDiff.ACTION_PROPERTYADDED, propertyName, propertyType, "", newPropertyValue));
682    }
683    }
684  0 } else if (!oldProperty.toText().equals(((newProperty == null) ? "" : newProperty.toText()))) {
685    // The property exists in both objects and is different
686  0 if (pclass != null) {
687    // Put the values as they would be displayed in the interface
688  0 String newPropertyValue = (newProperty.getValue() instanceof String) ? newProperty.toText()
689    : pclass.displayView(propertyName, this, context);
690  0 String oldPropertyValue = (oldProperty.getValue() instanceof String) ? oldProperty.toText()
691    : pclass.displayView(propertyName, oldCollection, context);
692  0 difflist
693    .add(new ObjectDiff(getXClassReference(), getNumber(), "", ObjectDiff.ACTION_PROPERTYCHANGED,
694    propertyName, propertyType, oldPropertyValue, newPropertyValue));
695    } else {
696    // Cannot get property definition, so use the plain value
697  0 difflist
698    .add(new ObjectDiff(getXClassReference(), getNumber(), "", ObjectDiff.ACTION_PROPERTYCHANGED,
699    propertyName, propertyType, oldProperty.toText(), newProperty.toText()));
700    }
701    }
702    }
703   
704    // Iterate over the old properties, in case there are some removed properties
705  0 for (Object key : oldCollection.getFields().keySet()) {
706  0 String propertyName = (String) key;
707  0 BaseProperty newProperty = (BaseProperty) this.getFields().get(propertyName);
708  0 BaseProperty oldProperty = (BaseProperty) oldCollection.getFields().get(propertyName);
709  0 BaseClass bclass = getXClass(context);
710  0 PropertyClass pclass = (PropertyClass) ((bclass == null) ? null : bclass.getField(propertyName));
711  0 String propertyType = (pclass == null) ? "" : pclass.getClassType();
712   
713  0 if (newProperty == null) {
714    // The property exists in the old object, but not in the new one
715  0 if ((oldProperty != null) && (!oldProperty.toText().equals(""))) {
716  0 if (pclass != null) {
717    // Put the values as they would be displayed in the interface
718  0 String oldPropertyValue = (oldProperty.getValue() instanceof String) ? oldProperty.toText()
719    : pclass.displayView(propertyName, oldCollection, context);
720  0 difflist.add(new ObjectDiff(oldCollection.getXClassReference(), oldCollection.getNumber(), "",
721    ObjectDiff.ACTION_PROPERTYREMOVED, propertyName, propertyType, oldPropertyValue, ""));
722    } else {
723    // Cannot get property definition, so use the plain value
724  0 difflist.add(new ObjectDiff(oldCollection.getXClassReference(), oldCollection.getNumber(), "",
725    ObjectDiff.ACTION_PROPERTYREMOVED, propertyName, propertyType, oldProperty.toText(), ""));
726    }
727    }
728    }
729    }
730   
731  0 return difflist;
732    }
733   
 
734  10587 toggle public List getFieldsToRemove()
735    {
736  10587 return this.fieldsToRemove;
737    }
738   
 
739  21 toggle public void setFieldsToRemove(List fieldsToRemove)
740    {
741  21 this.fieldsToRemove = fieldsToRemove;
742    }
743   
 
744  0 toggle @Override
745    public Element toXML()
746    {
747  0 return super.toXML();
748    }
749   
750    /**
751    * @deprecated since 9.0RC1, use {@link #toXML()} instead
752    */
 
753  0 toggle @Override
754    @Deprecated
755    public Element toXML(BaseClass bclass)
756    {
757    // Set passed class in the context so that the input event generator finds it
758  0 XWikiContext xcontext = getXWikiContext();
759   
760  0 BaseClass currentBaseClass;
761  0 DocumentReference classReference;
762  0 if (bclass != null && xcontext != null) {
763  0 classReference = bclass.getDocumentReference();
764  0 currentBaseClass = xcontext.getBaseClass(bclass.getDocumentReference());
765  0 xcontext.addBaseClass(bclass);
766    } else {
767  0 classReference = null;
768  0 currentBaseClass = null;
769    }
770   
771  0 try {
772  0 return super.toXML();
773    } finally {
774  0 if (classReference != null) {
775  0 if (currentBaseClass != null) {
776  0 xcontext.addBaseClass(currentBaseClass);
777    } else {
778  0 xcontext.removeBaseClass(classReference);
779    }
780    }
781    }
782    }
783   
784    /**
785    * Return a XML version of this collection.
786    * <p>
787    * The XML is not formated. to get formatted XML you can use {@link #toXMLString(boolean)} instead.
788    *
789    * @return the XML as a String
790    */
 
791  0 toggle public String toXMLString()
792    {
793  0 return toXMLString(true);
794    }
795   
796    /**
797    * @since 2.4M2
798    */
 
799  143 toggle public Map<String, Object> getCustomMappingMap() throws XWikiException
800    {
801  143 Map<String, Object> map = new HashMap<String, Object>();
802  143 for (String name : this.fields.keySet()) {
803  8675 BaseProperty property = (BaseProperty) get(name);
804  8675 map.put(name, property.getCustomMappingValue());
805    }
806  143 map.put("id", getId());
807   
808  143 return map;
809    }
810   
 
811  3076622 toggle @Override
812    public void setDocumentReference(DocumentReference reference)
813    {
814  3076646 super.setDocumentReference(reference);
815   
816    // We force to refresh the XClass reference so that next time it's retrieved again it'll be resolved against
817    // the new document reference.
818  3076611 this.xClassReferenceCache = null;
819    }
820   
 
821  32 toggle @Override
822    public void merge(ElementInterface previousElement, ElementInterface newElement, MergeConfiguration configuration,
823    XWikiContext context, MergeResult mergeResult)
824    {
825  32 BaseCollection<R> previousCollection = (BaseCollection<R>) previousElement;
826  32 BaseCollection<R> newCollection = (BaseCollection<R>) newElement;
827   
828  32 List<ObjectDiff> classDiff = newCollection.getDiff(previousCollection, context);
829  32 for (ObjectDiff diff : classDiff) {
830  1 PropertyInterface propertyResult = getField(diff.getPropName());
831  1 PropertyInterface previousProperty = previousCollection.getField(diff.getPropName());
832  1 PropertyInterface newProperty = newCollection.getField(diff.getPropName());
833   
834  1 if (diff.getAction() == ObjectDiff.ACTION_PROPERTYADDED) {
835  0 if (propertyResult == null) {
836    // Add if none has been added by user already
837  0 safeput(diff.getPropName(),
838  0 configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
839  0 mergeResult.setModified(true);
840  0 } else if (!propertyResult.equals(newProperty)) {
841    // collision between DB and new: property to add but already exists in the DB
842  0 mergeResult.getLog().error("Collision found on property [{}]", newProperty.getReference());
843    }
844  1 } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYREMOVED) {
845  0 if (propertyResult != null) {
846  0 if (propertyResult.equals(previousProperty)) {
847    // Delete if it's the same as previous one
848  0 removeField(diff.getPropName());
849  0 mergeResult.setModified(true);
850    } else {
851    // collision between DB and new: property to remove but not the same as previous
852    // version
853  0 mergeResult.getLog().error("Collision found on property [{}]", previousProperty.getReference());
854    }
855    } else {
856    // Already removed from DB, lets assume the user is prescient
857  0 mergeResult.getLog().warn("Property [{}] already removed", previousProperty.getReference());
858    }
859  1 } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYCHANGED) {
860  1 if (propertyResult != null) {
861  1 if (propertyResult.equals(previousProperty)) {
862    // Let some automatic migration take care of that modification between DB and new
863  1 safeput(diff.getPropName(),
864  1 configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
865  1 mergeResult.setModified(true);
866  0 } else if (!propertyResult.equals(newProperty)) {
867    // Try to apply 3 ways merge on the property
868  0 mergeField(propertyResult, previousProperty, newProperty, configuration, context, mergeResult);
869    }
870    } else {
871    // collision between DB and new: property to modify but does not exists in DB
872    // Lets assume it's a mistake to fix
873  0 mergeResult.getLog().warn("Collision found on property [{}]", newProperty.getReference());
874   
875  0 safeput(diff.getPropName(),
876  0 configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
877  0 mergeResult.setModified(true);
878    }
879    }
880    }
881    }
882   
 
883  0 toggle protected void mergeField(PropertyInterface currentElement, ElementInterface previousElement,
884    ElementInterface newElement, MergeConfiguration configuration, XWikiContext context, MergeResult mergeResult)
885    {
886  0 currentElement.merge(previousElement, newElement, configuration, context, mergeResult);
887    }
888   
 
889  32807 toggle @Override
890    public boolean apply(ElementInterface newElement, boolean clean)
891    {
892  32807 boolean modified = false;
893   
894  32807 BaseCollection<R> newCollection = (BaseCollection<R>) newElement;
895   
896  32807 if (clean) {
897    // Delete fields that don't exist anymore
898  2013 List<String> fieldsToDelete = new ArrayList<String>(this.fields.size());
899  2013 for (String key : this.fields.keySet()) {
900  10670 if (newCollection.safeget(key) == null) {
901  1 fieldsToDelete.add(key);
902    }
903    }
904   
905  2013 for (String key : fieldsToDelete) {
906  1 removeField(key);
907  1 modified = true;
908    }
909    }
910   
911    // Add new fields and update existing fields
912  32807 for (Map.Entry<String, Object> entry : newCollection.fields.entrySet()) {
913  265460 PropertyInterface field = (PropertyInterface) this.fields.get(entry.getKey());
914  265460 PropertyInterface newField = (PropertyInterface) entry.getValue();
915   
916  265460 if (field == null) {
917    // If the field does not exist add it
918  8284 safeput(entry.getKey(), newField);
919  8284 modified = true;
920  257176 } else if (field.getClass() != newField.getClass()) {
921    // If the field is of different type, remove it first
922  45 removeField(entry.getKey());
923  45 safeput(entry.getKey(), newField);
924  45 modified = true;
925    } else {
926    // Otherwise try to merge the fields
927  257131 modified |= field.apply(newField, clean);
928    }
929    }
930   
931  32807 return modified;
932    }
933   
934    /**
935    * Set the owner document of this base object.
936    *
937    * @param ownerDocument The owner document.
938    * @since 5.3M1
939    */
&