/* * Copyright (c) 2008, SQL Power Group Inc. * * This file is part of SQL Power Library. * * SQL Power Library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * SQL Power Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.swingui; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.LayoutManager; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.BorderFactory; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.log4j.Logger; import ca.sqlpower.sql.JDBCDataSourceType; /** * The PlatformSpecificConnectionOptionPanel creates and modifies a * panel that contains a Jlabel and JTextField for each parameter * in the template defined for the object. */ public class PlatformSpecificConnectionOptionPanel { private class JDBCURLUpdater implements DocumentListener { public void insertUpdate(DocumentEvent e) { updateUrlFromFields(); } public void removeUpdate(DocumentEvent e) { updateUrlFromFields(); } public void changedUpdate(DocumentEvent e) { updateUrlFromFields(); } } private static class PlatformOptionsLayout implements LayoutManager { /** The number of pixels to leave before each label except the first one. */ int preLabelGap = 10; /** The number of pixels to leave between every component. */ int gap = 5; public void addLayoutComponent(String name, Component comp) { // nothing to do } public void removeLayoutComponent(Component comp) { // nothing to do } public Dimension preferredLayoutSize(Container parent) { int height = 0; int width = 0; for (int i = 0; i < parent.getComponentCount(); i++) { Component c = parent.getComponent(i); height = Math.max(height, c.getPreferredSize().height); width += c.getPreferredSize().getWidth(); } return new Dimension(width, height); } public Dimension minimumLayoutSize(Container parent) { int height = 0; for (int i = 0; i < parent.getComponentCount(); i++) { Component c = parent.getComponent(i); height = Math.max(height, c.getMinimumSize().height); } return new Dimension(0, height); } public void layoutContainer(Container parent) { // compute total width of all labels int labelSize = 0; int labelCount = 0; for (int i = 0; i < parent.getComponentCount(); i++) { Component c = parent.getComponent(i); if (c instanceof JLabel) { if (i > 0) labelSize += preLabelGap; labelSize += c.getPreferredSize().width; labelCount += 1; } } int gapSize = gap * (parent.getComponentCount() - 1); // compute how wide each non-label component should be (if there are any non-labels) int nonLabelWidth = 0; if (parent.getComponentCount() != labelCount) { nonLabelWidth = (parent.getWidth() - labelSize - gapSize) / (parent.getComponentCount() - labelCount); } // impose a minimum so the non-labels at least show up when we're tight on space if (nonLabelWidth < 20) { nonLabelWidth = 20; } // lay out the container int x = 0; for (int i = 0; i < parent.getComponentCount(); i++) { Component c = parent.getComponent(i); if (i > 0) x += gap; if (c instanceof JLabel) { if (i > 0) x += preLabelGap; c.setBounds(x, 0, c.getPreferredSize().width, parent.getHeight()); x += c.getPreferredSize().width; } else { c.setBounds(x, 0, nonLabelWidth, parent.getHeight()); x += nonLabelWidth; } } } } private static Logger logger = Logger.getLogger(PlatformSpecificConnectionOptionPanel.class); private JDBCURLUpdater urlUpdater = new JDBCURLUpdater(); private boolean updatingUrlFromFields = false; private boolean updatingFieldsFromUrl = false; private JTextField dbUrlField; private JPanel platformSpecificOptionPanel; private JDBCDataSourceType template; public PlatformSpecificConnectionOptionPanel(JTextField dbUrlField) { platformSpecificOptionPanel = new JPanel(); platformSpecificOptionPanel.setLayout(new PlatformOptionsLayout()); platformSpecificOptionPanel.setBorder(BorderFactory.createEmptyBorder()); platformSpecificOptionPanel.add(new JLabel(Messages.getString("PlatformSpecificConnectionOptionPanel.noOptionsForDriver"))); //$NON-NLS-1$ this.dbUrlField = dbUrlField; dbUrlField.getDocument().addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent e) { updateFieldsFromUrl(); } public void removeUpdate(DocumentEvent e) { updateFieldsFromUrl(); } public void changedUpdate(DocumentEvent e) { updateFieldsFromUrl(); } }); } /** * Copies the values from the platform-specific url fields into the main * url. */ private void updateUrlFromFields() { if (updatingFieldsFromUrl) return; if (template == null || template.getJdbcUrl() == null) return; try { updatingUrlFromFields = true; StringBuffer newUrl = new StringBuffer(); Pattern p = Pattern.compile("<(.*?)>"); //$NON-NLS-1$ Matcher m = p.matcher(template.getJdbcUrl()); while (m.find()) { String varName = m.group(1); if (varName.indexOf(':') != -1) { varName = varName.substring(0, varName.indexOf(':')); } String varValue = getPlatformSpecificFieldValue(varName); varValue = escapeDollarBackslash(varValue); m.appendReplacement(newUrl, varValue); } m.appendTail(newUrl); dbUrlField.setText(newUrl.toString()); } finally { updatingUrlFromFields = false; } } /** * Escapes all instances of backslash and dollar-sign in the given input * string by preceding them with a backslash character. This is necessary * before passing user input into {@link Matcher#appendReplacement(StringBuffer, String)}. * * @param varValue The string to escape metacharacters in. Null is allowable. * @return The escaped string, or null if the input string is null. */ private String escapeDollarBackslash(String varValue) { if (varValue == null) return null; StringBuilder sb = new StringBuilder(); for (int i = 0; i < varValue.length(); i++) { char ch = varValue.charAt(i); if (ch == '\\' || ch == '$') { sb.append('\\'); } sb.append(ch); } return sb.toString(); } /** * Retrieves the named platform-specific option by looking it up in the * platformSpecificOptionPanel component. */ private String getPlatformSpecificFieldValue(String varName) { // we're looking for the contents of the JTextField that comes after a JLabel with the same text as varName for (int i = 0; i < platformSpecificOptionPanel.getComponentCount(); i++) { if (platformSpecificOptionPanel.getComponent(i) instanceof JLabel && ((JLabel) platformSpecificOptionPanel.getComponent(i)).getText().equals(varName) && platformSpecificOptionPanel.getComponentCount() >= i+1) { return ((JTextField) platformSpecificOptionPanel.getComponent(i+1)).getText(); } } return ""; //$NON-NLS-1$ } /** * Parses the main url against the current template (if possible) and fills in the * individual fields with the values it finds. */ private void updateFieldsFromUrl() { if (updatingUrlFromFields) return; if (template == null || template.getJdbcUrl() == null) return; try { updatingFieldsFromUrl = true; for (int i = 0; i < platformSpecificOptionPanel.getComponentCount(); i++) { platformSpecificOptionPanel.getComponent(i).setEnabled(true); } logger.debug("template is " + template); //$NON-NLS-1$ logger.debug("dbUrlField is " + dbUrlField); //$NON-NLS-1$ Map<String, String> map = template.retrieveURLParsing(dbUrlField.getText()); if (!map.isEmpty()) { platformSpecificOptionPanel.setEnabled(true); for (int g = 0; g < map.size(); g++) { ((JTextField) platformSpecificOptionPanel.getComponent(2*g+1)).setText((String)map.values().toArray()[g]); } } else { for (int i = 0; i < platformSpecificOptionPanel.getComponentCount(); i++) { platformSpecificOptionPanel.getComponent(i).setEnabled(false); } } } finally { updatingFieldsFromUrl = false; } } /** * Sets up the platformSpecificOptionPanel component to contain labels and * text fields associated with each variable in the current template. */ private void createFieldsFromTemplate() { for (int i = 0; i < platformSpecificOptionPanel.getComponentCount(); i++) { Component c = platformSpecificOptionPanel.getComponent(i); if (c instanceof JTextField) { ((JTextField) c).getDocument().removeDocumentListener(urlUpdater); } } platformSpecificOptionPanel.removeAll(); if (template != null) { Map<String, String> map = template.retrieveURLParsing(dbUrlField.getText()); if (map.size() == 0) { map = template.retrieveURLDefaults(); } for(String key : map.keySet()) { String var = key; String def = map.get(key); platformSpecificOptionPanel.add(new JLabel(var)); JTextField field = new JTextField(def); platformSpecificOptionPanel.add(field); field.getDocument().addDocumentListener(urlUpdater); logger.debug("The default value for key " + key + " is: " + def); //$NON-NLS-1$ //$NON-NLS-2$ } } else { platformSpecificOptionPanel.add(new JLabel(Messages.getString("PlatformSpecificConnectionOptionPanel.unknownDriverClass"))); //$NON-NLS-1$ } platformSpecificOptionPanel.revalidate(); platformSpecificOptionPanel.repaint(); } public JPanel getPanel() { return platformSpecificOptionPanel; } public JDBCDataSourceType getTemplate() { return template; } public void setTemplate(JDBCDataSourceType template) { this.template = template; createFieldsFromTemplate(); updateUrlFromFields(); } }