OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Compare Revisions

Regard whitespace Rev 141 → Rev 142

/trunk/OpenConcerto/.classpath
38,5 → 38,7
<classpathentry kind="lib" path="lib/json-smart-1.3.1.jar"/>
<classpathentry kind="lib" path="lib/icu4j-56_1-module_format+calendar.jar"/>
<classpathentry kind="lib" path="lib/icu4j-56-data-western_europe.jar"/>
<classpathentry kind="lib" path="lib/DS_Desktop_Notify.jar"/>
<classpathentry kind="lib" path="lib/mime_util.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
/trunk/OpenConcerto/lib/jOpenCalendar.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mysql-connector-java-5.1.40-bin.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mysql-connector-java-5.1.40-bin.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/accessors-smart-1.1.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/accessors-smart-1.1.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/jOpenDocument-1.4rc2.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/DS_Desktop_Notify.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/DS_Desktop_Notify.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/json-smart-2.2.1.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/json-smart-2.2.1.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/icudata_56.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/icudata_56.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/mime_util.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mime_util.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/src/org/jopendocument/link/OOConnexion.java
117,15 → 117,33
}
 
public static void main(String[] args) throws IOException {
if (args.length == 0) {
System.out.println("Usage : " + OOConnexion.class.getName() + " officeFile");
if (args.length == 0 || args.length > 2) {
System.out.println("Usage : " + OOConnexion.class.getName() + " officeFile | --type param");
System.out.println("Open officeFile in the default installation of LibreOffice");
System.out.println("--type is either file or url");
System.exit(1);
}
final OOConnexion conn = OOConnexion.create();
if (conn == null)
throw new IllegalStateException("No Office found");
conn.loadDocument(new File(args[0]), false);
final boolean file;
final String arg;
if (args.length == 1) {
file = true;
arg = args[0];
} else if (args[0].equals("--file")) {
file = true;
arg = args[1];
} else if (args[0].equals("--url")) {
file = false;
arg = args[1];
} else {
throw new IllegalArgumentException("Type not valid : " + args[0]);
}
if (file)
conn.loadDocument(new File(arg), false);
else
conn.loadDocumentFromURLAsync(arg, false);
conn.closeConnexion();
}
 
/trunk/OpenConcerto/src/org/jopenchart/sample/devguide/PieChartSample.java
63,14 → 63,13
 
private static void chart3() {
PieChartWithSeparatedLabels c = new PieChartWithSeparatedLabels();
c.setInnerDimension(50, 50);
c.addLabel(new Label("AAAAAA"), Color.red);
c.addLabel(new Label("BBBB"));
c.addLabel(new Label("CCCCCCCCCCCCCCCCCCCCCCCCCCCC"));
c.addLabel(new Label("D"));
c.addLabel(new Label("EEE"));
c.addLabel(new Label("FFF"));
c.addLabel(new Label("GG"));
c.addLabel(new Label("HHH"));
 
c.setDimension(new Dimension(400, 200));
 
ArrayList<Number> l = new ArrayList<Number>();
/trunk/OpenConcerto/src/org/openconcerto/map/model/Ville.java
230,7 → 230,7
}
 
public static synchronized Ville getVilleContaining(String string, String codepostal) {
if (codepostal.length() < 0 && string.length() <= 2) {
if (codepostal.length() == 0 && string.length() <= 2) {
return null;
}
 
/trunk/OpenConcerto/src/org/openconcerto/ui/component/combo/ISearchableCombo.java
16,6 → 16,7
import static org.openconcerto.ui.component.ComboLockedMode.ITEMS_LOCKED;
import static org.openconcerto.ui.component.ComboLockedMode.LOCKED;
import static org.openconcerto.ui.component.ComboLockedMode.UNLOCKED;
 
import org.openconcerto.laf.LAFUtils;
import org.openconcerto.ui.TM;
import org.openconcerto.ui.component.ComboLockedMode;
25,19 → 26,21
import org.openconcerto.ui.component.MutableListCombo;
import org.openconcerto.ui.component.MutableListComboPopupListener;
import org.openconcerto.ui.component.text.DocumentComponent;
import org.openconcerto.ui.component.text.TextBehaviour;
import org.openconcerto.ui.component.text.TextComponent;
import org.openconcerto.ui.valuewrapper.ValueChangeSupport;
import org.openconcerto.ui.valuewrapper.ValueWrapper;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.IdentityHashSet;
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidState;
import org.openconcerto.utils.model.DefaultIMutableListModel;
import org.openconcerto.utils.model.IListModel;
import org.openconcerto.utils.model.IMutableListModel;
import org.openconcerto.utils.model.ISearchable;
import org.openconcerto.utils.model.ListComboBoxModel;
import org.openconcerto.utils.model.NewSelection;
import org.openconcerto.utils.model.Reloadable;
56,6 → 59,7
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusAdapter;
72,11 → 76,9
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
 
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
127,6 → 129,7
protected final ISearchableComboPopup<T> popupCompletion;
// fullList
private final DefaultIMutableListModel<ISearchableComboItem<T>> model;
protected ISearchableComboItem<T> initialValue;
// list for the popup
private final ListComboBoxModel listModel;
private final ISearchableComboItem<T> emptyItem;
160,7 → 163,7
private int minimumSearch = 1;
private int maximumResult = 300;
 
private final Map<T, ISearchableComboItem<T>> itemsByOriginalItem;
private final ListMap<T, ISearchableComboItem<T>> itemsByOriginalItem;
protected boolean updating = false;
private ValidState parsedValidState = ValidState.getTrueInstance();
private ValidState validState = ValidState.createCached(false, "Not initialised");
205,7 → 208,7
// items
this.actions = new ArrayList<Action>();
this.cache = null;
this.itemsByOriginalItem = new HashMap<T, ISearchableComboItem<T>>();
this.itemsByOriginalItem = new ListMap<T, ISearchableComboItem<T>>();
this.model = new DefaultIMutableListModel<ISearchableComboItem<T>>();
this.getModel().setSelectOnAdd(false);
this.setOnRemovingOrReplacingSelection(NewSelection.NONE);
610,12 → 613,10
for (final T originalItem : originalItems) {
final ISearchableComboItem<T> textSelectorItem;
if (this.itemsByOriginalItem.containsKey(originalItem)) {
// allow another item with the same original : add another item to our model, but
// keep the first one in itemsByOriginalItem (this map is only used in setValue() to
// quickly find the ISearchableComboItem)
// allow another item with the same original since our source is a list
textSelectorItem = createItem(originalItem);
// see ISearchableComboPopup.validateSelection()
assert !textSelectorItem.equals(this.itemsByOriginalItem.get(originalItem)) : "Have to not be equal to be able to choose one or the other";
assert !this.itemsByOriginalItem.get(originalItem).contains(textSelectorItem) : "Have to not be equal to be able to choose one or the other";
} else {
// reuse the selected value, otherwise the popup will select nothing
if (sel != null && CompareUtils.equals(selOriginal, originalItem))
622,19 → 623,25
textSelectorItem = sel;
else
textSelectorItem = createItem(originalItem);
this.itemsByOriginalItem.put(originalItem, textSelectorItem);
}
this.itemsByOriginalItem.add(originalItem, textSelectorItem);
toAdd.add(textSelectorItem);
}
// only 1 fire
this.getModel().addAll(index, toAdd);
assert this.itemsByOriginalItem.allValues().size() == getModel().getSize();
}
 
private void rmItemsFromModel(final int index0, final int index1) {
if (index0 == 0 && index1 == getModel().getSize() - 1) {
this.itemsByOriginalItem.clear();
} else {
for (final ISearchableComboItem<T> item : getModel().getList().subList(index0, index1 + 1)) {
this.itemsByOriginalItem.remove(item.getOriginal(), item);
}
}
getModel().removeElementsAt(index0, index1);
// remove from our map
// ATTN for ~35000 items, new HashSet() got us from 6000ms to 8ms !
this.itemsByOriginalItem.keySet().retainAll(new HashSet<T>(getCache().getList()));
assert this.itemsByOriginalItem.allValues().size() == getModel().getSize();
}
 
// conversion
744,11 → 751,12
final ISearchableComboItem<T> comboItem;
// as in docChanged(), "" means empty (otherwise an ISearchableComboItem will be created and
// it won't be contained in the list)
if (val == null || "".equals(val))
if (val == null || "".equals(val) || !valid.isValid()) {
comboItem = null;
else if (this.itemsByOriginalItem.containsKey(val)) {
comboItem = this.itemsByOriginalItem.get(val);
} else if (this.itemsByOriginalItem.containsKey(val)) {
comboItem = this.itemsByOriginalItem.get(val).get(0);
} else {
log("in " + this.getClass().getSimpleName() + ".setValue createItem() since not found");
// always set the passed value, even if it's not valid (e.g.
// getMode().valueMustBeInList())
comboItem = createItem(val);
758,8 → 766,7
 
final void setValue(final ISearchableComboItem<T> val) {
log("entering " + this.getClass().getSimpleName() + ".setValue(ISearchableComboItem) " + val);
assert this.isEmptyItem(val) || new IdentityHashSet<ISearchableComboItem<T>>(this.getModelValues()).contains(val) : "Item not in model, perhaps use setValue(T)";
// valid since val is in our model
// valid since we didn't try to parse a string
this.setComboItemValue(val, ValidState.getTrueInstance());
}
 
801,7 → 808,23
 
this.updateValidState();
this.supp.fireValueChange();
 
// the selection has changed, this is where we reset the search
// some caches force the inclusion of the selection, so clear the search after setting the
// value to avoid :
// 1. clear search
// 2. cache reloads without the selection
// 3. fireValueChange()
// 4. cache reloads again to include the selection
// instead we want :
// 1. fireValueChange()
// 2. no reload since in general the value was in the cache
// 3. clear search
// 4. cache reloads with the selection
if (this.getCache() instanceof ISearchable) {
((ISearchable) this.getCache()).setSearch(null, null);
}
}
 
private final void setText(final String newText) {
if (!this.text.getText().equals(newText)) {
832,9 → 855,11
protected final void docChanged(final DocumentEvent e) {
if (!this.updating) {
final String text = SimpleDocumentListener.getText(e.getDocument());
final T val;
ValidState valid = ValidState.getTrueInstance();
if (text.length() == 0) {
// "" means empty
this.setValue(null, ValidState.getTrueInstance());
val = null;
} else if (this.getMode().valueMustBeInList() && !this.getMode().isListMutable()) {
// value can only be set by the popup (or setMatchingCompletions()) ; the list
// cannot be modified. Thus don't call stringToT() as it might not be implemented
842,14 → 867,18
// this avoids having to decide between 2 different values with the same label, or
// worse this is locked and one of those 2 values are not in us. In that case
// setting the invalid one will in fact select the other.
this.setValue(null, ValidState.createCached(false, "la valeur n'a pas été sélectionnée dans la liste"));
val = null;
valid = ValidState.createCached(false, "la valeur n'a pas été sélectionnée dans la liste");
} else {
T parsed = null;
try {
this.setValue(stringToT(text));
parsed = stringToT(text);
} catch (Exception exn) {
this.setValue(null, ValidState.createCached(false, "la valeur n'est pas correcte: " + exn.getLocalizedMessage()));
valid = ValidState.createCached(false, "la valeur n'est pas correcte: " + exn.getLocalizedMessage());
}
val = valid.isValid() ? parsed : null;
}
this.setValue(val, valid);
if (this.isSearchable())
this.updateAutoCompletion(text);
}
1202,6 → 1231,8
@Override
public void focusGained(FocusEvent e) {
firePropertyChange("textCompFocused", false, true);
// set on focus gained like TextBehaviour
ISearchableCombo.this.initialValue = getSelection();
}
 
@Override
1211,6 → 1242,20
firePropertyChange("textCompFocused", true, false);
}
});
this.text.getActionMap().put(TextBehaviour.REVERT_ACTION_KEY, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
final ISearchableComboItem<T> v = ISearchableCombo.this.initialValue;
if (v == null || getModel().getList().contains(v)) {
// quicker and allow to tell apart original items that are equal
setValue(v);
} else {
// the combo might have been refreshed several times and the initial item gone.
// But there might be a new one with the same value.
setValue(v.getOriginal());
}
}
});
this.text.addMouseListener(this.clickL);
this.text.addMouseMotionListener(this.dragL);
}
/trunk/OpenConcerto/src/org/openconcerto/ui/component/combo/ISearchableComboCompletionThread.java
87,6 → 87,9
final String aText = this.t.trim();
final String normalizedText = aText.length() < minimumSearch ? "" : aText;
 
// null : not searchable
// true : search changed
// false : search didn't change
Boolean searched = null;
// If there was a search and now the text is below minimum, we must unset the search
// Efficient since setSearch() only carry out its action if the search changes
/trunk/OpenConcerto/src/org/openconcerto/ui/component/text/TextBehaviour.java
13,22 → 13,34
package org.openconcerto.ui.component.text;
 
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
 
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.text.JTextComponent;
 
/**
* Listens to its passed text component to select all on focus gained and revert when pressing ESC.
* To control the revert action use {@link #REVERT_ACTION_KEY}.
*
* @author Sylvain
*/
public class TextBehaviour {
 
/**
* Use this key to register the revert action in {@link JComponent#getActionMap()}. If none is
* set, this class will simply set an action that {@link JTextComponent#setText(String) restore}
* the text that was on {@link FocusListener#focusGained(FocusEvent) focus gained}.
*/
public static final String REVERT_ACTION_KEY = "action map key to revert value";
 
// MAYBE faire un stopManage() qui enlèverait tous les listeners
public static final TextBehaviour manage(JTextComponent comp) {
return new TextBehaviour(comp);
48,6 → 60,7
// select all on focus gained
// except if the user is selecting with the mouse
this.comp.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
TextBehaviour.this.gained = true;
TextBehaviour.this.initialText = TextBehaviour.this.comp.getText();
57,10 → 70,12
}
});
this.comp.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
TextBehaviour.this.mousePressed = true;
}
 
@Override
public void mouseReleased(MouseEvent e) {
// don't override the user selection
if (TextBehaviour.this.gained && TextBehaviour.this.comp.getSelectedText() == null) {
71,15 → 86,16
TextBehaviour.this.mousePressed = false;
}
});
this.comp.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent keyEvent) {
// Sert a annuler une saisie
if (keyEvent.getKeyChar() == KeyEvent.VK_ESCAPE) {
this.comp.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), REVERT_ACTION_KEY);
if (this.comp.getActionMap().get(REVERT_ACTION_KEY) == null) {
this.comp.getActionMap().put(REVERT_ACTION_KEY, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
TextBehaviour.this.comp.setText(TextBehaviour.this.initialText);
TextBehaviour.this.comp.selectAll();
}
}
});
}
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/ui/component/DialogCallbackHandler.java
New file
0,0 → 1,307
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.component;
 
/* Java imports */
import java.awt.Component;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
 
/* JAAS imports */
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.ConfirmationCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
 
/**
* <p>
* Uses a Swing dialog window to query the user for answers to authentication questions. This can be
* used by a JAAS application to instantiate a CallbackHandler
*
* @see javax.security.auth.callback
*/
public class DialogCallbackHandler implements CallbackHandler {
 
/* -- Fields -- */
 
/* The parent window, or null if using the default parent */
private final Component parentComponent;
private static final int JPasswordFieldLen = 8;
private static final int JTextFieldLen = 8;
 
/* -- Methods -- */
 
/**
* Creates a callback dialog with the default parent window.
*/
public DialogCallbackHandler() {
this(null);
}
 
/**
* Creates a callback dialog and specify the parent window.
*
* @param parentComponent the parent window -- specify <code>null</code> for the default parent
*/
public DialogCallbackHandler(final Component parentComponent) {
this.parentComponent = parentComponent;
}
 
/*
* An interface for recording actions to carry out if the user clicks OK for the dialog.
*/
private static interface Action {
void perform();
}
 
/**
* Handles the specified set of callbacks.
*
* @param callbacks the callbacks to handle
* @throws UnsupportedCallbackException if the callback is not an instance of NameCallback or
* PasswordCallback
*/
@Override
public void handle(final Callback[] callbacks) throws UnsupportedCallbackException {
/* Collect messages to display in the dialog */
final List<Object> messages = new ArrayList<Object>(3);
 
/* Collection actions to perform if the user clicks OK */
final List<Action> okActions = new ArrayList<Action>(2);
 
final ConfirmationInfo confirmation = new ConfirmationInfo();
final List<JTextComponent> fields = new ArrayList<JTextComponent>();
 
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
final TextOutputCallback tc = (TextOutputCallback) callbacks[i];
 
switch (tc.getMessageType()) {
case TextOutputCallback.INFORMATION:
confirmation.messageType = JOptionPane.INFORMATION_MESSAGE;
break;
case TextOutputCallback.WARNING:
confirmation.messageType = JOptionPane.WARNING_MESSAGE;
break;
case TextOutputCallback.ERROR:
confirmation.messageType = JOptionPane.ERROR_MESSAGE;
break;
default:
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized message type");
}
 
messages.add(tc.getMessage());
 
} else if (callbacks[i] instanceof NameCallback) {
final NameCallback nc = (NameCallback) callbacks[i];
 
final JLabel prompt = new JLabel(nc.getPrompt());
 
final JTextField name = new JTextField(JTextFieldLen);
final String defaultName = nc.getDefaultName();
if (defaultName != null) {
name.setText(defaultName);
}
 
/*
* Put the prompt and name in a horizontal box, and add that to the set of messages.
*/
final Box namePanel = Box.createHorizontalBox();
namePanel.add(prompt);
namePanel.add(name);
messages.add(namePanel);
 
/* Store the name back into the callback if OK */
okActions.add(new Action() {
@Override
public void perform() {
nc.setName(name.getText());
}
});
fields.add(name);
} else if (callbacks[i] instanceof PasswordCallback) {
final PasswordCallback pc = (PasswordCallback) callbacks[i];
 
final JLabel prompt = new JLabel(pc.getPrompt());
 
final JPasswordField password = new JPasswordField(JPasswordFieldLen);
if (!pc.isEchoOn()) {
password.setEchoChar('*');
}
 
final Box passwordPanel = Box.createHorizontalBox();
passwordPanel.add(prompt);
passwordPanel.add(password);
messages.add(passwordPanel);
 
okActions.add(new Action() {
@Override
public void perform() {
pc.setPassword(password.getPassword());
}
});
fields.add(password);
} else if (callbacks[i] instanceof ConfirmationCallback) {
final ConfirmationCallback cc = (ConfirmationCallback) callbacks[i];
 
confirmation.setCallback(cc);
if (cc.getPrompt() != null) {
messages.add(cc.getPrompt());
}
 
} else {
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
 
// TODO also create dialog in the EDT
final RunnableFuture<Integer> f = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
if (!fields.isEmpty()) {
fields.get(0).addHierarchyListener(new HierarchyListener() {
@Override
public void hierarchyChanged(final HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && e.getComponent().isVisible()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
e.getComponent().requestFocus();
}
});
}
}
});
}
 
/* Display the dialog */
return JOptionPane.showOptionDialog(DialogCallbackHandler.this.parentComponent, messages.toArray(), "Confirmation", /* title */
confirmation.optionType, confirmation.messageType, null, /* icon */
confirmation.options, /* options */
confirmation.initialValue); /* initialValue */
 
}
});
SwingUtilities.invokeLater(f);
final int result;
try {
result = f.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
/* Perform the OK actions */
if (result == JOptionPane.OK_OPTION || result == JOptionPane.YES_OPTION) {
final Iterator<Action> iterator = okActions.iterator();
while (iterator.hasNext()) {
iterator.next().perform();
}
}
confirmation.handleResult(result);
}
 
/*
* Provides assistance with translating between JAAS and Swing confirmation dialogs.
*/
private static class ConfirmationInfo {
 
private int[] translations;
 
int optionType = JOptionPane.OK_CANCEL_OPTION;
Object[] options = null;
Object initialValue = null;
 
int messageType = JOptionPane.QUESTION_MESSAGE;
 
private ConfirmationCallback callback;
 
/* Set the confirmation callback handler */
void setCallback(final ConfirmationCallback callback) throws UnsupportedCallbackException {
this.callback = callback;
 
final int confirmationOptionType = callback.getOptionType();
switch (confirmationOptionType) {
case ConfirmationCallback.YES_NO_OPTION:
this.optionType = JOptionPane.YES_NO_OPTION;
this.translations = new int[] { JOptionPane.YES_OPTION, ConfirmationCallback.YES, JOptionPane.NO_OPTION, ConfirmationCallback.NO, JOptionPane.CLOSED_OPTION, ConfirmationCallback.NO };
break;
case ConfirmationCallback.YES_NO_CANCEL_OPTION:
this.optionType = JOptionPane.YES_NO_CANCEL_OPTION;
this.translations = new int[] { JOptionPane.YES_OPTION, ConfirmationCallback.YES, JOptionPane.NO_OPTION, ConfirmationCallback.NO, JOptionPane.CANCEL_OPTION,
ConfirmationCallback.CANCEL, JOptionPane.CLOSED_OPTION, ConfirmationCallback.CANCEL };
break;
case ConfirmationCallback.OK_CANCEL_OPTION:
this.optionType = JOptionPane.OK_CANCEL_OPTION;
this.translations = new int[] { JOptionPane.OK_OPTION, ConfirmationCallback.OK, JOptionPane.CANCEL_OPTION, ConfirmationCallback.CANCEL, JOptionPane.CLOSED_OPTION,
ConfirmationCallback.CANCEL };
break;
case ConfirmationCallback.UNSPECIFIED_OPTION:
this.options = callback.getOptions();
/*
* There's no way to know if the default option means to cancel the login, but there
* isn't a better way to guess this.
*/
this.translations = new int[] { JOptionPane.CLOSED_OPTION, callback.getDefaultOption() };
break;
default:
throw new UnsupportedCallbackException(callback, "Unrecognized option type: " + confirmationOptionType);
}
 
final int confirmationMessageType = callback.getMessageType();
switch (confirmationMessageType) {
case ConfirmationCallback.WARNING:
this.messageType = JOptionPane.WARNING_MESSAGE;
break;
case ConfirmationCallback.ERROR:
this.messageType = JOptionPane.ERROR_MESSAGE;
break;
case ConfirmationCallback.INFORMATION:
this.messageType = JOptionPane.INFORMATION_MESSAGE;
break;
default:
throw new UnsupportedCallbackException(callback, "Unrecognized message type: " + confirmationMessageType);
}
}
 
/* Process the result returned by the Swing dialog */
void handleResult(int result) {
if (this.callback == null) {
return;
}
 
for (int i = 0; i < this.translations.length; i += 2) {
if (this.translations[i] == result) {
result = this.translations[i + 1];
break;
}
}
this.callback.setSelectedIndex(result);
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/component/ITextCombo.java
18,6 → 18,7
 
import static org.openconcerto.ui.component.ComboLockedMode.LOCKED;
import static org.openconcerto.ui.component.ComboLockedMode.UNLOCKED;
 
import org.openconcerto.ui.component.InteractionMode.InteractionComponent;
import org.openconcerto.ui.component.combo.ISearchableComboPopup;
import org.openconcerto.ui.component.text.TextComponent;
492,12 → 493,20
* @return <code>true</code> if s is really added.
*/
private final boolean addToCache(String s) {
if (s != null && s.length() > 0 && this.getListModel().getList().indexOf(s) < 0) {
this.addItem(makeObj(s));
return true;
} else
return false;
final boolean added = s != null && s.length() > 0 && this.getListModel().getList().indexOf(s) < 0;
if (added) {
final Object obj = makeObj(s);
// BasicComboBoxUI$Handler.intervalAdded(ListDataEvent) calls contentsChanged() which
// calls JComboBox.configureEditor() with the current selection. But the selection is
// only set by BasicComboBoxUI.Handler.focusLost() so this.addItem() replaces the
// current editor value by the current selection.
this.completing = true;
this.setSelectedItem(obj);
this.completing = false;
this.addItem(obj);
}
return added;
}
 
private final void removeCurrentText() {
final String t = this.getTextComp().getText();
/trunk/OpenConcerto/src/org/openconcerto/ui/component/HTMLTextField.java
13,9 → 13,14
package org.openconcerto.ui.component;
 
import org.openconcerto.ui.TM;
import org.openconcerto.utils.ExceptionHandler;
 
import java.awt.Component;
import java.awt.Desktop;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
25,6 → 30,7
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkEvent.EventType;
74,12 → 80,22
}
 
protected void linkActivated(final HyperlinkEvent e, final JComponent src) {
final URI uri = getURI(e);
if (uri == null)
return;
if ("file".equals(uri.getScheme())) {
final File f = new File(uri.getPath());
if (!f.exists()) {
JOptionPane.showMessageDialog((Component) e.getSource(), TM.tr("missingFile", f), null, JOptionPane.WARNING_MESSAGE);
return;
}
}
// this ties into the OS and can block
exec.submit(new Runnable() {
@Override
public void run() {
try {
Desktop.getDesktop().browse(e.getURL().toURI());
Desktop.getDesktop().browse(uri);
} catch (Exception exn) {
ExceptionHandler.handle(src, org.openconcerto.utils.i18n.TM.tr("linkOpenError", e.getURL()), exn);
}
86,4 → 102,13
}
});
}
 
private URI getURI(final HyperlinkEvent e) {
try {
return e.getURL().toURI();
} catch (URISyntaxException exn) {
ExceptionHandler.handle((Component) e.getSource(), "Couldn't get URI", exn);
return null;
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/valuewrapper/ValueWrapperFactory.java
43,11 → 43,12
if (isValueWrapper(comp, c)) {
return (ValueWrapper<Z>) comp;
} else if (getConvertorVW(comp, c) != null) {
// e.g. comp is ValueWrapper<X> and there's a convertor between X and Z
return getConvertorVW(comp, c);
} else if (isValueObject(comp, c)) {
return new ValueWrapperFromVO<Z>((MutableValueObject) comp);
return new ValueWrapperFromVO<Z>((MutableValueObject<Z>) comp);
} else if (comp instanceof JFormattedTextField && JFormattedTextFieldValueWrapper.isCompatible((JFormattedTextField) comp, c)) {
return new JFormattedTextFieldValueWrapper((JFormattedTextField) comp, c);
return new JFormattedTextFieldValueWrapper<Z>((JFormattedTextField) comp, c);
} else if (Boolean.class.isAssignableFrom(c)) {
return (ValueWrapper<Z>) new BooleanValueWrapper((JToggleButton) comp);
} else if (String.class.isAssignableFrom(c))
61,7 → 62,7
private static <Z> boolean isValueWrapper(final JComponent comp, final Class<Z> c) {
if (!(comp instanceof ValueWrapper))
return false;
return ReflectUtils.isCastable((ValueWrapper) comp, ValueWrapper.class, c);
return ReflectUtils.isCastable((ValueWrapper<?>) comp, ValueWrapper.class, c);
}
 
@SuppressWarnings("unchecked")
72,6 → 73,7
final List<Class<?>> typeArguments = ReflectUtils.getTypeArguments(vw, ValueWrapper.class);
if (typeArguments.size() == 0)
throw new IllegalArgumentException("unable to find type arguments of " + vw + " \n(you should define a class that specify them, eg class C extends ValueWrapper<Integer>)");
assert typeArguments.size() == 1 : "Wrong number of arguments for ValueWrapper : " + typeArguments;
final Class<?> typeArgument = typeArguments.get(0);
return createCVW(vw, typeArgument, c);
}
87,7 → 89,7
private static <Z> boolean isValueObject(final JComponent comp, final Class<Z> c) {
if (!(comp instanceof MutableValueObject))
return false;
return ReflectUtils.isCastable((MutableValueObject) comp, ValueObject.class, c);
return ReflectUtils.isCastable((MutableValueObject<?>) comp, ValueObject.class, c);
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/ui/JDateTime.java
99,6 → 99,7
this.add(this.time, c);
 
this.resetValue();
updateValue();
}
 
protected void updateValue() {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/NotSpecifedConvertor.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/ui/light/IUserControlContainer.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIFilesWithSelectionContext.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/ui/light/IUserControl.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIListRow.java
New file
0,0 → 1,69
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.io.JSONConverter;
 
import net.minidev.json.JSONObject;
 
public class LightUIListRow extends LightUIPanel {
private Number rowId;
 
// Init from json constructor
public LightUIListRow(final JSONObject json) {
super(json);
}
 
// Clone constructor
public LightUIListRow(final LightUIListRow listItem) {
super(listItem);
this.rowId = listItem.rowId;
}
 
public LightUIListRow(final LightUIList parent, final Number rowId) {
super(parent.getId() + ".item.panel." + rowId.toString());
this.rowId = rowId;
this.setType(TYPE_LIST_ROW);
this.setFillHeight(false);
}
 
public LightUIListRow(final LightUIList parent, final Number id, final String label) {
this(parent, id);
 
final LightUILine line = new LightUILine();
line.addChild(new LightUILabel(this.getId() + ".label", label));
this.addChild(line);
}
 
public Number getRowId() {
return this.rowId;
}
 
public LightUIListRow clone() {
return new LightUIListRow(this);
}
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
json.put("row-id", this.getRowId());
return json;
}
 
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
this.rowId = JSONConverter.getParameterFromJSON(json, "row-id", Number.class);
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/ColumnsSpec.java
13,6 → 13,7
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.io.JSONConverter;
import org.openconcerto.utils.io.Transferable;
 
63,9 → 64,6
if (columns == null) {
throw new IllegalArgumentException("null columns");
}
if (columns.isEmpty()) {
throw new IllegalArgumentException("empty columns");
}
this.columns = columns;
 
// Possible checks
72,9 → 70,6
if (possibleColumnIds == null) {
throw new IllegalArgumentException("null possible column ids");
}
if (possibleColumnIds.isEmpty()) {
throw new IllegalArgumentException("empty possible column ids");
}
this.possibleColumnIds = possibleColumnIds;
 
// Sort assign
82,62 → 77,62
 
}
 
public String getId() {
public final String getId() {
return this.id;
}
 
public List<String> getPossibleColumnIds() {
public final List<String> getPossibleColumnIds() {
return this.possibleColumnIds;
}
 
public List<String> getSortedIds() {
public final List<String> getSortedIds() {
return this.sortedIds;
}
 
public int getFixedColumns() {
public final int getFixedColumns() {
return this.fixedColumns;
 
}
 
public int getColumnCount() {
public final int getColumnCount() {
return this.columns.size();
 
}
 
public ColumnSpec getColumn(int i) {
public final ColumnSpec getColumn(int i) {
return this.columns.get(i);
}
 
public ColumnSpec setColumn(int i, final ColumnSpec column) {
public final ColumnSpec setColumn(int i, final ColumnSpec column) {
return this.columns.set(i, column);
}
 
public Boolean isAdaptWidth() {
public final Boolean isAdaptWidth() {
return this.adaptWidth;
}
 
public void setAdaptWidth(final boolean adaptWidth) {
public final void setAdaptWidth(final boolean adaptWidth) {
this.adaptWidth = adaptWidth;
}
 
public Boolean isAllowMove() {
public final Boolean isAllowMove() {
return this.allowMove;
}
 
public void setAllowMove(final boolean allowMove) {
public final void setAllowMove(final boolean allowMove) {
this.allowMove = allowMove;
}
 
public Boolean isAllowResize() {
public final Boolean isAllowResize() {
return this.allowResize;
}
 
public void setAllowResize(final boolean allowResize) {
public final void setAllowResize(final boolean allowResize) {
this.allowResize = allowResize;
}
 
public List<String> getColumnsIds() {
ArrayList<String> result = new ArrayList<String>(this.columns.size());
final ArrayList<String> result = new ArrayList<String>(this.columns.size());
for (ColumnSpec c : this.columns) {
result.add(c.getId());
}
144,8 → 139,7
return result;
}
 
public void setUserPrefs(final Document columnsPrefs) {
 
public final boolean setUserPrefs(final Document columnsPrefs) {
if (columnsPrefs != null) {
// user preferences application
final Element rootElement = columnsPrefs.getRootElement();
173,9 → 167,9
throw new IllegalArgumentException("ColumnSpec setPrefs - Invalid column node for " + columnId + ", it must have attribute width, min-width, max-width");
}
 
final int width = Integer.parseInt(xmlColumn.getAttribute("width").getValue());
final int maxWidth = Integer.parseInt(xmlColumn.getAttribute("max-width").getValue());
final int minWidth = Integer.parseInt(xmlColumn.getAttribute("min-width").getValue());
final double width = Double.parseDouble(xmlColumn.getAttribute("width").getValue());
final double maxWidth = Double.parseDouble(xmlColumn.getAttribute("max-width").getValue());
final double minWidth = Double.parseDouble(xmlColumn.getAttribute("min-width").getValue());
 
columnSpec.setPrefs(width, maxWidth, minWidth);
if (i != j) {
182,6 → 176,8
final ColumnSpec swap = this.columns.get(i);
this.columns.set(i, this.columns.get(j));
this.columns.set(j, swap);
i--;
}
find = true;
break;
188,27 → 184,16
}
}
if (!find) {
System.out.println("XML columns preferences does'nt contain this column: " + columnId);
return false;
}
}
} else {
System.out.println("ColumnsSpec.setUserPrefs() - Incorrect columns count in XML for ColumnsSpec: " + this.id);
return false;
}
}
return true;
}
 
public Document createDefaultXmlPref() {
final Element rootElement = new Element("list");
 
for (final ColumnSpec column : this.columns) {
final Element columnElement = column.createXmlColumnPref();
rootElement.addContent(columnElement);
}
final Document xmlConf = new Document(rootElement);
 
return xmlConf;
}
 
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(this.id);
225,15 → 210,15
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readUTF();
this.fixedColumns = in.readInt();
this.columns = (List<ColumnSpec>) in.readObject();
this.possibleColumnIds = (List<String>) in.readObject();
this.sortedIds = (List<String>) in.readObject();
this.columns = CollectionUtils.castList((List<?>) in.readObject(), ColumnSpec.class);
this.possibleColumnIds = CollectionUtils.castList((List<?>) in.readObject(), String.class);
this.sortedIds = CollectionUtils.castList((List<?>) in.readObject(), String.class);
this.allowMove = in.readBoolean();
this.allowResize = in.readBoolean();
this.adaptWidth = in.readBoolean();
}
 
public List<Object> getDefaultValues() {
public final List<Object> getDefaultValues() {
final List<Object> l = new ArrayList<Object>();
for (ColumnSpec column : this.columns) {
final Object v = column.getDefaultValue();
242,7 → 227,7
return l;
}
 
public ColumnSpec getColumn(String id) {
public final ColumnSpec getColumn(String id) {
for (ColumnSpec c : this.columns) {
if (c.getId().equals(id)) {
return c;
251,7 → 236,7
return null;
}
 
public ColumnSpec getColumnWithEditor(String id) {
public final ColumnSpec getColumnWithEditor(String id) {
for (ColumnSpec c : this.columns) {
LightUIElement editor = c.getEditor();
if (editor != null && c.getEditor().getId().equals(id)) {
290,30 → 275,19
 
@Override
public void fromJSON(final JSONObject json) {
this.id = (String) JSONConverter.getParameterFromJSON(json, "id", String.class);
this.fixedColumns = (Integer) JSONConverter.getParameterFromJSON(json, "fixed-columns", Integer.class);
this.adaptWidth = (Boolean) JSONConverter.getParameterFromJSON(json, "adapt-width", Boolean.class, false);
this.allowMove = (Boolean) JSONConverter.getParameterFromJSON(json, "allow-move", Boolean.class, false);
this.allowResize = (Boolean) JSONConverter.getParameterFromJSON(json, "allow-resize", Boolean.class, false);
final JSONArray jsonSortedIds = (JSONArray) JSONConverter.getParameterFromJSON(json, "sorted-ids", JSONArray.class, null);
if (jsonSortedIds != null) {
this.sortedIds = (List<String>) (List<?>) jsonSortedIds;
}
final JSONArray jsonPossibleColumnIds = (JSONArray) JSONConverter.getParameterFromJSON(json, "possible-column-ids", JSONArray.class, null);
if (jsonPossibleColumnIds != null) {
this.possibleColumnIds = (List<String>) (List<?>) jsonPossibleColumnIds;
}
this.id = JSONConverter.getParameterFromJSON(json, "id", String.class);
this.fixedColumns = JSONConverter.getParameterFromJSON(json, "fixed-columns", Integer.class);
this.adaptWidth = JSONConverter.getParameterFromJSON(json, "adapt-width", Boolean.class, false);
this.allowMove = JSONConverter.getParameterFromJSON(json, "allow-move", Boolean.class, false);
this.allowResize = JSONConverter.getParameterFromJSON(json, "allow-resize", Boolean.class, false);
this.sortedIds = CollectionUtils.castList(JSONConverter.getParameterFromJSON(json, "sorted-ids", List.class, new ArrayList<String>()), String.class);
this.possibleColumnIds = CollectionUtils.castList(JSONConverter.getParameterFromJSON(json, "possible-column-ids", List.class, new ArrayList<String>()), String.class);
 
final JSONArray jsonColumns = (JSONArray) JSONConverter.getParameterFromJSON(json, "columns", JSONArray.class, null);
final List<JSONObject> jsonColumns = CollectionUtils.castList(JSONConverter.getParameterFromJSON(json, "columns", JSONArray.class, null), JSONObject.class);
if (jsonColumns != null) {
final int columnsSize = jsonColumns.size();
for (int i = 0; i < columnsSize; i++) {
final Object objColumnSpec = jsonColumns.get(i);
if (!(objColumnSpec instanceof JSONObject)) {
throw new IllegalArgumentException("invalid value for 'columns', List<ColumnSpec> expected");
for (final JSONObject jsonColumn : jsonColumns) {
this.columns.add(new ColumnSpec(jsonColumn));
}
this.columns.add(new ColumnSpec((JSONObject) objColumnSpec));
}
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/CustomEditorProvider.java
13,8 → 13,6
package org.openconcerto.ui.light;
 
public interface CustomEditorProvider {
 
public LightUIElement createUIElement(String id);
 
public abstract class CustomEditorProvider {
public abstract LightUIElement createUIElement(final String id);
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIButton.java
58,4 → 58,10
listener.actionPerformed(new ActionEvent(this, 1, "click"));
}
}
 
@Override
public void destroy() {
super.destroy();
this.clickListeners.clear();
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUITextArea.java
17,7 → 17,7
 
import net.minidev.json.JSONObject;
 
public class LightUITextArea extends LightUIElement implements IUserControl {
public class LightUITextArea extends LightUserControl {
 
private int nbLine = 4;
 
70,13 → 70,13
}
 
@Override
public void setValueFromContext(final Object value) {
public void _setValueFromContext(final Object value) {
this.setValue((String) JSONConverter.getObjectFromJSON(value, String.class));
}
 
@Override
public Object getValueFromContext() {
return this.getValue();
public Object getValueForContext() {
return (this.getValue() == null) ? null : ((this.getValue().trim().equals("")) ? null : this.getValue());
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUILabel.java
40,21 → 40,15
}
 
public LightUILabel(final String id) {
super(id);
this.setType(TYPE_LABEL);
this(id, "", false);
}
 
public LightUILabel(final String id, final boolean isBold) {
super(id);
this.setType(TYPE_LABEL);
this.setFontBold(isBold);
this(id, "", isBold);
}
 
public LightUILabel(final String id, final String label) {
super(id);
this.setType(TYPE_LABEL);
 
this.setLabel(label);
this(id, label, false);
}
 
public LightUILabel(final String id, final String label, final boolean isBold) {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/StringValueConvertor.java
14,6 → 14,7
package org.openconcerto.ui.light;
 
import java.util.List;
import java.util.Map.Entry;
 
public abstract class StringValueConvertor extends ComboValueConvertor<String> {
 
30,9 → 31,11
/**
* @param index: index of listId.
*
* @return id store at index in listId.
* @return id store at index in listId or null if index is under 1.
*
* @throws IllegalArgumentException when index is out out bounds or if list of ids is not initialized
*/
public String getIdFromIndex(final Integer index) {
public String getIdFromIndex(final Integer index) throws IllegalArgumentException {
int realIndex = index;
// values start at 1.
realIndex = index - 1;
42,13 → 45,18
if (realIndex >= this.listId.size()) {
throw new IllegalArgumentException("index is out of bounds");
}
if(realIndex == -1){
return null;
} else {
return this.listId.get(realIndex);
}
}
 
/**
* @param id: id store in listId.
*
* @return index of id in listId.
* @return index of id in listId or null if not found.
*/
public Integer getIndexFromId(final String id) {
if (this.listId == null) {
66,12 → 74,16
}
@Override
public void fillComboWithValue(final LightUIComboBox combo) {
public void fillComboWithValue(final LightUIComboBox combo, final String selectedValue) {
int i = 1;
for(final String value : this.values.values()) {
for (final Entry<String, String> entry : this.values.entrySet()) {
final LightUIComboBoxElement comboElement = new LightUIComboBoxElement(i);
comboElement.setValue1(value);
comboElement.setValue1(entry.getValue());
combo.addValue(comboElement);
 
if (entry.getKey().equals(selectedValue)) {
combo.setSelectedValue(comboElement);
}
i++;
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUITabbed.java
17,7 → 17,7
 
import net.minidev.json.JSONObject;
 
public abstract class LightUITabbed extends LightUIContainer implements IUserControlContainer {
public abstract class LightUITabbed extends LightUserControlContainer {
// Init from json constructor
public LightUITabbed(final JSONObject json) {
super(json);
50,13 → 50,16
}
 
@Override
public void setValueFromContext(final Object value) {
public void _setValueFromContext(final Object value) {
final JSONObject jsonContext = (JSONObject) JSONConverter.getObjectFromJSON(value, JSONObject.class);
if (jsonContext == null) {
System.err.println("LightUITabbed.setValueFromContext() - json is null for this panel: " + this.getId());
} else {
final int childCount = this.getChildrenCount();
for (int i = 0; i < childCount; i++) {
final LightUITab child = this.getChild(i, LightUITab.class);
if (jsonContext.containsKey(child.getUUID())) {
child.setValueFromContext(jsonContext.get(child.getUUID()));
child._setValueFromContext(jsonContext.get(child.getUUID()));
} else {
System.out.println("LightUITabbed.setValueFromContext() - Context doesn't contains value for UUID: " + child.getUUID() + " ID: " + child.getId());
}
63,3 → 66,4
}
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUserControl.java
New file
0,0 → 1,46
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import net.minidev.json.JSONObject;
 
public abstract class LightUserControl extends LightUIElement {
public LightUserControl(final JSONObject json) {
super(json);
}
 
public LightUserControl(final String id) {
super(id);
}
 
public LightUserControl(final LightUserControl userControl) {
super(userControl);
}
 
public void setValueFromContext(final Object value) {
if (this.isEnabled()) {
this._setValueFromContext(value);
}
}
 
/**
* Set value for all elements send by client with buttons TYPE_BUTTON_WITH_SELECTION_CONTEXT and
* TYPE_BUTTON_WITH_CONTEXT
*
* @param value to set for element
*/
protected abstract void _setValueFromContext(final Object value);
 
public abstract Object getValueForContext();
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUISlider.java
New file
0,0 → 1,102
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.io.JSONConverter;
 
import net.minidev.json.JSONObject;
 
public class LightUISlider extends LightUserControl {
Integer maxValue = 1;
Integer minValue = 0;
Integer increment = 1;
 
public LightUISlider(final String id) {
super(id);
this.setType(TYPE_SLIDER);
this.setValueType(VALUE_TYPE_INTEGER);
this.setValue("0");
}
 
public LightUISlider(final JSONObject json) {
super(json);
}
 
public LightUISlider(final LightUISlider slider) {
super(slider);
this.maxValue = slider.maxValue;
this.minValue = slider.minValue;
this.increment = slider.increment;
}
 
public Integer getMaxValue() {
return this.maxValue;
}
 
public void setMaxValue(final int maxValue) {
this.maxValue = maxValue;
}
 
public Integer getMinValue() {
return this.minValue;
}
 
public void setMinValue(final int minValue) {
this.minValue = minValue;
}
 
public Integer getIncrement() {
return this.increment;
}
 
public void setIncrement(final int increment) {
this.increment = increment;
}
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
json.put("max-value", this.maxValue);
json.put("min-value", this.minValue);
json.put("increment", this.increment);
 
return json;
}
 
@Override
public void fromJSON(JSONObject json) {
super.fromJSON(json);
this.maxValue = JSONConverter.getParameterFromJSON(json, "max-value", Integer.class, 1);
this.minValue = JSONConverter.getParameterFromJSON(json, "min-value", Integer.class, 0);
this.increment = JSONConverter.getParameterFromJSON(json, "increment", Integer.class, 1);
}
 
@Override
public void _setValueFromContext(Object value) {
if (value instanceof Number) {
this.setValue(value.toString());
} else {
throw new IllegalArgumentException("Incorrect value " + value + "type for ui element: " + this.getId());
}
}
 
@Override
public Object getValueForContext() {
if (this.getValue() == null) {
return null;
} else {
return Integer.parseInt(this.getValue());
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIContainer.java
22,7 → 22,7
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
 
public abstract class LightUIContainer extends LightUIElement {
public class LightUIContainer extends LightUIElement {
private List<LightUIElement> children;
 
public LightUIContainer(final String id) {
47,6 → 47,15
this.children.add(child);
}
 
public void addChildren(final List<LightUIElement> children) {
if (children == null) {
throw new IllegalArgumentException("List null, id:" + this.getId());
}
for(final LightUIElement child: children){
this.addChild(child);
}
}
 
public void insertChild(final int index, final LightUIElement child) {
if (child == null) {
throw new IllegalArgumentException("Attempt to put null child in container, id:" + this.getId());
72,7 → 81,7
 
public <T extends LightUIElement> T getChild(final int index, final Class<T> expectedClass) {
if (this.children != null)
return (T) this.children.get(index);
return expectedClass.cast(this.children.get(index));
else
return null;
}
79,7 → 88,7
 
public <T extends LightUIElement> T getFirstChild(final Class<T> expectedClass) {
if (this.getChildrenCount() > 0)
return (T) this.children.get(0);
return expectedClass.cast(this.children.get(0));
else
return null;
}
104,7 → 113,7
final LightUIElement child = this.children.get(i);
if (child.getId().equals(pChild.getId())) {
this.children.set(i, pChild);
child.setParent(this);
pChild.setParent(this);
return true;
}
if (child instanceof LightUIContainer) {
125,7 → 134,7
return this.findChild(searchParam, byUUID, LightUIElement.class);
}
 
public <T extends LightUIElement> T findChild(final String searchParam, final boolean byUUID, final Class<T> childClass) {
public <T extends LightUIElement> T findChild(final String searchParam, final boolean byUUID, final Class<T> expectedClass) {
final int childCount = this.getChildrenCount();
LightUIElement result = null;
for (int i = 0; i < childCount; i++) {
143,7 → 152,7
}
 
if (child instanceof LightUIContainer) {
result = ((LightUIContainer) child).findChild(searchParam, byUUID, childClass);
result = ((LightUIContainer) child).findChild(searchParam, byUUID, expectedClass);
if (result != null) {
break;
}
150,7 → 159,7
}
 
if (child instanceof LightUITable) {
result = ((LightUITable) child).findElement(searchParam, byUUID, childClass);
result = ((LightUITable) child).findElement(searchParam, byUUID, expectedClass);
if (result != null) {
break;
}
158,15 → 167,39
}
 
if (result != null) {
if (childClass.isAssignableFrom(result.getClass())) {
return (T) result;
if (expectedClass.isAssignableFrom(result.getClass())) {
return expectedClass.cast(result);
} else {
throw new InvalidClassException(childClass.getName(), result.getClass().getName(), result.getId());
throw new InvalidClassException(expectedClass.getName(), result.getClass().getName(), result.getId());
}
}
return null;
}
 
public <T extends LightUIElement> List<T> findChildren(final Class<T> expectedClass, final boolean recursively) {
final List<T> result = new ArrayList<T>();
final int childCount = this.getChildrenCount();
for (int i = 0; i < childCount; i++) {
final LightUIElement child = this.getChild(i);
 
if (recursively) {
if (child instanceof LightUIContainer) {
result.addAll(((LightUIContainer) child).findChildren(expectedClass, true));
}
 
if (child instanceof LightUITable) {
result.addAll(((LightUITable) child).findChildren(expectedClass, true));
}
}
 
if (expectedClass.isAssignableFrom(child.getClass())) {
result.add(expectedClass.cast(child));
}
}
 
return result;
}
 
@Override
protected void copy(LightUIElement element) {
super.copy(element);
230,4 → 263,12
}
}
}
 
@Override
public void destroy() {
super.destroy();
for (final LightUIElement child : this.children) {
child.destroy();
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/RowsBulk.java
31,6 → 31,7
private List<Row> rows;
private int offset;
private int total;
private boolean remove;
 
public RowsBulk() {// Serialization
}
39,10 → 40,11
this.fromJSON(json);
}
 
public RowsBulk(List<Row> rows, int offset, int total) {
public RowsBulk(List<Row> rows, int offset, int total, final boolean remove) {
this.rows = rows;
this.offset = offset;
this.total = total;
this.remove = remove;
}
 
// Sending by column : size gain is 5%
56,7 → 58,7
int columnCount = in.readByte();
// id
for (int j = 0; j < rowCount; j++) {
Row row = new Row(in.readLong(), columnCount);
Row row = new Row((Number) in.readObject(), columnCount);
this.rows.add(row);
}
 
71,6 → 73,7
}
this.offset = in.readInt();
this.total = in.readInt();
this.remove = in.readBoolean();
}
 
@Override
86,7 → 89,7
// ids
for (int j = 0; j < rowCount; j++) {
Row row = this.rows.get(j);
out.writeLong(row.getId());
out.writeObject(row.getId());
 
}
 
104,20 → 107,25
}
out.writeInt(this.offset);
out.writeInt(this.total);
out.writeBoolean(this.remove);
}
 
public List<Row> getRows() {
public final List<Row> getRows() {
return this.rows;
}
 
public int getOffset() {
public final int getOffset() {
return this.offset;
}
 
public int getTotal() {
public final int getTotal() {
return this.total;
}
 
public final boolean isRemove() {
return this.remove;
}
 
@Override
public JSONObject toJSON() {
final JSONObject result = new JSONObject();
125,20 → 133,26
result.put("rows", JSONConverter.getJSON(this.rows));
result.put("offset", this.offset);
result.put("total", this.total);
result.put("remove", this.remove);
return result;
}
 
@Override
public void fromJSON(final JSONObject json) {
this.offset = (Integer) JSONConverter.getParameterFromJSON(json, "offset", Integer.class);
this.total = (Integer) JSONConverter.getParameterFromJSON(json, "total", Integer.class);
this.offset = JSONConverter.getParameterFromJSON(json, "offset", Integer.class);
this.total = JSONConverter.getParameterFromJSON(json, "total", Integer.class);
 
final JSONArray jsonRows = (JSONArray) JSONConverter.getParameterFromJSON(json, "rows", JSONArray.class);
final JSONArray jsonRows = JSONConverter.getParameterFromJSON(json, "rows", JSONArray.class);
this.rows = new ArrayList<Row>();
if (jsonRows != null) {
for (final Object o : jsonRows) {
this.rows.add(new Row((JSONObject) JSONConverter.getObjectFromJSON(o, JSONObject.class)));
this.rows.add(new Row(JSONConverter.getObjectFromJSON(o, JSONObject.class)));
}
}
}
 
@Override
public String toString() {
return this.rows.size() + " rows at offset " + offset + " (total:" + total + ") remove:" + this.remove;
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIHourEditor.java
New file
0,0 → 1,85
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.io.JSONConverter;
 
import net.minidev.json.JSONObject;
 
public class LightUIHourEditor extends LightUserControl {
 
private int hour;
private int minute;
 
public LightUIHourEditor(final JSONObject json) {
super(json);
}
 
public LightUIHourEditor(final String id, int hour, int minute) {
super(id);
this.hour = hour;
this.minute = minute;
this.setType(TYPE_HOUR_EDITOR);
this.setValueType(LightUIElement.VALUE_TYPE_INTEGER);
}
 
public LightUIHourEditor(LightUIHourEditor lightUIHourEditor) {
super(lightUIHourEditor);
this.hour = lightUIHourEditor.hour;
this.minute = lightUIHourEditor.minute;
}
 
public int getHour() {
return hour;
}
 
public void setHour(int hour) {
this.hour = hour;
}
 
public int getMinute() {
return minute;
}
 
public void setMinute(int minute) {
this.minute = minute;
}
 
@Override
public JSONToLightUIConvertor getConvertor() {
return new JSONToLightUIConvertor() {
@Override
public LightUIElement convert(final JSONObject json) {
return new LightUIHourEditor(json);
}
};
}
 
@Override
public Object getValueForContext() {
return this.hour + 60 * this.minute;
}
 
@Override
public void _setValueFromContext(Object value) {
final Integer strValue = (Integer) JSONConverter.getObjectFromJSON(value, Integer.class);
this.hour = strValue / 60;
this.minute = strValue % 60;
}
 
@Override
public LightUIElement clone() {
return new LightUIHourEditor(this);
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUICheckBox.java
15,7 → 15,7
 
import net.minidev.json.JSONObject;
 
public class LightUICheckBox extends LightUIElement implements IUserControl {
public class LightUICheckBox extends LightUserControl {
 
public LightUICheckBox(final JSONObject json) {
super(json);
64,12 → 64,12
}
 
@Override
public Object getValueFromContext() {
public Object getValueForContext() {
return this.isChecked();
}
 
@Override
public void setValueFromContext(Object value) {
public void _setValueFromContext(Object value) {
boolean bValue = false;
if (value != null) {
if (value.equals("true")) {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIFileUploadWithSelection.java
New file
0,0 → 1,80
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.io.JSONConverter;
 
import net.minidev.json.JSONObject;
 
public class LightUIFileUploadWithSelection extends LightUIFileUpload {
 
private static final String TABLE_ID_JSON_KEY = "table-id";
private String tableId;
 
public LightUIFileUploadWithSelection(final JSONObject json) {
super(json);
}
 
public LightUIFileUploadWithSelection(final LightUIFileUploadWithSelection file) {
super(file);
}
 
public LightUIFileUploadWithSelection(final String id, final String tableId, final String sendFileUrl) {
super(id, sendFileUrl);
this.setType(LightUIElement.TYPE_FILE_UPLOAD_WITH_SELECTION);
this.setGridWidth(1);
 
this.tableId = tableId;
}
 
@Override
public JSONToLightUIConvertor getConvertor() {
return new JSONToLightUIConvertor() {
@Override
public LightUIElement convert(final JSONObject json) {
return new LightUIFileUploadWithSelection(json);
}
};
}
 
@Override
public LightUIElement clone() {
return new LightUIFileUploadWithSelection(this);
}
 
@Override
protected void copy(LightUIElement element) {
super.copy(element);
if (!(element instanceof LightUIFileUploadWithSelection)) {
throw new InvalidClassException(this.getClassName(), element.getClassName(), element.getId());
}
 
final LightUIFileUploadWithSelection files = (LightUIFileUploadWithSelection) element;
this.tableId = files.tableId;
}
 
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
 
this.tableId = JSONConverter.getParameterFromJSON(json, TABLE_ID_JSON_KEY, String.class);
}
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
json.put(TABLE_ID_JSON_KEY, this.tableId);
return json;
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUITextField.java
17,7 → 17,7
 
import net.minidev.json.JSONObject;
 
public class LightUITextField extends LightUIElement implements IUserControl {
public class LightUITextField extends LightUserControl {
public LightUITextField(final String id) {
super(id);
this.setType(TYPE_TEXT_FIELD);
48,13 → 48,13
}
 
@Override
public Object getValueFromContext() {
return this.getValue();
public Object getValueForContext() {
return (this.getValue() == null) ? null : ((this.getValue().trim().equals("")) ? null : this.getValue());
}
 
@Override
public void setValueFromContext(final Object value) {
final String strValue = (String) JSONConverter.getObjectFromJSON(value, String.class);
public void _setValueFromContext(final Object value) {
final String strValue = JSONConverter.getObjectFromJSON(value, String.class);
if (strValue != null && !strValue.trim().isEmpty()) {
this.setValue(strValue);
} else {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIButtonLink.java
13,6 → 13,7
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.io.JSONConverter;
 
import java.util.HashMap;
107,6 → 108,6
public void fromJSON(JSONObject json) {
super.fromJSON(json);
this.openNewFrame = JSONConverter.getParameterFromJSON(json, OPEN_NEW_FRAME_KEY, Boolean.class, false);
this.urlParameters = JSONConverter.getParameterFromJSON(json, URL_PARAMETER_KEY, Map.class);
this.urlParameters = CollectionUtils.castMap(JSONConverter.getParameterFromJSON(json, URL_PARAMETER_KEY, Map.class), String.class, String.class);
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIElement.java
18,13 → 18,16
 
import java.awt.Color;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
 
import net.minidev.json.JSONObject;
 
public class LightUIElement implements Transferable {
 
private static final String HORIZONTALLY_RESIZABLE = "horizontally-resizable";
private static final String VERTICALLY_SCROLLABLE = "vertically-scrollable";
private static final String VERTICALLY_RESIZABLE = "vertically-resizable";
private static final String HORIZONTALLY_SCROLLABLE = "horizontally-scrollable";
 
public enum GlyphIcon {
WARNING("#warning"), PLUS("#plus"), PENCIL("#pencil"), SEARCH("#search"), REMOVE("#remove"), STAR("#star"), STAR_EMPTY("#star-empty"), USER("#user"), LOCK("#lock"), UNLOCK(
"#unlock"), DOWNLOAD("#download"), UPLOAD("#upload");
60,9 → 63,11
public static final int TYPE_DROPDOWN_BUTTON = 12;
public static final int TYPE_FRAME = 13;
public static final int TYPE_IMAGE = 14;
public static final int TYPE_FILE = 15;
public static final int TYPE_FILE_UPLOAD_WITH_SELECTION = 15;
public static final int TYPE_PANEL_LINE = 16;
public static final int TYPE_TAB_ELEMENT = 17;
public static final int TYPE_SLIDER = 18;
public static final int TYPE_PICTURE_UPLOAD = 19;
public static final int TYPE_BUTTON = 20;
public static final int TYPE_BUTTON_WITH_CONTEXT = 21;
public static final int TYPE_BUTTON_CANCEL = 22;
71,6 → 76,12
public static final int TYPE_BUTTON_LINK = 25;
public static final int TYPE_RAW_HTML = 26;
public static final int TYPE_TEXT_AREA = 27;
public static final int TYPE_FILE_UPLOAD = 28;
public static final int TYPE_LIST_ROW = 29;
public static final int TYPE_BADGE = 30;
public static final int TYPE_AUTOCOMPLETE_COMBOBOX = 31;
public static final int TYPE_COLOR_PICKER = 32;
public static final int TYPE_HOUR_EDITOR = 33;
// valueType
public static final int VALUE_TYPE_STRING = 0;
public static final int VALUE_TYPE_INTEGER = 1;
148,6 → 159,9
private boolean required = false;
private boolean verticallyResizable = false;
private boolean visible = true;
private boolean notSaved = false;
private boolean verticallyScrollable = false;
private boolean horizontallyScrollable = false;
 
private String displayPrecision;// "(1,2)" means that 0.159 is shown as 0.16
private String icon;
167,8 → 181,6
 
private LightUIElement parent;
 
private Map<Integer, String> actions = new HashMap<Integer, String>();
 
public LightUIElement(final String id) {
this.id = id;
this.UUID = java.util.UUID.randomUUID().toString();
431,6 → 443,14
this.readOnly = readOnly;
}
 
public boolean isNotSaved() {
return this.notSaved;
}
 
public void setNotSaved(boolean notSaved) {
this.notSaved = notSaved;
}
 
public Color getBackgroundColor() {
return this.backgroundColor;
}
563,28 → 583,44
return this.verticallyResizable;
}
 
public final void setVerticalyResizable(boolean verticallyResizable) {
public final void setVerticallyResizable(boolean verticallyResizable) {
this.verticallyResizable = verticallyResizable;
}
 
public final boolean isHorizontalyResizable() {
public final boolean isHorizontallyResizable() {
return this.horizontallyResizable;
}
 
public final void setHorizontalyResizable(boolean horizontallyResizable) {
public final void setHorizontallyResizable(boolean horizontallyResizable) {
this.horizontallyResizable = horizontallyResizable;
}
 
public final boolean isHorizontallyScrollable() {
return this.horizontallyScrollable;
}
 
public final void setHorizontallyScrollable(boolean horizontallyScrollable) {
this.horizontallyScrollable = horizontallyScrollable;
}
 
public final boolean isVerticallyScrollable() {
return this.verticallyScrollable;
}
 
public final void setVerticallyScrollable(boolean verticallyScrollable) {
this.verticallyScrollable = verticallyScrollable;
}
 
public final LightUIElement getParent() {
return this.parent;
}
 
public final <T extends LightUIContainer> T getParent(final Class<T> classExpected) {
public final <T extends LightUIElement> T getParent(final Class<T> expectedClass) {
LightUIElement parent = this.parent;
while (parent != null && !classExpected.isAssignableFrom(parent.getClass())) {
while (parent != null && !expectedClass.isAssignableFrom(parent.getClass())) {
parent = parent.getParent();
}
return (T) parent;
return expectedClass.cast(parent);
}
 
public void setParent(LightUIElement parent) {
591,14 → 627,6
this.parent = parent;
}
 
public final Map<Integer, String> getActions() {
return this.actions;
}
 
public final void addAction(final Integer actionType, final String actionId) {
this.actions.put(actionType, actionId);
}
 
public void dump(PrintStream out, final int depth) {
final String type = this.getTypeAsString();
String valueType = "?";
689,13 → 717,13
 
}
 
protected void addSpacer(PrintStream out, int depth) {
protected final void addSpacer(PrintStream out, int depth) {
for (int i = 0; i < depth; i++) {
out.print(" ");
}
}
 
public String getTypeAsString() {
public final String getTypeAsString() {
String type = "?";
if (this.type == TYPE_CHECKBOX) {
type = "checkbox";
708,7 → 736,7
} else if (this.type == TYPE_TEXT_FIELD) {
type = "textfield";
} else if (this.type == TYPE_TABLE) {
type = "list";
type = "table";
} else if (this.type == TYPE_TABBED_UI) {
type = "tabs";
} else if (this.type == TYPE_TREE) {
723,8 → 751,8
type = "combo element";
} else if (this.type == TYPE_BUTTON_WITH_SELECTION_CONTEXT) {
type = "button with selection context";
} else if (this.type == TYPE_FILE) {
type = "file";
} else if (this.type == TYPE_FILE_UPLOAD_WITH_SELECTION) {
type = "file upload with selection";
} else if (this.type == TYPE_FRAME) {
type = "frame";
} else if (this.type == TYPE_DROPDOWN_BUTTON) {
735,6 → 763,12
type = "list";
} else if (this.type == TYPE_RAW_HTML) {
type = "raw html";
} else if (this.type == TYPE_SLIDER) {
type = "slider";
} else if (this.type == TYPE_PICTURE_UPLOAD) {
type = "picture upload";
} else if (this.type == TYPE_FILE_UPLOAD) {
type = "file upload";
}
 
return type;
784,15 → 818,19
 
this.enabled = element.enabled;
this.fillWidth = element.fillWidth;
this.fillHeight = element.fillHeight;
this.foldable = element.foldable;
this.folded = element.folded;
this.fontBold = element.fontBold;
this.fontItalic = element.fontItalic;
this.horizontallyResizable = element.horizontallyResizable;
this.horizontallyScrollable = element.horizontallyScrollable;
this.required = element.required;
this.readOnly = element.readOnly;
this.verticallyResizable = element.verticallyResizable;
this.verticallyScrollable = element.verticallyScrollable;
this.visible = element.visible;
this.notSaved = element.notSaved;
 
this.displayPrecision = element.displayPrecision;
this.icon = element.icon;
806,9 → 844,6
this.borderColor = element.borderColor;
this.cellBackgroundColor = element.cellBackgroundColor;
this.foreColor = element.foreColor;
 
this.actions = element.actions;
 
}
 
@Override
905,6 → 940,9
if (this.fillWidth) {
result.put("fill-width", true);
}
if (this.fillHeight) {
result.put("fill-height", true);
}
if (this.foldable) {
result.put("foldable", true);
}
918,8 → 956,11
result.put("italic", true);
}
if (this.horizontallyResizable) {
result.put("horizontally-resizable", true);
result.put(HORIZONTALLY_RESIZABLE, true);
}
if (this.horizontallyScrollable) {
result.put(HORIZONTALLY_SCROLLABLE, true);
}
if (this.required) {
result.put("required", true);
}
927,11 → 968,17
result.put("read-only", true);
}
if (this.verticallyResizable) {
result.put("vertically-resizable", true);
result.put(VERTICALLY_RESIZABLE, true);
}
if (this.verticallyScrollable) {
result.put(VERTICALLY_SCROLLABLE, true);
}
if (!this.visible) {
result.put("visible", false);
}
if (this.notSaved) {
result.put("not-saved", true);
}
 
if (this.displayPrecision != null) {
result.put("display-precision", this.displayPrecision);
970,10 → 1017,6
result.put("fore-color", JSONConverter.getJSON(this.foreColor));
}
 
if (!this.actions.isEmpty()) {
result.put("actions", this.actions);
}
 
return result;
}
 
1021,11 → 1064,14
this.fillWidth = JSONConverter.getParameterFromJSON(json, "fill-width", Boolean.class, false);
this.fontBold = JSONConverter.getParameterFromJSON(json, "bold", Boolean.class, false);
this.fontItalic = JSONConverter.getParameterFromJSON(json, "italic", Boolean.class, false);
this.horizontallyResizable = JSONConverter.getParameterFromJSON(json, "horizontally-resizable", Boolean.class, false);
this.verticallyResizable = JSONConverter.getParameterFromJSON(json, "vertically-resizable", Boolean.class, false);
this.horizontallyResizable = JSONConverter.getParameterFromJSON(json, HORIZONTALLY_RESIZABLE, Boolean.class, false);
this.horizontallyScrollable = JSONConverter.getParameterFromJSON(json, HORIZONTALLY_SCROLLABLE, Boolean.class, false);
this.verticallyResizable = JSONConverter.getParameterFromJSON(json, VERTICALLY_RESIZABLE, Boolean.class, false);
this.verticallyScrollable = JSONConverter.getParameterFromJSON(json, VERTICALLY_SCROLLABLE, Boolean.class, false);
this.required = JSONConverter.getParameterFromJSON(json, "required", Boolean.class, false);
this.readOnly = JSONConverter.getParameterFromJSON(json, "read-only", Boolean.class, false);
this.visible = JSONConverter.getParameterFromJSON(json, "visible", Boolean.class, true);
this.notSaved = JSONConverter.getParameterFromJSON(json, "not-saved", Boolean.class, false);
 
this.backgroundColor = JSONConverter.getParameterFromJSON(json, "background-color", Color.class);
this.borderColor = JSONConverter.getParameterFromJSON(json, "border-color", Color.class);
1032,4 → 1078,8
this.cellBackgroundColor = JSONConverter.getParameterFromJSON(json, "cell-background-color", Color.class);
this.foreColor = JSONConverter.getParameterFromJSON(json, "fore-color", Color.class);
}
 
public void destroy() {
this.setValue(null);
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIFrame.java
23,10 → 23,14
import net.minidev.json.JSONObject;
 
public class LightUIFrame extends LightUIContainer {
private static final String ACTIVE = "active";
private static final String TITLE_PANEL = "title-panel";
private static final String FOOTER_PANEL = "footer-panel";
private static final String CHILDREN_FRAME = "children-frame";
 
private Boolean active = false;
private String title;
private final LightUIPanel footerPanel = new LightUIPanel(this.getId() + ".footer.panel");
private LightUIPanel titlePanel = null;
private LightUIPanel footerPanel = new LightUIPanel(this.getId() + ".footer.panel");
 
private List<LightUIFrame> childrenFrame;
 
39,9 → 43,10
public LightUIFrame(final LightUIFrame frame) {
super(frame);
this.active = frame.active;
this.title = frame.title;
this.footerPanel.copy(frame.footerPanel);
this.titlePanel = frame.titlePanel;
this.childrenFrame = frame.childrenFrame;
 
this.setFooterPanel(frame.footerPanel);
}
 
/**
56,20 → 61,49
this.childrenFrame = new ArrayList<LightUIFrame>();
this.addChild(new LightUIPanel(this.getId() + ".main.panel"));
this.footerPanel.setParent(this);
this.footerPanel.setFillHeight(false);
this.footerPanel.setHeight(50);
 
this.createTitlePanel();
}
 
public String getTitle() {
return this.title;
public LightUIPanel createTitlePanel(final String title) {
this.createTitlePanel();
final LightUILabel titleLabel = new LightUILabel(this.titlePanel.getId() + ".label", title, true);
titleLabel.setVerticalAlignement(VALIGN_CENTER);
this.titlePanel.getLastLine().addChild(titleLabel);
return this.titlePanel;
}
 
public void setTitle(final String title) {
this.title = title;
public LightUIPanel createTitlePanel() {
this.titlePanel = new LightUIPanel(this.getId() + ".title.panel");
this.titlePanel.setFillHeight(false);
this.titlePanel.setHeight(50);
return this.titlePanel;
}
 
public LightUIPanel getTitlePanel() {
return this.titlePanel;
}
 
public LightUIPanel getFooterPanel() {
return this.footerPanel;
}
 
/**
* Set the footer panel of the frame, be careful the ID is automatically replace by
* <code>this.getId() + ".footer.panel"</code>.
*
* @param footerPanel - The new footer panel of this frame.
*/
public void setFooterPanel(final LightUIPanel footerPanel) {
footerPanel.setId(this.getId() + ".footer.panel");
this.footerPanel = footerPanel;
this.footerPanel.setFillHeight(false);
this.footerPanel.setParent(this);
this.footerPanel.setHeight(50);
}
 
public void updateFooterPanel(final LightUIPanel footerPanel) {
if (footerPanel != null) {
this.footerPanel.copy(footerPanel);
207,12 → 241,12
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
if (this.active) {
json.put("active", true);
json.put(ACTIVE, true);
}
if (this.title != null) {
json.put("title", this.title);
if (this.titlePanel != null) {
json.put(TITLE_PANEL, this.titlePanel.toJSON());
}
json.put("footer-panel", this.footerPanel.toJSON());
json.put(FOOTER_PANEL, this.footerPanel.toJSON());
return json;
}
 
219,15 → 253,19
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
this.active = (Boolean) JSONConverter.getParameterFromJSON(json, "active", Boolean.class, false);
this.title = (String) JSONConverter.getParameterFromJSON(json, "title", String.class);
this.active = JSONConverter.getParameterFromJSON(json, ACTIVE, Boolean.class, false);
 
final JSONObject jsonFooterPanel = (JSONObject) JSONConverter.getParameterFromJSON(json, "footer-panel", JSONObject.class, null);
final JSONObject jsonTitlePanel = JSONConverter.getParameterFromJSON(json, TITLE_PANEL, JSONObject.class);
if (jsonTitlePanel != null) {
this.titlePanel.fromJSON(jsonTitlePanel);
}
 
final JSONObject jsonFooterPanel = (JSONObject) JSONConverter.getParameterFromJSON(json, FOOTER_PANEL, JSONObject.class, null);
if (jsonFooterPanel != null) {
this.footerPanel.fromJSON(jsonFooterPanel);
}
 
final JSONArray jsonChildrenFrame = (JSONArray) JSONConverter.getParameterFromJSON(json, "children-frame", JSONArray.class, null);
final JSONArray jsonChildrenFrame = (JSONArray) JSONConverter.getParameterFromJSON(json, CHILDREN_FRAME, JSONArray.class, null);
this.childrenFrame = new ArrayList<LightUIFrame>();
if (jsonChildrenFrame != null) {
for (final Object objJsonFrame : jsonChildrenFrame) {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/RowSpec.java
19,6 → 19,7
import java.io.ObjectOutput;
import java.util.List;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.io.JSONAble;
import org.openconcerto.utils.io.JSONConverter;
import net.minidev.json.JSONArray;
105,7 → 106,7
if (jsonColumnIds != null) {
try {
this.columnIds = new String[jsonColumnIds.size()];
this.columnIds = ((List<String>) (List<?>) jsonColumnIds).toArray(this.columnIds);
this.columnIds = CollectionUtils.castList((List<?>) jsonColumnIds, String.class).toArray(this.columnIds);
} catch (final Exception ex) {
throw new IllegalArgumentException("invalid value for 'possible-column-ids', List<String> expected");
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIPanel.java
24,11 → 24,10
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
 
public class LightUIPanel extends LightUIContainer implements Transferable, IUserControlContainer {
public class LightUIPanel extends LightUserControlContainer implements Transferable {
private static final long serialVersionUID = -3399395824294128572L;
 
private String title;
private boolean verticallyScrollable = false;
 
private Color titleColor;
private Color titleBackgroundColor;
49,6 → 48,7
this.setType(TYPE_PANEL);
this.setWeightX(1);
this.setFillWidth(true);
this.setFillHeight(true);
}
 
public final LightUILine getLastLine() {
70,14 → 70,6
this.title = title;
}
 
public boolean isVerticallyScrollable() {
return this.verticallyScrollable;
}
 
public void setVerticallyScrollable(final boolean verticallyScrollable) {
this.verticallyScrollable = verticallyScrollable;
}
 
public void setTitleColor(final Color c) {
this.titleColor = c;
}
130,7 → 122,6
}
final LightUIPanel panelElement = (LightUIPanel) element;
this.title = panelElement.title;
this.verticallyScrollable = panelElement.verticallyScrollable;
this.titleColor = panelElement.titleColor;
this.titleBackgroundColor = panelElement.titleBackgroundColor;
this.controlers.addAll(panelElement.controlers);
184,7 → 175,7
this.addSpacer(out, depth);
out.println("Title : " + this.title);
this.addSpacer(out, depth);
out.println("v-scroll : " + this.verticallyScrollable + " title-color: " + this.titleColor.toString() + " bg-title-color: " + this.titleBackgroundColor.toString());
out.println("v-scroll : " + this.isVerticallyScrollable() + " title-color: " + this.titleColor.toString() + " bg-title-color: " + this.titleBackgroundColor.toString());
super.dump(out, depth);
this.addSpacer(out, depth);
out.println("------------------------");
191,8 → 182,12
}
 
@Override
public void setValueFromContext(final Object value) {
public void _setValueFromContext(final Object value) {
final JSONObject jsonContext = (JSONObject) JSONConverter.getObjectFromJSON(value, JSONObject.class);
 
if (jsonContext == null) {
System.err.println("LightUIPanel.setValueFromContext() - json is null for this panel: " + this.getId());
} else {
final int childCount = this.getChildrenCount();
for (int i = 0; i < childCount; i++) {
final LightUILine line = this.getChild(i, LightUILine.class);
199,20 → 194,21
final int lineChildCount = line.getChildrenCount();
for (int j = 0; j < lineChildCount; j++) {
final LightUIElement lineChild = line.getChild(j);
if (lineChild instanceof IUserControlContainer) {
if (lineChild instanceof LightUserControlContainer) {
if (!jsonContext.containsKey(lineChild.getUUID())) {
throw new IllegalArgumentException("Impossible to find key " + lineChild.getUUID() + " in context, LightUIElement id: " + lineChild.getId());
System.err.println("LightUIPanel.setValueFromContext() - Impossible to find key " + lineChild.getUUID() + " in context, LightUIElement id: " + lineChild.getId());
}
((IUserControlContainer) lineChild).setValueFromContext(jsonContext.get(lineChild.getUUID()));
} else if (lineChild instanceof IUserControl) {
((LightUserControlContainer) lineChild).setValueFromContext(jsonContext.get(lineChild.getUUID()));
} else if (lineChild instanceof LightUserControl) {
if (!jsonContext.containsKey(lineChild.getUUID())) {
throw new IllegalArgumentException("Impossible to find key " + lineChild.getUUID() + " in context, LightUIElement id: " + lineChild.getId());
System.err.println("LightUIPanel.setValueFromContext() - Impossible to find key " + lineChild.getUUID() + " in context, LightUIElement id: " + lineChild.getId());
}
((IUserControl) lineChild).setValueFromContext(jsonContext.get(lineChild.getUUID()));
((LightUserControl) lineChild).setValueFromContext(jsonContext.get(lineChild.getUUID()));
}
}
}
}
}
 
@Override
public LightUIElement clone() {
231,9 → 227,6
if (this.titleBackgroundColor != null) {
result.put("title-bgcolor", JSONConverter.getJSON(this.titleBackgroundColor));
}
if (this.verticallyScrollable) {
result.put("vertically-scrollable", true);
}
if (!this.controlers.isEmpty()) {
result.put("controlers", JSONConverter.getJSON(this.controlers));
}
245,7 → 238,6
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
this.title = (String) JSONConverter.getParameterFromJSON(json, "title", String.class, null);
this.verticallyScrollable = (Boolean) JSONConverter.getParameterFromJSON(json, "vertically-scrollable", Boolean.class, false);
this.titleColor = (Color) JSONConverter.getParameterFromJSON(json, "title-color", Color.class, null);
this.titleBackgroundColor = (Color) JSONConverter.getParameterFromJSON(json, "title-bgcolor", Color.class, null);
 
/trunk/OpenConcerto/src/org/openconcerto/ui/light/RowSelectionSpec.java
20,6 → 20,7
import java.util.ArrayList;
import java.util.List;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.io.JSONAble;
import org.openconcerto.utils.io.JSONConverter;
import net.minidev.json.JSONArray;
28,7 → 29,7
public class RowSelectionSpec implements Externalizable, JSONAble {
private String tableId;
 
private List<Long> ids;
private List<Number> ids;
 
/**
* Define selected ids of a table. ids are ids from selected lines
41,31 → 42,31
this.fromJSON(json);
}
 
public RowSelectionSpec(String tableId, List<Long> ids) {
this.init(tableId, ids);
}
 
public RowSelectionSpec(String tableId) {
this.init(tableId, null);
this(tableId, new ArrayList<Number>());
}
 
private void init(final String tableId, final List<Long> ids) {
public RowSelectionSpec(final String tableId, final List<Number> selectedIds) {
this.tableId = tableId;
if (ids != null) {
this.ids = ids;
} else {
this.ids = new ArrayList<Long>();
this.ids = selectedIds;
}
}
 
public List<Long> getIds() {
public final List<Number> getIds() {
return this.ids;
}
 
public final void setIds(List<Number> ids) {
this.ids = ids;
}
 
public String getTableId() {
return this.tableId;
}
 
public boolean hasSelectedId(){
return this.getIds() != null && !this.getIds().isEmpty();
}
 
@Override
public String toString() {
final StringBuilder r = new StringBuilder("RowSelectionSpec: ").append(this.tableId).append(" : ");
95,7 → 96,7
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.tableId = in.readUTF();
this.ids = (List<Long>) in.readObject();
this.ids = CollectionUtils.castList((List<?>) in.readObject(), Number.class);
 
}
 
110,11 → 111,11
 
@Override
public void fromJSON(JSONObject json) {
this.tableId = (String) JSONConverter.getParameterFromJSON(json, "table-id", String.class);
final JSONArray jsonIds = (JSONArray) JSONConverter.getParameterFromJSON(json, "ids", JSONArray.class);
this.ids = new ArrayList<Long>();
this.tableId = JSONConverter.getParameterFromJSON(json, "table-id", String.class);
final JSONArray jsonIds = JSONConverter.getParameterFromJSON(json, "ids", JSONArray.class);
this.ids = new ArrayList<Number>();
for (final Object jsonId : jsonIds) {
this.ids.add((Long) JSONConverter.getObjectFromJSON(jsonId, Long.class));
this.ids.add(JSONConverter.getObjectFromJSON(jsonId, Number.class));
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/ui/light/IntValueConvertor.java
25,11 → 25,15
}
@Override
public void fillComboWithValue(final LightUIComboBox combo) {
public void fillComboWithValue(final LightUIComboBox combo, final Integer selectedValue) {
for(final Entry<Integer, String> entry : this.values.entrySet()) {
final LightUIComboBoxElement comboElement = new LightUIComboBoxElement(entry.getKey());
comboElement.setValue1(entry.getValue());
combo.addValue(comboElement);
 
if (entry.getKey().equals(selectedValue)) {
combo.setSelectedValue(comboElement);
}
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIComboBox.java
22,24 → 22,19
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
 
public class LightUIComboBox extends LightUIElement implements IUserControl {
public static final int TYPE_COMBO_STANDARD = 1;
public static final int TYPE_COMBO_AUTOCOMPLETE = 2;
public static final int TYPE_COMBO_AUTOCOMPLETE_WITH_ACTION = 3;
public class LightUIComboBox extends LightUserControl {
private static final String HAS_NOT_SPECIFIED_LINE = "has-not-specified-line";
private static final String VALUES = "values";
private static final String SELECTED_VALUE = "selected-value";
private static final String ALREADY_FILLED = "already-filled";
 
public static final int COMBO_ACTION_SEARCH = 1;
public static final int COMBO_ACTION_ADD = 2;
public static final int COMBO_ACTION_MODIFY = 3;
private boolean alreadyFilled = false;
private boolean hasNotSpecifedLine = false;
 
private int comboType = TYPE_COMBO_STANDARD;
private boolean fillFromConvertor = false;
private LightUIComboBoxElement selectedValue = null;
 
private List<LightUIComboBoxElement> values = new ArrayList<LightUIComboBoxElement>();
 
private LightUIComboBoxElement selectedValue = null;
 
private List<Integer> comboActions = new ArrayList<Integer>();
 
// Init from json constructor
public LightUIComboBox(final JSONObject json) {
super(json);
55,14 → 50,6
this.setType(TYPE_COMBOBOX);
}
 
public int getComboType() {
return this.comboType;
}
 
public void setComboType(final int comboType) {
this.comboType = comboType;
}
 
public void addValue(final LightUIComboBoxElement values) {
this.values.add(values);
}
71,14 → 58,6
this.values.addAll(values);
}
 
public List<Integer> getComboActions() {
return this.comboActions;
}
 
public void addComboAction(final Integer comboAction) {
this.comboActions.add(comboAction);
}
 
public static LightUIComboBoxElement getDefaultValue() {
final String defaultLabelKey = "not.specified.label";
final String defaultLabel = TranslationManager.getInstance().getTranslationForItem(defaultLabelKey);
97,8 → 76,9
return this.values;
}
 
// if id=0 means this is the not specifed line
public boolean hasSelectedValue() {
return this.selectedValue != null && this.selectedValue.getId() != 0;
return this.selectedValue != null && ((this.hasNotSpecifedLine && this.selectedValue.getId() != 0) || !this.hasNotSpecifedLine);
}
 
public LightUIComboBoxElement getSelectedValue() {
114,14 → 94,35
this.values.clear();
}
 
public void setFillFromConvertor(final boolean fillFromConvertor) {
this.fillFromConvertor = fillFromConvertor;
public void setAlreadyFilled(final boolean alreadyFilled) {
this.alreadyFilled = alreadyFilled;
}
 
public boolean isFillFromConvertor() {
return this.fillFromConvertor;
public boolean isAlreadyFilled() {
return this.alreadyFilled;
}
 
public void setHasNotSpecifedLine(final boolean hasNotSpecifedLine) {
this.hasNotSpecifedLine = hasNotSpecifedLine;
}
 
public boolean hasNotSpecifedLine() {
return this.hasNotSpecifedLine;
}
 
public void setSelectedId(final Integer id) {
if (id == null) {
this.setSelectedValue(null);
} else {
for (final LightUIComboBoxElement value : this.values) {
if (value.getId() == id) {
this.setSelectedValue(value);
break;
}
}
}
}
 
@Override
protected void copy(LightUIElement element) {
super.copy(element);
131,11 → 132,9
}
 
final LightUIComboBox combo = (LightUIComboBox) element;
this.comboType = combo.comboType;
this.fillFromConvertor = combo.fillFromConvertor;
this.alreadyFilled = combo.alreadyFilled;
this.values = combo.values;
this.selectedValue = combo.selectedValue;
this.comboActions = combo.comboActions;
}
 
@Override
162,17 → 161,17
for (final LightUIComboBoxElement value : this.values) {
jsonValues.add(value.toJSON());
}
json.put("values", jsonValues);
json.put(VALUES, jsonValues);
}
 
if (this.comboType != TYPE_COMBO_STANDARD) {
json.put("combo-type", this.comboType);
if (this.alreadyFilled) {
json.put(ALREADY_FILLED, true);
}
if (this.fillFromConvertor) {
json.put("fill-from-convertor", true);
if (this.hasNotSpecifedLine) {
json.put(HAS_NOT_SPECIFIED_LINE, true);
}
if (this.selectedValue != null) {
json.put("selected-value", this.selectedValue.toJSON());
json.put(SELECTED_VALUE, this.selectedValue.toJSON());
}
 
return json;
182,27 → 181,25
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
 
this.comboType = (Integer) JSONConverter.getParameterFromJSON(json, "combo-type", Integer.class, TYPE_COMBO_STANDARD);
this.fillFromConvertor = (Boolean) JSONConverter.getParameterFromJSON(json, "fill-from-convertor", Boolean.class, false);
final JSONObject jsonSelectedValue = (JSONObject) JSONConverter.getParameterFromJSON(json, "", JSONObject.class);
this.alreadyFilled = JSONConverter.getParameterFromJSON(json, ALREADY_FILLED, Boolean.class, false);
this.hasNotSpecifedLine = JSONConverter.getParameterFromJSON(json, HAS_NOT_SPECIFIED_LINE, Boolean.class, false);
 
final JSONObject jsonSelectedValue = JSONConverter.getParameterFromJSON(json, "", JSONObject.class);
if (jsonSelectedValue != null) {
this.selectedValue = new LightUIComboBoxElement(jsonSelectedValue);
}
 
final JSONArray jsonValues = (JSONArray) JSONConverter.getParameterFromJSON(json, "values", JSONArray.class);
final JSONArray jsonValues = JSONConverter.getParameterFromJSON(json, VALUES, JSONArray.class);
this.values = new ArrayList<LightUIComboBoxElement>();
if (jsonValues != null) {
for (final Object jsonValue : jsonValues) {
if (!(jsonValue instanceof JSONObject)) {
throw new IllegalArgumentException("value for 'values' is invalid");
this.values.add(new LightUIComboBoxElement(JSONConverter.getObjectFromJSON(jsonValue, JSONObject.class)));
}
this.values.add(new LightUIComboBoxElement((JSONObject) jsonValue));
}
}
}
 
@Override
public Object getValueFromContext() {
public Object getValueForContext() {
if (this.hasSelectedValue()) {
return this.getSelectedValue();
} else {
211,12 → 208,18
}
 
@Override
public void setValueFromContext(Object value) {
public void _setValueFromContext(Object value) {
if (value != null) {
final JSONObject jsonSelectedValue = (JSONObject) JSONConverter.getObjectFromJSON(value, JSONObject.class);
final JSONObject jsonSelectedValue = JSONConverter.getObjectFromJSON(value, JSONObject.class);
this.selectedValue = new LightUIComboBoxElement(jsonSelectedValue);
} else {
this.selectedValue = null;
}
}
 
@Override
public void destroy() {
super.destroy();
this.clearValues();
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/ComboValueConvertor.java
36,20 → 36,32
 
protected abstract void init();
 
public boolean hasNotSpecifiedLine() {
public final boolean hasNotSpecifiedLine() {
return this.hasNotSpecifedLine;
}
 
public void fillCombo(final LightUIComboBox combo) {
public final void fillCombo(final LightUIComboBox combo, final K selectedValue) {
if (this.hasNotSpecifedLine) {
combo.addValue(LightUIComboBox.getDefaultValue());
}
this.fillComboWithValue(combo);
combo.setFillFromConvertor(true);
this.fillComboWithValue(combo, selectedValue);
if (selectedValue == null) {
combo.setSelectedValue(null);
}
 
protected abstract void fillComboWithValue(final LightUIComboBox combo);
if(!combo.hasSelectedValue()){
if(combo.hasNotSpecifedLine()){
combo.setSelectedValue(LightUIComboBox.getDefaultValue());
} else if(!combo.getValues().isEmpty()) {
combo.setSelectedValue(combo.getValues().get(0));
}
}
 
combo.setAlreadyFilled(true);
}
 
protected abstract void fillComboWithValue(final LightUIComboBox combo, final K selectedValue);
 
@Override
public String convert(final K key) {
if (key == null) {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIList.java
11,31 → 11,13
* When distributing the software, include this License Header Notice in each file.
*/
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
 
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.io.JSONConverter;
import org.openconcerto.utils.ui.StringWithId;
 
import java.util.ArrayList;
 
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
 
public class LightUIList extends LightUIElement {
private ArrayList<StringWithId> values = new ArrayList<StringWithId>();
public class LightUIList extends LightUIContainer {
 
// Init from json constructor
public LightUIList(final JSONObject json) {
45,65 → 27,19
// Clone constructor
public LightUIList(final LightUIList listElement) {
super(listElement);
this.values = listElement.values;
}
 
public LightUIList(final String id, final ArrayList<StringWithId> values) {
public LightUIList(final String id) {
super(id);
this.setType(TYPE_LIST);
 
this.values = values;
}
 
public ArrayList<StringWithId> getValues() {
return (ArrayList<StringWithId>) this.values.clone();
}
public LightUIList(final String id, final ArrayList<LightUIListRow> rows) {
super(id);
this.setType(TYPE_LIST);
 
@Override
public JSONToLightUIConvertor getConvertor() {
return new JSONToLightUIConvertor() {
@Override
public LightUIElement convert(final JSONObject json) {
return new LightUIList(json);
for (final LightUIListRow row : rows) {
this.addChild(row);
}
};
}
 
@Override
public LightUIElement clone() {
return new LightUIList(this);
}
 
@Override
protected void copy(LightUIElement element) {
super.copy(element);
if (!(element instanceof LightUIList)) {
throw new InvalidClassException(this.getClassName(), element.getClassName(), element.getId());
}
}
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
json.put("class", "LightUIList");
if (this.values != null && this.values.size() > 0) {
json.put("values", this.values);
}
return json;
}
 
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
final JSONArray jsonValues = (JSONArray) JSONConverter.getParameterFromJSON(json, "values", JSONArray.class);
this.values = new ArrayList<StringWithId>();
if (jsonValues != null) {
for (final Object jsonValue : jsonValues) {
if (!(jsonValue instanceof JSONObject)) {
throw new IllegalArgumentException("values of list must be json of StringWithId");
}
this.values.add(new StringWithId((JSONObject) jsonValue));
}
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIColorPicker.java
New file
0,0 → 1,57
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import java.awt.Color;
 
import net.minidev.json.JSONObject;
 
public class LightUIColorPicker extends LightUserControl {
 
public LightUIColorPicker(final String id) {
super(id);
this.setType(TYPE_COLOR_PICKER);
this.setBorderColor(Color.BLACK);
}
 
public LightUIColorPicker(final JSONObject json) {
super(json);
}
 
public LightUIColorPicker(final LightUIColorPicker element) {
super(element);
}
 
public void setSelectedColor(final Color color) {
this.setValue(String.valueOf(color.getRGB()));
}
 
public Color getSelectedColor() {
if (this.getValue() == null || this.getValue().isEmpty()) {
return null;
} else {
return Color.decode(this.getValue());
}
}
 
@Override
protected void _setValueFromContext(final Object value) {
this.setValue((String) value);
}
 
@Override
public Object getValueForContext() {
return this.getValue();
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIDate.java
21,7 → 21,7
 
import net.minidev.json.JSONObject;
 
public class LightUIDate extends LightUIElement implements IUserControl {
public class LightUIDate extends LightUserControl {
 
public LightUIDate(final JSONObject json) {
super(json);
60,12 → 60,12
}
 
@Override
public Object getValueFromContext() {
public Object getValueForContext() {
return this.getValueAsDate();
}
 
@Override
public void setValueFromContext(Object value) {
public void _setValueFromContext(Object value) {
final String strValue = (String) JSONConverter.getObjectFromJSON(value, String.class);
this.setValue(strValue);
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIPictureUpload.java
New file
0,0 → 1,53
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.io.JSONConverter;
 
import net.minidev.json.JSONObject;
 
public class LightUIPictureUpload extends LightUIFileUpload {
 
private static final String FILE_PATH = "file-path";
 
private String filePath = null;
 
public LightUIPictureUpload(final String id, final String sendFileUrl) {
super(id, sendFileUrl);
this.setType(TYPE_PICTURE_UPLOAD);
}
 
public String getFilePath() {
return this.filePath;
}
 
public void setFilePath(final String filePath) {
this.filePath = filePath;
}
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
if (this.filePath != null) {
json.put(FILE_PATH, this.filePath);
}
return json;
}
 
@Override
public void fromJSON(JSONObject json) {
super.fromJSON(json);
this.filePath = JSONConverter.getParameterFromJSON(json, FILE_PATH, String.class);
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/Row.java
13,9 → 13,9
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.io.JSONAble;
import org.openconcerto.utils.io.JSONConverter;
import org.openconcerto.utils.ui.StringWithId;
 
import java.io.Externalizable;
import java.io.IOException;
29,7 → 29,7
 
public class Row implements Externalizable, JSONAble {
 
private long id;
private Number id;
private String extendId;
private List<Object> values;
45,7 → 45,7
this.fromJSON(json);
}
 
public Row(long id, int valueCount) {
public Row(final Number id, int valueCount) {
this.id = id;
this.values = new ArrayList<Object>();
if (valueCount > 0) {
55,69 → 55,65
}
}
 
public Row(long id) {
public Row(final Number id) {
this.id = id;
}
public void setId(final long id) {
this.id = id;
}
 
public void setValues(List<Object> values) {
public final void setValues(List<Object> values) {
this.values = values;
}
 
public List<Object> getValues() {
public final List<Object> getValues() {
return this.values;
}
 
public long getId() {
public final Number getId() {
return this.id;
}
public String getExtendId() {
public final String getExtendId() {
return this.extendId;
}
public void setExtendId(final String extendId) {
public final void setExtendId(final String extendId) {
this.extendId = extendId;
}
public void addValue(Object v) {
public final void addValue(Object v) {
this.values.add(v);
}
 
public void setValue(int index, Object v) {
public final void setValue(int index, Object v) {
this.values.set(index, v);
}
public Boolean isFillWidth() {
public final Boolean isFillWidth() {
return this.fillWidth;
}
 
public void setFillWidth(final Boolean fillWidth) {
public final void setFillWidth(final Boolean fillWidth) {
this.fillWidth = fillWidth;
}
public Boolean isToggleable() {
public final Boolean isToggleable() {
return this.toggleable;
}
 
public void setToggleable(final Boolean toggleable) {
public final void setToggleable(final Boolean toggleable) {
this.toggleable = toggleable;
}
public Boolean isVisible() {
public final Boolean isVisible() {
return this.visible;
}
 
public void setVisible(final Boolean visible) {
public final void setVisible(final Boolean visible) {
this.visible = visible;
}
 
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(this.id);
out.writeObject(this.id);
out.writeUTF(this.extendId);
out.writeObject(this.values);
out.writeBoolean(this.fillWidth);
127,9 → 123,9
 
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readLong();
this.id = (Number) in.readObject();
this.extendId = in.readUTF();
this.values = (List<Object>) in.readObject();
this.values = CollectionUtils.castList((List<?>) in.readObject(), Object.class);
this.fillWidth = in.readBoolean();
this.toggleable = in.readBoolean();
this.visible = in.readBoolean();
162,11 → 158,11
 
@Override
public void fromJSON(final JSONObject json) {
this.id = (Long) JSONConverter.getParameterFromJSON(json, "id", Long.class);
this.extendId = (String) JSONConverter.getParameterFromJSON(json, "extend-id", String.class);
this.fillWidth = (Boolean) JSONConverter.getParameterFromJSON(json, "fill-width", Boolean.class, false);
this.toggleable = (Boolean) JSONConverter.getParameterFromJSON(json, "toggleable", Boolean.class, false);
this.visible = (Boolean) JSONConverter.getParameterFromJSON(json, "visible", Boolean.class, true);
this.id = JSONConverter.getParameterFromJSON(json, "id", Number.class);
this.extendId = JSONConverter.getParameterFromJSON(json, "extend-id", String.class);
this.fillWidth = JSONConverter.getParameterFromJSON(json, "fill-width", Boolean.class, false);
this.toggleable = JSONConverter.getParameterFromJSON(json, "toggleable", Boolean.class, false);
this.visible = JSONConverter.getParameterFromJSON(json, "visible", Boolean.class, true);
 
final JSONArray jsonValues = (JSONArray) JSONConverter.getParameterFromJSON(json, "values", JSONArray.class);
if (jsonValues != null) {
176,18 → 172,8
Object objValue = jsonValues.get(i);
if (objValue instanceof JSONObject) {
final JSONObject jsonValue = (JSONObject) objValue;
final String valueClassName = (String) JSONConverter.getParameterFromJSON(jsonValue, "class", String.class);
if (valueClassName == null){
throw new IllegalArgumentException("null value store in ghost");
}
if(valueClassName.equals(StringWithId.class.getSimpleName())) {
objValue = new StringWithId(jsonValue);
} else if (valueClassName.equals(LightUIElement.class.getSimpleName())) {
objValue = JSONToLightUIConvertorManager.getInstance().createUIElementFromJSON(jsonValue);
} else {
throw new IllegalArgumentException("invalid value for 'values', StringWithId or LightUIElement expected");
}
} else {
if(objValue instanceof String) {
objValue = JSONConverter.getObjectFromJSON(objValue, String.class);
} else if(objValue instanceof Integer) {
207,6 → 193,6
@Override
public String toString() {
return "Row id: " + this.id + " values: " + this.values;
return "Row index: " + this.id + " values: " + this.values;
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/ColumnSpec.java
15,7 → 15,6
 
import org.openconcerto.utils.io.JSONConverter;
import org.openconcerto.utils.io.Transferable;
import org.openconcerto.utils.ui.StringWithId;
 
import java.io.Externalizable;
import java.io.IOException;
27,17 → 26,33
import net.minidev.json.JSONObject;
 
public class ColumnSpec implements Externalizable, Transferable {
private static final String DEFAULT_VALUE = "default-value";
private static final String EDITORS = "editors";
private static final String VALUE_CLASS = "value-class";
private static final String ID = "id";
private static final String COLUMN_NAME = "column-name";
private static final String WIDTH = "width";
private static final String MAX_WIDTH = "max-width";
private static final String MIN_WIDTH = "min-width";
private static final String EDITABLE = "editable";
private static final String HORIZONTAL_ALIGNMENT = "h-align";
// Must stay immutable
private String id;
private String columnName;
 
private String id;
private Class<?> valueClass;
private String columnName;
 
// Default value (to add a new line)
private Object defaultValue;
private int width;
private int maxWidth;
private int minWidth;
 
private Integer horizontalAlignment = null;
 
private double width;
private double maxWidth;
private double minWidth;
 
private boolean editable;
 
private LightUIElement editors;
 
public ColumnSpec() {
48,12 → 63,12
this.fromJSON(json);
}
 
public ColumnSpec(final String id, final Class<?> valueClass, final String columnName, final Object defaultValue, final int width, final boolean editable, final LightUIElement editors) {
public ColumnSpec(final String id, final Class<?> valueClass, final String columnName, final Object defaultValue, final double width, final boolean editable, final LightUIElement editors) {
this.init(id, valueClass, columnName, defaultValue, editable, editors);
this.width = width;
 
final int minWidth = width - 200;
final int maxWidth = width + 200;
final double minWidth = width - 200;
final double maxWidth = width + 200;
 
this.minWidth = (minWidth < 10) ? 10 : minWidth;
this.maxWidth = maxWidth;
73,7 → 88,7
this.editors = editors;
}
 
public void setPrefs(final int width, final int maxWidth, final int minWidth) {
public void setPrefs(final double width, final double maxWidth, final double minWidth) {
this.width = width;
this.maxWidth = maxWidth;
this.minWidth = minWidth;
95,6 → 110,14
this.valueClass = valueClass;
}
 
public int getHorizontalAlignment() {
return this.horizontalAlignment;
}
 
public void setHorizontalAlignment(final int horizontalAlignment) {
this.horizontalAlignment = horizontalAlignment;
}
 
public String getColumnName() {
return this.columnName;
}
111,27 → 134,27
this.defaultValue = defaultValue;
}
 
public int getMaxWidth() {
public double getMaxWidth() {
return this.maxWidth;
}
 
public void setMaxWidth(final int maxWidth) {
public void setMaxWidth(final double maxWidth) {
this.maxWidth = maxWidth;
}
 
public int getMinWidth() {
public double getMinWidth() {
return this.minWidth;
}
 
public void setMinWidth(final int minWidth) {
public void setMinWidth(final double minWidth) {
this.minWidth = minWidth;
}
 
public int getWidth() {
public double getWidth() {
return this.width;
}
 
public void setWidth(int width) {
public void setWidth(double width) {
this.width = width;
}
 
160,10 → 183,10
 
public Element createXmlColumnPref() {
final Element columnElement = new Element("column");
columnElement.setAttribute("id", this.getId());
columnElement.setAttribute("max-width", String.valueOf(this.getMaxWidth()));
columnElement.setAttribute("min-width", String.valueOf(this.getMinWidth()));
columnElement.setAttribute("width", String.valueOf(this.getWidth()));
columnElement.setAttribute(ID, this.getId());
columnElement.setAttribute(MAX_WIDTH, String.valueOf(this.getMaxWidth()));
columnElement.setAttribute(MIN_WIDTH, String.valueOf(this.getMinWidth()));
columnElement.setAttribute(WIDTH, String.valueOf(this.getWidth()));
return columnElement;
}
 
171,13 → 194,14
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(this.id);
out.writeUTF(this.columnName);
out.writeInt(this.width);
out.writeInt(this.maxWidth);
out.writeInt(this.minWidth);
out.writeDouble(this.width);
out.writeDouble(this.maxWidth);
out.writeDouble(this.minWidth);
out.writeObject(this.defaultValue);
out.writeBoolean(this.editable);
out.writeObject(this.editors);
out.writeObject(this.valueClass);
out.writeInt(this.horizontalAlignment);
}
 
@Override
184,13 → 208,14
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readUTF();
this.columnName = in.readUTF();
this.width = in.readInt();
this.maxWidth = in.readInt();
this.minWidth = in.readInt();
this.width = in.readDouble();
this.maxWidth = in.readDouble();
this.minWidth = in.readDouble();
this.defaultValue = in.readObject();
this.editable = in.readBoolean();
this.editors = (LightUIElement) in.readObject();
this.valueClass = (Class<?>) in.readObject();
this.horizontalAlignment = in.readInt();
}
 
@Override
197,45 → 222,60
public JSONObject toJSON() {
final JSONObject result = new JSONObject();
result.put("class", "ColumnSpec");
result.put("id", this.id);
result.put("column-name", this.columnName);
result.put("width", this.width);
result.put("max-width", this.maxWidth);
result.put("min-width", this.minWidth);
result.put(ID, this.id);
result.put(COLUMN_NAME, this.columnName);
result.put(WIDTH, this.width);
result.put(MAX_WIDTH, this.maxWidth);
result.put(MIN_WIDTH, this.minWidth);
if (this.defaultValue != null) {
result.put("default-value", JSONConverter.getJSON(this.defaultValue));
result.put(DEFAULT_VALUE, JSONConverter.getJSON(this.defaultValue));
}
if (this.editable) {
result.put("editable", true);
result.put(EDITABLE, true);
}
if (this.editors != null) {
result.put("editors", JSONConverter.getJSON(this.editors));
result.put(EDITORS, JSONConverter.getJSON(this.editors));
}
result.put("value-class", JSONConverter.getJSON(this.valueClass));
result.put(VALUE_CLASS, JSONConverter.getJSON(this.valueClass));
 
if (this.horizontalAlignment != null) {
result.put(HORIZONTAL_ALIGNMENT, this.horizontalAlignment);
}
return result;
}
 
@Override
public void fromJSON(final JSONObject json) {
this.id = (String) JSONConverter.getParameterFromJSON(json, "id", String.class);
this.columnName = (String) JSONConverter.getParameterFromJSON(json, "column-name", String.class);
this.width = (Integer) JSONConverter.getParameterFromJSON(json, "width", Integer.class);
this.maxWidth = (Integer) JSONConverter.getParameterFromJSON(json, "max-width", Integer.class);
this.minWidth = (Integer) JSONConverter.getParameterFromJSON(json, "min-width", Integer.class);
this.editable = (Boolean) JSONConverter.getParameterFromJSON(json, "editable", Boolean.class, Boolean.FALSE);
this.id = JSONConverter.getParameterFromJSON(json, ID, String.class);
this.columnName = JSONConverter.getParameterFromJSON(json, COLUMN_NAME, String.class);
 
final JSONObject jsonDefaultValue = (JSONObject) JSONConverter.getParameterFromJSON(json, "default-value", JSONObject.class);
if (jsonDefaultValue != null) {
final String defaultValueClassName = (String) JSONConverter.getParameterFromJSON(jsonDefaultValue, "class", String.class);
if (defaultValueClassName.equals(StringWithId.class.getSimpleName())) {
this.defaultValue = new StringWithId(jsonDefaultValue);
/** JavaScript convert Double to Long, this will fix that. **/
final Number numWidth = JSONConverter.getParameterFromJSON(json, WIDTH, Number.class);
final Number numMaxWidth = JSONConverter.getParameterFromJSON(json, MAX_WIDTH, Number.class);
final Number numMinWidth = JSONConverter.getParameterFromJSON(json, MIN_WIDTH, Number.class);
if (numWidth != null) {
this.width = numWidth.doubleValue();
}
if (numMaxWidth != null) {
this.maxWidth = numMaxWidth.doubleValue();
}
final JSONObject jsonEditors = (JSONObject) JSONConverter.getParameterFromJSON(json, "editors", JSONObject.class);
if (numMinWidth != null) {
this.minWidth = numMinWidth.doubleValue();
}
/************************************************************/
 
this.editable = JSONConverter.getParameterFromJSON(json, EDITABLE, Boolean.class, Boolean.FALSE);
this.horizontalAlignment = JSONConverter.getParameterFromJSON(json, HORIZONTAL_ALIGNMENT, Integer.class);
 
final JSONObject jsonDefaultValue = JSONConverter.getParameterFromJSON(json, DEFAULT_VALUE, JSONObject.class);
// TODO: implement default value
 
final JSONObject jsonEditors = JSONConverter.getParameterFromJSON(json, EDITORS, JSONObject.class);
if (jsonEditors != null) {
this.editors = JSONToLightUIConvertorManager.getInstance().createUIElementFromJSON(jsonEditors);
}
final String sValueClass = (String) JSONConverter.getParameterFromJSON(json, "value-class", String.class);
 
final String sValueClass = JSONConverter.getParameterFromJSON(json, VALUE_CLASS, String.class);
if (sValueClass != null) {
try {
this.valueClass = Class.forName(sValueClass);
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIComboBoxElement.java
15,6 → 15,7
 
import org.openconcerto.utils.io.JSONConverter;
import org.openconcerto.utils.io.Transferable;
 
import net.minidev.json.JSONObject;
 
public class LightUIComboBoxElement implements Transferable {
36,6 → 37,24
this.id = id;
}
 
public LightUIComboBoxElement(final int id, final String value1) {
this.id = id;
this.value1 = value1;
}
public LightUIComboBoxElement(final int id, final String value1, final String value2) {
this.id = id;
this.value1 = value1;
this.value2 = value2;
}
 
public LightUIComboBoxElement(final LightUIComboBoxElement comboElement) {
this.id = comboElement.id;
this.value1 = comboElement.value1;
this.value2 = comboElement.value2;
this.icon = comboElement.icon;
}
 
public int getId() {
return this.id;
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/TreeSpec.java
23,7 → 23,7
}
 
public String getId() {
return id;
return this.id;
}
 
public void setId(String id) {
31,7 → 31,7
}
 
public List<TreeItem> getChildren() {
return children;
return this.children;
}
 
public void setChildren(List<TreeItem> children) {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUIFileUpload.java
New file
0,0 → 1,90
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.io.JSONConverter;
 
import net.minidev.json.JSONObject;
 
public class LightUIFileUpload extends LightUserControl {
private static final String SEND_FILE_URL_JSON_KEY = "send-file-url";
 
private String sendFileUrl;
 
public LightUIFileUpload(final JSONObject json) {
super(json);
}
 
public LightUIFileUpload(final LightUIFileUpload file) {
super(file);
}
 
public LightUIFileUpload(final String id, final String sendFileUrl) {
super(id);
this.setType(LightUIElement.TYPE_FILE_UPLOAD);
 
this.sendFileUrl = sendFileUrl;
}
 
@Override
public JSONToLightUIConvertor getConvertor() {
return new JSONToLightUIConvertor() {
@Override
public LightUIElement convert(final JSONObject json) {
return new LightUIFileUpload(json);
}
};
}
 
@Override
public LightUIElement clone() {
return new LightUIFileUpload(this);
}
 
@Override
protected void copy(LightUIElement element) {
super.copy(element);
if (!(element instanceof LightUIFileUpload)) {
throw new InvalidClassException(this.getClassName(), element.getClassName(), element.getId());
}
 
final LightUIFileUpload files = (LightUIFileUpload) element;
this.sendFileUrl = files.sendFileUrl;
 
}
 
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
 
this.sendFileUrl = JSONConverter.getParameterFromJSON(json, SEND_FILE_URL_JSON_KEY, String.class);
}
 
@Override
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
json.put(SEND_FILE_URL_JSON_KEY, this.sendFileUrl);
return json;
}
 
@Override
public void _setValueFromContext(final Object value) {
this.setValue((String) value);
}
 
@Override
public Object getValueForContext() {
return this.getValue();
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUserControlContainer.java
New file
0,0 → 1,44
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.light;
 
import net.minidev.json.JSONObject;
 
public abstract class LightUserControlContainer extends LightUIContainer {
public LightUserControlContainer(final String id) {
super(id);
}
 
public LightUserControlContainer(final JSONObject json) {
super(json);
}
 
public LightUserControlContainer(final LightUserControlContainer userControlContainer) {
super(userControlContainer);
}
 
public void setValueFromContext(final Object value) {
if (this.isEnabled()) {
this._setValueFromContext(value);
}
}
 
/**
* Set value for all elements send by client with buttons TYPE_BUTTON_WITH_SELECTION_CONTEXT and
* TYPE_BUTTON_WITH_CONTEXT
*
* @param value to set for element
*/
protected abstract void _setValueFromContext(final Object value);
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUITable.java
13,6 → 13,7
package org.openconcerto.ui.light;
 
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.io.JSONConverter;
 
import java.awt.event.ActionEvent;
20,15 → 21,29
import java.util.ArrayList;
import java.util.List;
 
import javax.xml.parsers.ParserConfigurationException;
 
import org.jdom2.Document;
import org.jdom2.Element;
 
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
 
public class LightUITable extends LightUIElement implements IUserControlContainer {
public class LightUITable extends LightUserControlContainer {
 
public static final int DEFAULT_LINE_HEIGHT = 40;
 
private static final String LINE_PER_ROW = "line-per-row";
private static final String TABLE_SPEC = "table-spec";
private static final String ALLOW_SELECTION = "allow-selection";
private static final String ALLOW_MULTI_SELECTION = "allow-multi-selection";
private static final String DYNAMIC_LOAD = "dynamic-load";
private static final String AUTO_SELECT_FIRST_LINE = "auto-select-first-line";
private Boolean dynamicLoad = false;
private Boolean verticallyScrollable = false;
private Boolean allowSelection = false;
private Boolean allowMultiSelection = false;
private Boolean autoSelectFirstLine = true;
private TableSpec tableSpec = null;
private String elementCode = null;
 
private List<ActionListener> selectionListeners = new ArrayList<ActionListener>();
 
35,6 → 50,8
// Nombre de ligne à afficher par Row
private int linePerRow = 1;
 
private int lineHeight = DEFAULT_LINE_HEIGHT;
 
// Init from json constructor
public LightUITable(final JSONObject json) {
super(json);
43,9 → 60,7
// Clone constructor
public LightUITable(final LightUITable tableElement) {
super(tableElement);
this.verticallyScrollable = tableElement.verticallyScrollable;
this.tableSpec = tableElement.tableSpec;
this.elementCode = tableElement.elementCode;
this.allowSelection = tableElement.allowSelection;
}
 
55,82 → 70,187
 
this.setWeightX(1);
this.setFillWidth(true);
}
 
public int getIndexfromRowID(int rowID) {
final TableContent content = this.getTableSpec().getContent();
final RowSelectionSpec selection = new RowSelectionSpec(this.getId());
final ColumnsSpec columnsSpec = new ColumnsSpec(this.getId(), new ArrayList<ColumnSpec>(), new ArrayList<String>(), new ArrayList<String>());
final TableSpec tableSpec = new TableSpec(this.getId(), selection, columnsSpec);
tableSpec.setContent(new TableContent(this.getId()));
 
for (int i = 0; i < content.getRows().size(); i++) {
Row r = content.getRows().get(i);
if (r.getId() == rowID) {
return i;
this.setTableSpec(tableSpec);
}
}
return -1;
}
 
@Override
public void setId(final String id) {
super.setId(id);
 
if (this.tableSpec != null) {
this.tableSpec.setId(id);
 
if (this.tableSpec.getSelection() != null) {
this.tableSpec.getSelection().setTableId(id);
}
 
public String getElementCode() {
return this.elementCode;
if (this.tableSpec.getContent() != null) {
this.tableSpec.getContent().setTableId(id);
}
 
public void setElementCode(final String elementCode) {
this.elementCode = elementCode;
}
}
 
public void setLinePerRow(int linePerRow) {
public final void setLinePerRow(int linePerRow) {
this.linePerRow = linePerRow;
}
 
public int getLinePerRow() {
public final int getLinePerRow() {
return this.linePerRow;
}
 
public TableSpec getTableSpec() {
public final void setLineHeight(int lineHeight) {
this.lineHeight = lineHeight;
}
 
public final int getLineHeight() {
return this.lineHeight;
}
 
public final TableSpec getTableSpec() {
return this.tableSpec;
}
 
public void setTableSpec(final TableSpec tableSpec) {
public final void setTableSpec(final TableSpec tableSpec) {
this.tableSpec = tableSpec;
}
 
public Boolean isAllowSelection() {
public final Boolean isAllowSelection() {
return this.allowSelection;
}
 
public void setAllowSelection(final boolean allowSelection) {
public final void setAllowSelection(final boolean allowSelection) {
this.allowSelection = allowSelection;
}
 
public Boolean isDynamicLoad() {
public final Boolean isAllowMultiSelection() {
return this.allowMultiSelection;
}
 
public final void setAllowMultiSelection(final boolean allowMultiSelection) {
this.allowMultiSelection = allowMultiSelection;
}
 
public final Boolean isDynamicLoad() {
return this.dynamicLoad;
}
 
public void setDynamicLoad(final boolean dynamicLoad) {
public final void setDynamicLoad(final boolean dynamicLoad) {
this.dynamicLoad = dynamicLoad;
}
 
public Boolean isVerticallyScrollable() {
return this.verticallyScrollable;
public final Boolean isAutoSelectFirstLine() {
return this.autoSelectFirstLine;
}
 
public void setVerticallyScrollable(final Boolean verticallyScrollable) {
this.verticallyScrollable = verticallyScrollable;
public final void setAutoSelectFirstLine(final boolean autoSelectFirstLine) {
this.autoSelectFirstLine = autoSelectFirstLine;
}
 
public boolean replaceChild(final LightUIElement pChild) {
final List<Row> tableRows = this.getTableSpec().getContent().getRows();
final int tableRowsCount = tableRows.size();
public final Row removeRow(final int index) {
return this.tableSpec.getContent().removeRow(index);
}
 
public final boolean removeRow(final Row row) {
final TableContent content = this.getTableSpec().getContent();
return content.removeRow(row);
}
 
public final boolean hasRow() {
return (this.tableSpec != null && this.tableSpec.getContent() != null && this.tableSpec.getContent().getRowsCount() > 0);
}
 
public final Row getRow(final int index) {
return this.getTableSpec().getContent().getRow(index);
}
 
public Row getRowById(final Number rowId) {
final int size = this.getTableSpec().getContent().getRowsCount();
for (int i = 0; i < size; i++) {
final Row row = this.getRow(i);
if (NumberUtils.areNumericallyEqual(row.getId(), rowId)) {
return row;
} else {
System.err.println("LightUITable.getSelectedRows() - Null selectedRow");
}
}
return null;
}
 
public final Row setRow(final int index, final Row row) {
return this.getTableSpec().getContent().setRow(index, row);
}
 
public final boolean addRow(final Row row) {
return this.getTableSpec().getContent().addRow(row);
}
 
public final int getRowsCount() {
return this.getTableSpec().getContent().getRowsCount();
}
 
public final void clearRows() {
this.getTableSpec().getContent().clearRows();
}
 
/**
* Get Ids of SQLRowAccessor store in selected rows
*
* @return The list of selected DB Ids
*/
public final List<Number> getSelectedIds() {
return this.getTableSpec().getSelection().getIds();
}
 
public final Number getFirstSelectedId() {
final List<Number> selectedIds = this.getTableSpec().getSelection().getIds();
if (selectedIds.isEmpty()) {
return null;
} else {
return selectedIds.get(0);
}
}
 
public final void setSelectedIds(final List<Number> selectedIds, final boolean fire) {
this.getTableSpec().getSelection().setIds(selectedIds);
if (fire) {
this.fireSelectionChange();
}
}
 
public final void clearSelection(final boolean fire) {
this.getTableSpec().getSelection().getIds().clear();
if (fire) {
this.fireSelectionChange();
}
}
 
public final List<Row> getSelectedRows() {
final List<Row> selectedRows = new ArrayList<Row>();
 
if (this.getTableSpec().getSelection() != null) {
final List<Number> selectedIds = this.getSelectedIds();
for (final Number selectedId : selectedIds) {
final Row selectedRow = this.getRowById(selectedId);
if (selectedRow != null) {
selectedRows.add(selectedRow);
}
}
}
 
return selectedRows;
}
 
public final boolean replaceChild(final LightUIElement pChild) {
pChild.setReadOnly(this.isReadOnly());
 
for (int i = 0; i < tableRowsCount; i++) {
final Row tableRow = tableRows.get(i);
for (int i = 0; i < this.getRowsCount(); i++) {
final Row tableRow = this.getTableSpec().getContent().getRow(i);
final List<Object> tableRowValues = tableRow.getValues();
final int tableRowValuesCount = tableRowValues.size();
 
160,17 → 280,15
return false;
}
 
public LightUIElement findElement(final String searchParam, final boolean byUUID) {
public final LightUIElement findElement(final String searchParam, final boolean byUUID) {
return this.findElement(searchParam, byUUID, LightUIElement.class);
}
 
public <T extends LightUIElement> T findElement(final String searchParam, final boolean byUUID, final Class<T> objectClass) {
if (this.tableSpec != null) {
final TableContent content = this.tableSpec.getContent();
if (content != null) {
final List<Row> listRows = content.getRows();
if (listRows != null) {
for (final Row row : listRows) {
public final <T extends LightUIElement> T findElement(final String searchParam, final boolean byUUID, final Class<T> objectClass) {
if (this.hasRow()) {
 
for (int i = 0; i < this.getRowsCount(); i++) {
final Row row = this.getRow(i);
final List<Object> rowValues = row.getValues();
for (final Object value : rowValues) {
if (value instanceof LightUIContainer) {
184,19 → 302,19
if (byUUID) {
if (element.getUUID().equals(searchParam)) {
if (objectClass.isAssignableFrom(element.getClass())) {
return (T) element;
return objectClass.cast(element);
} else {
throw new IllegalArgumentException("Element found at is not an instance of " + objectClass.getName() + ", element class: " + element.getClass().getName()
+ " element ID: " + element.getId());
throw new IllegalArgumentException(
"Element found at is not an instance of " + objectClass.getName() + ", element class: " + element.getClass().getName() + " element ID: " + element.getId());
}
}
} else {
if (element.getId().equals(searchParam)) {
if (objectClass.isAssignableFrom(element.getClass())) {
return (T) element;
return objectClass.cast(element);
} else {
throw new IllegalArgumentException("Element found at is not an instance of " + objectClass.getName() + ", element class: " + element.getClass().getName()
+ " element ID: " + element.getId());
throw new IllegalArgumentException(
"Element found at is not an instance of " + objectClass.getName() + ", element class: " + element.getClass().getName() + " element ID: " + element.getId());
}
}
}
213,37 → 331,128
} else {
System.out.println("LightUITable.getElementById() - No rows for table: " + this.getId());
}
} else {
System.out.println("LightUITable.getElementById() - Null TableContent for table: " + this.getId());
return null;
}
 
public <T extends LightUIElement> List<T> findChildren(final Class<T> expectedClass, final boolean recursively) {
final List<T> result = new ArrayList<T>();
 
if (this.hasRow()) {
final int size = this.getRowsCount();
for (int i = 0; i < size; i++) {
final Row row = this.getRow(i);
final List<Object> rowValues = row.getValues();
for (final Object value : rowValues) {
if (recursively) {
if (value instanceof LightUIContainer) {
result.addAll(((LightUIContainer) value).findChildren(expectedClass, recursively));
} else if (value instanceof LightUITable) {
result.addAll(((LightUITable) value).findChildren(expectedClass, recursively));
}
}
if (expectedClass.isAssignableFrom(value.getClass())) {
result.add(expectedClass.cast(value));
}
}
}
} else {
System.out.println("LightUITable.getElementById() - Null TableSpec for table: " + this.getId());
System.out.println("LightUITable.getElementById() - No rows for table: " + this.getId());
}
return null;
 
return result;
}
 
public void addSelectionListener(final ActionListener selectionListener) {
public final void addSelectionListener(final ActionListener selectionListener) {
this.selectionListeners.add(selectionListener);
}
 
public void removeSelectionListeners() {
public final void removeSelectionListeners() {
this.selectionListeners.clear();
}
 
public void fireSelectionChange() {
public final void fireSelectionChange() {
for (final ActionListener listener : this.selectionListeners) {
listener.actionPerformed(new ActionEvent(this, 1, "selection"));
}
}
 
// TODO: garder l'ordre des colonnes invisibles
/**
* Create columns preferences with the current ColumnsSpec
*
* @return XML document with columns preferences
*/
public final Document createXmlPreferences(final Document userPrefs, final ColumnsSpec columnsSpec) throws ParserConfigurationException {
 
final Element rootElement = new Element("list");
final Document xmlConf = new Document();
 
final int columnSpecCount = columnsSpec.getColumnCount();
final List<String> visibleIds = new ArrayList<String>();
for (int i = 0; i < columnSpecCount; i++) {
final ColumnSpec columnSpec = columnsSpec.getColumn(i);
final Element xmlColumn = this.createXmlColumn(columnSpec.getId(), columnSpec.getMaxWidth(), columnSpec.getMinWidth(), columnSpec.getWidth());
rootElement.addContent(xmlColumn);
visibleIds.add(columnSpec.getId());
}
 
final Element rootUserPrefs = userPrefs.getRootElement();
final List<Element> xmlColumns = rootUserPrefs.getChildren();
final int columnsSize = xmlColumns.size();
for (int i = 0; i < columnsSize; i++) {
final Element xmlColumn = xmlColumns.get(i);
final String columnId = xmlColumn.getAttribute("id").getValue();
if (!visibleIds.contains(columnId)) {
final int maxWidth = Integer.parseInt(xmlColumn.getAttribute("max-width").getValue());
final int minWidth = Integer.parseInt(xmlColumn.getAttribute("min-width").getValue());
final int width = Integer.parseInt(xmlColumn.getAttribute("width").getValue());
final Element newXmlColumn = this.createXmlColumn(columnId, maxWidth, minWidth, width);
rootElement.addContent(newXmlColumn);
}
}
xmlConf.setRootElement(rootElement);
return xmlConf;
}
 
/**
* Create default columns preferences from the SQLTableModelLinesSourceOnline
*
* @return XML document with columns preferences
*/
public Document createDefaultXmlPreferences() {
final Element rootElement = new Element("list");
 
if (this.getTableSpec() != null && this.getTableSpec().getColumns() != null) {
final int sqlColumnsCount = this.getTableSpec().getColumns().getColumnCount();
for (int i = 0; i < sqlColumnsCount; i++) {
final ColumnSpec column = this.getTableSpec().getColumns().getColumn(i);
final String columnId = column.getId();
final Element columnElement = this.createXmlColumn(columnId, column.getMaxWidth(), column.getMinWidth(), column.getWidth());
rootElement.addContent(columnElement);
}
}
final Document xmlConf = new Document(rootElement);
 
return xmlConf;
}
 
protected final Element createXmlColumn(final String columnId, final double maxWidth, final double minWidth, final double width) {
final Element columnElement = new Element("column");
columnElement.setAttribute("id", columnId);
columnElement.setAttribute("max-width", String.valueOf(maxWidth));
columnElement.setAttribute("min-width", String.valueOf(minWidth));
columnElement.setAttribute("width", String.valueOf(width));
return columnElement;
}
 
@Override
public void setReadOnly(final boolean readOnly) {
super.setReadOnly(readOnly);
final List<Row> rows = this.tableSpec.getContent().getRows();
if (rows != null) {
final int rowCount = rows.size();
for (int i = 0; i < rowCount; i++) {
final Row row = rows.get(i);
 
if (this.hasRow()) {
final int size = this.getRowsCount();
for (int i = 0; i < size; i++) {
final Row row = this.getRow(i);
final List<Object> values = row.getValues();
for (final Object value : values) {
if (value != null && value instanceof LightUIElement) {
265,7 → 474,7
}
 
@Override
public void setValueFromContext(final Object value) {
public void _setValueFromContext(final Object value) {
if (value != null) {
final JSONArray jsonContext = (JSONArray) JSONConverter.getObjectFromJSON(value, JSONArray.class);
final ColumnsSpec columnsSpec = this.getTableSpec().getColumns();
280,21 → 489,24
}
}
 
final TableContent tableContent = this.getTableSpec().getContent();
if (tableContent != null) {
final List<Row> rows = tableContent.getRows();
for (int i = 0; i < rows.size(); i++) {
final Row row = rows.get(i);
if (this.hasRow()) {
final int size = this.getRowsCount();
if (jsonContext.size() != size) {
System.err.println("LightUITable.setValueFromContext() - Incorrect line count in JSON");
} else {
 
for (int i = 0; i < size; i++) {
final Row row = this.getRow(i);
final JSONObject jsonLineContext = (JSONObject) JSONConverter.getObjectFromJSON(jsonContext.get(i), JSONObject.class);
final Long rowId = (Long) JSONConverter.getParameterFromJSON(jsonLineContext, "row.id", Long.class);
final Number rowId = JSONConverter.getParameterFromJSON(jsonLineContext, "row.id", Number.class);
final String rowExtendId = (String) JSONConverter.getParameterFromJSON(jsonLineContext, "row.extend.id", String.class);
if (rowId == row.getId() && (row.getExtendId() == null || (row.getExtendId() != null && rowExtendId.equals(row.getExtendId())))) {
if (NumberUtils.areNumericallyEqual(rowId, row.getId()) && (row.getExtendId() == null || (row.getExtendId() != null && rowExtendId.equals(row.getExtendId())))) {
if (row.isFillWidth()) {
if (!row.getValues().isEmpty() && row.getValues().get(0) instanceof IUserControl) {
if (!row.getValues().isEmpty() && row.getValues().get(0) instanceof LightUserControl) {
final LightUIElement element = (LightUIElement) row.getValues().get(0);
if (element instanceof IUserControl) {
if (element instanceof LightUserControl) {
if (jsonLineContext.containsKey(element.getUUID())) {
((IUserControl) element).setValueFromContext(jsonLineContext.get(element.getUUID()));
((LightUserControl) element)._setValueFromContext(jsonLineContext.get(element.getUUID()));
} else {
System.out.println("LightUITable.setValueFromContext() - Unable to find element : id - " + element.getId() + " uuid - " + element.getUUID());
System.out.println("LightUITable.setValueFromContext() - In JSON : " + jsonLineContext.toJSONString());
304,13 → 516,13
} else {
for (int k = 0; k < editorsIndex.size(); k++) {
final Object objEditor = row.getValues().get(editorsIndex.get(k));
if (!(objEditor instanceof IUserControl)) {
if (!(objEditor instanceof LightUserControl)) {
throw new IllegalArgumentException("Impossible to find editor for row: " + rowId.toString() + " at position: " + String.valueOf(k));
}
final LightUIElement editor = (LightUIElement) objEditor;
 
if (editor instanceof IUserControl && jsonLineContext.containsKey(editor.getUUID())) {
((IUserControl) editor).setValueFromContext(jsonLineContext.get(editor.getUUID()));
if (editor instanceof LightUserControl && jsonLineContext.containsKey(editor.getUUID())) {
((LightUserControl) editor)._setValueFromContext(jsonLineContext.get(editor.getUUID()));
} else {
throw new IllegalArgumentException(
"Impossible to find value for editor: " + editor.getId() + " for row: " + rowId.toString() + " at position: " + String.valueOf(k));
324,6 → 536,7
}
}
}
}
 
@Override
public LightUIElement clone() {
334,21 → 547,22
public JSONObject toJSON() {
final JSONObject json = super.toJSON();
if (this.allowSelection) {
json.put("allow-selection", true);
json.put(ALLOW_SELECTION, true);
}
if (this.allowMultiSelection) {
json.put(ALLOW_MULTI_SELECTION, true);
}
if (this.dynamicLoad) {
json.put("dynamic-load", true);
json.put(DYNAMIC_LOAD, true);
}
if (this.verticallyScrollable) {
json.put("vertically-scrollable", true);
if (!this.autoSelectFirstLine) {
json.put(AUTO_SELECT_FIRST_LINE, false);
}
if (this.tableSpec != null) {
json.put("table-spec", this.tableSpec.toJSON());
json.put(TABLE_SPEC, this.tableSpec.toJSON());
}
if (this.elementCode != null) {
json.put("element-code", this.elementCode);
}
json.put("line-per-row", this.linePerRow);
 
json.put(LINE_PER_ROW, this.linePerRow);
return json;
}
 
355,16 → 569,22
@Override
public void fromJSON(final JSONObject json) {
super.fromJSON(json);
this.allowSelection = JSONConverter.getParameterFromJSON(json, "allow-selection", Boolean.class, false);
this.dynamicLoad = JSONConverter.getParameterFromJSON(json, "dynamic-load", Boolean.class, false);
this.verticallyScrollable = JSONConverter.getParameterFromJSON(json, "vertically-scrollable", Boolean.class, false);
this.elementCode = JSONConverter.getParameterFromJSON(json, "element-code", String.class);
this.linePerRow = JSONConverter.getParameterFromJSON(json, "line-per-row", Integer.class);
this.allowSelection = JSONConverter.getParameterFromJSON(json, ALLOW_SELECTION, Boolean.class, false);
this.allowSelection = JSONConverter.getParameterFromJSON(json, ALLOW_MULTI_SELECTION, Boolean.class, false);
this.dynamicLoad = JSONConverter.getParameterFromJSON(json, DYNAMIC_LOAD, Boolean.class, false);
this.autoSelectFirstLine = JSONConverter.getParameterFromJSON(json, AUTO_SELECT_FIRST_LINE, Boolean.class, true);
this.linePerRow = JSONConverter.getParameterFromJSON(json, LINE_PER_ROW, Integer.class);
 
final JSONObject jsonRawContent = (JSONObject) JSONConverter.getParameterFromJSON(json, "table-spec", JSONObject.class);
final JSONObject jsonRawContent = (JSONObject) JSONConverter.getParameterFromJSON(json, TABLE_SPEC, JSONObject.class);
 
if (jsonRawContent != null) {
this.tableSpec = new TableSpec(jsonRawContent);
}
}
 
@Override
public void destroy() {
super.destroy();
this.selectionListeners.clear();
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/light/LightUILine.java
68,7 → 68,7
this.gridAlignment = gridAlignment;
}
 
public Integer getHeight() {
public Integer getTotalHeight() {
Integer h = 0;
final int size = this.getChildrenCount();
for (int i = 0; i < size; i++) {
77,7 → 77,7
return h;
}
 
public Integer getWidth() {
public Integer getTotalGridWidth() {
Integer w = 0;
final int size = this.getChildrenCount();
for (int i = 0; i < size; i++) {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/TreeItem.java
40,7 → 40,7
}
 
public final String getId() {
return id;
return this.id;
}
 
public final void setId(String id) {
48,7 → 48,7
}
 
public String getLabel() {
return label;
return this.label;
}
 
public void setLabel(String label) {
56,15 → 56,15
}
 
public TreeItem getChild(int i) {
return children.get(i);
return this.children.get(i);
}
 
public int getChildCount() {
return children.size();
return this.children.size();
}
 
public final List<TreeItem> getChildren() {
return children;
return this.children;
}
 
public final void addChild(TreeItem item) {
90,7 → 90,7
}
 
public final boolean isSelected() {
return isSelected;
return this.isSelected;
}
 
public final void setSelected(boolean isSelected) {
98,7 → 98,7
}
 
public final Color getColor() {
return color;
return this.color;
}
 
public final void setColor(Color color) {
106,7 → 106,7
}
 
public final boolean isExpanded() {
return expanded;
return this.expanded;
}
 
public final void setExpanded(boolean expanded) {
114,7 → 114,7
}
 
public final String getIconId() {
return iconId;
return this.iconId;
}
 
public final void setRightIconId(String iconId) {
/trunk/OpenConcerto/src/org/openconcerto/ui/light/SearchSpec.java
23,7 → 23,7
 
public class SearchSpec implements Transferable {
private String tableId;
private List<SearchContent> content;
private List<SearchContent> content = new ArrayList<SearchContent>();
 
public SearchSpec(final String tableId) {
this.tableId = tableId;
33,11 → 33,11
this.fromJSON(json);
}
 
public String getTableId() {
public final String getTableId() {
return this.tableId;
}
 
public List<SearchContent> getContent() {
public final List<SearchContent> getContent() {
return this.content;
}
 
/trunk/OpenConcerto/src/org/openconcerto/ui/light/TableContent.java
31,7 → 31,7
}
 
public TableContent(final String tableId) {
this.init(tableId, null);
this.init(tableId, new ArrayList<Row>());
}
 
public TableContent(final String tableId, final List<Row> rows) {
42,38 → 42,65
this.fromJSON(json);
}
 
private void init(final String tableId, final List<Row> rows) {
private final void init(final String tableId, final List<Row> rows) {
this.tableId = tableId;
if (rows != null) {
this.rows = rows;
} else {
this.rows = new ArrayList<Row>();
}
}
 
public String getTableId() {
public final String getTableId() {
return this.tableId;
}
 
public void setTableId(final String tableId) {
public final void setTableId(final String tableId) {
this.tableId = tableId;
}
 
public List<Row> getRows() {
return this.rows;
public final synchronized Row getRow(final int index) {
return this.rows.get(index);
}
 
public void setRows(List<Row> rows) {
public final synchronized int getRowsCount() {
return this.rows.size();
}
 
public final synchronized boolean addRow(final Row row) {
return this.rows.add(row);
}
 
public final synchronized Row setRow(final int index, final Row row) {
return this.rows.set(index, row);
}
 
public final synchronized Row removeRow(final int index) {
return this.rows.remove(index);
}
 
public final synchronized boolean removeRow(final Row row) {
return this.rows.remove(row);
}
 
public final synchronized void setRows(List<Row> rows) {
this.rows = rows;
}
 
public final synchronized void clearRows() {
this.rows.clear();
}
/**
* @return a copy of the list
*/
public final synchronized List<Row> getRows(){
return new ArrayList<Row>(this.rows);
}
 
@Override
public String toString() {
return "TableContent of " + this.tableId + " lines count : " + getRows().size();
public synchronized String toString() {
return "TableContent of " + this.tableId + " lines count : " + this.getRowsCount();
}
 
@Override
public JSONObject toJSON() {
public synchronized JSONObject toJSON() {
final JSONObject result = new JSONObject();
result.put("class", "TableContent");
result.put("table-id", this.tableId);
82,14 → 109,15
}
 
@Override
public void fromJSON(final JSONObject json) {
this.tableId = (String) JSONConverter.getParameterFromJSON(json, "table-id", String.class);
final JSONArray jsonRows = (JSONArray) JSONConverter.getParameterFromJSON(json, "rows", JSONArray.class);
public synchronized void fromJSON(final JSONObject json) {
this.tableId = JSONConverter.getParameterFromJSON(json, "table-id", String.class);
final JSONArray jsonRows = JSONConverter.getParameterFromJSON(json, "rows", JSONArray.class);
if (jsonRows != null) {
this.rows = new ArrayList<Row>();
final List<Row> listRows = new ArrayList<Row>();
for (final Object o : jsonRows) {
this.rows.add(new Row((JSONObject) JSONConverter.getObjectFromJSON(o, JSONObject.class)));
listRows.add(new Row(JSONConverter.getObjectFromJSON(o, JSONObject.class)));
}
this.setRows(listRows);
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/translation/messages_fr.properties
6,6 → 6,8
saveWindowState=Impossible de sauvegarder la taille et la position de la fenêtre
prefCreationError=Impossible de créer le panneau de préférence
 
missingFile=Le fichier n\u2019existe pas : {0}
 
userExit.question=Voulez-vous vraiment quitter ?
userExit.title=Quitter
 
/trunk/OpenConcerto/src/org/openconcerto/ui/translation/messages_en.properties
6,6 → 6,8
saveWindowState=Error while saving size and position of the window
prefCreationError=Error while creating the preference panel
 
missingFile=The file doesn\u2019t exist : {0}
 
userExit.question=Do you really want to quit ?
userExit.title=Quit
 
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_en.properties
129,6 → 129,9
infoPanel.dirs=Folders
infoPanel.logs=Logs
infoPanel.docs=Documents
infoPanel.dataDir=Data
infoPanel.prefsDir=Preferences
infoPanel.cacheDir=Cache
 
infoPanel.softwareTitle=Software
infoPanel.systemTitle=System information
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_fr.properties
130,6 → 130,9
infoPanel.dirs=Dossiers
infoPanel.logs=Journaux
infoPanel.docs=Documents
infoPanel.dataDir=Données
infoPanel.prefsDir=Préférences
infoPanel.cacheDir=Cache
 
infoPanel.softwareTitle=Logiciel
infoPanel.systemTitle=Informations système
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/SQLCreateTable.java
34,7 → 34,7
private boolean createID, createOrder, createArchive;
 
public SQLCreateTable(final DBRoot b, final String name) {
super(b.getServer().getSQLSystem().getSyntax(), b.getName(), name);
super(b.getDBSystemRoot().getSyntax(), b.getName(), name);
this.b = b;
this.reset();
}
108,7 → 108,9
}
 
@Override
protected void modifyClauses(List<String> genClauses) {
protected void modifyClauses(final List<String> genClauses, final NameTransformer transf, final ClauseType type) {
super.modifyClauses(genClauses, transf, type);
if (type == ClauseType.ADD_COL) {
if (this.isCreateID())
genClauses.add(0, SQLBase.quoteIdentifier(SQLSyntax.ID_NAME) + this.getSyntax().getPrimaryIDDefinition());
if (this.isCreateArchive())
122,11 → 124,12
}
}
}
}
 
@Override
protected void modifyOutClauses(List<DeferredClause> clauses) {
super.modifyOutClauses(clauses);
if (this.isCreateOrder() && getSyntax().getSystem() == SQLSystem.MSSQL) {
protected void modifyOutClauses(final List<DeferredClause> clauses, final ClauseType type) {
super.modifyOutClauses(clauses, type);
if (type == ClauseType.ADD_CONSTRAINT && this.isCreateOrder() && getSyntax().getSystem() == SQLSystem.MSSQL) {
clauses.add(this.createUniquePartialIndex("orderIdx", Collections.singletonList(SQLSyntax.ORDER_NAME), null));
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ReOrder.java
191,7 → 191,7
@Override
public final String getInc() {
final SQLField oF = this.t.getOrderField();
final SQLSyntax syntax = SQLSyntax.get(this.t.getServer().getSQLSystem());
final SQLSyntax syntax = SQLSyntax.get(this.t);
 
// last order of the whole table
final SQLSelect selTableLast = new SQLSelect(true);
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/SQLUtils.java
19,10 → 19,12
import org.openconcerto.sql.model.IResultSetHandler;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLRequestLog;
import org.openconcerto.sql.model.SQLResultSet;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.utils.RTInterruptedException;
 
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
371,6 → 373,7
final long afterQueryInfo = System.nanoTime();
final long afterExecute, afterHandle;
final Statement stmt = conn.createStatement();
int count = 0;
try {
if (Thread.currentThread().isInterrupted())
throw new RTInterruptedException("Interrupted before executing : " + query);
379,7 → 382,13
for (final ResultSetHandler h : handlers) {
if (Thread.currentThread().isInterrupted())
throw new RTInterruptedException("Interrupted while handling results : " + query);
results.add(h == null ? null : h.handle(stmt.getResultSet()));
if (h == null) {
results.add(null);
} else {
final ResultSet resultSet = stmt.getResultSet();
results.add(h.handle(resultSet));
count += SQLResultSet.getRowProcessedCount(resultSet);
}
stmt.getMoreResults();
}
afterHandle = System.nanoTime();
386,7 → 395,7
} finally {
stmt.close();
}
SQLRequestLog.log(query, "executeMultiple", conn, timeMs, time, afterCache, afterQueryInfo, afterExecute, afterHandle, System.nanoTime());
SQLRequestLog.log(query, "executeMultiple", conn, timeMs, time, afterCache, afterQueryInfo, afterExecute, afterHandle, System.nanoTime(), count);
return null;
}
});
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ModelCreator.java
14,19 → 14,31
package org.openconcerto.sql.utils;
 
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
import org.openconcerto.sql.model.SQLServer;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.DefaultListModel;
import org.openconcerto.utils.ClipboardUtils;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.LogUtils;
import org.openconcerto.utils.StringUtils;
 
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
46,10 → 58,13
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
 
import org.apache.commons.dbutils.ResultSetHandler;
import org.jedit.JEditTextArea;
import org.jedit.JavaTokenMarker;
 
61,26 → 76,30
final JTextField rootTF = new JTextField();
 
public static void main(String[] args) {
 
SwingUtilities.invokeLater(new Runnable() {
 
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
 
LogUtils.rmRootHandlers();
LogUtils.setUpConsoleHandler();
Logger.getLogger("org.openconcerto.sql").setLevel(Level.WARNING);
 
ModelCreator m = new ModelCreator();
m.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
m.setSize(1000, 800);
m.setVisible(true);
}
});
 
}
 
ModelCreator() {
super("FrameWork SQL Toolbox");
 
this.pref = Preferences.userRoot().node("/ilm/sql/" + getClass().getSimpleName());
 
JPanel confPanel = new JPanel();
100,7 → 119,13
c.weightx = 0;
c.gridwidth = 4;
buttonConnect = new JButton("Connexion");
confPanel.add(buttonConnect, c);
JPanel toolbar = new JPanel();
toolbar.setLayout(new FlowLayout());
toolbar.add(buttonConnect);
JButton bAnalyse = new JButton("Analyse de toutes les tables");
 
toolbar.add(bAnalyse);
confPanel.add(toolbar, c);
final DefaultListModel model = new DefaultListModel();
 
this.list = new JList(model);
135,11 → 160,13
rootTF.setEnabled(false);
buttonConnect.setText("Connexion en cours");
buttonConnect.setEnabled(false);
final String url = rootTF.getText();
final String[] args = rootTF.getText().split(" ");
final String url = args[0];
final List<String> rootsToMap = args.length == 1 ? Collections.<String> emptyList() : SQLRow.toList(args[1]);
final Thread t = new Thread(new Runnable() {
@Override
public void run() {
connect(model, url);
connect(model, url, rootsToMap);
}
});
t.setDaemon(true);
148,9 → 175,74
 
});
this.list.addListSelectionListener(this);
bAnalyse.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
analyseAll();
 
}
});
}
 
protected void analyseAll() {
PrintStream ptr = System.out;
final ListModel model = this.list.getModel();
int stop = model.getSize();
for (int i = 0; i < stop; i++) {
SQLTable t = (SQLTable) model.getElementAt(i);
analyse(ptr, t);
}
}
 
private void analyse(final PrintStream ptr, SQLTable t) {
final DecimalFormat df = new DecimalFormat("0.##%");
 
ptr.println("Table " + t.getName());
final int totalCount = t.getRowCount(false, ArchiveMode.UNARCHIVED);
ptr.println("Nombre de lignes : " + totalCount);
if (totalCount > 0) {
final List<String> fieldNames = new ArrayList<String>(t.getFieldsNames(VirtualFields.CONTENT));
Collections.sort(fieldNames);
for (String fieldName : fieldNames) {
final SQLField sqlField = t.getField(fieldName);
SQLSelect select = new SQLSelect();
select.addSelect(sqlField);
select.addSelectFunctionStar("count");
select.addGroupBy(sqlField);
select.addRawOrder("count(*) DESC");
select.setLimit(10);
 
ptr.print("- champ " + SQLBase.quoteIdentifier(fieldName));
final Number uniqCount = (Number) t.getDBSystemRoot().getDataSource()
.executeScalar("SELECT COUNT(*) FROM (" + new SQLSelect().addSelect(sqlField).addGroupBy(sqlField).asString() + ") g");
if (uniqCount.longValue() > 10l) {
ptr.print(", top 10 des valeurs (sur " + uniqCount + "):");
}
ptr.println();
t.getDBSystemRoot().getDataSource().execute(select.asString(), new ResultSetHandler() {
@Override
public Object handle(ResultSet rs) throws SQLException {
while (rs.next()) {
final int count = rs.getInt(2);
String percent = df.format(count / (double) totalCount);
percent = StringUtils.rightAlign(percent, 5);
ptr.print(" " + percent + " : \"" + rs.getObject(1) + "\"");
if (count != totalCount) {
ptr.print(" (" + count + "/" + totalCount + ")");
}
 
ptr.println();
}
return null;
}
});
 
}
}
ptr.println();
}
 
public void valueChanged(ListSelectionEvent e) {
this.pane.removeAll();
 
202,13 → 294,11
return p;
}
 
private void connect(final DefaultListModel model, final String textUrl) {
private void connect(final DefaultListModel model, final String textUrl, final List<String> roots) {
ModelCreator.this.pref.put("url", textUrl);
 
try {
final SQL_URL url = SQL_URL.create(textUrl);
List<String> roots = new ArrayList<String>();
roots.add("Common");
final DBSystemRoot sysRoot = SQLServer.create(url, roots, null);
final List<SQLTable> tables = new ArrayList<SQLTable>(sysRoot.getRoot(url.getRootName()).getDescs(SQLTable.class));
Collections.sort(tables, new Comparator<SQLTable>() {
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ChangeTable.java
14,6 → 14,7
package org.openconcerto.sql.utils;
 
import static java.util.Collections.singletonList;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLField;
69,8 → 70,8
public static final String MYSQL_FAKE_PROCEDURE = "Unique constraint violation";
public static final String MYSQL_TRIGGER_EXCEPTION = "call " + SQLBase.quoteIdentifier(MYSQL_FAKE_PROCEDURE);
// group 1 is the table name, group 2 the where
public static final Pattern MYSQL_UNIQUE_TRIGGER_PATTERN = Pattern.compile("IF\\s*\\(\\s*" + Pattern.quote("SELECT COUNT(*)") + "\\s+FROM\\s+(.+)\\s+where\\s+(.+)\\)\\s*>\\s*1\\s+then\\s+"
+ Pattern.quote(MYSQL_TRIGGER_EXCEPTION), Pattern.CASE_INSENSITIVE);
public static final Pattern MYSQL_UNIQUE_TRIGGER_PATTERN = Pattern.compile(
"IF\\s*\\(\\s*" + Pattern.quote("SELECT COUNT(*)") + "\\s+FROM\\s+(.+)\\s+where\\s+(.+)\\)\\s*>\\s*1\\s+then\\s+" + Pattern.quote(MYSQL_TRIGGER_EXCEPTION), Pattern.CASE_INSENSITIVE);
// to split the where
public static final Pattern MYSQL_WHERE_PATTERN = Pattern.compile("\\s+and\\s+", Pattern.CASE_INSENSITIVE);
// to find the column name
96,12 → 97,12
 
public static enum ConcatStep {
// drop constraints first since, at least in pg, they depend on indexes
DROP_FOREIGN(ClauseType.DROP_CONSTRAINT),
DROP_CONSTRAINT(ClauseType.DROP_CONSTRAINT),
// drop indexes before columns to avoid having to know if the index is dropped because its
// columns are dropped
DROP_INDEX(ClauseType.DROP_INDEX), ALTER_TABLE(ClauseType.ADD_COL, ClauseType.DROP_COL, ClauseType.ALTER_COL, ClauseType.OTHER),
// likewise add indexes before since constraints need them
ADD_INDEX(ClauseType.ADD_INDEX), ADD_FOREIGN(ClauseType.ADD_CONSTRAINT);
ADD_INDEX(ClauseType.ADD_INDEX), ADD_CONSTRAINT(ClauseType.ADD_CONSTRAINT);
 
private final Set<ClauseType> types;
 
337,8 → 338,8
* @param l an existing link, e.g. root1.LOCAL pointing to root1.BATIMENT.
* @param newDest the new destination for the link, e.g. root2.BATIMENT.
* @return a new instance, e.g. root1.LOCAL pointing to root2.BATIMENT.
* @throws IllegalArgumentException if <code>newDest</code> is not compatible with
* <code>l.{@link Link#getTarget() getTarget()}</code>.
* @throws IllegalArgumentException if <code>newDest</code> is not compatible with <code>l.
* {@link Link#getTarget() getTarget()}</code>.
*/
static public FCSpec createFromLink(final Link l, final SQLTable newDest) {
if (newDest != l.getTarget()) {
478,13 → 479,32
*
* @param name the name of the column.
* @param count the number of characters.
* @param lenient <code>true</code> if <code>count</code> should be restricted to the maximum
* allowed value of the system, <code>false</code> will throw an exception.
* @param lenient <code>true</code> if <code>count</code> should be restricted to the
* {@link SQLSyntax#getMaximumVarCharLength() maximum} allowed value of the system,
* <code>false</code> will throw an exception.
* @return this.
* @throws IllegalArgumentException if <code>count</code> is too high and <code>lenient</code>
* is <code>false</code>.
*/
public final T addVarCharColumn(final String name, int count, final boolean lenient) throws IllegalArgumentException {
return this.addVarCharColumn(name, count, lenient, "''", false);
}
 
/**
* Adds a varchar column.
*
* @param name the name of the column.
* @param count the number of characters.
* @param lenient <code>true</code> if <code>count</code> should be restricted to the
* {@link SQLSyntax#getMaximumVarCharLength() maximum} allowed value of the system,
* <code>false</code> will throw an exception.
* @param defaultValue the SQL default value of the column, can be <code>null</code>, e.g. "''".
* @param nullable whether the column accepts NULL.
* @return this.
* @throws IllegalArgumentException if <code>count</code> is too high and <code>lenient</code>
* is <code>false</code>.
*/
public final T addVarCharColumn(final String name, int count, final boolean lenient, final String defaultValue, final boolean nullable) throws IllegalArgumentException {
final int max = getSyntax().getMaximumVarCharLength();
if (count > max) {
if (lenient) {
494,7 → 514,7
throw new IllegalArgumentException("Count too high : " + count + " > " + max);
}
}
return this.addColumn(name, "varchar(" + count + ") default '' NOT NULL");
return this.addColumn(name, "varchar(" + count + ")", defaultValue, nullable);
}
 
public final T addDateAndTimeColumn(String name) {
571,7 → 591,8
}
 
public final T addBooleanColumn(String name, Boolean defaultVal, boolean nullable) {
return this.addColumn(name, getSyntax().getBooleanType(), SQLType.getBoolean(getSyntax()).toString(defaultVal), nullable);
final SQLType boolType = SQLType.getBoolean(getSyntax());
return this.addColumn(name, boolType.getTypeName(), boolType.toString(defaultVal), nullable);
}
 
/**
598,9 → 619,15
return this.addColumn(name, this.getSyntax().getFieldDecl(f));
}
 
public final T addIndex(final Index index) {
return this.addOutsideClause(getSyntax().getCreateIndex(index));
public final boolean addIndex(final Index index) {
// only add index if there won't be an automatic one created. As explained in
// addForeignConstraint() if we did add one in H2 we would need to drop it explicitly.
final boolean add = !this.hasAutomaticIndex(index);
if (add) {
this.addOutsideClause(getSyntax().getCreateIndex(index));
}
return add;
}
 
public final T addForeignConstraint(Link l, boolean createIndex) {
return this.addForeignConstraint(FCSpec.createFromLink(l), createIndex);
624,9 → 651,16
return this.addForeignConstraint(new FCSpec(fieldName, refTable, refCols, null, null), createIndex);
}
 
// createIndex = false for when we only removeForeignConstraint() without removing the column :
// - if there's was no index, don't add one
// - if there's was one automatic index (e.g. H2) it was dropped and will be added again
// - if there's was one manual index (e.g. pg) it wasn't dropped
public final T addForeignConstraint(final FCSpec fkSpec, boolean createIndex) {
this.fks.add(fkSpec);
if (createIndex)
// check autoCreatesFKIndex() to avoid creating a duplicate index. Also, on H2, only
// automatically created indexes are automatically dropped ; so if we added one here we
// would need to drop it in AlterTable.dropColumn()
if (createIndex && !getSyntax().getSystem().autoCreatesFKIndex())
this.addOutsideClause(new OutsideClause() {
@Override
public ClauseType getType() {
650,6 → 684,17
return Collections.unmodifiableList(this.fks);
}
 
// true if a foreign constraint will create an equivalent index
private final boolean hasAutomaticIndex(final Index i) {
if (i.isUnique() || !StringUtils.isEmpty(i.getFilter()) || !getSyntax().getSystem().autoCreatesFKIndex())
return false;
for (final FCSpec fc : this.fks) {
if (fc.getCols().equals(i.getCols()))
return true;
}
return false;
}
 
// * addForeignColumn = addColumn + addForeignConstraint
 
public T addForeignColumn(SQLCreateTableBase<?> createTable) {
766,7 → 811,7
return this.addOutsideClause(createUniquePartialIndex(name, cols, where));
} else if (system == SQLSystem.H2) {
// initial select to check uniqueness
if (this instanceof AlterTable) {
if (checkExistingUniqueness()) {
// TODO should implement SIGNAL instead of abusing CSVREAD
this.addOutsideClause(new DeferredClause() {
@Override
798,7 → 843,7
});
} else if (system == SQLSystem.MYSQL) {
// initial select to check uniqueness
if (this instanceof AlterTable) {
if (checkExistingUniqueness()) {
this.addOutsideClause(new DeferredClause() {
@Override
public ClauseType getType() {
850,6 → 895,11
}
}
 
// can't be inlined with javac 1.6
private boolean checkExistingUniqueness() {
return this instanceof AlterTable;
}
 
protected final DeferredClause createUniquePartialIndex(final String name, final List<String> cols, final String userWhere) {
// http://stackoverflow.com/questions/767657/how-do-i-create-a-unique-constraint-that-also-allows-nulls
final Where notNullWhere = getSyntax().getSystem() == SQLSystem.MSSQL ? Where.createRaw(getNotNullWhere(cols)) : null;
959,21 → 1009,26
return thisAsT();
}
 
protected final List<String> getClauses(SQLName tableName, ClauseType type) {
if (this.inClauses.size() == 0)
return this.clauses.getNonNull(type);
else {
protected final List<String> getClauses(final SQLName tableName, final NameTransformer transf, final ClauseType type) {
final List<String> res = new ArrayList<String>(this.clauses.getNonNull(type));
for (final DeferredClause c : this.inClauses.getNonNull(type))
res.add(c.asString(this, tableName));
this.modifyClauses(res, transf, type);
return res;
}
 
protected void modifyClauses(final List<String> genClauses, final NameTransformer transf, final ClauseType type) {
if (type == ClauseType.ADD_CONSTRAINT) {
genClauses.addAll(this.getForeignConstraints(transf));
}
}
 
protected final List<String> getClauses(SQLName tableName, Collection<ClauseType> types) {
protected final List<String> getClauses(final SQLName tableName, final NameTransformer transf, final Collection<ClauseType> types) {
final List<String> res = new ArrayList<String>();
for (final ClauseType type : types)
res.addAll(this.getClauses(tableName, type));
for (final ClauseType type : ORDERED_TYPES) {
if (types.contains(type))
res.addAll(this.getClauses(tableName, transf, type));
}
return res;
}
 
995,22 → 1050,37
}
 
protected final void outClausesAsString(final StringBuffer res, final SQLName tableName, final Set<ClauseType> types) {
final List<DeferredClause> clauses = new ArrayList<DeferredClause>();
for (final ClauseType type : types)
clauses.addAll(this.outClauses.getNonNull(type));
this.modifyOutClauses(clauses);
if (clauses.size() > 0) {
final List<String> outClauses = this.getOutClauses(tableName, types);
if (outClauses.size() > 0) {
res.append("\n\n");
res.append(CollectionUtils.join(clauses, "\n", new ITransformer<DeferredClause, String>() {
res.append(CollectionUtils.join(outClauses, "\n"));
}
}
 
private final List<String> getOutClauses(final SQLName tableName, final Set<ClauseType> types) {
final List<String> res = new ArrayList<String>();
for (final ClauseType type : ORDERED_TYPES) {
if (types.contains(type)) {
final String outClauses = this.getOutClauses(tableName, type);
if (outClauses.length() > 0)
res.add(outClauses);
}
}
return res;
}
 
protected final String getOutClauses(final SQLName tableName, final ClauseType type) {
final List<DeferredClause> clauses = new ArrayList<DeferredClause>(this.outClauses.getNonNull(type));
this.modifyOutClauses(clauses, type);
return CollectionUtils.join(clauses, "\n", new ITransformer<DeferredClause, String>() {
@Override
public String transformChecked(DeferredClause input) {
return input.asString(ChangeTable.this, tableName);
}
}));
});
}
}
 
protected void modifyOutClauses(List<DeferredClause> clauses) {
protected void modifyOutClauses(final List<DeferredClause> genClauses, final ClauseType type) {
}
 
public final String asString() {
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/Diff.java
168,7 → 168,7
return new DropTable(this.a.getTable(tableName));
else if (!inA && inB) {
final SQLTable bT = this.b.getTable(tableName);
return bT.getCreateTable(this.a.getServer().getSQLSystem());
return bT.getCreateTable(this.a.getDBSystemRoot().getSyntax());
} else {
// in both
final SQLTable aT = this.a.getTable(tableName);
228,8 → 228,6
alterTable.dropIndex(rm.getName());
}
for (final Index added : CollectionUtils.substract(bIndexes, aIndexes)) {
// TODO mv system.autoCreatesFKIndex() of getCreateTable() into ChangeTable
// (ok in MySQL : the explicit CREATE INDEXs replace the implicit ones)
alterTable.addIndex(added);
}
} catch (SQLException e) {
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/MySQLXML2.java
14,6 → 14,7
package org.openconcerto.sql.utils;
 
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLType;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.FileUtils;
28,14 → 29,20
import java.util.Iterator;
import java.util.List;
 
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.xpath.XPath;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;
 
abstract class MySQLXML2 {
 
private final SQLSyntax destSyntax;
 
public MySQLXML2(final SQLSyntax s) {
this.destSyntax = s;
}
 
public void convert(File xmlFile, OutputStream outputStream) throws JDOMException, IOException {
final Document doc = new SAXBuilder().build(xmlFile);
 
146,7 → 153,7
if (type.indexOf("bool") >= 0 || type.indexOf("int") >= 0 || type.indexOf("float") >= 0 || isFunc(type, defAttr)) {
def = defAttr;
} else {
final SQLType t = SQLType.get(Types.VARCHAR, 250);
final SQLType t = SQLType.getFromSyntax(this.destSyntax, Types.VARCHAR, 250);
def = t.toString(defAttr);
}
res += " default " + def;
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/Copy.java
15,6 → 15,7
 
import static org.openconcerto.utils.FileUtils.addSuffix;
import static java.util.Collections.singleton;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.DBRoot;
34,6 → 35,7
import org.openconcerto.utils.LogUtils;
import org.openconcerto.utils.PropertiesUtils;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2.List2;
import org.openconcerto.utils.Tuple3;
import org.openconcerto.utils.cc.IClosure;
90,8 → 92,12
// no names => same string for any Locale, no invalid FS characters
private static final DateFormat DATE_FMT = new SimpleDateFormat("yyyy MM dd HH.mm.ss");
 
private synchronized static final String format(final Date d) {
return DATE_FMT.format(d);
}
 
static private boolean isEmpty(final String name) {
return name == null || name.trim().isEmpty();
return StringUtils.isEmpty(name, true);
}
 
private static <T extends Enum<T>> T getEnum(final String propName, final Class<T> enumType, final T defaultVal) {
314,7 → 320,7
if (this.mode == FileExistsMode.FAIL) {
throw new IllegalStateException("Dump exists at " + this.dir);
} else if (this.mode == FileExistsMode.RENAME) {
previousDump = addSuffix(this.dir, '_' + DATE_FMT.format(new Date()));
previousDump = addSuffix(this.dir, '_' + format(new Date()));
if (!this.dir.renameTo(previousDump))
throw new IOException("could not rename " + this.dir + " to " + previousDump);
} else if (this.mode == FileExistsMode.DELETE) {
373,7 → 379,7
final Properties dumpProps = propsFile.exists() ? PropertiesUtils.createFromFile(propsFile) : new Properties();
final Integer storedVersion = dumpExists ? Integer.parseInt(dumpProps.getProperty(VERSION_KEY, "1")) : null;
 
final SQLSyntax syntax = this.sysRoot.getServer().getSQLSystem().getSyntax();
final SQLSyntax syntax = this.sysRoot.getSyntax();
if (this.store) {
if (dumpExists) {
assert storedVersion != null;
386,9 → 392,9
if (dumpProps.getProperty(CREATED_KEY) == null) {
if (dumpExists)
System.err.println("Missing created key on existing dump");
dumpProps.setProperty(CREATED_KEY, DATE_FMT.format(now));
dumpProps.setProperty(CREATED_KEY, format(now));
}
dumpProps.setProperty(MODIFIED_KEY, DATE_FMT.format(now));
dumpProps.setProperty(MODIFIED_KEY, format(now));
 
FileUtils.mkdir_p(this.dir);
final FileOutputStream out = new FileOutputStream(propsFile);
427,11 → 433,12
final String newName = newRootName.equals(dbName) ? "" : " -> " + newRootName;
System.err.print("Structure of " + dbName + newName + "." + resolvedTables + " ... ");
for (final SQLSystem sys : SQLSystem.values()) {
if (sys.getSyntax() != null) {
final SQLSyntax targetSyntax = sys.getSyntax();
if (targetSyntax != null) {
// write how to create the root
final File rootFile = getRootFile(newRootName, sys, true);
FileUtils.mkParentDirs(rootFile);
FileUtils.write(r.getDefinitionSQL(sys, false).asString(newRootName, false, true), rootFile);
FileUtils.write(r.getDefinitionSQL(targetSyntax, false).asString(newRootName, false, true), rootFile);
 
// write how to create the tables in 2 directories
// 1. the creation of the table itself
443,7 → 450,7
 
for (final String tableName : resolvedTables) {
final SQLTable t = r.getTable(tableName);
final List2<String> createTable = t.getCreateTable(sys).getCreateAndAlter(nameTransformer);
final List2<String> createTable = t.getCreateTable(targetSyntax).getCreateAndAlter(nameTransformer);
if (isEmpty(createTable.get0()))
throw new IllegalStateException("Empty CREATE for " + t.getSQLName());
FileUtils.write(createTable.get0(), getTableFile(createDir, tableName));
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/MySQL2Postgresql.java
13,14 → 13,15
package org.openconcerto.sql.utils;
 
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.utils.cc.IClosure;
 
import java.io.File;
import java.io.IOException;
 
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
 
public class MySQL2Postgresql extends MySQLXML2 {
 
37,6 → 38,10
new MySQL2Postgresql().convert(xmlFile);
}
 
public MySQL2Postgresql() {
super(SQLSystem.POSTGRESQL.getSyntax());
}
 
public void fixXML(Document doc) throws JDOMException {
super.fixXML(doc);
this.fixAttr(doc, "Type", "starts-with", "tinyint(1)", new IClosure<Element>() {
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/GenerationUtils.java
26,8 → 26,6
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
 
import org.jdom2.Element;
 
public abstract class GenerationUtils {
 
static public void setPropertyAccessors() {
64,9 → 62,10
}
});
 
OgnlRuntime.setPropertyAccessor(Element.class, new PropertyAccessor() {
OgnlRuntime.setPropertyAccessor(org.jdom.Element.class, new PropertyAccessor() {
@Override
public Object getProperty(Map context, Object target, Object name) {
Element elem = (Element) target;
org.jdom.Element elem = (org.jdom.Element) target;
String n = (String) name;
// that way for XML and SQLRow, fields are accessed the same way
final String attributeValue = elem.getAttributeValue(n);
77,12 → 76,33
return elem.getChild(n);
}
 
@Override
public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
// impossible
throw new OgnlException("", new UnsupportedOperationException("setProperty not supported on XML elements"));
}
});
OgnlRuntime.setPropertyAccessor(org.jdom2.Element.class, new PropertyAccessor() {
@Override
public Object getProperty(Map context, Object target, Object name) {
org.jdom2.Element elem = (org.jdom2.Element) target;
String n = (String) name;
// that way for XML and SQLRow, fields are accessed the same way
final String attributeValue = elem.getAttributeValue(n);
if (attributeValue != null)
return attributeValue;
else
// retourne le premier, TODO collections
return elem.getChild(n);
}
 
@Override
public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
// impossible
throw new OgnlException("", new UnsupportedOperationException("setProperty not supported on XML elements"));
}
});
 
OgnlRuntime.setPropertyAccessor(RowBacked.class, new ObjectPropertyAccessor() {
public Object getProperty(Map context, Object target, Object name) throws OgnlException {
// try the normal way (ie thru getters), if that fails try the get() method
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/DropTable.java
13,9 → 13,10
package org.openconcerto.sql.utils;
 
import org.openconcerto.sql.model.Constraint;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Link;
 
/**
* Construct a DROP TABLE statement.
27,17 → 28,22
private final SQLTable t;
 
public DropTable(SQLTable t) {
super(t.getServer().getSQLSystem().getSyntax(), t.getDBRoot().getName(), t.getName());
super(t.getDBSystemRoot().getSyntax(), t.getDBRoot().getName(), t.getName());
this.t = t;
}
 
private final AlterTable getAlterTable() {
final AlterTable alterTable = new AlterTable(this.t);
for (final Link foreignLink : this.t.getForeignLinks()) {
if (foreignLink.getName() == null)
throw new IllegalStateException(foreignLink + " is not a real constraint, use AddFK");
alterTable.dropForeignConstraint(foreignLink.getName());
// drop all constraints, i.e. must be the opposite of SQLCreateTableBase
for (final Constraint c : this.t.getAllConstraints()) {
if (c.getType().equals(ConstraintType.FOREIGN_KEY)) {
if (c.getName() == null)
throw new IllegalStateException(c + " is not a real constraint, use AddFK");
alterTable.dropForeignConstraint(c.getName());
} else if (c.getType().equals(ConstraintType.UNIQUE) || c.getType().equals(ConstraintType.CHECK)) {
alterTable.dropConstraint(c.getName());
}
}
return alterTable;
}
 
47,9 → 53,9
}
 
@Override
protected String asString(final NameTransformer transf, ConcatStep step) {
public String asString(final NameTransformer transf, ConcatStep step) {
switch (step) {
case DROP_FOREIGN:
case DROP_CONSTRAINT:
return this.getAlterTable().asString(transf, step);
case ALTER_TABLE:
return this.asString(transf);
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/MySQL2Derby.java
13,11 → 13,13
package org.openconcerto.sql.utils;
 
import org.openconcerto.sql.model.SQLSystem;
 
import java.io.File;
import java.io.IOException;
 
import org.jdom2.Document;
import org.jdom2.JDOMException;
import org.jdom.Document;
import org.jdom.JDOMException;
 
public class MySQL2Derby extends MySQLXML2 {
 
34,6 → 36,10
new MySQL2Derby().convert(xmlFile);
}
 
public MySQL2Derby() {
super(SQLSystem.DERBY.getSyntax());
}
 
protected void fixXML(Document doc) throws JDOMException {
super.fixXML(doc);
fixType(doc, "starts-with", "tinyint(", "smallint");
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/AlterTable.java
23,6 → 23,8
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
44,7 → 46,7
private final SQLTable t;
 
public AlterTable(SQLTable t) {
super(t.getServer().getSQLSystem().getSyntax(), t.getDBRoot().getName(), t.getName());
super(t.getDBSystemRoot().getSyntax(), t.getDBRoot().getName(), t.getName());
this.t = t;
}
 
260,17 → 262,72
return this.asString(transf, ChangeTable.ORDERED_TYPES);
}
 
// Contrary to CREATE TABLE, ALTER TABLE might need to interleave in and out clauses, e.g. :
// ALTER TABLE DROP CONSTRAINT
// DROP INDEX
// ALTER TABLE DROP COLUMN
// return list of clauses and their types (true : out/standalone, false : in)
private List<Tuple2<Boolean, List<String>>> getAllClauses(final NameTransformer transf, final Set<ClauseType> types, final SQLName tableName) {
final List<Tuple2<Boolean, String>> clauses = new ArrayList<Tuple2<Boolean, String>>();
Boolean lastIsStandalone = null;
for (final ClauseType type : ORDERED_TYPES) {
if (types.contains(type)) {
final List<Tuple2<Boolean, String>> inClauses = new ArrayList<Tuple2<Boolean, String>>();
for (final String inClause : this.getClauses(tableName, transf, type)) {
inClauses.add(Tuple2.create(false, inClause));
}
 
final String outClausesS = this.getOutClauses(tableName, type);
final List<Tuple2<Boolean, String>> outClauses;
if (!StringUtils.isEmpty(outClausesS))
outClauses = Collections.singletonList(Tuple2.create(true, outClausesS));
else
outClauses = Collections.emptyList();
 
// within a given type there's no mandatory order between in and out clauses, so try
// to group clauses to limit the number of SQL statements. By default
// (lastIsStandalone == null) inClauses before outClauses, as it might create the
// table that outClauses needs.
if (Boolean.TRUE.equals(lastIsStandalone)) {
clauses.addAll(outClauses);
clauses.addAll(inClauses);
} else {
clauses.addAll(inClauses);
clauses.addAll(outClauses);
}
 
lastIsStandalone = clauses.isEmpty() ? null : clauses.get(clauses.size() - 1).get0();
}
}
 
// now group adjacent clauses
// [true, foo], [true, bar], [false, baz] =>
// [[true, [foo, bar]], [false, [baz]]]
lastIsStandalone = null;
final List<Tuple2<Boolean, List<String>>> res = new ArrayList<Tuple2<Boolean, List<String>>>();
for (final Tuple2<Boolean, String> clause : clauses) {
final Boolean currentIsStandAlone = clause.get0();
assert currentIsStandAlone != null;
if (!currentIsStandAlone.equals(lastIsStandalone)) {
res.add(Tuple2.<Boolean, List<String>> create(currentIsStandAlone, new ArrayList<String>()));
}
res.get(res.size() - 1).get1().add(clause.get1());
lastIsStandalone = currentIsStandAlone;
}
return res;
}
 
private final String asString(final NameTransformer transf, final Set<ClauseType> types) {
final SQLName tableName = transf.transformTableName(new SQLName(getRootName(), this.getName()));
final List<String> genClauses = new ArrayList<String>(this.getClauses(tableName, types));
this.modifyClauses(genClauses);
if (types.contains(ClauseType.ADD_CONSTRAINT))
genClauses.addAll(this.getForeignConstraints(transf));
 
final StringBuffer res = new StringBuffer(512);
final String alterTable = "ALTER TABLE " + tableName.quote();
// sometimes there's only OutsideClauses
if (this.getSyntax().supportMultiAlterClause() && genClauses.size() > 0) {
for (final Tuple2<Boolean, List<String>> standaloneAndClauses : getAllClauses(transf, types, tableName)) {
final List<String> genClauses = standaloneAndClauses.get1();
if (standaloneAndClauses.get0()) {
// e.g. "CREATE INDEX ;"
res.append(CollectionUtils.join(genClauses, "\n"));
} else if (this.getSyntax().supportMultiAlterClause()) {
res.append(alterTable + " \n");
res.append(CollectionUtils.join(genClauses, ",\n"));
res.append(";");
282,13 → 339,11
}
}));
}
 
this.outClausesAsString(res, tableName, types);
 
res.append('\n');
}
// remove last newline
if (res.length() > 0)
res.setLength(res.length() - 1);
return res.toString();
}
 
protected void modifyClauses(final List<String> genClauses) {
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/SQLCreateTableBase.java
97,7 → 97,7
* indexes and constraints.
*/
public final List2<String> getCreateAndAlter(final NameTransformer transf) {
return new List2<String>(this.asString(transf, ConcatStep.ALTER_TABLE), this.asString(transf, ConcatStep.ADD_INDEX) + '\n' + this.asString(transf, ConcatStep.ADD_FOREIGN));
return new List2<String>(this.asString(transf, ConcatStep.ALTER_TABLE), this.asString(transf, ConcatStep.ADD_INDEX) + '\n' + this.asString(transf, ConcatStep.ADD_CONSTRAINT));
}
 
@Override
106,7 → 106,7
case ALTER_TABLE:
return this.asString(transf, step.getTypes());
case ADD_INDEX:
case ADD_FOREIGN:
case ADD_CONSTRAINT:
return new AlterTable(getSyntax(), getRootName(), getName()).mutateTo(this).asString(transf, step);
default:
return null;
124,18 → 124,7
tableName = transformedName;
}
 
final List<String> genClauses = new ArrayList<String>(this.getClauses(tableName, types));
this.modifyClauses(genClauses);
if (this.pk.size() > 0 && types.contains(ClauseType.ADD_COL))
genClauses.add("PRIMARY KEY (" + CollectionUtils.join(this.pk, ",", new ITransformer<String, String>() {
@Override
public String transformChecked(String input) {
return SQLBase.quoteIdentifier(input);
}
}) + ")");
if (types.contains(ClauseType.ADD_CONSTRAINT)) {
genClauses.addAll(this.getForeignConstraints(transf));
}
final List<String> genClauses = this.getClauses(tableName, transf, types);
if (genClauses.size() > 0) {
if (this.tmp) {
res.append("CREATE TEMPORARY TABLE ");
154,7 → 143,16
return res.toString();
}
 
protected void modifyClauses(final List<String> genClauses) {
@Override
protected void modifyClauses(final List<String> genClauses, final NameTransformer transf, final ClauseType type) {
super.modifyClauses(genClauses, transf, type);
if (type == ClauseType.ADD_COL && this.pk.size() > 0) {
genClauses.add("PRIMARY KEY (" + CollectionUtils.join(this.pk, ",", new ITransformer<String, String>() {
@Override
public String transformChecked(String input) {
return SQLBase.quoteIdentifier(input);
}
 
}) + ")");
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/State.java
29,13 → 29,8
 
public static final org.openconcerto.sql.State INSTANCE = new org.openconcerto.sql.State();
 
public static final DateFormat TIME_FMT;
private final DateFormat TIME_FMT;
 
static {
TIME_FMT = DateFormat.getTimeInstance();
TIME_FMT.setTimeZone(TimeZone.getTimeZone("GMT"));
}
 
private final List<String> requests;
 
private final List<String> failed;
70,6 → 65,9
this.framesVisible = 0;
this.cacheHit = 0;
this.upDate = System.currentTimeMillis();
 
this.TIME_FMT = DateFormat.getTimeInstance();
this.TIME_FMT.setTimeZone(TimeZone.getTimeZone("GMT"));
}
 
final String getFull() {
90,7 → 88,7
}
 
private String getUptime() {
return TIME_FMT.format(new Long(System.currentTimeMillis() - this.upDate));
return this.TIME_FMT.format(new Long(System.currentTimeMillis() - this.upDate));
}
 
private String getHostDesc() {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/PGSQLBase.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/model/MSSQLBase.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/model/MySQLBase.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLServer.java
247,8 → 247,7
final Set<String> allCats;
final Connection conn = this.getDS().getNewConnection();
try {
@SuppressWarnings("unchecked")
final List<String> allCatsList = (List<String>) SQLDataSource.COLUMN_LIST_HANDLER.handle(conn.getMetaData().getCatalogs());
final List<String> allCatsList = ColumnListHandlerGeneric.create(String.class).handle(conn.getMetaData().getCatalogs());
allCats = new HashSet<String>(allCatsList);
} finally {
this.getDS().returnConnection(conn);
417,8 → 416,8
final DBSystemRoot sysRoot = this.getDBSystemRoot();
if (sysRoot != null && !sysRoot.createNode(this, baseName))
throw new IllegalStateException(baseName + " is filtered, you must add it to rootsToMap");
final SQLBase base = this.getSQLSystem().getSyntax()
.createBase(this, baseName, coalesce(systemRootInit, this.systemRootInit), login == null ? this.login : login, pass == null ? this.pass : pass, coalesce(dsInit, this.dsInit));
final SQLBase base = new SQLBase(this, baseName, coalesce(systemRootInit, this.systemRootInit), login == null ? this.login : login, pass == null ? this.pass : pass,
coalesce(dsInit, this.dsInit));
return this.putBase(baseName, base, readCache);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxH2.java
28,6 → 28,7
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
36,8 → 37,15
 
class SQLSyntaxH2 extends SQLSyntax {
 
static protected final IdentityHashMap<String, String> DATE_SPECS;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>(DateProp.JAVA_DATE_SPECS_PURE);
DATE_SPECS.put(DateProp.MICROSECOND, "SSS000");
}
 
SQLSyntaxH2() {
super(SQLSystem.H2);
super(SQLSystem.H2, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Integer.class, "integer", "int", "int4", "mediumint");
this.typeNames.addAll(Byte.class, "tinyint");
47,7 → 55,8
this.typeNames.addAll(Float.class, "real");
this.typeNames.addAll(Double.class, "double precision", "float", "float4", "float8");
this.typeNames.addAll(Timestamp.class, "timestamp", "smalldatetime", "datetime");
this.typeNames.addAll(java.util.Date.class, "date");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time");
this.typeNames.addAll(Blob.class, "blob", "tinyblob", "mediumblob", "longblob", "image",
// byte[]
"bytea", "raw", "varbinary", "longvarbinary", "binary");
167,7 → 176,7
@Override
public void _loadData(final File f, final SQLTable t) {
checkServerLocalhost(t);
final String quotedPath = t.getBase().quoteString(f.getAbsolutePath());
final String quotedPath = quoteString(f.getAbsolutePath());
t.getDBSystemRoot().getDataSource().execute("insert into " + t.getSQLName().quote() + " select * from CSVREAD(" + quotedPath + ", NULL, 'UTF8', ',', '\"', '\\', '\\N') ;");
}
 
174,8 → 183,8
@Override
protected void _storeData(final SQLTable t, final File f) {
checkServerLocalhost(t);
final String quotedPath = t.getBase().quoteString(f.getAbsolutePath());
final String quotedSel = t.getBase().quoteString(SQLSyntaxPG.selectAll(t).asString());
final String quotedPath = quoteString(f.getAbsolutePath());
final String quotedSel = quoteString(SQLSyntaxPG.selectAll(t).asString());
t.getBase().getDataSource().execute("CALL CSVWRITE(" + quotedPath + ", " + quotedSel + ", 'UTF8', ',', '\"', '\\', '\\N', '\n');");
}
 
204,10 → 213,25
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "DAY_OF_WEEK(" + sqlTS + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
return "FORMATDATETIME(" + sqlTS + ", " + SQLBase.quoteStringStd(basic ? TS_BASIC_JAVA_FORMAT : TS_EXTENDED_JAVA_FORMAT) + ")";
return this.getFormatTimestamp(sqlTS, SQLBase.quoteStringStd(basic ? TS_BASIC_JAVA_FORMAT : TS_EXTENDED_JAVA_FORMAT));
}
 
@Override
public final String getFormatTimestamp(String sqlTS, String format) {
return "FORMATDATETIME(" + sqlTS + ", " + format + ")";
}
 
@Override
public String quoteForTimestampFormat(String text) {
return SQLBase.quoteStringStd(text);
}
 
// (SELECT "C1" as "num", "C2" as "name" FROM VALUES(1, 'Hello'), (2, 'World')) AS V;
@Override
public String getConstantTable(List<List<String>> rows, String alias, List<String> columnsAlias) {
235,28 → 259,28
// src can be null since H2 supports alias to Java static functions
// perhaps join on FUNCTION_COLUMNS to find out parameters' types
final String src = "coalesce(\"SOURCE\", \"JAVA_CLASS\" || '.' || \"JAVA_METHOD\" ||' parameter(s): ' || \"COLUMN_COUNT\")";
return "SELECT ALIAS_SCHEMA as \"schema\", ALIAS_NAME as \"name\", " + src + " as \"src\" FROM \"INFORMATION_SCHEMA\".FUNCTION_ALIASES where ALIAS_CATALOG=" + b.quoteString(b.getMDName())
+ " and ALIAS_SCHEMA in (" + quoteStrings(b, schemas) + ")";
return "SELECT ALIAS_SCHEMA as \"schema\", ALIAS_NAME as \"name\", " + src + " as \"src\" FROM \"INFORMATION_SCHEMA\".FUNCTION_ALIASES where ALIAS_CATALOG=" + this.quoteString(b.getMDName())
+ " and ALIAS_SCHEMA in (" + quoteStrings(schemas) + ")";
}
 
@Override
public String getTriggerQuery(SQLBase b, TablesMap tables) {
return "SELECT \"TRIGGER_NAME\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"JAVA_CLASS\" as \"ACTION\", \"SQL\" from INFORMATION_SCHEMA.TRIGGERS " + getTablesMapJoin(b, tables) + " where "
return "SELECT \"TRIGGER_NAME\", \"TABLE_SCHEMA\", \"TABLE_NAME\", \"JAVA_CLASS\" as \"ACTION\", \"SQL\" from INFORMATION_SCHEMA.TRIGGERS " + getTablesMapJoin(tables) + " where "
+ getInfoSchemaWhere(b);
}
 
private String getTablesMapJoin(final SQLBase b, final TablesMap tables) {
return getTablesMapJoin(b, tables, SQLBase.quoteIdentifier("TABLE_SCHEMA"), SQLBase.quoteIdentifier("TABLE_NAME"));
private String getTablesMapJoin(final TablesMap tables) {
return getTablesMapJoin(tables, SQLBase.quoteIdentifier("TABLE_SCHEMA"), SQLBase.quoteIdentifier("TABLE_NAME"));
}
 
private final String getInfoSchemaWhere(SQLBase b) {
return "\"TABLE_CATALOG\" = " + b.quoteString(b.getMDName());
return "\"TABLE_CATALOG\" = " + this.quoteString(b.getMDName());
}
 
@Override
public String getColumnsQuery(SQLBase b, TablesMap tables) {
return "SELECT \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
+ "\" , \"CHARACTER_SET_NAME\", \"COLLATION_NAME\", \"SEQUENCE_NAME\" from INFORMATION_SCHEMA.\"COLUMNS\" " + getTablesMapJoin(b, tables) + " where " + getInfoSchemaWhere(b);
+ "\" , \"CHARACTER_SET_NAME\", \"COLLATION_NAME\", \"SEQUENCE_NAME\" from INFORMATION_SCHEMA.\"COLUMNS\" " + getTablesMapJoin(tables) + " where " + getInfoSchemaWhere(b);
}
 
@Override
266,7 → 290,7
//
+ "case \"CONSTRAINT_TYPE\" when 'REFERENTIAL' then 'FOREIGN KEY' else \"CONSTRAINT_TYPE\" end as \"CONSTRAINT_TYPE\", \"COLUMN_LIST\", \"CHECK_EXPRESSION\" AS \"DEFINITION\"\n"
//
+ "FROM INFORMATION_SCHEMA.CONSTRAINTS " + getTablesMapJoin(b, tables)
+ "FROM INFORMATION_SCHEMA.CONSTRAINTS " + getTablesMapJoin(tables)
// where
+ " where " + getInfoSchemaWhere(b);
// don't cache since we don't listen on system tables
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLType.java
18,7 → 18,7
 
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
 
import java.math.BigDecimal;
import java.math.BigInteger;
30,16 → 30,14
import java.sql.Types;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.jdom2.Element;
 
/**
* The type of a SQL field. Allow one to convert a Java object to its SQL serialization.
*
50,7 → 48,7
@ThreadSafe
public abstract class SQLType {
 
private static Class<?> getClass(int type, final int size) {
private static Class<?> getClass(int type, final int size, SQLSyntax s) {
switch (type) {
case Types.BIT:
// As of MySQL 5.0.3, BIT is for storing bit-field values
74,13 → 72,22
case Types.TIMESTAMP:
return Timestamp.class;
case Types.DATE:
return java.util.Date.class;
return java.sql.Date.class;
case Types.TIME:
return java.sql.Time.class;
case Types.INTEGER:
return Integer.class;
case Types.SMALLINT:
/*
* http://download.oracle.com/otndocs/jcp/jdbc-4_1-mrel-spec/index.html Appendix B : The
* JDBC 1.0 specification defined the Java object mapping for the SMALLINT and TINYINT
* JDBC types to be Integer. The Java language did not include the Byte and Short data
* types when the JDBC 1.0 specification was finalized. The mapping of SMALLINT and
* TINYINT to Integer is maintained to preserve backwards compatibility
*/
return s.getSystem() == SQLSystem.H2 ? Short.class : Integer.class;
case Types.TINYINT:
return Integer.class;
return s.getSystem() == SQLSystem.H2 ? Byte.class : Integer.class;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
109,21 → 116,21
 
// useful when no SQLBase is known
public static SQLType getBoolean(final SQLSyntax s) {
// TODO use get() once it accepts a SQLSyntax
final SQLType res = new BooleanType(Types.BOOLEAN, 1, null, Boolean.class);
res.setSyntax(s);
return res;
return getFromSyntax(s, Types.BOOLEAN, 1);
}
 
public static SQLType get(final int type, final int size) {
return get(null, type, size, null, null);
public static SQLType getFromSyntax(final SQLSyntax s, final int type, final int size) {
return get(s, type, size, null, s.getTypeNames(getClass(type, size, s)).iterator().next());
}
 
public static SQLType get(final SQLBase base, final int type, final int size) {
return getFromSyntax(SQLSyntax.get(base), type, size);
}
 
/**
* Get the corresponding type.
*
* @param base the base of this type, can be <code>null</code> but {@link #toString(Object)}
* will have to use standard SQL which might not be valid for all bases (eg escapes).
* @param base the base of this type, cannot be <code>null</code>.
* @param type a value from java.sql.Types.
* @param size the size as COLUMN_SIZE is defined in
* {@link java.sql.DatabaseMetaData#getColumns(java.lang.String, java.lang.String, java.lang.String, java.lang.String)}
133,11 → 140,15
* @throws IllegalStateException if type is unknown.
*/
public static SQLType get(final SQLBase base, final int type, final int size, Integer decDigits, final String typeName) {
final List<String> typeID = Arrays.asList(base == null ? null : base.getURL(), type + "", size + "", String.valueOf(decDigits), typeName);
return get(SQLSyntax.get(base), type, size, decDigits, typeName);
}
 
public static SQLType get(final SQLSyntax s, final int type, final int size, Integer decDigits, final String typeName) {
final List<String> typeID = Arrays.asList(s.toString(), type + "", size + "", String.valueOf(decDigits), typeName);
synchronized (instances) {
SQLType res = instances.get(typeID);
if (res == null) {
final Class<?> clazz = getClass(type, size);
final Class<?> clazz = getClass(type, size, s);
if (Boolean.class.isAssignableFrom(clazz))
res = new BooleanType(type, size, typeName, clazz);
else if (Number.class.isAssignableFrom(clazz))
154,7 → 165,8
else
// BLOB & CLOB and the rest
res = new UnknownType(type, size, typeName, clazz);
res.setBase(base);
if (s != null)
res.setSyntax(s);
instances.put(typeID, res);
}
return res;
170,17 → 182,6
return get(base, type, size, decDigits, typeName);
}
 
static void remove(final SQLBase base) {
synchronized (instances) {
final Iterator<Entry<List<String>, SQLType>> iter = instances.entrySet().iterator();
while (iter.hasNext()) {
final Entry<List<String>, SQLType> e = iter.next();
if (e.getValue().getBase() == base)
iter.remove();
}
}
}
 
// *** instance
 
// a value from java.sql.Types
195,8 → 196,6
private final Class<?> javaType;
 
@GuardedBy("this")
private SQLBase base;
@GuardedBy("this")
private SQLSyntax syntax;
 
@GuardedBy("this")
232,6 → 231,11
return this.decimalDigits;
}
 
/**
* The type returned from the DB.
*
* @return the java class returned by {@link SQLResultSet#getObject(int)}.
*/
public Class<?> getJavaType() {
return this.javaType;
}
245,34 → 249,19
return this.typeName;
}
 
// TODO remove once quoteString() is in SQLSyntax
private synchronized final void setBase(SQLBase base) {
// set only once
assert this.base == null;
if (base != null) {
this.base = base;
this.setSyntax(this.base.getServer().getSQLSystem().getSyntax());
}
}
 
private synchronized final void setSyntax(SQLSyntax s) {
// set only once
assert this.syntax == null;
if (s != null) {
if (this.syntax != null)
throw new IllegalStateException("Already set to " + this.syntax);
this.syntax = s;
}
}
 
private synchronized final SQLBase getBase() {
return this.base;
}
 
public synchronized final SQLSyntax getSyntax() {
return this.syntax;
}
 
protected final String quoteString(String s) {
return SQLBase.quoteString(this.getBase(), s);
return SQLSyntax.quoteString(this.getSyntax(), s);
}
 
@Override
350,7 → 339,7
sb.append(this.decimalDigits);
}
sb.append("\" typeName=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.typeName));
sb.append(JDOM2Utils.OUTPUTTER.escapeAttributeEntities(this.typeName));
sb.append("\"/>");
this.xml = sb.toString();
}
406,10 → 395,11
}
 
/**
* Test whether o is valid for this type. Ie does o is an instance of getJavaType().
* Test whether the passed parameter is valid for this type.
*
* @param o the object to test.
* @return <code>true</code> if o can be passed to {@link #toString(Object)}.
* @return <code>true</code> if o can be passed to {@link #toString(Object)}, always
* <code>true</code> for an instance of {@link #getJavaType()}.
* @see #check(Object)
*/
public boolean isValid(Object o) {
503,7 → 493,7
@Override
public final String toStringRaw(Object o) {
// time has no special characters to escape
return "'" + toCSVRaw(o) + "'";
return getSyntax().cast("'" + toCSVRaw(o) + "'", this);
}
 
static protected long getTime(Object o) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/ListListHandlerGeneric.java
New file
0,0 → 1,75
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.model;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
 
import net.jcip.annotations.Immutable;
 
/**
* <code>ResultSetHandler</code> implementation that converts a <code>ResultSet</code> into a
* {@link List} of lists.
*
* @see ArrayListHandler
*/
@Immutable
public class ListListHandlerGeneric<A> implements ResultSetHandler {
 
public static ListListHandlerGeneric<Object> create(final List<Class<?>> classes) {
return create(Object.class, classes);
}
 
public static <A> ListListHandlerGeneric<A> create(final Class<A> arrayClass, final int colCount) {
return create(arrayClass, Collections.<Class<? extends A>> nCopies(colCount, arrayClass));
}
 
// syntactic sugar, MAYBE cache instances
public static <A> ListListHandlerGeneric<A> create(final Class<A> arrayClass, final List<Class<? extends A>> classes) {
return new ListListHandlerGeneric<A>(arrayClass, classes);
}
 
private final Class<A> arrayClass;
private final List<Class<? extends A>> classes;
 
public ListListHandlerGeneric(final Class<A> arrayClass, final List<Class<? extends A>> classes) {
if (arrayClass == null)
throw new NullPointerException("Missing array component class");
this.arrayClass = arrayClass;
this.classes = classes == null ? null : new ArrayList<Class<? extends A>>(classes);
}
 
@Override
public final List<List<A>> handle(ResultSet rs) throws SQLException {
final int cols = this.classes == null ? rs.getMetaData().getColumnCount() : this.classes.size();
final List<List<A>> result = new ArrayList<List<A>>();
while (rs.next()) {
final List<A> array = new ArrayList<A>();
for (int i = 0; i < cols; i++) {
if (this.classes == null)
array.add(this.arrayClass.cast(rs.getObject(i + 1)));
else
array.add(SQLResultSet.getValue(rs, this.classes.get(i), i + 1));
}
result.add(array);
}
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesListFetcher.java
28,6 → 28,7
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.Transformer;
 
import java.sql.ResultSet;
import java.sql.SQLException;
788,18 → 789,25
}
 
public final SQLSelect getReq() {
return this.getReq(null);
return this.getReq(null, null);
}
 
// see fetch() comment as to why the Where parameter isn't public.
private synchronized final SQLSelect getReq(final Where w) {
static private final SQLSelect checkTr(final List<String> origSelect, final SQLSelect tr) {
if (!origSelect.equals(tr.getSelect()))
throw new IllegalArgumentException("Select clause cannot be modified");
return tr;
}
 
public synchronized final SQLSelect getReq(final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf) {
checkTable(w);
final boolean isNopTransf = selTransf == null || selTransf == Transformer.<SQLSelect> nopTransformer();
if (this.isFrozen()) {
if (w == null) {
if (w == null && isNopTransf) {
return this.frozen;
} else {
final SQLSelect res = new SQLSelect(this.frozen);
res.andWhere(w);
return res;
final SQLSelect copy = new SQLSelect(this.frozen);
final SQLSelect res = isNopTransf ? copy : checkTr(copy.getSelect(), selTransf.transformChecked(copy));
return res.andWhere(w);
}
}
 
841,11 → 849,14
 
if (this.getSelID() != null)
sel.andWhere(getIDWhere(t, this.getSelID()));
final List<String> origSel = new ArrayList<String>(sel.getSelect());
SQLSelect res = sel;
for (final ITransformer<SQLSelect, SQLSelect> tr : this.getSelectTransformers()) {
res = tr.transformChecked(res);
}
return res.andWhere(w);
if (!isNopTransf)
res = selTransf.transformChecked(res);
return checkTr(origSel, res).andWhere(w);
}
 
static private Where getIDWhere(final SQLTable t, final Number id) {
1147,17 → 1158,20
* table.
*/
public final List<SQLRowValues> fetch(final Where w, final Boolean unmodifiableRows) throws IllegalArgumentException {
return this.fetch(true, w, unmodifiableRows);
return this.fetch(w, null, unmodifiableRows);
}
 
private final List<SQLRowValues> fetch(final boolean merge, final Where w, final Boolean unmodifiableRows) throws IllegalArgumentException {
checkTable(w);
public final List<SQLRowValues> fetch(final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf, final Boolean unmodifiableRows) throws IllegalArgumentException {
return this.fetch(true, w, selTransf, unmodifiableRows);
}
 
private final List<SQLRowValues> fetch(final boolean merge, final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf, final Boolean unmodifiableRows) throws IllegalArgumentException {
final SQLSelect req;
final Map<Path, Map<Path, SQLRowValuesListFetcher>> grafts;
final boolean freezeRows;
// the only other internal state used is this.descendantPath which is final immutable
synchronized (this) {
req = this.getReq(w);
req = this.getReq(w, selTransf);
grafts = this.getGrafts();
freezeRows = unmodifiableRows == null ? this.areReturnedRowsUnmodifiable() : unmodifiableRows.booleanValue();
}
1164,9 → 1178,7
// getName() would take 5% of ResultSetHandler.handle()
final List<FieldRef> selectFields = req.getSelectFields();
final int selectFieldsSize = selectFields.size();
final List<String> selectFieldsNames = new ArrayList<String>(selectFieldsSize);
for (final FieldRef f : selectFields)
selectFieldsNames.add(f.getField().getName());
final List<String> selectFieldsNames = req.getSelectNames();
final SQLTable table = getGraph().getTable();
 
// create a flat list of the graph nodes, we just need the table, field count and the index
1198,8 → 1210,8
// otherwise walk() would already have thrown an exception
assert fieldIndex.get() <= selectFieldsSize;
if (fieldIndex.get() != selectFieldsSize) {
throw new IllegalStateException(
"Fields have been added to the select (which is useless, since only fields specified by rows are returned) : " + selectFields.subList(fieldIndex.get(), selectFieldsSize));
throw new IllegalStateException("Items have been added to the select (which is useless, since only fields specified by rows are returned and WHERE cannot access SELECT columns) : "
+ selectFields.subList(fieldIndex.get(), selectFieldsSize));
}
assert l.size() == graphSize : "All nodes weren't explored once : " + l.size() + " != " + graphSize + "\n" + this.getGraph().printGraph();
 
1245,7 → 1257,7
final SQLRowValuesListFetcher graft = e.getValue();
 
// don't merge then...
final List<SQLRowValues> referentVals = graft.fetch(false, new Where(graft.getGraph().getTable().getKey(), ids), false);
final List<SQLRowValues> referentVals = graft.fetch(false, new Where(graft.getGraph().getTable().getKey(), ids), null, false);
// ...but now
merge(merged, referentVals, byRows, descendantPath);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMS.java
27,7 → 27,6
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.io.BufferedReader;
47,6 → 46,7
import java.sql.Types;
import java.util.Arrays;
import java.util.BitSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
54,8 → 54,23
 
class SQLSyntaxMS extends SQLSyntax {
 
static private final IdentityHashMap<String, String> DATE_SPECS;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>();
DATE_SPECS.put(DateProp.YEAR, "yyyy");
DATE_SPECS.put(DateProp.MONTH_NAME, "MMMM");
DATE_SPECS.put(DateProp.MONTH_NUMBER, "MM");
DATE_SPECS.put(DateProp.DAY_IN_MONTH, "dd");
DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "dddd");
DATE_SPECS.put(DateProp.HOUR, "HH");
DATE_SPECS.put(DateProp.MINUTE, "mm");
DATE_SPECS.put(DateProp.SECOND, "ss");
DATE_SPECS.put(DateProp.MICROSECOND, "ffffff");
}
 
SQLSyntaxMS() {
super(SQLSystem.MSSQL);
super(SQLSystem.MSSQL, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "bit");
// tinyint is unsigned
this.typeNames.addAll(Short.class, "smallint", "tinyint");
63,8 → 78,8
this.typeNames.addAll(Long.class, "bigint");
this.typeNames.addAll(BigDecimal.class, "decimal", "numeric", "smallmoney", "money");
this.typeNames.addAll(Float.class, "real");
this.typeNames.addAll(Double.class, "float");
this.typeNames.addAll(Timestamp.class, "smalldatetime", "datetime");
this.typeNames.addAll(Double.class, "double precision", "float");
this.typeNames.addAll(Timestamp.class, "datetime2", "datetime", "smalldatetime");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time");
this.typeNames.addAll(Blob.class, "image",
75,6 → 90,22
}
 
@Override
public final String quoteString(String s) {
final String res = super.quoteString(s);
if (s == null)
return res;
// only use escape form if needed (=> equals with other systems most of the time)
boolean simpleASCII = true;
final int l = s.length();
for (int i = 0; simpleASCII && i < l; i++) {
final char c = s.charAt(i);
simpleASCII = c <= 0xFF;
}
// see http://msdn.microsoft.com/fr-fr/library/ms191200(v=sql.105).aspx
return simpleASCII ? res : "N" + res;
}
 
@Override
public int getMaximumIdentifierLength() {
// https://msdn.microsoft.com/en-us/library/ms143432.aspx
return 128;
81,11 → 112,6
}
 
@Override
SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new MSSQLBase(server, name, systemRootInit, login, pass, dsInit);
}
 
@Override
public String getInitSystemRoot() {
final String sql;
try {
108,16 → 134,6
}
 
@Override
public String getDateAndTimeType() {
return "datetime2";
}
 
@Override
public String getBooleanType() {
return "bit";
}
 
@Override
public int getMaximumVarCharLength() {
// http://msdn.microsoft.com/en-us/library/ms176089(v=sql.105).aspx
return 8000;
165,7 → 181,7
final String s = enable ? "with check check constraint all" : "nocheck constraint all";
return "exec sp_MSforeachtable @command1 = 'ALTER TABLE ? " + s + "' , @whereand = " +
//
b.getBase().quoteString("and schema_id = SCHEMA_ID( " + b.getBase().quoteString(b.getName()) + " )");
quoteString("and schema_id = SCHEMA_ID( " + quoteString(b.getName()) + " )");
}
 
@Override
193,7 → 209,7
//
" join [test].[sys].[columns] cols on t.object_id = cols.object_id and cols.column_id = indexCols.column_id \n" +
//
" where schema_name(t.schema_id) = " + t.getBase().quoteString(t.getSchema().getName()) + " and t.name = " + t.getBase().quoteString(t.getName()) + "\n"
" where schema_name(t.schema_id) = " + quoteString(t.getSchema().getName()) + " and t.name = " + quoteString(t.getName()) + "\n"
//
+ "ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\";";
// don't cache since we don't listen on system tables
493,7 → 509,7
//
+ " FROM " + new SQLName(b.getName(), "sys", "objects") + "\n"
// scalar, inline table-valued, table-valued
+ " where type IN ('FN', 'IF', 'TF') and SCHEMA_NAME( schema_id ) in (" + quoteStrings(b, schemas) + ") ";
+ " where type IN ('FN', 'IF', 'TF') and SCHEMA_NAME( schema_id ) in (" + quoteStrings(schemas) + ") ";
}
 
@Override
505,7 → 521,7
//
+ "join " + new SQLName(b.getName(), "sys", "objects") + " tabl on trig.parent_id = tabl.object_id\n"
// requested tables
+ getTablesMapJoin(b, tables, "SCHEMA_NAME( tabl.schema_id )", "tabl.name");
+ getTablesMapJoin(tables, "SCHEMA_NAME( tabl.schema_id )", "tabl.name");
}
 
@Override
518,20 → 534,18
return "SELECT TABLE_SCHEMA as \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", TABLE_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", COLUMN_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
+ "\" , CHARACTER_SET_NAME as \"CHARACTER_SET_NAME\", COLLATION_NAME as \"COLLATION_NAME\" from INFORMATION_SCHEMA.COLUMNS\n" +
// requested tables
getTablesMapJoin(b, tables, "TABLE_SCHEMA", "TABLE_NAME");
getTablesMapJoin(tables, "TABLE_SCHEMA", "TABLE_NAME");
}
 
@Override
public List<Map<String, Object>> getConstraints(SQLBase b, TablesMap tables) throws SQLException {
final String where = getTablesMapJoin(b, tables, "SCHEMA_NAME(t.schema_id)", "t.name");
final String where = getTablesMapJoin(tables, "SCHEMA_NAME(t.schema_id)", "t.name");
final String sel = "SELECT SCHEMA_NAME(t.schema_id) AS \"TABLE_SCHEMA\", t.name AS \"TABLE_NAME\", k.name AS \"CONSTRAINT_NAME\", case k.type when 'UQ' then 'UNIQUE' when 'PK' then 'PRIMARY KEY' end as \"CONSTRAINT_TYPE\", col_name(c.object_id, c.column_id) AS \"COLUMN_NAME\", c.key_ordinal AS \"ORDINAL_POSITION\", null AS [DEFINITION]\n"
+ "FROM sys.key_constraints k\n"
//
+ "JOIN sys.index_columns c ON c.object_id = k.parent_object_id AND c.index_id = k.unique_index_id\n"
//
+ "JOIN sys.tables t ON t.object_id = k.parent_object_id\n"
+ where
+ "\nUNION ALL\n"
+ "JOIN sys.tables t ON t.object_id = k.parent_object_id\n" + where + "\nUNION ALL\n"
//
+ "SELECT SCHEMA_NAME(t.schema_id) AS \"TABLE_SCHEMA\", t.name AS \"TABLE_NAME\", k.name AS \"CONSTRAINT_NAME\", 'CHECK' as \"CONSTRAINT_TYPE\", col.name AS \"COLUMN_NAME\", 1 AS \"ORDINAL_POSITION\", k.[definition] AS [DEFINITION]\n"
+ "FROM sys.check_constraints k\n"
540,8 → 554,7
//
+ "left join sys.columns col on k.parent_column_id = col.column_id and col.object_id = t.object_id\n"
//
+ where
+ "\nUNION ALL\n"
+ where + "\nUNION ALL\n"
//
+ "SELECT SCHEMA_NAME(t.schema_id) AS [TABLE_SCHEMA], t.name AS [TABLE_NAME], k.name AS [CONSTRAINT_NAME], 'DEFAULT' as [CONSTRAINT_TYPE], col.name AS [COLUMN_NAME], 1 AS [ORDINAL_POSITION], k.[definition] AS [DEFINITION]\n"
+ "FROM sys.[default_constraints] k\n"
576,8 → 589,18
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "SELECT DATEPART(dw, " + sqlTS + ")";
}
 
@Override
public String getMonth(String sqlTS) {
return "SELECT DATEPART(month, " + sqlTS + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
final String extended = "CONVERT(nvarchar(30), CAST(" + sqlTS + " as datetime), 126) + '000'";
final String extended = "CONVERT(nvarchar(30), " + sqlTS + ", 126) + '000'";
if (basic) {
return "replace( replace( " + extended + ", '-', ''), ':' , '' )";
} else {
584,4 → 607,14
return extended;
}
}
 
@Override
public String getFormatTimestamp(String sqlTS, String nativeFormat) {
return "FORMAT(" + sqlTS + ", " + nativeFormat + ")";
}
 
@Override
public String quoteForTimestampFormat(String text) {
return StringUtils.doubleQuote(text);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntax.java
29,8 → 29,8
import org.openconcerto.utils.NetUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.IdentityHashSet;
 
import java.io.File;
import java.io.FileFilter;
40,20 → 40,31
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import com.ibm.icu.text.DateTimePatternGenerator;
 
import net.jcip.annotations.GuardedBy;
 
/**
* A class that abstract the syntax of different SQL systems. Type is an SQL datatype like 'int' or
* 'varchar', definition is the type plus default and constraints like 'int default 1 not null
67,6 → 78,7
static public final String ORDER_NAME = "ORDRE";
static public final String ARCHIVE_NAME = "ARCHIVE";
static public final String ID_NAME = "ID";
@GuardedBy("this")
static private final Map<SQLSystem, SQLSyntax> instances = new HashMap<SQLSystem, SQLSyntax>();
static public final String DATA_EXT = ".txt";
 
75,6 → 87,167
 
static private final StringUtils.Escaper DEFAULT_LIKE_ESCAPER = new StringUtils.Escaper('\\', '\\');
 
static public final class DateProp {
static public final String YEAR = new String("year with four digits");
static public final String MONTH_NAME = new String("full name of the month");
static public final String MONTH_NUMBER = new String("2 digits number of the month (starting at 1)");
static public final String DAY_IN_MONTH = new String("2 digits day number in the month");
static public final String DAY_NAME_IN_WEEK = new String("full name of day");
static public final String HOUR = new String("hour in day (00-23)");
static public final String MINUTE = new String("minute in hour");
static public final String SECOND = new String("second in minute");
static public final String MICROSECOND = new String("microseconds (000000-999999)");
 
static public final Set<String> ALL_INSTANCES = new IdentityHashSet<String>(Arrays.asList(YEAR, MONTH_NAME, MONTH_NUMBER, DAY_IN_MONTH, DAY_NAME_IN_WEEK, HOUR, MINUTE, SECOND, MICROSECOND));
static public final Set<String> LOCALE_SENSITIVE_INSTANCES = new IdentityHashSet<String>(Arrays.asList(MONTH_NAME, DAY_NAME_IN_WEEK));
 
static public final List<String> TIME_SKELETON = Arrays.asList(HOUR, MINUTE, SECOND);
static public final List<String> SHORT_DATE_SKELETON = Arrays.asList(DAY_IN_MONTH, MONTH_NUMBER, YEAR);
static public final List<String> LONG_DATE_SKELETON = Arrays.asList(DAY_NAME_IN_WEEK, DAY_IN_MONTH, MONTH_NAME, YEAR);
static public final List<String> SHORT_DATETIME_SKELETON = Arrays.asList(DAY_IN_MONTH, MONTH_NUMBER, YEAR, HOUR, MINUTE);
static public final List<String> LONG_DATETIME_SKELETON = Arrays.asList(DAY_NAME_IN_WEEK, DAY_IN_MONTH, MONTH_NAME, YEAR, HOUR, MINUTE, SECOND);
 
// pure format (i.e. no literal string)
static protected final IdentityHashMap<String, String> JAVA_DATE_SPECS_PURE;
static private final SortedMap<String, String> REVERSE_JAVA_SPEC;
static private final Pattern REVERSE_SPEC_PATTERN;
 
static {
JAVA_DATE_SPECS_PURE = new IdentityHashMap<String, String>();
JAVA_DATE_SPECS_PURE.put(YEAR, "yyyy");
JAVA_DATE_SPECS_PURE.put(MONTH_NAME, "MMMM");
JAVA_DATE_SPECS_PURE.put(MONTH_NUMBER, "MM");
JAVA_DATE_SPECS_PURE.put(DAY_IN_MONTH, "dd");
JAVA_DATE_SPECS_PURE.put(DAY_NAME_IN_WEEK, "EEEE");
JAVA_DATE_SPECS_PURE.put(HOUR, "HH");
JAVA_DATE_SPECS_PURE.put(MINUTE, "mm");
JAVA_DATE_SPECS_PURE.put(SECOND, "ss");
 
// reverse, so longer strings come first (e.g. MMMM|MM to match the longer one)
final SortedMap<String, String> m = new TreeMap<String, String>(Collections.reverseOrder());
REVERSE_JAVA_SPEC = CollectionUtils.invertMap(m, JAVA_DATE_SPECS_PURE);
assert REVERSE_JAVA_SPEC.size() == JAVA_DATE_SPECS_PURE.size() : "Duplicate values";
assert !JAVA_DATE_SPECS_PURE.containsKey(null) : "Null spec";
assert !JAVA_DATE_SPECS_PURE.containsValue(null) : "Null value";
 
REVERSE_SPEC_PATTERN = Pattern.compile(CollectionUtils.join(REVERSE_JAVA_SPEC.keySet(), "|"));
}
 
/**
* Return the best pattern matching the input skeleton.
*
* @param simpleFormat the fields needed (the string literals are ignored), e.g. [YEAR,
* DAY_IN_MONTH, MONTH_NUMBER].
* @param l the locale needed.
* @return the best match, e.g. [DAY_IN_MONTH, "/", MONTH_NUMBER, "/", YEAR] for
* {@link Locale#FRANCE} , [MONTH_NUMBER, "/", DAY_IN_MONTH, "/", YEAR] for
* {@link Locale#US}.
*/
public static List<String> getBestPattern(final List<String> simpleFormat, final Locale l) {
final StringBuilder sb = new StringBuilder(128);
for (final String p : simpleFormat) {
if (JAVA_DATE_SPECS_PURE.containsKey(p))
sb.append(JAVA_DATE_SPECS_PURE.get(p));
else if (ALL_INSTANCES.contains(p))
throw new IllegalArgumentException("Unsupported spec : " + p);
else
// ignore
Log.get().log(Level.FINE, "Ignore {0}", p);
}
// needs same length so our pattern works
final String bestPattern = DateTimePatternGenerator.getInstance(l).getBestPattern(sb.toString(), DateTimePatternGenerator.MATCH_ALL_FIELDS_LENGTH);
return parseJavaPattern(bestPattern);
}
 
static List<String> parseJavaPattern(final String bestPattern) {
final Matcher matcher = REVERSE_SPEC_PATTERN.matcher(bestPattern);
final Matcher quotedMatcher = SQLBase.quotedPatrn.matcher(bestPattern);
final List<String> res = new ArrayList<String>();
int index = 0;
while (index < bestPattern.length()) {
final int quoteIndex = bestPattern.indexOf('\'', index);
final int endSansQuote = quoteIndex < 0 ? bestPattern.length() : quoteIndex;
 
// parse quote-free string
matcher.region(index, endSansQuote);
while (matcher.find()) {
if (index < matcher.start())
res.add(bestPattern.substring(index, matcher.start()));
res.add(REVERSE_JAVA_SPEC.get(matcher.group()));
index = matcher.end();
}
assert index <= endSansQuote : "region() failed";
if (index < endSansQuote)
res.add(bestPattern.substring(index, endSansQuote));
index = endSansQuote;
 
// parse quoted string
if (index < bestPattern.length()) {
quotedMatcher.region(index, bestPattern.length());
if (!quotedMatcher.find() || quotedMatcher.start() != quotedMatcher.regionStart())
throw new IllegalStateException("Quoted string error : " + bestPattern.substring(quoteIndex));
res.add(SQLBase.unquoteStringStd(quotedMatcher.group()));
index = quotedMatcher.end();
}
}
return res;
}
}
 
static public final class CaseBuilder {
// null for "case when"
private final String oneExpr;
private final List<String> expressions;
private String elseExpression;
 
CaseBuilder(final String oneExpr) {
this.oneExpr = oneExpr;
this.expressions = new ArrayList<String>();
this.elseExpression = null;
}
 
public final CaseBuilder addWhen(final String test, final String val) {
this.expressions.add(test);
this.expressions.add(val);
return this;
}
 
public CaseBuilder setElse(String elseExpression) {
this.elseExpression = elseExpression;
return this;
}
 
public final String build() {
if (this.expressions.size() == 0)
return null;
final StringBuilder sb = new StringBuilder(150);
this.build(sb);
return sb.toString();
}
 
public final void build(final StringBuilder sb) {
sb.append("CASE ");
if (this.oneExpr != null) {
sb.append(this.oneExpr);
sb.append(' ');
}
final int stop = this.expressions.size();
for (int i = 0; i < stop; i += 2) {
sb.append("WHEN ");
sb.append(this.expressions.get(i));
sb.append(" THEN ");
sb.append(this.expressions.get(i + 1));
sb.append(' ');
}
if (this.elseExpression != null) {
sb.append("ELSE ");
sb.append(this.elseExpression);
sb.append(' ');
}
sb.append("END");
}
}
 
static public enum ConstraintType {
CHECK, FOREIGN_KEY("FOREIGN KEY"), PRIMARY_KEY("PRIMARY KEY"), UNIQUE,
/**
105,38 → 278,69
}
 
static {
register(new SQLSyntaxPG());
register(new SQLSyntaxH2());
register(new SQLSyntaxMySQL());
register(new SQLSyntaxMS());
 
DEFAULT_LIKE_ESCAPER.add('_', '_');
DEFAULT_LIKE_ESCAPER.add('%', '%');
}
 
static private void register(SQLSyntax s) {
instances.put(s.getSystem(), s);
public final static SQLSyntax get(DBStructureItemDB sql) {
return sql.getDBSystemRoot().getSyntax();
}
 
public final static SQLSyntax get(DBStructureItem<?> sql) {
return get(sql.getServer().getSQLSystem());
public final static SQLSyntax get(SQLIdentifier sql) {
return sql.getDBSystemRoot().getSyntax();
}
 
public final static SQLSyntax get(SQLSystem system) {
final SQLSyntax res = instances.get(system);
/**
* Get the default syntax for the passed system. NOTE : when needing a syntax for system
* currently accessible, {@link DBSystemRoot#getSyntax()} should be used so that server options
* can be read. Otherwise constructors of subclasses should be used to specify options.
*
* @param system a SQL system.
* @return the default syntax.
*/
synchronized final static SQLSyntax get(SQLSystem system) {
SQLSyntax res = instances.get(system);
if (res == null) {
res = create(system, null);
if (res == null)
throw new IllegalArgumentException("unsupported system: " + system);
instances.put(system, res);
}
return res;
}
 
static SQLSyntax create(DBSystemRoot sysRoot) {
return create(sysRoot.getServer().getSQLSystem(), sysRoot);
}
 
private static SQLSyntax create(final SQLSystem sys, final DBSystemRoot sysRoot) {
final SQLSyntax res;
if (sys == SQLSystem.POSTGRESQL)
res = new SQLSyntaxPG();
else if (sys == SQLSystem.H2)
res = new SQLSyntaxH2();
else if (sys == SQLSystem.MYSQL)
res = SQLSyntaxMySQL.create(sysRoot);
else if (sys == SQLSystem.MSSQL)
res = new SQLSyntaxMS();
else
res = null;
assert res == null || res.getSystem() == sys;
return res;
}
 
private final SQLSystem sys;
// list to specify the preferred first
protected final ListMap<Class<?>, String> typeNames;
// need identity since we use plain strings
protected final IdentityHashMap<String, String> dateSpecifiers;
 
protected SQLSyntax(final SQLSystem sys) {
protected SQLSyntax(final SQLSystem sys, final IdentityHashMap<String, String> dateSpecifiers) {
this.sys = sys;
this.typeNames = new ListMap<Class<?>, String>();
if (!dateSpecifiers.keySet().equals(DateProp.ALL_INSTANCES))
throw new IllegalArgumentException("Not all instances : " + dateSpecifiers.keySet());
this.dateSpecifiers = dateSpecifiers;
}
 
/**
153,6 → 357,21
return this.sys;
}
 
/**
* Quote an SQL string.
*
* @param s an arbitrary string, e.g. "salut\ l'ami".
* @return the quoted form, e.g. "'salut\\ l''ami'".
* @see SQLBase#quoteStringStd(String)
*/
public String quoteString(String s) {
return SQLBase.quoteStringStd(s);
}
 
public final static String quoteString(SQLSyntax b, String s) {
return b == null ? SQLBase.quoteStringStd(s) : b.quoteString(s);
}
 
public abstract int getMaximumIdentifierLength();
 
public String getInitSystemRoot() {
354,7 → 573,7
 
public final String getFieldDecl(SQLField f) {
String res = "";
final SQLSyntax fs = SQLSyntax.get(f.getServer().getSQLSystem());
final SQLSyntax fs = SQLSyntax.get(f);
if (fs.isAuto(f))
res += this.getAuto();
else {
398,7 → 617,7
}
 
static final String getNormalizedDefault(SQLField f) {
final SQLSyntax fs = f.getServer().getSQLSystem().getSyntax();
final SQLSyntax fs = SQLSyntax.get(f);
final String stdDefault = fs.transfDefaultSQL2Common(f);
if (stdDefault == null) {
return null;
457,7 → 676,7
}
 
public final String getType(SQLField f) {
final SQLSyntax fs = f.getServer().getSQLSystem().getSyntax();
final SQLSyntax fs = SQLSyntax.get(f);
final SQLType t = f.getType();
 
final String sqlType;
491,23 → 710,10
}
} else if (t.getJavaType() == BigDecimal.class) {
sqlType = "DECIMAL(" + t.getSize() + "," + t.getDecimalDigits() + ")";
} else if (t.getJavaType() == Boolean.class) {
sqlType = getBooleanType();
} else if (Number.class.isAssignableFrom(t.getJavaType())) {
if (Double.class.isAssignableFrom(t.getJavaType())) {
// this is the standard name (the only one accepted by pg)
sqlType = "double precision";
} else if (Float.class.isAssignableFrom(t.getJavaType())) {
// MySQL needs REAL_AS_FLOAT mode (e.g. from ANSI)
sqlType = "real";
} else {
// always remove unsigned they're a pain to maintain across systems
// use standard SQL types
sqlType = typeName.replace("unsigned", "").replace("int2", "smallint").replace("integer", "int").replace("int4", "int").replace("int8", "bigint").trim();
// don't care for qualifiers (like unsigned) they're a pain to maintain across systems
sqlType = this.getTypeNames(t.getJavaType()).iterator().next();
}
} else {
sqlType = typeName;
}
return sqlType;
}
 
536,14 → 742,10
*
* @return the type that store both the date and time.
*/
public String getDateAndTimeType() {
return "timestamp";
public final String getDateAndTimeType() {
return this.getTypeNames(Timestamp.class).iterator().next();
}
 
public String getBooleanType() {
return "boolean";
}
 
/**
* The maximum number of characters in a column. Can be less than that if there are other
* columns.
595,10 → 797,26
*/
protected abstract Tuple2<Boolean, String> getCast();
 
/**
* How to write a cast.
*
* @param expr the expression to cast.
* @param type the keyword (some systems don't use regular type names).
* @return the CAST expression.
* @see #cast(String, Class)
*/
public String cast(final String expr, final String type) {
return "CAST( " + expr + " AS " + type + " )";
}
 
public String cast(final String expr, final Class<?> javaType) {
return this.cast(expr, this.getTypeNames(javaType).iterator().next());
}
 
public final String cast(final String expr, final SQLType type) {
return this.cast(expr, type.getJavaType());
}
 
// JDBC says: ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION
public List<Map<String, Object>> getIndexInfo(final SQLTable t) throws SQLException {
final List<?> indexesInfo = t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<List<?>, SQLException>() {
854,10 → 1072,6
throw new IllegalArgumentException("the server of " + t + " is not this computer: " + t.getServer());
}
 
SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new SQLBase(server, name, systemRootInit, login, pass, dsInit);
}
 
/**
* The function to return the character with the given ASCII code.
*
933,8 → 1147,36
return x + (eq ? " IS NOT DISTINCT FROM " : " IS DISTINCT FROM ") + y;
}
 
public final CaseBuilder createCaseWhenBuilder() {
return new CaseBuilder(null);
}
 
public final CaseBuilder createCaseBuilder(final String oneExpr) {
if (oneExpr == null)
throw new IllegalArgumentException("Missing expression");
return new CaseBuilder(oneExpr);
}
 
/**
* Return the SQL expression to get the day of the week for the passed date.
*
* @param sqlTS an SQL expression of type time stamp.
* @return 1 for Sunday through 7 for Saturday.
*/
public abstract String getDayOfWeek(final String sqlTS);
 
/**
* Return the SQL expression to get the month of the passed date.
*
* @param sqlTS an SQL expression of type time stamp.
* @return 1 for January through 12 for December.
*/
public String getMonth(final String sqlTS) {
return "MONTH(" + sqlTS + ")";
}
 
public final String getFormatTimestamp(final Timestamp ts, final boolean basic) {
return this.getFormatTimestamp(SQLBase.quoteStringStd(ts.toString()), basic);
return this.getFormatTimestamp(SQLType.getFromSyntax(this, Types.TIMESTAMP, 0).toString(ts), basic);
}
 
/**
952,6 → 1194,120
*/
public abstract String getFormatTimestamp(final String sqlTS, final boolean basic);
 
/**
* Return the native format from the passed simple one. If names are required, the server locale
* must be set correctly or {@link #getFormatTimestampSimple(DBRoot, String, List, Locale)}
* should be used.
*
* @param simpleFormat a list of either {@link DateProp} or literal string to include.
* @param useServerLocale <code>true</code> to allow the server to format strings (e.g. month
* names).
* @return the native format (to use with {@link #getFormatTimestamp(String, String)}).
* @throws IllegalArgumentException if <code>useServerLocale</code> is <code>false</code> and
* <code>simpleFormat</code> contains some name format properties.
*/
public final String getTimestampFormat(final List<String> simpleFormat, final boolean useServerLocale) throws IllegalArgumentException {
final StringBuilder res = new StringBuilder();
final StringBuilder literal = new StringBuilder();
for (final String s : simpleFormat) {
if (!useServerLocale && DateProp.LOCALE_SENSITIVE_INSTANCES.contains(s))
throw new IllegalArgumentException("passed locale sensitive property : " + s);
final String spec = this.dateSpecifiers.get(s);
if (spec == null) {
// we can't append multiple literal : 'foo' then 'bar' makes 'foo''bar' i.e.
// "foo'bar" instead of the wanted "foobar"
literal.append(s);
} else {
if (literal.length() > 0) {
res.append(this.quoteForTimestampFormat(literal.toString()));
literal.setLength(0);
}
res.append(spec);
}
}
if (literal.length() > 0) {
res.append(this.quoteForTimestampFormat(literal.toString()));
}
return quoteString(res.toString());
}
 
public abstract String quoteForTimestampFormat(final String text);
 
/**
* Return the SQL expression that format the passed time stamp.
*
* @param sqlTS an SQL expression of type time stamp.
* @param nativeFormat a SQL varchar for a native format to this syntax, e.g. obtained from
* {@link #getTimestampFormat(DBRoot, List, boolean)} .
* @return the SQL needed to format the passed parameter, e.g. FORMATDATETIME(CURRENT_TIMESTAMP,
* 'yyyy').
*/
public abstract String getFormatTimestamp(final String sqlTS, final String nativeFormat);
 
public final String getFormatTimestampSimple(String sqlTS, List<String> format) {
return this.getFormatTimestampSimple(sqlTS, format, Locale.getDefault());
}
 
static private final int[] CALENDAR_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
 
/**
* Return the SQL expression that format the passed time stamp.
*
* @param sqlTS an SQL expression of type time stamp.
* @param simpleFormat a list of either {@link DateProp} or literal string to include, e.g. [
* "year is ", {@link DateProp#YEAR}].
* @param l the locale to use, <code>null</code> meaning use the server.
* @return the SQL needed to format the passed parameter.
*/
public final String getFormatTimestampSimple(String sqlTS, List<String> simpleFormat, final Locale l) {
final boolean useServerLocale = l == null;
if (!useServerLocale) {
final List<String> statements = new ArrayList<String>();
// minimize function calls
final List<String> nonLocalSensitive = new ArrayList<String>();
for (final String s : simpleFormat) {
if (!DateProp.LOCALE_SENSITIVE_INSTANCES.contains(s)) {
nonLocalSensitive.add(s);
} else {
if (!nonLocalSensitive.isEmpty()) {
statements.add(this.getFormatTimestamp(sqlTS, this.getTimestampFormat(nonLocalSensitive, useServerLocale)));
nonLocalSensitive.clear();
}
final StringBuilder sb = new StringBuilder(512);
final DateFormatSymbols symbols = DateFormatSymbols.getInstance(l);
if (s == DateProp.DAY_NAME_IN_WEEK) {
final CaseBuilder caseBuilder = createCaseBuilder(this.getDayOfWeek(sqlTS));
final String[] weekdays = symbols.getWeekdays();
for (int j = 0; j < CALENDAR_DAYS.length; j++) {
// SQL function begins at 1
caseBuilder.addWhen(String.valueOf(j + 1), quoteString(weekdays[CALENDAR_DAYS[j]]));
}
caseBuilder.setElse(quoteString("unknown week day name"));
caseBuilder.build(sb);
} else if (s == DateProp.MONTH_NAME) {
final CaseBuilder caseBuilder = createCaseBuilder(this.getMonth(sqlTS));
int i = 1;
for (final String m : symbols.getMonths()) {
caseBuilder.addWhen(String.valueOf(i), quoteString(m));
i++;
}
caseBuilder.setElse(quoteString("unknown month name"));
caseBuilder.build(sb);
} else {
throw new IllegalStateException("Unknown prop : " + s);
}
statements.add(sb.toString());
}
}
if (!nonLocalSensitive.isEmpty()) {
statements.add(this.getFormatTimestamp(sqlTS, this.getTimestampFormat(nonLocalSensitive, useServerLocale)));
}
return CollectionUtils.join(statements, this.getConcatOp());
} else {
return this.getFormatTimestamp(sqlTS, this.getTimestampFormat(simpleFormat, useServerLocale));
}
}
 
public final String getInsertOne(final SQLName tableName, final List<String> fields, String... values) {
return this.getInsertOne(tableName, fields, Arrays.asList(values));
}
999,7 → 1355,25
return sb.toString();
}
 
public final String getConstantTableStatement(final List<List<String>> rows) {
return this.getConstantTableStatement(rows, -1);
}
 
/**
* Return a complete statement to return the passed list of rows.
*
* @param rows the rows with the SQL expression for each cell.
* @param colCount the number of columns the rows must have, -1 meaning infer it from
* <code>rows</code>.
* @return the complete SQL expression that can be executed as is.
* @see #getValues(List, int)
* @see #getConstantTable(List, String, List)
*/
public String getConstantTableStatement(final List<List<String>> rows, int colCount) {
return this.getValues(rows, colCount);
}
 
/**
* Get a constant table usable as a join.
*
* @param rows the SQL values for the table, e.g. [["1", "'one'"], ["2", "'two'"]].
1027,15 → 1401,15
return sb.toString();
}
 
protected final String getTablesMapJoin(final SQLBase b, final TablesMap tables, final String schemaExpr, final String tableExpr) {
protected final String getTablesMapJoin(final TablesMap tables, final String schemaExpr, final String tableExpr) {
final List<List<String>> rows = new ArrayList<List<String>>();
for (final Entry<String, Set<String>> e : tables.entrySet()) {
final String schemaName = b.quoteString(e.getKey());
final String schemaName = this.quoteString(e.getKey());
if (e.getValue() == null) {
rows.add(Arrays.asList(schemaName, "NULL"));
} else {
for (final String tableName : e.getValue())
rows.add(Arrays.asList(schemaName, b.quoteString(tableName)));
rows.add(Arrays.asList(schemaName, this.quoteString(tableName)));
}
}
final String tableAlias = "tables";
1079,11 → 1453,11
*/
public abstract List<Map<String, Object>> getConstraints(SQLBase b, TablesMap tables) throws SQLException;
 
protected static final String quoteStrings(final SQLBase b, Collection<String> c) {
protected final String quoteStrings(Collection<String> c) {
return CollectionUtils.join(c, ", ", new ITransformer<String, String>() {
@Override
public String transformChecked(String s) {
return b.quoteString(s);
return quoteString(s);
}
});
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/HierarchyLevel.java
78,16 → 78,16
throw new IllegalArgumentException(clazz + " hierarchy unknown");
}
 
static private Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> byClass;
static private final Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> byClass;
 
static private final Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> getByClass() {
// java : Cannot refer to the static enum field HierarchyLevel.byClass within an initializer
// thus not in ctor but on demand.
if (byClass == null) {
static {
byClass = new HashMap<Class<? extends DBStructureItemJDBC>, HierarchyLevel>();
for (HierarchyLevel l : HierarchyLevel.values())
for (HierarchyLevel l : HierarchyLevel.values()) {
byClass.put(l.getJDBCClass(), l);
}
}
 
static private final Map<Class<? extends DBStructureItemJDBC>, HierarchyLevel> getByClass() {
return byClass;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBFileCache.java
15,6 → 15,7
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.utils.BaseDirs;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.ProductInfo;
import org.openconcerto.utils.StringUtils;
50,18 → 51,18
private static final String FILE_STRUCT_VERSION = "20080904-1411";
 
static private final File getFwkSaveDir() {
final File confDir;
final BaseDirs confDir;
// require at least an application name, since settings might influence what is to be saved
// MAYBE pass the server and allow it to have an ID
// (this would handle the case when one app needs two different connections to a server)
if (Configuration.getInstance() != null) {
confDir = Configuration.getInstance().getConfDir();
confDir = Configuration.getInstance().getBaseDirs();
} else if (ProductInfo.getInstance() != null) {
confDir = new File(Configuration.getDefaultConfDir(), ProductInfo.getInstance().getName());
confDir = BaseDirs.create(ProductInfo.getInstance());
} else {
return null;
}
return new File(confDir, "DBCache/" + FILE_STRUCT_VERSION);
return new File(confDir.getCacheFolder(), "DBCache/" + FILE_STRUCT_VERSION);
}
 
static private final File getValidFwkSaveDir() {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLDataSource.java
30,6 → 30,8
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLNonTransientException;
import java.sql.SQLTransientException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.Arrays;
46,7 → 48,6
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
 
import org.apache.commons.dbcp.AbandonedConfig;
69,6 → 70,7
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.h2.constant.ErrorCode;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
 
94,11 → 96,6
DRIVERS.put(SQLSystem.DERBY, "org.apache.derby.jdbc.ClientDriver");
DRIVERS.put(SQLSystem.H2, "org.h2.Driver");
DRIVERS.put(SQLSystem.MSSQL, "com.microsoft.sqlserver.jdbc.SQLServerDriver");
 
// by default h2 convert database name to upper case (we used to work around it with
// SQLSystem.getMDName() but in r2251 an equalsIgnoreCase() was replaced by equals())
// see http://code.google.com/p/h2database/issues/detail?id=204
System.setProperty("h2.databaseToUpper", "false");
}
 
// timeouts in seconds
151,6 → 148,7
// all thread safe
public static final ColumnListHandler COLUMN_LIST_HANDLER = new ColumnListHandler();
public static final ArrayListHandler ARRAY_LIST_HANDLER = new ArrayListHandler();
public static final ListListHandlerGeneric<Object> LIST_LIST_HANDLER = ListListHandlerGeneric.create(Object.class, null);
public static final ArrayHandler ARRAY_HANDLER = new ArrayHandler();
public static final ScalarHandler SCALAR_HANDLER = new ScalarHandler();
public static final MapListHandler MAP_LIST_HANDLER = new MapListHandler(ROW_PROC);
204,7 → 202,7
@GuardedBy("this")
private boolean checkOnceDBTxIsolation;
 
private final ReentrantLock testLock = new ReentrantLock();
private final Object testLock = new String("testLock");
 
public SQLDataSource(DBSystemRoot sysRoot, String base, String login, String pass) {
this(sysRoot, sysRoot.getServer().getURL(base), login, pass, Collections.<SQLTable> emptySet());
250,7 → 248,7
this.setLoginTimeout(loginTimeOut);
this.setSocketTimeout(socketTimeOut);
this.setTCPKeepAlive(true);
this.setRetryWait(3000);
this.setRetryWait(7000);
// ATTN DO NOT call execute() or any method that might create a connection
// since at this point dsInit() has not been called and thus connection properties might be
// missing (eg allowMultiQueries). And the faulty connection will stay in the pool.
271,7 → 269,9
if (this.getSystem() == SQLSystem.MYSQL) {
this.addConnectionProperty("socketTimeout", timeout + "000");
} else if (this.getSystem() == SQLSystem.H2) {
this.addConnectionProperty("QUERY_TIMEOUT", timeout + "000");
// org.h2.util.NetUtils.createSocket() doesn't use setSoTimeout(), so this is the next
// best thing. But it isn't checked everywhere, see DbSettings.maxQueryTimeout
this.addConnectionProperty("MAX_QUERY_TIMEOUT", timeout + "000");
} else if (this.getSystem() == SQLSystem.POSTGRESQL) {
this.addConnectionProperty("socketTimeout", timeout + "");
} else {
310,6 → 310,10
updateCache();
}
 
private synchronized Set<SQLTable> getTables() {
return this.tables;
}
 
private synchronized void updateCache() {
if (this.cache != null)
this.cache.getSupp().die();
492,8 → 496,8
* @see MapHandler
* @see #execute(String)
*/
public Map execute1(String query) {
return (Map) this.execute(query, MAP_HANDLER);
public Map<String, Object> execute1(String query) {
return (Map<String, Object>) this.execute(query, MAP_HANDLER);
}
 
/**
582,7 → 586,7
final List<Object> key = cache == null ? null : getCacheKey(query, rsh);
final CacheResult<Object> cacheRes;
if (key != null) {
final Set<? extends SQLData> data = irsh == null || irsh.getCacheModifiers() == null ? this.tables : irsh.getCacheModifiers();
final Set<? extends SQLData> data = irsh == null || irsh.getCacheModifiers() == null ? this.getTables() : irsh.getCacheModifiers();
cacheRes = cache.check(key, readCache, canWriteCache, data);
if (cacheRes.getState() == CacheResult.State.INTERRUPTED)
throw new RTInterruptedException("interrupted while waiting for the cache");
601,6 → 605,7
QueryInfo info = null;
final long afterCache = System.nanoTime();
final long afterQueryInfo, afterExecute, afterHandle;
int count = 0;
try {
info = new QueryInfo(query, changeState, passedConn);
try {
622,9 → 627,10
if (this.getSystem() == SQLSystem.DERBY || this.getSystem() == SQLSystem.POSTGRESQL) {
rs = new SQLResultSet(rs);
}
 
result = rsh.handle(rs);
count = SQLResultSet.getRowProcessedCount(rs);
}
 
afterHandle = System.nanoTime();
 
stmt.close();
649,7 → 655,7
throw e;
}
 
SQLRequestLog.log(query, "", info.getConnection(), timeMs, time, afterCache, afterQueryInfo, afterExecute, afterHandle, System.nanoTime());
SQLRequestLog.log(query, "", info.getConnection(), timeMs, time, afterCache, afterQueryInfo, afterExecute, afterHandle, System.nanoTime(), count);
 
return result;
}
789,11 → 795,15
return "cancel of " + System.identityHashCode(getConnection()) + " failed for " + getQuery();
}
 
final boolean canObtainNewConnection() {
return this.privateConnection;
}
 
// an error has occured, try within another connection if possible
final Connection obtainNewConnection() {
if (!this.privateConnection)
if (!this.canObtainNewConnection()) {
return null;
else {
} else {
// ATTN should be sure that our connection was not already closed,
// see #closeConnection()
closeConnection(this.getConnection());
939,15 → 949,31
try {
res = executeOnce(query, queryInfo.getConnection());
} catch (SQLException exn) {
// TODO only retry for transient errors
if (State.DEBUG)
State.INSTANCE.addFailedRequest(query);
// only retry for transient errors
final boolean retry;
if (exn instanceof SQLTransientException) {
retry = true;
} else if (exn instanceof SQLNonTransientException) {
retry = false;
} else if (getSystem() == SQLSystem.H2) {
// 1. server was killed, maybe it will be restarted
// 2. client network interface was brought down, maybe it will be brought up again
retry = exn.getErrorCode() == ErrorCode.CONNECTION_BROKEN_1;
} else if (getSystem() == SQLSystem.POSTGRESQL) {
// Class 08 — Connection Exception (e.g. SocketException)
// Class 57 — Operator Intervention (e.g. server shutdown)
retry = exn.getSQLState().startsWith("08") || exn.getSQLState().startsWith("57");
} else {
retry = getSystem() == SQLSystem.MYSQL;
}
// maybe this was a network problem, so wait a little
final int retryWait = this.retryWait;
if (retryWait < 0)
if (!retry || retryWait < 0 || !queryInfo.canObtainNewConnection())
throw exn;
try {
Thread.sleep(this.retryWait);
Thread.sleep(retryWait);
} catch (InterruptedException e) {
throw new RTInterruptedException(e.getMessage() + " : " + query, exn);
}
954,10 → 980,7
// and try to obtain a new connection
try {
final Connection otherConn = queryInfo.obtainNewConnection();
if (otherConn != null) {
res = executeOnce(query, otherConn);
} else
throw exn;
} catch (Exception e) {
if (e == exn)
throw exn;
1291,11 → 1314,21
*/
private final Connection borrowConnection(final boolean test) throws NoSuchElementException {
if (test) {
this.testLock.lock();
synchronized (this.testLock) {
// invalidate all bad connections
setTestOnBorrow(true);
try {
return this._borrowConnection(test);
} finally {
setTestOnBorrow(false);
}
try {
}
} else {
return this._borrowConnection(test);
}
}
 
private final Connection _borrowConnection(final boolean test) throws NoSuchElementException {
// when we call borrowConnection() with test, it's because there was an error so this
// call is already a second try, thus getRawConnection() shouldn't try a third time.
final Connection res = this.getRawConnection(!test);
1306,13 → 1339,7
this.closeConnection(res);
throw e;
}
} finally {
if (test) {
setTestOnBorrow(false);
this.testLock.unlock();
}
}
}
 
// initialize the passed connection if needed
protected final void initConnection(final Connection res) {
1363,7 → 1390,7
throw new RTInterruptedException(e1);
}
final int retryWait = retry ? this.retryWait : -1;
if (retryWait < 0)
if (retryWait < 0 || e1 instanceof SQLNonTransientException)
getRawConnectionThrow(e1, null);
try {
// on attend un petit peu
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowAccessor.java
14,10 → 14,14
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Step;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.HashingStrategy;
import org.openconcerto.utils.convertor.StringClobConvertor;
 
26,12 → 30,15
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
 
/**
* A class that represent a row of a table. The row might not acutally exists in the database, and
115,6 → 122,14
*/
public abstract class SQLRowAccessor implements SQLData {
 
@Deprecated
static public final String ACCESS_DB_IF_NEEDED_PROP = "SQLRowAccessor.accessDBIfNeeded";
static private final boolean ACCESS_DB_IF_NEEDED = Boolean.parseBoolean(System.getProperty(ACCESS_DB_IF_NEEDED_PROP, "false"));
 
public static boolean getAccessDBIfNeeded() {
return ACCESS_DB_IF_NEEDED;
}
 
static private final HashingStrategy<SQLRowAccessor> ROW_STRATEGY = new HashingStrategy<SQLRowAccessor>() {
@Override
public int computeHashCode(SQLRowAccessor object) {
271,7 → 286,7
// MAYBE change paramter to enum MissingMode = THROW_EXCEPTION, ADD, RETURN_NULL
public final Object getObject(String fieldName, final boolean mustBePresent) throws IllegalArgumentException {
if (mustBePresent && !this.getFields().contains(fieldName))
throw new IllegalArgumentException("Field " + fieldName + " not present in this : " + this.getFields());
throw new IllegalArgumentException("Field " + fieldName + " not present in this : " + this.getFields() + " table " + this.getTable().getName());
return this.getObject(fieldName);
}
 
385,6 → 400,25
}
 
/**
* Returns the foreign table of <i>fieldName</i>.
*
* @param fieldName the name of a foreign field, e.g. "ID_ARTICLE_2".
* @return the table the field points to (never <code>null</code>), e.g. |ARTICLE|.
* @throws IllegalArgumentException if <i>fieldName</i> is not a foreign field.
*/
protected final SQLTable getForeignTable(String fieldName) throws IllegalArgumentException {
return this.getForeignLink(Collections.singletonList(fieldName)).getTarget();
}
 
protected final Link getForeignLink(final List<String> fieldsNames) throws IllegalArgumentException {
final DatabaseGraph graph = this.getTable().getDBSystemRoot().getGraph();
final Link foreignLink = graph.getForeignLink(this.getTable(), fieldsNames);
if (foreignLink == null)
throw new IllegalArgumentException(fieldsNames + " are not a foreign key of " + this.getTable());
return foreignLink;
}
 
/**
* Return the foreign row, if any, for the passed field.
*
* @param fieldName name of the foreign field.
395,6 → 429,26
public abstract SQLRowAccessor getForeign(String fieldName);
 
/**
* Return the non empty foreign row, if any, for the passed field.
*
* @param fieldName name of the foreign field.
* @return <code>null</code> if the value of <code>fieldName</code> is
* {@link #isForeignEmpty(String) empty}, otherwise a SQLRowAccessor with the value of
* <code>fieldName</code> as its ID.
* @throws IllegalArgumentException if fieldName is not a foreign field or if the field isn't
* specified.
*/
public final SQLRowAccessor getNonEmptyForeign(String fieldName) {
if (this.isForeignEmpty(fieldName)) {
return null;
} else {
final SQLRowAccessor res = this.getForeign(fieldName);
assert res != null;
return res;
}
}
 
/**
* Return the ID of a foreign row.
*
* @param fieldName name of the foreign field.
408,17 → 462,61
}
 
/**
* Return the ID of a foreign row.
* Return the ID of a foreign row. NOTE : there's two cases when the result can be
* <code>null</code> :
* <ol>
* <li><code>field</code> is defined and has the value <code>null</code></li>
* <li><code>field</code> is defined and has an SQLRowValues value without an ID</li>
* </ol>
* In the second case, <code>field</code> is *not* {@link #isForeignEmpty(String) empty}, an ID
* is just missing.
*
* @param fieldName name of the foreign field.
* @return the value of <code>fieldName</code> or {@link #getIDNumber()} if the value is a
* {@link SQLRowAccessor}, <code>null</code> if the actual value is.
* {@link SQLRowValues}, <code>null</code> if the actual value is.
* @throws IllegalArgumentException if fieldName is not a foreign field or if the field isn't
* specified.
*/
public abstract Number getForeignIDNumber(String fieldName) throws IllegalArgumentException;
public final Number getForeignIDNumber(String fieldName) throws IllegalArgumentException {
final Value<Number> res = getForeignIDNumberValue(fieldName);
return res.hasValue() ? res.getValue() : null;
}
 
/**
* Return the ID of a foreign row.
*
* @param fieldName name of the foreign field.
* @return {@link Value#getNone()} if there's a {@link SQLRowValues} without
* {@link SQLRowValues#hasID() ID}, otherwise the value of <code>fieldName</code> or
* {@link #getIDNumber()} if the value is a {@link SQLRowValues}, never
* <code>null</code> (the {@link Value#getValue()} is <code>null</code> when
* <code>fieldName</code> is).
* @throws IllegalArgumentException if fieldName is not a foreign field or if the field isn't
* specified.
*/
public final Value<Number> getForeignIDNumberValue(final String fieldName) throws IllegalArgumentException {
fetchIfNeeded(fieldName);
// don't use getForeign() to avoid creating a SQLRow
final Object val = this.getContainedObject(fieldName);
if (val instanceof SQLRowValues) {
final SQLRowValues vals = (SQLRowValues) val;
return vals.hasID() ? Value.getSome(vals.getIDNumber()) : Value.<Number> getNone();
} else {
if (!this.getTable().getField(fieldName).isForeignKey())
throw new IllegalArgumentException(fieldName + "is not a foreign key of " + this.getTable());
return Value.getSome((Number) val);
}
}
 
private void fetchIfNeeded(String fieldName) {
if (getAccessDBIfNeeded() && (this instanceof SQLRow) && !getFields().contains(fieldName)) {
assert false : "Missing " + fieldName + " in " + this;
Log.get().log(Level.WARNING, "Missing " + fieldName + " in " + this, new IllegalStateException());
((SQLRow) this).fetchValues();
}
}
 
/**
* Whether the passed field is empty.
*
* @param fieldName name of the foreign field.
425,7 → 523,18
* @return <code>true</code> if {@link #getForeignIDNumber(String)} is the
* {@link SQLTable#getUndefinedIDNumber()}.
*/
public abstract boolean isForeignEmpty(String fieldName);
public final boolean isForeignEmpty(String fieldName) {
final Value<Number> fID = this.getForeignIDNumberValue(fieldName);
if (!fID.hasValue()) {
// a foreign row values without ID is *not* undefined
return false;
} else {
// keep getForeignTable at the 1st line since it does the check
final SQLTable foreignTable = this.getForeignTable(fieldName);
final Number undefID = foreignTable.getUndefinedIDNumber();
return NumberUtils.areNumericallyEqual(fID.getValue(), undefID);
}
}
 
public abstract Collection<? extends SQLRowAccessor> getReferentRows();
 
483,4 → 592,18
public final int hashCodeAsRow() {
return this.getTable().hashCode() + this.getID();
}
 
public SQLRowValues getMergedRowValuesFromDatabase() {
SQLRowValues result = this.asRowValues();
if (this.hasID()) {
SQLRow r = result.getTable().getRow(getID());
for (String f : r.getFields()) {
if (!result.getFields().contains(f)) {
result.put(f, r.getObject(f));
}
}
}
return result;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSchema.java
13,6 → 13,8
package org.openconcerto.sql.model;
 
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
 
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.utils.ChangeTable;
import org.openconcerto.sql.utils.ChangeTable.ClauseType;
22,7 → 24,6
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.CopyOnWriteMap;
import org.openconcerto.utils.change.CollectionChangeEventCreator;
import org.openconcerto.xml.JDOMUtils;
 
import java.io.IOException;
import java.sql.DatabaseMetaData;
38,11 → 39,11
import java.util.Map.Entry;
import java.util.Set;
 
import net.jcip.annotations.GuardedBy;
 
import org.apache.commons.dbutils.ResultSetHandler;
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
 
public final class SQLSchema extends SQLIdentifier {
 
/**
78,7 → 79,7
sb.append(' ');
sb.append(VERSION_XMLATTR);
sb.append("=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(version));
sb.append(OUTPUTTER.escapeAttributeEntities(version));
sb.append('"');
}
}
319,7 → 320,7
sb.append("<schema ");
if (this.getName() != null) {
sb.append(" name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append(OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append('"');
}
synchronized (getTreeMutex()) {
330,13 → 331,13
sb.append("<procedures>\n");
for (final Entry<String, String> e : this.procedures.entrySet()) {
sb.append("<proc name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(e.getKey()));
sb.append(OUTPUTTER.escapeAttributeEntities(e.getKey()));
sb.append("\" ");
if (e.getValue() == null) {
sb.append("/>");
} else {
sb.append("><src>");
sb.append(JDOMUtils.OUTPUTTER.escapeElementEntities(e.getValue()));
sb.append(OUTPUTTER.escapeElementEntities(e.getValue()));
sb.append("</src></proc>\n");
}
}
381,7 → 382,7
return Tuple2.create(false, null);
 
final SQLSystem sys = getServer().getSQLSystem();
final SQLSyntax syntax = sys.getSyntax();
final SQLSyntax syntax = this.getDBSystemRoot().getSyntax();
final SQLDataSource ds = this.getDBSystemRoot().getDataSource();
synchronized (this.getTreeMutex()) {
// don't refresh until after the insert, that way if the refresh triggers an access to
448,6 → 449,7
}
 
final String updateVersion(boolean createTable) throws SQLException {
// don't use the VM time as it can vary between clients, use the server time
return this.setFwkMetadata(SQLSchema.VERSION_MDKEY, getVersionSQL(SQLSyntax.get(this)), createTable).get1();
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/StructureSource.java
169,7 → 169,7
// DatabaseMetaData param to avoid re-asking it
protected final Set<String> getJDBCSchemas(final DatabaseMetaData metaData) throws SQLException {
// getSchemas(this.getBase().getMDName(), null) not implemented by pg
final Set<String> res = new HashSet<String>((List) SQLDataSource.COLUMN_LIST_HANDLER.handle(metaData.getSchemas()));
final Set<String> res = new HashSet<String>(ColumnListHandlerGeneric.create(String.class).handle(metaData.getSchemas()));
// if db does not support schemas
if (res.isEmpty() && !this.getBase().getServer().getSQLSystem().getLevels().contains(HierarchyLevel.SQLSCHEMA))
res.add(null);
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBStructureItem.java
112,7 → 112,7
}
 
public final boolean contains(String childName) {
return this.getChildrenNames().contains(childName);
return this.getChildrenMap().containsKey(childName);
}
 
/**
176,15 → 176,21
}
 
private final D getAncestor(int level) {
if (level < 0)
if (level < 0) {
throw new IllegalArgumentException("negative level: " + level);
else if (level == 0)
} else if (level == 0) {
return thisAsD();
else if (this.getParent() == null)
} else {
// need to upcast parent since D extends this class and private methods aren't inherited
// http://stackoverflow.com/questions/7719843/type-parameterized-field-of-a-generic-class-becomes-invisible-after-upgrading-to
// http://stackoverflow.com/questions/15062841/java-casting-with-method-calls
final DBStructureItem<D> parentForJavac = this.getParent();
if (parentForJavac == null)
throw new IllegalArgumentException(this + " is the root, can't go up of " + level);
else
return this.getParent().getAncestor(level - 1);
return parentForJavac.getAncestor(level - 1);
}
}
 
/**
* The youngest common ancestor.
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBRoot.java
150,7 → 150,7
@Override
public Object handle(SQLDataSource ds) throws SQLException {
// don't create foreign constraints now, so we can insert undefined with cycles
final List<List<String>> createTablesSQL = ChangeTable.cat(undefinedNonDefaultValues.keySet(), getName(), EnumSet.of(ConcatStep.ADD_FOREIGN));
final List<List<String>> createTablesSQL = ChangeTable.cat(undefinedNonDefaultValues.keySet(), getName(), EnumSet.of(ConcatStep.ADD_CONSTRAINT));
for (final String sql : createTablesSQL.get(0))
ds.execute(sql);
final Map<SQLCreateTableBase<?>, Number> newUndefIDs;
312,16 → 312,20
this.getBase().fetchTables(TablesMap.createFromTables(this.getSchema().getName(), tableNames));
}
 
public final SQLCreateRoot getDefinitionSQL(final SQLSystem sys) {
return this.getDefinitionSQL(sys, true);
public final SQLCreateRoot getDefinitionSQL() {
return this.getDefinitionSQL(getDBSystemRoot().getSyntax());
}
 
public final SQLCreateRoot getDefinitionSQL(final SQLSystem sys, final boolean withTables) {
final SQLCreateRoot res = new SQLCreateRoot(sys.getSyntax(), this.getName());
public final SQLCreateRoot getDefinitionSQL(final SQLSyntax s) {
return this.getDefinitionSQL(s, true);
}
 
public final SQLCreateRoot getDefinitionSQL(final SQLSyntax s, final boolean withTables) {
final SQLCreateRoot res = new SQLCreateRoot(s, this.getName());
if (withTables) {
// order by name to be able to do diffs
for (final SQLTable table : new TreeMap<String, SQLTable>(this.getTablesMap()).values()) {
res.addTable(table.getCreateTable(sys));
res.addTable(table.getCreateTable(s));
}
}
return res;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLBase.java
52,11 → 52,11
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
/**
* Une base de donnée SQL. Une base est unique, pour obtenir une instance il faut passer par
* SQLServer. Une base permet d'accéder aux tables qui la composent, ainsi qu'à son graphe.
67,7 → 67,7
* @see #getGraph()
*/
@ThreadSafe
public class SQLBase extends SQLIdentifier {
public final class SQLBase extends SQLIdentifier {
 
/**
* Boolean system property, if <code>true</code> then the structure and the graph of SQL base
154,7 → 154,6
protected synchronized void onDrop() {
// allow schemas (and their descendants) to be gc'd even we aren't
this.schemas.clear();
SQLType.remove(this);
super.onDrop();
}
 
782,6 → 781,7
static private final Pattern percent = Pattern.compile("%.");
 
private final static String quote(final SQLBase b, final String pattern, Object... params) {
final SQLSyntax s = b == null ? null : SQLSyntax.get(b);
final Matcher m = percent.matcher(pattern);
final StringBuffer sb = new StringBuffer();
int i = 0;
794,7 → 794,7
} else {
final Object param = params[i++];
if (modifier == 's') {
replacement = quoteString(b, param.toString());
replacement = SQLSyntax.quoteString(s, param.toString());
} else if (modifier == 'i') {
if (param instanceof SQLName)
replacement = ((SQLName) param).quote();
832,11 → 832,11
* @see #quoteStringStd(String)
*/
public String quoteString(String s) {
return quoteStringStd(s);
return SQLSyntax.get(this).quoteString(s);
}
 
static private final Pattern singleQuote = Pattern.compile("'", Pattern.LITERAL);
static private final Pattern quotedPatrn = Pattern.compile("^'(('')|[^'])*'$");
static public final Pattern quotedPatrn = Pattern.compile("'(('')|[^'])*'");
static private final Pattern twoSingleQuote = Pattern.compile("''", Pattern.LITERAL);
 
/**
864,15 → 864,11
* @see #quoteStringStd(String)
*/
public final static String unquoteStringStd(String s) {
if (!quotedPatrn.matcher(s).find())
if (!quotedPatrn.matcher(s).matches())
throw new IllegalArgumentException("Invalid quoted string " + s);
return twoSingleQuote.matcher(s.substring(1, s.length() - 1)).replaceAll("'");
}
 
public final static String quoteString(SQLBase b, String s) {
return b == null ? quoteStringStd(s) : b.quoteString(s);
}
 
// * quoteIdentifier
 
static private final Pattern doubleQuote = Pattern.compile("\"");
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRequestLogModel.java
30,14 → 30,18
 
@Override
public Class<?> getColumnClass(int columnIndex) {
if (columnIndex < 5)
if (columnIndex == 9) {
return Integer.class;
}
if (columnIndex < 5) {
return Long.class;
}
return String.class;
}
 
@Override
public int getColumnCount() {
return 9;
return 10;
}
 
@Override
61,6 → 65,8
return "Connection";
case 8:
return "Thread";
case 9:
return "Returned rows";
}
return "??";
}
96,6 → 102,8
if (l.isInSwing())
return "Swing";
return l.getThreadId();
case 9:
return l.getResultCount();
}
return "";
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowListRSH.java
13,6 → 13,7
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.utils.Tuple2;
 
import java.sql.ResultSet;
181,7 → 182,9
tables.add(ref.getTable());
}
 
return new IResultSetHandler(create(indexes), readCache, writeCache) {
// the SELECT requesting a lock means the caller expects the DB to be accessed
final boolean acquireLock = sel.getLockStrength() != LockStrength.NONE;
return new IResultSetHandler(create(indexes), readCache && !acquireLock, writeCache && !acquireLock) {
@Override
public Set<? extends SQLData> getCacheModifiers() {
return tables;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFieldsSet.java
57,10 → 57,10
return new SQLFieldsSet(res, false);
}
 
static private final SetMapItf<SQLTable, SQLField> toSetMap(final Collection<SQLField> fields) {
static private final SetMapItf<SQLTable, SQLField> toSetMap(final Collection<? extends FieldRef> fields) {
final SetMapItf<SQLTable, SQLField> res = createMap();
for (final SQLField f : fields)
res.add(f.getTable(), f);
for (final FieldRef f : fields)
res.add(f.getField().getTable(), f.getField());
return res;
}
 
92,7 → 92,7
*
* @param fields un ensemble de SQLField, l'ensemble n'est pas modifié.
*/
public SQLFieldsSet(final Collection<SQLField> fields) {
public SQLFieldsSet(final Collection<? extends FieldRef> fields) {
this(toSetMap(fields), false);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLField.java
17,14 → 17,17
package org.openconcerto.sql.model;
 
import static org.openconcerto.sql.model.SQLBase.quoteIdentifier;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLTable.FieldGroup;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.model.graph.SQLKey.Type;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.Value;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.math.BigDecimal;
43,11 → 46,11
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.jdom2.Element;
 
/**
* Un champ SQL. Pour obtenir une instance de cette classe il faut utiliser
* {@link SQLTable#getField(String)}. Un champ connait sa table, son nom, son type et sa valeur par
241,7 → 244,7
* @see SQLSyntax#getType(SQLField)
*/
public final String getTypeDecl() {
return this.getServer().getSQLSystem().getSyntax().getType(this);
return this.getDBSystemRoot().getSyntax().getType(this);
}
 
/**
368,7 → 371,7
}
 
public boolean isKey() {
return this.getTable().getKeys().contains(this);
return this.isPrimaryKey() || this.isForeignKey();
}
 
/**
381,6 → 384,27
return this.getTable().getPrimaryKeys().equals(Collections.singleton(this));
}
 
/**
* Is this the one and only field in a foreign key of its table.
*
* @return <code>true</code> if this is part of a foreign key that has no other fields.
* @see #getFieldGroup()
*/
public boolean isForeignKey() {
final FieldGroup fieldGroup = getFieldGroup();
return fieldGroup.getKeyType() == Type.FOREIGN_KEY && fieldGroup.getSingleField() != null;
}
 
/**
* To which group this field belong.
*
* @return the group of this field.
* @see SQLTable#getFieldGroups()
*/
public FieldGroup getFieldGroup() {
return this.getTable().getFieldGroups().get(this.getName());
}
 
public final SQLTable getForeignTable() {
return this.getDBSystemRoot().getGraph().getForeignTable(this);
}
427,7 → 451,7
if (this.xml == null) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("<field name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append(JDOM2Utils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append("\" >");
sb.append(this.type.toXML());
sb.append(XMLCodecUtils.encodeSimple(this.metadata));
468,9 → 492,9
* @return a map containing properties that differs and their values.
*/
public synchronized Map<Properties, String> getDiffMap(SQLField o, SQLSystem otherSystem, boolean compareDefault) {
if (o == null)
return Collections.singletonMap(null, "other field is null");
final Map<Properties, String> res = new HashMap<Properties, String>();
if (o == null)
res.put(null, "other field is null");
if (!this.getName().equals(o.getName()))
res.put(Properties.NAME, "name unequal : " + quoteIdentifier(this.getName()) + " != " + quoteIdentifier(o.getName()));
if (!this.getType().equals(o.getType(), otherSystem))
483,8 → 507,8
}
 
private boolean defaultEquals(SQLField o) {
final SQLSyntax syntax = this.getServer().getSQLSystem().getSyntax();
final SQLSyntax oSyntax = o.getServer().getSQLSystem().getSyntax();
final SQLSyntax syntax = this.getDBSystemRoot().getSyntax();
final SQLSyntax oSyntax = o.getDBSystemRoot().getSyntax();
// don't compare actual default for auto fields, e.g. on MySQL it's null on PG it
// nextval(seq)
if (syntax.isAuto(this) && oSyntax.isAuto(o))
/trunk/OpenConcerto/src/org/openconcerto/sql/model/Trigger.java
13,17 → 13,17
package org.openconcerto.sql.model;
 
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.util.HashMap;
import java.util.Map;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
 
import org.jdom2.Element;
 
@Immutable
public final class Trigger {
 
82,7 → 82,7
public synchronized String toXML() {
// this is immutable so only compute once the XML
if (this.xml == null)
this.xml = "<trigger name=\"" + JDOMUtils.OUTPUTTER.escapeAttributeEntities(getName()) + "\">" + XMLCodecUtils.encodeSimple(this.m) + "</trigger>";
this.xml = "<trigger name=\"" + JDOM2Utils.OUTPUTTER.escapeAttributeEntities(getName()) + "\">" + XMLCodecUtils.encodeSimple(this.m) + "</trigger>";
return this.xml;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/JDBCStructureSource.java
113,7 → 113,7
 
// create metadata table here to avoid a second refresh
// null if we shouldn't alter the base
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(getBase().getServer().getSQLSystem().getSyntax());
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(getBase().getDBSystemRoot().getSyntax());
final boolean useCache = getBase().getDBSystemRoot().useCache();
Statement stmt = null;
try {
271,7 → 271,7
}
// try to find out more about those procedures
if (proceduresBySchema.size() > 0) {
final String sel = system.getSyntax().getFunctionQuery(getBase(), proceduresBySchema.keySet());
final String sel = getBase().getDBSystemRoot().getSyntax().getFunctionQuery(getBase(), proceduresBySchema.keySet());
if (sel != null) {
// don't cache since we don't listen on system tables
for (final Object o : (List) getBase().getDataSource().execute(sel, new IResultSetHandler(SQLDataSource.MAP_LIST_HANDLER, false))) {
321,7 → 321,7
@Override
protected Object getQuery(SQLBase b, TablesMap tables) {
try {
return b.getServer().getSQLSystem().getSyntax().getTriggerQuery(b, tables);
return b.getDBSystemRoot().getSyntax().getTriggerQuery(b, tables);
} catch (SQLException e) {
return e;
}
340,7 → 340,7
 
@Override
protected String getQuery(SQLBase b, TablesMap tables) {
return b.getServer().getSQLSystem().getSyntax().getColumnsQuery(b, tables);
return b.getDBSystemRoot().getSyntax().getColumnsQuery(b, tables);
}
 
@Override
357,7 → 357,7
@Override
protected Object getQuery(SQLBase b, TablesMap tables) {
try {
return b.getServer().getSQLSystem().getSyntax().getConstraints(b, tables);
return b.getDBSystemRoot().getSyntax().getConstraints(b, tables);
} catch (Exception e) {
return e;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSystem.java
155,7 → 155,10
// jdbc:h2:~/test
sep = "";
}
return s.getName() + sep + base;
// by default h2 convert database name to upper case (we used to work around it
// with SQLSystem.getMDName() but in r2251 an equalsIgnoreCase() was replaced by
// equals()) see http://code.google.com/p/h2database/issues/detail?id=204
return s.getName() + sep + base + ";DATABASE_TO_UPPER=false";
}
};
}
508,7 → 511,10
}
 
/**
* The syntax for this system.
* The default syntax for this system. NOTE : when needing a syntax for a system currently
* accessible, {@link DBSystemRoot#getSyntax()} should be used so that server options can be
* read. Otherwise constructors of {@link SQLSyntax} subclasses should be used to specify
* options.
*
* @return the syntax for this system, or <code>null</code> if none exists.
*/
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesCluster.java
360,20 → 360,16
}
 
public final StoreResult insert() throws SQLException {
return this.insert(false, false);
return this.store(StoreMode.INSERT);
}
 
public final StoreResult insert(boolean insertPK, boolean insertOrder) throws SQLException {
return this.store(new Insert(insertPK, insertOrder));
}
 
public final StoreResult store(final StoreMode mode) throws SQLException {
return this.store(mode, true);
return this.store(mode, null);
}
 
// checkValidity false useful when we want to avoid loading the graph
public final StoreResult store(final StoreMode mode, final boolean checkValidity) throws SQLException {
return this.store(mode, null, null, checkValidity);
public final StoreResult store(final StoreMode mode, final Boolean checkValidity) throws SQLException {
return this.store(mode, null, null, checkValidity, true);
}
 
/**
383,12 → 379,15
* @param start when storing a subset, the start of <code>pruneGraph</code> in this, can be
* <code>null</code>.
* @param pruneGraph the maximum graph to store, can be <code>null</code>.
* @param checkValidity <code>true</code> to check if foreign keys point to valid rows.
* @param checkValidity whether to ask for checking if foreign keys point to valid rows, see
* {@link SQLRowValues#setValidityChecked(SQLRowValues.ValidityCheck)}.
* @param fireEvent <code>false</code> if stored rows shouldn't be fetched and
* {@link SQLTableEvent} should not be fired.
* @return the store result.
* @throws SQLException if an exception occurs.
* @see {@link #prune(SQLRowValues, SQLRowValues)}
*/
public final StoreResult store(final StoreMode mode, final SQLRowValues start, final SQLRowValues pruneGraph, final boolean checkValidity) throws SQLException {
public final StoreResult store(final StoreMode mode, final SQLRowValues start, final SQLRowValues pruneGraph, final Boolean checkValidity, final boolean fireEvent) throws SQLException {
final Map<SQLRowValues, SQLRowValues> prune2orig;
final SQLRowValuesCluster toStore;
final boolean prune = pruneGraph != null;
412,7 → 411,7
}
// check validity first, avoid beginning a transaction for nothing
// do it after reset otherwise check previous values
if (checkValidity)
if (SQLRowValues.isValidityChecked(checkValidity))
for (final Node n : nodes.values()) {
n.noLink.checkValidity();
}
482,9 → 481,9
 
if (n.isStored()) {
// if there's a cycle, we have to update an already inserted row
res.add(n.update());
res.add(n.update(fireEvent));
} else {
res.add(n.store(mode));
res.add(n.store(fireEvent, mode));
final SQLRow r = n.getStoredRow();
 
// fill the noLink of referent nodes with the new ID
499,7 → 498,7
// link together the new values
// if there is a cycle not all foreign keys can be stored at the same time, so
// wait for the last DB access
if (lastDBAccess)
if (lastDBAccess) {
for (final Map.Entry<String, SQLRowValues> e : toStore.getSrc().getForeigns().entrySet()) {
final SQLRowValues foreign = nodes.get(e.getValue()).getStoredValues();
assert foreign != null : "since this the last db access for this row, all foreigns should have been inserted";
509,15 → 508,23
n.getStoredValues().put(e.getKey(), foreign);
}
}
}
// all nodes share the same graph, so pick any and freeze the graph
// null if !fireEvent or if non-rowable table
final SQLRowValues graphFetched = nodes.values().iterator().next().getStoredValues();
if (graphFetched != null)
graphFetched.getGraph().freeze();
return res;
}
});
// fire events
if (fireEvent) {
for (final SQLTableEvent n : events) {
// MAYBE put a Map<SQLRowValues, SQLTableEvent> to know how our fellow values have been
// affected
// MAYBE put a Map<SQLRowValues, SQLTableEvent> to know how our fellow values have
// been affected
n.getTable().fire(n);
}
}
 
return new StoreResult(res);
}
963,6 → 970,7
return map;
}
 
// TODO handle referents (and decide how to handle multiple paths to the same node)
final void grow(final SQLRowValues start, final SQLRowValues toGrow, final boolean checkFields) {
this.containsCheck(start);
if (!start.getTable().equals(toGrow.getTable()))
975,7 → 983,6
final SQLRowValues leaf = toGrow.assurePath(input.getPath());
if (leaf.hasID()) {
final SQLRowValuesListFetcher fetcher = new SQLRowValuesListFetcher(input.getCurrent());
fetcher.setSelID(leaf.getID());
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect input) {
985,7 → 992,7
return input;
}
});
final SQLRowValues fetched = CollectionUtils.getSole(fetcher.fetch());
final SQLRowValues fetched = fetcher.fetchOne(leaf.getIDNumber());
if (fetched == null)
throw new IllegalArgumentException("no row for " + fetcher);
leaf.load(fetched, null);
1524,12 → 1531,19
this.noLink = new SQLRowValues(vals, ForeignCopyMode.NO_COPY);
}
 
private SQLTableEvent store(StoreMode mode) throws SQLException {
private SQLTableEvent store(final boolean fetchStoredRow, StoreMode mode) throws SQLException {
return this.store(fetchStoredRow, mode, true);
}
 
private SQLTableEvent store(final boolean fetchStoredRow, StoreMode mode, final boolean setRowValues) throws SQLException {
assert !this.isStored();
return this.addEvent(mode.execOn(this.noLink));
final SQLTableEvent evt = this.addEvent(mode.execOn(this.noLink, fetchStoredRow));
if (fetchStoredRow && evt.getRow() != null && setRowValues)
evt.setRowValues(evt.getRow().asRowValues());
return evt;
}
 
private SQLTableEvent update() throws SQLException {
private SQLTableEvent update(final boolean fetchStoredRow) throws SQLException {
assert this.isStored();
 
// fields that have been updated since last store
1540,13 → 1554,15
final SQLRowValues updatingVals = this.getStoredRow().createEmptyUpdateRow();
updatingVals.load(this.noLink, fieldsToUpdate);
 
final SQLTableEvent evt = new Node(updatingVals).store(StoreMode.COMMIT);
final SQLTableEvent evt = new Node(updatingVals).store(fetchStoredRow, StoreMode.COMMIT, false);
// Update previous rowValues, and use it for the new event
// that way there's only one graph of rowValues (with the final values) for all events.
// Load all fields since updating 1 field might change the value of another (e.g.
// with a trigger).
if (fetchStoredRow && evt.getRow() != null) {
this.getStoredValues().load(evt.getRow(), null);
evt.setRowValues(this.getStoredValues());
}
return this.addEvent(evt);
}
 
1574,7 → 1590,8
}
 
private final SQLTableEvent addEvent(SQLTableEvent evt) {
assert evt != null;
if (evt == null)
throw new IllegalStateException("Couldn't update missing row " + this.noLink);
this.modif.add(evt);
return evt;
}
1591,9 → 1608,11
* @author Sylvain
*/
public static abstract class StoreMode {
abstract SQLTableEvent execOn(SQLRowValues vals) throws SQLException;
abstract SQLTableEvent execOn(SQLRowValues vals, final boolean fetchStoredRow) throws SQLException;
 
public static final StoreMode COMMIT = new Commit();
public static final StoreMode INSERT = new Insert(false, false);
public static final StoreMode INSERT_VERBATIM = new Insert(true, true);
}
 
public static class Insert extends StoreMode {
1608,20 → 1627,21
}
 
@Override
SQLTableEvent execOn(SQLRowValues vals) throws SQLException {
SQLTableEvent execOn(SQLRowValues vals, final boolean fetchStoredRow) throws SQLException {
final Set<SQLField> autoFields = new HashSet<SQLField>();
if (!this.insertPK)
autoFields.addAll(vals.getTable().getPrimaryKeys());
if (!this.insertOrder)
autoFields.add(vals.getTable().getOrderField());
return vals.insertJustThis(autoFields);
return vals.insertJustThis(fetchStoredRow, autoFields);
}
}
 
public static class Commit extends StoreMode {
 
@Override
SQLTableEvent execOn(SQLRowValues vals) throws SQLException {
return vals.commitJustThis();
SQLTableEvent execOn(SQLRowValues vals, final boolean fetchStoredRow) throws SQLException {
return vals.commitJustThis(fetchStoredRow);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTableEvent.java
145,15 → 145,13
}
 
/**
* Return the rowValues that has changed. NOTE: if this event was generated by
* {@link SQLRowValues} the result will be linked with all rows committed at the same time.
* If this event was generated by {@link SQLRowValues} the result will be linked with all rows
* committed at the same time.
*
* @return the rowValues that has changed.
* @return the frozen rowValues that has changed, <code>null</code> if not generated by
* {@link SQLRowValues}.
*/
public final SQLRowValues getRowValues() {
if (this.vals == null) {
this.vals = this.getRow().asRowValues();
}
return this.vals;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRequestLog.java
51,6 → 51,8
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
 
import net.jcip.annotations.GuardedBy;
 
public class SQLRequestLog {
 
private static final Color BG_PINK = new Color(254, 240, 240);
69,10 → 71,12
private String threadId;
private static List<ChangeListener> listeners = new ArrayList<ChangeListener>(2);
private static JLabel textInfo = new JLabel("Total: ");
@GuardedBy("EDT")
private static final DateFormat sdt = new SimpleDateFormat("HH:mm:ss.SS");
private static final DecimalFormat dformat = new DecimalFormat("##0.00");
 
private boolean isHighlighted = false;
private int rs_count;
 
private static final String format(final Object nano) {
final long l = ((Number) nano).longValue();
84,7 → 88,7
}
 
public SQLRequestLog(String query, String comment, int connectionId, long starAtMs, String ex, boolean inSwing, long startTime, long afterCache, long afterQueryInfo, long afterExecute,
long afterHandle, long endTime) {
long afterHandle, long endTime, int count) {
this.query = query;
this.comment = comment;
this.connectionId = connectionId;
95,6 → 99,7
this.afterExecute = afterExecute;
this.afterHandle = afterHandle;
this.endTime = endTime;
this.rs_count = count;
this.stack = ex;
this.inSwing = inSwing;
this.forShare = query.contains("FOR SHARE");
104,9 → 109,10
this.threadId = "[" + Thread.currentThread().getId() + "] " + Thread.currentThread().getName();
}
 
static long count = 0;
static long total_count = 0;
 
public static void log(String query, String comment, int connectionId, long starAtMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime) {
public static void log(String query, String comment, int connectionId, long starAtMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime,
int count) {
 
if (enabled) {
if (list.size() < 50000) {
113,7 → 119,7
final String ex = ExceptionUtils.getStackTrace(new Exception());
 
list.add(new SQLRequestLog(query, comment, connectionId, starAtMs, ex, SwingUtilities.isEventDispatchThread(), startTime, afterCache, afterQueryInfo, afterExecute, afterHandle,
endTime));
endTime, count));
fireEvent();
}
 
122,7 → 128,7
}
 
public static void log(String query, String comment, long starAtMs, long startTime) {
log(query, comment, 0, starAtMs, startTime, startTime, startTime, startTime, startTime, startTime);
log(query, comment, 0, starAtMs, startTime, startTime, startTime, startTime, startTime, startTime, 0);
}
 
public static void log(PreparedStatement pStmt, String comment, long timeMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime) {
129,7 → 135,7
// only call potentially expensive and/or exceptions throwing methods if necessary
if (enabled) {
try {
log(pStmt.toString(), comment, pStmt.getConnection(), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime);
log(pStmt.toString(), comment, pStmt.getConnection(), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime, 0);
} catch (Exception e) {
// never propagate exceptions
Log.get().log(Level.WARNING, "Couldn't log " + pStmt, e);
137,8 → 143,9
}
}
 
public static void log(String query, String comment, Connection conn, long timeMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime) {
log(query, comment, System.identityHashCode(conn), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime);
public static void log(String query, String comment, Connection conn, long timeMs, long startTime, long afterCache, long afterQueryInfo, long afterExecute, long afterHandle, long endTime,
int count) {
log(query, comment, System.identityHashCode(conn), timeMs, startTime, afterCache, afterQueryInfo, afterExecute, afterHandle, endTime, count);
}
 
private static void fireEvent() {
152,7 → 159,7
final long totalMs = getTotalMs();
final long totalSQLMs = getTotalSQLMs();
textInfo.setText("Total: " + totalMs + " ms, Swing: " + getTotalSwing() + " ms, SQL: " + totalSQLMs + " ms, processing: " + (totalMs - totalSQLMs) + " ms , " + getNbConnections()
+ " conn., " + getNbThread() + " threads. Total: " + list.size() + " / " + count);
+ " conn., " + getNbThread() + " threads. Total: " + list.size() + " / " + total_count);
}
});
}
474,11 → 481,11
public static synchronized void clear() {
list.clear();
fireEvent();
count = 0;
total_count = 0;
}
 
public static long getCount() {
return count;
return total_count;
}
 
public static synchronized int getSize() {
550,6 → 557,10
return this.forShare;
}
 
public int getResultCount() {
return rs_count;
}
 
public void printStack() {
System.err.println("Stacktrace of : " + this.query);
System.err.println(this.stack);
564,6 → 575,7
}
 
private static void showStack(final SQLRequestLogModel model, TableRowSorter<TableModel> sorter, int s) {
assert SwingUtilities.isEventDispatchThread();
if (s >= 0 && s < model.getRowCount()) {
final SQLRequestLog rowAt = model.getRowAt(sorter.convertRowIndexToModel(s));
rowAt.printStack();
/trunk/OpenConcerto/src/org/openconcerto/sql/model/ColumnListHandlerGeneric.java
New file
0,0 → 1,116
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.model;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
 
/**
* <code>ResultSetHandler</code> implementation that converts one <code>ResultSet</code> column into
* a {@link List}.
*/
@Immutable
public class ColumnListHandlerGeneric<T> implements ResultSetHandler {
 
@GuardedBy("cache")
static private final Map<Class<?>, ColumnListHandlerGeneric<?>> cache = new LinkedHashMap<Class<?>, ColumnListHandlerGeneric<?>>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Class<?>, ColumnListHandlerGeneric<?>> eldest) {
return this.size() > 20;
}
};
 
public static <T> ColumnListHandlerGeneric<T> create(Class<T> clz) {
synchronized (cache) {
@SuppressWarnings("unchecked")
ColumnListHandlerGeneric<T> res = (ColumnListHandlerGeneric<T>) cache.get(clz);
if (res == null) {
res = create(1, clz);
assert res != null;
cache.put(clz, res);
}
return res;
}
}
 
// syntactic sugar
public static <T> ColumnListHandlerGeneric<T> create(int columnIndex, Class<T> clz) {
return new ColumnListHandlerGeneric<T>(columnIndex, clz);
}
 
/**
* The column number to retrieve.
*/
private final int columnIndex;
 
/**
* The column name to retrieve. Either columnName or columnIndex will be used but never both.
*/
private final String columnName;
 
private final Class<T> clz;
 
public ColumnListHandlerGeneric(int columnIndex, Class<T> clz) {
this(columnIndex, null, clz);
}
 
/**
* Creates a new instance of ColumnListHandler.
*
* @param columnName The name of the column to retrieve from the <code>ResultSet</code>.
*/
public ColumnListHandlerGeneric(String columnName, Class<T> clz) {
this(-1, columnName, clz);
}
 
protected ColumnListHandlerGeneric(int columnIndex, String columnName, Class<T> clz) {
final boolean noName = columnName == null;
final boolean noIndex = columnIndex <= 0;
if (noName && noIndex)
throw new IllegalArgumentException("Missing column information");
assert noName || noIndex : "A constructor passed more than one argument";
this.columnIndex = columnIndex;
this.columnName = columnName;
this.clz = clz;
}
 
/**
* Returns one <code>ResultSet</code> column as a <code>List</code>. The elements are added to
* the <code>List</code> via {@link SQLResultSet#getValue(ResultSet, Class, int)}.
*
* @return a <code>List</code>, never <code>null</code>.
* @throws SQLException if one object couldn't be retrieved from the result set.
*/
@Override
public final List<T> handle(ResultSet rs) throws SQLException {
final List<T> result = new ArrayList<T>();
while (rs.next()) {
if (this.columnName == null) {
result.add(SQLResultSet.getValue(rs, this.clz, this.columnIndex));
} else {
result.add(SQLResultSet.getValue(rs, this.clz, this.columnName));
}
}
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRow.java
18,6 → 18,7
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
186,6 → 187,25
return res;
}
 
static final SQLRow createFromSelect(final SQLTable t, final VirtualFields vfs, final int id, final LockStrength l) {
final SQLSelect sel = new SQLSelect(true).addAllSelect(t.getFields(vfs));
sel.setLockStrength(l);
sel.setWhere(new Where(t.getKey(), "=", id));
return new SQLRow(t, id, t.getDBSystemRoot().getDataSource().execute1(sel.asString()));
}
 
/**
* Create an empty existing row (without checking the DB).
*
* @param t the table.
* @param id the ID.
* @return a new {@link #exists() existing} {@link #isFilled() filled} {@link #getFields()
* empty} row.
*/
static final SQLRow createEmpty(final SQLTable t, final int id) {
return new SQLRow(t, id, Collections.<String, Object> emptyMap());
}
 
private final int ID;
private final Number idNumber;
private Map<String, Object> values;
226,7 → 246,7
private SQLRow(SQLTable table, final Number id, Map<String, ?> values) {
this(table, id == null ? getID(values, table, false) : id);
// faire une copie, sinon backdoor pour changer les valeurs sans qu'on s'en aperçoive
this.setValues(new HashMap<String, Object>(values));
this.setValues(values == null ? null : new HashMap<String, Object>(values));
}
 
// return ID, must always be present but may be null if <code>nullAllowed</code>
428,18 → 448,6
return this.getForeignRow(fieldName);
}
 
@Override
public Number getForeignIDNumber(String fieldName) throws IllegalArgumentException {
final SQLRow foreignRow = this.getForeignRow(fieldName, SQLRowMode.NO_CHECK);
return foreignRow == null ? null : foreignRow.getIDNumber();
}
 
@Override
public boolean isForeignEmpty(String fieldName) {
final SQLRow foreignRow = this.getForeignRow(fieldName, SQLRowMode.NO_CHECK);
return foreignRow == null || foreignRow.isUndefined();
}
 
/**
* Retourne la ligne sur laquelle pointe le champ passé. Elle peut être archivé ou indéfinie.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/model/ColumnListHandler.java
14,100 → 14,57
/*
* Copyright 2004 The Apache Software Foundation
*
* 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
* 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.
* 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.openconcerto.sql.model;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
/**
* <code>ResultSetHandler</code> implementation that converts one
* <code>ResultSet</code> column into a <code>List</code> of
* <code>Object</code>s. This class is thread safe.
* <code>ResultSetHandler</code> implementation that converts one <code>ResultSet</code> column into
* a <code>List</code> of <code>Object</code>s. This class is thread safe.
*
* @see ResultSetHandler
* @since DbUtils 1.1
*/
public class ColumnListHandler implements ResultSetHandler {
public class ColumnListHandler extends ColumnListHandlerGeneric<Object> {
 
/**
* The column number to retrieve.
* Creates a new instance of ColumnListHandler. The first column of each row will be returned
* from <code>handle()</code>.
*/
private int columnIndex = 1;
 
/**
* The column name to retrieve. Either columnName or columnIndex
* will be used but never both.
*/
private String columnName = null;
 
/**
* Creates a new instance of ColumnListHandler. The first column of each
* row will be returned from <code>handle()</code>.
*/
public ColumnListHandler() {
super();
this(1);
}
 
/**
* Creates a new instance of ColumnListHandler.
*
* @param columnIndex The index of the column to retrieve from the
* <code>ResultSet</code>.
* @param columnIndex The index of the column to retrieve from the <code>ResultSet</code>.
*/
public ColumnListHandler(int columnIndex) {
this.columnIndex = columnIndex;
this(columnIndex, null);
}
 
/**
* Creates a new instance of ColumnListHandler.
*
* @param columnName The name of the column to retrieve from the
* <code>ResultSet</code>.
* @param columnName The name of the column to retrieve from the <code>ResultSet</code>.
*/
public ColumnListHandler(String columnName) {
this.columnName = columnName;
this(-1, columnName);
}
 
/**
* Returns one <code>ResultSet</code> column as a <code>List</code> of
* <code>Object</code>s. The elements are added to the <code>List</code> via
* the <code>ResultSet.getObject()</code> method.
*
* @return A <code>List</code> of <code>Object</code>s, never
* <code>null</code>.
*
* @throws SQLException
*
* @see org.apache.commons.dbutils.ResultSetHandler#handle(java.sql.ResultSet)
*/
public Object handle(ResultSet rs) throws SQLException {
 
List result = new ArrayList();
 
while (rs.next()) {
if (this.columnName == null) {
result.add(rs.getObject(this.columnIndex));
} else {
result.add(rs.getObject(this.columnName));
private ColumnListHandler(int columnIndex, String columnName) {
super(columnIndex, columnName, Object.class);
}
}
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxPG.java
23,8 → 23,8
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.io.File;
43,9 → 43,11
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.apache.commons.dbcp.DelegatingConnection;
76,10 → 78,25
 
private static final int MAX_VARCHAR_L = (MAX_FIELD_SIZE - MAX_LENGTH_BYTES) / MAX_BYTES_PER_CHAR;
 
static private final IdentityHashMap<String, String> DATE_SPECS;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>();
DATE_SPECS.put(DateProp.YEAR, "YYYY");
DATE_SPECS.put(DateProp.MONTH_NAME, "TMmonth");
DATE_SPECS.put(DateProp.MONTH_NUMBER, "MM");
DATE_SPECS.put(DateProp.DAY_IN_MONTH, "DD");
DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "TMday");
DATE_SPECS.put(DateProp.HOUR, "HH24");
DATE_SPECS.put(DateProp.MINUTE, "MI");
DATE_SPECS.put(DateProp.SECOND, "SS");
DATE_SPECS.put(DateProp.MICROSECOND, "US");
}
 
SQLSyntaxPG() {
super(SQLSystem.POSTGRESQL);
super(SQLSystem.POSTGRESQL, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Short.class, "smallint");
this.typeNames.addAll(Short.class, "smallint", "int2");
this.typeNames.addAll(Integer.class, "integer", "int", "int4");
this.typeNames.addAll(Long.class, "bigint", "int8");
this.typeNames.addAll(BigDecimal.class, "decimal", "numeric");
87,13 → 104,29
this.typeNames.addAll(Double.class, "double precision", "float8");
// since 7.3 default is without timezone
this.typeNames.addAll(Timestamp.class, "timestamp", "timestamp without time zone");
this.typeNames.addAll(java.util.Date.class, "time", "time without time zone", "date");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time", "time without time zone");
this.typeNames.addAll(Blob.class, "bytea");
this.typeNames.addAll(Clob.class, "varchar", "char", "character varying", "character", "text");
this.typeNames.addAll(String.class, "varchar", "char", "character varying", "character", "text");
}
 
static final Pattern BACKSLASH_PATTERN = Pattern.compile("\\", Pattern.LITERAL);
static final String TWO_BACKSLASH_REPLACEMENT = Matcher.quoteReplacement("\\\\");
 
@Override
public final String quoteString(String s) {
final String res = super.quoteString(s);
if (s == null)
return res;
// see PostgreSQL Documentation 4.1.2.1 String Constants
// escape \ by replacing them with \\
final Matcher matcher = BACKSLASH_PATTERN.matcher(res);
// only use escape form if needed (=> equals with other systems most of the time)
return matcher.find() ? "E" + matcher.replaceAll(TWO_BACKSLASH_REPLACEMENT) : res;
}
 
@Override
public int getMaximumIdentifierLength() {
// http://www.postgresql.org/docs/9.1/static/sql-syntax-lexical.html
return 63;
183,7 → 216,7
//
+ "WHERE ci.relkind IN ('i','') AND n.nspname <> 'pg_catalog' AND n.nspname !~ '^pg_toast'\n"
//
+ " AND n.nspname = " + t.getBase().quoteString(t.getSchema().getName()) + " AND ct.relname = " + t.getBase().quoteString(t.getName()) + "\n"
+ " AND n.nspname = " + quoteString(t.getSchema().getName()) + " AND ct.relname = " + quoteString(t.getName()) + "\n"
//
+ "ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\";";
// don't cache since we don't listen on system tables
275,7 → 308,7
 
@Override
public void _loadData(final File f, final SQLTable t) throws IOException, SQLException {
final String copy = "COPY " + t.getSQLName().quote() + " FROM STDIN " + getDataOptions(t.getBase()) + ";";
final String copy = "COPY " + t.getSQLName().quote() + " FROM STDIN " + getDataOptions() + ";";
final Number count = t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<Number, IOException>() {
@Override
public Number handle(SQLDataSource ds) throws SQLException, IOException {
299,8 → 332,8
}
}
 
private static String getDataOptions(final SQLBase b) {
return " WITH NULL " + b.quoteString("\\N") + " CSV HEADER QUOTE " + b.quoteString("\"") + " ESCAPE AS " + b.quoteString("\\");
private String getDataOptions() {
return " WITH NULL " + quoteString("\\N") + " CSV HEADER QUOTE " + quoteString("\"") + " ESCAPE AS " + quoteString("\\");
}
 
@Override
317,7 → 350,7
});
// you can't specify line separator to pg, so use STDOUT as it always use \n
try {
final String sql = "COPY (" + selectAll(t).asString() + ") to STDOUT " + getDataOptions(t.getBase()) + " FORCE QUOTE " + cols + " ;";
final String sql = "COPY (" + selectAll(t).asString() + ") to STDOUT " + getDataOptions() + " FORCE QUOTE " + cols + " ;";
t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<Number, IOException>() {
@Override
public Number handle(SQLDataSource ds) throws SQLException, IOException {
361,16 → 394,31
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "EXTRACT(DOW from " + sqlTS + ") + 1";
}
 
@Override
public String getMonth(String sqlTS) {
return "EXTRACT(MONTH from " + sqlTS + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
return "to_char(cast(" + sqlTS + " as timestamp), " + SQLBase.quoteStringStd(basic ? "YYYYMMDD\"T\"HH24MISS.US" : "YYYY-MM-DD\"T\"HH24:MI:SS.US") + ")";
return this.getFormatTimestamp(sqlTS, SQLBase.quoteStringStd(basic ? "YYYYMMDD\"T\"HH24MISS.US" : "YYYY-MM-DD\"T\"HH24:MI:SS.US"));
}
 
@Override
public SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new PGSQLBase(server, name, systemRootInit, login, pass, dsInit);
public String getFormatTimestamp(String sqlTS, String nativeFormat) {
return "to_char(" + sqlTS + ", " + nativeFormat + ")";
}
 
@Override
public String quoteForTimestampFormat(String text) {
return StringUtils.doubleQuote(text, false);
}
 
@Override
public final String getCreateSynonym(final SQLTable t, final SQLName newName) {
String res = super.getCreateSynonym(t, newName);
 
406,7 → 454,7
@Override
public String getFunctionQuery(SQLBase b, Set<String> schemas) {
return "SELECT ROUTINE_SCHEMA as \"schema\", ROUTINE_NAME as \"name\", ROUTINE_DEFINITION as \"src\" FROM \"information_schema\".ROUTINES where ROUTINE_CATALOG='" + b.getMDName()
+ "' and ROUTINE_SCHEMA in (" + quoteStrings(b, schemas) + ")";
+ "' and ROUTINE_SCHEMA in (" + quoteStrings(schemas) + ")";
}
 
@Override
419,7 → 467,7
// schema
"INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" +
// requested tables
getTablesMapJoin(b, tables, "n.nspname", "c.relname") +
getTablesMapJoin(tables, "n.nspname", "c.relname") +
// where
"\nwhere not t." + (b.getVersion()[0] >= 9 ? "tgisinternal" : "tgisconstraint");
}
429,7 → 477,7
return "SELECT TABLE_SCHEMA as \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", TABLE_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", COLUMN_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
+ "\" , CHARACTER_SET_NAME as \"CHARACTER_SET_NAME\", COLLATION_NAME as \"COLLATION_NAME\" from INFORMATION_SCHEMA.COLUMNS\n" +
// requested tables
getTablesMapJoin(b, tables, "TABLE_SCHEMA", "TABLE_NAME");
getTablesMapJoin(tables, "TABLE_SCHEMA", "TABLE_NAME");
}
 
@Override
442,7 → 490,7
+ "from pg_catalog.pg_constraint c\n" + "join pg_namespace nsp on nsp.oid = c.connamespace\n" + "left join pg_class rel on rel.oid = c.conrelid\n"
+ "left join pg_attribute att on att.attrelid = c.conrelid and att.attnum = ANY(c.conkey)\n"
// requested tables
+ getTablesMapJoin(b, tables, "nsp.nspname", "rel.relname")
+ getTablesMapJoin(tables, "nsp.nspname", "rel.relname")
// order
+ "\norder by nsp.nspname, rel.relname, c.conname";
// don't cache since we don't listen on system tables
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSelect.java
208,6 → 208,10
return getSystemRoot().getServer().getSQLSystem();
}
 
public final SQLSyntax getSyntax() {
return getSystemRoot().getSyntax();
}
 
public String asString() {
final SQLSystem sys = this.getSQLSystem();
 
231,7 → 235,7
archive = Where.and(getUndefWhere(fromTable, alias), archive);
}
// archive == null si pas d'archive et pas d'undefined
if (archive != null && archive.getClause() != "") {
if (archive != null) {
result.append("\n WHERE ");
result.append(archive.getClause());
}
430,19 → 434,22
}
 
/**
* Add an ORDER BY {@link SQLTable#getOrderField() t.ORDER}.
* Add an ORDER BY for the passed table.
*
* @param t the table.
* @param fieldMustExist if <code>true</code> then <code>t</code> must be
* {@link SQLTable#isOrdered() ordered}.
* {@link SQLTable#isOrdered() ordered} or have a {@link SQLTable#isRowable() numeric
* primary key}.
* @return this.
* @throws IllegalArgumentException if <code>t</code> isn't ordered and <code>mustExist</code>
* is <code>true</code>.
* @throws IllegalArgumentException if <code>t</code> has no usable order field and
* <code>mustExist</code> is <code>true</code>.
*/
public SQLSelect addOrder(final TableRef t, final boolean fieldMustExist) {
final SQLField orderField = t.getTable().getOrderField();
if (orderField != null)
this.addFieldOrder(t.getField(orderField.getName()));
else if (t.getTable().isRowable())
this.addFieldOrder(t.getKey());
else if (fieldMustExist)
throw new IllegalArgumentException("table is not ordered : " + t);
return this;
977,6 → 984,14
return current;
}
 
public final FieldRef followFieldPath(final IFieldPath fp) {
return this.followFieldPath(fp.getPath().getFirst().getAlias(), fp);
}
 
public final FieldRef followFieldPath(final String tableAlias, final IFieldPath fp) {
return this.followPath(tableAlias, fp.getPath()).getField(fp.getFieldName());
}
 
public boolean isExcludeUndefined() {
return this.generalExcludeUndefined;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValues.java
17,7 → 17,6
import org.openconcerto.sql.model.SQLRowValuesCluster.ValueChangeListener;
import org.openconcerto.sql.model.SQLTable.FieldGroup;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
34,7 → 33,6
import org.openconcerto.utils.CopyUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.Tuple2;
44,6 → 42,8
import org.openconcerto.utils.cc.LinkedIdentitySet;
import org.openconcerto.utils.cc.TransformedMap;
import org.openconcerto.utils.convertor.NumberConvertor;
import org.openconcerto.utils.convertor.ValueConvertor;
import org.openconcerto.utils.convertor.ValueConvertorFactory;
 
import java.math.BigDecimal;
import java.sql.Connection;
56,7 → 56,6
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
70,6 → 69,8
import java.util.Map.Entry;
import java.util.Set;
 
import net.jcip.annotations.GuardedBy;
 
/**
* A class that represent a row of a table that can be modified before being inserted or updated.
* The row might not actually exists in the database, and it might not define all the fields. One
149,21 → 150,77
}
};
 
private static boolean checkValidity = true;
static public enum ValidityCheck {
/**
* The check is never performed.
*/
FORBIDDEN {
@Override
public boolean shouldCheck(final Boolean asked) {
return false;
}
},
/**
* The check is only performed if requested.
*/
FALSE_BY_DEFAULT {
@Override
public boolean shouldCheck(Boolean asked) {
return asked == null ? false : asked.booleanValue();
}
},
/**
* The check is performed unless specified.
*/
TRUE_BY_DEFAULT {
@Override
public boolean shouldCheck(Boolean asked) {
return asked == null ? true : asked.booleanValue();
}
},
/**
* The check is always performed. This is not generally recommended as some methods of the
* framework will fail.
*/
FORCED {
@Override
public boolean shouldCheck(Boolean asked) {
return true;
}
};
 
public static void setValidityChecked(final boolean b) {
checkValidity = b;
public abstract boolean shouldCheck(final Boolean asked);
}
 
@GuardedBy("this")
private static ValidityCheck checkValidity;
 
/**
* Whether or not {@link #getInvalid()} is called before each data modification.
* Set how {@link #getInvalid()} should be called before each data modification. Initially set
* to {@link ValidityCheck#TRUE_BY_DEFAULT}. NOTE : that the check also makes sure that
* referenced rows are not archived, so if it isn't performed a row could point to an archived
* row.
*
* @param vc the new mode, <code>null</code> to set the default.
*/
public synchronized static void setValidityChecked(final ValidityCheck vc) {
checkValidity = vc == null ? ValidityCheck.TRUE_BY_DEFAULT : vc;
}
 
/**
* Whether or not {@link #getInvalid()} should be called.
*
* @param asked what the caller requested.
* @return <code>true</code> if the validity is checked.
*/
public static boolean isValidityChecked() {
return checkValidity;
public synchronized static boolean isValidityChecked(final Boolean asked) {
return checkValidity.shouldCheck(asked);
}
 
static {
setValidityChecked(null);
}
 
private static final boolean DEFAULT_ALLOW_BACKTRACK = true;
 
// i.e. no re-hash for up to 6 entries (8*0.8=6.4)
688,45 → 745,6
}
}
 
/**
* Returns the foreign table of <i>fieldName</i>.
*
* @param fieldName the name of a foreign field, e.g. "ID_ARTICLE_2".
* @return the table the field points to (never <code>null</code>), e.g. |ARTICLE|.
* @throws IllegalArgumentException if <i>fieldName</i> is not a foreign field.
*/
private final SQLTable getForeignTable(String fieldName) throws IllegalArgumentException {
return this.getForeignLink(Collections.singletonList(fieldName)).getTarget();
}
 
private final Link getForeignLink(final List<String> fieldsNames) throws IllegalArgumentException {
final DatabaseGraph graph = this.getTable().getDBSystemRoot().getGraph();
final Link foreignLink = graph.getForeignLink(this.getTable(), fieldsNames);
if (foreignLink == null)
throw new IllegalArgumentException(fieldsNames + " are not a foreign key of " + this.getTable());
return foreignLink;
}
 
@Override
public boolean isForeignEmpty(String fieldName) {
// don't use getForeign() to avoid creating a SQLRow
// keep getForeignTable at the 1st line since it does the check
final SQLTable foreignTable = this.getForeignTable(fieldName);
final Object val = this.getContainedObject(fieldName);
if (val instanceof SQLRowValues) {
return ((SQLRowValues) val).isUndefined();
} else {
final Number undefID = foreignTable.getUndefinedIDNumber();
return NumberUtils.areNumericallyEqual((Number) val, undefID);
}
}
 
@Override
public Number getForeignIDNumber(String fieldName) throws IllegalArgumentException {
final SQLRowAccessor foreign = getForeign(fieldName);
return foreign == null ? null : foreign.getIDNumber();
}
 
public boolean isDefault(String fieldName) {
return SQL_DEFAULT.equals(this.getObject(fieldName));
}
810,7 → 828,9
final Map<String, FieldGroup> fieldGroups = getTable().getFieldGroups();
for (final String fieldName : toRm) {
if (fieldGroups.get(fieldName).getKeyType() == Type.FOREIGN_KEY)
this._put(fieldName, null);
// name is OK since it is a foreign key
// value null is also OK
this._put(fieldName, null, false, ValueOperation.CHECK);
}
if (fields == null && !protectGraph) {
assert !retain && toRm.equals(this.values.keySet());
931,11 → 951,12
return this.put(fieldName, value, true);
}
 
// table.contains() can take up to 35% of this method
SQLRowValues put(String fieldName, Object value, final boolean checkName) {
if (checkName && !this.getTable().contains(fieldName))
throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
_put(fieldName, value);
SQLRowValues put(String fieldName, Object value, final boolean check) {
return this.put(fieldName, value, check, check ? ValueOperation.CONVERT : ValueOperation.PASS);
}
 
SQLRowValues put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
_put(fieldName, value, checkName, checkValue);
// if there's no graph, there can't be any listeners
final SQLRowValuesCluster graph = this.getGraph(false);
if (graph != null)
943,42 → 964,47
return this;
}
 
private void _put(String fieldName, Object value) {
if (value == SQL_EMPTY_LINK)
// keep getForeignTable since it does the check
value = this.getForeignTable(fieldName).getUndefinedIDNumber();
// use assertion since check() is not perfect
assert check(fieldName, value);
checkFrozen();
this.updateLinks(fieldName, this.values.put(fieldName, value), value);
static public enum ValueOperation {
CONVERT, CHECK, PASS
}
 
private boolean check(String fieldName, Object value) {
if (value == null || value == SQL_DEFAULT)
return true;
final SQLField field = getTable().getField(fieldName);
final Class<?> javaType = field.getType().getJavaType();
// createStatement() does some conversion so don't be too strict
if (value instanceof Number) {
checkGroup(Number.class, value, field, javaType);
} else if (value instanceof Date) {
checkGroup(Date.class, value, field, javaType);
} else if (value instanceof SQLRowValues) {
if (!getTable().getForeignKeys().contains(field))
throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
} else if (!javaType.isInstance(value))
throw new IllegalArgumentException("Wrong type for " + field + ", expected " + javaType + " but got " + value.getClass());
return true;
// TODO support java.time.LocalDateTime in Java 8
static private <T, U> U convert(final Class<T> source, final Object value, final Class<U> dest) {
final ValueConvertor<T, U> conv = ValueConvertorFactory.find(source, dest);
if (conv == null)
throw new IllegalArgumentException("No convertor to " + dest + " from " + source);
assert source == value.getClass();
@SuppressWarnings("unchecked")
final T tVal = (T) value;
return conv.convert(tVal);
}
 
private void checkGroup(final Class<?> superClass, final Object value, final SQLField field, final Class<?> javaType) {
if (superClass.isInstance(value)) {
if (!superClass.isAssignableFrom(javaType))
throw new IllegalArgumentException("Incompatible type for " + field + ", expected " + javaType + " but got " + value.getClass() + "(" + superClass + ")");
private void _put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
// table.contains() can take up to 35% of this method
if (checkName && !this.getTable().contains(fieldName))
throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
if (value == SQL_EMPTY_LINK) {
// keep getForeignTable since it does the check
value = this.getForeignTable(fieldName).getUndefinedIDNumber();
} else if (value != null && value != SQL_DEFAULT && checkValue != ValueOperation.PASS) {
final SQLField field = this.getTable().getField(fieldName);
if (value instanceof SQLRowValues) {
if (!field.isForeignKey())
throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
} else {
throw new IllegalStateException("value is not an instance of the superclass for " + field);
final Class<?> javaType = field.getType().getJavaType();
if (!javaType.isInstance(value)) {
if (checkValue == ValueOperation.CONVERT) {
value = convert(value.getClass(), value, javaType);
} else {
throw new IllegalArgumentException("Wrong type for " + fieldName + ", expected " + javaType + " but got " + value.getClass());
}
}
}
}
checkFrozen();
this.updateLinks(fieldName, this.values.put(fieldName, value), value);
}
 
public SQLRowValues put(String fieldName, int value) {
return this.put(fieldName, Integer.valueOf(value));
1252,7 → 1278,8
toLoad.keySet().removeAll(this.getFields());
}
for (final Map.Entry<String, ?> e : toLoad.entrySet()) {
this._put(e.getKey(), e.getValue());
// names are checked at the start
this._put(e.getKey(), e.getValue(), false, ValueOperation.CONVERT);
}
// if there's no graph, there can't be any listeners
final SQLRowValuesCluster graph = this.getGraph(false);
1737,9 → 1764,8
// *** modify
 
void checkValidity() {
if (!checkValidity)
return;
// this checks archived which the DB doesn't with just foreign constraints
// it also locks foreign rows so that they don't *become* archived
final Object[] pb = this.getInvalid();
if (pb != null)
throw new IllegalStateException("can't update " + this + " : the field " + pb[0] + " points to " + pb[1]);
1756,16 → 1782,15
* </ol>
*/
public Object[] getInvalid() {
final Map<SQLField, Link> foreignLinks = new HashMap<SQLField, Link>();
final Map<String, Link> foreignLinks = new HashMap<String, Link>();
for (final Link foreignLink : this.getTable().getForeignLinks()) {
for (final SQLField f : foreignLink.getFields()) {
for (final String f : foreignLink.getCols()) {
foreignLinks.put(f, foreignLink);
}
}
 
for (final String fieldName : this.values.keySet()) {
final SQLField field = this.getTable().getField(fieldName);
final Link foreignLink = foreignLinks.remove(field);
final Link foreignLink = foreignLinks.remove(fieldName);
if (foreignLink != null) {
final SQLTable foreignTable = foreignLink.getTarget();
if (foreignTable.isRowable()) {
1805,7 → 1830,7
*/
public SQLRow insert() throws SQLException {
// remove unwanted fields, keep ARCHIVE
return this.insert(false, false);
return this.store(SQLRowValuesCluster.StoreMode.INSERT);
}
 
/**
1817,14 → 1842,18
* @throws IllegalStateException if the ID of the new line cannot be retrieved.
*/
public SQLRow insertVerbatim() throws SQLException {
return this.insert(true, true);
return this.store(SQLRowValuesCluster.StoreMode.INSERT_VERBATIM);
}
 
public SQLRow insert(final boolean insertPK, final boolean insertOrder) throws SQLException {
return this.getGraph().store(new SQLRowValuesCluster.Insert(insertPK, insertOrder)).getStoredRow(this);
return this.store(new SQLRowValuesCluster.Insert(insertPK, insertOrder));
}
 
SQLTableEvent insertJustThis(final Set<SQLField> autoFields) throws SQLException {
public SQLRow store(final SQLRowValuesCluster.StoreMode mode) throws SQLException {
return this.getGraph().store(mode).getStoredRow(this);
}
 
SQLTableEvent insertJustThis(final boolean fetchStoredRow, final Set<SQLField> autoFields) throws SQLException {
final Map<String, Object> copy = this.clearFields(new HashMap<String, Object>(this.values), autoFields);
 
try {
1846,7 → 1875,7
assert this.getTable().isRowable() == (fieldsAndID.get1() != null);
if (this.getTable().isRowable()) {
// pour pouvoir avoir les valeurs des champs non précisés
return new SQLTableEvent(getChangedRow(fieldsAndID.get1().intValue()), Mode.ROW_ADDED, fieldsAndID.get0());
return new SQLTableEvent(getEventRow(fieldsAndID.get1().intValue(), fetchStoredRow), Mode.ROW_ADDED, fieldsAndID.get0());
} else
return new SQLTableEvent(getTable(), SQLRow.NONEXISTANT_ID, Mode.ROW_ADDED, fieldsAndID.get0());
} catch (SQLException e) {
1854,12 → 1883,19
}
}
 
private SQLRow getChangedRow(final int newID) {
private SQLRow getEventRow(final int newID, final boolean fetch) {
final SQLRow res;
if (fetch) {
// don't read the cache since no event has been fired yet
// don't write to it since the transaction isn't committed yet, so other threads
// should not see the new values.
return new SQLRow(getTable(), newID).fetchValues(false);
res = new SQLRow(getTable(), newID).fetchValues(false);
} else {
res = SQLRow.createEmpty(getTable(), newID);
}
assert res.isFilled();
return res;
}
 
// * update
 
1878,11 → 1914,12
/**
* Permet de mettre à jour une ligne existante avec les valeurs courantes.
*
* @param fetchStoredRow <code>true</code> to fetch the just stored row.
* @param id l'id à mettre à jour.
* @return the updated row.
* @throws SQLException si pb lors de la maj.
*/
SQLTableEvent updateJustThis(final int id) throws SQLException {
SQLTableEvent updateJustThis(boolean fetchStoredRow, final int id) throws SQLException {
if (id == this.getTable().getUndefinedID()) {
throw new IllegalArgumentException("can't update undefined with " + this);
}
1901,17 → 1938,19
final Tuple2<PreparedStatement, List<String>> pStmt = createUpdateStatement(getTable(), updatedValues, id);
final long timeMs = System.currentTimeMillis();
final long time = System.nanoTime();
pStmt.get0().executeUpdate();
final int updateCount = pStmt.get0().executeUpdate();
final long afterExecute = System.nanoTime();
// logging after closing fails to get the connection info
SQLRequestLog.log(pStmt.get0(), "rowValues.update()", timeMs, time, afterExecute, afterExecute, afterExecute, afterExecute, System.nanoTime());
pStmt.get0().close();
return pStmt.get1();
if (updateCount > 1)
throw new IllegalStateException(updateCount + " rows updated with ID " + id);
return updateCount == 0 ? null : pStmt.get1();
}
});
}
 
return new SQLTableEvent(getChangedRow(id), Mode.ROW_UPDATED, updatedCols);
return updatedCols == null ? null : new SQLTableEvent(getEventRow(id, fetchStoredRow), Mode.ROW_UPDATED, updatedCols);
}
 
// * commit
1924,14 → 1963,14
* @throws SQLException
*/
public SQLRow commit() throws SQLException {
return this.getGraph().store(SQLRowValuesCluster.StoreMode.COMMIT).getStoredRow(this);
return this.store(SQLRowValuesCluster.StoreMode.COMMIT);
}
 
SQLTableEvent commitJustThis() throws SQLException {
SQLTableEvent commitJustThis(boolean fetchStoredRow) throws SQLException {
if (!hasID()) {
return this.insertJustThis(Collections.<SQLField> emptySet());
return this.insertJustThis(fetchStoredRow, Collections.<SQLField> emptySet());
} else
return this.updateJustThis(this.getID());
return this.updateJustThis(fetchStoredRow, this.getID());
}
 
/**
2237,10 → 2276,6
} else
toIns = value;
// sql index start at 1
if (toIns instanceof Date) {
// to convert from java.util to java.sql, needed for pg and MS
pStmt.setObject(i + 1, new Timestamp(((Date) toIns).getTime()));
} else
pStmt.setObject(i + 1, toIns);
i++;
}
2349,8 → 2384,8
* @throws SQLException if an error occurs while inserting.
*/
@SuppressWarnings("unchecked")
public static final Insertion<Object[]> insert(final SQLTable t, final String sql) throws SQLException {
return (Insertion<Object[]>) insert(t, sql, ReturnMode.ALL_FIELDS);
public static final Insertion<List<Object>> insert(final SQLTable t, final String sql) throws SQLException {
return (Insertion<List<Object>>) insert(t, sql, ReturnMode.ALL_FIELDS);
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFunctionField.java
New file
0,0 → 1,76
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.model;
 
/**
* A field and a function in an SQL statement, such as 'sum(obs.CONSTAT)'.
*
* @author vincent
*/
public class SQLFunctionField implements FieldRef {
 
public static class SQLFunction {
public static final SQLFunction LOWER = new SQLFunction("LOWER");
public static final SQLFunction UPPER = new SQLFunction("UPPER");
public static final SQLFunction COUNT = new SQLFunction("COUNT");
public static final SQLFunction SUM = new SQLFunction("SUM");
 
private final String sqlString;
 
public SQLFunction(final String sqlString) {
this.sqlString = sqlString;
}
 
public String getSQL() {
return this.sqlString;
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " " + this.getSQL();
}
}
 
private final SQLFunction function;
private final FieldRef field;
 
public SQLFunctionField(final SQLFunction function, final FieldRef field) {
this.function = function;
this.field = field;
}
 
public SQLFunction getFunction() {
return this.function;
}
 
@Override
public SQLField getField() {
return this.field.getField();
}
 
@Override
public String getAlias() {
return this.field.getAlias();
}
 
@Override
public String getFieldRef() {
return this.getFunction().getSQL() + "(" + this.field.getFieldRef() + ")";
}
 
@Override
public TableRef getTableRef() {
return this.field.getTableRef();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLResultSet.java
40,15 → 40,90
import org.apache.commons.collections.map.LazyMap;
 
/**
* A resultSet that wraps onto another one, caching name to index translation, and using a
* ResultSetFullnameHelper.
* A resultSet that wraps onto another one, caching name to index translation,
* and using a ResultSetFullnameHelper.
*
* @author Sylvain
*/
public class SQLResultSet implements ResultSet {
 
static public final <T> T getValue(final ResultSet rs, final Class<T> clz, final int columnIndex)
throws SQLException {
final Object res;
if (clz == Object.class)
res = rs.getObject(columnIndex);
else if (Integer.class.isAssignableFrom(clz))
res = rs.getInt(columnIndex);
else if (Long.class.isAssignableFrom(clz))
res = rs.getLong(columnIndex);
else if (String.class.isAssignableFrom(clz))
res = rs.getString(columnIndex);
else if (Boolean.class.isAssignableFrom(clz))
res = rs.getBoolean(columnIndex);
else if (java.sql.Date.class.isAssignableFrom(clz))
res = rs.getDate(columnIndex);
else if (java.util.Date.class.isAssignableFrom(clz))
res = rs.getTimestamp(columnIndex);
else if (Short.class.isAssignableFrom(clz))
res = rs.getShort(columnIndex);
else if (Byte.class.isAssignableFrom(clz))
res = rs.getByte(columnIndex);
else if (Double.class.isAssignableFrom(clz))
res = rs.getDouble(columnIndex);
else if (Float.class.isAssignableFrom(clz))
res = rs.getFloat(columnIndex);
else if (Time.class.isAssignableFrom(clz))
res = rs.getTime(columnIndex);
else
res = rs.getObject(columnIndex);
return clz.cast(res);
}
 
static public final <T> T getValue(final ResultSet rs, final Class<T> clz, final String columnLabel)
throws SQLException {
final Object res;
if (clz == Object.class)
res = rs.getObject(columnLabel);
else if (Integer.class.isAssignableFrom(clz))
res = rs.getInt(columnLabel);
else if (Long.class.isAssignableFrom(clz))
res = rs.getLong(columnLabel);
else if (String.class.isAssignableFrom(clz))
res = rs.getString(columnLabel);
else if (Boolean.class.isAssignableFrom(clz))
res = rs.getBoolean(columnLabel);
else if (java.sql.Date.class.isAssignableFrom(clz))
res = rs.getDate(columnLabel);
else if (java.util.Date.class.isAssignableFrom(clz))
res = rs.getTimestamp(columnLabel);
else if (Short.class.isAssignableFrom(clz))
res = rs.getShort(columnLabel);
else if (Byte.class.isAssignableFrom(clz))
res = rs.getByte(columnLabel);
else if (Double.class.isAssignableFrom(clz))
res = rs.getDouble(columnLabel);
else if (Float.class.isAssignableFrom(clz))
res = rs.getFloat(columnLabel);
else if (Time.class.isAssignableFrom(clz))
res = rs.getTime(columnLabel);
else
res = rs.getObject(columnLabel);
return clz.cast(res);
}
 
static public final int getRowProcessedCount(final ResultSet rs) {
if (rs instanceof SQLResultSet) {
return ((SQLResultSet) rs).getRowProcessedCount();
} else {
// ResultSet.getRow() always return 0 after the last row
return 0;
}
}
 
private final ResultSet delegate;
private final ResultSetFullnameHelper helper;
private final Map indexes;
private int rowProcessedCount;
 
public SQLResultSet(ResultSet delegate) {
this.delegate = delegate;
421,9 → 496,14
}
 
public boolean next() throws SQLException {
rowProcessedCount++;
return getDelegate().next();
}
 
public int getRowProcessedCount() {
return rowProcessedCount;
}
 
public boolean previous() throws SQLException {
return getDelegate().previous();
}
849,4 → 929,15
public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
getDelegate().updateSQLXML(columnLabel, xmlObject);
}
 
@Override
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
return getDelegate().getObject(columnIndex, type);
}
 
@Override
public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
return getDelegate().getObject(columnLabel, type);
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/DatabaseGraph.java
16,7 → 16,7
*/
package org.openconcerto.sql.model.graph;
 
import static org.openconcerto.xml.JDOMUtils.OUTPUTTER;
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
import static java.util.Collections.singletonList;
 
import org.openconcerto.sql.Log;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/DirectedEdge.java
13,10 → 13,10
package org.openconcerto.sql.model.graph;
 
import org.jgrapht.Graph;
 
import net.jcip.annotations.ThreadSafe;
 
import org.jgrapht.Graph;
 
@ThreadSafe
public class DirectedEdge<V> {
 
51,17 → 51,22
}
 
@Override
public boolean equals(Object other) {
if (!(other instanceof DirectedEdge)) {
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
}
final DirectedEdge<?> o = (DirectedEdge<?>) other;
final DirectedEdge<?> o = (DirectedEdge<?>) obj;
return this.getSource().equals(o.getSource()) && this.getTarget().equals(o.getTarget());
}
 
@Override
public int hashCode() {
return this.getSource().hashCode() + this.getTarget().hashCode();
final int prime = 31;
int result = 1;
result = prime * result + this.getSource().hashCode();
result = prime * result + this.getTarget().hashCode();
return result;
}
 
public static final <V, E extends DirectedEdge<V>> void addEdge(Graph<V, E> g, E e) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/graph/Link.java
16,7 → 16,7
*/
package org.openconcerto.sql.model.graph;
 
import static org.openconcerto.xml.JDOMUtils.OUTPUTTER;
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
import static java.util.Collections.singletonList;
 
import org.openconcerto.sql.model.SQLField;
48,7 → 48,7
* @author ILM Informatique 13 mai 2004
*/
@ThreadSafe
public class Link extends DirectedEdge<SQLTable> {
public final class Link extends DirectedEdge<SQLTable> {
 
public static enum Direction {
FOREIGN {
282,16 → 282,21
 
@Override
public boolean equals(Object other) {
if (!(other instanceof Link)) {
if (this == other)
return true;
if (other == null || getClass() != other.getClass())
return false;
}
Link o = (Link) other;
final Link o = (Link) other;
return this.getFields().equals(o.getFields()) && this.getRefFields().equals(o.getRefFields()) && this.getUpdateRule() == o.getUpdateRule() && this.getDeleteRule() == o.getDeleteRule();
}
 
@Override
public int hashCode() {
return this.getFields().hashCode() + this.getRefFields().hashCode();
final int prime = 31;
int result = 1;
result = prime * result + this.getFields().hashCode();
result = prime * result + this.getRefFields().hashCode();
return result;
}
 
// instead of using SQLField, this class only uses String and SQLName
351,7 → 356,7
pWriter.write("\"");
}
 
static Link fromXML(final SQLTable t, final Element linkElem) {
static public Link fromXML(final SQLTable t, final Element linkElem) {
final SQLName to = SQLName.parse(linkElem.getAttributeValue("to"));
final SQLTable foreignTable = t.getDBSystemRoot().getDesc(to, SQLTable.class);
final String linkName = linkElem.getAttributeValue("name");
/trunk/OpenConcerto/src/org/openconcerto/sql/model/Constraint.java
15,7 → 15,7
 
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.utils.cc.HashingStrategy;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.JDOM2Utils;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.util.HashMap;
22,11 → 22,11
import java.util.List;
import java.util.Map;
 
import org.jdom2.Element;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.Immutable;
 
import org.jdom2.Element;
 
@Immutable
public final class Constraint {
 
89,7 → 89,7
public synchronized String toXML() {
// this is immutable so only compute once the XML
if (this.xml == null)
this.xml = "<constraint name=\"" + JDOMUtils.OUTPUTTER.escapeAttributeEntities(getName()) + "\" >" + XMLCodecUtils.encodeSimple(this.m) + "</constraint>";
this.xml = "<constraint name=\"" + JDOM2Utils.OUTPUTTER.escapeAttributeEntities(getName()) + "\" >" + XMLCodecUtils.encodeSimple(this.m) + "</constraint>";
return this.xml;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSearchMode.java
13,35 → 13,66
package org.openconcerto.sql.model;
 
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
 
public abstract class SQLSearchMode {
 
static public final SQLSearchMode EQUALS = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " = " + r.getBase().quoteString(term);
public String generateSQL(final SQLSyntax s, final String term) {
return " = " + s.quoteString(term);
}
};
 
static public final SQLSearchMode CONTAINS = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " like " + r.getBase().quoteString("%" + SQLSyntax.get(r).getLitteralLikePattern(term) + "%");
public List<SQLSearchMode> getHigherModes() {
return Collections.emptyList();
}
};
static public final SQLSearchMode STARTS_WITH = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " like " + r.getBase().quoteString(SQLSyntax.get(r).getLitteralLikePattern(term) + "%");
public String generateSQL(final SQLSyntax s, final String term) {
return " like " + s.quoteString(s.getLitteralLikePattern(term) + "%");
}
 
@Override
public List<SQLSearchMode> getHigherModes() {
return Collections.singletonList(EQUALS);
}
};
 
static public final SQLSearchMode ENDS_WITH = new SQLSearchMode() {
@Override
public String generateSQL(final DBRoot r, final String term) {
return " like " + r.getBase().quoteString("%" + SQLSyntax.get(r).getLitteralLikePattern(term));
public String generateSQL(final SQLSyntax s, final String term) {
return " like " + s.quoteString("%" + s.getLitteralLikePattern(term));
}
 
@Override
public List<SQLSearchMode> getHigherModes() {
return Collections.singletonList(EQUALS);
}
};
 
public abstract String generateSQL(final DBRoot r, final String term);
private static final List<SQLSearchMode> CONTAINS_HIGHER_MODES = Arrays.asList(EQUALS, STARTS_WITH);
static public final SQLSearchMode CONTAINS = new SQLSearchMode() {
 
@Override
public String generateSQL(final SQLSyntax s, final String term) {
return " like " + s.quoteString("%" + s.getLitteralLikePattern(term) + "%");
}
 
@Override
public List<SQLSearchMode> getHigherModes() {
return CONTAINS_HIGHER_MODES;
}
};
 
public final String generateSQL(final DBRoot r, final String term) {
return this.generateSQL(SQLSyntax.get(r), term);
}
 
public abstract String generateSQL(final SQLSyntax s, final String term);
 
// from highest to lowest
public abstract List<SQLSearchMode> getHigherModes();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMySQL.java
26,7 → 26,6
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
 
import java.io.BufferedReader;
48,6 → 47,8
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
63,12 → 64,50
*
* @author Sylvain CUAZ
*/
class SQLSyntaxMySQL extends SQLSyntax {
public class SQLSyntaxMySQL extends SQLSyntax {
 
private static final Pattern INT_PATTERN = Pattern.compile("(bigint|smallint|int)");
final static SQLSyntax create(DBSystemRoot sysRoot) {
final boolean noBackslashEscapes;
if (sysRoot == null) {
noBackslashEscapes = false;
} else {
final String modes = (String) sysRoot.getDataSource().executeScalar("SELECT @@global.sql_mode;");
noBackslashEscapes = modes.contains("NO_BACKSLASH_ESCAPES");
}
return new SQLSyntaxMySQL(noBackslashEscapes);
}
 
SQLSyntaxMySQL() {
super(SQLSystem.MYSQL);
static private final IdentityHashMap<String, String> DATE_SPECS;
static private final Map<Class<?>, String> CAST_TYPES;
 
static {
DATE_SPECS = new IdentityHashMap<String, String>();
DATE_SPECS.put(DateProp.YEAR, "%Y");
DATE_SPECS.put(DateProp.MONTH_NAME, "%M");
DATE_SPECS.put(DateProp.MONTH_NUMBER, "%m");
DATE_SPECS.put(DateProp.DAY_IN_MONTH, "%d");
DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "%W");
DATE_SPECS.put(DateProp.HOUR, "%H");
DATE_SPECS.put(DateProp.MINUTE, "%i");
DATE_SPECS.put(DateProp.SECOND, "%S");
DATE_SPECS.put(DateProp.MICROSECOND, "%f");
CAST_TYPES = new HashMap<Class<?>, String>();
CAST_TYPES.put(Short.class, "signed integer");
CAST_TYPES.put(Integer.class, "signed integer");
CAST_TYPES.put(Long.class, "signed integer");
CAST_TYPES.put(BigDecimal.class, "decimal");
CAST_TYPES.put(Timestamp.class, "datetime");
CAST_TYPES.put(java.sql.Date.class, "date");
CAST_TYPES.put(java.sql.Time.class, "time");
CAST_TYPES.put(Blob.class, "binary");
CAST_TYPES.put(String.class, "char");
}
 
private final boolean noBackslashEscapes;
 
public SQLSyntaxMySQL(final boolean noBackslashEscapes) {
super(SQLSystem.MYSQL, DATE_SPECS);
this.noBackslashEscapes = noBackslashEscapes;
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Short.class, "smallint");
this.typeNames.addAll(Integer.class, "integer", "int");
76,8 → 115,9
this.typeNames.addAll(BigDecimal.class, "decimal", "numeric");
this.typeNames.addAll(Float.class, "float");
this.typeNames.addAll(Double.class, "double precision", "real");
this.typeNames.addAll(Timestamp.class, "timestamp");
this.typeNames.addAll(java.util.Date.class, "time");
this.typeNames.addAll(Timestamp.class, "datetime", "timestamp");
this.typeNames.addAll(java.sql.Date.class, "date");
this.typeNames.addAll(java.sql.Time.class, "time");
this.typeNames.addAll(Blob.class, "blob", "tinyblob", "mediumblob", "longblob", "varbinary", "binary");
this.typeNames.addAll(Clob.class, "text", "tinytext", "mediumtext", "longtext", "varchar", "char");
this.typeNames.addAll(String.class, "varchar", "char");
84,6 → 124,21
}
 
@Override
public final String quoteString(String s) {
final String res = super.quoteString(s);
if (s == null)
return res;
// ATTN if noBackslashEscapes is changed for the session,
// then SQL can be injected :
// toto \'; drop table ;
// is quoted to :
// 'toto \''; drop table ;'
// and since DDL is not transactional in MySQL the table is forever dropped.
// escape \ by replacing them with \\
return !this.noBackslashEscapes ? SQLSyntaxPG.BACKSLASH_PATTERN.matcher(res).replaceAll(SQLSyntaxPG.TWO_BACKSLASH_REPLACEMENT) : res;
}
 
@Override
public int getMaximumIdentifierLength() {
// http://dev.mysql.com/doc/refman/5.7/en/identifiers.html
return 64;
104,11 → 159,6
}
 
@Override
public String getDateAndTimeType() {
return "datetime";
}
 
@Override
protected String getAutoDateType(SQLField f) {
return "timestamp";
}
125,9 → 175,9
}
 
@Override
public String cast(String expr, String type) {
public String cast(String expr, Class<?> javaType) {
// MySQL doesn't use types but keywords
return super.cast(expr, INT_PATTERN.matcher(type).replaceAll("integer").replace("integer", "signed integer"));
return this.cast(expr, CAST_TYPES.get(javaType));
}
 
@Override
276,13 → 326,12
// MySQL dumps strings in binary, so fields must be consistent otherwise the
// file is invalid
throw new IllegalArgumentException(t + " has more than on character set : " + charsets);
final SQLBase base = t.getBase();
// if no string cols there should only be values within ASCII (eg dates, ints, etc)
final String charset = charsets.size() == 0 ? "UTF8" : charsets.keySet().iterator().next();
final String cols = CollectionUtils.join(t.getOrderedFields(), ",", new ITransformer<SQLField, String>() {
@Override
public String transformChecked(SQLField input) {
return base.quoteString(input.getName());
return quoteString(input.getName());
}
});
final File tmp = File.createTempFile(SQLSyntaxMySQL.class.getSimpleName() + "storeData", ".txt");
292,7 → 341,7
tmp.delete();
final SQLSelect sel = new SQLSelect(true).addSelectStar(t);
// store the data in the temp file
base.getDataSource().execute("SELECT " + cols + " UNION " + sel.asString() + " INTO OUTFILE " + base.quoteString(tmp.getAbsolutePath()) + " " + getDATA_OPTIONS(base) + ";");
t.getDBSystemRoot().getDataSource().execute("SELECT " + cols + " UNION " + sel.asString() + " INTO OUTFILE " + quoteString(tmp.getAbsolutePath()) + " " + getDATA_OPTIONS() + ";");
// then read it to remove superfluous escape char and convert to utf8
final BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(tmp), charset));
Writer w = null;
342,8 → 391,8
}
}
 
private static String getDATA_OPTIONS(final SQLBase b) {
return "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY " + b.quoteString("\\") + " LINES TERMINATED BY '\n' ";
private String getDATA_OPTIONS() {
return "FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY " + quoteString("\\") + " LINES TERMINATED BY '\n' ";
}
 
@Override
367,7 → 416,7
throw new IllegalStateException("the database charset is not utf8 and this version doesn't support specifying another one : " + dbCharset);
}
}
ds.execute(t.getBase().quote("LOAD DATA LOCAL INFILE %s INTO TABLE %f ", f.getAbsolutePath(), t) + charsetClause + getDATA_OPTIONS(t.getBase()) + " IGNORE 1 LINES;");
ds.execute(t.getBase().quote("LOAD DATA LOCAL INFILE %s INTO TABLE %f ", f.getAbsolutePath(), t) + charsetClause + getDATA_OPTIONS() + " IGNORE 1 LINES;");
return null;
}
});
377,11 → 426,6
}
 
@Override
SQLBase createBase(SQLServer server, String name, final IClosure<? super DBSystemRoot> systemRootInit, String login, String pass, IClosure<? super SQLDataSource> dsInit) {
return new MySQLBase(server, name, systemRootInit, login, pass, dsInit);
}
 
@Override
public String getNullIsDataComparison(String x, boolean eq, String y) {
final String nullSafe = x + " <=> " + y;
if (eq)
391,10 → 435,34
}
 
@Override
public String getDayOfWeek(String sqlTS) {
return "DAYOFWEEK(" + sqlTS + ")";
}
 
@Override
public String getFormatTimestamp(String sqlTS, boolean basic) {
return "DATE_FORMAT(" + sqlTS + ", " + SQLBase.quoteStringStd(basic ? "%Y%m%dT%H%i%s.%f" : "%Y-%m-%dT%H:%i:%s.%f") + ")";
return this.getFormatTimestamp(sqlTS, SQLBase.quoteStringStd(basic ? "%Y%m%dT%H%i%s.%f" : "%Y-%m-%dT%H:%i:%s.%f"));
}
 
@Override
public String getFormatTimestamp(String sqlTS, String nativeFormat) {
return "DATE_FORMAT(" + sqlTS + ", " + nativeFormat + ")";
}
 
static private final Pattern PERCENT_PATTERN = Pattern.compile("(%+)");
 
@Override
public String quoteForTimestampFormat(String text) {
return PERCENT_PATTERN.matcher(text).replaceAll("$1$1");
}
 
@Override
public String getConstantTableStatement(List<List<String>> rows, int colCount) {
if (colCount < 0)
colCount = rows.get(0).size();
return getConstantTable(rows, null, Collections.<String> nCopies(colCount, null));
}
 
private final void getRow(StringBuilder sb, List<String> row, final int requiredColCount, List<String> columnsAlias) {
// should be OK since requiredColCount is computed from columnsAlias in getConstantTable()
assert columnsAlias == null || requiredColCount == columnsAlias.size();
403,7 → 471,7
throw new IllegalArgumentException("Wrong number of columns, should be " + requiredColCount + " but row is " + row);
for (int i = 0; i < actualColCount; i++) {
sb.append(row.get(i));
if (columnsAlias != null) {
if (columnsAlias != null && columnsAlias.get(i) != null) {
sb.append(" as ");
sb.append(SQLBase.quoteIdentifier(columnsAlias.get(i)));
}
421,7 → 489,9
if (colCount < 1)
throw new IllegalArgumentException("Empty columns will cause a syntax error");
final StringBuilder sb = new StringBuilder(rows.size() * 64);
sb.append("( SELECT ");
if (alias != null)
sb.append("( ");
sb.append("SELECT ");
// aliases needed only for the first row
getRow(sb, rows.get(0), colCount, columnsAlias);
for (int i = 1; i < rowCount; i++) {
428,8 → 498,10
sb.append("\nUNION ALL\nSELECT ");
getRow(sb, rows.get(i), colCount, null);
}
if (alias != null) {
sb.append(" ) as ");
sb.append(SQLBase.quoteIdentifier(alias));
}
return sb.toString();
}
 
459,7 → 531,7
translated = new TablesMap(1);
translated.put(b.getMDName(), tables.get(null));
}
return getTablesMapJoin(b, translated, schemaCol, tableCol);
return getTablesMapJoin(translated, schemaCol, tableCol);
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBSystemRoot.java
76,6 → 76,9
private boolean incoherentPath;
private final PropertyChangeListener coherenceListener;
 
@GuardedBy("this")
private SQLSyntax syntax;
 
private final LoadingChangeSupport loadingListenersSupp;
 
DBSystemRoot(DBStructureItemJDBC delegate) {
97,6 → 100,7
}
};
this.loadingListenersSupp = new LoadingChangeSupport(this);
this.syntax = null;
}
 
private synchronized void rootsChanged(PropertyChangeEvent evt) {
271,7 → 275,7
public synchronized final void setUseCache(final boolean useCache) throws SQLException {
if (this.hasDataSource() && useCache) {
// null if we shouldn't alter the base
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(this.getServer().getSQLSystem().getSyntax());
final SQLCreateMoveableTable createMetadata = SQLSchema.getCreateMetadata(this.getSyntax());
final TablesMap m = new TablesMap();
for (final DBRoot r : this.getChildrenMap().values()) {
// works because when created a root is always fully loaded (we don't allow roots
390,6 → 394,12
return this.ds != null;
}
 
public synchronized final SQLSyntax getSyntax() {
if (this.syntax == null)
this.syntax = SQLSyntax.create(this);
return this.syntax;
}
 
@Override
protected synchronized void onDrop() {
this.rmChildrenListener(this.coherenceListener);
536,7 → 546,7
* @see #addRoot(String)
*/
public final DBRoot createRoot(final String rootName) throws SQLException {
for (final String s : new SQLCreateRoot(SQLSyntax.get(this), rootName).asList(rootName, false, true))
for (final String s : new SQLCreateRoot(this.getSyntax(), rootName).asList(rootName, false, true))
getDataSource().execute(s);
return this.addRoot(rootName);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/XMLStructureSource.java
23,8 → 23,6
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
40,10 → 38,6
 
public class XMLStructureSource extends StructureSource<IOException> {
 
/**
* Date format used in xml files.
*/
public static final DateFormat XMLDATE_FMT = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
public static final String version = "20141001-1155";
 
private final Map<String, Element> xmlSchemas;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFilter.java
71,10 → 71,11
 
private final SQLElementDirectory dir;
private final GraFFF filterGraph;
// the filter, from descendant to ancestor
@GuardedBy("filteredIDs")
private final List<Set<SQLRow>> filteredIDs;
// CopyOnWriteArrayList is simpler but rmListener() cannot be implemented
@GuardedBy("listeners")
@GuardedBy("this")
private List<SQLFilterListener> listeners;
 
public SQLFilter(SQLElementDirectory dir, final GraFFF filterGraph) {
192,7 → 193,7
}
 
final List<SQLFilterListener> dispatchingListeners;
synchronized (this.listeners) {
synchronized (this) {
dispatchingListeners = this.listeners;
}
for (final SQLFilterListener l : dispatchingListeners) {
208,7 → 209,7
}
 
public void addListener(SQLFilterListener l) {
synchronized (this.listeners) {
synchronized (this) {
final List<SQLFilterListener> newListeners = new ArrayList<SQLFilterListener>(this.listeners.size() + 1);
newListeners.addAll(this.listeners);
newListeners.add(l);
232,7 → 233,7
}
 
private void rmListener_(SQLFilterListener lToRm) {
synchronized (this.listeners) {
synchronized (this) {
final int stop = this.listeners.size();
int indexToRm = -1;
for (int i = 0; i < stop && indexToRm < 0; i++) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTable.java
13,8 → 13,11
package org.openconcerto.sql.model;
 
import static org.openconcerto.xml.JDOM2Utils.OUTPUTTER;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.graph.DatabaseGraph;
38,7 → 41,6
import org.openconcerto.utils.cc.CopyOnWriteMap;
import org.openconcerto.utils.cc.CustomEquals;
import org.openconcerto.utils.change.CollectionChangeEventCreator;
import org.openconcerto.xml.JDOMUtils;
 
import java.math.BigDecimal;
import java.sql.DatabaseMetaData;
214,7 → 216,7
l.add(Arrays.asList(b.quoteString(tableName), undefSQL));
}
}
final SQLSyntax syntax = system.getSyntax();
final SQLSyntax syntax = schema.getDBSystemRoot().getSyntax();
if (toInsert.size() > 0) {
// INSERT
SQLRowValues.insertCount(undefT, "(" + SQLSyntax.quoteIdentifiers(Arrays.asList(UNDEF_TABLE_TABLENAME_FIELD, UNDEF_TABLE_ID_FIELD)) + ") " + syntax.getValues(toInsert, 2));
310,7 → 312,7
private String version;
private final CopyOnWriteMap<String, SQLField> fields;
@GuardedBy("this")
private final Set<SQLField> primaryKeys;
private Set<SQLField> primaryKeys;
// the vast majority of our code use getKey(), so cache it for performance
@GuardedBy("this")
private SQLField primaryKey;
363,8 → 365,7
}
};
assert isOrdered(this.fields);
// order matters (eg for indexes)
this.primaryKeys = new LinkedHashSet<SQLField>();
this.primaryKeys = Collections.emptySet();
this.primaryKey = null;
this.primaryKeyOK = true;
this.keys = null;
512,7 → 513,7
 
// must be called in setState() after fields have been set (for isRowable())
private int fetchUndefID() {
final int res;
int res;
final SQLField pk;
synchronized (this) {
pk = isRowable() ? this.getKey() : null;
520,8 → 521,14
if (pk != null) {
final Tuple2<Boolean, Number> currentValue = getUndefID(this.getSchema(), this.getName());
if (!currentValue.get0()) {
try {
// no row
res = this.findMinID(pk);
} catch (Exception e) {
// we ***** don't care
e.printStackTrace();
res = SQLRow.NONEXISTANT_ID;
}
} else {
// a row
final Number id = currentValue.get1();
546,8 → 553,9
// empty table
throw new IllegalStateException(this + " is empty, can not infer UNDEFINED_ID");
} else {
final String update = SQLSyntax.get(this).getInsertOne(new SQLName(this.getDBRoot().getName(), undefTable), Arrays.asList(UNDEF_TABLE_TABLENAME_FIELD, UNDEF_TABLE_ID_FIELD),
getBase().quoteString(this.getName()), String.valueOf(undef));
final SQLSyntax syntax = SQLSyntax.get(this);
final String update = syntax.getInsertOne(new SQLName(this.getDBRoot().getName(), undefTable), Arrays.asList(UNDEF_TABLE_TABLENAME_FIELD, UNDEF_TABLE_ID_FIELD),
syntax.quoteString(this.getName()), String.valueOf(undef));
Log.get().config("the first row (which should be the undefined):\n" + update);
return undef.intValue();
}
630,9 → 638,11
}
}
 
this.primaryKeys.clear();
// order matters (e.g. for indexes)
final Set<SQLField> newPK = new LinkedHashSet<SQLField>();
for (final String pk : primaryKeys)
this.primaryKeys.add(this.getField(pk));
newPK.add(this.getField(pk));
this.primaryKeys = Collections.unmodifiableSet(newPK);
this.primaryKey = primaryKeys.size() == 1 ? this.getField(primaryKeys.get(0)) : null;
this.primaryKeyOK = primaryKeys.size() <= 1;
 
762,7 → 772,7
* @return the fields (SQLField) which are the keys of this table, can be empty.
*/
public synchronized Set<SQLField> getPrimaryKeys() {
return Collections.unmodifiableSet(this.primaryKeys);
return this.primaryKeys;
}
 
public final Set<Link> getForeignLinks() {
1117,7 → 1127,7
}
}
}
return res;
return Collections.unmodifiableSet(res);
}
 
public final Set<String> getFieldsNames(final VirtualFields vfs) {
1128,6 → 1138,23
return res;
}
 
public final List<SQLField> getFields(final Collection<String> names) {
return this.getFields(names, new ArrayList<SQLField>());
}
 
public final <T extends Collection<SQLField>> T getFields(final Collection<String> names, final T res) {
return this.getFields(names, res, true);
}
 
public final <T extends Collection<SQLField>> T getFields(final Collection<String> names, final T res, final boolean required) {
for (final String name : names) {
final SQLField f = required ? this.getField(name) : this.getFieldRaw(name);
if (f != null)
res.add(f);
}
return res;
}
 
/**
* Retourne les champs du contenu de cette table. C'est à dire ni la clef primaire, ni les
* champs d'archive et d'ordre.
1267,7 → 1294,7
* archivée.
*/
public SQLRow checkValidity(int ID) {
SQLRow row = this.getUncheckedRow(ID);
final SQLRow row = SQLRow.createFromSelect(this, VirtualFields.PRIMARY_KEY.union(VirtualFields.ARCHIVE), ID, LockStrength.SHARE);
// l'inverse de getValidRow()
return row.isValid() ? null : row;
}
1684,13 → 1711,13
public synchronized String toXML() {
final StringBuilder sb = new StringBuilder(16000);
sb.append("<table name=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append(OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append("\"");
 
final String schemaName = this.getSchema().getName();
if (schemaName != null) {
sb.append(" schema=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(schemaName));
sb.append(OUTPUTTER.escapeAttributeEntities(schemaName));
sb.append('"');
}
 
1698,7 → 1725,7
 
if (getType() != null) {
sb.append(" type=\"");
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(getType()));
sb.append(OUTPUTTER.escapeAttributeEntities(getType()));
sb.append('"');
}
 
1706,7 → 1733,7
 
if (this.getComment() != null) {
sb.append("<comment>");
sb.append(JDOMUtils.OUTPUTTER.escapeElementEntities(this.getComment()));
sb.append(OUTPUTTER.escapeElementEntities(this.getComment()));
sb.append("</comment>\n");
}
for (SQLField field : this.fields.values()) {
1924,11 → 1951,11
}
 
public final SQLCreateMoveableTable getCreateTable() {
return this.getCreateTable(this.getServer().getSQLSystem());
return this.getCreateTable(SQLSyntax.get(this));
}
 
public synchronized final SQLCreateMoveableTable getCreateTable(final SQLSystem system) {
final SQLSyntax syntax = SQLSyntax.get(system);
public synchronized final SQLCreateMoveableTable getCreateTable(final SQLSyntax syntax) {
final SQLSystem system = syntax.getSystem();
final SQLCreateMoveableTable res = new SQLCreateMoveableTable(syntax, this.getDBRoot().getName(), this.getName());
for (final SQLField f : this.getOrderedFields()) {
res.addColumn(f);
1954,7 → 1981,6
// not null" in addUniqueConstraint(). Thus when converting to another system we must
// parse indexes to recreate actual constraints.
final boolean convertMSIndex = this.getServer().getSQLSystem() == SQLSystem.MSSQL && system != SQLSystem.MSSQL;
final Set<List<SQLField>> foreignKeysFields = getForeignKeysFields();
for (final Index i : this.getIndexes(true)) {
Value<String> msWhere = null;
if (convertMSIndex && (msWhere = i.getMSUniqueWhere()).hasValue()) {
1961,12 → 1987,12
if (msWhere.getValue() != null)
Log.get().warning("MS filter might not be valid in " + system + " : " + msWhere.getValue());
res.addUniqueConstraint(i.getName(), i.getCols(), msWhere.getValue());
} else if (!system.autoCreatesFKIndex() || !foreignKeysFields.contains(i.getFields())) {
} else {
// partial unique index sometimes cannot be handled natively by the DB system
if (i.isUnique() && i.getFilter() != null && !system.isIndexFilterConditionSupported())
res.addUniqueConstraint(i.getName(), i.getCols(), i.getFilter());
else
res.addOutsideClause(syntax.getCreateIndex(i));
res.addIndex(i);
}
}
} catch (SQLException e) {
2040,13 → 2066,13
* automatically.
*
* @return the list of indexes.
* @throws SQLException if an error occurs.
* @throws SQLException if an error occurs while accessing the DB.
*/
public synchronized final List<Index> getIndexes() throws SQLException {
public final List<Index> getIndexes() throws SQLException {
return this.getIndexes(false);
}
 
protected synchronized final List<Index> getIndexes(final boolean normalized) throws SQLException {
public synchronized final List<Index> getIndexes(final boolean normalized) throws SQLException {
// in pg, a unique constraint creates a unique index that is not removeable
// (except of course if we drop the constraint)
// in mysql unique constraints and indexes are one and the same thing
2065,7 → 2091,7
 
final List<Index> indexes = new ArrayList<Index>();
Index currentIndex = null;
for (final Map<String, Object> norm : this.getServer().getSQLSystem().getSyntax().getIndexInfo(this)) {
for (final Map<String, Object> norm : this.getDBSystemRoot().getSyntax().getIndexInfo(this)) {
final Index index = new Index(norm);
final short seq = ((Number) norm.get("ORDINAL_POSITION")).shortValue();
if (seq == 1) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/Where.java
14,6 → 14,7
package org.openconcerto.sql.model;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
24,10 → 25,10
import java.util.Map;
import java.util.Map.Entry;
 
import org.apache.commons.collections.functors.InstanceofPredicate;
 
import net.jcip.annotations.Immutable;
 
import org.apache.commons.collections.functors.InstanceofPredicate;
 
/**
* Une clause WHERE dans une requete SQL. Une clause peut être facilement combinée avec d'autre,
* exemple : prenomPasVide.and(pasIndéfini).and(age_sup_3.or(assez_grand)).
102,6 → 103,10
return AndCombiner.combine(where1, where2);
}
 
static public Where or(final Where where1, final Where where2) {
return OrCombiner.combine(where1, where2);
}
 
static public Where isNull(final FieldRef ref) {
return new Where(ref, "is", (Object) null);
}
144,7 → 149,7
 
static private final String comparison(final FieldRef ref, final String op, final String y) {
if (op == NULL_IS_DATA_EQ || op == NULL_IS_DATA_NEQ) {
return ref.getField().getServer().getSQLSystem().getSyntax().getNullIsDataComparison(ref.getFieldRef(), op == NULL_IS_DATA_EQ, y);
return ref.getField().getDBSystemRoot().getSyntax().getNullIsDataComparison(ref.getFieldRef(), op == NULL_IS_DATA_EQ, y);
} else {
return ref.getFieldRef() + " " + op + " " + y;
}
261,6 → 266,8
 
// raw ctor, see static methods
private Where(final String clause, final Collection<? extends FieldRef> refs) {
if (StringUtils.isEmpty(clause, true))
throw new IllegalArgumentException("No clause");
this.fields = Collections.unmodifiableList(new ArrayList<FieldRef>(refs));
this.clause = clause;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/users/UserManager.java
22,11 → 22,15
import org.openconcerto.sql.model.SQLTableModifiedListener;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
@ThreadSafe
public class UserManager {
private static UserManager instance;
 
51,39 → 55,40
}
 
private final SQLTable t;
private final Map<Integer, User> byID;
@GuardedBy("this")
private Map<Integer, User> byID;
@GuardedBy("this")
private boolean dirty;
@GuardedBy("this")
private User currentUser;
 
private UserManager(final SQLTable t) {
// to keep the ORDER for #getAllUser()
this.byID = new LinkedHashMap<Integer, User>();
this.byID = Collections.emptyMap();
this.currentUser = null;
this.t = t;
this.t.addTableModifiedListener(new SQLTableModifiedListener() {
@Override
public void tableModified(SQLTableEvent evt) {
UserManager.this.dirty = true;
clearCache();
}
});
fillUsers();
}
 
public synchronized void clearCache() {
this.dirty = true;
}
 
private synchronized void fillUsers() {
this.byID.clear();
 
// to keep the ORDER for #getAllUser()
final Map<Integer, User> mutable = new LinkedHashMap<Integer, User>();
final SQLRowValuesListFetcher fetcher = new SQLRowValuesListFetcher(new SQLRowValues(this.t).setAllToNull());
fetcher.setOrdered(true);
for (final SQLRowValues v : fetcher.fetch()) {
final User u = new User(v.getID(), v.getString("NOM"));
u.setFirstName(v.getString("PRENOM"));
u.setNickName(v.getString("SURNOM"));
if (v.getFields().contains("DISABLED")) {
u.setActive(!v.getBoolean("DISABLED"));
final User u = new User(v);
mutable.put(v.getID(), u);
}
this.byID.put(v.getID(), u);
}
 
this.byID = Collections.unmodifiableMap(mutable);
this.dirty = false;
}
 
91,7 → 96,7
return this.t;
}
 
private final Map<Integer, User> getUsers() {
private synchronized final Map<Integer, User> getUsers() {
if (this.dirty) {
fillUsers();
}
98,22 → 103,21
return this.byID;
}
 
public final User getCurrentUser() {
public synchronized final User getCurrentUser() {
return this.currentUser;
}
 
public void setCurrentUser(final int id) {
public synchronized void setCurrentUser(final int id) {
this.currentUser = getUser(Integer.valueOf(id));
}
 
public synchronized List<User> getAllUser() {
public List<User> getAllUser() {
return new ArrayList<User>(this.getUsers().values());
}
 
public synchronized List<User> getAllActiveUsers() {
final ArrayList<User> result = new ArrayList<User>();
final Collection<User> values = this.getUsers().values();
for (User user : values) {
public List<User> getAllActiveUsers() {
final List<User> result = new ArrayList<User>();
for (User user : this.getUsers().values()) {
if (user.isActive()) {
result.add(user);
}
121,9 → 125,10
return result;
}
 
public synchronized User getUser(final Integer v) {
if (this.getUsers().containsKey(v))
return this.getUsers().get(v);
public User getUser(final Integer v) {
final Map<Integer, User> users = this.getUsers();
if (users.containsKey(v))
return users.get(v);
else
throw new IllegalStateException("Bad user! " + v);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/users/User.java
13,17 → 13,28
package org.openconcerto.sql.users;
 
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.users.rights.UserRights;
 
public class User {
import net.jcip.annotations.Immutable;
 
@Immutable
public final class User {
private final int id;
private String name, firstName, nickName;
private final String name, firstName, nickName;
private final UserRights userRights;
private boolean active = true;
private Boolean active;
 
public User(int id, String name) {
this.id = id;
this.name = name.trim();
public User(final SQLRowAccessor r) {
this.id = r.getID();
this.name = r.getString("NOM").trim();
this.firstName = r.getString("PRENOM").trim();
this.nickName = r.getString("SURNOM").trim();
if (r.getFields().contains("DISABLED")) {
this.active = !r.getBoolean("DISABLED");
} else {
this.active = null;
}
this.userRights = new UserRights(this.getId());
}
 
44,14 → 55,6
return this.getFullName() + " /" + getId();
}
 
public void setFirstName(String string) {
this.firstName = string.trim();
}
 
public void setNickName(String string) {
this.nickName = string.trim();
}
 
public String getFirstName() {
return this.firstName;
}
65,10 → 68,6
}
 
public boolean isActive() {
return active;
return this.active;
}
 
public void setActive(boolean b) {
this.active = b;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/users/UserCommonSQLElement.java
111,10 → 111,9
}
 
@Override
protected ComboSQLRequest createComboRequest() {
final ComboSQLRequest res = super.createComboRequest();
res.setFieldSeparator(" ");
return res;
protected void _initComboRequest(ComboSQLRequest req) {
super._initComboRequest(req);
req.setFieldSeparator(" ");
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/preferences/UserProps.java
55,9 → 55,8
@Override
protected String getPropsFileName() {
final Configuration conf = Configuration.getInstance();
String string = conf.getConfDir() + File.separator + "Configuration" + File.separator;
string += conf.getSystemRoot().getName() + "_";
return string + "User.properties";
// MAYBE use UserCommonSQLElement (but PropsConfiguration.getDirectory() hangs)
return conf.getConfDirForRoot() + File.separator + "User.properties";
}
 
public synchronized static UserProps getInstance() {
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/AddMDFields.java
17,6 → 17,8
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.utils.AlterTable;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.sql.utils.SQLCreateTableBase;
 
import java.sql.SQLException;
import java.util.ArrayList;
42,6 → 44,21
return fields.subList(2, 4);
}
 
static public final void addFields(final SQLCreateTable createTable) {
addFields(createTable, createTable.getRoot().findTable("USER_COMMON"));
}
 
static public final void addFields(final SQLCreateTableBase<?> createTable, final SQLTable userT) {
if (userT == null)
throw new IllegalArgumentException("Missing user table");
for (final String fk : getFKFields()) {
createTable.addForeignColumn(fk, userT);
}
for (final String fk : getDateFields()) {
createTable.addDateAndTimeColumn(fk);
}
}
 
public AddMDFields(DBSystemRoot b) {
super(b);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/MergeTable.java
161,7 → 161,7
}
}
 
final SQLSyntax syntax = t.getServer().getSQLSystem().getSyntax();
final SQLSyntax syntax = t.getDBSystemRoot().getSyntax();
final Set<SQLTable> toRefresh = new HashSet<SQLTable>();
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/correct/SetFFRules.java
84,7 → 84,7
SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
@Override
public Object create() throws SQLException {
for (final List<String> l : ChangeTable.cat(Collections.singleton(alterTable), t.getDBRoot().getName(), EnumSet.of(ConcatStep.ADD_FOREIGN))) {
for (final List<String> l : ChangeTable.cat(Collections.singleton(alterTable), t.getDBRoot().getName(), EnumSet.of(ConcatStep.ADD_CONSTRAINT))) {
for (final String sql : l)
getDS().execute(sql);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/Changer.java
170,6 → 170,6
}
 
protected final SQLSyntax getSyntax() {
return SQLSyntax.get(this.getSystemRoot().getServer().getSQLSystem());
return this.getSystemRoot().getSyntax();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/replication/MemoryRep.java
132,7 → 132,7
}
}).getSystemRoot("");
// slave is a copy so it needn't have checks (and it simplify replicate())
this.slave.getDataSource().execute(this.slave.getServer().getSQLSystem().getSyntax().disableFKChecks(null));
this.slave.getDataSource().execute(this.slave.getSyntax().disableFKChecks(null));
this.count = new AtomicInteger(0);
this.canceledCount = 0;
}
305,7 → 305,7
}
 
protected final void replicateStruct() throws SQLException, IOException {
final SQLSystem slaveSystem = this.slave.getServer().getSQLSystem();
final SQLSyntax slaveSyntax = this.slave.getSyntax();
final SQLDataSource slaveDS = this.slave.getDataSource();
final List<SQLCreateTableBase<?>> createTables = new ArrayList<SQLCreateTableBase<?>>();
// undefined IDs by table by root
313,7 → 313,7
for (final Entry<String, Set<String>> e : this.tables.entrySet()) {
final String rootName = e.getKey();
final Set<String> tableNames = e.getValue();
slaveDS.execute(new SQLCreateRoot(slaveSystem.getSyntax(), rootName).asString());
slaveDS.execute(new SQLCreateRoot(this.slave.getSyntax(), rootName).asString());
final DBRoot root = this.master.getRoot(rootName);
 
final Map<String, Number> rootUndefIDs = new HashMap<String, Number>(tableNames.size());
321,7 → 321,7
 
for (final String tableName : tableNames) {
final SQLTable masterTable = root.getTable(tableName);
final SQLCreateMoveableTable ct = masterTable.getCreateTable(slaveSystem);
final SQLCreateMoveableTable ct = masterTable.getCreateTable(slaveSyntax);
// remove constraints towards non-copied tables
for (final FCSpec fc : new ArrayList<FCSpec>(ct.getForeignConstraints())) {
final SQLName refTable = new SQLName(rootName, tableName).resolve(fc.getRefTable());
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceStateOffline.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceStateOnline.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ChangeFKRunnable.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSource.java
13,7 → 13,9
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLFieldsSet;
import org.openconcerto.sql.model.SQLRow;
22,6 → 24,8
import org.openconcerto.sql.model.SQLRowValues.ForeignCopyMode;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.change.ListChangeIndex;
import org.openconcerto.utils.change.ListChangeRecorder;
34,6 → 38,7
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
 
import javax.swing.SwingUtilities;
 
75,7 → 80,8
}
}
 
private final SQLTable table;
private final ListSQLRequest req;
private final SQLElement elem;
// only from EDT
private SQLRowValues inited;
// this.cols + debugCols, unmodifiable
95,14 → 101,23
this.lines = new ArrayList<WeakReference<SQLTableModelLinesSource>>();
}
 
public SQLTableModelSource(SQLRowValues graph) {
this.table = graph.getTable();
public SQLTableModelSource(final ListSQLRequest req, final SQLElement elem) {
if (elem == null)
throw new IllegalArgumentException("Missing element");
this.req = req;
this.elem = elem;
if (!this.getPrimaryTable().equals(this.elem.getTable()))
throw new IllegalArgumentException("not the same table: " + this.getPrimaryTable() + " != " + this.elem);
this.setAllCols(SQLTableModelColumns.empty());
this.cols = new ListChangeRecorder<SQLTableModelColumn>(new ArrayList<SQLTableModelColumn>());
this.debugCols = new ArrayList<SQLTableModelColumn>();
this.inited = graph;
this.inited = req.getGraph();
}
 
public SQLElement getElem() {
return this.elem;
}
 
// lazy initialization since this method calls colsChanged() which subclasses overload and
// they need their own attribute that aren't set yet since super() must be the first statement.
public void init() {
117,7 → 132,7
public void executeChecked(final FieldPath input) {
final SQLField f = input.getField();
if (f.getTable().getLocalContentFields().contains(f)) {
final SQLTableModelColumnPath col = new SQLTableModelColumnPath(input);
final SQLTableModelColumnPath col = new SQLTableModelColumnPath(input, null, getElem().getDirectory());
SQLTableModelSource.this.cols.add(col);
} else
SQLTableModelSource.this.debugCols.add(new SQLTableModelColumnPath(input.getPath(), f.getName(), f.toString()) {
145,7 → 160,8
}
 
public SQLTableModelSource(SQLTableModelSource src) {
this.table = src.table;
this.req = src.req;
this.elem = src.elem;
this.setAllCols(src.getAllColumns());
this.cols = new ListChangeRecorder<SQLTableModelColumn>(new ArrayList<SQLTableModelColumn>(src.cols));
this.debugCols = new ArrayList<SQLTableModelColumn>(src.debugCols);
178,13 → 194,46
fireColsChanged(beforeState, afterState);
}
 
protected abstract SQLTableModelSourceState createState();
protected final SQLTableModelSourceState createState() {
return new SQLTableModelSourceState(this.getAllColumns(), this.getReq());
}
 
protected void colsChanged(final ListChangeIndex<SQLTableModelColumn> change) {
private final void colsChanged(final ListChangeIndex<SQLTableModelColumn> change) {
final AtomicBoolean biggerGraph = new AtomicBoolean(false);
// add needed fields for each new column
this.getReq().changeGraphToFetch(new IClosure<SQLRowValues>() {
@Override
public void executeChecked(SQLRowValues g) {
for (final SQLTableModelColumn col : change.getItemsAdded()) {
// DebugRow should uses all *fetched* fields, but since it cannot know them
// getPaths() return all fields in the table. So don't fetch all fields just for
// this debug column
if (!(col instanceof DebugRow)) {
for (final IFieldPath p : col.getPaths()) {
if (BaseFillSQLRequest.addToFetch(g, p.getPath(), Collections.singleton(p.getFieldName())))
biggerGraph.set(true);
}
}
}
}
});
if (biggerGraph.get() && !allowBiggerGraph())
throw new IllegalStateException("Bigger graph not allowed");
}
 
protected abstract boolean allowBiggerGraph();
 
private void fireColsChanged(final SQLTableModelSourceState beforeState, final SQLTableModelSourceState afterState) {
// let know each of our LinesSource that the columns have changed
for (final SQLTableModelLinesSource line : getLines()) {
line.colsChanged(beforeState, afterState);
}
// before notifying our regular listeners
this.supp.firePropertyChange("cols", null, this.cols);
}
 
private final List<SQLTableModelLinesSource> getLines() {
final List<SQLTableModelLinesSource> res = new ArrayList<SQLTableModelLinesSource>();
int i = 0;
while (i < this.lines.size()) {
final WeakReference<SQLTableModelLinesSource> l = this.lines.get(i);
192,14 → 241,17
if (line == null)
this.lines.remove(i);
else {
line.colsChanged(beforeState, afterState);
res.add(line);
i++;
}
}
// before notifying our regular listeners
this.supp.firePropertyChange("cols", null, this.cols);
return res;
}
 
protected final int getLinesCount() {
return this.getLines().size();
}
 
public final SQLTableModelLinesSource createLinesSource(ITableModel model) {
this.init();
final SQLTableModelLinesSource res = this._createLinesSource(model);
214,7 → 266,9
*
* @return the maximum graph of our lines.
*/
public abstract SQLRowValues getMaxGraph();
public final SQLRowValues getMaxGraph() {
return this.getReq().getGraphToFetch();
}
 
// * columns
 
293,8 → 347,12
 
// * SQLIdentifier
 
public final ListSQLRequest getReq() {
return this.req;
}
 
public final SQLTable getPrimaryTable() {
return this.table;
return this.getReq().getPrimaryTable();
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSourceOnline.java
53,7 → 53,7
fireChanged(evt);
}
};
this.getReq().addWhereListener(this.listener);
this.getParent().getReq().addWhereListener(this.listener);
this.moveQ = null;
}
 
68,7 → 68,7
 
@Override
protected void die() {
this.getReq().rmWhereListener(this.listener);
this.getParent().getReq().rmWhereListener(this.listener);
 
if (this.moveQ != null) {
final RunningState threadState = this.moveQ.getRunningState();
88,14 → 88,6
return this.parent;
}
 
public final ListSQLRequest getReq() {
return this.getParent().getReq();
}
 
public final ListSQLRequest getUpdateQueueReq() {
return ((SQLTableModelSourceStateOnline) this.getModel().getUpdateQ().getState()).getReq();
}
 
@Override
public List<ListSQLLine> getAll() {
final List<SQLRowValues> values = this.getUpdateQueueReq().getValues();
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceState.java
13,17 → 13,25
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.request.ListSQLRequest;
 
import net.jcip.annotations.Immutable;
 
@Immutable
abstract class SQLTableModelSourceState {
public final class SQLTableModelSourceState {
 
private final ListSQLRequest req;
private final SQLTableModelColumns allCols;
 
protected SQLTableModelSourceState(final SQLTableModelColumns allCols) {
protected SQLTableModelSourceState(final SQLTableModelColumns allCols, final ListSQLRequest req) {
this.req = req.toUnmodifiable();
this.allCols = allCols;
}
 
public final ListSQLRequest getReq() {
return this.req;
}
 
public final SQLTableModelColumns getAllColumns() {
return this.allCols;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceOffline.java
14,8 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.request.ListSQLRequest;
 
/**
* A SQLTableModelSource *not* directly tied to the database.
24,28 → 23,14
*/
public class SQLTableModelSourceOffline extends SQLTableModelSource {
 
private final SQLRowValuesListFetcher fetcher;
private final SQLElement elem;
 
public SQLTableModelSourceOffline(final SQLRowValuesListFetcher fetcher, final SQLElement elem) {
super(fetcher.getGraph());
this.fetcher = fetcher.toUnmodifiable();
this.elem = elem;
if (!this.getPrimaryTable().equals(this.elem.getTable()))
throw new IllegalArgumentException("not the same table: " + this.getPrimaryTable() + " != " + this.elem);
public SQLTableModelSourceOffline(final ListSQLRequest req, final SQLElement elem) {
super(req, elem);
}
 
public final SQLRowValuesListFetcher getFetcher() {
return this.fetcher;
}
 
public SQLElement getElem() {
return this.elem;
}
 
@Override
protected SQLTableModelSourceState createState() {
return new SQLTableModelSourceStateOffline(this.getAllColumns(), this.getFetcher());
protected boolean allowBiggerGraph() {
// MAYBE allow if all lines source have only committed rows (refresh them)
return this.getLinesCount() == 0;
}
 
@Override
52,9 → 37,4
protected SQLTableModelLinesSourceOffline _createLinesSource(final ITableModel model) {
return new SQLTableModelLinesSourceOffline(this, model);
}
 
@Override
public SQLRowValues getMaxGraph() {
return this.getFetcher().getGraph();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/TableAction.java
18,6 → 18,7
 
public class TableAction {
private String id = null;
private boolean showList = false;
private List<RowAction> actions = new ArrayList<RowAction>();
public TableAction(final RowAction action) {
48,7 → 49,15
return null;
}
public void setShowList(final boolean showList) {
this.showList = showList;
}
public boolean isShowList() {
return this.showList;
}
public int getActionsCount() {
return this.actions.size();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/UpdateQueue.java
28,6 → 28,7
import org.openconcerto.sql.view.list.search.SearchOne.Mode;
import org.openconcerto.sql.view.list.search.SearchQueue;
import org.openconcerto.sql.view.list.search.SearchQueue.SetStateRunnable;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SleepingQueue;
50,8 → 51,6
 
import net.jcip.annotations.GuardedBy;
 
import org.apache.commons.collections.CollectionUtils;
 
public final class UpdateQueue extends SleepingQueue {
 
/**
132,7 → 131,7
@Override
protected void started() {
// savoir quand les tables qu'on affiche changent
addListeners();
addSourceListener();
stateChanged(null, this.getModel().getReq().createState());
// Only starts once there's something to search, that way the runnable passed to
// ITableModel.search() will be meaningful
222,7 → 221,7
if (id < SQLRow.MIN_VALID_ID)
throw new IllegalArgumentException("invalid ID: " + id);
if (!fullList.isEmpty()) {
final SQLRowValues proto = this.getModel().getLinesSource().getParent().getMaxGraph();
final SQLRowValues proto = this.getState().getReq().getGraphToFetch();
final List<Path> pathsToT = new ArrayList<Path>();
proto.getGraph().walk(proto, pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
@Override
286,7 → 285,7
this.tableModel.getSearchQueue().fullListChanged();
}
 
final void reorder(final List<Number> idsOrder) {
final void reorder(final List<Integer> idsOrder) {
final List<ListSQLLine> fullList = this.getFullList();
synchronized (fullList) {
for (final ListSQLLine l : fullList) {
383,7 → 382,7
put(new SetStateRunnable() {
@Override
public void run() {
UpdateQueue.this.state = afterState;
setState(afterState);
}
});
// TODO if request didn't change and the new graph is smaller, copy and prune the
393,6 → 392,14
});
}
 
protected final void setState(final SQLTableModelSourceState newState) {
if (this.state != null)
this.rmTableListener();
this.state = newState;
if (this.state != null)
this.addTableListener();
}
 
protected final SQLTableModelSourceState getState() {
assert this.currentlyInQueue();
if (this.state == null)
402,29 → 409,24
 
@Override
protected void willDie() {
this.removeListeners();
this.rmTableListener();
this.removeSourceListener();
super.willDie();
}
 
protected final void addTableListener() {
for (final SQLTable t : this.tableModel.getReq().getTables()) {
t.addTableModifiedListener(this.tableListener);
this.getState().getReq().addTableListener(this.tableListener);
}
}
 
private void addListeners() {
this.addTableListener();
private void addSourceListener() {
this.tableModel.getLinesSource().addListener(this.tableListener);
}
 
protected final void rmTableListener() {
for (final SQLTable t : this.tableModel.getReq().getTables()) {
t.removeTableModifiedListener(this.tableListener);
this.getState().getReq().removeTableListener(this.tableListener);
}
}
 
private void removeListeners() {
this.rmTableListener();
private void removeSourceListener() {
this.tableModel.getLinesSource().rmListener(this.tableListener);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/IListe.java
15,7 → 15,6
 
import org.openconcerto.openoffice.XMLFormatVersion;
import org.openconcerto.openoffice.spreadsheet.SpreadSheet;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.Log;
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.SQLComponent;
136,6 → 135,8
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
 
import net.jcip.annotations.GuardedBy;
 
/**
* Une liste de lignes correspondant à une ListSQLRequest. Diagramme pour la sélection :
* <img src="doc-files/listSelection.png"/><br/>
272,11 → 273,14
private final JTable jTable;
private final JTextField filter;
private boolean debugFilter;
@GuardedBy("this")
private FilterWorker filterWorker;
// optional popup on the table
private final JPopupMenu popup;
private final TableSorter sorter;
// record the source when non-displayable (ie getModel() == null)
@GuardedBy("this")
// record the source when non-displayable (ie getModel() == null), also allow to be read outside
// of the EDT
private SQLTableModelSource src;
private boolean adjustVisible;
private ColumnSizeAdjustor tcsa;
306,23 → 310,11
 
private int retainCount = 0;
 
public IListe(final ListSQLRequest req) {
this(req, null);
}
 
public IListe(final ListSQLRequest req, File configFile) {
this((Object) req, configFile);
}
 
public IListe(final SQLTableModelSource req) {
this(req, null);
}
 
public IListe(final SQLTableModelSource req, File configFile) {
this((Object) req, configFile);
}
 
private IListe(final Object req, File configFile) {
public IListe(final SQLTableModelSource req, final File configFile) {
if (req == null)
throw new NullPointerException("Création d'une IListe avec une requete null");
 
441,7 → 433,6
this.filter = new JTextField();
this.filter.setEditable(false);
this.debugFilter = false;
this.filterWorker = null;
 
// do not handle F2, let our application use it :
// remove F2 keybinding, use space
529,10 → 520,7
// MAYBE only set this.src and let the model be null so that the mere creation of an IListe
// does not spawn several threads and access the db. But a lot of code assumes there's
// immediately a model.
if (req instanceof SQLTableModelSource)
this.setSource((SQLTableModelSource) req);
else
this.setRequest((ListSQLRequest) req);
this.setSource(req);
this.state = ListSelectionState.manage(this.jTable.getSelectionModel(), new TableListStateModel(this.sorter));
this.state.addPropertyChangeListener("selectedIndex", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
736,6 → 724,9
}
});
FontUtils.setFontFor(this.filter, SEP);
// initially hide to limit modifications for instances which don't need the filter, see
// setFilter() comment
this.setFilter(null);
this.updateFilter();
 
// * JTable
981,10 → 972,15
* @param text the text to display, <code>null</code> to hide the label.
*/
private void setFilter(String text) {
final boolean currentVisible = this.filter.isVisible();
final boolean newVisible = text != null;
// limit modifications due to a bug in Swing that can bring the frame to the back.
if (newVisible || currentVisible) {
this.filter.setText(text == null ? "" : text);
this.filter.setVisible(text != null);
this.filter.setVisible(newVisible);
this.revalidate();
}
}
 
public void selectID(final int id) {
this.selectIDs(Collections.singleton(id));
1146,7 → 1142,7
return clazz.cast(toCast);
}
 
public SQLRow fetchRow(int id) {
private SQLRow fetchRow(int id) {
if (id < SQLRow.MIN_VALID_ID) {
return null;
} else
1419,32 → 1415,25
}
 
public final ListSQLRequest getRequest() {
// TODO a superclass of ListSQLRequest for use in SQLTableModelSource
// our clients always use either setWhere() or setSelTransf()
// also add the ability to Offline to respect the filter
return ((SQLTableModelSourceOnline) this.getSource()).getReq();
return this.getSource().getReq();
}
 
public final void setRequest(ListSQLRequest listReq) {
// a ListSQLRequest can be changed with setWhere()/setFilterEnable(), so copy it
this.setSource(new SQLTableModelSourceOnline(listReq));
}
 
public final void setSource(SQLTableModelSource src) {
if (src == null)
throw new NullPointerException();
// necessary to limit table model changes, since it recreates columns (and thus forget about
// customizations, eg renderers)
synchronized (this) {
// necessary to limit table model changes, since it recreates columns (and thus forget
// about customizations, eg renderers)
if (this.src == src)
return;
 
this.src = src;
}
this.setTableModel(new ITableModel(src));
}
 
public final SQLTableModelSource getSource() {
final ITableModel m = this.getModel();
return m == null ? null : m.getReq();
public synchronized final SQLTableModelSource getSource() {
return this.src;
}
 
public final File getConfigFile() {
1485,7 → 1474,7
if (!requiredToLive && !this.isDead()) {
this.setTableModel(null);
} else if (requiredToLive && this.isDead()) {
this.setTableModel(new ITableModel(this.src));
this.setTableModel(new ITableModel(this.getSource()));
}
}
 
1577,7 → 1566,7
Thread.sleep(60);
 
final List<String> ancestors = new ArrayList<String>();
final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
final SQLElementDirectory dir = getSource().getElem().getDirectory();
// always put the description of getRows(), but only put their ancestor if they all have
// the same parent
Tuple2<SQLRow, String> parentAndDesc = getParent(this.getRows(), dir);
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/AutoCompletionManager.java
232,6 → 232,15
} else {
rowV = AutoCompletionManager.this.fillFrom.getTable().getRow(id);
}
// Test pour éviter de perdre la sélection d'un article ayant la même désignation
// mais un code différent (Problème remonté par Afhymat avec la tabulation)
if (fillFrom != null && fillFrom.getTable().getName().equals("ARTICLE") && fillFrom.getName().equals("NOM") && rowDest != null && !rowDest.isUndefined()) {
SQLRowAccessor rowArt = rowDest.getForeign("ID_ARTICLE");
if (rowArt != null && !rowArt.isUndefined() && rowArt.getString("NOM") != null && rowArt.getString("NOM").trim().equalsIgnoreCase(rowV.getString("NOM").trim())) {
return;
}
}
 
final Set<String> keys = AutoCompletionManager.this.fillBy.keySet();
// Fill the table model rowvalue with the selected item using the fields defined
// with 'fill'
250,7 → 259,8
|| !AutoCompletionManager.this.table.getRowValuesTableModel().getValueAt(rowE, column).equals(fromV)) {
AutoCompletionManager.this.table.getRowValuesTableModel().setValueAt(fromV, rowE, column);
// Test Only if not foreign --> Bug avec le
// sqltextcombocelleditor, si test edit cellAt -> fire idSelected
// sqltextcombocelleditor, si test edit cellAt -> fire
// idSelected
// -1 sur la combo ce qui entraine une déselection (Bug Remonté
// par SA Poulignier)
if (!AutoCompletionManager.this.foreign && AutoCompletionManager.this.table.getEditingColumn() == column
278,10 → 288,12
}).start();
}
 
public void fillRowValues(SQLRowAccessor from, SQLRowValues to) {
final Set<String> keys = AutoCompletionManager.this.fillBy.keySet();
for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
String fromField = iter.next();
public Set<String> getFieldsFrom() {
return this.fillBy.keySet();
}
 
public void fillRowValues(SQLRowAccessor from, Set<String> fields, SQLRowValues to) {
for (String fromField : fields) {
String toField = AutoCompletionManager.this.fillBy.get(fromField);
to.put(toField, getValueFrom(from.asRow(), fromField, to));
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/AbstractUpdateOneRunnable.java
19,7 → 19,6
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.request.ListSQLRequest;
78,9 → 77,10
final List<ListSQLLine> lines = (List<ListSQLLine>) e.getValue();
// primary table already handled above
if (p.length() > 0 && !lines.isEmpty()) {
final ListSQLRequest updateQueueReq = getModel().getLinesSource().getUpdateQueueReq();
// deepCopy() instead of new SQLRowValues() otherwise the used line's graph will be
// modified (eg the new instance would be linked to it)
final SQLRowValues proto = getModel().getLinesSource().getParent().getMaxGraph().followPathToOne(p, CreateMode.CREATE_NONE, false).deepCopy();
final SQLRowValues proto = updateQueueReq.getGraphToFetch().followPathToOne(p, CreateMode.CREATE_NONE, false).deepCopy();
final String lastReferentField = SearchQueue.getLastReferentField(p);
// there's only one path from the graph start to proto, and we will graft the newly
// fetched values at the end of p, so remove other values
102,25 → 102,22
// reloading rows from the primary table and not just the changed rows)
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(proto);
BaseFillSQLRequest.setupForeign(fetcher);
final ITransformer<SQLSelect, SQLSelect> transf = new ITransformer<SQLSelect, SQLSelect>() {
if (updateQueueReq.isLockSelect()) {
fetcher.appendSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect input) {
if (ListSQLRequest.getDefaultLockSelect())
input.addLockedTable(getTable().getName());
return input.setWhere(getRow().getWhere());
return input;
}
};
fetcher.setSelTransf(transf);
final List<SQLRowValues> fetched = fetcher.fetch();
if (fetched.size() > 1)
throw new IllegalStateException("more than one row fetched for " + this + " with " + fetcher.getReq() + " :\n" + fetched);
});
}
final SQLRowValues soleFetched = fetcher.fetchOne(getRow().getIDNumber());
 
// OK if lastReferentField != null : a referent row has been deleted
if (fetched.size() == 0 && lastReferentField == null) {
if (soleFetched == null && lastReferentField == null) {
Log.get().fine("no row fetched for " + this + ", lines have been changed without the TableModel knowing : " + lines + " req :\n" + fetcher.getReq());
getModel().updateAll();
} else {
final SQLRowValues soleFetched = CollectionUtils.getSole(fetched);
// copy it to each affected lines
for (final ListSQLLine line : lines) {
// don't update a part of the line, if the whole has been be passed to
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTextComboTableCellEditor.java
129,13 → 129,11
}
this.comboBox.grabFocus();
 
this.comboBox.addValueListener(new PropertyChangeListener() {
this.comboBox.addModelListener("wantedID", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!SQLTextComboTableCellEditor.this.comboBox.isUpdating()) {
SQLTextComboTableCellEditor.this.val = SQLTextComboTableCellEditor.this.comboBox.getSelectedId();
public void propertyChange(final PropertyChangeEvent evt) {
SQLTextComboTableCellEditor.this.val = SQLTextComboTableCellEditor.this.comboBox.getWantedID();
}
}
});
// Filtre sur une valeur specifique
if (this.fieldWhere != null && table instanceof RowValuesTable) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSource.java
16,6 → 16,7
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.IPredicate;
 
51,6 → 52,10
 
public abstract SQLTableModelSource getParent();
 
public final ListSQLRequest getUpdateQueueReq() {
return this.getModel().getUpdateQ().getState().getReq();
}
 
public abstract List<ListSQLLine> getAll();
 
/**
126,7 → 131,7
} else {
throw new IllegalArgumentException("No ID for " + v);
}
final ListSQLLine res = new ListSQLLine(this, v, id, this.getModel().getUpdateQ().getState().getAllColumns());
final ListSQLLine res = new ListSQLLine(this, v, id, this.getModel().getUpdateQ().getState());
this.lineCreated(res);
return res;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceOnline.java
13,15 → 13,9
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.change.ListChangeIndex;
 
import java.util.Collections;
 
/**
* A SQLTableModelSource directly tied to the database. Any changes to its lines are propagated to
* the database without any delay.
30,55 → 24,21
*/
public class SQLTableModelSourceOnline extends SQLTableModelSource {
 
private final ListSQLRequest req;
 
public SQLTableModelSourceOnline(ListSQLRequest req) {
super(req.getGraph());
this.req = req;
public SQLTableModelSourceOnline(final ListSQLRequest req, final SQLElement elem) {
super(req, elem);
}
 
public SQLTableModelSourceOnline(SQLTableModelSourceOnline src) {
super(src);
this.req = src.req;
}
 
public final ListSQLRequest getReq() {
return this.req;
}
 
@Override
protected SQLTableModelSourceState createState() {
return new SQLTableModelSourceStateOnline(this.getAllColumns(), this.getReq());
protected boolean allowBiggerGraph() {
return true;
}
 
@Override
protected void colsChanged(final ListChangeIndex<SQLTableModelColumn> change) {
super.colsChanged(change);
// add needed fields for each new column
this.getReq().changeGraphToFetch(new IClosure<SQLRowValues>() {
@Override
public void executeChecked(SQLRowValues g) {
for (final SQLTableModelColumn col : change.getItemsAdded()) {
// DebugRow should uses all *fetched* fields, but since it cannot know them
// getPaths() return all fields in the table. So don't fetch all fields just for
// this debug column
if (!(col instanceof DebugRow)) {
for (final IFieldPath p : col.getPaths()) {
BaseFillSQLRequest.addToFetch(g, p.getPath(), Collections.singleton(p.getFieldName()));
}
}
}
}
});
}
 
@Override
protected SQLTableModelLinesSourceOnline _createLinesSource(final ITableModel model) {
return new SQLTableModelLinesSourceOnline(this, model);
}
 
@Override
public SQLRowValues getMaxGraph() {
return this.getReq().getGraphToFetch();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ITableModel.java
16,6 → 16,7
import static org.openconcerto.sql.view.list.ITableModel.SleepState.AWAKE;
import static org.openconcerto.sql.view.list.ITableModel.SleepState.HIBERNATING;
import static org.openconcerto.sql.view.list.ITableModel.SleepState.SLEEPING;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.model.SQLRowAccessor;
936,5 → 937,4
}
}
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelColumnPath.java
14,6 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowAccessor;
36,14 → 37,10
*/
public class SQLTableModelColumnPath extends SQLTableModelColumn {
 
// try to find a RowItemDesc, fall back to SQLFieldTranslator.getDefaultDesc()
private static final RowItemDesc getDescFor(SQLField field) {
// FIXME : remove Configuration.getInstance() need
return getDescFor(field, Configuration.getInstance());
}
 
public static final RowItemDesc getDescFor(SQLField field, Configuration conf) {
final RowItemDesc res = conf == null ? SQLFieldTranslator.NULL_DESC : conf.getTranslator().getDescFor(field.getTable(), field.getName());
public static final RowItemDesc getDescFor(final SQLField field, SQLElementDirectory dir) {
if (dir == null && Configuration.getInstance() != null)
dir = Configuration.getInstance().getDirectory();
final RowItemDesc res = dir == null ? SQLFieldTranslator.NULL_DESC : dir.getTranslator().getDescFor(field.getTable(), field.getName());
if (res.equals(SQLFieldTranslator.NULL_DESC))
return SQLFieldTranslator.getDefaultDesc(field);
else
50,15 → 47,12
return res;
}
 
private static final String getLabelFor(SQLField field) {
return getDescFor(field).getLabel();
}
 
private final FieldPath p;
private final SQLElementDirectory dir;
private boolean editable;
 
public SQLTableModelColumnPath(Path p, String fieldName, final String name) {
this(new FieldPath(p, fieldName), name);
this(new FieldPath(p, fieldName), name, null);
}
 
public SQLTableModelColumnPath(SQLField f) {
66,12 → 60,13
}
 
public SQLTableModelColumnPath(FieldPath fp) {
this(fp, getDescFor(fp.getField()).getTitleLabel());
this(fp, null, null);
}
 
public SQLTableModelColumnPath(FieldPath fp, final String name) {
super(name);
public SQLTableModelColumnPath(FieldPath fp, final String name, final SQLElementDirectory dir) {
super(name == null ? getDescFor(fp.getField(), dir).getTitleLabel() : name);
this.p = fp;
this.dir = dir;
this.editable = true;
}
 
84,9 → 79,9
public String getToolTip() {
final List<String> humanPath = new ArrayList<String>(this.p.getPath().length());
for (final SQLField f : this.p.getPath().getSingleFields()) {
humanPath.add(getLabelFor(f));
humanPath.add(getDescFor(f, this.dir).getLabel());
}
humanPath.add(getLabelFor(this.p.getField()));
humanPath.add(getDescFor(this.p.getField(), this.dir).getLabel());
return CollectionUtils.join(humanPath, IListe.SEP);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ListSQLLine.java
18,6 → 18,7
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.view.list.search.SearchQueue;
import org.openconcerto.utils.CollectionUtils;
68,7 → 69,7
@GuardedBy("this")
private SQLRowValues row;
// Immutable
private final SQLTableModelColumns columns;
private final SQLTableModelSourceState state;
private final int id;
// allow to order by something not in the row
@GuardedBy("this")
79,12 → 80,12
@GuardedBy("this")
private List<Object> list;
 
ListSQLLine(SQLTableModelLinesSource src, SQLRowValues row, int id, final SQLTableModelColumns columns) {
ListSQLLine(SQLTableModelLinesSource src, SQLRowValues row, int id, final SQLTableModelSourceState state) {
super();
this.src = src;
this.setRow(row);
this.id = id;
this.columns = columns;
this.state = state;
this.clearCache();
}
 
95,6 → 96,14
updateList(columnCount, Collections.<Integer> emptySet());
}
 
public final SQLTableModelSourceState getState() {
return this.state;
}
 
public final SQLTableModelColumns getColumns() {
return this.getState().getAllColumns();
}
 
public final SQLTableModelLinesSource getSrc() {
return this.src;
}
140,7 → 149,7
}
 
public final void setValueAt(Object obj, int colIndex) {
this.columns.getColumns().get(colIndex).put(this, obj);
this.getColumns().getColumns().get(colIndex).put(this, obj);
}
 
// should update this.list at the passed indexes, and then recursively for dependent columns
175,7 → 184,7
for (int i = 0; i < newSize; i++) {
final Object o;
if (i >= alreadyLoaded || colsToUpdate.contains(i))
o = this.columns.getAllColumns().get(i).show(this.getRow());
o = this.getColumns().getAllColumns().get(i).show(this.getRow());
else
o = this.list.get(i);
newList.add(o);
200,7 → 209,11
* @return the columns that were affected, <code>null</code> meaning all.
*/
synchronized Set<Integer> loadAt(int id, SQLRowValues vals, Path p) {
assert vals == null || vals.getID() == id;
final String lastReferentField = SearchQueue.getLastReferentField(p);
// null vals means id was deleted, the only way we care is if it was pointing to us
// (otherwise the foreign key pointing to it would have changed first)
assert vals != null || lastReferentField != null;
// load() empties vals, so getFields() before
final Set<Integer> indexes = lastReferentField == null ? this.pathToIndex(p, vals.getFields()) : null;
// replace our values with the new ones
207,6 → 220,9
final SQLRowValues copy = this.getRow().deepCopy();
if (lastReferentField == null) {
for (final SQLRowValues v : copy.followPath(p, CreateMode.CREATE_NONE, false)) {
// check id, e.g. if p is BATIMENT <- LOCAL -> FAMILLE_LOCAL, then there's multiple
// familles
if (v.getID() == id)
v.load(vals.deepCopy(), null);
}
} else {
267,7 → 283,7
} else {
final Set<Integer> res = new HashSet<Integer>();
final Set<FieldPath> modifiedPaths = FieldPath.create(p, modifiedFields);
final List<? extends SQLTableModelColumn> cols = this.columns.getAllColumns();
final List<? extends SQLTableModelColumn> cols = this.getColumns().getAllColumns();
for (int i = 0; i < cols.size(); i++) {
final SQLTableModelColumn col = cols.get(i);
if (CollectionUtils.containsAny(col.getPaths(), modifiedPaths))
278,8 → 294,9
}
 
private static boolean containsFK(final SQLTable t, Collection<String> fields) {
final Set<SQLField> ffs = t.getFields(VirtualFields.FOREIGN_KEYS);
for (final String f : fields) {
if (t.getForeignKeys().contains(t.getField(f)))
if (ffs.contains(t.getField(f)))
return true;
}
return false;
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSourceOffline.java
16,21 → 16,18
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.OrderComparator;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
import org.openconcerto.sql.model.SQLRowValuesCluster.WalkOptions;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.IClosure;
 
import java.sql.SQLException;
import java.util.ArrayList;
42,6 → 39,7
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
62,16 → 60,16
// row container with equals() using reference equality
@NotThreadSafe
static private final class Row {
private final Number id;
private final Integer id;
private SQLRowValues vals;
 
private Row(Number id, SQLRowValues vals) {
private Row(Integer id, SQLRowValues vals) {
super();
this.id = id;
this.setRow(vals);
}
 
public final Number getID() {
public final Integer getID() {
return this.id;
}
 
109,7 → 107,7
private final List<Row> lines;
// since new lines have no database ID, give them a virtual one
private int freeID;
private final Map<Number, Row> id2line;
private final Map<Integer, Row> id2line;
// values that can be modified, read-only
private final SQLRowValues modifiableVals;
// original value for modified lines
123,7 → 121,7
this.lines = new LinkedList<Row>();
// the firsts are used in other part of the fwk
this.freeID = SQLRow.MIN_VALID_ID - 10;
this.id2line = new HashMap<Number, Row>();
this.id2line = new HashMap<Integer, Row>();
this.dbOrder = true;
}
 
131,19 → 129,6
super(model);
this.parent = parent;
this.modifiableVals = this.getParent().getElem().getPrivateGraph().toImmutable();
if (this.modifiableVals.getGraphSize() > 1) {
// because of updateRow() and commit() (precisely SQLElement.update())
if (this.modifiableVals.hasReferents())
throw new IllegalArgumentException("Referents are not supported");
this.modifiableVals.getGraph().walk(this.modifiableVals, null, new ITransformer<State<Object>, Object>() {
@Override
public Object transformChecked(State<Object> input) {
if (input.isBackwards())
throw new IllegalArgumentException("Referents are not supported");
return null;
}
}, new WalkOptions(Direction.ANY).setRecursionType(RecursionType.BREADTH_FIRST).setStartIncluded(false));
}
this.dbVals = new HashMap<Row, SQLRowValues>();
this.deleted = new HashSet<Number>();
}
157,15 → 142,15
return this.getModel().getUpdateQ().currentlyInQueue();
}
 
private final Row getRow(Number id) {
private final Row getRow(Integer id) {
return this.getRow(id, false);
}
 
private final Row getRow(Number id, final boolean required) {
private final Row getRow(Integer id, final boolean required) {
return this.getRow(id, required, null);
}
 
private final Row getRow(Number id, final boolean required, final Object idSource) {
private final Row getRow(Integer id, final boolean required, final Object idSource) {
final Row res = this.id2line.get(id);
if (required && res == null)
throw new IllegalArgumentException("Not in the list : " + (idSource == null ? id : idSource));
253,14 → 238,10
}
}
 
protected final SQLRowValuesListFetcher getFetcher() {
return ((SQLTableModelSourceStateOffline) this.getModel().getUpdateQ().getState()).getFetcher();
protected final List<SQLRowValues> fetch() {
return this.getUpdateQueueReq().getValues();
}
 
prote