All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zaproxy.zap.utils.PagingTableModel Maven / Gradle / Ivy

Go to download

The Zed Attack Proxy (ZAP) is an easy to use integrated penetration testing tool for finding vulnerabilities in web applications. It is designed to be used by people with a wide range of security experience and as such is ideal for developers and functional testers who are new to penetration testing. ZAP provides automated scanners as well as a set of tools that allow you to find security vulnerabilities manually.

The newest version!
/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2012 The ZAP Development Team
 *
 * Licensed 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.zaproxy.zap.utils;

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * A paginated {@code TableModel}. The model will have at most, by default, {@value
 * #DEFAULT_MAX_PAGE_SIZE} rows loaded in memory at any given time. The advertised row count will be
 * of all the entries (as if they were all loaded in memory).
 *
 * 

If a {@code JTable}, using this model, is wrapped in a {@code JScrollPane} the vertical scroll * bar will be shown as if all the entries were loaded. * *

Rows (page segments) will be loaded in a separate thread, on demand. * *

Implementation based on PagingTableModel located in {@literal * http://www.coderanch.com/t/345383/GUI/java/JTable-Paging}, with permission from the author, Brian * Cole ([email protected], http://bitguru.com).
* Contains the following changes: * *

    *
  • Removed simulation code; *
  • Added type parameter; *
  • Added abstract methods. *
* * @param the type of elements in this table model * @see #loadPage(int, int) * @see #setMaxPageSize(int) * @see javax.swing.table.TableModel * @see javax.swing.JTable * @see javax.swing.JScrollPane */ @SuppressWarnings("serial") public abstract class PagingTableModel extends AbstractTableModel { private static final long serialVersionUID = -6353414328926478100L; private static final Logger LOGGER = LogManager.getLogger(PagingTableModel.class); /** Default segment loader thread name. */ public static final String DEFAULT_SEGMENT_LOADER_THREAD_NAME = "ZAP-PagingTableModel-SegmentLoaderThread"; /** Default maximum page size. */ public static final int DEFAULT_MAX_PAGE_SIZE = 50; /** The maximum size of the page. */ private int maxPageSize; private int dataOffset = 0; private List data = Collections.emptyList(); private SortedSet pending = new TreeSet<>(); private final String segmentLoaderThreadName; /** * Constructs a {@code PagingTableModel} with default default segment loader thread name ( * {@value #DEFAULT_SEGMENT_LOADER_THREAD_NAME}) and default maximum page size ({@value * #DEFAULT_MAX_PAGE_SIZE}). */ public PagingTableModel() { this(DEFAULT_SEGMENT_LOADER_THREAD_NAME, DEFAULT_MAX_PAGE_SIZE); } /** * Constructs a {@code PagingTableModel} with the given segment loader thread name and default * maximum page size ({@value #DEFAULT_MAX_PAGE_SIZE}). * * @param segmentLoaderThreadName the name for segment loader thread * @throws IllegalArgumentException if {@code maxPageSize} is negative or zero. */ public PagingTableModel(String segmentLoaderThreadName) { this(segmentLoaderThreadName, DEFAULT_MAX_PAGE_SIZE); } /** * Constructs a {@code PagingTableModel} with the given maximum page size and default segment * loader thread name ( {@value #DEFAULT_SEGMENT_LOADER_THREAD_NAME}). * * @param maxPageSize the maximum page size * @throws IllegalArgumentException if {@code maxPageSize} is negative or zero. */ public PagingTableModel(final int maxPageSize) { this(DEFAULT_SEGMENT_LOADER_THREAD_NAME, maxPageSize); } /** * Constructs a {@code PagingTableModel} with the given segment loader thread name and given * maximum page size. * * @param segmentLoaderThreadName the name for segment loader thread * @param maxPageSize the maximum page size * @throws IllegalArgumentException if {@code maxPageSize} is negative or zero. */ public PagingTableModel(String segmentLoaderThreadName, int maxPageSize) { this.segmentLoaderThreadName = segmentLoaderThreadName; setMaxPageSizeWithoutPageChanges(maxPageSize); } /** * Returns the maximum page size. * * @return the maximum page size. */ public int getMaxPageSize() { return maxPageSize; } /** * Sets the maximum size of the page. * *

If the given maximum size is greater than the current maximum size a new page will be * loaded, otherwise the current page will be shrunk to meet the given maximum size. In both * cases the {@code TableModelListener} will be notified of the change. * *

The call to this method has no effect if the given maximum size is equal to the current * maximum size. * * @param maxPageSize the new maximum page size * @throws IllegalArgumentException if {@code maxPageSize} is negative or zero. * @see #setMaxPageSizeWithoutPageChanges(int) * @see TableModelListener */ public void setMaxPageSize(final int maxPageSize) { if (maxPageSize <= 0) { throw new IllegalArgumentException("Parameter maxPageSize must be greater than zero."); } if (this.maxPageSize == maxPageSize) { return; } int oldMaxPageSize = this.maxPageSize; setMaxPageSizeWithoutPageChanges(maxPageSize); int rowCount = getRowCount(); if (rowCount > 0) { if (maxPageSize > oldMaxPageSize) { schedule(dataOffset); } else if (data.size() > maxPageSize) { final List shrunkData = data.subList(0, maxPageSize); EventQueue.invokeLater( new Runnable() { @Override public void run() { setData(dataOffset, new ArrayList<>(shrunkData)); } }); } } } /** * Sets the maximum size of the page. * *

As opposed to method {@code #setMaxPageSize(int)} no changes will be made to the current * page. * * @param maxPageSize the new maximum page size * @throws IllegalArgumentException if {@code maxPageSize} is negative or zero. * @see #setMaxPageSize(int) */ public void setMaxPageSizeWithoutPageChanges(final int maxPageSize) { if (maxPageSize <= 0) { throw new IllegalArgumentException("Parameter maxPageSize must be greater than zero."); } this.maxPageSize = maxPageSize; } @Override public void fireTableDataChanged() { // clear cached data clear(); super.fireTableDataChanged(); } /** * Returns the number of all items. * * @return number of items */ @Override public abstract int getRowCount(); /** * Called by {@link PagingTableModel#getValueAt(int, int)} when requested row is already loaded. * * @param rowObject the row object * @param columnIndex the column index * @return value from requested column */ protected abstract Object getRealValueAt(T rowObject, int columnIndex); /** * Gets the placeholder value that should be shown for the given column, until the actual values * are ready to be shown. * * @param columnIndex the column index * @return Value is used to display while loading entry */ protected abstract Object getPlaceholderValueAt(int columnIndex); /** * Called when a new page is required. * *

The returned {@code List} should support fast (preferably constant time) random access. * * @param offset the start offset of the page * @param length the length of the page * @return an excerpt of whole list * @see List#get(int) */ protected abstract List loadPage(int offset, int length); @Override public final Object getValueAt(int rowIndex, int columnIndex) { T rowObject = getRowObject(rowIndex); if (rowObject == null) { // is not loaded yet schedule(rowIndex); // return default value meanwhile return getPlaceholderValueAt(columnIndex); } return getRealValueAt(rowObject, columnIndex); } /** * Gets the object at the given row. * * @param rowIndex the index of the row * @return {@code null} if object is not in the current page */ protected T getRowObject(int rowIndex) { int pageIndex = rowIndex - dataOffset; if (pageIndex >= 0 && pageIndex < data.size()) { return data.get(pageIndex); } return null; } /** * Schedule the loading of the neighborhood around offset (if not already scheduled). * * @param offset the offset row */ private void schedule(int offset) { if (isPending(offset)) { return; } int startOffset = Math.max(0, offset - maxPageSize / 2); int length = offset + maxPageSize / 2 - startOffset; load(startOffset, length); } private boolean isPending(int offset) { int pendingCount = pending.size(); if (pendingCount == 0) { return false; } if (pendingCount == 1) { // special case (for speed) Segment seg = pending.first(); return seg.contains(offset); } Segment low = new Segment(offset - maxPageSize, 0); Segment high = new Segment(offset + 1, 0); // search pending segments that may contain offset for (Segment seg : pending.subSet(low, high)) { if (seg.contains(offset)) { return true; } } return false; } private void load(final int startOffset, final int length) { Segment seg = new Segment(startOffset, length); pending.add(seg); SegmentLoaderThread segmentLoader = new SegmentLoaderThread(seg, segmentLoaderThreadName); segmentLoader.start(); } /** * Sets the given {@code page} as the currently loaded data and notifies the table model * listeners of the rows updated. * *

Note: This method must be call on the EDT, failing to do so might result * in GUI state inconsistencies. * * @param offset the start offset of the given {@code page} * @param page the new data * @see EventQueue#invokeLater(Runnable) * @see TableModelListener */ private void setData(int offset, List page) { int lastRow = offset + page.size() - 1; dataOffset = offset; data = page; fireTableRowsUpdated(offset, lastRow); } protected void clear() { data.clear(); data = Collections.emptyList(); pending.clear(); } /** * This class is used to keep track of which rows have been scheduled for loading, so that rows * don't get scheduled twice concurrently. The idea is to store Segments in a sorted data * structure for fast searching. * *

The compareTo() method sorts first by base position, then by length. */ static final class Segment implements Comparable { private final int base; private final int length; public Segment(int base, int length) { this.base = base; this.length = length; } public int getBase() { return base; } public int getLength() { return length; } public boolean contains(int pos) { return (base <= pos && pos < base + length); } @Override public boolean equals(Object o) { if (o != null && o instanceof Segment) { Segment s = (Segment) o; boolean hasSameBase = (base == s.base); boolean hasSameLength = (length == s.length); return hasSameBase && hasSameLength; } return false; } @Override public int hashCode() { return (41 * (41 + base) + length); } @Override public int compareTo(Segment other) { // return negative/zero/positive as this object is // less-than/equal-to/greater-than other int d = base - other.base; if (d != 0) { return d; } return length - other.length; } } private class SegmentLoaderThread extends Thread { private final Segment segment; public SegmentLoaderThread(Segment segment, String name) { super(name); this.segment = segment; } @Override public void run() { final List page; try { page = loadPage(segment.getBase(), segment.getLength()); } catch (Exception e) { LOGGER.warn("error retrieving page at {}: aborting", segment.getBase(), e); pending.remove(segment); return; } EventQueue.invokeLater( new Runnable() { @Override public void run() { setData(segment.getBase(), page); pending.remove(segment); } }); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy