001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: ChunkedIntArray.java 1225427 2011-12-29 04:33:32Z mrglavas $
020 */
021 package org.apache.xml.dtm.ref;
022
023 import org.apache.xml.res.XMLErrorResources;
024 import org.apache.xml.res.XMLMessages;
025
026 /**
027 * <code>ChunkedIntArray</code> is an extensible array of blocks of integers.
028 * (I'd consider Vector, but it's unable to handle integers except by
029 * turning them into Objects.)
030
031 * <p>Making this a separate class means some call-and-return overhead. But
032 * doing it all inline tends to be fragile and expensive in coder time,
033 * not to mention driving up code size. If you want to inline it, feel free.
034 * The Java text suggest that private and Final methods may be inlined,
035 * and one can argue that this beast need not be made subclassable...</p>
036 *
037 * <p>%REVIEW% This has strong conceptual overlap with the IntVector class.
038 * It would probably be a good thing to merge the two, when time permits.<p>
039 */
040 final class ChunkedIntArray
041 {
042 static final int slotsize=4; // Locked, MUST be power of two in current code
043 // Debugging tip: Cranking lowbits down to 4 or so is a good
044 // way to pound on the array addressing code.
045 static final int lowbits=10; // How many bits address within chunks
046 static final int chunkalloc=1<<lowbits;
047 static final int lowmask=chunkalloc-1;
048
049 ChunksVector chunks=new ChunksVector();
050 final int fastArray[] = new int[chunkalloc];
051 int lastUsed=0;
052
053 /**
054 * Create a new CIA with specified record size. Currently record size MUST
055 * be a power of two... and in fact is hardcoded to 4.
056 */
057 ChunkedIntArray(int slotsize)
058 {
059 if(this.slotsize<slotsize)
060 throw new ArrayIndexOutOfBoundsException(XMLMessages.createXMLMessage(XMLErrorResources.ER_CHUNKEDINTARRAY_NOT_SUPPORTED, new Object[]{Integer.toString(slotsize)})); //"ChunkedIntArray("+slotsize+") not currently supported");
061 else if (this.slotsize>slotsize)
062 System.out.println("*****WARNING: ChunkedIntArray("+slotsize+") wasting "+(this.slotsize-slotsize)+" words per slot");
063 chunks.addElement(fastArray);
064 }
065 /**
066 * Append a 4-integer record to the CIA, starting with record 1. (Since
067 * arrays are initialized to all-0, 0 has been reserved as the "unknown"
068 * value in DTM.)
069 * @return the index at which this record was inserted.
070 */
071 int appendSlot(int w0, int w1, int w2, int w3)
072 {
073 /*
074 try
075 {
076 int newoffset = (lastUsed+1)*slotsize;
077 fastArray[newoffset] = w0;
078 fastArray[newoffset+1] = w1;
079 fastArray[newoffset+2] = w2;
080 fastArray[newoffset+3] = w3;
081 return ++lastUsed;
082 }
083 catch(ArrayIndexOutOfBoundsException aioobe)
084 */
085 {
086 final int slotsize=4;
087 int newoffset = (lastUsed+1)*slotsize;
088 int chunkpos = newoffset >> lowbits;
089 int slotpos = (newoffset & lowmask);
090
091 // Grow if needed
092 if (chunkpos > chunks.size() - 1)
093 chunks.addElement(new int[chunkalloc]);
094 int[] chunk = chunks.elementAt(chunkpos);
095 chunk[slotpos] = w0;
096 chunk[slotpos+1] = w1;
097 chunk[slotpos+2] = w2;
098 chunk[slotpos+3] = w3;
099
100 return ++lastUsed;
101 }
102 }
103 /**
104 * Retrieve an integer from the CIA by record number and column within
105 * the record, both 0-based (though position 0 is reserved for special
106 * purposes).
107 * @param position int Record number
108 * @param slotpos int Column number
109 */
110 int readEntry(int position, int offset) throws ArrayIndexOutOfBoundsException
111 {
112 /*
113 try
114 {
115 return fastArray[(position*slotsize)+offset];
116 }
117 catch(ArrayIndexOutOfBoundsException aioobe)
118 */
119 {
120 // System.out.println("Using slow read (1)");
121 if (offset>=slotsize)
122 throw new ArrayIndexOutOfBoundsException(XMLMessages.createXMLMessage(XMLErrorResources.ER_OFFSET_BIGGER_THAN_SLOT, null)); //"Offset bigger than slot");
123 position*=slotsize;
124 int chunkpos = position >> lowbits;
125 int slotpos = position & lowmask;
126 int[] chunk = chunks.elementAt(chunkpos);
127 return chunk[slotpos + offset];
128 }
129 }
130
131 // Check that the node at index "position" is not an ancestor
132 // of the node at index "startPos". IF IT IS, DO NOT ACCEPT IT AND
133 // RETURN -1. If position is NOT an ancestor, return position.
134 // Special case: The Document node (position==0) is acceptable.
135 //
136 // This test supports DTM.getNextPreceding.
137 int specialFind(int startPos, int position)
138 {
139 // We have to look all the way up the ancestor chain
140 // to make sure we don't have an ancestor.
141 int ancestor = startPos;
142 while(ancestor > 0)
143 {
144 // Get the node whose index == ancestor
145 ancestor*=slotsize;
146 int chunkpos = ancestor >> lowbits;
147 int slotpos = ancestor & lowmask;
148 int[] chunk = chunks.elementAt(chunkpos);
149
150 // Get that node's parent (Note that this assumes w[1]
151 // is the parent node index. That's really a DTM feature
152 // rather than a ChunkedIntArray feature.)
153 ancestor = chunk[slotpos + 1];
154
155 if(ancestor == position)
156 break;
157 }
158
159 if (ancestor <= 0)
160 {
161 return position;
162 }
163 return -1;
164 }
165
166 /**
167 * @return int index of highest-numbered record currently in use
168 */
169 int slotsUsed()
170 {
171 return lastUsed;
172 }
173
174 /** Disard the highest-numbered record. This is used in the string-buffer
175 CIA; when only a single characters() chunk has been recieved, its index
176 is moved into the Text node rather than being referenced by indirection
177 into the text accumulator.
178 */
179 void discardLast()
180 {
181 --lastUsed;
182 }
183
184 /**
185 * Overwrite the integer found at a specific record and column.
186 * Used to back-patch existing records, most often changing their
187 * "next sibling" reference from 0 (unknown) to something meaningful
188 * @param position int Record number
189 * @param offset int Column number
190 * @param value int New contents
191 */
192 void writeEntry(int position, int offset, int value) throws ArrayIndexOutOfBoundsException
193 {
194 /*
195 try
196 {
197 fastArray[( position*slotsize)+offset] = value;
198 }
199 catch(ArrayIndexOutOfBoundsException aioobe)
200 */
201 {
202 if (offset >= slotsize)
203 throw new ArrayIndexOutOfBoundsException(XMLMessages.createXMLMessage(XMLErrorResources.ER_OFFSET_BIGGER_THAN_SLOT, null)); //"Offset bigger than slot");
204 position*=slotsize;
205 int chunkpos = position >> lowbits;
206 int slotpos = position & lowmask;
207 int[] chunk = chunks.elementAt(chunkpos);
208 chunk[slotpos + offset] = value; // ATOMIC!
209 }
210 }
211
212 /**
213 * Overwrite an entire (4-integer) record at the specified index.
214 * Mostly used to create record 0, the Document node.
215 * @param position integer Record number
216 * @param w0 int
217 * @param w1 int
218 * @param w2 int
219 * @param w3 int
220 */
221 void writeSlot(int position, int w0, int w1, int w2, int w3)
222 {
223 position *= slotsize;
224 int chunkpos = position >> lowbits;
225 int slotpos = (position & lowmask);
226
227 // Grow if needed
228 if (chunkpos > chunks.size() - 1)
229 chunks.addElement(new int[chunkalloc]);
230 int[] chunk = chunks.elementAt(chunkpos);
231 chunk[slotpos] = w0;
232 chunk[slotpos + 1] = w1;
233 chunk[slotpos + 2] = w2;
234 chunk[slotpos + 3] = w3;
235 }
236
237 /**
238 * Retrieve the contents of a record into a user-supplied buffer array.
239 * Used to reduce addressing overhead when code will access several
240 * columns of the record.
241 * @param position int Record number
242 * @param buffer int[] Integer array provided by user, must be large enough
243 * to hold a complete record.
244 */
245 void readSlot(int position, int[] buffer)
246 {
247 /*
248 try
249 {
250 System.arraycopy(fastArray, position*slotsize, buffer, 0, slotsize);
251 }
252 catch(ArrayIndexOutOfBoundsException aioobe)
253 */
254 {
255 // System.out.println("Using slow read (2): "+position);
256 position *= slotsize;
257 int chunkpos = position >> lowbits;
258 int slotpos = (position & lowmask);
259
260 // Grow if needed
261 if (chunkpos > chunks.size() - 1)
262 chunks.addElement(new int[chunkalloc]);
263 int[] chunk = chunks.elementAt(chunkpos);
264 System.arraycopy(chunk,slotpos,buffer,0,slotsize);
265 }
266 }
267
268 static class ChunksVector
269 {
270 static final int BLOCKSIZE = 64;
271 int[] m_map[] = new int[BLOCKSIZE][];
272 int m_mapSize = BLOCKSIZE;
273 int pos = 0;
274
275 ChunksVector()
276 {
277 }
278
279 final int size()
280 {
281 return pos;
282 }
283
284 void addElement(int[] value)
285 {
286 if(pos >= m_mapSize)
287 {
288 int orgMapSize = m_mapSize;
289 while(pos >= m_mapSize)
290 m_mapSize+=BLOCKSIZE;
291 int[] newMap[] = new int[m_mapSize][];
292 System.arraycopy(m_map, 0, newMap, 0, orgMapSize);
293 m_map = newMap;
294 }
295 // For now, just do a simple append. A sorted insert only
296 // makes sense if we're doing an binary search or some such.
297 m_map[pos] = value;
298 pos++;
299 }
300
301 final int[] elementAt(int pos)
302 {
303 return m_map[pos];
304 }
305 }
306 }