1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.objects

File BaseCollection.java

 

Coverage histogram

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

Code metrics

144
310
60
1
936
662
145
0.47
5.17
60
2.42

Classes

Class Line # Actions
BaseCollection 64 310 0% 145 223
0.5661478656.6%
 

Contributing tests

This file is covered by 221 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: 2ce1523dd680a72fcb5fd00356358b6c65073905 $
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  4927 toggle protected EntityReferenceResolver<String> getRelativeEntityReferenceResolver()
118    {
119  4927 if (this.relativeEntityReferenceResolver == null) {
120  4928 this.relativeEntityReferenceResolver = Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "relative");
121    }
122   
123  4927 return this.relativeEntityReferenceResolver;
124    }
125   
126    /**
127    * @return the component used to normalize references
128    */
 
129  86265 toggle protected DocumentReferenceResolver<EntityReference> getCurrentReferenceDocumentReferenceResolver()
130    {
131  86268 if (this.currentReferenceDocumentReferenceResolver == null) {
132  14542 this.currentReferenceDocumentReferenceResolver =
133    Utils.getComponent(DocumentReferenceResolver.TYPE_REFERENCE, "current");
134    }
135   
136  86271 return this.currentReferenceDocumentReferenceResolver;
137    }
138   
 
139  112593 toggle public int getNumber()
140    {
141  112592 return this.number;
142    }
143   
 
144  106279 toggle public void setNumber(int number)
145    {
146  106276 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  0 toggle public void addPropertyForRemoval(PropertyInterface field)
157    {
158  0 getFieldsToRemove().add(field);
159    }
160   
161    /**
162    * Get the absolute reference of the XClass.
163    *
164    * @since 2.2M2
165    */
 
166  256052 toggle public DocumentReference getXClassReference()
167    {
168  256041 if (this.xClassReferenceCache == null && getRelativeXClassReference() != null) {
169  86268 this.xClassReferenceCache = getCurrentReferenceDocumentReferenceResolver()
170    .resolve(getRelativeXClassReference(), getDocumentReference());
171    }
172   
173  256053 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  331570 toggle public EntityReference getRelativeXClassReference()
182    {
183  331570 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  17260 toggle @Deprecated
192    public String getClassName()
193    {
194  17260 String xClassAsString;
195  17260 if (getXClassReference() != null) {
196  17260 xClassAsString = getLocalEntityReferenceSerializer().serialize(getXClassReference());
197    } else {
198  0 xClassAsString = "";
199    }
200  17260 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  155424 toggle public void setXClassReference(EntityReference xClassReference)
213    {
214    // Ensure that the reference to the XClass is always relative to the document wiki.
215  155426 EntityReference ref = xClassReference;
216   
217  155426 if (ref != null) {
218  52627 EntityReference wiki = xClassReference.extractReference(EntityType.WIKI);
219  52627 if (wiki != null) {
220  6709 ref = xClassReference.removeParent(wiki);
221    }
222    }
223   
224  155426 this.xClassReference = ref;
225  155425 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  4907 toggle @Deprecated
234    public void setClassName(String name)
235    {
236  4906 EntityReference xClassReference = null;
237  4906 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  4907 if (!"internal".equals(name)) {
242  4907 xClassReference = getRelativeEntityReferenceResolver().resolve(name, EntityType.DOCUMENT);
243    }
244    }
245  4906 setXClassReference(xClassReference);
246    }
247   
 
248  2293988 toggle @Override
249    public PropertyInterface safeget(String name)
250    {
251  2294003 return (PropertyInterface) getFields().get(name);
252    }
253   
 
254  12069 toggle @Override
255    public PropertyInterface get(String name) throws XWikiException
256    {
257  12066 return safeget(name);
258    }
259   
 
260  1077800 toggle @Override
261    public void safeput(String name, PropertyInterface property)
262    {
263  1077816 addField(name, property);
264  1077942 if (property instanceof BaseProperty) {
265  1019575 ((BaseProperty) property).setObject(this);
266  1019569 ((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  39604 toggle @Override
280    public BaseClass getXClass(XWikiContext context)
281    {
282  39603 BaseClass baseClass = null;
283   
284  39603 if ((context == null) || (context.getWiki() == null)) {
285  1 return baseClass;
286    }
287   
288  39604 DocumentReference classReference = getXClassReference();
289   
290  39603 if (classReference != null) {
291  39549 try {
292  39549 baseClass = context.getWiki().getXClass(classReference, context);
293    } catch (Exception e) {
294  0 LOGGER.error("Failed to get class [" + classReference + "]", e);
295    }
296    }
297   
298  39604 return baseClass;
299    }
300   
 
301  1206278 toggle public String getStringValue(String name)
302    {
303  1206287 BaseProperty prop = (BaseProperty) safeget(name);
304  1206327 if (prop == null || prop.getValue() == null) {
305  10427 return "";
306    } else {
307  1195905 return prop.getValue().toString();
308    }
309    }
310   
 
311  637 toggle public String getLargeStringValue(String name)
312    {
313  637 return getStringValue(name);
314    }
315   
 
316  343255 toggle public void setStringValue(String name, String value)
317    {
318  343258 BaseStringProperty property = (BaseStringProperty) safeget(name);
319  343276 if (property == null) {
320  178169 property = new StringProperty();
321    }
322  343261 property.setName(name);
323  343250 property.setValue(value);
324  343258 safeput(name, property);
325    }
326   
 
327  226 toggle public void setLargeStringValue(String name, String value)
328    {
329  226 BaseStringProperty property = (BaseStringProperty) safeget(name);
330  226 if (property == null) {
331  224 property = new LargeStringProperty();
332    }
333  226 property.setName(name);
334  226 property.setValue(value);
335  226 safeput(name, property);
336    }
337   
 
338  329673 toggle public int getIntValue(String name)
339    {
340  329675 return getIntValue(name, 0);
341    }
342   
 
343  335263 toggle public int getIntValue(String name, int default_value)
344    {
345  335267 try {
346  335273 NumberProperty prop = (NumberProperty) safeget(name);
347  335272 if (prop == null) {
348  22422 return default_value;
349    } else {
350  312850 return ((Number) prop.getValue()).intValue();
351    }
352    } catch (Exception e) {
353  24 return default_value;
354    }
355    }
356   
 
357  346309 toggle public void setIntValue(String name, int value)
358    {
359  346309 NumberProperty property = new IntegerProperty();
360  346308 property.setName(name);
361  346308 property.setValue(value);
362  346305 safeput(name, property);
363    }
364   
 
365  0 toggle public long getLongValue(String name)
366    {
367  0 try {
368  0 NumberProperty prop = (NumberProperty) safeget(name);
369  0 if (prop == null) {
370  0 return 0;
371    } else {
372  0 return ((Number) prop.getValue()).longValue();
373    }
374    } catch (Exception e) {
375  0 return 0;
376    }
377    }
378   
 
379  1 toggle public void setLongValue(String name, long value)
380    {
381  1 NumberProperty property = new LongProperty();
382  1 property.setName(name);
383  1 property.setValue(value);
384  1 safeput(name, property);
385    }
386   
 
387  0 toggle public float getFloatValue(String name)
388    {
389  0 try {
390  0 NumberProperty prop = (NumberProperty) safeget(name);
391  0 if (prop == null) {
392  0 return 0;
393    } else {
394  0 return ((Number) prop.getValue()).floatValue();
395    }
396    } catch (Exception e) {
397  0 return 0;
398    }
399    }
400   
 
401  0 toggle public void setFloatValue(String name, float value)
402    {
403  0 NumberProperty property = new FloatProperty();
404  0 property.setName(name);
405  0 property.setValue(new Float(value));
406  0 safeput(name, property);
407    }
408   
 
409  0 toggle public double getDoubleValue(String name)
410    {
411  0 try {
412  0 NumberProperty prop = (NumberProperty) safeget(name);
413  0 if (prop == null) {
414  0 return 0;
415    } else {
416  0 return ((Number) prop.getValue()).doubleValue();
417    }
418    } catch (Exception e) {
419  0 return 0;
420    }
421    }
422   
 
423  0 toggle public void setDoubleValue(String name, double value)
424    {
425  0 NumberProperty property = new DoubleProperty();
426  0 property.setName(name);
427  0 property.setValue(new Double(value));
428  0 safeput(name, property);
429    }
430   
 
431  4 toggle public Date getDateValue(String name)
432    {
433  4 try {
434  4 DateProperty prop = (DateProperty) safeget(name);
435  4 if (prop == null) {
436  3 return null;
437    } else {
438  1 return (Date) prop.getValue();
439    }
440    } catch (Exception e) {
441  0 return null;
442    }
443    }
444   
 
445  4 toggle public void setDateValue(String name, Date value)
446    {
447  4 DateProperty property = new DateProperty();
448  4 property.setName(name);
449  4 property.setValue(value);
450  4 safeput(name, property);
451    }
452   
 
453  0 toggle public Set<?> getSetValue(String name)
454    {
455  0 ListProperty prop = (ListProperty) safeget(name);
456  0 if (prop == null) {
457  0 return new HashSet<Object>();
458    } else {
459  0 return new HashSet<Object>((Collection<?>) prop.getValue());
460    }
461    }
462   
 
463  0 toggle public void setSetValue(String name, Set<?> value)
464    {
465  0 ListProperty property = new ListProperty();
466  0 property.setValue(value);
467  0 safeput(name, property);
468    }
469   
 
470  541 toggle public List getListValue(String name)
471    {
472  541 ListProperty prop = (ListProperty) safeget(name);
473  541 if (prop == null) {
474  242 return new ArrayList();
475    } else {
476  299 return (List) prop.getValue();
477    }
478    }
479   
 
480  108 toggle public void setStringListValue(String name, List value)
481    {
482  108 ListProperty property = (ListProperty) safeget(name);
483  108 if (property == null) {
484  108 property = new StringListProperty();
485    }
486  108 property.setValue(value);
487  108 safeput(name, property);
488    }
489   
 
490  20 toggle public void setDBStringListValue(String name, List value)
491    {
492  20 ListProperty property = (ListProperty) safeget(name);
493  20 if (property == null) {
494  9 property = new DBStringListProperty();
495    }
496  20 property.setValue(value);
497  20 safeput(name, property);
498    }
499   
500    // These functions should not be used
501    // but instead our own implementation
 
502  2597091 toggle private Map<String, Object> getFields()
503    {
504  2597094 return this.fields;
505    }
506   
 
507  134144 toggle public void setFields(Map fields)
508    {
509  134139 this.fields = fields;
510    }
511   
 
512  4030704 toggle public PropertyInterface getField(String name)
513    {
514  4030712 return (PropertyInterface) this.fields.get(name);
515    }
516   
 
517  1093403 toggle public void addField(String name, PropertyInterface element)
518    {
519  1093401 this.fields.put(name, element);
520   
521  1093500 if (element instanceof BaseElement) {
522  1093501 ((BaseElement) element).setOwnerDocument(getOwnerDocument());
523    }
524    }
525   
 
526  124 toggle public void removeField(String name)
527    {
528  124 Object field = safeget(name);
529  124 if (field != null) {
530  7 this.fields.remove(name);
531  7 this.fieldsToRemove.add(field);
532    }
533    }
534   
 
535  33127 toggle public Collection getFieldList()
536    {
537  33127 return this.fields.values();
538    }
539   
 
540  662636 toggle public Set<String> getPropertyList()
541    {
542  662635 return this.fields.keySet();
543    }
544   
 
545  86680 toggle public Object[] getProperties()
546    {
547  86680 return getFields().values().toArray();
548    }
549   
 
550  138 toggle public String[] getPropertyNames()
551    {
552  138 return getFields().keySet().toArray(new String[0]);
553    }
554   
555    /**
556    * Return an iterator that will operate on a collection of values (as would be returned by getProperties or
557    * getFieldList) sorted by their name (ElementInterface.getName()).
558    */
 
559  86531 toggle public Iterator getSortedIterator()
560    {
561  86531 Iterator it = null;
562  86531 try {
563    // Use getProperties to get the values in list form (rather than as generic collection)
564  86531 List propList = Arrays.asList(getProperties());
565   
566    // Use the element comparator to sort the properties by name (based on ElementInterface)
567  86531 Collections.sort(propList, new ElementComparator());
568   
569    // Iterate over the sorted property list
570  86531 it = propList.iterator();
571    } catch (ClassCastException ccex) {
572    // If sorting by the comparator resulted in a ClassCastException (possible),
573    // iterate over the generic collection of values.
574  0 it = getFieldList().iterator();
575    }
576   
577  86531 return it;
578    }
579   
 
580  7813 toggle @Override
581    public boolean equals(Object coll)
582    {
583    // Same Java object, they sure are equal
584  7813 if (this == coll) {
585  0 return true;
586    }
587   
588  7813 if (!super.equals(coll)) {
589  25 return false;
590    }
591  7788 BaseCollection collection = (BaseCollection) coll;
592  7788 if (collection.getXClassReference() == null) {
593  7761 if (getXClassReference() != null) {
594  0 return false;
595    }
596  27 } else if (!collection.getXClassReference().equals(getXClassReference())) {
597  0 return false;
598    }
599   
600  7788 if (getFields().size() != collection.getFields().size()) {
601  4 return false;
602    }
603   
604  7784 for (Map.Entry<String, Object> entry : getFields().entrySet()) {
605  58917 Object prop = entry.getValue();
606  58917 Object prop2 = collection.getFields().get(entry.getKey());
607  58917 if (!prop.equals(prop2)) {
608  2 return false;
609    }
610    }
611   
612  7782 return true;
613    }
614   
 
615  134134 toggle @Override
616    public BaseCollection clone()
617    {
618  134135 BaseCollection collection = (BaseCollection) super.clone();
619  134139 collection.setXClassReference(getRelativeXClassReference());
620  134140 collection.setNumber(getNumber());
621  134139 Map fields = getFields();
622  134140 Map cfields = new HashMap();
623  134137 for (Object objEntry : fields.entrySet()) {
624  873207 Map.Entry entry = (Map.Entry) objEntry;
625  873217 PropertyInterface prop = (PropertyInterface) ((BaseElement) entry.getValue()).clone();
626  873214 prop.setObject(collection);
627  873198 cfields.put(entry.getKey(), prop);
628    }
629  134140 collection.setFields(cfields);
630   
631  134140 return collection;
632    }
633   
 
634  0 toggle public void merge(BaseObject object)
635    {
636  0 Iterator itfields = object.getPropertyList().iterator();
637  0 while (itfields.hasNext()) {
638  0 String name = (String) itfields.next();
639  0 if (safeget(name) == null) {
640  0 safeput(name, (PropertyInterface) ((BaseElement) object.safeget(name)).clone());
641    }
642    }
643    }
644   
 
645  0 toggle public List<ObjectDiff> getDiff(Object oldObject, XWikiContext context)
646    {
647  0 ArrayList<ObjectDiff> difflist = new ArrayList<ObjectDiff>();
648  0 BaseCollection oldCollection = (BaseCollection) oldObject;
649    // Iterate over the new properties first, to handle changed and added objects
650  0 for (Object key : this.getFields().keySet()) {
651  0 String propertyName = (String) key;
652  0 BaseProperty newProperty = (BaseProperty) this.getFields().get(propertyName);
653  0 BaseProperty oldProperty = (BaseProperty) oldCollection.getFields().get(propertyName);
654  0 BaseClass bclass = getXClass(context);
655  0 PropertyClass pclass = (PropertyClass) ((bclass == null) ? null : bclass.getField(propertyName));
656  0 String propertyType = (pclass == null) ? "" : pclass.getClassType();
657   
658  0 if (oldProperty == null) {
659    // The property exist in the new object, but not in the old one
660  0 if ((newProperty != null) && (!newProperty.toText().equals(""))) {
661  0 if (pclass != null) {
662  0 String newPropertyValue = (newProperty.getValue() instanceof String) ? newProperty.toText()
663    : pclass.displayView(propertyName, this, context);
664  0 difflist.add(new ObjectDiff(getXClassReference(), getNumber(), "",
665    ObjectDiff.ACTION_PROPERTYADDED, propertyName, propertyType, "", newPropertyValue));
666    }
667    }
668  0 } else if (!oldProperty.toText().equals(((newProperty == null) ? "" : newProperty.toText()))) {
669    // The property exists in both objects and is different
670  0 if (pclass != null) {
671    // Put the values as they would be displayed in the interface
672  0 String newPropertyValue = (newProperty.getValue() instanceof String) ? newProperty.toText()
673    : pclass.displayView(propertyName, this, context);
674  0 String oldPropertyValue = (oldProperty.getValue() instanceof String) ? oldProperty.toText()
675    : pclass.displayView(propertyName, oldCollection, context);
676  0 difflist
677    .add(new ObjectDiff(getXClassReference(), getNumber(), "", ObjectDiff.ACTION_PROPERTYCHANGED,
678    propertyName, propertyType, oldPropertyValue, newPropertyValue));
679    } else {
680    // Cannot get property definition, so use the plain value
681  0 difflist
682    .add(new ObjectDiff(getXClassReference(), getNumber(), "", ObjectDiff.ACTION_PROPERTYCHANGED,
683    propertyName, propertyType, oldProperty.toText(), newProperty.toText()));
684    }
685    }
686    }
687   
688    // Iterate over the old properties, in case there are some removed properties
689  0 for (Object key : oldCollection.getFields().keySet()) {
690  0 String propertyName = (String) key;
691  0 BaseProperty newProperty = (BaseProperty) this.getFields().get(propertyName);
692  0 BaseProperty oldProperty = (BaseProperty) oldCollection.getFields().get(propertyName);
693  0 BaseClass bclass = getXClass(context);
694  0 PropertyClass pclass = (PropertyClass) ((bclass == null) ? null : bclass.getField(propertyName));
695  0 String propertyType = (pclass == null) ? "" : pclass.getClassType();
696   
697  0 if (newProperty == null) {
698    // The property exists in the old object, but not in the new one
699  0 if ((oldProperty != null) && (!oldProperty.toText().equals(""))) {
700  0 if (pclass != null) {
701    // Put the values as they would be displayed in the interface
702  0 String oldPropertyValue = (oldProperty.getValue() instanceof String) ? oldProperty.toText()
703    : pclass.displayView(propertyName, oldCollection, context);
704  0 difflist.add(new ObjectDiff(oldCollection.getXClassReference(), oldCollection.getNumber(), "",
705    ObjectDiff.ACTION_PROPERTYREMOVED, propertyName, propertyType, oldPropertyValue, ""));
706    } else {
707    // Cannot get property definition, so use the plain value
708  0 difflist.add(new ObjectDiff(oldCollection.getXClassReference(), oldCollection.getNumber(), "",
709    ObjectDiff.ACTION_PROPERTYREMOVED, propertyName, propertyType, oldProperty.toText(), ""));
710    }
711    }
712    }
713    }
714   
715  0 return difflist;
716    }
717   
 
718  4483 toggle public List getFieldsToRemove()
719    {
720  4483 return this.fieldsToRemove;
721    }
722   
 
723  0 toggle public void setFieldsToRemove(List fieldsToRemove)
724    {
725  0 this.fieldsToRemove = fieldsToRemove;
726    }
727   
 
728  0 toggle @Override
729    public Element toXML()
730    {
731  0 return super.toXML();
732    }
733   
734    /**
735    * @deprecated since 9.0RC1, use {@link #toXML()} instead
736    */
 
737  0 toggle @Override
738    @Deprecated
739    public Element toXML(BaseClass bclass)
740    {
741    // Set passed class in the context so that the input event generator finds it
742  0 XWikiContext xcontext = getXWikiContext();
743   
744  0 BaseClass currentBaseClass;
745  0 DocumentReference classReference;
746  0 if (bclass != null && xcontext != null) {
747  0 classReference = bclass.getDocumentReference();
748  0 currentBaseClass = xcontext.getBaseClass(bclass.getDocumentReference());
749  0 xcontext.addBaseClass(bclass);
750    } else {
751  0 classReference = null;
752  0 currentBaseClass = null;
753    }
754   
755  0 try {
756  0 return super.toXML();
757    } finally {
758  0 if (classReference != null) {
759  0 if (currentBaseClass != null) {
760  0 xcontext.addBaseClass(currentBaseClass);
761    } else {
762  0 xcontext.removeBaseClass(classReference);
763    }
764    }
765    }
766    }
767   
768    /**
769    * Return a XML version of this collection.
770    * <p>
771    * The XML is not formated. to get formatted XML you can use {@link #toXMLString(boolean)} instead.
772    *
773    * @return the XML as a String
774    */
 
775  0 toggle public String toXMLString()
776    {
777  0 return toXMLString(true);
778    }
779   
780    /**
781    * @since 2.4M2
782    */
 
783  40 toggle public Map<String, Object> getCustomMappingMap() throws XWikiException
784    {
785  40 Map<String, Object> map = new HashMap<String, Object>();
786  40 for (String name : this.fields.keySet()) {
787  394 BaseProperty property = (BaseProperty) get(name);
788  394 map.put(name, property.getCustomMappingValue());
789    }
790  40 map.put("id", getId());
791   
792  40 return map;
793    }
794   
 
795  263591 toggle @Override
796    public void setDocumentReference(DocumentReference reference)
797    {
798  263594 super.setDocumentReference(reference);
799   
800    // We force to refresh the XClass reference so that next time it's retrieved again it'll be resolved against
801    // the new document reference.
802  263581 this.xClassReferenceCache = null;
803    }
804   
 
805  55 toggle @Override
806    public void merge(ElementInterface previousElement, ElementInterface newElement, MergeConfiguration configuration,
807    XWikiContext context, MergeResult mergeResult)
808    {
809  55 BaseCollection<R> previousCollection = (BaseCollection<R>) previousElement;
810  55 BaseCollection<R> newCollection = (BaseCollection<R>) newElement;
811   
812  55 List<ObjectDiff> classDiff = newCollection.getDiff(previousCollection, context);
813  55 for (ObjectDiff diff : classDiff) {
814  1 PropertyInterface propertyResult = getField(diff.getPropName());
815  1 PropertyInterface previousProperty = previousCollection.getField(diff.getPropName());
816  1 PropertyInterface newProperty = newCollection.getField(diff.getPropName());
817   
818  1 if (diff.getAction() == ObjectDiff.ACTION_PROPERTYADDED) {
819  0 if (propertyResult == null) {
820    // Add if none has been added by user already
821  0 safeput(diff.getPropName(),
822  0 configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
823  0 mergeResult.setModified(true);
824  0 } else if (!propertyResult.equals(newProperty)) {
825    // collision between DB and new: property to add but already exists in the DB
826  0 mergeResult.getLog().error("Collision found on property [{}]", newProperty.getReference());
827    }
828  1 } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYREMOVED) {
829  0 if (propertyResult != null) {
830  0 if (propertyResult.equals(previousProperty)) {
831    // Delete if it's the same as previous one
832  0 removeField(diff.getPropName());
833  0 mergeResult.setModified(true);
834    } else {
835    // collision between DB and new: property to remove but not the same as previous
836    // version
837  0 mergeResult.getLog().error("Collision found on property [{}]", previousProperty.getReference());
838    }
839    } else {
840    // Already removed from DB, lets assume the user is prescient
841  0 mergeResult.getLog().warn("Property [{}] already removed", previousProperty.getReference());
842    }
843  1 } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYCHANGED) {
844  1 if (propertyResult != null) {
845  1 if (propertyResult.equals(previousProperty)) {
846    // Let some automatic migration take care of that modification between DB and new
847  1 safeput(diff.getPropName(),
848  1 configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
849  1 mergeResult.setModified(true);
850  0 } else if (!propertyResult.equals(newProperty)) {
851    // Try to apply 3 ways merge on the property
852  0 mergeField(propertyResult, previousProperty, newProperty, configuration, context, mergeResult);
853    }
854    } else {
855    // collision between DB and new: property to modify but does not exists in DB
856    // Lets assume it's a mistake to fix
857  0 mergeResult.getLog().warn("Collision found on property [{}]", newProperty.getReference());
858   
859  0 safeput(diff.getPropName(),
860  0 configuration.isProvidedVersionsModifiables() ? newProperty : newProperty.clone());
861  0 mergeResult.setModified(true);
862    }
863    }
864    }
865    }
866   
 
867  0 toggle protected void mergeField(PropertyInterface currentElement, ElementInterface previousElement,
868    ElementInterface newElement, MergeConfiguration configuration, XWikiContext context, MergeResult mergeResult)
869    {
870  0 currentElement.merge(previousElement, newElement, configuration, context, mergeResult);
871    }
872   
 
873  12 toggle @Override
874    public boolean apply(ElementInterface newElement, boolean clean)
875    {
876  12 boolean modified = false;
877   
878  12 BaseCollection<R> newCollection = (BaseCollection<R>) newElement;
879   
880  12 if (clean) {
881    // Delete fields that don't exist anymore
882  12 List<String> fieldsToDelete = new ArrayList<String>(this.fields.size());
883  12 for (String key : this.fields.keySet()) {
884  35 if (newCollection.safeget(key) == null) {
885  0 fieldsToDelete.add(key);
886    }
887    }
888   
889  12 for (String key : fieldsToDelete) {
890  0 removeField(key);
891  0 modified = true;
892    }
893    }
894   
895    // Add new fields and update existing fields
896  12 for (Map.Entry<String, Object> entry : newCollection.fields.entrySet()) {
897  37 PropertyInterface field = (PropertyInterface) this.fields.get(entry.getKey());
898  37 PropertyInterface newField = (PropertyInterface) entry.getValue();
899   
900  37 if (field == null) {
901    // If the field does not exist add it
902  2 safeput(entry.getKey(), newField);
903  2 modified = true;
904  35 } else if (field.getClass() != newField.getClass()) {
905    // If the field is of different type, remove it first
906  0 removeField(entry.getKey());
907  0 safeput(entry.getKey(), newField);
908  0 modified = true;
909    } else {
910    // Otherwise try to merge the fields
911  35 modified |= field.apply(newField, clean);
912    }
913    }
914   
915  12 return modified;
916    }
917   
918    /**
919    * Set the owner document of this base object.
920    *
921    * @param ownerDocument The owner document.
922    * @since 5.3M1
923    */
 
924  595985 toggle @Override
925    public void setOwnerDocument(XWikiDocument ownerDocument)
926    {
927  595986 super.setOwnerDocument(ownerDocument);
928   
929  595982 for (String propertyName : getPropertyList()) {
930  3988842 PropertyInterface property = getField(propertyName);
931  3988773 if (property instanceof BaseElement) {
932  3988838 ((BaseElement) property).setOwnerDocument(ownerDocument);
933    }
934    }
935    }
936    }