<commit_before>/*
 * Copyright 2007 the original author or authors.
 *
 * 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 net.sf.jailer.ui.databrowser;

import java.awt.BasicStroke;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.DefaultComboBoxModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.RowSorter;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;

import net.sf.jailer.CommandLineParser;
import net.sf.jailer.Configuration;
import net.sf.jailer.ScriptFormat;
import net.sf.jailer.database.Session;
import net.sf.jailer.datamodel.Association;
import net.sf.jailer.datamodel.Cardinality;
import net.sf.jailer.datamodel.Column;
import net.sf.jailer.datamodel.DataModel;
import net.sf.jailer.datamodel.PrimaryKey;
import net.sf.jailer.datamodel.Table;
import net.sf.jailer.modelbuilder.JDBCMetaDataBasedModelElementFinder;
import net.sf.jailer.ui.ConditionEditor;
import net.sf.jailer.ui.DbConnectionDialog;
import net.sf.jailer.ui.ExtractionModelFrame;
import net.sf.jailer.ui.QueryBuilderDialog;
import net.sf.jailer.ui.QueryBuilderDialog.Relationship;
import net.sf.jailer.ui.RestrictionDefinition;
import net.sf.jailer.ui.UIUtil;
import net.sf.jailer.ui.databrowser.Desktop.RowBrowser;
import net.sf.jailer.util.CancellationException;
import net.sf.jailer.util.CancellationHandler;
import net.sf.jailer.util.Pair;
import net.sf.jailer.util.SqlUtil;

/**
 * Content UI of a row browser frame (as {@link JInternalFrame}s). Contains a
 * table for rendering rows.
 * 
 * @author <NAME>
 */
@SuppressWarnings("serial")
public abstract class BrowserContentPane extends javax.swing.JPanel {

	/**
	 * Allowed row limits.
	 */
	private static final Integer[] ROW_LIMITS = new Integer[] { 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 70000, 100000 };

	/**
	 * Concurrently loads rows.
	 */
	private final class LoadJob {
		private List<Row> rows = Collections.synchronizedList(new ArrayList<Row>());
		private Exception exception;
		private boolean isCanceled = false;
		private final int limit;
		private final String andCond;
		private final boolean selectDistinct;
		
		public LoadJob(int limit, String andCond, boolean selectDistinct) {
			this.andCond = andCond;
			this.selectDistinct = selectDistinct;
			synchronized (this) {
				this.limit = limit;
			}
		}

		public void run() {
			int l;
			synchronized (this) {
				l = limit;
				if (isCanceled) {
					CancellationHandler.reset(this);
					return;
				}
			}
			try {
				reloadRows(andCond, rows, this, l + 1, selectDistinct);
			} catch (SQLException e) {
				CancellationHandler.reset(this);
				synchronized (rows) {
					exception = e;
				}
			} catch (CancellationException e) {
				Session._log.info("cancelled");
				return;
			}
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					Exception e;
					int l;
					boolean limitExceeded = false;
					synchronized (rows) {
						e = exception;
						l = limit;
						if (rows.size() > limit) {
							limitExceeded = true;
							rows.remove(rows.size() - 1);
						}
						isCanceled = true; // done
					}
					if (e != null) {
						updateMode("error");
						if (!asking) {
							try {
								if (table.getName() != null && !table.exists(session, JDBCMetaDataBasedModelElementFinder.getDefaultSchema(session, session.getSchemaName()))) {
									asking = true;
									String schemaMappingOption = "Schema Mapping";
									switch (JOptionPane.showOptionDialog(BrowserContentPane.this, "Table \"" + table.getName() + "\" not found!", "Unknown table", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, new Object[] { "Cancel", schemaMappingOption, "Analyze Database" }, "Cancel")) {
									case 1: openSchemaMappingDialog(); break;
									case 2: openSchemaAnalyzer(); break;
									}
									asking = false;
								} else {
									UIUtil.showException(BrowserContentPane.this, "Error", e);
								}
							} catch (Exception e1) {
								asking = false;
								UIUtil.showException(BrowserContentPane.this, "Error", e);
							}
						}
					} else {
						Set<String> prevIDs = new TreeSet<String>();
						int prevSize = 0;
						if (BrowserContentPane.this.rows != null) {
							prevSize = BrowserContentPane.this.rows.size();
							for (Row r: BrowserContentPane.this.rows) {
								prevIDs.add(r.rowId);
							}
						}
						onContentChange(new ArrayList<Row>(), false);
						BrowserContentPane.this.rows.clear();
						BrowserContentPane.this.rows.addAll(rows);
						updateTableModel(l, limitExceeded);
						Set<String> currentIDs = new TreeSet<String>();
						if (rows != null) {
							for (Row r: rows) {
								currentIDs.add(r.rowId);
							}
						}
						onContentChange(rows, rows.isEmpty() || rows.size() != prevSize || !prevIDs.equals(currentIDs) || rows.size() != currentIDs.size());
						updateMode("table");
						updateWhereField();
					}
				}
			});
		}

		public void cancel() {
			synchronized (this) {
				if (isCanceled) {
					return;
				}
				isCanceled = true;
			}
			CancellationHandler.cancel(this);
		}
	}
	
	private static boolean asking = false;
	
	/**
	 * Current LoadJob.
	 */
	private LoadJob currentLoadJob;

	/**
	 * Table to read rows from.
	 */
	Table table;

	/**
	 * Parent row, or <code>null</code>.
	 */
	Row parentRow;

	/**
	 * The data model.
	 */
	DataModel dataModel;

	/**
	 * {@link Association} with parent row, or <code>null</code>.
	 */
	private Association association;

	/**
	 * Rows to render.
	 */
	List<Row> rows = new ArrayList<Row>();

	/**
	 * Number of non-distinct rows;
	 */
	private int noNonDistinctRows;
	
	/**
	 * Number of distinct rows;
	 */
	private int noDistinctRows;
	
	/**
	 * Indexes of highlighted rows.
	 */
	Set<Integer> highlightedRows = new HashSet<Integer>();

	/**
	 * Indexes of primary key columns.
	 */
	private Set<Integer> pkColumns = new HashSet<Integer>();

	/**
	 * DB session.
	 */
	Session session;

	private int currentRowSelection = -1;

	private List<Row> parentRows;
	protected final Set<Pair<BrowserContentPane, Row>> currentClosure;
	protected Set<Pair<BrowserContentPane, String>> currentClosureRowIDs;
	private DetailsView singleRowDetailsView;
	private int initialRowHeight;
	public SQLBrowserContentPane sqlBrowserContentPane;

	private boolean suppressReload;

	/**
	 * For concurrent reload of rows.
	 */
	private static final LinkedBlockingQueue<LoadJob> runnableQueue = new LinkedBlockingQueue<LoadJob>();

	/**
	 * Maximum number of concurrent DB connections.
	 */
	private static int MAX_CONCURRENT_CONNECTIONS = 5;

	static {
		// initialize listeners for #runnableQueue
		for (int i = 0; i < MAX_CONCURRENT_CONNECTIONS; ++i) {
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for (;;) {
						try {
							runnableQueue.take().run();
						} catch (InterruptedException e) {
							// ignore
						}
					}
				}
			}, "browser-" + i);
			t.setDaemon(true);
			t.start();
		}
	}
	
	static class SqlStatementTable extends Table {
		public SqlStatementTable(String name, PrimaryKey primaryKey, boolean defaultUpsert) {
			super(name, primaryKey, defaultUpsert);
		}
	}

	/**
	 * Constructor.
	 * 
	 * @param dataModel
	 *            the data model
	 * @param table
	 *            to read rows from. Opens SQL browser if table is <code>null</code>.
	 * @param condition
	 *            initial condition
	 * @param session
	 *            DB session
	 * @param parentRow
	 *            parent row
	 * @param parentRows
	 *            all parent rows, if there are more than 1
	 * @param association
	 *            {@link Association} with parent row
	 * @param selectDistinct 
	 * @param limit 
	 * @param selectDistinct 
	 * @param limit 
	 * @param reload 
	 */
	public BrowserContentPane(final DataModel dataModel, final Table table, String condition, Session session, Row parentRow, List<Row> parentRows,
			final Association association, Frame parentFrame, Set<Pair<BrowserContentPane, Row>> currentClosure, Set<Pair<BrowserContentPane, String>> currentClosureRowIDs, Integer limit, Boolean selectDistinct, boolean reload) {
		this.table = table;
		this.session = session;
		this.dataModel = dataModel;
		this.parentRow = parentRow;
		this.parentRows = parentRows;
		this.association = association;
		this.currentClosure = currentClosure;
		this.currentClosureRowIDs = currentClosureRowIDs;

		suppressReload = true;
		
		if (table == null) {
			this.table = new SqlStatementTable(null, null, false);
		}
		
		initComponents();
		
		dropA.setText(null);
		dropA.setIcon(dropDownIcon);
		dropA.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				openColumnDropDownBox(dropA, "A", table);
			}
			
			public void mouseEntered(java.awt.event.MouseEvent evt) {
				dropA.setEnabled(false);
            }
            public void mouseExited(java.awt.event.MouseEvent evt) {
            	dropA.setEnabled(true);
           }
        });
		
		if (association != null) {
			dropB.setText(null);
			dropB.setIcon(dropDownIcon);
			dropB.addMouseListener(new java.awt.event.MouseAdapter() {
				public void mousePressed(java.awt.event.MouseEvent evt) {
					openColumnDropDownBox(dropB, "B", association.source);
				}
				
				public void mouseEntered(java.awt.event.MouseEvent evt) {
					dropB.setEnabled(false);
	            }
	            public void mouseExited(java.awt.event.MouseEvent evt) {
	            	dropB.setEnabled(true);
	           }
	        });
		}
		
		andCondition.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				int key = e.getKeyCode();
				if (key == KeyEvent.VK_ENTER) {
					reloadRows();
				}
			}
		});

		initialRowHeight = rowsTable.getRowHeight();

		rowsTable = new JTable() {
			private int x[] = new int[2];
			private int y[] = new int[2];
			private Color color = new Color(0, 0, 200);

			@Override
			public void paint(Graphics graphics) {
				super.paint(graphics);
				if (!(graphics instanceof Graphics2D))
					return;
				RowSorter<? extends TableModel> rowSorter = rowsTable.getRowSorter();
				for (int i = 0; i < rowsTable.getRowCount(); ++i) {
					if (rowSorter.convertRowIndexToView(i) != i) {
						return;
					}
				}
				int maxI = Math.min(rowsTable.getRowCount(), rows.size());

				for (int i = 0; i < maxI; ++i) {
					if (rows.get(i).isBlockEnd()) {
						Graphics2D g2d = (Graphics2D) graphics;
						g2d.setColor(color);
						g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
						g2d.setStroke(new BasicStroke(1));
						Rectangle r = rowsTable.getCellRect(i, 0, false);
						x[0] = (int) r.getMinX();
						y[0] = (int) r.getMaxY();
						r = rowsTable.getCellRect(i, rowsTable.getColumnCount() - 1, false);
						x[1] = (int) r.getMaxX();
						y[1] = (int) r.getMaxY();
						g2d.drawPolyline(x, y, 2);
					}
				}
			}
		};

		rowsTable.setAutoCreateRowSorter(true);
		rowsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
		rowsTableScrollPane.setViewportView(rowsTable);

		andCondition.setText(ConditionEditor.toSingleLine(condition));
		from.setText(table == null? "" : this.dataModel.getDisplayName(table));
		adjustGui();
		rowsTable.setShowGrid(false);
		final TableCellRenderer defaultTableCellRenderer = rowsTable.getDefaultRenderer(String.class);

		rowsTable.setDefaultRenderer(Object.class, new TableCellRenderer() {

			final Color BG1 = new Color(255, 255, 255);
			final Color BG2 = new Color(230, 255, 255);
			final Color BG3 = new Color(190, 195, 255);
			final Color FG1 = new Color(155, 0, 0);
			final Font font = new JLabel().getFont();
			final Font nonbold = new Font(font.getName(), font.getStyle() & ~Font.BOLD, font.getSize());
			final Font bold = new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize());

			public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
				isSelected = currentRowSelection == row || currentRowSelection == -2;

				Component render = defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, false, row, column);
				
				if (value instanceof Row) {
					Row theRow = (Row) value;
					Pair<BrowserContentPane, String> pair = new Pair<BrowserContentPane, String>(BrowserContentPane.this, theRow.rowId);
					singleRowDetailsView.setBorderColor(isSelected? render.getBackground() : BrowserContentPane.this.currentClosureRowIDs.contains(pair)? BG3 : Color.WHITE);
					return singleRowDetailsView;
				}

				final RowSorter<?> rowSorter = rowsTable.getRowSorter();
				if (render instanceof JLabel) {
					if (!isSelected) {
						if (row < rows.size() && BrowserContentPane.this.currentClosureRowIDs.contains(new Pair<BrowserContentPane, String>(BrowserContentPane.this, rows.get(rowSorter.convertRowIndexToModel(row)).rowId))) {
							((JLabel) render).setBackground(BG3);
						} else {
							((JLabel) render).setBackground((row % 2 == 0) ? BG1 : BG2);
						}
					}
					((JLabel) render).setForeground(pkColumns.contains(rowsTable.convertColumnIndexToModel(column)) ? FG1 : Color.BLACK);
					try {
						((JLabel) render).setFont(highlightedRows.contains(rowSorter.convertRowIndexToModel(row)) ? bold : nonbold);
					} catch (Exception e) {
						// ignore
					}
				}
				return render;
			}
		});
		rowsTable.setRowSelectionAllowed(false);
		rowsTable.setColumnSelectionAllowed(false);
		rowsTable.setCellSelectionEnabled(false);
		rowsTable.setEnabled(false);
		rowsTable.addMouseListener(new MouseListener() {
			private JPopupMenu lastMenu;

			@Override
			public void mouseReleased(MouseEvent e) {
			}

			@Override
			public void mousePressed(MouseEvent e) {
				int ri = rowsTable.rowAtPoint(e.getPoint());
				if (ri >= 0) {
					int i = rowsTable.getRowSorter().convertRowIndexToModel(ri);
					Row row = rows.get(i);

					if (lastMenu == null || !lastMenu.isVisible()) {
						setCurrentRowSelection(ri);
						Rectangle r = rowsTable.getCellRect(ri, 0, false);
						int x = Math.max((int) e.getPoint().x, (int) r.getMinX());
						int y = (int) r.getMaxY() - 2;
						if (singleRowDetailsView != null) {
							y = e.getY();
						}
						Point p = SwingUtilities.convertPoint(rowsTable, x, y, null);
						JPopupMenu popup;
						popup = createPopupMenu(row, i, p.x + getOwner().getX(), p.y + getOwner().getY(), rows.size() == 1);
						popup.show(rowsTable, x, y);
						popup.addPropertyChangeListener("visible", new PropertyChangeListener() {

							@Override
							public void propertyChange(PropertyChangeEvent evt) {
								if (Boolean.FALSE.equals(evt.getNewValue())) {
									setCurrentRowSelection(-1);
								}
							}
						});
						lastMenu = popup;<commit_msg>preparing release 3.7.5<commit_after>/*
 * Copyright 2007 the original author or authors.
 *
 * 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 net.sf.jailer.ui.databrowser;

import java.awt.BasicStroke;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.DefaultComboBoxModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.RowSorter;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;

import net.sf.jailer.CommandLineParser;
import net.sf.jailer.Configuration;
import net.sf.jailer.ScriptFormat;
import net.sf.jailer.database.Session;
import net.sf.jailer.datamodel.Association;
import net.sf.jailer.datamodel.Cardinality;
import net.sf.jailer.datamodel.Column;
import net.sf.jailer.datamodel.DataModel;
import net.sf.jailer.datamodel.PrimaryKey;
import net.sf.jailer.datamodel.Table;
import net.sf.jailer.modelbuilder.JDBCMetaDataBasedModelElementFinder;
import net.sf.jailer.ui.ConditionEditor;
import net.sf.jailer.ui.DbConnectionDialog;
import net.sf.jailer.ui.ExtractionModelFrame;
import net.sf.jailer.ui.QueryBuilderDialog;
import net.sf.jailer.ui.QueryBuilderDialog.Relationship;
import net.sf.jailer.ui.RestrictionDefinition;
import net.sf.jailer.ui.UIUtil;
import net.sf.jailer.ui.databrowser.Desktop.RowBrowser;
import net.sf.jailer.util.CancellationException;
import net.sf.jailer.util.CancellationHandler;
import net.sf.jailer.util.Pair;
import net.sf.jailer.util.SqlUtil;

/**
 * Content UI of a row browser frame (as {@link JInternalFrame}s). Contains a
 * table for rendering rows.
 * 
 * @author <NAME>
 */
@SuppressWarnings("serial")
public abstract class BrowserContentPane extends javax.swing.JPanel {

	/**
	 * Allowed row limits.
	 */
	private static final Integer[] ROW_LIMITS = new Integer[] { 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 70000, 100000 };

	/**
	 * Concurrently loads rows.
	 */
	private final class LoadJob {
		private List<Row> rows = Collections.synchronizedList(new ArrayList<Row>());
		private Exception exception;
		private boolean isCanceled = false;
		private final int limit;
		private final String andCond;
		private final boolean selectDistinct;
		
		public LoadJob(int limit, String andCond, boolean selectDistinct) {
			this.andCond = andCond;
			this.selectDistinct = selectDistinct;
			synchronized (this) {
				this.limit = limit;
			}
		}

		public void run() {
			int l;
			synchronized (this) {
				l = limit;
				if (isCanceled) {
					CancellationHandler.reset(this);
					return;
				}
			}
			try {
				reloadRows(andCond, rows, this, l + 1, selectDistinct);
			} catch (SQLException e) {
				CancellationHandler.reset(this);
				synchronized (rows) {
					exception = e;
				}
			} catch (CancellationException e) {
				Session._log.info("cancelled");
				return;
			}
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					Exception e;
					int l;
					boolean limitExceeded = false;
					synchronized (rows) {
						e = exception;
						l = limit;
						if (rows.size() > limit) {
							limitExceeded = true;
							rows.remove(rows.size() - 1);
						}
						isCanceled = true; // done
					}
					if (e != null) {
						updateMode("error");
						if (!asking) {
							try {
								if (table.getName() != null && !table.exists(session, JDBCMetaDataBasedModelElementFinder.getDefaultSchema(session, session.getSchemaName()))) {
									asking = true;
									String schemaMappingOption = "Schema Mapping";
									switch (JOptionPane.showOptionDialog(BrowserContentPane.this, "Table \"" + table.getName() + "\" not found!", "Unknown table", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, new Object[] { "Cancel", schemaMappingOption, "Analyze Database" }, "Cancel")) {
									case 1: openSchemaMappingDialog(); break;
									case 2: openSchemaAnalyzer(); break;
									}
									asking = false;
								} else {
									UIUtil.showException(BrowserContentPane.this, "Error", e);
								}
							} catch (Exception e1) {
								asking = false;
								UIUtil.showException(BrowserContentPane.this, "Error", e);
							}
						}
					} else {
						Set<String> prevIDs = new TreeSet<String>();
						int prevSize = 0;
						if (BrowserContentPane.this.rows != null) {
							prevSize = BrowserContentPane.this.rows.size();
							for (Row r: BrowserContentPane.this.rows) {
								prevIDs.add(r.rowId);
							}
						}
						onContentChange(new ArrayList<Row>(), false);
						BrowserContentPane.this.rows.clear();
						BrowserContentPane.this.rows.addAll(rows);
						updateTableModel(l, limitExceeded);
						Set<String> currentIDs = new TreeSet<String>();
						if (rows != null) {
							for (Row r: rows) {
								currentIDs.add(r.rowId);
							}
						}
						onContentChange(rows, rows.isEmpty() || rows.size() != prevSize || !prevIDs.equals(currentIDs) || rows.size() != currentIDs.size());
						updateMode("table");
						updateWhereField();
					}
				}
			});
		}

		public void cancel() {
			synchronized (this) {
				if (isCanceled) {
					return;
				}
				isCanceled = true;
			}
			CancellationHandler.cancel(this);
		}
	}
	
	private static boolean asking = false;
	
	/**
	 * Current LoadJob.
	 */
	private LoadJob currentLoadJob;

	/**
	 * Table to read rows from.
	 */
	Table table;

	/**
	 * Parent row, or <code>null</code>.
	 */
	Row parentRow;

	/**
	 * The data model.
	 */
	DataModel dataModel;

	/**
	 * {@link Association} with parent row, or <code>null</code>.
	 */
	private Association association;

	/**
	 * Rows to render.
	 */
	List<Row> rows = new ArrayList<Row>();

	/**
	 * Number of non-distinct rows;
	 */
	private int noNonDistinctRows;
	
	/**
	 * Number of distinct rows;
	 */
	private int noDistinctRows;
	
	/**
	 * Indexes of highlighted rows.
	 */
	Set<Integer> highlightedRows = new HashSet<Integer>();

	/**
	 * Indexes of primary key columns.
	 */
	private Set<Integer> pkColumns = new HashSet<Integer>();

	/**
	 * DB session.
	 */
	Session session;

	private int currentRowSelection = -1;

	private List<Row> parentRows;
	protected final Set<Pair<BrowserContentPane, Row>> currentClosure;
	protected Set<Pair<BrowserContentPane, String>> currentClosureRowIDs;
	private DetailsView singleRowDetailsView;
	private int initialRowHeight;
	public SQLBrowserContentPane sqlBrowserContentPane;

	private boolean suppressReload;

	/**
	 * For concurrent reload of rows.
	 */
	private static final LinkedBlockingQueue<LoadJob> runnableQueue = new LinkedBlockingQueue<LoadJob>();

	/**
	 * Maximum number of concurrent DB connections.
	 */
	private static int MAX_CONCURRENT_CONNECTIONS = 5;

	static {
		// initialize listeners for #runnableQueue
		for (int i = 0; i < MAX_CONCURRENT_CONNECTIONS; ++i) {
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					for (;;) {
						try {
							runnableQueue.take().run();
						} catch (InterruptedException e) {
							// ignore
						}
					}
				}
			}, "browser-" + i);
			t.setDaemon(true);
			t.start();
		}
	}
	
	static class SqlStatementTable extends Table {
		public SqlStatementTable(String name, PrimaryKey primaryKey, boolean defaultUpsert) {
			super(name, primaryKey, defaultUpsert);
		}
	}

	/**
	 * Constructor.
	 * 
	 * @param dataModel
	 *            the data model
	 * @param table
	 *            to read rows from. Opens SQL browser if table is <code>null</code>.
	 * @param condition
	 *            initial condition
	 * @param session
	 *            DB session
	 * @param parentRow
	 *            parent row
	 * @param parentRows
	 *            all parent rows, if there are more than 1
	 * @param association
	 *            {@link Association} with parent row
	 * @param selectDistinct 
	 * @param limit 
	 * @param selectDistinct 
	 * @param limit 
	 * @param reload 
	 */
	public BrowserContentPane(final DataModel dataModel, final Table table, String condition, Session session, Row parentRow, List<Row> parentRows,
			final Association association, Frame parentFrame, Set<Pair<BrowserContentPane, Row>> currentClosure, Set<Pair<BrowserContentPane, String>> currentClosureRowIDs, Integer limit, Boolean selectDistinct, boolean reload) {
		this.table = table;
		this.session = session;
		this.dataModel = dataModel;
		this.parentRow = parentRow;
		this.parentRows = parentRows;
		this.association = association;
		this.currentClosure = currentClosure;
		this.currentClosureRowIDs = currentClosureRowIDs;

		suppressReload = true;
		
		if (table == null) {
			this.table = new SqlStatementTable(null, null, false);
		}
		
		initComponents();
		
		dropA.setText(null);
		dropA.setIcon(dropDownIcon);
		dropA.addMouseListener(new java.awt.event.MouseAdapter() {
			public void mousePressed(java.awt.event.MouseEvent evt) {
				openColumnDropDownBox(dropA, "A", table);
			}
			
			public void mouseEntered(java.awt.event.MouseEvent evt) {
				dropA.setEnabled(false);
            }
            public void mouseExited(java.awt.event.MouseEvent evt) {
            	dropA.setEnabled(true);
           }
        });
		
		if (association != null) {
			dropB.setText(null);
			dropB.setIcon(dropDownIcon);
			dropB.addMouseListener(new java.awt.event.MouseAdapter() {
				public void mousePressed(java.awt.event.MouseEvent evt) {
					openColumnDropDownBox(dropB, "B", association.source);
				}
				
				public void mouseEntered(java.awt.event.MouseEvent evt) {
					dropB.setEnabled(false);
	            }
	            public void mouseExited(java.awt.event.MouseEvent evt) {
	            	dropB.setEnabled(true);
	           }
	        });
		}
		
		andCondition.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				int key = e.getKeyCode();
				if (key == KeyEvent.VK_ENTER) {
					reloadRows();
				}
			}
		});

		initialRowHeight = rowsTable.getRowHeight();

		rowsTable = new JTable() {
			private int x[] = new int[2];
			private int y[] = new int[2];
			private Color color = new Color(0, 0, 200);

			@Override
			public void paint(Graphics graphics) {
				super.paint(graphics);
				if (!(graphics instanceof Graphics2D))
					return;
				RowSorter<? extends TableModel> rowSorter = rowsTable.getRowSorter();
				for (int i = 0; i < rowsTable.getRowCount(); ++i) {
					if (rowSorter.convertRowIndexToView(i) != i) {
						return;
					}
				}
				int maxI = Math.min(rowsTable.getRowCount(), rows.size());

				for (int i = 0; i < maxI; ++i) {
					if (rows.get(i).isBlockEnd()) {
						Graphics2D g2d = (Graphics2D) graphics;
						g2d.setColor(color);
						g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
						g2d.setStroke(new BasicStroke(1));
						Rectangle r = rowsTable.getCellRect(i, 0, false);
						x[0] = (int) r.getMinX();
						y[0] = (int) r.getMaxY();
						r = rowsTable.getCellRect(i, rowsTable.getColumnCount() - 1, false);
						x[1] = (int) r.getMaxX();
						y[1] = (int) r.getMaxY();
						g2d.drawPolyline(x, y, 2);
					}
				}
			}
		};

		rowsTable.setAutoCreateRowSorter(true);
		rowsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
		rowsTableScrollPane.setViewportView(rowsTable);

		andCondition.setText(ConditionEditor.toSingleLine(condition));
		from.setText(table == null? "" : this.dataModel.getDisplayName(table));
		adjustGui();
		rowsTable.setShowGrid(false);
		final TableCellRenderer defaultTableCellRenderer = rowsTable.getDefaultRenderer(String.class);

		rowsTable.setDefaultRenderer(Object.class, new TableCellRenderer() {

			final Color BG1 = new Color(255, 255, 255);
			final Color BG2 = new Color(230, 255, 255);
			final Color BG3 = new Color(190, 195, 255);
			final Color FG1 = new Color(155, 0, 0);
			final Font font = new JLabel().getFont();
			final Font nonbold = new Font(font.getName(), font.getStyle() & ~Font.BOLD, font.getSize());
			final Font bold = new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize());

			public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
				isSelected = currentRowSelection == row || currentRowSelection == -2;

				Component render = defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, false, row, column);
				
				if (value instanceof Row) {
					Row theRow = (Row) value;
					Pair<BrowserContentPane, String> pair = new Pair<BrowserContentPane, String>(BrowserContentPane.this, theRow.rowId);
					singleRowDetailsView.setBorderColor(isSelected? render.getBackground() : BrowserContentPane.this.currentClosureRowIDs.contains(pair)? BG3 : Color.WHITE);
					return singleRowDetailsView;
				}

				final RowSorter<?> rowSorter = rowsTable.getRowSorter();
				if (render instanceof JLabel) {
					if (!isSelected) {
						if (row < rows.size() && BrowserContentPane.this.currentClosureRowIDs.contains(new Pair<BrowserContentPane, String>(BrowserContentPane.this, rows.get(rowSorter.convertRowIndexToModel(row)).rowId))) {
							((JLabel) render).setBackground(BG3);
						} else {
							((JLabel) render).setBackground((row % 2 == 0) ? BG1 : BG2);
						}
					}
					((JLabel) render).setForeground(pkColumns.contains(rowsTable.convertColumnIndexToModel(column)) ? FG1 : Color.BLACK);
					try {
						((JLabel) render).setFont(highlightedRows.contains(rowSorter.convertRowIndexToModel(row)) ? bold : nonbold);
					} catch (Exception e) {
						// ignore
					}
				}
				return render;
			}
		});
		rowsTable.setRowSelectionAllowed(false);
		rowsTable.setColumnSelectionAllowed(false);
		rowsTable.setCellSelectionEnabled(false);
		rowsTable.setEnabled(false);
		rowsTable.addMouseListener(new MouseListener() {
			private JPopupMenu lastMenu;

			@Override
			public void mouseReleased(MouseEvent e) {
			}

			@Override
			public void mousePressed(MouseEvent e) {
				int ri = rowsTable.rowAtPoint(e.getPoint());
				if (ri >= 0) {
					int i = rowsTable.getRowSorter().convertRowIndexToModel(ri);
					Row row = rows.get(i);

					if (lastMenu == null || !lastMenu.isVisible()) {
						setCurrentRowSelection(ri);
						if (e.getButton() != MouseEvent.BUTTON1 || e.getClickCount() > 1) {
							Rectangle r = rowsTable.getCellRect(ri, 0, false);
							int x = Math.max((int) e.getPoint().x, (int) r.getMinX());
							int y = (int) r.getMaxY() - 2;
							if (singleRowDetailsView != null) {
								y = e.getY();
							}
							Point p = SwingUtilities.convertPoint(rowsTable, x, y, null);
							JPopupMenu popup;
							popup = createPopupMenu(row, i, p.x + getOwner().getX(), p.y + getOwner().getY(), rows.size() == 1);
							popup.show(rowsTable, x, y);
							popup.addPropertyChangeListener("visible", new PropertyChangeListener() {
	
								@Override
								public void propertyChange(PropertyChangeEvent evt) {
									if (Boolean.FALSE.equals(evt.getNewValue())) {
										setCurrentRowSelection(-1);
									}
								}
							});
							lastMenu = popup;
						}
{'fraction_non_alphanumeric': 0.1194452470384282, 'fraction_numerical': 0.008061253972840219, 'mean_word_length': 3.4682416731216112, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 82, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5450252', 'n_tokens_mistral': 13142, 'n_tokens_neox': 10700, 'n_words': 2484}