/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.wicket.pageStore; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.apache.wicket.util.collections.IntHashMap; /** * Manages positions and size of serialized pages in the pagemap file. * <p> * The pages are stored inside the file in a cyclic way. Newer pages are placed after older ones, * until the maximum file size is reached. After that, the next page is stored in the beginning of * the file. * * @author Matej Knopp */ public class PageWindowManager implements Serializable { private static final long serialVersionUID = 1L; /** * Contains information about a page inside the file. * * @author Matej Knopp */ private static class PageWindowInternal implements Serializable { private static final long serialVersionUID = 1L; /** id of page or -1 if the window is empty */ private int pageId; /** offset in the file where the serialized page data begins */ private int filePartOffset; /** size of serialized page data */ private int filePartSize; } /** list of PageWindowInternal objects */ private final List<PageWindowInternal> windows = new ArrayList<PageWindowInternal>(); /** * map from page id to list of pagewindow indices (referring to the windows list) - to improve * searching speed the index must be cleaned when the instances in the windows list change their * indexes (e.g. items are shifted on page window removal) */ private IntHashMap<Integer> idToWindowIndex = null; /** * Inversed index of #idToWindowIndex */ private IntHashMap<Integer> windowIndexToPageId = null; /** index of last added page */ private int indexPointer = -1; private int totalSize = 0; /** * Maximum page size. After this size is exceeded, the pages will be saved starting at the * beginning of file. */ private final long maxSize; /** * * @param pageId * @param windowIndex */ private void putWindowIndex(int pageId, int windowIndex) { if (idToWindowIndex != null && pageId != -1 && windowIndex != -1) { Integer oldPageId = windowIndexToPageId.remove(windowIndex); if (oldPageId != null) { idToWindowIndex.remove(oldPageId); } idToWindowIndex.put(pageId, windowIndex); windowIndexToPageId.put(windowIndex, pageId); } } /** * * @param pageId */ private void removeWindowIndex(int pageId) { Integer windowIndex = idToWindowIndex.remove(pageId); if (windowIndex != null) { windowIndexToPageId.remove(windowIndex); } } /** * */ private void rebuildIndices() { idToWindowIndex = null; idToWindowIndex = new IntHashMap<Integer>(); windowIndexToPageId = null; windowIndexToPageId = new IntHashMap<Integer>(); for (int i = 0; i < windows.size(); ++i) { PageWindowInternal window = windows.get(i); putWindowIndex(window.pageId, i); } } /** * Returns the index of the given page in the {@link #windows} list. * * @param pageId * @return window index */ private int getWindowIndex(int pageId) { if (idToWindowIndex == null) { rebuildIndices(); } Integer result = idToWindowIndex.get(pageId); return result != null ? result : -1; } /** * Increments the {@link #indexPointer}. If the maximum file size has been reached, the * {@link #indexPointer} is set to 0. * * @return new index pointer */ private int incrementIndexPointer() { if ((maxSize > 0) && (totalSize >= maxSize) && (indexPointer == windows.size() - 1)) { indexPointer = 0; } else { ++indexPointer; } return indexPointer; } /** * Returns the offset in file of the window on given index. The offset is counted by getting the * previous page offset and adding the previous page size to it. * * @param index * @return window file offset */ private int getWindowFileOffset(int index) { if (index > 0) { PageWindowInternal window = windows.get(index - 1); return window.filePartOffset + window.filePartSize; } return 0; } /** * Splits the window with given index to two windows. First of those will have size specified by * the argument, the other one will fill up the rest of the original window. * * @param index * @param size */ private void splitWindow(int index, int size) { PageWindowInternal window = windows.get(index); int delta = window.filePartSize - size; if (index == windows.size() - 1) { // if this is last window totalSize -= delta; window.filePartSize = size; } else if (window.filePartSize != size) { PageWindowInternal newWindow = new PageWindowInternal(); newWindow.pageId = -1; window.filePartSize = size; windows.add(index + 1, newWindow); newWindow.filePartOffset = getWindowFileOffset(index + 1); newWindow.filePartSize = delta; } idToWindowIndex = null; windowIndexToPageId = null; } /** * Merges the window with given index with the next window. The resulting window will have size * of the two windows summed together. * * @param index */ private void mergeWindowWithNext(int index) { if (index < windows.size() - 1) { PageWindowInternal window = windows.get(index); PageWindowInternal next = windows.get(index + 1); window.filePartSize += next.filePartSize; windows.remove(index + 1); idToWindowIndex = null; // reset index windowIndexToPageId = null; } } /** * Adjusts the window on given index to the specified size. If the new size is smaller than the * window size, the window will be split. Otherwise the window will be merged with as many * subsequent window as necessary. In case the window is last window in the file, the size will * be adjusted without splitting or merging. * * @param index * @param size */ private void adjustWindowSize(int index, int size) { PageWindowInternal window = windows.get(index); // last window, just adjust size if (index == windows.size() - 1) { int delta = size - window.filePartSize; totalSize += delta; window.filePartSize = size; } else { // merge as many times as necessary while (window.filePartSize < size && index < windows.size() - 1) { mergeWindowWithNext(index); } // done merging - do we have enough room ? if (window.filePartSize < size) { // no, this is the last window int delta = size - window.filePartSize; totalSize += delta; window.filePartSize = size; } else { // yes, we might want to split the window, so that we don't lose // space when the created window was too big splitWindow(index, size); } } window.pageId = -1; } /** * Allocates window on given index with to size. If the index is pointing to existing window, * the window size will be adjusted. Otherwise a new window with appropriated size will be * created. * * @param index * @param size * @return page window */ private PageWindowInternal allocatePageWindow(int index, int size) { final PageWindowInternal window; // new window if (index == windows.size()) { // new page window window = new PageWindowInternal(); window.filePartOffset = getWindowFileOffset(index); totalSize += size; window.filePartSize = size; windows.add(window); } else { // get the window window = windows.get(index); // adjust if necessary if (window.filePartSize != size) { adjustWindowSize(index, size); } } return window; } /** * Public (read only) version of page window. * * @author Matej Knopp */ public static class PageWindow { private final PageWindowInternal pageWindowInternal; /** * Construct. * * @param pageWindowInternal */ private PageWindow(PageWindowInternal pageWindowInternal) { this.pageWindowInternal = pageWindowInternal; } /** * @return page Id */ public int getPageId() { return pageWindowInternal.pageId; } /** * @return offset in the pagemap file where the serialized page data starts */ public int getFilePartOffset() { return pageWindowInternal.filePartOffset; } /** * @return size of the serialized page data */ public int getFilePartSize() { return pageWindowInternal.filePartSize; } } /** * Creates and returns a new page window for given page. * * @param pageId * @param size * @return page window */ public synchronized PageWindow createPageWindow(int pageId, int size) { int index = getWindowIndex(pageId); // if we found the page window, mark it as invalid if (index != -1) { removeWindowIndex(pageId); (windows.get(index)).pageId = -1; } // if we are not going to reuse a page window (because it's not on // indexPointer position or because we didn't find it), increment the // indexPointer if (index == -1 || index != indexPointer) { index = incrementIndexPointer(); } PageWindowInternal window = allocatePageWindow(index, size); window.pageId = pageId; putWindowIndex(pageId, index); return new PageWindow(window); } /** * Returns the page window for given page or null if no window was found. * * @param pageId * @return page window or null */ public synchronized PageWindow getPageWindow(int pageId) { int index = getWindowIndex(pageId); if (index != -1) { return new PageWindow(windows.get(index)); } return null; } /** * Removes the page window for given page. * * @param pageId */ public synchronized void removePage(int pageId) { int index = getWindowIndex(pageId); if (index != -1) { PageWindowInternal window = windows.get(index); removeWindowIndex(pageId); if (index == windows.size() - 1) { windows.remove(index); totalSize -= window.filePartSize; if (indexPointer == index) { --indexPointer; } } else { window.pageId = -1; } } } /** * Returns last n saved page windows. * * @param count * @return list of page windows */ public synchronized List<PageWindow> getLastPageWindows(int count) { List<PageWindow> result = new ArrayList<PageWindow>(); // start from current index to 0 int currentIndex = indexPointer; do { if (currentIndex == -1) { break; } if (currentIndex < windows.size()) { PageWindowInternal window = windows.get(currentIndex); if (window.pageId != -1) { result.add(new PageWindow(window)); } } --currentIndex; if (currentIndex == -1) { // rewind to the last entry and collect all entries until current index currentIndex = windows.size() - 1; } } while (result.size() < count && currentIndex != indexPointer); return result; } /** * Creates a new PageWindowManager. * * @param maxSize * maximum page size. After this size is exceeded, the pages will be saved starting * at the beginning of file */ public PageWindowManager(long maxSize) { this.maxSize = maxSize; } /** * Returns the size of all saved pages * * @return total size */ public synchronized int getTotalSize() { return totalSize; } }