Skip to content

Commit db3d1c6

Browse files
committed
XWIKI-19223: Improve xobject memory storage in XWikidocument
1 parent 703dc60 commit db3d1c6

File tree

4 files changed

+303
-21
lines changed

4 files changed

+303
-21
lines changed

‎xwiki-platform-core/xwiki-platform-oldcore/pom.xml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,10 @@
607607
<version>1.6</version>
608608
<scope>test</scope>
609609
</dependency>
610+
<dependency>
611+
<groupId>com.google.guava</groupId>
612+
<artifactId>guava-testlib</artifactId>
613+
</dependency>
610614

611615
<dependency>
612616
<groupId>org.xwiki.commons</groupId>

‎xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/doc/XWikiDocument.java‎

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
importjava.util.SortedMap;
5050
importjava.util.TreeMap;
5151
importjava.util.Vector;
52+
importjava.util.concurrent.ConcurrentSkipListMap;
5253
importjava.util.regex.Matcher;
5354
importjava.util.regex.Pattern;
5455
importjava.util.zip.ZipEntry;
@@ -167,6 +168,7 @@
167168
importcom.xpn.xwiki.doc.merge.MergeResult;
168169
importcom.xpn.xwiki.doc.rcs.XWikiRCSNodeInfo;
169170
importcom.xpn.xwiki.internal.cache.rendering.RenderingCache;
171+
importcom.xpn.xwiki.internal.doc.BaseObjects;
170172
importcom.xpn.xwiki.internal.doc.XWikiAttachmentList;
171173
importcom.xpn.xwiki.internal.filter.XWikiDocumentFilterUtils;
172174
importcom.xpn.xwiki.internal.render.OldRendering;
@@ -504,10 +506,10 @@ private static LinkParser getLinkParser(){
504506

505507
/**
506508
* Map holding document objects indexed by XClass references (i.e. Document References since a XClass reference
507-
* points to a document). The map is not synchronized, and uses a TreeMap implementation to preserve index ordering
508-
* (consistent sorted order for output to XML, rendering in velocity, etc.)
509+
* points to a document). The preserve index ordering (consistent sorted order for output to XML, rendering in
510+
* velocity, etc.)
509511
*/
510-
privateMap<DocumentReference, List<BaseObject>>xObjects = newTreeMap<DocumentReference, List<BaseObject>>();
512+
privateMap<DocumentReference, BaseObjects>xObjects = newConcurrentSkipListMap<>();
511513

512514
privatefinalXWikiAttachmentListattachmentList = newXWikiAttachmentList(XWikiDocument.this);
513515

@@ -2544,7 +2546,7 @@ public void setXClass(BaseClass xwikiClass)
25442546
*/
25452547
publicMap<DocumentReference, List<BaseObject>> getXObjects()
25462548
{
2547-
returnthis.xObjects;
2549+
return(Map) this.xObjects;
25482550
}
25492551

25502552
/**
@@ -2572,7 +2574,9 @@ public void setXObjects(Map<DocumentReference, List<BaseObject>> objects)
25722574
}
25732575

25742576
// Replace the current objects with the provided ones.
2575-
this.xObjects = objects;
2577+
Map<DocumentReference, BaseObjects> objectsCopy = newConcurrentSkipListMap<>();
2578+
objects.forEach((k, v) -> objectsCopy.put(k, newBaseObjects(v)));
2579+
this.xObjects = objectsCopy;
25762580
}
25772581

25782582
/**
@@ -2636,9 +2640,9 @@ public int createXObject(EntityReference classReference, XWikiContext context) t
26362640
BaseObjectobject = BaseClass.newCustomClassInstance(absoluteClassReference, context);
26372641
object.setOwnerDocument(this);
26382642
object.setXClassReference(classReference);
2639-
List<BaseObject>objects = this.xObjects.get(absoluteClassReference);
2643+
BaseObjectsobjects = this.xObjects.get(absoluteClassReference);
26402644
if (objects == null){
2641-
objects = newArrayList<BaseObject>();
2645+
objects = newBaseObjects();
26422646
this.xObjects.put(absoluteClassReference, objects);
26432647
}
26442648
objects.add(object);
@@ -2758,16 +2762,7 @@ public void setXObjects(DocumentReference classReference, List<BaseObject> objec
27582762
}
27592763

27602764
// Add new objects
2761-
if (objects.isEmpty()){
2762-
// Pretty wrong but can't remove that for retro compatibility reasons...
2763-
// Note that it means that someone can put an unmodifiable list here make impossible to add any object of
2764-
// this class.
2765-
this.xObjects.put(classReference, objects);
2766-
} else{
2767-
for (BaseObjectbaseObject : objects){
2768-
addXObject(classReference, baseObject);
2769-
}
2770-
}
2765+
this.xObjects.put(classReference, newBaseObjects(objects));
27712766

27722767
setMetaDataDirty(true);
27732768
}
@@ -3062,9 +3057,9 @@ public void setXObject(DocumentReference classReference, int nb, BaseObject obje
30623057
object.setNumber(nb);
30633058
}
30643059

3065-
List<BaseObject>objects = this.xObjects.get(classReference);
3060+
BaseObjectsobjects = this.xObjects.get(classReference);
30663061
if (objects == null){
3067-
objects = newArrayList<BaseObject>();
3062+
objects = newBaseObjects();
30683063
this.xObjects.put(classReference, objects);
30693064
}
30703065
while (nb >= objects.size()){
@@ -3088,9 +3083,9 @@ public void setXObject(int nb, BaseObject object)
30883083
object.setOwnerDocument(this);
30893084
object.setNumber(nb);
30903085

3091-
List<BaseObject>objects = this.xObjects.get(object.getXClassReference());
3086+
BaseObjectsobjects = this.xObjects.get(object.getXClassReference());
30923087
if (objects == null){
3093-
objects = newArrayList<BaseObject>();
3088+
objects = newBaseObjects();
30943089
this.xObjects.put(object.getXClassReference(), objects);
30953090
}
30963091
while (nb >= objects.size()){
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
packagecom.xpn.xwiki.internal.doc;
21+
22+
importjava.util.AbstractList;
23+
importjava.util.Collection;
24+
importjava.util.Map;
25+
importjava.util.concurrent.ConcurrentSkipListMap;
26+
27+
importcom.xpn.xwiki.objects.BaseObject;
28+
29+
/**
30+
* Implement a list of{@link BaseObject} as seen by a document (meaning that the list index matched the object number)
31+
* using a{@link Map} storage to avoid wasting memory with null entries.
32+
*
33+
* @version $Id$
34+
* @since 14.0RC1
35+
*/
36+
// TODO: expose APIs to navigate non null objects directly instead of going through each entry and skipping explicitly
37+
// null ones
38+
publicclassBaseObjectsextendsAbstractList<BaseObject>
39+
{
40+
// Sort keys so that it's possible to navigate non null entries without going through them all
41+
privateMap<Integer, BaseObject> map = newConcurrentSkipListMap<>();
42+
43+
privateintsize;
44+
45+
/**
46+
* Constructs an empty list.
47+
*/
48+
publicBaseObjects()
49+
{
50+
51+
}
52+
53+
/**
54+
* Constructs a list containing the elements of the specified collection, in the order they are returned by the
55+
* collection's iterator.
56+
*
57+
* @param collection the collection to copy
58+
*/
59+
publicBaseObjects(Collection<BaseObject> collection)
60+
{
61+
collection.forEach(this::add);
62+
}
63+
64+
@Override
65+
publicBaseObjectget(intindex)
66+
{
67+
rangeCheck(index);
68+
69+
returnthis.map.get(index);
70+
}
71+
72+
@Override
73+
publicintsize()
74+
{
75+
returnthis.size;
76+
}
77+
78+
privateBaseObjectput(intindex, BaseObjectelement)
79+
{
80+
BaseObjectold;
81+
if (element == null){
82+
// We don't want to keep null values in memory
83+
old = this.map.remove(index);
84+
} else{
85+
// Make sure the object number is right
86+
element.setNumber(index);
87+
88+
old = this.map.put(index, element);
89+
}
90+
91+
// Increment size if needed
92+
if (this.size <= index){
93+
this.size = index + 1;
94+
}
95+
96+
returnold;
97+
}
98+
99+
@Override
100+
publicvoidadd(intindex, BaseObjectelement)
101+
{
102+
// Check if the index is valid
103+
rangeCheckForAdd(index);
104+
105+
// Move right values
106+
if (index < this.size){
107+
for (inti = this.size - 1; i >= index; --i){
108+
put(i + 1, get(i));
109+
}
110+
}
111+
112+
// Insert new value
113+
put(index, element);
114+
}
115+
116+
@Override
117+
publicBaseObjectset(intindex, BaseObjectelement)
118+
{
119+
// Check if the index is valid
120+
rangeCheck(index);
121+
122+
// Set the value and remember the old one
123+
returnput(index, element);
124+
}
125+
126+
@Override
127+
publicBaseObjectremove(intindex)
128+
{
129+
rangeCheck(index);
130+
131+
returnthis.map.remove(index);
132+
}
133+
134+
privatevoidrangeCheck(intindex)
135+
{
136+
if (index < 0 || index >= this.size){
137+
thrownewIndexOutOfBoundsException(outOfBoundsMsg(index));
138+
}
139+
}
140+
141+
privatevoidrangeCheckForAdd(intindex)
142+
{
143+
if (index < 0 || index > this.size || index == Integer.MAX_VALUE){
144+
thrownewIndexOutOfBoundsException(outOfBoundsMsg(index));
145+
}
146+
}
147+
148+
privateStringoutOfBoundsMsg(intindex)
149+
{
150+
return"Index: " + index + ", Size: " + size;
151+
}
152+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
packagecom.xpn.xwiki.internal.doc;
21+
22+
importjava.util.Arrays;
23+
24+
importorg.junit.jupiter.api.Test;
25+
26+
importcom.xpn.xwiki.objects.BaseObject;
27+
28+
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
29+
importstaticorg.junit.jupiter.api.Assertions.assertNull;
30+
importstaticorg.junit.jupiter.api.Assertions.assertSame;
31+
importstaticorg.junit.jupiter.api.Assertions.assertThrows;
32+
33+
/**
34+
* Validate{@link BaseObjects}.
35+
*
36+
* @version $Id$
37+
*/
38+
publicclassBaseObjectsTest
39+
{
40+
privatestaticclassTestBaseObjectextendsBaseObject
41+
{
42+
@Override
43+
publicStringtoString()
44+
{
45+
returnString.valueOf(getNumber());
46+
}
47+
}
48+
49+
privatestaticfinalBaseObjectXOBJ1 = newTestBaseObject();
50+
51+
privatestaticfinalBaseObjectXOBJ2 = newTestBaseObject();
52+
53+
privatestaticfinalBaseObjectXOBJ3 = newTestBaseObject();
54+
55+
privatestaticfinalBaseObjectXOBJ4 = newTestBaseObject();
56+
57+
@Test
58+
voidadd()
59+
{
60+
BaseObjectsobjects = newBaseObjects();
61+
62+
assertEquals(0, objects.size());
63+
64+
objects.add(XOBJ1);
65+
66+
assertEquals(1, objects.size());
67+
68+
objects.add(null);
69+
70+
assertEquals(2, objects.size());
71+
72+
objects.add(XOBJ2);
73+
74+
assertEquals(3, objects.size());
75+
assertSame(XOBJ1, objects.get(0));
76+
assertEquals(0, objects.get(0).getNumber());
77+
assertNull(objects.get(1));
78+
assertSame(XOBJ2, objects.get(2));
79+
assertEquals(2, objects.get(2).getNumber());
80+
81+
objects.add(1, XOBJ3);
82+
83+
assertEquals(4, objects.size());
84+
assertSame(XOBJ1, objects.get(0));
85+
assertEquals(0, objects.get(0).getNumber());
86+
assertSame(XOBJ3, objects.get(1));
87+
assertEquals(1, objects.get(1).getNumber());
88+
assertNull(objects.get(2));
89+
assertSame(XOBJ2, objects.get(3));
90+
assertEquals(3, objects.get(3).getNumber());
91+
92+
assertThrows(IndexOutOfBoundsException.class, () -> objects.add(Integer.MAX_VALUE, XOBJ4));
93+
}
94+
95+
@Test
96+
voidset()
97+
{
98+
BaseObjectsobjects = newBaseObjects();
99+
100+
assertEquals(0, objects.size());
101+
102+
assertThrows(IndexOutOfBoundsException.class, () -> objects.set(0, XOBJ1));
103+
104+
objects.add(XOBJ1);
105+
objects.add(XOBJ2);
106+
objects.add(XOBJ3);
107+
108+
objects.set(1, XOBJ4);
109+
110+
assertSame(XOBJ1, objects.get(0));
111+
assertSame(XOBJ4, objects.get(1));
112+
assertSame(XOBJ3, objects.get(2));
113+
}
114+
115+
@Test
116+
voidremove()
117+
{
118+
BaseObjectsobjects = newBaseObjects(Arrays.asList(XOBJ1, XOBJ2, XOBJ3));
119+
120+
assertEquals(3, objects.size());
121+
122+
objects.remove(0);
123+
objects.remove(2);
124+
125+
assertNull(objects.get(0));
126+
assertSame(XOBJ2, objects.get(1));
127+
assertNull(objects.get(2));
128+
129+
assertEquals(3, objects.size());
130+
}
131+
}

0 commit comments

Comments
(0)