OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Compare Revisions

Regard whitespace Rev 21 → Rev 25

/trunk/OpenConcerto/Configuration/Template/Default/VenteFacture.xml
95,8 → 95,8
<element location="B63" type="fill">
<field base="Societe" table="SAISIE_VENTE_FACTURE" name="ID_MODE_REGLEMENT">
 
<field base="Societe" table="MODE_REGLEMENT" name="NOM" prefix="Règlement souhaité" conditionField="COMPTANT" conditionExpValue="false" display="false"/>
<field base="Societe" table="MODE_REGLEMENT" name="NOM" prefix="Facture acquitée par" conditionField="COMPTANT" conditionExpValue="true" display="false"/>
<field base="Societe" table="MODE_REGLEMENT" name="NOM" prefix="Règlement souhaité" conditionField="COMPTANT" conditionExpValue="true" display="false"/>
<field base="Societe" table="MODE_REGLEMENT" name="NOM" prefix="Facture acquitée par" conditionField="COMPTANT" conditionExpValue="false" display="false"/>
 
<field base="Societe" table="MODE_REGLEMENT" name="ID_TYPE_REGLEMENT">
<field base="Societe" table="TYPE_REGLEMENT" name="NOM" valuesExpected="Indéfini"/>
112,6 → 112,10
 
<element location="B64" type="fill">
<field base="Societe" table="SAISIE_VENTE_FACTURE" name="DATE" type="DateEcheance" prefix="Règlement de cette facture au plus tard le " valuesExpected=" "/>
</element>
 
<element location="B64" type="fill">
<field base="Societe" table="SAISIE_VENTE_FACTURE" name="ID_MODE_REGLEMENT">
 
<field base="Societe" table="MODE_REGLEMENT" name="NOM" prefix="Règlement à date de réception de facture" conditionField="COMPTANT" conditionExpValue="false" display="false"/>
118,11 → 122,7
</field>
</element>
 
<element location="B64" type="fill">
<field base="Societe" table="SAISIE_VENTE_FACTURE" name="DATE" type="DateEcheance" prefix="Règlement de cette facture au plus tard le " valuesExpected=" "/>
</element>
 
<table endPageLine="65" firstLine="63" endLine="65" lastColumn="I" base="Societe" table="TVA">
<element location="I" name="NOM" prefix="Total ">
</element>
/trunk/OpenConcerto/src/org/openconcerto/map/ui/StatusPanel.java
16,7 → 16,8
import org.openconcerto.map.model.Ville;
import org.openconcerto.ui.component.ComboLockedMode;
import org.openconcerto.ui.component.ITextComboCache;
import org.openconcerto.ui.component.ITextSelector;
import org.openconcerto.ui.component.combo.ISearchableTextCombo;
import org.openconcerto.utils.model.DefaultIListModel;
 
import java.awt.Dimension;
import java.awt.GridBagConstraints;
37,7 → 38,6
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
 
 
public class StatusPanel extends JPanel implements VilleRendererListener, ZoomListener {
 
/**
81,7 → 81,7
 
}
/* final JButton button = new JButton("Centrer"); */
ITextSelector txt = new ITextSelector("", ComboLockedMode.ITEMS_LOCKED, 40);
ISearchableTextCombo txt = new ISearchableTextCombo(ComboLockedMode.ITEMS_LOCKED, 1, 40);
txt.addValueListener(new PropertyChangeListener() {
 
public void propertyChange(PropertyChangeEvent evt) {
100,8 → 100,7
txt.setMinimumSearch(0);
txt.setMaximumResult(200);
 
ITextComboCache cache = new ITextComboCacheVille();
txt.initCache(cache);
txt.initCache(new DefaultIListModel<String>(new ITextComboCacheVille().getCache()));
c.weightx = 1;
c.gridx++;
this.add(txt, c);
/trunk/OpenConcerto/src/org/openconcerto/map/ui/ITextComboVilleViewer.java
14,9 → 14,9
package org.openconcerto.map.ui;
 
import org.openconcerto.map.model.Ville;
import org.openconcerto.ui.PopupMouseListener;
import org.openconcerto.ui.component.ComboLockedMode;
import org.openconcerto.ui.component.ITextSelector;
import org.openconcerto.ui.component.IComboCacheListModel;
import org.openconcerto.ui.component.combo.ISearchableTextCombo;
import org.openconcerto.ui.component.text.DocumentComponent;
import org.openconcerto.ui.component.text.TextComponent;
import org.openconcerto.ui.state.WindowStateManager;
27,6 → 27,7
import org.openconcerto.utils.checks.EmptyObjectHelper;
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidState;
import org.openconcerto.utils.text.SimpleDocumentListener;
 
import java.awt.BorderLayout;
import java.awt.Dimension;
39,12 → 40,9
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
 
55,7 → 53,7
* Selecteur de Ville
*/
private static final long serialVersionUID = 3397210337907076649L;
private final ITextSelector text = new ITextSelector(ComboLockedMode.ITEMS_LOCKED);
private final ISearchableTextCombo text = new ISearchableTextCombo(ComboLockedMode.UNLOCKED);
private final JButton button = new JButton("Afficher sur la carte");
private Ville currentVille = null;
private final EmptyObjectHelper emptyHelper;
81,7 → 79,7
});
 
this.cache = new ITextComboCacheVille();
this.text.initCache(this.cache);
new IComboCacheListModel(this.cache).initCacheLater(this.text);
this.add(this.text, BorderLayout.CENTER);
 
this.add(this.button, BorderLayout.EAST);
108,44 → 106,16
}
}
});
this.text.getDocument().addDocumentListener(new DocumentListener() {
 
public void changedUpdate(final DocumentEvent e) {
ITextComboVilleViewer.this.currentVille = Ville.getVilleFromVilleEtCode(ITextComboVilleViewer.this.text.getValue());
this.text.getDocument().addDocumentListener(new SimpleDocumentListener() {
@Override
public void update(DocumentEvent e) {
ITextComboVilleViewer.this.currentVille = Ville.getVilleFromVilleEtCode(getText(e.getDocument()));
ITextComboVilleViewer.this.button.setEnabled(ITextComboVilleViewer.this.currentVille != null && ITextComboVilleViewer.this.isEnabled());
 
}
 
public void insertUpdate(final DocumentEvent e) {
this.changedUpdate(e);
}
 
public void removeUpdate(final DocumentEvent e) {
this.changedUpdate(e);
}
});
final JPopupMenu popupMenu = new JPopupMenu();
// FIXME ajouter la possibilité de supprimer une ville précédemment enregistrée
final JMenuItem menuItem = new JMenuItem("Enregistrer cette ville");
menuItem.addActionListener(new ActionListener() {
 
public void actionPerformed(final ActionEvent e) {
final String t = ITextComboVilleViewer.this.text.getTextComp().getText();
ITextComboVilleViewer.this.cache.addToCache(t);
final Ville createVilleFrom = ITextComboVilleViewer.this.cache.createVilleFrom(t);
if (createVilleFrom != null) {
final String villeEtCode = createVilleFrom.getVilleEtCode();
ITextComboVilleViewer.this.setValue(villeEtCode);
ITextComboVilleViewer.this.firePropertyChange("value", null, villeEtCode);
}
}
});
popupMenu.add(menuItem);
 
this.text.getTextComp().addMouseListener(new PopupMouseListener(popupMenu));
 
}
 
@Override
public void addEmptyListener(final EmptyListener l) {
this.emptyHelper.addListener(l);
198,6 → 168,9
 
@Override
public ValidState getValidState() {
// TODO listen to Ville list, otherwise if we type a city that doesn't exist, the value
// change and we're invalid, then we add the city but this does not change the value of the
// combo and thus we're still invalid even though the city is now in the list
final Ville villeFromVilleEtCode = Ville.getVilleFromVilleEtCode(this.getValue());
final boolean b = villeFromVilleEtCode != null;
if (b) {
224,7 → 197,6
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
this.text.setEnabled(enabled);
this.text.setEditable(enabled);
this.button.setEnabled(enabled);
}
 
/trunk/OpenConcerto/src/org/openconcerto/map/ui/ITextComboCacheVille.java
21,7 → 21,6
 
import javax.swing.JOptionPane;
 
 
public class ITextComboCacheVille implements ITextComboCache {
final ArrayList<String> villesNames = Ville.getVillesNames();
private Ville lastGood;
44,6 → 43,11
return v;
}
 
@Override
public boolean isValid() {
return this.villesNames.size() > 0;
}
 
public void addToCache(String string) {
Ville v = this.createVilleFrom(string);
if (v != null) {
55,6 → 59,9
}
 
public void deleteFromCache(String string) {
final Ville v = Ville.getVilleFromVilleEtCode(string);
if (v != null)
Ville.removeVille(v);
}
 
public List<String> getCache() {
64,7 → 71,8
return villesNames;
}
 
public List<String> loadCache() {
@Override
public List<String> loadCache(final boolean readCache) {
return villesNames;
}
 
/trunk/OpenConcerto/src/org/openconcerto/map/model/Ville.java
32,7 → 32,6
import javax.swing.JFrame;
import javax.swing.UIManager;
 
 
public class Ville {
 
private static Map<String, Ville> map = new HashMap<String, Ville>();
130,6 → 129,16
// FIXME: fire missing
}
 
public static synchronized void removeVille(final Ville v) {
villes.remove(v);
final String villeEtCode = v.getVilleEtCode();
villesNames.remove(villeEtCode);
map.remove(villeEtCode);
 
accessor.delete(v);
// FIXME: fire missing
}
 
// ** getter
 
private static final synchronized void await() {
/trunk/OpenConcerto/src/org/openconcerto/map/model/DatabaseAccessor.java
18,6 → 18,7
public interface DatabaseAccessor {
 
public void store(Ville v);
public void delete(Ville v);
 
public List<Ville> read();
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/ColumnStyle.java
14,7 → 14,6
package org.openconcerto.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.LengthUnit;
import org.openconcerto.openoffice.ODFrame;
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.StyleProperties;
import org.openconcerto.openoffice.StyleStyle;
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/Cell.java
19,6 → 19,7
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.OOXML;
import org.openconcerto.openoffice.StyleDesc;
import org.openconcerto.openoffice.XMLFormatVersion;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.utils.CollectionUtils;
80,8 → 81,8
 
private final Row<D> row;
 
Cell(Row<D> parent, Element elem) {
super(parent.getODDocument(), elem, CellStyle.class);
Cell(Row<D> parent, Element elem, StyleDesc<CellStyle> styleDesc) {
super(parent.getODDocument(), elem, styleDesc);
this.row = parent;
}
 
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/TableCalcNode.java
15,6 → 15,7
 
import org.openconcerto.openoffice.ImmutableDocStyledNode;
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.StyleDesc;
import org.openconcerto.openoffice.StyleStyle;
 
import org.jdom.Element;
34,7 → 35,12
super(parent, local, styleClass);
}
 
protected TableCalcNode(D parent, Element local, StyleDesc<S> styleDesc) {
super(parent, local, styleDesc);
}
 
protected final Namespace getTABLE() {
return this.getODDocument().getVersion().getTABLE();
// a lot faster than asking to the version of our document
return this.getElement().getNamespace();
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/RepeatedBreaker.java
New file
0,0 → 1,97
/*
* 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.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.ODNode;
 
import java.util.List;
 
import org.jdom.Element;
 
abstract class RepeatedBreaker<P, C extends ODNode> {
 
@SuppressWarnings("rawtypes")
static private final RepeatedBreaker CELL_BREAKER = new RepeatedBreaker<Row<?>, Cell<?>>("number-columns-repeated") {
@Override
Cell<?> create(Element elem, Row<?> parent, int index, boolean single) {
return createD(elem, parent, index, single);
}
 
<D extends ODDocument> Cell<D> createD(Element elem, Row<D> parent, int index, boolean single) {
return single ? new MutableCell<D>(parent, elem, parent.getSheet().getCellStyleDesc()) : new Cell<D>(parent, elem, parent.getSheet().getCellStyleDesc());
}
};
 
@SuppressWarnings("rawtypes")
static private final RepeatedBreaker ROW_BREAKER = new RepeatedBreaker<Table<?>, Row<?>>(Axis.ROW.getRepeatedAttrName()) {
@Override
Row<?> create(Element elem, Table<?> parent, int index, boolean single) {
return createD(elem, parent, index, single);
}
 
<D extends ODDocument> Row<D> createD(Element elem, Table<D> parent, int index, boolean single) {
return new Row<D>(parent, elem, index, parent.getRowStyleDesc(), parent.getCellStyleDesc());
}
};
 
@SuppressWarnings("unchecked")
static final <D extends ODDocument> RepeatedBreaker<Row<D>, Cell<D>> getCellBreaker() {
return (RepeatedBreaker<Row<D>, Cell<D>>) CELL_BREAKER;
}
 
@SuppressWarnings("unchecked")
static final <D extends ODDocument> RepeatedBreaker<Table<D>, Row<D>> getRowBreaker() {
return (RepeatedBreaker<Table<D>, Row<D>>) ROW_BREAKER;
}
 
private final String attrName;
 
public RepeatedBreaker(final String attrName) {
this.attrName = attrName;
}
 
abstract C create(final Element elem, final P parent, final int index, final boolean single);
 
public final void breakRepeated(final P parent, final List<C> children, final int col) {
final C c = children.get(col);
final Element element = c.getElement();
final String repeatedS = element.getAttributeValue(this.attrName, element.getNamespace());
if (repeatedS != null) {
final int repeated = Integer.parseInt(repeatedS);
final int firstIndex = children.indexOf(c);
final int lastIndex = firstIndex + repeated - 1;
 
final int preRepeated = col - firstIndex;
final int postRepeated = lastIndex - col;
 
breakRepeated(parent, children, element, firstIndex, preRepeated, true);
element.removeAttribute(this.attrName, element.getNamespace());
breakRepeated(parent, children, element, col + 1, postRepeated, false);
}
children.set(col, this.create(element, parent, col, true));
}
 
private final void breakRepeated(final P parent, final List<C> children, Element element, int firstIndex, int repeat, boolean before) {
if (repeat > 0) {
final Element newElem = (Element) element.clone();
element.getParentElement().addContent(element.getParent().indexOf(element) + (before ? 0 : 1), newElem);
newElem.setAttribute(this.attrName, repeat + "", element.getNamespace());
final C preCell = this.create(newElem, parent, firstIndex, false);
for (int i = 0; i < repeat; i++) {
children.set(firstIndex + i, preCell);
}
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/Table.java
16,6 → 16,7
import org.openconcerto.openoffice.LengthUnit;
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.Style;
import org.openconcerto.openoffice.StyleDesc;
import org.openconcerto.openoffice.StyleStyleDesc;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.SheetTableModel.MutableTableModel;
30,7 → 31,6
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
60,92 → 60,105
}
 
// ATTN Row have their index as attribute
private final List<Row<D>> rows;
private int headerRowCount;
private final List<Column<D>> cols;
private int headerColumnCount;
private final ArrayList<Row<D>> rows;
private TableGroup rowGroup;
private final ArrayList<Column<D>> cols;
private TableGroup columnGroup;
 
public Table(D parent, Element local) {
super(parent, local, TableStyle.class);
 
this.rows = new ArrayList<Row<D>>();
this.cols = new ArrayList<Column<D>>();
this.rows = new ArrayList<Row<D>>(64);
this.cols = new ArrayList<Column<D>>(32);
 
// read columns first since Row constructor needs it
this.readColumns();
this.readRows();
}
 
private void readColumns() {
this.read(true);
this.read(Axis.COLUMN);
}
 
private final void readRows() {
this.read(false);
this.read(Axis.ROW);
}
 
private final void read(final boolean col) {
final Tuple2<List<Element>, Integer> r = flatten(col);
(col ? this.cols : this.rows).clear();
for (final Element clone : r.get0()) {
if (col)
this.addCol(clone);
else
this.addRow(clone);
private final void read(final Axis axis) {
final boolean col = axis == Axis.COLUMN;
final Tuple2<TableGroup, List<Element>> r = TableGroup.createRoot(this, axis);
final ArrayList<?> l = col ? this.cols : this.rows;
final int oldSize = l.size();
l.clear();
final int newSize = r.get0().getSize();
l.ensureCapacity(newSize);
if (col) {
final StyleStyleDesc<ColumnStyle> colStyleDesc = getColumnStyleDesc();
for (final Element clone : r.get1())
this.addCol(clone, colStyleDesc);
this.columnGroup = r.get0();
} else {
final StyleStyleDesc<RowStyle> rowStyleDesc = getRowStyleDesc();
final StyleStyleDesc<CellStyle> cellStyleDesc = getCellStyleDesc();
for (final Element clone : r.get1())
this.addRow(clone, rowStyleDesc, cellStyleDesc);
this.rowGroup = r.get0();
}
if (col)
this.headerColumnCount = r.get1();
else
this.headerRowCount = r.get1();
// this always copy the array, so make sure we reclaim enough memory (~ 64k)
if (oldSize - newSize > 8192) {
l.trimToSize();
}
assert newSize == (col ? this.getColumnCount() : this.getRowCount());
}
 
private final void addCol(Element clone) {
this.cols.add(new Column<D>(this, clone));
private final void addCol(Element clone, StyleStyleDesc<ColumnStyle> colStyleDesc) {
this.cols.add(new Column<D>(this, clone, colStyleDesc));
}
 
private Tuple2<List<Element>, Integer> flatten(boolean col) {
final List<Element> res = new ArrayList<Element>();
final Element header = this.getElement().getChild("table-header-" + getName(col) + "s", getTABLE());
if (header != null)
res.addAll(flatten(header, col));
final int headerCount = res.size();
static final int flattenChildren(final List<Element> res, final Element elem, final Axis axis) {
int count = 0;
// array so that flatten1() can modify an int
int[] index = new int[] { 0 };
// copy since we will change our children (don't use List.listIterator(int) since it
// re-filters all content)
@SuppressWarnings("unchecked")
final List<Element> children = new ArrayList<Element>(elem.getChildren(axis.getElemName(), elem.getNamespace()));
final int stop = children.size();
for (int i = 0; i < stop; i++) {
final Element row = children.get(i);
count += flatten1(res, row, axis, index);
}
return count;
}
 
res.addAll(flatten(getElement(), col));
 
return Tuple2.create(res, headerCount);
static int flatten1(final List<Element> res, final Element row, final Axis axis) {
return flatten1(res, row, axis, null);
}
 
@SuppressWarnings("unchecked")
private List<Element> flatten(final Element elem, boolean col) {
final String childName = getName(col);
final List<Element> children = elem.getChildren("table-" + childName, getTABLE());
// not final, since iter.add() does not work consistently, and
// thus we must recreate an iterator each time
ListIterator<Element> iter = children.listIterator();
while (iter.hasNext()) {
final Element row = iter.next();
final Attribute repeatedAttr = row.getAttribute("number-" + childName + "s-repeated", getTABLE());
if (repeatedAttr != null) {
// add XML elements to res and return the logical count
private static int flatten1(final List<Element> res, final Element row, final Axis axis, final int[] parentIndex) {
final int resSize = res.size();
final Attribute repeatedAttr = axis.getRepeatedAttr(row);
final int repeated = repeatedAttr == null ? 1 : Integer.parseInt(repeatedAttr.getValue());
if (axis == Axis.COLUMN && repeated > 1) {
row.removeAttribute(repeatedAttr);
final int index = iter.previousIndex();
int repeated = Integer.parseInt(repeatedAttr.getValue());
if (repeated > 60000) {
repeated = 10;
}
final Element parent = row.getParentElement();
final int index = (parentIndex == null ? parent.indexOf(row) : parentIndex[0]) + 1;
res.add(row);
// -1 : we keep the original row
for (int i = 0; i < repeated - 1; i++) {
final Element clone = (Element) row.clone();
// cannot use iter.add() since on JDOM 1.1 if row is the last table-column
// before table-row the clone is added at the very end
children.add(index, clone);
res.add(clone);
parent.addContent(index + i, clone);
}
// restart after the added rows
iter = children.listIterator(index + repeated);
} else {
res.add(row);
}
if (parentIndex != null)
parentIndex[0] += res.size() - resSize;
return repeated;
}
 
return children;
}
 
public final String getName() {
return getName(this.getElement());
}
158,10 → 171,6
this.getElement().detach();
}
 
private final String getName(boolean col) {
return col ? "column" : "row";
}
 
public final Object getPrintRanges() {
return this.getElement().getAttributeValue("print-ranges", this.getTABLE());
}
235,14 → 244,38
 
// clone xml elements and add them to our tree
final List<Element> clones = new ArrayList<Element>(count * copies);
for (int i = 0; i < copies; i++) {
for (int l = start; l < stop; l++) {
final Element r = this.rows.get(l).getElement();
clones.add((Element) r.clone());
for (int l = start; l < stop;) {
final Row<D> immutableRow = this.getRow(l);
final Row<D> toClone;
// MAYBE use something else than getMutableRow() since we don't need a single row.
// the repeated row starts before the copied range, split it at the beginning
if (immutableRow.getY() < l) {
toClone = this.getMutableRow(l);
} else {
assert immutableRow.getY() == l;
if (immutableRow.getLastY() >= stop) {
// the repeated row goes beyond the copied range, split it at the end
assert this.getRow(stop) == immutableRow;
this.getMutableRow(stop);
toClone = this.getRow(l);
} else {
toClone = immutableRow;
}
}
assert toClone.getY() == l;
assert toClone.getLastY() < stop : "Row goes to far";
l += toClone.getRepeated();
clones.add((Element) toClone.getElement().clone());
}
final int clonesSize = clones.size();
for (int i = 1; i < copies; i++) {
for (int j = 0; j < clonesSize; j++) {
clones.add((Element) clones.get(j).clone());
}
}
// works anywhere its XML element is
JDOMUtils.insertAfter(this.rows.get(stop - 1).getElement(), clones);
assert this.getRow(stop - 1).getLastY() == stop - 1 : "Adding XML element too far";
JDOMUtils.insertAfter(this.getRow(stop - 1).getElement(), clones);
 
for (final Point coverOrigin : coverOriginsToUpdate) {
final MutableCell<D> coveringCell = getCellAt(coverOrigin);
249,7 → 282,7
coveringCell.setRowsSpanned(coveringCell.getRowsSpanned() + count * copies);
}
 
// synchronize our rows with our new tree
// synchronize our rows with our new tree (rows' index have changed)
this.readRows();
 
// 19.627 in OpenDocument-v1.2-cs01-part1 : The table:end-cell-address attribute specifies
323,9 → 356,13
}
}
 
private synchronized void addRow(Element child) {
this.rows.add(new Row<D>(this, child, this.rows.size()));
private synchronized void addRow(Element child, StyleDesc<RowStyle> styleDesc, StyleDesc<CellStyle> cellStyleDesc) {
final Row<D> row = new Row<D>(this, child, this.rows.size(), styleDesc, cellStyleDesc);
final int toRepeat = row.getRepeated();
for (int i = 0; i < toRepeat; i++) {
this.rows.add(row);
}
}
 
public final Point resolveHint(String ref) {
final Point res = resolve(ref);
346,8 → 383,18
return this.getImmutableCellAt(x, y).isValid();
}
 
/**
* Return a modifiable cell at the passed coordinates. This is slower than
* {@link #getImmutableCellAt(int, int)} since this method may modify the underlying XML (e.g.
* break up repeated cells to allow for modification of only the returned cell).
*
* @param x the column.
* @param y the row.
* @return the cell.
* @see #getImmutableCellAt(int, int)
*/
public final MutableCell<D> getCellAt(int x, int y) {
return this.getRow(y).getMutableCellAt(x);
return this.getMutableRow(y).getMutableCellAt(x);
}
 
public final MutableCell<D> getCellAt(String ref) {
375,11 → 422,20
 
// *** get cell
 
protected final Cell<D> getImmutableCellAt(int x, int y) {
/**
* Return a non modifiable cell at the passed coordinates. This is faster than
* {@link #getCellAt(int, int)} since this method never modifies the underlying XML.
*
* @param x the column.
* @param y the row.
* @return the cell.
* @see #getCellAt(int, int)
*/
public final Cell<D> getImmutableCellAt(int x, int y) {
return this.getRow(y).getCellAt(x);
}
 
protected final Cell<D> getImmutableCellAt(String ref) {
public final Cell<D> getImmutableCellAt(String ref) {
final Point p = resolveHint(ref);
return this.getImmutableCellAt(p.x, p.y);
}
457,7 → 513,7
}
 
public final CellStyle getStyleAt(int column, int row) {
return getCellStyleDesc().findStyle(this.getODDocument().getPackage(), this.getElement().getDocument(), this.getStyleNameAt(column, row));
return getCellStyleDesc().findStyleForNode(this.getImmutableCellAt(column, row), this.getStyleNameAt(column, row));
}
 
protected StyleStyleDesc<CellStyle> getCellStyleDesc() {
506,6 → 562,14
return res;
}
 
protected final StyleStyleDesc<ColumnStyle> getColumnStyleDesc() {
return Style.getStyleStyleDesc(ColumnStyle.class, XMLVersion.getVersion(getElement()));
}
 
protected final StyleStyleDesc<RowStyle> getRowStyleDesc() {
return Style.getStyleStyleDesc(RowStyle.class, XMLVersion.getVersion(getElement()));
}
 
/**
* Retourne la valeur de la cellule spécifiée.
*
518,10 → 582,20
 
// *** get count
 
private Row<D> getRow(int index) {
final Row<D> getRow(int index) {
return this.rows.get(index);
}
 
final Row<D> getMutableRow(int y) {
final Row<D> c = this.getRow(y);
if (c.getRepeated() > 1) {
RepeatedBreaker.<D> getRowBreaker().breakRepeated(this, this.rows, y);
return this.getRow(y);
} else {
return c;
}
}
 
public final Column<D> getColumn(int i) {
return this.cols.get(i);
}
530,8 → 604,22
return this.rows.size();
}
 
public final TableGroup getRowGroup() {
return this.rowGroup;
}
 
/**
* Return the deepest group at the passed row.
*
* @param y a row index.
* @return the group at the index, never <code>null</code>.
*/
public final TableGroup getRowGroupAt(final int y) {
return this.getRowGroup().getDescendentOrSelfContaining(y);
}
 
public final int getHeaderRowCount() {
return this.headerRowCount;
return this.getRowGroup().getFollowingHeaderCount();
}
 
public final int getColumnCount() {
538,8 → 626,22
return this.cols.size();
}
 
public final TableGroup getColumnGroup() {
return this.columnGroup;
}
 
/**
* Return the deepest group at the passed column.
*
* @param x a column index.
* @return the group at the index, never <code>null</code>.
*/
public final TableGroup getColumnGroupAt(final int x) {
return this.getColumnGroup().getDescendentOrSelfContaining(x);
}
 
public final int getHeaderColumnCount() {
return this.headerColumnCount;
return this.getColumnGroup().getFollowingHeaderCount();
}
 
// *** set count
595,17 → 697,22
} else {
elemToClone = getColumn(colIndex).getElement();
}
final StyleStyleDesc<ColumnStyle> columnStyleDesc = getColumnStyleDesc();
for (int i = 0; i < toGrow; i++) {
final Element newElem = (Element) elemToClone.clone();
this.getElement().addContent(indexOfLastCol + 1 + i, newElem);
this.cols.add(new Column<D>(this, newElem));
this.cols.add(new Column<D>(this, newElem, columnStyleDesc));
}
// now update widths
updateWidth(keepTableWidth);
 
// add needed cells
for (final Row r : this.rows) {
r.columnCountChanged();
final StyleStyleDesc<CellStyle> cellStyleDesc = this.getCellStyleDesc();
final int rowCount = this.getRowCount();
for (int i = 0; i < rowCount;) {
final Row<D> r = this.getRow(i);
r.columnCountChanged(cellStyleDesc);
i += r.getRepeated();
}
}
}
629,16 → 736,21
*/
public final void removeColumn(int firstIndex, int lastIndex, final boolean keepTableWidth) {
// first check that removeCells() will succeed, so that we avoid an incoherent XML state
for (final Row r : this.rows) {
final int rowCount = this.getRowCount();
for (int i = 0; i < rowCount;) {
final Row<D> r = this.getRow(i);
r.checkRemove(firstIndex, lastIndex);
i += r.getRepeated();
}
// rm column element
remove(true, firstIndex, lastIndex - 1);
remove(Axis.COLUMN, firstIndex, lastIndex - 1);
// update widths
updateWidth(keepTableWidth);
// rm cells
for (final Row r : this.rows) {
for (int i = 0; i < rowCount;) {
final Row<D> r = this.getRow(i);
r.removeCells(firstIndex, lastIndex);
i += r.getRepeated();
}
}
 
709,20 → 821,41
return colStyle;
}
 
private final void setCount(final boolean col, final int newSize) {
private final void setCount(final Axis col, final int newSize) {
this.remove(col, newSize, -1);
}
 
// both inclusive
private final void remove(final boolean col, final int fromIndex, final int toIndexIncl) {
// ok since rows and cols are flattened in ctor
final List<? extends TableCalcNode> l = col ? this.cols : this.rows;
private final void remove(final Axis col, final int fromIndex, final int toIndexIncl) {
assert col == Axis.COLUMN || toIndexIncl < 0 : "Row index will be wrong";
final List<? extends TableCalcNode<?, ?>> l = col == Axis.COLUMN ? this.cols : this.rows;
final int toIndexValid = CollectionUtils.getValidIndex(l, toIndexIncl);
for (int i = toIndexValid; i >= fromIndex; i--) {
// works anywhere its XML element is
l.remove(i).getElement().detach();
final int toRemoveCount = toIndexValid - fromIndex + 1;
int removedCount = 0;
while (removedCount < toRemoveCount) {
// works backwards to keep y OK
final int i = toIndexValid - removedCount;
final TableCalcNode<?, ?> removed = l.get(i);
if (removed instanceof Row) {
final Row<?> r = (Row<?>) removed;
final int removeFromRepeated = i - Math.max(fromIndex, r.getY()) + 1;
// removedCount grows each iteration
assert removeFromRepeated > 0;
final int newRepeated = r.getRepeated() - removeFromRepeated;
if (newRepeated == 0)
removed.getElement().detach();
else
r.setRepeated(newRepeated);
removedCount += removeFromRepeated;
} else {
// Columns are always flattened
removed.getElement().detach();
removedCount++;
}
}
// one remove to be efficient
l.subList(fromIndex, toIndexValid + 1).clear();
}
 
public final void ensureRowCount(int newSize) {
if (newSize > this.getRowCount())
741,25 → 874,24
* @param rowIndex the index of the row to be copied, -1 for empty row (i.e. default style).
*/
public final void setRowCount(int newSize, int rowIndex) {
final int toGrow = newSize - this.getRowCount();
if (toGrow < 0) {
setCount(Axis.ROW, newSize);
} else if (toGrow > 0) {
final Element elemToClone;
if (rowIndex < 0) {
elemToClone = Row.createEmpty(this.getODDocument().getVersion());
// each row MUST have the same number of columns
elemToClone.addContent(Cell.createEmpty(this.getODDocument().getVersion(), this.getColumnCount()));
} else
elemToClone = getRow(rowIndex).getElement();
final int toGrow = newSize - this.getRowCount();
if (toGrow < 0) {
setCount(false, newSize);
} else {
for (int i = 0; i < toGrow; i++) {
final Element newElem = (Element) elemToClone.clone();
elemToClone = (Element) getRow(rowIndex).getElement().clone();
}
Axis.ROW.setRepeated(elemToClone, toGrow);
// as per section 8.1.1 rows are the last elements inside a table
this.getElement().addContent(newElem);
addRow(newElem);
this.getElement().addContent(elemToClone);
addRow(elemToClone, getRowStyleDesc(), getCellStyleDesc());
}
}
}
 
// *** table models
 
948,7 → 1080,7
public final Range getCurrentRegion() {
while (this.checkFrame())
;// bounded by table size
return new Range(getName(), new Point(minX, minY), new Point(maxX, maxY));
return new Range(getName(), new Point(this.minX, this.minY), new Point(this.maxX, this.maxY));
}
}
 
968,7 → 1100,8
* @param startX x coordinate.
* @param startY y coordinate.
* @return the smallest range containing the passed cell.
* @see http://msdn.microsoft.com/library/aa214248(v=office.11).aspx
* @see <a href="http://msdn.microsoft.com/library/aa214248(v=office.11).aspx">CurrentRegion
* Property</a>
*/
public final Range getCurrentRegion(final int startX, final int startY) {
return this.getCurrentRegion(startX, startY, false);
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/MutableCell.java
18,11 → 18,13
import org.openconcerto.openoffice.ODFrame;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.OOXML;
import org.openconcerto.openoffice.StyleDesc;
import org.openconcerto.openoffice.spreadsheet.BytesProducer.ByteArrayProducer;
import org.openconcerto.openoffice.spreadsheet.BytesProducer.ImageProducer;
import org.openconcerto.openoffice.style.data.DataStyle;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.Tuple3;
 
import java.awt.Color;
import java.awt.Image;
36,6 → 38,9
import java.util.Date;
import java.util.List;
 
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.Duration;
 
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Namespace;
51,6 → 56,7
 
static private final DateFormat TextPDateFormat = DateFormat.getDateInstance();
static private final DateFormat TextPTimeFormat = DateFormat.getTimeInstance();
static private final NumberFormat TextPMinuteSecondFormat = new DecimalFormat("00.###");
static private final NumberFormat TextPFloatFormat = DecimalFormat.getNumberInstance();
static private final NumberFormat TextPPercentFormat = DecimalFormat.getPercentInstance();
static private final NumberFormat TextPCurrencyFormat = DecimalFormat.getCurrencyInstance();
76,8 → 82,8
}
}
 
MutableCell(Row<D> parent, Element elem) {
super(parent, elem);
MutableCell(Row<D> parent, Element elem, StyleDesc<CellStyle> styleDesc) {
super(parent, elem, styleDesc);
}
 
// ask our column to our row so we don't have to update anything when columns are removed/added
142,6 → 148,10
}
 
public void setValue(Object obj) {
this.setValue(obj, true);
}
 
public void setValue(Object obj, final boolean allowTypeChange) throws UnsupportedOperationException {
final ODValueType type;
final ODValueType currentType = getValueType();
// try to keep current type, since for example a Number can work with FLOAT, PERCENTAGE
148,18 → 158,13
// and CURRENCY
if (currentType != null && currentType.canFormat(obj.getClass())) {
type = currentType;
} else if (obj instanceof Number) {
type = ODValueType.FLOAT;
} else if (obj instanceof Date || obj instanceof Calendar) {
type = ODValueType.DATE;
} else if (obj instanceof Boolean) {
type = ODValueType.BOOLEAN;
} else if (obj instanceof String) {
type = ODValueType.STRING;
} else {
type = ODValueType.forObject(obj);
}
if (type == null) {
throw new IllegalArgumentException("Couldn't infer type of " + obj);
}
this.setValue(obj, type, true);
this.setValue(obj, type, allowTypeChange, true);
}
 
/**
167,16 → 172,20
*
* @param obj the new cell value.
* @param vt the value type.
* @param allowTypeChange if <code>true</code> <code>obj</code> and <code>vt</code> might be
* changed to allow the data style to format, e.g. from Boolean.FALSE to 0.
* @param lenient <code>false</code> to throw an exception if we can't format according to the
* ODF, <code>true</code> to try best-effort.
* @throws UnsupportedOperationException if <code>obj</code> couldn't be formatted.
*/
public void setValue(final Object obj, final ODValueType vt, final boolean lenient) throws UnsupportedOperationException {
public void setValue(Object obj, ODValueType vt, final boolean allowTypeChange, final boolean lenient) throws UnsupportedOperationException {
final String text;
final String formatted = format(obj, lenient);
final Tuple3<String, ODValueType, Object> formatted = format(obj, vt, !allowTypeChange, lenient);
vt = formatted.get1();
obj = formatted.get2();
 
if (formatted != null) {
text = formatted;
if (formatted.get0() != null) {
text = formatted.get0();
} else {
// either there were no format or formatting failed
if (vt == ODValueType.FLOAT) {
186,9 → 195,21
} else if (vt == ODValueType.CURRENCY) {
text = formatCurrency((Number) obj, getDefaultStyle());
} else if (vt == ODValueType.DATE) {
text = TextPDateFormat.format(obj);
final Date d;
if (obj instanceof Calendar) {
d = ((Calendar) obj).getTime();
} else {
d = (Date) obj;
}
text = TextPDateFormat.format(d);
} else if (vt == ODValueType.TIME) {
text = TextPTimeFormat.format(obj);
if (obj instanceof Duration) {
final Duration normalized = getODDocument().getEpoch().normalizeToHours((Duration) obj);
text = "" + normalized.getHours() + ':' + TextPMinuteSecondFormat.format(normalized.getMinutes()) + ':'
+ TextPMinuteSecondFormat.format(normalized.getField(DatatypeConstants.SECONDS));
} else {
text = TextPTimeFormat.format(((Calendar) obj).getTime());
}
} else if (vt == ODValueType.BOOLEAN) {
if (lenient)
text = obj.toString();
203,14 → 224,19
this.setValue(vt, obj, text);
}
 
// return null if no data style exists, or if one exists but we couldn't use it
private String format(Object obj, boolean lenient) {
// return null String if no data style exists, or if one exists but we couldn't use it
private Tuple3<String, ODValueType, Object> format(Object obj, ODValueType valueType, boolean onlyCast, boolean lenient) {
String res = null;
try {
final DataStyle ds = getDataStyle();
final Tuple3<DataStyle, ODValueType, Object> ds = getDataStyleAndValue(obj, valueType, onlyCast);
if (ds != null) {
obj = ds.get2();
valueType = ds.get1();
// act like OO, that is if we set a String to a Date cell, change the value and
// value-type but leave the data-style untouched
if (ds != null && ds.canFormat(obj.getClass()))
return ds.format(obj, getDefaultStyle(), lenient);
if (ds.get0().canFormat(obj.getClass()))
res = ds.get0().format(obj, getDefaultStyle(), lenient);
}
} catch (UnsupportedOperationException e) {
if (lenient)
Log.get().warning(ExceptionUtils.getStackTrace(e));
217,12 → 243,17
else
throw e;
}
return null;
return Tuple3.create(res, valueType, obj);
}
 
public final DataStyle getDataStyle() {
final Tuple3<DataStyle, ODValueType, Object> s = this.getDataStyleAndValue(this.getValue(), this.getValueType(), true);
return s != null ? s.get0() : null;
}
 
private final Tuple3<DataStyle, ODValueType, Object> getDataStyleAndValue(Object obj, ODValueType valueType, boolean onlyCast) {
final CellStyle s = this.getStyle();
return s != null ? getStyle().getDataStyle() : null;
return s != null ? getStyle().getDataStyle(obj, valueType, onlyCast) : null;
}
 
protected final CellStyle getDefaultStyle() {
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/Column.java
14,6 → 14,7
package org.openconcerto.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.StyleStyleDesc;
import org.openconcerto.openoffice.XMLVersion;
 
import org.jdom.Element;
30,8 → 31,8
return res;
}
 
public Column(final Table<D> parent, Element tableColElem) {
super(parent.getODDocument(), tableColElem, ColumnStyle.class);
public Column(final Table<D> parent, Element tableColElem, StyleStyleDesc<ColumnStyle> colStyleDesc) {
super(parent.getODDocument(), tableColElem, colStyleDesc);
}
 
public final Float getWidth() {
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/CellStyle.java
13,23 → 13,44
package org.openconcerto.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.Log;
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.Style;
import org.openconcerto.openoffice.StyleStyle;
import org.openconcerto.openoffice.StyleStyleDesc;
import org.openconcerto.openoffice.StyledNode;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.style.RelationalOperator;
import org.openconcerto.openoffice.style.SideStyleProperties;
import org.openconcerto.openoffice.style.data.BooleanStyle;
import org.openconcerto.openoffice.style.data.DataStyle;
import org.openconcerto.openoffice.style.data.NumberStyle;
import org.openconcerto.openoffice.text.ParagraphStyle.StyleParagraphProperties;
import org.openconcerto.openoffice.text.TextStyle.StyleTextProperties;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.Tuple3;
import org.openconcerto.xml.JDOMUtils;
 
import java.awt.Color;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import org.jdom.Attribute;
import org.jdom.Element;
 
public class CellStyle extends StyleStyle {
 
private static final Pattern numberPatrn = Pattern.compile("-?\\d+(?:\\.\\d+)?");
private static final Pattern escapedQuotePatrn = Pattern.compile("\"\"", Pattern.LITERAL);
private static final Pattern stringPatrn = Pattern.compile("\"(?:[^\\p{Cntrl}\"]|\\p{Space}|" + escapedQuotePatrn.pattern() + ")*\"");
private static final String valuePatrn = "(" + numberPatrn.pattern() + "|" + stringPatrn.pattern() + ")";
private static final Pattern cellContentPatrn = Pattern.compile("cell-content\\(\\) *(" + RelationalOperator.OR_PATTERN + ") *" + valuePatrn + "");
private static final Pattern cellContentBetweenPatrn = Pattern.compile("cell-content-is(?:-not)?-between\\(" + valuePatrn + ", *" + valuePatrn + "\\)");
 
// from section 18.728 in v1.2-part1
public static final StyleStyleDesc<CellStyle> DESC = new StyleStyleDesc<CellStyle>(CellStyle.class, XMLVersion.OD, "table-cell", "ce", "table", Arrays.asList("table:body",
"table:covered-table-cell", "table:even-rows", "table:first-column", "table:first-row", "table:last-column", "table:last-row", "table:odd-columns", "table:odd-rows", "table:table-cell")) {
42,8 → 63,54
public CellStyle create(ODPackage pkg, Element e) {
return new CellStyle(pkg, e);
}
 
@Override
protected boolean supportConditions() {
return true;
}
 
@Override
protected Element evaluateConditions(final StyledNode<CellStyle, ?> styledNode, final List<Element> styleMaps) {
final Cell<?> cell = (Cell<?>) styledNode;
final Object cellValue = cell.getValue();
for (final Element styleMap : styleMaps) {
final String condition = styleMap.getAttributeValue("condition", getVersion().getSTYLE()).trim();
Matcher matcher = cellContentPatrn.matcher(condition);
if (matcher.matches()) {
if (RelationalOperator.getInstance(matcher.group(1)).compare(cellValue, parse(matcher.group(2))))
return styleMap;
} else if ((matcher = cellContentBetweenPatrn.matcher(condition)).matches()) {
final boolean wantBetween = condition.startsWith("cell-content-is-between");
assert wantBetween ^ condition.startsWith("cell-content-is-not-between");
final Object o1 = parse(matcher.group(1));
final Object o2 = parse(matcher.group(2));
final boolean isBetween = CompareUtils.compare(cellValue, o1) >= 0 && CompareUtils.compare(cellValue, o2) <= 0;
if (isBetween == wantBetween)
return styleMap;
} else {
// If a consumer does not recognize a condition, it shall ignore the <style:map>
// element containing the condition.
Log.get().fine("Ignoring " + JDOMUtils.output(styleMap));
}
}
return null;
}
};
 
private static final Pattern conditionPatrn = Pattern.compile("value\\(\\) *(" + RelationalOperator.OR_PATTERN + ") *(true|false|" + numberPatrn.pattern() + ")");
 
// from style:condition :
// "n is a number for non-Boolean data styles and true or false for Boolean data styles"
private static final Object convertForCondition(final Object value, final DataStyle style) {
final Object castedValue;
if (style instanceof BooleanStyle) {
castedValue = BooleanStyle.toBoolean(value);
} else {
castedValue = NumberStyle.toNumber(value, style.getEpoch());
}
return castedValue;
}
 
private StyleTableCellProperties cellProps;
private StyleTextProperties textProps;
private StyleParagraphProperties pProps;
52,10 → 119,67
super(pkg, tableColElem);
}
 
public final DataStyle getDataStyle() {
return (DataStyle) Style.getReferencedStyle(getPackage(), getElement().getAttribute("data-style-name", getSTYLE()));
private final DataStyle getDataStyle(final Attribute name) {
return (DataStyle) Style.getReferencedStyle(getPackage(), name);
}
 
// return value since it can be changed depending on the data style.
// e.g. in OO if we input 12:30 in an empty cell, it will have value-type="time"
// but if we had previously set a number style (like 0,00) it would have been converted to 0,52
// value-type="float"
final Tuple3<DataStyle, ODValueType, Object> getDataStyle(final Object cellValue, final ODValueType valueType, final boolean onlyCast) {
DataStyle res = getDataStyle(this.getElement().getAttribute("data-style-name", this.getSTYLE()));
ODValueType returnValueType = valueType;
Object returnCellValue = cellValue;
// if the type is null, then the cell is empty so don't try to convert the cell value or
// evaluate conditions
if (res != null && valueType != null) {
if (!onlyCast) {
final Object convertedForStyle = res.convert(cellValue);
// if conversion is successful
if (convertedForStyle != null) {
returnCellValue = convertedForStyle;
returnValueType = res.getDataType();
}
}
 
final List<?> styleMaps = res.getElement().getChildren("map", getSTYLE());
if (styleMaps.size() > 0) {
final Object converted = convertForCondition(returnCellValue, res);
// we can't compare() so don't try
if (converted != null) {
for (Object child : styleMaps) {
final Element styleMap = (Element) child;
final Matcher matcher = conditionPatrn.matcher(styleMap.getAttributeValue("condition", getSTYLE()).trim());
if (!matcher.matches())
throw new IllegalStateException("Cannot parse " + JDOMUtils.output(styleMap));
if (RelationalOperator.getInstance(matcher.group(1)).compare(converted, parse(matcher.group(2)))) {
res = getDataStyle(styleMap.getAttribute("apply-style-name", getSTYLE()));
break;
}
}
}
}
}
// if the type is null, then the cell is empty, we cannot make up some value, otherwise
// don't change it to null
assert (valueType == null) == (returnValueType == null) : "don't change type to null";
assert !onlyCast || (returnValueType == valueType && returnCellValue == cellValue) : "Requested to only cast, but different object";
// if res is null, the document is incoherent (non existing style name)
return res == null ? null : Tuple3.create(res, returnValueType, returnCellValue);
}
 
static private Object parse(String val) {
if (val.equalsIgnoreCase("true"))
return Boolean.TRUE;
else if (val.equalsIgnoreCase("false"))
return Boolean.FALSE;
else if (val.charAt(0) == '"')
return escapedQuotePatrn.matcher(val.substring(1, val.length() - 1)).replaceAll("\"");
else
return new BigDecimal(val);
}
 
public final Color getBackgroundColor() {
return getTableCellProperties().getBackgroundColor();
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/SheetTableModel.java
14,6 → 14,7
package org.openconcerto.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.utils.CompareUtils;
 
import javax.swing.table.AbstractTableModel;
 
76,6 → 77,50
throw new IndexOutOfBoundsException("column: " + columnIndex + " not between 0 and " + (this.getColumnCount() - 1));
}
 
@Override
public int hashCode() {
final int rowCount = getRowCount();
final int columnCount = getColumnCount();
final int prime = 17;
int result = 1;
result = prime * result + rowCount;
result = prime * result + columnCount;
// use some of the values
final int maxX = Math.min(4, columnCount);
final int maxY = Math.min(8, rowCount);
for (int y = 0; y < maxY; y++) {
for (int x = 0; x < maxX; x++) {
final Object v = this.getValueAt(x, y);
result = prime * result + (v == null ? 0 : v.hashCode());
}
}
return result;
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof SheetTableModel))
return false;
final SheetTableModel<?> other = (SheetTableModel<?>) obj;
 
final int rowCount = this.getRowCount();
final int columnCount = this.getColumnCount();
if (other.getRowCount() != rowCount || other.getColumnCount() != columnCount)
return false;
 
for (int y = 0; y < rowCount; y++) {
for (int x = 0; x < columnCount; x++) {
if (!CompareUtils.equals(this.getValueAt(x, y), other.getValueAt(x, y)))
return false;
}
}
return true;
}
 
static public final class MutableTableModel<D extends ODDocument> extends SheetTableModel<D> {
 
MutableTableModel(final Table<D> table, final int row, final int column) {
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/TableGroup.java
New file
0,0 → 1,174
/*
* 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.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.ODNode;
import org.openconcerto.openoffice.StyleProperties;
import org.openconcerto.utils.Tuple2;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.jdom.Element;
 
/**
* A group of columns/rows that can be hidden.
*
* @author Sylvain CUAZ
*/
public class TableGroup extends ODNode {
 
static Tuple2<TableGroup, List<Element>> createRoot(final Table<?> table, final Axis col) {
final TableGroup group = new TableGroup(table, col);
return Tuple2.create(group, group.flatten());
}
 
private final Table<?> table;
private final Axis axis;
private final TableGroup parent;
private final List<TableGroup> children;
 
private int headerCount;
private int first;
private int size;
 
// root group
private TableGroup(final Table<?> table, final Axis col) {
this(table, col, null, table.getElement(), 0);
}
 
private TableGroup(Table<?> table, TableGroup parent, Element elem, final int first) {
this(table, parent.axis, parent, elem, first);
}
 
private TableGroup(Table<?> table, final Axis col, TableGroup parent, Element elem, final int first) {
super(elem);
if (table == null)
throw new NullPointerException("null table");
this.table = table;
this.axis = col;
this.parent = parent;
this.first = first;
this.children = new ArrayList<TableGroup>();
}
 
private List<Element> flatten() {
// max() since a group can have only group children
final List<Element> res = new ArrayList<Element>(Math.max(128, getElement().getContentSize()));
final String fullName = this.axis.getElemName();
final String groupName = this.axis.getGroupName();
final String pluralName = this.axis.getPluralName();
 
// A table shall not contain more than one <table:table-header-rows> element.
final Element header = this.getElement().getChild(this.axis.getHeaderName(), getElement().getNamespace());
if (header != null)
this.headerCount = Table.flattenChildren(res, header, this.axis);
else
this.headerCount = 0;
int size = this.headerCount;
 
this.children.clear();
@SuppressWarnings("unchecked")
final List<Element> content = new ArrayList<Element>(getElement().getChildren());
for (final Element child : content) {
if (child.getName().equals(fullName)) {
size += Table.flatten1(res, child, this.axis);
} else if (child.getName().equals(pluralName)) {
// ignore table-rows element (but add its children)
size += Table.flattenChildren(res, child, this.axis);
} else if (child.getName().equals(groupName)) {
final TableGroup g = new TableGroup(getTable(), this, child, this.first + size);
this.children.add(g);
res.addAll(g.flatten());
size += g.getSize();
}
// else nothing to do (header or soft-page-break)
}
 
this.size = size;
 
return res;
}
 
public final Table<?> getTable() {
return this.table;
}
 
/**
* The parent of this group.
*
* @return the parent, <code>null</code> if this is the root group.
*/
public final TableGroup getParent() {
return this.parent;
}
 
public final List<TableGroup> getChildren() {
return Collections.unmodifiableList(this.children);
}
 
final TableGroup getDescendentOrSelfContaining(final int y) {
if (!this.contains(y))
return null;
for (final TableGroup g : this.getChildren()) {
final TableGroup res = g.getDescendentOrSelfContaining(y);
if (res != null)
return res;
}
return this;
}
 
public final boolean isDisplayed() {
if (this.getParent() == null)
return true;
else
// from table:display : the default value for this attribute is true
return StyleProperties.parseBoolean(getElement().getAttributeValue("display", getElement().getNamespace()), true);
}
 
/**
* The index of the first row/column in this group.
*
* @return index of the first element.
*/
public final int getFirst() {
return this.first;
}
 
public final int getHeaderCount() {
return this.headerCount;
}
 
public final int getSize() {
return this.size;
}
 
public final boolean contains(final int i) {
return i >= this.getFirst() && i < this.getFirst() + this.getSize();
}
 
final int getFollowingHeaderCount() {
int res = this.getHeaderCount();
// the table and each distinct group may contain one <table:table-header-rows> element, if
// and only if the table rows contained in the <table:table-header-rows> elements are
// adjacent.
for (final TableGroup g : this.getChildren()) {
if (g.getFirst() != this.getFirst() + res)
break;
res += g.getFollowingHeaderCount();
}
return res;
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/Axis.java
New file
0,0 → 1,79
/*
* 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.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.StyleProperties;
 
import org.jdom.Attribute;
import org.jdom.Element;
 
public enum Axis {
ROW("row"), COLUMN("column");
 
private final String shortName, elemName, headerName, groupName, pluralName, repeatedAttrName;
 
private Axis(final String shortName) {
this.shortName = shortName;
 
this.elemName = "table-" + shortName;
this.headerName = "table-header-" + shortName + "s";
this.groupName = this.elemName + "-group";
this.pluralName = this.elemName + "s";
this.repeatedAttrName = "number-" + shortName + "s-repeated";
}
 
public final String getShortName() {
return this.shortName;
}
 
public final String getElemName() {
return this.elemName;
}
 
public final String getHeaderName() {
return this.headerName;
}
 
public final String getGroupName() {
return this.groupName;
}
 
public final String getPluralName() {
return this.pluralName;
}
 
public final String getRepeatedAttrName() {
return this.repeatedAttrName;
}
 
final Attribute getRepeatedAttr(final Element elem) {
assert elem.getName().equals(this.getElemName());
return elem.getAttribute(getRepeatedAttrName(), elem.getNamespace());
}
 
final int getRepeated(final Element elem) {
assert elem.getName().equals(this.getElemName());
return StyleProperties.parseInt(elem.getAttributeValue(getRepeatedAttrName(), elem.getNamespace()), 1);
}
 
final void setRepeated(final Element elem, final int i) {
assert elem.getName().equals(this.getElemName());
if (i < 1)
throw new IllegalArgumentException("repeated <1 : " + i);
if (i == 1)
elem.removeAttribute(getRepeatedAttrName(), elem.getNamespace());
else
elem.setAttribute(getRepeatedAttrName(), String.valueOf(i), elem.getNamespace());
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/Row.java
16,10 → 16,13
*/
package org.openconcerto.openoffice.spreadsheet;
 
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.StyleDesc;
import org.openconcerto.openoffice.StyleProperties;
import org.openconcerto.openoffice.StyleStyleDesc;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.ODDocument;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
import org.jdom.Element;
39,46 → 42,79
 
private final Table<D> parent;
private final int index;
private int repeated;
// the same immutable cell instance is repeated, but each MutableCell is only once
// ATTN MutableCell have their index as attribute
private final List<Cell<D>> cells;
// array is faster than List
private Cell<D>[] cells;
private int cellCount;
 
Row(Table<D> parent, Element tableRowElem, int index) {
super(parent.getODDocument(), tableRowElem, RowStyle.class);
Row(Table<D> parent, Element tableRowElem, int index, StyleDesc<RowStyle> styleDesc, StyleDesc<CellStyle> cellStyleDesc) {
super(parent.getODDocument(), tableRowElem, styleDesc);
this.parent = parent;
this.index = index;
this.cells = new ArrayList<Cell<D>>();
this.repeated = Axis.ROW.getRepeated(getElement());
@SuppressWarnings("unchecked")
final Cell<D>[] unsafe = new Cell[parent.getColumnCount()];
this.cells = unsafe;
this.cellCount = 0;
for (final Element cellElem : this.getCellElements()) {
addCellElem(cellElem);
addCellElem(cellElem, cellStyleDesc);
}
}
 
private final void ensureRoom(int additionalItems) {
final int requiredSize = this.getCellCount() + additionalItems;
if (requiredSize > this.cells.length) {
this.cells = Arrays.copyOf(this.cells, requiredSize);
}
}
 
protected final Table<D> getSheet() {
return this.parent;
}
 
// ATTN index of the first row
final int getY() {
return this.index;
}
 
// plain Cell instances have multiple indexes (if repeated) but MutableCell are unique
final int getX(MutableCell<D> c) {
return this.cells.indexOf(c);
// inclusive
final int getLastY() {
return this.getY() + this.getRepeated() - 1;
}
 
private void addCellElem(final Element cellElem) {
final Cell<D> cell = new Cell<D>(this, cellElem);
this.cells.add(cell);
final int getRepeated() {
return this.repeated;
}
 
final String repeatedS = cellElem.getAttributeValue("number-columns-repeated", this.getSheet().getTABLE());
if (repeatedS != null) {
final int toRepeat = Integer.parseInt(repeatedS) - 1;
for (int i = 0; i < toRepeat; i++) {
this.cells.add(cell);
final void setRepeated(int newRepeated) {
Axis.ROW.setRepeated(getElement(), newRepeated);
this.repeated = newRepeated;
}
 
// plain Cell instances have multiple indexes (if repeated) but MutableCell are unique
final int getX(MutableCell<D> c) {
final int stop = this.getCellCount();
for (int i = 0; i < stop; i++) {
final Cell<D> item = this.cells[i];
if (c.equals(item))
return i;
}
return -1;
}
 
private void addCellElem(final Element cellElem, StyleDesc<CellStyle> cellStyleDesc) {
final Cell<D> cell = new Cell<D>(this, cellElem, cellStyleDesc);
final String repeatedS = cellElem.getAttributeValue("number-columns-repeated", this.getTABLE());
final int toRepeat = StyleProperties.parseInt(repeatedS, 1);
final int stop = this.cellCount + toRepeat;
for (int i = this.cellCount; i < stop; i++) {
this.cells[i] = cell;
}
this.cellCount = stop;
}
 
/**
* All cells of this row.
*
90,8 → 126,16
return this.getElement().getChildren();
}
 
protected final int getCellCount() {
return this.cellCount;
}
 
private final List<Cell<D>> getCellsAsList() {
return Arrays.asList(this.cells);
}
 
protected final Cell<D> getCellAt(int col) {
return this.cells.get(col);
return this.cells[col];
}
 
protected final Cell<D> getValidCellAt(int col) {
102,68 → 146,41
}
 
public final MutableCell<D> getMutableCellAt(final int col) {
final Cell c = this.getValidCellAt(col);
final Cell<D> c = this.getValidCellAt(col);
if (!(c instanceof MutableCell)) {
final Element element = c.getElement();
final String repeatedS = element.getAttributeValue("number-columns-repeated", this.getSheet().getTABLE());
if (repeatedS != null) {
final int repeated = Integer.parseInt(repeatedS);
final int firstIndex = this.cells.indexOf(c);
final int lastIndex = firstIndex + repeated - 1;
 
final int preRepeated = col - firstIndex;
final int postRepeated = lastIndex - col;
 
casse(element, firstIndex, preRepeated, true);
element.removeAttribute("number-columns-repeated", this.getSheet().getTABLE());
casse(element, col + 1, postRepeated, false);
RepeatedBreaker.<D> getCellBreaker().breakRepeated(this, getCellsAsList(), col);
}
this.cells.set(col, new MutableCell<D>(this, element));
}
return (MutableCell<D>) this.getValidCellAt(col);
}
 
private final void casse(Element element, int firstIndex, int repeat, boolean before) {
if (repeat > 0) {
final Element newElem = (Element) element.clone();
element.getParentElement().addContent(element.getParent().indexOf(element) + (before ? 0 : 1), newElem);
newElem.setAttribute("number-columns-repeated", repeat + "", this.getSheet().getTABLE());
final Cell<D> preCell = new Cell<D>(this, newElem);
for (int i = 0; i < repeat; i++) {
this.cells.set(firstIndex + i, preCell);
}
}
}
 
// rempli cette ligne avec autant de cellules vides qu'il faut
void columnCountChanged() {
final int diff = this.getSheet().getColumnCount() - this.cells.size();
void columnCountChanged(StyleStyleDesc<CellStyle> cellStyleDesc) {
final int diff = this.getSheet().getColumnCount() - getCellCount();
if (diff < 0) {
throw new IllegalStateException("should have used Table.removeColumn()");
} else if (diff > 0) {
final Element e = Cell.createEmpty(this.getSheet().getODDocument().getVersion(), diff);
this.getElement().addContent(e);
addCellElem(e);
this.ensureRoom(diff);
addCellElem(e, cellStyleDesc);
}
if (this.cells.size() != this.getSheet().getColumnCount())
throw new IllegalStateException();
assert this.getCellCount() == this.getSheet().getColumnCount();
}
 
void checkRemove(int firstIndex, int lastIndexExcl) {
if (lastIndexExcl > this.cells.size()) {
throw new IndexOutOfBoundsException(lastIndexExcl + " > " + this.cells.size());
if (lastIndexExcl > getCellCount()) {
throw new IndexOutOfBoundsException(lastIndexExcl + " > " + getCellCount());
}
if (!this.getCellAt(firstIndex).isValid())
throw new IllegalArgumentException("unable to remove covered cell at " + firstIndex);
}
 
// ATTN unsafe, must call checkRemove() first
void removeCells(int firstIndex, int lastIndexExcl) {
checkRemove(firstIndex, lastIndexExcl);
 
this.getMutableCellAt(firstIndex).unmerge();
 
// if lastIndex == size, nothing to do
if (lastIndexExcl < this.cells.size()) {
if (lastIndexExcl < getCellCount()) {
if (!this.getCellAt(lastIndexExcl - 1).isValid()) {
int currentCol = lastIndexExcl - 2;
// the covering cell is on this row since last cells of previous rows have been
182,8 → 199,13
for (int i = firstIndex; i < lastIndexExcl; i++) {
// ok to detach multiple times the same element (since repeated cells share the same XML
// element)
this.cells.remove(firstIndex).getElement().detach();
this.cells[firstIndex].getElement().detach();
}
final int movedCount = getCellCount() - lastIndexExcl;
System.arraycopy(this.cells, lastIndexExcl, this.cells, firstIndex, movedCount);
this.cells = Arrays.copyOfRange(this.cells, 0, firstIndex + movedCount);
this.cellCount = this.cells.length;
assert this.getCellCount() == this.getSheet().getColumnCount();
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/spreadsheet/SpreadSheet.java
13,8 → 13,6
package org.openconcerto.openoffice.spreadsheet;
 
import static org.openconcerto.openoffice.ODPackage.RootElement.CONTENT;
import static org.openconcerto.openoffice.ODPackage.RootElement.STYLES;
import org.openconcerto.openoffice.ContentType;
import org.openconcerto.openoffice.ContentTypeVersioned;
import org.openconcerto.openoffice.ODDocument;
21,13 → 19,11
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.OOUtils;
import org.openconcerto.openoffice.XMLFormatVersion;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.SheetTableModel.MutableTableModel;
import org.openconcerto.utils.Tuple2;
 
import java.awt.Point;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
39,7 → 35,6
 
import javax.swing.table.TableModel;
 
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.xpath.XPath;
49,14 → 44,20
*
* @author Sylvain
*/
public class SpreadSheet implements ODDocument {
public class SpreadSheet extends ODDocument {
 
public static SpreadSheet createFromFile(File f) throws IOException {
return create(new ODPackage(f));
return new ODPackage(f).getSpreadSheet();
}
 
public static SpreadSheet create(final ODPackage fd) {
return new SpreadSheet(fd.getDocument(CONTENT.getZipEntry()), fd.getDocument(STYLES.getZipEntry()), fd);
/**
* This method should be avoided, use {@link ODPackage#getSpreadSheet()}.
*
* @param fd a package.
* @return the spreadsheet.
*/
public static SpreadSheet get(final ODPackage fd) {
return fd.hasODDocument() ? fd.getSpreadSheet() : new SpreadSheet(fd);
}
 
public static SpreadSheet createEmpty(TableModel t) throws IOException {
65,7 → 66,7
 
public static SpreadSheet createEmpty(TableModel t, XMLFormatVersion ns) throws IOException {
final ContentTypeVersioned ct = ContentType.SPREADSHEET.getVersioned(ns.getXMLVersion());
final SpreadSheet spreadSheet = create(ct.createPackage(ns));
final SpreadSheet spreadSheet = ct.createPackage(ns).getSpreadSheet();
spreadSheet.getBody().addContent(Sheet.createEmpty(ns.getXMLVersion()));
spreadSheet.getSheet(0).merge(t, 0, 0, true);
return spreadSheet;
85,46 → 86,14
return SpreadSheet.createEmpty(t, ns).saveAs(f);
}
 
private final ODPackage originalFile;
private final Map<Element, Sheet> sheets;
 
public SpreadSheet(Document doc, Document styles) {
this(doc, styles, null);
}
 
private SpreadSheet(final Document doc, final Document styles, final ODPackage orig) {
if (orig != null) {
// ATTN OK because this is our private instance (see createFromFile())
this.originalFile = orig;
} else {
this.originalFile = new ODPackage();
}
this.originalFile.putFile("content.xml", doc);
if (styles != null)
this.originalFile.putFile("styles.xml", styles);
 
private SpreadSheet(final ODPackage orig) {
super(orig);
// map Sheet by XML elements so has not to depend on ordering or name
this.sheets = new HashMap<Element, Sheet>();
}
 
final Document getContent() {
return this.getPackage().getContent().getDocument();
}
 
@Override
public final XMLVersion getVersion() {
return this.getPackage().getVersion();
}
 
@Override
public XMLFormatVersion getFormatVersion() {
return this.getPackage().getFormatVersion();
}
 
private Element getBody() {
return ContentType.SPREADSHEET.getVersioned(getVersion()).getBody(getContent());
}
 
// ** from 8.3.1 Referencing Table Cells (just double the backslash for . and escape the $)
private static final String minCell = "\\$?([A-Z]+)\\$?([0-9]+)";
// added parens to capture cell address
334,17 → 303,4
parentElement.addContent(getContentIndex(toIndex), sheet.getElement());
// no need to update this.sheets since it doesn't depend on order
}
 
// *** Files
 
public File saveAs(File file) throws FileNotFoundException, IOException {
this.getPackage().setFile(file);
return this.getPackage().save();
}
 
@Override
public final ODPackage getPackage() {
return this.originalFile;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/StyleDesc.java
186,18 → 186,74
}
 
/**
* Resolve the passed style name.
* Resolve the passed style name. Note: this always return the style named <code>name</code>,
* possibly ignoring conditions.
*
* @param pkg the package of the searched for style.
* @param doc the document of the searched for style.
* @param name the name of the style.
* @return a corresponding StyleStyle.
* @return the corresponding style, <code>null</code> if not found.
* @see #findStyleForNode(StyledNode, String)
*/
public final S findStyle(final ODPackage pkg, final Document doc, final String name) {
final Element styleElem = pkg.getStyle(doc, this, name);
return styleElem == null ? null : this.create(pkg, styleElem);
public final S findStyleWithName(final ODPackage pkg, final Document doc, final String name) {
return this.findStyle(pkg, doc, name, null);
}
 
/**
* Find the style for the passed node. Depending on conditions the returned style might not be
* named <code>name</code>.
*
* @param styledNode needed to evaluate conditions, not <code>null</code>.
* @param name the name of the style.
* @return the corresponding style, <code>null</code> if not found.
* @see #findStyleWithName(ODPackage, Document, String)
*/
public final S findStyleForNode(final StyledNode<S, ?> styledNode, final String name) {
return this.findStyleForNode(styledNode.getODDocument().getPackage(), styledNode.getElement().getDocument(), styledNode, name);
}
 
public final S findStyleForNode(final ODPackage pkg, final Document doc, final StyledNode<S, ?> styledNode, final String name) {
if (styledNode == null)
throw new NullPointerException("null node");
return this.findStyle(pkg, doc, name, styledNode);
}
 
/**
* Resolve the passed style name. If <code>styledNode</code> is <code>null</code> the returned
* style will be the one named <code>name</code> otherwise depending on conditions it can be
* another one.
*
* @param pkg the package of the searched for style.
* @param doc the document of the searched for style.
* @param name the name of the style.
* @param styledNode needed to evaluate conditions, can be <code>null</code>.
* @return the corresponding style, <code>null</code> if not found.
*/
private final S findStyle(final ODPackage pkg, final Document doc, final String name, final StyledNode<S, ?> styledNode) {
Element styleElem = pkg.getStyle(doc, this, name);
if (styleElem == null)
return null;
if (styledNode != null && supportConditions()) {
@SuppressWarnings("unchecked")
final List<Element> styleMaps = styleElem.getChildren("map", getVersion().getSTYLE());
final Element styleMap = evaluateConditions(styledNode, styleMaps);
if (styleMap != null) {
if (styleElem != styleMap.getParent())
throw new IllegalStateException("map element not in " + styleElem);
styleElem = pkg.getStyle(doc, this, styleMap.getAttributeValue("apply-style-name", getVersion().getSTYLE()));
}
}
return this.create(pkg, styleElem);
}
 
protected boolean supportConditions() {
return false;
}
 
protected Element evaluateConditions(final StyledNode<S, ?> styledNode, final List<Element> styleMaps) {
return null;
}
 
public final S createAutoStyle(final ODPackage pkg) {
return this.createAutoStyle(pkg, getBaseName());
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/Style.java
105,6 → 105,7
if (elemName2Desc.get(desc.getVersion()).put(desc.getElementName(), desc) != null)
throw new IllegalStateException(desc.getElementName() + " duplicate element name");
}
assert desc != null : "Will need containsKey() in getStyleDesc()";
if (class2Desc.get(desc.getVersion()).put(desc.getStyleClass(), desc) != null)
throw new IllegalStateException(desc.getStyleClass() + " duplicate");
}
184,7 → 185,7
 
public static <S extends Style> S getStyle(final ODPackage pkg, final Class<S> clazz, final String name) {
final StyleDesc<S> styleDesc = getStyleDesc(clazz, pkg.getVersion());
return styleDesc.create(pkg, pkg.getStyle(styleDesc, name));
return styleDesc.findStyleWithName(pkg, pkg.getContent().getDocument(), name);
}
 
/**
227,16 → 228,14
return (StyleStyleDesc<S>) getStyleDesc(clazz, version);
}
 
@SuppressWarnings("unchecked")
private static <S extends Style> StyleDesc<S> getStyleDesc(Class<S> clazz, final XMLVersion version, final boolean mustExist) {
loadDescs();
final Map<Class<? extends Style>, StyleDesc<?>> map = class2Desc.get(version);
if (map.containsKey(clazz))
return (StyleDesc<S>) map.get(clazz);
else if (mustExist)
@SuppressWarnings("unchecked")
final StyleDesc<S> res = (StyleDesc<S>) map.get(clazz);
if (res == null && mustExist)
throw new IllegalArgumentException("unregistered " + clazz + " for version " + version);
else
return null;
return res;
}
 
protected static <S extends Style> StyleDesc<S> getNonNullStyleDesc(final Class<S> clazz, final XMLVersion version, final Element styleElem, final String styleName) {
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ODValueType.java
14,6 → 14,7
package org.openconcerto.openoffice;
 
import org.openconcerto.utils.FormatGroup;
import org.openconcerto.utils.TimeUtils;
import org.openconcerto.utils.XMLDateFormat;
 
import java.math.BigDecimal;
25,8 → 26,6
import java.util.Date;
import java.util.List;
 
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
 
/**
109,12 → 108,7
return o.toString();
} else {
final Calendar cal = (Calendar) o;
// adjust the format TZ to the calendar's
// that way even you pass a non default Calendar, if you did
// myCal.set(HOUR_OF_DAY, 22), the string will have "22H"
final SimpleDateFormat fmt = (SimpleDateFormat) TIME_FORMAT.clone();
fmt.setTimeZone(cal.getTimeZone());
return fmt.format(cal.getTime());
return TimeUtils.timePartToDuration(cal).toString();
}
}
 
123,7 → 117,7
if (date.length() == 0)
return null;
else {
return getTypeFactory().newDuration(date);
return TimeUtils.getTypeFactory().newDuration(date);
}
}
 
206,8 → 200,11
*
* @param o the object.
* @return a value type capable of formatting <code>o</code> or <code>null</code>.
* @throws NullPointerException if <code>o</code> is <code>null</code>.
*/
public static ODValueType forObject(Object o) {
public static ODValueType forObject(Object o) throws NullPointerException {
if (o == null)
throw new NullPointerException();
if (o instanceof Number)
return FLOAT;
else if (o instanceof Boolean)
214,7 → 211,7
return BOOLEAN;
else if (o instanceof String)
return STRING;
else if (o instanceof Duration || o instanceof Calendar && !((Calendar) o).isSet(Calendar.DATE))
else if (o instanceof Duration)
return TIME;
else if (DATE.canFormat(o.getClass()))
return DATE;
224,8 → 221,6
 
// see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#isoformats
 
// time means Duration for OpenDocument (see 6.7.1)
static private final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("'PT'HH'H'mm'M'ss.S'S'");
static private final Format DATE_FORMAT;
static {
// first date and time so we don't loose time information on format() or parse()
232,16 → 227,4
// MAYBE add HH':'mm':'ss,SSS for OOo 1
DATE_FORMAT = new FormatGroup(new XMLDateFormat(), new SimpleDateFormat("yyyy-MM-dd'T'HH':'mm':'ss"), new SimpleDateFormat("yyyy-MM-dd"));
}
 
static private DatatypeFactory typeFactory = null;
 
static public final DatatypeFactory getTypeFactory() {
if (typeFactory == null)
try {
typeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
throw new IllegalStateException(e);
}
return typeFactory;
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/text/TextDocument.java
New file
0,0 → 1,92
/*
* 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.openoffice.text;
 
import org.openconcerto.openoffice.ContentType;
import org.openconcerto.openoffice.ContentTypeVersioned;
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.XMLFormatVersion;
 
import java.io.File;
import java.io.IOException;
 
import org.jdom.Element;
 
public class TextDocument extends ODDocument {
 
public static TextDocument createFromFile(File f) throws IOException {
return new ODPackage(f).getTextDocument();
}
 
/**
* This method should be avoided, use {@link ODPackage#getTextDocument()}.
*
* @param fd a package.
* @return the text document.
*/
public static TextDocument get(final ODPackage fd) {
return fd.hasODDocument() ? fd.getTextDocument() : new TextDocument(fd);
}
 
public static TextDocument createEmpty(String s) throws IOException {
return createEmpty(s, XMLFormatVersion.getDefault());
}
 
public static TextDocument createEmpty(String s, XMLFormatVersion ns) throws IOException {
final ContentTypeVersioned ct = ContentType.TEXT.getVersioned(ns.getXMLVersion());
final TextDocument res = ct.createPackage(ns).getTextDocument();
final Element textP = Paragraph.createEmpty(ns.getXMLVersion());
textP.addContent(s);
res.getBody().addContent(textP);
return res;
}
 
private TextDocument(final ODPackage orig) {
super(orig);
}
 
public final Paragraph getParagraph(int i) {
final Element proto = Paragraph.createEmpty(getVersion());
final Element child = (Element) this.getBody().getChildren(proto.getName(), proto.getNamespace()).get(i);
return new Paragraph(child, this);
}
 
/**
* Append a paragraph or a heading.
*
* @param p paragraph to add.
*/
public synchronized void add(TextNode<?> p) {
this.add(p, null, -1);
}
 
public synchronized void add(TextNode<?> p, Element where, int index) {
// add it first to avoid infinite loop, since setDocument() can call this method
final Element addToElem = where == null ? this.getBody() : where;
if (index < 0)
addToElem.addContent(p.getElement());
else
addToElem.addContent(index, p.getElement());
 
try {
p.setDocument(this);
} catch (RuntimeException e) {
// the paragraph can throw an exception to notify that is not compatible with us (eg
// missing styles), in that case remove it
p.getElement().detach();
throw e;
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/text/Paragraph.java
13,8 → 13,8
package org.openconcerto.openoffice.text;
 
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.ODSingleXMLDocument;
 
import java.util.HashSet;
import java.util.Set;
55,8 → 55,12
return res;
}
 
Paragraph(Element elem, TextDocument parent) {
super(elem, ParagraphStyle.class, parent);
}
 
public Paragraph(Element elem) {
super(elem, ParagraphStyle.class);
this(elem, null);
}
 
public Paragraph(XMLVersion ns) {
72,6 → 76,21
addContent(text);
}
 
// MAYBE add updateStyle() which evaluates the conditions in style:map of the conditional style
// to update style-name
/**
* A style containing conditions and maps to other styles.
*
* @return the conditional style or <code>null</code> if none or if this isn't in a document.
*/
public final ParagraphStyle getConditionalStyle() {
final String condName = this.getElement().getAttributeValue("cond-style-name", this.getElement().getNamespace());
if (condName == null)
return null;
else
return getStyle(condName);
}
 
public final void setStyle(String styleName) {
getElement().setAttribute("style-name", styleName, getElement().getNamespace());
}
99,11 → 118,12
return getTextStyles(getElement());
}
 
protected void checkDocument(ODSingleXMLDocument doc) {
if (this.getStyleName() != null && getStyle(doc.getPackage(), doc.getDocument()) == null)
@Override
protected void checkDocument(ODDocument doc) {
if (this.getStyleName() != null && getStyle(doc.getPackage(), doc.getContentDocument()) == null)
throw new IllegalArgumentException("unknown style " + getStyleName() + " in " + doc);
for (final String styleName : this.getUsedTextStyles()) {
if (doc.getStyle(TextStyle.DESC, styleName) == null) {
if (doc.getPackage().getStyle(TextStyle.DESC, styleName) == null) {
throw new IllegalArgumentException(this + " is using a text:span with an undefined style : " + styleName);
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/text/TextNode.java
13,7 → 13,7
package org.openconcerto.openoffice.text;
 
import org.openconcerto.openoffice.ODSingleXMLDocument;
import org.openconcerto.openoffice.ODDocument;
import org.openconcerto.openoffice.StyleStyle;
import org.openconcerto.openoffice.StyledNode;
 
26,28 → 26,32
*
* @param <S> type of style.
*/
public abstract class TextNode<S extends StyleStyle> extends StyledNode<S, ODSingleXMLDocument> {
public abstract class TextNode<S extends StyleStyle> extends StyledNode<S, TextDocument> {
 
protected ODSingleXMLDocument parent;
protected TextDocument parent;
 
public TextNode(Element local, final Class<S> styleClass) {
this(local, styleClass, null);
}
 
protected TextNode(Element local, final Class<S> styleClass, final TextDocument parent) {
super(local, styleClass);
this.parent = null;
this.parent = parent;
}
 
@Override
public final ODSingleXMLDocument getODDocument() {
public final TextDocument getODDocument() {
return this.parent;
}
 
public final void setDocument(ODSingleXMLDocument doc) {
public final void setDocument(TextDocument doc) {
if (doc != this.parent) {
if (doc == null) {
this.parent = null;
this.getElement().detach();
} else if (doc.getDocument() != this.getElement().getDocument())
} else if (doc.getContentDocument() != this.getElement().getDocument()) {
doc.add(this);
else {
} else {
this.checkDocument(doc);
this.parent = doc;
}
54,5 → 58,5
}
}
 
protected abstract void checkDocument(ODSingleXMLDocument doc);
protected abstract void checkDocument(ODDocument doc);
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ODPackage.java
15,15 → 15,21
 
import static org.openconcerto.openoffice.ODPackage.RootElement.CONTENT;
import static org.openconcerto.openoffice.ODPackage.RootElement.META;
import static org.openconcerto.openoffice.ODPackage.RootElement.SETTINGS;
import static org.openconcerto.openoffice.ODPackage.RootElement.STYLES;
import org.openconcerto.openoffice.spreadsheet.SpreadSheet;
import org.openconcerto.openoffice.text.ParagraphStyle;
import org.openconcerto.openoffice.text.TextDocument;
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.CopyUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.StreamUtils;
import org.openconcerto.utils.StringInputStream;
import org.openconcerto.utils.Tuple3;
import org.openconcerto.utils.Zip;
import org.openconcerto.utils.ZippedFilesProcessor;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.Validator;
 
import java.io.BufferedInputStream;
37,14 → 43,19
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.ZipEntry;
 
import org.jdom.Attribute;
import org.jdom.DocType;
import org.jdom.Document;
import org.jdom.Element;
62,6 → 73,7
 
// use raw format, otherwise spaces are added to every spreadsheet cell
private static final XMLOutputter OUTPUTTER = new XMLOutputter(Format.getRawFormat());
static final String MIMETYPE_ENTRY = "mimetype";
/** Normally mimetype contains only ASCII characters */
static final Charset MIMETYPE_ENC = Charset.forName("UTF-8");
 
89,6 → 101,10
return EnumSet.of(CONTENT, STYLES, META, SETTINGS);
}
 
public final static RootElement fromDocument(final Document doc) {
return fromElementName(doc.getRootElement().getName());
}
 
public final static RootElement fromElementName(final String name) {
for (final RootElement e : values()) {
if (e.getElementName().equals(name))
98,8 → 114,7
}
 
static final Document createSingle(final Document from) {
final XMLFormatVersion version = XMLFormatVersion.get(from);
return SINGLE_CONTENT.createDocument(version.getXMLVersion(), version.getOfficeVersion());
return SINGLE_CONTENT.createDocument(XMLFormatVersion.get(from));
}
 
private final String nsPrefix;
120,20 → 135,25
return this.name;
}
 
public final Document createDocument(final XMLVersion version, final String officeVersion) {
public final Document createDocument(final XMLFormatVersion fv) {
final XMLVersion version = fv.getXMLVersion();
final Element root = new Element(getElementName(), version.getNS(getElementNSPrefix()));
// 19.388 office:version identifies the version of ODF specification
if (officeVersion != null)
root.setAttribute("version", officeVersion, version.getOFFICE());
if (fv.getOfficeVersion() != null)
root.setAttribute("version", fv.getOfficeVersion(), version.getOFFICE());
// avoid declaring namespaces in each child
for (final Namespace ns : version.getALL())
root.addNamespaceDeclaration(ns);
 
final Document res = new Document(root);
return new Document(root, createDocType(version));
}
 
public final DocType createDocType(final XMLVersion version) {
// OpenDocument use relaxNG
if (version == XMLVersion.OOo)
res.setDocType(new DocType(getElementNSPrefix() + ":" + getElementName(), "-//OpenOffice.org//DTD OfficeDocument 1.0//EN", "office.dtd"));
return res;
return new DocType(getElementNSPrefix() + ":" + getElementName(), "-//OpenOffice.org//DTD OfficeDocument 1.0//EN", "office.dtd");
else
return null;
}
 
/**
162,17 → 182,69
* @return <code>true</code> if <code>name</code> is a standard file, eg <code>true</code>.
*/
public static final boolean isStandardFile(final String name) {
return name.equals("mimetype") || subdocNames.contains(name) || name.startsWith("Thumbnails") || name.startsWith("META-INF") || name.startsWith("Configurations");
return name.equals(MIMETYPE_ENTRY) || subdocNames.contains(name) || name.startsWith("Thumbnails") || name.startsWith("META-INF") || name.startsWith("Configurations");
}
 
/**
* Create a package from a collection of sub-documents.
*
* @param content the content.
* @param style the styles, can be <code>null</code>.
* @return a package containing the XML documents.
*/
public static ODPackage createFromDocuments(Document content, Document style) {
return createFromDocuments(null, content, style, null, null);
}
 
public static ODPackage createFromDocuments(final ContentTypeVersioned type, Document content, Document style, Document meta, Document settings) {
final ODPackage pkg = new ODPackage();
if (type != null)
pkg.setContentType(type);
pkg.putFile(RootElement.CONTENT.getZipEntry(), content);
pkg.putFile(RootElement.STYLES.getZipEntry(), style);
pkg.putFile(RootElement.META.getZipEntry(), meta);
pkg.putFile(RootElement.SETTINGS.getZipEntry(), settings);
return pkg;
}
 
static private XMLVersion getVersion(final XMLFormatVersion fv, final ContentTypeVersioned ct) {
final XMLVersion v;
if (ct == null && fv == null)
v = null;
else if (ct != null)
v = ct.getVersion();
else
v = fv.getXMLVersion();
assert fv == null || ct == null || fv.getXMLVersion() == ct.getVersion();
return v;
}
 
static private <T> void checkVersion(final Class<T> clazz, final String s, final T actual, final T required) {
if (actual != null && required != null) {
final boolean ok;
if (actual instanceof ContentTypeVersioned) {
// we can change our template status since it doesn't affect our content
ok = ((ContentTypeVersioned) actual).getNonTemplate().equals(((ContentTypeVersioned) required).getNonTemplate());
} else {
ok = actual.equals(required);
}
if (!ok)
throw new IllegalArgumentException("Cannot change " + s + " from " + required + " to " + actual);
}
}
 
private final Map<String, ODPackageEntry> files;
private ContentTypeVersioned type;
private XMLFormatVersion version;
private File file;
private ODDocument doc;
 
public ODPackage() {
this.files = new HashMap<String, ODPackageEntry>();
this.type = null;
this.version = null;
this.file = null;
this.doc = null;
}
 
public ODPackage(InputStream ins) throws IOException {
186,7 → 258,7
final Object res;
if (subdocNames.contains(name)) {
try {
res = new ODXMLDocument(OOUtils.getBuilder().build(in));
res = OOUtils.getBuilder().build(in);
} catch (JDOMException e) {
// always correct
throw new IllegalStateException("parse error", e);
244,7 → 316,9
this.putFile(name, myData, entry.getType(), entry.isCompressed());
}
this.type = o.type;
this.version = o.version;
this.file = o.file;
this.doc = null;
}
 
public final File getFile() {
269,16 → 343,11
* @return the version of this package, can be <code>null</code>.
*/
public final XMLVersion getVersion() {
final XMLFormatVersion res = getFormatVersion();
return res == null ? null : res.getXMLVersion();
return getVersion(this.version, this.type);
}
 
public final XMLFormatVersion getFormatVersion() {
final ODXMLDocument content = this.getContent();
if (content == null)
return null;
else
return content.getFormatVersion();
return this.version;
}
 
/**
287,25 → 356,90
* @return the type of this package, can be <code>null</code>.
*/
public final ContentTypeVersioned getContentType() {
if (this.type == null) {
if (this.files.containsKey("mimetype"))
this.type = ContentTypeVersioned.fromMime(new String(this.getBinaryFile("mimetype"), MIMETYPE_ENC));
else if (this.getVersion().equals(XMLVersion.OOo)) {
final Element contentRoot = this.getContent().getDocument().getRootElement();
final String docClass = contentRoot.getAttributeValue("class", contentRoot.getNamespace("office"));
this.type = ContentTypeVersioned.fromClass(docClass);
} else if (this.getVersion().equals(XMLVersion.OD)) {
final Element bodyChild = (Element) this.getContent().getChild("body").getChildren().get(0);
this.type = ContentTypeVersioned.fromBody(bodyChild.getName());
return this.type;
}
 
public final void setContentType(final ContentTypeVersioned newType) {
this.putFile(MIMETYPE_ENTRY, newType.getMimeType().getBytes(MIMETYPE_ENC));
}
return this.type;
 
private void updateTypeAndVersion(final String entry, ODXMLDocument xml) {
this.setTypeAndVersion(entry.equals(CONTENT.getZipEntry()) ? ContentTypeVersioned.fromContent(xml) : null, xml.getFormatVersion(), entry);
}
 
private void updateTypeAndVersion(byte[] mimetype) {
this.setTypeAndVersion(ContentTypeVersioned.fromMime(mimetype), null, MIMETYPE_ENTRY);
}
 
private final void setTypeAndVersion(final ContentTypeVersioned ct, final XMLFormatVersion fv, final String entry) {
final Tuple3<XMLVersion, ContentTypeVersioned, XMLFormatVersion> requiredByPkg = this.getRequired(entry);
if (requiredByPkg != null) {
checkVersion(XMLVersion.class, "version", getVersion(fv, ct), requiredByPkg.get0());
checkVersion(ContentTypeVersioned.class, "type", ct, requiredByPkg.get1());
checkVersion(XMLFormatVersion.class, "format version", fv, requiredByPkg.get2());
}
 
// since we're adding "entry" never set attributes to null
if (fv != null && !fv.equals(this.version))
this.version = fv;
// don't let non-template from content overwrite the correct one
if (ct != null && !ct.equals(this.type) && (this.type == null || entry.equals(MIMETYPE_ENTRY)))
this.type = ct;
}
 
// find the versions required by the package without the passed entry
private final Tuple3<XMLVersion, ContentTypeVersioned, XMLFormatVersion> getRequired(final String entryToIgnore) {
if (this.files.size() == 0 || (this.files.size() == 1 && this.files.containsKey(entryToIgnore)))
return null;
 
final byte[] mimetype;
if (this.files.containsKey(MIMETYPE_ENTRY) && !MIMETYPE_ENTRY.equals(entryToIgnore)) {
mimetype = this.getBinaryFile(MIMETYPE_ENTRY);
} else {
mimetype = null;
}
XMLFormatVersion fv = null;
final Map<String, Object> versionFiles = new HashMap<String, Object>();
for (final String e : subdocNames) {
if (this.files.containsKey(e) && !e.equals(entryToIgnore)) {
final ODXMLDocument xmlFile = this.getXMLFile(e);
versionFiles.put(e, xmlFile);
if (fv == null)
fv = xmlFile.getFormatVersion();
else
assert fv.equals(xmlFile.getFormatVersion()) : "Incoherence";
}
}
final ODXMLDocument content = (ODXMLDocument) versionFiles.get(CONTENT.getZipEntry());
 
final ContentTypeVersioned ct;
if (mimetype != null)
ct = ContentTypeVersioned.fromMime(mimetype);
else if (content != null)
ct = ContentTypeVersioned.fromContent(content);
else
ct = null;
 
return Tuple3.create(getVersion(fv, ct), ct, fv);
}
 
public final String getMimeType() {
return this.getContentType().getMimeType();
}
 
public final boolean isTemplate() {
return this.getContentType().isTemplate();
}
 
public final void setTemplate(boolean b) {
if (this.type == null)
throw new IllegalStateException("No type");
final ContentTypeVersioned newType = b ? this.type.getTemplate() : this.type.getNonTemplate();
if (newType == null)
throw new IllegalStateException("Missing " + (b ? "" : "non-") + "template for " + this.type);
this.setContentType(newType);
}
 
/**
* Call {@link Validator#isValid()} on each XML subdocuments.
*
313,13 → 447,24
* if validation couldn't occur.
*/
public final Map<String, String> validateSubDocuments() {
return this.validateSubDocuments(true);
}
 
public final Map<String, String> validateSubDocuments(final boolean allowChangeToValidate) {
final OOXML ooxml = this.getFormatVersion().getXML();
if (!ooxml.canValidate())
return null;
final Map<String, String> res = new HashMap<String, String>();
for (final String s : subdocNames) {
if (this.getEntries().contains(s)) {
final String valid = ooxml.getValidator(this.getDocument(s)).isValid();
final Document doc = this.getDocument(s);
if (doc != null) {
if (allowChangeToValidate) {
// OpenOffice do not generate DocType declaration
final DocType docType = RootElement.fromDocument(doc).createDocType(ooxml.getVersion());
if (docType != null && doc.getDocType() == null)
doc.setDocType(docType);
}
final String valid = ooxml.getValidator(doc).isValid();
if (valid != null)
res.put(s, valid);
}
327,6 → 472,31
return res;
}
 
public final ODDocument getODDocument() {
// cache ODDocument otherwise a second one can modify the XML (e.g. remove rows) without the
// first one knowing
if (this.doc == null) {
final ContentType ct = this.getContentType().getType();
if (ct.equals(ContentType.SPREADSHEET))
this.doc = SpreadSheet.get(this);
else if (ct.equals(ContentType.TEXT))
this.doc = TextDocument.get(this);
}
return this.doc;
}
 
public final boolean hasODDocument() {
return this.doc != null;
}
 
public final SpreadSheet getSpreadSheet() {
return (SpreadSheet) this.getODDocument();
}
 
public final TextDocument getTextDocument() {
return (TextDocument) this.getODDocument();
}
 
// *** getter on files
 
public final Set<String> getEntries() {
447,6 → 617,97
return getStyles().getDefaultStyle(desc);
}
 
/**
* Verify that styles referenced by this document are indeed defined. NOTE this method is not
* perfect : not all problems are detected.
*
* @return <code>null</code> if no problem has been found, else a String describing it.
*/
public final String checkStyles() {
final ODXMLDocument stylesDoc = this.getStyles();
final ODXMLDocument contentDoc = this.getContent();
final Element styles;
if (stylesDoc != null) {
styles = stylesDoc.getChild("styles");
// check styles.xml
final String res = checkStyles(stylesDoc, styles);
if (res != null)
return res;
} else {
styles = contentDoc.getChild("styles");
}
 
// check content.xml
return checkStyles(contentDoc, styles);
}
 
static private final String checkStyles(ODXMLDocument doc, Element styles) {
try {
final CollectionMap<String, String> stylesNames = getStylesNames(doc, styles, doc.getChild("automatic-styles"));
// text:style-name : text:p, text:span
// table:style-name : table:table, table:row, table:column, table:cell
// draw:style-name : draw:text-box
// style:data-style-name : <style:style style:family="table-cell">
// TODO check by family
final Set<String> names = new HashSet<String>(stylesNames.values());
final Iterator attrs = doc.getXPath(".//@text:style-name | .//@table:style-name | .//@draw:style-name | .//@style:data-style-name | .//@style:list-style-name")
.selectNodes(doc.getDocument()).iterator();
while (attrs.hasNext()) {
final Attribute attr = (Attribute) attrs.next();
if (!names.contains(attr.getValue()))
return "unknown style referenced by " + attr.getName() + " in " + JDOMUtils.output(attr.getParent());
}
// TODO check other references like page-*-name (§3 of #prefix())
} catch (IllegalStateException e) {
return ExceptionUtils.getStackTrace(e);
} catch (JDOMException e) {
return ExceptionUtils.getStackTrace(e);
}
return null;
}
 
static private final CollectionMap<String, String> getStylesNames(final ODXMLDocument doc, final Element styles, final Element autoStyles) throws IllegalStateException {
// section 14.1 § Style Name : style:family + style:name is unique
final CollectionMap<String, String> res = new CollectionMap<String, String>(HashSet.class);
 
final List<Element> nodes = new ArrayList<Element>();
if (styles != null)
nodes.add(styles);
if (autoStyles != null)
nodes.add(autoStyles);
 
try {
{
final Iterator iter = doc.getXPath("./style:style/@style:name").selectNodes(nodes).iterator();
while (iter.hasNext()) {
final Attribute attr = (Attribute) iter.next();
final String styleName = attr.getValue();
final String family = attr.getParent().getAttributeValue("family", attr.getNamespace());
if (res.getNonNull(family).contains(styleName))
throw new IllegalStateException("duplicate style in " + family + " : " + styleName);
res.put(family, styleName);
}
}
{
final List<String> dataStyles = Arrays.asList("number-style", "currency-style", "percentage-style", "date-style", "time-style", "boolean-style", "text-style");
final String xpDataStyles = org.openconcerto.utils.CollectionUtils.join(dataStyles, " | ", new ITransformer<String, String>() {
@Override
public String transformChecked(String input) {
return "./number:" + input;
}
});
final Iterator listIter = doc.getXPath("./text:list-style | " + xpDataStyles).selectNodes(nodes).iterator();
while (listIter.hasNext()) {
final Element elem = (Element) listIter.next();
res.put(elem.getQualifiedName(), elem.getAttributeValue("name", doc.getVersion().getSTYLE()));
}
}
} catch (JDOMException e) {
throw new IllegalStateException(e);
}
return res;
}
 
// *** setter
 
public void putFile(String entry, Object data) {
460,29 → 721,54
public void putFile(final String entry, final Object data, final String mediaType, final boolean compress) {
if (entry == null)
throw new NullPointerException("null name");
if (data == null) {
this.rmFile(entry);
return;
}
final Object myData;
if (subdocNames.contains(entry)) {
final ODXMLDocument oodoc;
if (data instanceof Document)
oodoc = new ODXMLDocument((Document) data);
oodoc = ODXMLDocument.create((Document) data);
else
oodoc = (ODXMLDocument) data;
// si le package est vide n'importe quelle version convient
if (this.getVersion() != null && !oodoc.getVersion().equals(this.getVersion()))
throw new IllegalArgumentException("version mismatch " + this.getVersion() + " != " + oodoc);
checkEntryForDocument(entry);
this.updateTypeAndVersion(entry, oodoc);
myData = oodoc;
} else if (data != null && !(data instanceof byte[]))
} else if (!(data instanceof byte[])) {
throw new IllegalArgumentException("should be byte[] for " + entry + ": " + data);
else
} else {
if (entry.equals(MIMETYPE_ENTRY))
this.updateTypeAndVersion((byte[]) data);
myData = data;
}
final String inferredType = mediaType != null ? mediaType : FileUtils.findMimeType(entry);
this.files.put(entry, new ODPackageEntry(entry, inferredType, myData, compress));
}
 
// Perhaps add a clearODDocument() method to set doc to null and in ODDocument set pkg to null
// (after having verified !hasDocument()). For now just copy the package.
private void checkEntryForDocument(final String entry) {
if (this.hasODDocument() && (entry.equals(RootElement.CONTENT.getZipEntry()) || entry.equals(RootElement.STYLES.getZipEntry())))
throw new IllegalArgumentException("Cannot change content or styles with existing ODDocument");
}
 
public void rmFile(String entry) {
this.checkEntryForDocument(entry);
this.files.remove(entry);
if (entry.equals(MIMETYPE_ENTRY) || subdocNames.contains(entry)) {
final Tuple3<XMLVersion, ContentTypeVersioned, XMLFormatVersion> required = this.getRequired(entry);
this.type = required == null ? null : required.get1();
this.version = required == null ? null : required.get2();
}
}
 
public void clear() {
this.files.clear();
this.type = null;
this.version = null;
}
 
/**
* Transform this to use a {@link ODSingleXMLDocument}. Ie after this method, only "content.xml"
* remains and it's an instance of ODSingleXMLDocument.
491,13 → 777,7
*/
public ODSingleXMLDocument toSingle() {
if (!this.isSingle()) {
// this removes xml files used by OOSingleXMLDocument
final Document content = removeAndGetDoc(CONTENT.getZipEntry());
final Document styles = removeAndGetDoc(STYLES.getZipEntry());
final Document settings = removeAndGetDoc(SETTINGS.getZipEntry());
final Document meta = removeAndGetDoc(META.getZipEntry());
 
return ODSingleXMLDocument.createFromDocument(content, styles, settings, meta, this);
return ODSingleXMLDocument.create(this);
} else
return (ODSingleXMLDocument) this.getContent();
}
506,13 → 786,6
return this.getContent() instanceof ODSingleXMLDocument;
}
 
private Document removeAndGetDoc(String name) {
if (!this.files.containsKey(name))
return null;
final ODXMLDocument xmlDoc = (ODXMLDocument) this.files.remove(name).getData();
return xmlDoc == null ? null : xmlDoc.getDocument();
}
 
/**
* Split the {@link RootElement#SINGLE_CONTENT}. If this was {@link #isSingle() single} the
* former {@link #getContent() content} won't be useable anymore, you can check it with
554,12 → 827,12
final Zip z = new Zip(out);
 
// magic number, see section 17.4
z.zipNonCompressed("mimetype", this.getMimeType().getBytes(MIMETYPE_ENC));
z.zipNonCompressed(MIMETYPE_ENTRY, this.getMimeType().getBytes(MIMETYPE_ENC));
 
final Manifest manifest = new Manifest(this.getVersion(), this.getMimeType());
for (final String name : this.files.keySet()) {
// added at the end
if (name.equals("mimetype") || name.equals(Manifest.ENTRY_NAME))
if (name.equals(MIMETYPE_ENTRY) || name.equals(Manifest.ENTRY_NAME))
continue;
 
final ODPackageEntry entry = this.files.get(name);
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/DataStyle.java
14,7 → 14,9
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.Log;
import org.openconcerto.openoffice.ODEpoch;
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.Style;
import org.openconcerto.openoffice.StyleDesc;
import org.openconcerto.openoffice.StyleProperties;
74,24 → 76,62
Arrays.asList("presentation:date-time-decl", "style:style", "text:creation-date", "text:creation-time", "text:database-display", "text:date", "text:editing-duration",
"text:expression", "text:meta-field", "text:modification-date", "text:modification-time", "text:print-date", "text:print-time", "text:table-formula", "text:time",
"text:user-defined", "text:user-field-get", "text:user-field-input", "text:variable-get", "text:variable-input", "text:variable-set"));
this.getRefElementsMap().put("style:apply-style-name", "style:map");
}
}
 
// type accepted by #format()
private final Class<?> type;
private final ODValueType type;
private StyleTextProperties textProps;
 
protected DataStyle(final ODPackage pkg, Element elem, final Class<?> type) {
protected DataStyle(final ODPackage pkg, Element elem, final ODValueType type) {
super(pkg, elem);
this.type = type;
}
 
protected final Class<?> getDataType() {
public final ODValueType getDataType() {
return this.type;
}
 
public final ODEpoch getEpoch() {
return this.getPackage().getODDocument().getEpoch();
}
 
/**
* Convert the passed object to something that {@link #format(Object, CellStyle, boolean)} can
* accept.
*
* @param o the object to convert.
* @return an object that can be formatted, <code>null</code> if <code>o</code> cannot be
* converted.
* @throws NullPointerException if <code>o</code> is <code>null</code>.
* @see #canFormat(Class)
*/
public final Object convert(final Object o) throws NullPointerException {
if (o == null)
throw new NullPointerException();
 
final Object res;
if (this.canFormat(o.getClass()))
res = o;
else
res = this.convertNonNull(o);
assert res == null || this.canFormat(res.getClass());
return res;
}
 
// o is not null and canFormat(o.getClass()) is false
// return null if o cannot be converted
protected abstract Object convertNonNull(Object o);
 
/**
* Whether instances of the passed class can be {@link #format(Object, CellStyle, boolean)
* formatted}.
*
* @param toFormat the class.
* @return <code>true</code> if instances of <code>toFormat</code> can be formatted.
*/
public final boolean canFormat(Class<?> toFormat) {
return this.getDataType().isAssignableFrom(toFormat);
return this.getDataType().canFormat(toFormat);
}
 
public final String getTitle() {
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/CurrencyStyle.java
14,6 → 14,7
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.CellStyle;
 
34,10 → 35,15
};
 
public CurrencyStyle(final ODPackage pkg, Element elem) {
super(pkg, elem, Number.class);
super(pkg, elem, ODValueType.CURRENCY);
}
 
@Override
protected Object convertNonNull(Object o) {
return NumberStyle.toNumber(o, getEpoch());
}
 
@Override
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
final Number n = (Number) o;
final Namespace numberNS = this.getElement().getNamespace();
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/DateStyle.java
14,9 → 14,11
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.StyleProperties;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.CellStyle;
import org.openconcerto.utils.convertor.NumberConvertor;
 
import java.math.BigDecimal;
import java.text.DecimalFormat;
24,6 → 26,7
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
 
35,9 → 38,9
public class DateStyle extends DataStyle {
 
// see http://download.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html
private static final Locale BUDDHIST_LOCALE = new Locale("th", "TH");
private static final Locale JAPANESE_LOCALE = new Locale("ja", "JP", "JP");
private static final Locale GREGORIAN_LOCALE = new Locale("fr", "FR");
private static final Calendar BUDDHIST_CAL = Calendar.getInstance(new Locale("th", "TH"));
private static final Calendar JAPANESE_CAL = Calendar.getInstance(new Locale("ja", "JP", "JP"));
private static final Calendar GREGORIAN_CAL = new GregorianCalendar();
 
public static final DataStyleDesc<DateStyle> DESC = new DataStyleDesc<DateStyle>(DateStyle.class, XMLVersion.OD, "date-style", "N") {
@Override
63,17 → 66,17
return res;
}
 
private static final Locale getCalendarLocale(final Element elem, Locale defaultLocale) {
final Locale res;
private static final Calendar getCalendar(final Element elem, Calendar defaultCal) {
final Calendar res;
final String cal = elem.getAttributeValue("calendar", elem.getNamespace());
if (cal == null) {
res = defaultLocale;
res = defaultCal;
} else if ("buddhist".equals(cal)) {
res = BUDDHIST_LOCALE;
res = BUDDHIST_CAL;
} else if ("gengou".equals(cal)) {
res = JAPANESE_LOCALE;
res = JAPANESE_CAL;
} else if ("gregorian".equals(cal)) {
res = GREGORIAN_LOCALE;
res = GREGORIAN_CAL;
} else {
throw new IllegalArgumentException("Unsupported calendar : " + cal);
}
80,16 → 83,6
return res;
}
 
private static final Locale getCalendarLocale(final Locale locale) {
final Locale res;
if (locale.equals(BUDDHIST_LOCALE) || locale.equals(JAPANESE_LOCALE)) {
res = locale;
} else {
res = GREGORIAN_LOCALE;
}
return res;
}
 
static String formatSecondFraction(final Locale styleLocale, final BigDecimal seconds, final int decPlaces) {
if (decPlaces > 0) {
final DecimalFormat decFormat = new DecimalFormat();
106,18 → 99,35
}
 
public DateStyle(final ODPackage pkg, Element elem) {
super(pkg, elem, Date.class);
super(pkg, elem, ODValueType.DATE);
}
 
@Override
protected Object convertNonNull(Object o) {
if (o instanceof Number)
return getEpoch().getDate(NumberConvertor.toBigDecimal((Number) o));
else
return null;
}
 
private final void format(final StringBuilder res, final StringBuilder pattern, final Locale styleLocale, final Calendar currentCalendar, final Date d) {
if (pattern.length() > 0) {
final SimpleDateFormat fmt = new SimpleDateFormat(pattern.toString(), styleLocale);
pattern.setLength(0);
fmt.setCalendar((Calendar) currentCalendar.clone());
res.append(fmt.format(d));
}
}
 
@Override
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
final Date d = o instanceof Calendar ? ((Calendar) o).getTime() : (Date) o;
final Namespace numberNS = this.getElement().getNamespace();
final Locale styleLocale = getLocale(getElement());
final Locale styleCalendarLocale = getCalendarLocale(styleLocale);
final Calendar styleCalendar = Calendar.getInstance(styleLocale);
final StringBuilder res = new StringBuilder();
 
Locale currentCalendarLocale = styleCalendarLocale;
Calendar currentCalendar = styleCalendar;
final StringBuilder sb = new StringBuilder();
 
@SuppressWarnings("unchecked")
124,14 → 134,11
final List<Element> children = this.getElement().getChildren();
for (final Element elem : children) {
if (elem.getNamespace().equals(numberNS)) {
final Locale calendarLocaleElem = getCalendarLocale(elem, styleCalendarLocale);
if (!calendarLocaleElem.equals(currentCalendarLocale)) {
if (sb.length() > 0) {
res.append(new SimpleDateFormat(sb.toString(), currentCalendarLocale).format(d));
sb.setLength(0);
final Calendar calendarLocaleElem = getCalendar(elem, styleCalendar);
if (!calendarLocaleElem.equals(currentCalendar)) {
format(res, sb, styleLocale, currentCalendar, d);
currentCalendar = calendarLocaleElem;
}
currentCalendarLocale = calendarLocaleElem;
}
 
if (elem.getName().equals("text")) {
DataStyle.addStringLiteral(sb, elem.getText());
140,7 → 147,11
} else if (elem.getName().equals("year")) {
sb.append(isShort(elem) ? "yy" : "yyyy");
} else if (elem.getName().equals("quarter")) {
final int quarter = Calendar.getInstance(GREGORIAN_LOCALE).get(Calendar.MONTH) / 3 + 1;
final Calendar cal = (Calendar) currentCalendar.clone();
cal.setTime(d);
final double quarterLength = cal.getActualMaximum(Calendar.MONTH) / 4.0;
final int quarter = (int) (cal.get(Calendar.MONTH) / quarterLength + 1);
assert quarter >= 1 && quarter <= 4;
// TODO localize and honor short/long style
reportError("Quarters are not localized", lenient);
DataStyle.addStringLiteral(sb, isShort(elem) ? "Q" + quarter : "Q" + quarter);
184,6 → 195,7
}
}
}
return new SimpleDateFormat(sb.toString(), currentCalendarLocale).format(d);
format(res, sb, styleLocale, currentCalendar, d);
return res.toString();
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/TimeStyle.java
14,12 → 14,16
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.StyleProperties;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.CellStyle;
import org.openconcerto.utils.TimeUtils;
import org.openconcerto.utils.convertor.NumberConvertor;
 
import java.math.BigDecimal;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
 
48,12 → 52,21
}
 
public TimeStyle(final ODPackage pkg, Element elem) {
super(pkg, elem, Duration.class);
super(pkg, elem, ODValueType.TIME);
}
 
@Override
protected Duration convertNonNull(Object o) {
if (o instanceof Number) {
return TimeUtils.timePartToDuration(getEpoch().getDate(NumberConvertor.toBigDecimal((Number) o)));
} else {
return null;
}
}
 
@Override
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
final Duration d = (Duration) o;
final Duration d = o instanceof Calendar ? TimeUtils.timePartToDuration((Calendar) o) : (Duration) o;
final Namespace numberNS = this.getElement().getNamespace();
final StringBuilder sb = new StringBuilder();
 
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/BooleanStyle.java
14,10 → 14,15
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.CellStyle;
import org.openconcerto.utils.NumberUtils;
 
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
 
import org.jdom.Element;
import org.jdom.Namespace;
32,13 → 37,47
}
};
 
public static final Boolean toBoolean(Object o) {
if (o instanceof Boolean)
return (Boolean) o;
else if (o instanceof Number)
return Boolean.valueOf(!NumberUtils.areNumericallyEqual(0, (Number) o));
else
return null;
}
 
private static final Map<String, String> trues = new HashMap<String, String>(), falses = new HashMap<String, String>();
 
private static final void add(final String iso3, final String trueS, final String falseS) {
if (trueS == null || falseS == null)
throw new NullPointerException();
trues.put(iso3, trueS);
falses.put(iso3, falseS);
}
 
static {
add(Locale.FRENCH.getISO3Language(), "VRAI", "FAUX");
add(Locale.ENGLISH.getISO3Language(), "TRUE", "FALSE");
add(Locale.GERMAN.getISO3Language(), "WAHR", "FALSCH");
add(Locale.ITALY.getISO3Language(), "VERO", "FALSO");
add("spa", "VERDADERO", "FALSO");
add("por", "VERDADEIRO", "FALSO");
}
 
public BooleanStyle(final ODPackage pkg, Element elem) {
super(pkg, elem, Boolean.class);
super(pkg, elem, ODValueType.BOOLEAN);
}
 
@Override
protected Boolean convertNonNull(Object o) {
return toBoolean(o);
}
 
@Override
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
final Boolean b = (Boolean) o;
final Namespace numberNS = this.getElement().getNamespace();
final Locale styleLocale = DateStyle.getLocale(getElement());
final StringBuilder sb = new StringBuilder();
@SuppressWarnings("unchecked")
final List<Element> children = this.getElement().getChildren();
47,12 → 86,20
if (elem.getName().equals("text")) {
sb.append(elem.getText());
} else if (elem.getName().equals("boolean")) {
// TODO localize
// TODO localize more
final String s;
final String iso3Lang = styleLocale.getISO3Language();
final String localized = b.booleanValue() ? trues.get(iso3Lang) : falses.get(iso3Lang);
if (localized != null) {
s = localized;
} else {
reportError("Boolean not localized", lenient);
sb.append(o.toString());
s = b.toString();
}
sb.append(s);
}
}
}
return sb.toString();
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/PercentStyle.java
14,6 → 14,7
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.CellStyle;
 
33,10 → 34,15
};
 
public PercentStyle(final ODPackage pkg, Element elem) {
super(pkg, elem, Number.class);
super(pkg, elem, ODValueType.PERCENTAGE);
}
 
@Override
protected Object convertNonNull(Object o) {
return NumberStyle.toNumber(o, getEpoch());
}
 
@Override
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
final Number n = (Number) o;
final Namespace numberNS = this.getElement().getNamespace();
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/TextStyle.java
14,6 → 14,7
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.CellStyle;
 
33,10 → 34,15
};
 
public TextStyle(final ODPackage pkg, Element elem) {
super(pkg, elem, Object.class);
super(pkg, elem, ODValueType.STRING);
}
 
@Override
protected String convertNonNull(Object o) {
return o.toString();
}
 
@Override
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
final Namespace numberNS = this.getElement().getNamespace();
final StringBuilder sb = new StringBuilder();
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/data/NumberStyle.java
13,13 → 13,19
package org.openconcerto.openoffice.style.data;
 
import org.openconcerto.openoffice.ODEpoch;
import org.openconcerto.openoffice.ODPackage;
import org.openconcerto.openoffice.ODValueType;
import org.openconcerto.openoffice.XMLVersion;
import org.openconcerto.openoffice.spreadsheet.CellStyle;
import org.openconcerto.openoffice.spreadsheet.MutableCell;
 
import java.util.Calendar;
import java.util.Date;
import java.util.List;
 
import javax.xml.datatype.Duration;
 
import org.jdom.Element;
import org.jdom.Namespace;
 
33,11 → 39,41
}
};
 
public static final Number toNumber(Object value, ODEpoch epoch) {
final Number res;
if (value instanceof Number) {
res = (Number) value;
} else if (value instanceof Boolean) {
res = ((Boolean) value).booleanValue() ? 1 : 0;
} else if ((value instanceof Duration || value instanceof Date || value instanceof Calendar)) {
if (value instanceof Duration) {
res = epoch.getDays((Duration) value);
} else {
final Calendar cal;
if (value instanceof Calendar) {
cal = (Calendar) value;
} else {
cal = Calendar.getInstance();
cal.setTime((Date) value);
}
res = epoch.getDays(cal);
}
} else {
res = null;
}
return res;
}
 
public NumberStyle(final ODPackage pkg, Element elem) {
super(pkg, elem, Number.class);
super(pkg, elem, ODValueType.FLOAT);
}
 
@Override
protected Number convertNonNull(Object value) {
return toNumber(value, getEpoch());
}
 
@Override
public String format(Object o, CellStyle defaultStyle, boolean lenient) {
final Number n = (Number) o;
final Namespace numberNS = this.getElement().getNamespace();
/trunk/OpenConcerto/src/org/openconcerto/openoffice/style/RelationalOperator.java
New file
0,0 → 1,100
/*
* 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.openoffice.style;
 
import org.openconcerto.utils.CompareUtils;
 
import java.util.HashMap;
import java.util.Map;
 
public enum RelationalOperator {
LT("<") {
@Override
protected boolean evaluate(int i) {
return i < 0;
}
},
GT(">") {
@Override
protected boolean evaluate(int i) {
return i > 0;
}
},
LE("<=") {
@Override
protected boolean evaluate(int i) {
return i <= 0;
}
},
GE(">=") {
@Override
protected boolean evaluate(int i) {
return i >= 0;
}
},
EQ("=") {
@Override
protected boolean evaluate(int i) {
return i == 0;
}
},
NE("!=") {
@Override
protected boolean evaluate(int i) {
return !EQ.evaluate(i);
}
};
 
private final String s;
 
private RelationalOperator(final String s) {
this.s = s;
}
 
public final String asString() {
return this.s;
}
 
public final boolean compare(final Object o1, final Object o2) {
return this.evaluate(CompareUtils.compare(o1, o2));
}
 
protected abstract boolean evaluate(int i);
 
/**
* Regular expression with all operators.
*
* <pre>
* &lt;|&gt;|&lt;=|&gt;=|=|!=
* </pre>
*/
public static final String OR_PATTERN;
private static final Map<String, RelationalOperator> instances;
static {
instances = new HashMap<String, RelationalOperator>();
final StringBuilder sb = new StringBuilder(32);
for (final RelationalOperator op : values()) {
instances.put(op.s, op);
sb.append(op.asString());
sb.append('|');
}
// remove last |
sb.setLength(sb.length() - 1);
OR_PATTERN = sb.toString();
}
 
public static RelationalOperator getInstance(String op) {
return instances.get(op);
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ODXMLDocument.java
16,6 → 16,7
*/
package org.openconcerto.openoffice;
 
import org.openconcerto.openoffice.ODPackage.RootElement;
import org.openconcerto.openoffice.spreadsheet.ColumnStyle;
import org.openconcerto.utils.cc.IFactory;
import org.openconcerto.xml.JDOMUtils;
90,6 → 91,13
return Collections.unmodifiableSet(namePrefixes.keySet());
}
 
public static final ODXMLDocument create(final Document doc) {
if (RootElement.fromDocument(doc) == RootElement.SINGLE_CONTENT)
return new ODSingleXMLDocument(doc);
else
return new ODXMLDocument(doc);
}
 
private final Document content;
private final XMLFormatVersion version;
private final ChildCreator childCreator;
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ODSingleXMLDocument.java
15,13 → 15,9
 
import static org.openconcerto.openoffice.ODPackage.RootElement.CONTENT;
import org.openconcerto.openoffice.ODPackage.RootElement;
import org.openconcerto.openoffice.text.TextNode;
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.CopyUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.ProductInfo;
import org.openconcerto.utils.cc.IFactory;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.SimpleXMLPath;
import org.openconcerto.xml.Step;
33,7 → 29,6
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
56,7 → 51,7
*
* @author Sylvain CUAZ 24 nov. 2004
*/
public class ODSingleXMLDocument extends ODXMLDocument implements Cloneable, ODDocument {
public class ODSingleXMLDocument extends ODXMLDocument implements Cloneable {
 
final static Set<String> DONT_PREFIX;
static {
82,14 → 77,12
* @return the merged document.
*/
public static ODSingleXMLDocument createFromDocument(Document content, Document style) {
return createFromDocument(content, style, null);
return ODPackage.createFromDocuments(content, style).toSingle();
}
 
public static ODSingleXMLDocument createFromDocument(Document content, Document style, Document settings) {
return createFromDocument(content, style, settings, null, new ODPackage());
}
 
static ODSingleXMLDocument createFromDocument(Document content, Document style, Document settings, Document meta, ODPackage files) {
static ODSingleXMLDocument create(ODPackage files) {
final Document content = files.getContent().getDocument();
final Document style = files.getDocument(RootElement.STYLES.getZipEntry());
// signal that the xml is a complete document (was document-content)
final Document singleContent = RootElement.createSingle(content);
copyNS(content, singleContent);
97,8 → 90,8
final Element root = singleContent.getRootElement();
root.addContent(content.getRootElement().removeContent());
// see section 2.1.1 first meta, then settings, then the rest
prependToRoot(settings, root);
prependToRoot(meta, root);
prependToRoot(files.getDocument(RootElement.SETTINGS.getZipEntry()), root);
prependToRoot(files.getDocument(RootElement.META.getZipEntry()), root);
final ODSingleXMLDocument single = new ODSingleXMLDocument(singleContent, files);
if (single.getChild("body") == null)
throw new IllegalArgumentException("no body in " + single);
139,7 → 132,6
* @return the merged file.
* @throws JDOMException if the file is not a valid OpenDocument file.
* @throws IOException if the file can't be read.
* @see #createFromDocument(Document, Document)
*/
public static ODSingleXMLDocument createFromFile(File f) throws JDOMException, IOException {
// this loads all linked files
266,39 → 258,11
return this.numero;
}
 
@Override
public ODPackage getPackage() {
return this.pkg;
}
 
/**
* Append a paragraph or a heading.
*
* @param p paragraph to add.
*/
public synchronized void add(TextNode p) {
this.add(p, null, -1);
}
 
public synchronized void add(TextNode p, Element where, int index) {
// add it first to avoid infinite loop, since setDocument() can call this method
final Element addToElem = where == null ? this.getBody() : where;
if (index < 0)
addToElem.addContent(p.getElement());
else
addToElem.addContent(index, p.getElement());
 
try {
p.setDocument(this);
} catch (RuntimeException e) {
// the paragraph can throw an exception to notify that is not compatible with us (eg
// missing styles), in that case remove it
p.getElement().detach();
throw e;
}
}
 
/**
* Append a document.
*
* @param doc the document to add.
545,77 → 509,6
}
 
/**
* Verify that styles referenced by this document are indeed defined. NOTE this method is not
* perfect : not all problems are detected.
*
* @return <code>null</code> if no problem has been found, else a String describing it.
*/
public final String checkStyles() {
try {
final CollectionMap<String, String> stylesNames = this.getStylesNames();
// text:style-name : text:p, text:span
// table:style-name : table:table, table:row, table:column, table:cell
// draw:style-name : draw:text-box
// style:data-style-name : <style:style style:family="table-cell">
// TODO check by family
final Set<String> names = new HashSet<String>(stylesNames.values());
final Iterator attrs = this.getXPath(".//@text:style-name | .//@table:style-name | .//@draw:style-name | .//@style:data-style-name | .//@style:list-style-name")
.selectNodes(this.getDocument()).iterator();
while (attrs.hasNext()) {
final Attribute attr = (Attribute) attrs.next();
if (!names.contains(attr.getValue()))
return "unknown style referenced by " + attr.getName() + " in " + JDOMUtils.output(attr.getParent());
}
// TODO check other references like page-*-name (§3 of #prefix())
} catch (IllegalStateException e) {
return ExceptionUtils.getStackTrace(e);
} catch (JDOMException e) {
return ExceptionUtils.getStackTrace(e);
}
return null;
}
 
private final CollectionMap<String, String> getStylesNames() throws IllegalStateException {
// section 14.1 § Style Name : style:family + style:name is unique
final CollectionMap<String, String> res = new CollectionMap<String, String>(HashSet.class);
 
final List<Element> nodes = new ArrayList<Element>();
nodes.add(this.getChild("styles"));
nodes.add(this.getChild("automatic-styles"));
 
try {
{
final Iterator iter = this.getXPath("./style:style/@style:name").selectNodes(nodes).iterator();
while (iter.hasNext()) {
final Attribute attr = (Attribute) iter.next();
final String styleName = attr.getValue();
final String family = attr.getParent().getAttributeValue("family", attr.getNamespace());
if (res.getNonNull(family).contains(styleName))
throw new IllegalStateException("duplicate style in " + family + " : " + styleName);
res.put(family, styleName);
}
}
{
final List<String> dataStyles = Arrays.asList("number-style", "currency-style", "percentage-style", "date-style", "time-style", "boolean-style", "text-style");
final String xpDataStyles = org.openconcerto.utils.CollectionUtils.join(dataStyles, " | ", new ITransformer<String, String>() {
@Override
public String transformChecked(String input) {
return "./number:" + input;
}
});
final Iterator listIter = this.getXPath("./text:list-style | " + xpDataStyles).selectNodes(nodes).iterator();
while (listIter.hasNext()) {
final Element elem = (Element) listIter.next();
res.put(elem.getQualifiedName(), elem.getAttributeValue("name", getVersion().getSTYLE()));
}
}
} catch (JDOMException e) {
throw new IllegalStateException(e);
}
return res;
}
 
/**
* Préfixe les attributs en ayant besoin.
*
* @param elem l'élément à préfixer.
822,13 → 715,13
final Map<RootElement, Document> res = new HashMap<RootElement, Document>();
final XMLVersion version = getVersion();
final Element root = this.getDocument().getRootElement();
final String officeVersion = getFormatVersion().getOfficeVersion();
final XMLFormatVersion officeVersion = getFormatVersion();
 
// meta
{
final Element thisMeta = root.getChild("meta", version.getOFFICE());
if (thisMeta != null) {
final Document meta = createDocument(res, RootElement.META, version, officeVersion);
final Document meta = createDocument(res, RootElement.META, officeVersion);
meta.getRootElement().addContent(thisMeta.detach());
}
}
836,7 → 729,7
{
final Element thisSettings = root.getChild("settings", version.getOFFICE());
if (thisSettings != null) {
final Document settings = createDocument(res, RootElement.SETTINGS, version, officeVersion);
final Document settings = createDocument(res, RootElement.SETTINGS, officeVersion);
settings.getRootElement().addContent(thisSettings.detach());
}
}
843,7 → 736,7
// styles
// we must move office:styles, office:master-styles and referenced office:automatic-styles
{
final Document styles = createDocument(res, RootElement.STYLES, version, officeVersion);
final Document styles = createDocument(res, RootElement.STYLES, officeVersion);
// don't bother finding out which font is used where since there isn't that many of them
styles.getRootElement().addContent((Element) root.getChild(getFontDecls()[0], version.getOFFICE()).clone());
// extract common styles
874,7 → 767,7
// content
{
this.pkg = null;
final Document content = createDocument(res, RootElement.CONTENT, version, officeVersion);
final Document content = createDocument(res, RootElement.CONTENT, officeVersion);
getContentTypeVersioned().setType(content);
content.getRootElement().addContent(root.removeContent());
}
881,8 → 774,8
return res;
}
 
private Document createDocument(final Map<RootElement, Document> res, RootElement rootElement, final XMLVersion version, final String officeVersion) {
final Document doc = rootElement.createDocument(version, officeVersion);
private Document createDocument(final Map<RootElement, Document> res, RootElement rootElement, final XMLFormatVersion version) {
final Document doc = rootElement.createDocument(version);
copyNS(this.getDocument(), doc);
res.put(rootElement, doc);
return doc;
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ContentTypeVersioned.java
128,7 → 128,7
*
* @param version the version.
* @return the body of the created document.
* @see #createContent(boolean)
* @see #createContent(XMLFormatVersion, boolean)
*/
public final Element createContent(final XMLFormatVersion version) {
return this.createContent(version, false);
141,12 → 141,12
* @param singleXML <code>true</code> for {@link RootElement#SINGLE_CONTENT}, <code>false</code>
* for {@link RootElement#CONTENT}.
* @return the body of the created document.
* @see #createPackage()
* @see #createPackage(XMLFormatVersion)
*/
public Element createContent(final XMLFormatVersion version, final boolean singleXML) {
checkVersion(version);
final RootElement rootElement = singleXML ? RootElement.SINGLE_CONTENT : RootElement.CONTENT;
final Document doc = rootElement.createDocument(getVersion(), version.getOfficeVersion());
final Document doc = rootElement.createDocument(version);
final Namespace officeNS = getVersion().getOFFICE();
setType(doc, rootElement, officeNS);
// don't forget that, otherwise OO crash
170,7 → 170,7
}
 
public void setType(final Document doc) {
this.setType(doc, RootElement.fromElementName(doc.getRootElement().getName()), getVersion().getOFFICE());
this.setType(doc, RootElement.fromDocument(doc), getVersion().getOFFICE());
}
 
// not safe
199,7 → 199,7
public Document createStyles(final XMLFormatVersion version) {
checkVersion(version);
final Namespace officeNS = getVersion().getOFFICE();
final Document styles = RootElement.STYLES.createDocument(getVersion(), version.getOfficeVersion());
final Document styles = RootElement.STYLES.createDocument(version);
// some consumers demand empty children
styles.getRootElement().addContent(asList(new Element("styles", officeNS), new Element("automatic-styles", officeNS), new Element("master-styles", officeNS)));
return styles;
212,12 → 212,7
* @return a new package with minimal {@link RootElement#CONTENT} and {@link RootElement#STYLES}
*/
public ODPackage createPackage(final XMLFormatVersion version) {
final ODPackage res = new ODPackage();
res.putFile(RootElement.CONTENT.getZipEntry(), this.createContent(version, false).getDocument());
res.putFile(RootElement.STYLES.getZipEntry(), this.createStyles(version));
// add mimetype since ODPackage cannot find out about templates
res.putFile("mimetype", this.getMimeType().getBytes(ODPackage.MIMETYPE_ENC));
return res;
return ODPackage.createFromDocuments(this, this.createContent(version, false).getDocument(), this.createStyles(version), null, null);
}
 
// *** static
238,6 → 233,27
return null;
}
 
static public ContentTypeVersioned fromMime(byte[] mime) {
return fromMime(new String(mime, ODPackage.MIMETYPE_ENC));
}
 
static ContentTypeVersioned fromContent(final ODXMLDocument content) {
final ContentTypeVersioned res;
final XMLVersion vers = content.getVersion();
if (vers.equals(XMLVersion.OOo)) {
final Element contentRoot = content.getDocument().getRootElement();
final String docClass = contentRoot.getAttributeValue("class", contentRoot.getNamespace("office"));
res = ContentTypeVersioned.fromClass(docClass);
} else if (vers.equals(XMLVersion.OD)) {
final Element bodyChild = (Element) content.getChild("body").getChildren().get(0);
res = ContentTypeVersioned.fromBody(bodyChild.getName());
} else {
throw new IllegalStateException("Unknown content version : " + vers);
}
assert !res.isTemplate() : "template status cannot be inferred from content";
return res;
}
 
static ContentTypeVersioned fromClass(String name) {
return fromShortName(XMLVersion.OOo, name);
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ImmutableDocStyledNode.java
42,7 → 42,11
* @param styleClass our class of style, cannot be <code>null</code>.
*/
public ImmutableDocStyledNode(D parent, Element local, final Class<S> styleClass) {
super(local, styleClass);
this(parent, local, getStyleDesc(local, styleClass));
}
 
protected ImmutableDocStyledNode(D parent, Element local, StyleDesc<S> styleDesc) {
super(local, styleDesc);
this.parent = parent;
assert getDocuments(this.parent.getPackage()).contains(local.getDocument()) : "Local not in parent: " + parent;
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ODDocument.java
13,15 → 13,84
package org.openconcerto.openoffice;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
 
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
 
/**
* An ODF document, like a spreadsheet or a text file.
*
* @author Sylvain
*/
public interface ODDocument {
public XMLVersion getVersion();
public abstract class ODDocument {
 
public XMLFormatVersion getFormatVersion();
private final ODPackage pkg;
private ODEpoch epoch;
 
public ODPackage getPackage();
protected ODDocument(final ODPackage orig) {
// don't want multiple document per package.
if (orig.hasODDocument())
throw new IllegalStateException("ODPackage already has an ODDocument");
this.pkg = orig;
}
 
public final XMLVersion getVersion() {
return this.getFormatVersion().getXMLVersion();
}
 
public final XMLFormatVersion getFormatVersion() {
return this.getPackage().getFormatVersion();
}
 
public final ODPackage getPackage() {
return this.pkg;
}
 
public final Document getContentDocument() {
return this.getPackage().getContent().getDocument();
}
 
protected final Element getBody() {
return getPackage().getContentType().getBody(this.getContentDocument());
}
 
private final String findEpoch() throws ParseException {
final Namespace tableNS = getVersion().getTABLE();
final Element settings = this.getBody().getChild("calculation-settings", tableNS);
if (settings != null) {
final Element nullDateElem = settings.getChild("null-date", tableNS);
if (nullDateElem != null)
return nullDateElem.getAttributeValue("date-value", tableNS);
}
return null;
}
 
public final ODEpoch getEpoch() {
return this.getEpoch(false);
}
 
public final ODEpoch getEpoch(final boolean updateFromXML) {
if (this.epoch == null || updateFromXML) {
try {
this.epoch = ODEpoch.getInstance(this.findEpoch());
} catch (ParseException e) {
// quite rare
throw new IllegalStateException("Unable to parse the epoch of " + this, e);
}
}
assert this.epoch != null;
return this.epoch;
}
 
// *** Files
 
public File saveAs(File file) throws FileNotFoundException, IOException {
this.getPackage().setFile(file);
return this.getPackage().save();
}
}
/trunk/OpenConcerto/src/org/openconcerto/openoffice/StyledNode.java
26,6 → 26,10
*/
public abstract class StyledNode<S extends Style, D extends ODDocument> extends ODNode {
 
static protected final <S extends Style> StyleDesc<S> getStyleDesc(final Element local, final Class<S> styleClass) {
return Style.getStyleDesc(styleClass, XMLVersion.getVersion(local));
}
 
private final StyleDesc<S> styleClass;
 
/**
36,10 → 40,18
* @param styleClass our class of style, cannot be <code>null</code>.
*/
public StyledNode(Element local, final Class<S> styleClass) {
this(local, getStyleDesc(local, styleClass));
}
 
// allow to pass StyleDesc since Style.getStyleDesc() was the longest operation of this
// constructor, and this constructor is called for every Table, Column, Row and Cell, i.e.
// up to millions of times.
protected StyledNode(Element local, final StyleDesc<S> styleDesc) {
super(local);
if (styleClass == null)
throw new NullPointerException("null style class");
this.styleClass = Style.getStyleDesc(styleClass, XMLVersion.getVersion(getElement()));
if (styleDesc == null)
throw new NullPointerException("null style desc");
this.styleClass = styleDesc;
assert styleDesc.getVersion().equals(XMLVersion.getVersion(local));
assert this.styleClass.getRefElements().contains(this.getElement().getQualifiedName()) : this.getElement().getQualifiedName() + " not in " + this.styleClass;
}
 
47,14 → 59,23
public abstract D getODDocument();
 
public final S getStyle() {
// null avoid getting styleName if we haven't any Document
return this.getStyle(null);
}
 
protected final S getStyle(final String styleName) {
final D doc = this.getODDocument();
return doc == null ? null : this.getStyle(doc.getPackage(), getElement().getDocument());
return doc == null ? null : this.getStyle(doc.getPackage(), getElement().getDocument(), styleName == null ? getStyleName() : styleName);
}
 
protected final S getStyle(final ODPackage pkg, final Document doc) {
return this.styleClass.findStyle(pkg, doc, getStyleName());
return this.getStyle(pkg, doc, getStyleName());
}
 
protected final S getStyle(final ODPackage pkg, final Document doc, final String styleName) {
return this.styleClass.findStyleForNode(pkg, doc, this, styleName);
}
 
/**
* Assure that this node's style is only referenced by this. I.e. after this method returns the
* style of this node can be safely modified without affecting other nodes.
/trunk/OpenConcerto/src/org/openconcerto/openoffice/LengthUnit.java
25,7 → 25,7
* Units of length.
*
* @author Sylvain CUAZ
* @see http://www.w3.org/TR/xsl/#d0e5752
* @see <a href="http://www.w3.org/TR/xsl/#d0e5752">W3C Definitions</a>
*/
public enum LengthUnit {
/**
/trunk/OpenConcerto/src/org/openconcerto/openoffice/ODEpoch.java
New file
0,0 → 1,154
/*
* 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.openoffice;
 
import org.openconcerto.utils.TimeUtils;
 
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TimeZone;
 
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.Duration;
 
/**
* The null date of an OpenDocument.
*/
public final class ODEpoch {
 
static private final BigDecimal MS_PER_DAY = BigDecimal.valueOf(24l * 60l * 60l * 1000l);
static private final DateFormat DATE_FORMAT;
static private final ODEpoch DEFAULT_EPOCH;
static private final Map<String, ODEpoch> cache = new LinkedHashMap<String, ODEpoch>(4, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, ODEpoch> eldest) {
return this.size() > 16;
}
};
 
static {
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
DATE_FORMAT.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
try {
DEFAULT_EPOCH = new ODEpoch("1899-12-30");
} catch (ParseException e) {
// shouldn't happen since string are constants
throw new IllegalStateException(e);
}
}
 
static public final ODEpoch getDefaultEpoch() {
return DEFAULT_EPOCH;
}
 
static public final ODEpoch getInstance(String date) throws ParseException {
if (date == null || date.equals(DEFAULT_EPOCH.getDateString())) {
return DEFAULT_EPOCH;
} else {
ODEpoch res = cache.get(date);
if (res == null) {
res = new ODEpoch(date);
cache.put(date, res);
}
return res;
}
}
 
static private final Calendar parse(final String date) throws ParseException {
final Calendar cal = (Calendar) DATE_FORMAT.getCalendar().clone();
cal.setTime(DATE_FORMAT.parse(date));
return cal;
}
 
private final String dateString;
private final Calendar epochUTC;
 
private ODEpoch(final String date) throws ParseException {
this.dateString = date;
this.epochUTC = parse(date);
assert this.epochUTC.getTimeZone().equals(DATE_FORMAT.getTimeZone());
}
 
public final String getDateString() {
return this.dateString;
}
 
public final Calendar getCalendar() {
return (Calendar) this.epochUTC.clone();
}
 
private final Calendar getDate(final Duration duration) {
// If we don't use the UTC calendar, we go from 0:00 (the epoch), we add n days, we get
// to the last Sunday of March at 0:00, so far so good, but then we add say 10 hours, thus
// going through the change of offset, and arriving at 11:00.
final Calendar res = getCalendar();
duration.addTo(res);
return res;
}
 
public final Duration normalizeToDays(final Duration dur) {
final Duration res = dur.getYears() == 0 && dur.getMonths() == 0 ? dur : getDuration(getDays(dur));
assert res.getYears() == 0 && res.getMonths() == 0;
return res;
}
 
public final Duration normalizeToHours(final Duration dur) {
final Duration durationInDays = normalizeToDays(dur);
final BigInteger days = (BigInteger) durationInDays.getField(DatatypeConstants.DAYS);
final BigInteger hours = ((BigInteger) durationInDays.getField(DatatypeConstants.HOURS)).add(days.multiply(BigInteger.valueOf(24)));
return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, hours, (BigInteger) durationInDays.getField(DatatypeConstants.MINUTES),
(BigDecimal) durationInDays.getField(DatatypeConstants.SECONDS));
}
 
public final BigDecimal getDays(final Duration duration) {
return getDays(getDate(duration));
}
 
public final BigDecimal getDays(final Calendar cal) {
// can't use Duration.normalizeWith() since it doesn't handle DST, i.e. going from winter to
// summer at midnight will miss a day
final long diff = TimeUtils.normalizeLocalTime(cal) - this.epochUTC.getTimeInMillis();
return BigDecimal.valueOf(diff).divide(MS_PER_DAY, MathContext.DECIMAL128);
}
 
public final Calendar getDate(final BigDecimal days) {
return getDate(days, Calendar.getInstance());
}
 
public final Calendar getDate(final BigDecimal days, final Calendar res) {
final Calendar utcCal = getDate(getDuration(days));
// can't use getTimeZone().getOffset() since we have no idea for the UTC time
return TimeUtils.copyLocalTime(utcCal, res);
}
 
private final static Duration getDuration(final BigDecimal days) {
final BigDecimal posDays = days.abs();
final BigInteger wholeDays = posDays.toBigInteger().abs();
final BigDecimal hours = posDays.subtract(new BigDecimal(wholeDays)).multiply(BigDecimal.valueOf(24));
final BigInteger wholeHours = hours.toBigInteger();
final BigDecimal minutes = hours.subtract(new BigDecimal(wholeHours)).multiply(BigDecimal.valueOf(60));
final BigInteger wholeMinutes = minutes.toBigInteger();
// round to 16 digits, i.e. 10^-14 seconds is more than enough
// it is required since the number coming from getDays() might have been rounded
final BigDecimal seconds = minutes.subtract(new BigDecimal(wholeMinutes)).multiply(BigDecimal.valueOf(60)).round(MathContext.DECIMAL64);
return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, wholeDays, wholeHours, wholeMinutes, seconds);
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/table/AlternateTableCellRenderer.java
35,7 → 35,7
*/
public class AlternateTableCellRenderer extends TableCellRendererDecorator {
 
public static final Color COLOR_LIGHT_GRAY = new Color(243, 243, 243);
public static final Color COLOR_LIGHT_GRAY = new Color(243, 247, 251);
public static final Color DEFAULT_BG_COLOR = Color.WHITE;
/** Default map from white to gray */
public static final Map<Color, Color> DEFAULT_MAP = Collections.singletonMap(DEFAULT_BG_COLOR, COLOR_LIGHT_GRAY);
/trunk/OpenConcerto/src/org/openconcerto/ui/component/ITextSelectorCompletionThread.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/ui/component/ITextSelectorPopup.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/ui/component/ITextSelector.java
File deleted
/trunk/OpenConcerto/src/org/openconcerto/ui/component/ITextComboCache.java
18,9 → 18,16
public interface ITextComboCache {
 
/**
* Can this cache be used.
*
* @return <code>true</code> if this cache can be used.
*/
public boolean isValid();
 
/**
* Force le chargement du cache (en synchrone) et le renvoi
*/
public List<String> loadCache();
public List<String> loadCache(final boolean readCache);
 
/**
* Retourne les éléments du cache, et le charge de manière synchrone si n'a jamais été chargé
/trunk/OpenConcerto/src/org/openconcerto/ui/component/ImmutableITextComboCache.java
29,6 → 29,11
}
 
@Override
public boolean isValid() {
return true;
}
 
@Override
public final void addToCache(String string) {
throw new UnsupportedOperationException();
}
40,11 → 45,11
 
@Override
public final List<String> getCache() {
return this.loadCache();
return this.loadCache(true);
}
 
@Override
public List<String> loadCache() {
public List<String> loadCache(final boolean dsCache) {
return this.cache;
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/component/MutableListCombo.java
28,6 → 28,10
 
void removeCurrentText();
 
boolean canReload();
 
void reload();
 
/**
* The component and where the popup should appear.
*
/trunk/OpenConcerto/src/org/openconcerto/ui/component/IComboCacheListModel.java
New file
0,0 → 1,125
/*
* 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;
 
import org.openconcerto.ui.component.combo.ISearchableCombo;
import org.openconcerto.utils.change.CollectionChangeEvent;
import org.openconcerto.utils.change.IListDataEvent;
import org.openconcerto.utils.model.DefaultIMutableListModel;
import org.openconcerto.utils.model.Reloadable;
 
import java.util.Collection;
import java.util.List;
 
import javax.swing.SwingWorker;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
 
/**
* An IMutableListModel with items from {@link ITextComboCache}.
*
* @author Sylvain CUAZ
* @see #load(Runnable)
*/
public class IComboCacheListModel extends DefaultIMutableListModel<String> implements Reloadable {
 
private final ITextComboCache cache;
private final ListDataListener l;
 
public IComboCacheListModel(final ITextComboCache c) {
this.cache = c;
this.l = new ListDataListener() {
 
@SuppressWarnings("unchecked")
public void contentsChanged(ListDataEvent e) {
// selection change, see DefaultIMutableListModel#setSelectedItem()
if (e.getIndex0() < 0)
return;
 
final CollectionChangeEvent evt = ((IListDataEvent) e).getCollectionChangeEvent();
this.remove(evt);
this.add(evt.getItemsAdded());
}
 
public void intervalAdded(ListDataEvent e) {
this.add(getList().subList(e.getIndex0(), e.getIndex1() + 1));
}
 
public void intervalRemoved(ListDataEvent e) {
this.remove(((IListDataEvent) e).getCollectionChangeEvent());
}
 
private void add(Collection<String> toAdd) {
for (final String s : toAdd) {
IComboCacheListModel.this.cache.addToCache(s);
}
}
 
@SuppressWarnings("unchecked")
private void remove(CollectionChangeEvent evt) {
for (final String s : (Collection<String>) evt.getItemsRemoved())
IComboCacheListModel.this.cache.deleteFromCache(s);
}
};
}
 
public final void load(final Runnable r, final boolean readCache) {
if (this.cache.isValid()) {
new SwingWorker<List<String>, Object>() {
 
@Override
protected List<String> doInBackground() throws Exception {
return IComboCacheListModel.this.cache.loadCache(readCache);
}
 
@Override
protected void done() {
// don't remove and add from the cache, items just came from it
removeListDataListener(IComboCacheListModel.this.l);
removeAllElements();
try {
addAll(get());
} catch (Exception e1) {
// tant pis, pas de cache
e1.printStackTrace();
}
addListDataListener(IComboCacheListModel.this.l);
if (r != null)
r.run();
}
 
}.execute();
}
}
 
@Override
public void reload() {
this.load(null, false);
}
 
/**
* Load this and only afterwards call
* {@link ISearchableCombo#initCache(org.openconcerto.utils.model.IListModel)}.
*
* @param combo the combo to initialise.
*/
public void initCacheLater(final ISearchableCombo<String> combo) {
this.load(new Runnable() {
@Override
public void run() {
combo.initCache(IComboCacheListModel.this);
}
}, true);
}
}
/trunk/OpenConcerto/src/org/openconcerto/ui/component/ITextCombo.java
61,7 → 61,7
 
private final String defaultValue;
private final ComboLockedMode locked;
private final ValueChangeSupport supp;
private final ValueChangeSupport<String> supp;
protected final boolean autoComplete;
protected boolean keyPressed;
private boolean completing;
181,9 → 181,19
public void removeCurrentText() {
ITextCombo.this.removeCurrentText();
}
 
@Override
public boolean canReload() {
return true;
}
 
@Override
public void reload() {
ITextCombo.this.loadCache(true);
}
}).listen();
 
this.loadCache();
this.loadCache(false);
 
// ATTN marche car locked est final
if (!this.isLocked()) {
285,15 → 295,16
// *** cache
 
// charge les elements de completion si besoin
private synchronized final void loadCache() {
private synchronized final void loadCache(final boolean force) {
if (!this.cacheLoading) {
this.modeToSet = this.isEnabled();
this.setEnabled(false);
this.objToSelect = this.getValue();
this.cacheLoading = true;
final SwingWorker sw = new SwingWorker<List<String>, Object>() {
final SwingWorker<List<String>, Object> sw = new SwingWorker<List<String>, Object>() {
@Override
protected List<String> doInBackground() throws Exception {
return ITextCombo.this.cache.getCache();
return force ? ITextCombo.this.cache.loadCache(false) : ITextCombo.this.cache.getCache();
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/ui/component/combo/ISearchableCombo.java
26,7 → 26,9
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.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;
33,6 → 35,7
import org.openconcerto.utils.model.IListModel;
import org.openconcerto.utils.model.IMutableListModel;
import org.openconcerto.utils.model.ListComboBoxModel;
import org.openconcerto.utils.model.Reloadable;
import org.openconcerto.utils.text.SimpleDocumentListener;
 
import java.awt.Color;
64,6 → 67,7
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
 
408,7 → 412,7
 
/**
* Returns the actions added at the end of the list of items. The name of the action will be
* displayed and its actionPerformed() invoked when choosed.
* displayed and its actionPerformed() invoked when chosen.
*
* @return the list of actions
*/
436,6 → 440,8
if (!(acache instanceof IMutableListModel))
throw new IllegalArgumentException(this + " is unlocked but " + acache + " is not mutable");
final IMutableListModel<T> mutable = (IMutableListModel<T>) acache;
final boolean isReloadable = mutable instanceof Reloadable;
final Reloadable rel = isReloadable ? (Reloadable) mutable : null;
new MutableListComboPopupListener(new MutableListCombo() {
public ComboLockedMode getMode() {
return ISearchableCombo.this.getMode();
454,6 → 460,16
public void removeCurrentText() {
mutable.removeElement(getValue());
}
 
@Override
public boolean canReload() {
return isReloadable;
}
 
@Override
public void reload() {
rel.reload();
}
}).listen();
}
 
497,10 → 513,29
}
 
private void addItems(final int index, final Collection<T> originalItems) {
// selection cannot change
assert SwingUtilities.isEventDispatchThread();
final ISearchableComboItem<T> sel = getSelection();
final T selOriginal = sel == null ? null : sel.getOriginal();
 
final List<ISearchableComboItem<T>> toAdd = new ArrayList<ISearchableComboItem<T>>(originalItems.size());
for (final T originalItem : originalItems) {
final ISearchableComboItem<T> textSelectorItem = createItem(originalItem);
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)
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";
} else {
// reuse the selected value, otherwise the popup will select nothing
if (sel != null && CompareUtils.equals(selOriginal, originalItem))
textSelectorItem = sel;
else
textSelectorItem = createItem(originalItem);
this.itemsByOriginalItem.put(originalItem, textSelectorItem);
}
toAdd.add(textSelectorItem);
}
// only 1 fire
510,7 → 545,8
private void rmItemsFromModel(final int index0, final int index1) {
getModel().removeElementsAt(index0, index1);
// remove from our map
this.itemsByOriginalItem.keySet().retainAll(getCache().getList());
// ATTN for ~35000 items, new HashSet() got us from 6000ms to 8ms !
this.itemsByOriginalItem.keySet().retainAll(new HashSet<T>(getCache().getList()));
}
 
// conversion
579,7 → 615,7
// set
 
public void resetValue() {
this.setValue(null);
this.setValue((T) null);
}
 
public final void setValue(final T val) {
603,15 → 639,20
}
}
 
private final void setValue(final T val, final boolean valid) {
log("entering " + this.getClass().getSimpleName() + ".setValue " + val + " valid: " + valid);
private final boolean setValid(final boolean valid) {
final boolean invalidChange = this.invalidEdit != !valid;
if (invalidChange) {
this.invalidEdit = !valid;
this.text.setForeground(this.invalidEdit ? Color.GRAY : Color.BLACK);
}
return invalidChange;
}
 
if (this.getValue() != val) {
private final void setValue(final T val, final boolean valid) {
log("entering " + this.getClass().getSimpleName() + ".setValue " + val + " valid: " + valid);
final boolean invalidChange = this.setValid(valid);
 
if (!CompareUtils.equals(this.getValue(), val)) {
log("this.getValue() != val :" + this.getValue());
if (val == null)
this.setSelection(null);
634,6 → 675,24
}
}
 
// perhaps try to factor with the other setValue()
final void setValue(final ISearchableComboItem<T> val) {
log("entering " + this.getClass().getSimpleName() + ".setValue(ISearchableComboItem) " + val);
assert new IdentityHashSet<ISearchableComboItem<T>>(this.getModelValues()).contains(val) : "Item not in model, perhaps use setValue(T)";
// valid since val is in our model
final boolean invalidChange = this.setValid(true);
 
if (!CompareUtils.equals(this.getSelection(), val)) {
this.setSelection(val);
} else if (invalidChange) {
log("this.getSelection() == val and invalidChange");
// since val hasn't changed the model won't fire and thus our selectionChanged()
// will not be called, but it has to since invalidEdit did change
// so the text must be changed, and listeners notified
this.selectionChanged();
}
}
 
private final void setSelection(final ISearchableComboItem<T> val) {
log("entering " + this.getClass().getSimpleName() + ".setSelection " + val);
this.getModel().setSelectedItem(val);
787,8 → 846,8
* with the desired rows.
*
* @param rows the new row count.
* @param textArea <code>true</code> if the editor should be a text area (ie can have more than
* one line), <code>null</code> to retain the current editor, ignored if
* @param textArea <code>true</code> if the editor should be a text area (i.e. can have more
* than one line), <code>null</code> to retain the current editor, ignored if
* <code>rows</code> >= 2.
*/
public final void setRows(int rows, final Boolean textArea) {
/trunk/OpenConcerto/src/org/openconcerto/ui/component/combo/ISearchableComboPopup.java
13,8 → 13,6
package org.openconcerto.ui.component.combo;
 
import org.openconcerto.utils.model.SimpleListDataListener;
 
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
36,7 → 34,6
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ListDataEvent;
 
public class ISearchableComboPopup<T> extends JPopupMenu {
 
53,26 → 50,7
uiInit();
// Listeners
this.list.addMouseMotionListener(new ListMouseMotionHandler());
// JList always displays visibleRowCount even when fewer items exists
// so if we put a high number we get a big blank popup
// instead listen to model change to adjust row count
this.getListModel().addListDataListener(new SimpleListDataListener() {
@Override
public void contentsChanged(ListDataEvent e) {
// ATTN row count always gets back to zero when the contents change (because of
// removeAll())
final int rowCount = Math.min(getListModel().getSize(), 30);
// checking if rowCount changes doesn't work (one reason is probably that we're
// called before Swing and so setVisible displays an empty list)
ISearchableComboPopup.this.list.setVisibleRowCount(rowCount);
if (rowCount > 0 && isVisible()) {
// since "visible row count" is not dynamic
setVisible(false);
setVisible(true);
}
}
});
}
 
private ISearchableCombo<T> getCombo() {
return this.text;
98,6 → 76,7
final JLabel comp = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
comp.setFont(getCombo().getFont());
if (value instanceof Action) {
comp.setFont(comp.getFont().deriveFont(Font.ITALIC));
comp.setText((String) ((Action) value).getValue(Action.NAME));
comp.setIcon(null);
} else {
207,7 → 186,9
// if no selection, don't change the combo
if (sel != null) {
if (sel instanceof ISearchableComboItem) {
this.getCombo().setValue(((ISearchableComboItem<T>) sel).getOriginal());
// don't call setValue() with sel.getOriginal() to handle list with the same item
// multiple times.
this.getCombo().setValue((ISearchableComboItem<T>) sel);
} else if (sel instanceof Action) {
((Action) sel).actionPerformed(new ActionEvent(this.getCombo(), ActionEvent.ACTION_PERFORMED, this.getCombo().getName()));
} else
217,6 → 198,24
}
 
public void open() {
// JList always displays visibleRowCount even when fewer items exists
// so if we put a high number we get a big blank popup
// handle this in open() and not with a SimpleListDataListener since :
// 1. open() is called only once whereas add is called multiple times in
// ISearchableCombo.setMatchingCompletions().
// 2. open() is always called when the list is modified.
final int size = getListModel().getSize();
// rowCount == 0 looks like a bug so show 3 empty rows
final int rowCount = size == 0 ? 3 : Math.min(size, 30);
if (this.list.getVisibleRowCount() != rowCount) {
// checking if rowCount changes doesn't work (one reason is probably that we're
// called before Swing and so setVisible displays an empty list)
this.list.setVisibleRowCount(rowCount);
if (this.isShowing()) {
// since "visible row count" is not dynamic
setVisible(false);
}
}
// si on est pas déjà affiché
// afficher même qd pas d'items : si l'user clique il faut qu'il voit la liste même vide
if (!this.isShowing())
/trunk/OpenConcerto/src/org/openconcerto/ui/component/JRadioButtons.java
144,6 → 144,7
 
private final void addBtn(String btnLabel, V id) {
final JRadioButton btn = new JRadioButton(btnLabel);
btn.setOpaque(false);
for (final MouseListener l : this.mouseListeners) {
btn.addMouseListener(l);
}
/trunk/OpenConcerto/src/org/openconcerto/ui/component/MutableListComboPopupListener.java
29,6 → 29,7
import java.util.HashSet;
import java.util.Set;
 
import javax.swing.AbstractAction;
import javax.swing.JComboBox;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
130,7 → 131,15
MutableListComboPopupListener.this.combo.removeCurrentText();
}
});
 
if (this.combo.canReload()) {
this.popup.add(new JMenuItem(new AbstractAction("Recharger la liste") {
public void actionPerformed(ActionEvent e) {
MutableListComboPopupListener.this.combo.reload();
}
}));
}
}
// popups are never closed in a JComboBox (except when choosing a menu item)
if (SwingThreadUtils.getAncestorOrSelf(JComboBox.class, this.combo.getPopupComp()) != null)
addPopup(this.popup);
/trunk/OpenConcerto/src/org/openconcerto/ui/JDate.java
18,10 → 18,12
import org.openconcerto.utils.checks.ValidListener;
import org.openconcerto.utils.checks.ValidState;
 
import java.awt.Component;
import java.beans.PropertyChangeListener;
import java.util.Calendar;
import java.util.Date;
 
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.text.JTextComponent;
 
63,6 → 65,16
this.resetValue();
}
 
@Override
public void updateUI() {
super.updateUI();
// can't change BasicDatePickerUI behavior, so do it here
for (final Component child : this.getComponents()) {
if (child instanceof JButton)
((JComponent) child).setOpaque(false);
}
}
 
public final void resetValue() {
if (this.fillWithCurrentDate) {
this.setValue(new Date());
/trunk/OpenConcerto/src/org/openconcerto/ui/preferences/JavaPrefPreferencePanel.java
21,6 → 21,7
import org.openconcerto.utils.checks.ValidObject;
import org.openconcerto.utils.checks.ValidState;
 
import java.awt.BorderLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
47,12 → 48,16
private final ValidChangeSupport validSupp;
 
public JavaPrefPreferencePanel(final String title, final Preferences prefs) {
super(new BorderLayout());
this.title = title;
this.prefs = prefs;
this.layouter = new AutoLayouter(this);
this.views = new HashSet<PrefView<?>>();
this.modified = false;
this.validSupp = new ValidChangeSupport(this);
// anchor content at the top
final JPanel content = new JPanel();
this.add(content, BorderLayout.PAGE_START);
this.layouter = new AutoLayouter(content);
}
 
public final void setPrefs(Preferences prefs) {
/trunk/OpenConcerto/src/org/openconcerto/ui/preferences/AbstractProps.java
128,8 → 128,11
 
public void load() {
final File file = new File(getPropsFileName());
if (!file.exists())
System.out.println("Loading properties from " + file.getAbsolutePath());
if (!file.exists()) {
System.out.println("Warning: " + file.getAbsolutePath() + " does not exist");
return;
}
BufferedInputStream bufferedInputStream = null;
try {
final FileInputStream fileInputStream = new FileInputStream(file);
/trunk/OpenConcerto/src/org/openconcerto/ui/FormLayouter.java
151,6 → 151,7
 
final int realWidth = w * CELL_WIDTH - 1;
JPanel p = new JPanel();
p.setOpaque(false);
p.setLayout(new GridLayout());
p.setBorder(BorderFactory.createTitledBorder(desc));
p.add(comp);
/trunk/OpenConcerto/src/org/openconcerto/sql/Configuration.java
119,7 → 119,7
return newFile;
}
 
protected final File getConfDir(DBStructureItem<?> db) {
public final File getConfDir(DBStructureItem<?> db) {
return DBItemFileCache.getDescendant(new File(getConfDir(), "dataDepedent"), DBFileCache.getJDBCAncestorNames(db, true));
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/request/SQLRowView.java
19,7 → 19,9
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.ui.SwingThreadUtils;
 
import java.awt.Component;
49,7 → 51,7
 
// la table cible de cette requete
private final SQLTable table;
private final SQLTableListener tableListener;
private final SQLTableModifiedListener tableListener;
// l'id affiché ou SQLRow.NONEXISTANT_ID si les valeurs affichées ne sont pas liées à une ligne
// dans la base
private int selectedID;
74,7 → 76,15
this.filling = false;
this.updating = false;
this.selectedID = SQLRow.NONEXISTANT_ID;
this.tableListener = new SQLTableListener() {
this.tableListener = new SQLTableModifiedListener() {
@Override
public void tableModified(SQLTableEvent evt) {
if (evt.getMode() == Mode.ROW_UPDATED)
this.rowModified(evt.getTable(), evt.getId());
else if (evt.getMode() == Mode.ROW_DELETED)
this.rowDeleted(evt.getTable(), evt.getId());
// else don't care
}
 
public void rowModified(SQLTable t, int id) {
if (!isUpdating() && existsInDB()) {
88,10 → 98,6
}
}
 
public void rowAdded(SQLTable t, int id) {
// don't care
}
 
public void rowDeleted(SQLTable t, int id) {
if (!isUpdating() && existsInDB()) {
if (!t.equals(getTable())) {
115,12 → 121,12
 
public final void activate(boolean b) {
if (b) {
this.table.addTableListener(this.tableListener);
this.table.addTableModifiedListener(this.tableListener);
if (this.existsInDB())
// to catch up to the changes which happened while we weren't listening
this.select(this.getSelectedID());
} else
this.table.removeTableListener(this.tableListener);
this.table.removeTableModifiedListener(this.tableListener);
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/request/ComboSQLRequest.java
143,6 → 143,10
}
 
public final List<IComboSelectionItem> getComboItems() {
return this.getComboItems(true);
}
 
public final List<IComboSelectionItem> getComboItems(final boolean readCache) {
if (this.comboFields.isEmpty())
throw new IllegalStateException("La liste des items listitems est vide!! Ils faut utiliser addComboItem...");
 
151,14 → 155,17
final SQLRowValuesListFetcher comboSelect = this.getFetcher(null).freeze();
 
final CacheKey cacheKey = new CacheKey(comboSelect, this.fieldSeparator, this.undefLabel, this.customizeItem);
if (readCache) {
final CacheResult<List<IComboSelectionItem>> l = cache.check(cacheKey);
if (l.getState() == CacheResult.State.INTERRUPTED)
throw new RTInterruptedException("interrupted while waiting for the cache");
else if (l.getState() == CacheResult.State.VALID)
return l.getRes();
}
 
try {
final List<IComboSelectionItem> result = new ArrayList<IComboSelectionItem>();
// SQLRowValuesListFetcher don't cache
for (final SQLRowValues vals : comboSelect.fetch()) {
if (Thread.currentThread().isInterrupted())
throw new RTInterruptedException("interrupted in fill");
/trunk/OpenConcerto/src/org/openconcerto/sql/request/SQLCacheWatcher.java
16,7 → 16,7
import org.openconcerto.sql.model.SQLData;
import org.openconcerto.sql.model.SQLDataListener;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.utils.cache.CacheWatcher;
 
/**
27,7 → 27,7
*/
public class SQLCacheWatcher<K> extends CacheWatcher<K, SQLData> {
 
private final SQLTableListener listener;
private final SQLTableModifiedListener listener;
 
SQLCacheWatcher(final SQLCache<K, ?> c, final SQLData t) {
super(c, t);
36,7 → 36,7
clearCache();
}
});
this.getTable().addPremierTableListener(this.listener);
this.getTable().addPremierTableModifiedListener(this.listener);
}
 
private final SQLTable getTable() {
45,7 → 45,7
 
@Override
protected void dying() {
this.getTable().removeTableListener(this.listener);
this.getTable().removeTableModifiedListener(this.listener);
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/BaseSQLRequest.java
16,7 → 16,7
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLFieldsSet;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableModifiedListener;
 
import java.util.Collection;
import java.util.Set;
38,15 → 38,15
*/
protected abstract Collection<SQLField> getAllFields();
 
public final void addTableListener(SQLTableListener l) {
public final void addTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.addTableListener(l);
t.addTableModifiedListener(l);
}
}
 
public final void removeTableListener(SQLTableListener l) {
public final void removeTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.removeTableListener(l);
t.removeTableModifiedListener(l);
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/DefaultElementSQLObject.java
35,6 → 35,7
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
 
/**
* A default ElementSQLObject that displays a button to create, and when created a button to delete
61,6 → 62,7
super(parent, comp);
 
this.addValidListener(new ValidListener() {
@Override
public void validChange(ValidObject src, ValidState newValue) {
compChanged();
}
69,24 → 71,29
 
public void showSeparator(boolean visible) {
this.isSeparatorVisible = visible;
if (separator != null)
if (this.separator != null)
this.separator.setVisible(visible);
}
 
public void setDecorated(boolean decorated) {
this.isDecorated = decorated;
if (expandBtn != null)
if (this.expandBtn != null)
this.expandBtn.setVisible(decorated);
if (supprBtn != null)
if (this.supprBtn != null)
this.supprBtn.setVisible(decorated);
if (createBtn != null)
if (this.createBtn != null)
this.createBtn.setVisible(decorated);
}
 
@Override
protected void uiInit() {
final boolean isPlastic = UIManager.getLookAndFeel().getClass().getName().startsWith("com.jgoodies.plaf.plastic");
 
this.expandBtn = new JButton("+/-");
this.expandBtn.setEnabled(false);
this.expandBtn.setOpaque(false);
this.expandBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
toggleExpand();
}
95,8 → 102,10
this.supprBtn = new JButton(new ImageIcon(this.getClass().getResource("delete.png")));
this.supprBtn.setToolTipText("Supprimer");
this.supprBtn.setOpaque(false);
if (isPlastic)
this.supprBtn.setBorder(null);
this.supprBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0 || this.confirm())
setCreated(false);
107,7 → 116,10
}
});
this.createBtn = new JButton("Créer " + this.getSQLChild().getElement().getSingularName());
// false leaves only a line for the button under Plastic3DLookAndFeel
this.createBtn.setOpaque(isPlastic);
this.createBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setCreated(true);
}
116,6 → 128,7
this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
}
 
@Override
protected final void setCreatePanel() {
if (this.editP != null)
this.editP.setVisible(false);
138,6 → 151,7
return this.createP;
}
 
@Override
protected final void setEditPanel() {
this.supprBtn.setVisible(!this.required && this.isDecorated);
if (this.createP != null)
195,6 → 209,7
return this.editP;
}
 
@Override
protected void compChanged() {
this.expandBtn.setEnabled(this.getCurrentID() != SQLRow.NONEXISTANT_ID && this.getValidState().isValid());
}
219,6 → 234,7
this.expand(!this.isExpanded());
}
 
@Override
public void setEditable(boolean enabled) {
super.setEditable(enabled);
this.createBtn.setEnabled(enabled);
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElement.java
118,6 → 118,7
private SQLCache<SQLRowAccessor, Object> modelCache;
 
private final Map<String, JComponent> additionalFields;
private final List<SQLTableModelColumn> additionalListCols;
 
public SQLElement(String singular, String plural, SQLTable primaryTable) {
super();
136,6 → 137,7
this.modelCache = null;
 
this.additionalFields = new HashMap<String, JComponent>();
this.additionalListCols = new ArrayList<SQLTableModelColumn>();
}
 
/**
331,6 → 333,15
}
 
/**
* Fields that cannot be empty.
*
* @return fields that cannot be empty.
*/
public Set<String> getRequiredFields() {
return Collections.emptySet();
}
 
/**
* Fields that can only be set on insertion.
*
* @return fields that cannot be modified.
759,7 → 770,8
// shared must be RESTRICT, parent at least CASCADE (to avoid child without a parent),
// normal is free
if (action.compareTo(ReferenceAction.RESTRICT) < 0 && !this.getNormalForeignFields().contains(ff))
throw new IllegalArgumentException(ff + " is not normal: " + this.getNormalForeignFields());
// getField() checks if the field exists
throw new IllegalArgumentException(getTable().getField(ff).getSQLName() + " is not a normal foreign field : " + this.getNormalForeignFields());
this.getActions().put(ff, action);
}
 
877,6 → 889,7
 
private final SQLTableModelSourceOnline createAndInitTableSource() {
final SQLTableModelSourceOnline res = this.createTableSource();
res.getColumns().addAll(this.additionalListCols);
return initTableSource(res);
}
 
915,6 → 928,15
 
abstract protected List<String> getListFields();
 
public final void addListFields(final List<String> fields) {
for (final String f : fields)
this.addListColumn(new SQLTableModelColumnPath(getTable().getField(f)));
}
 
public final void addListColumn(SQLTableModelColumn col) {
this.additionalListCols.add(col);
}
 
public final Collection<IListeAction> getRowActions() {
return this.rowActions;
}
1365,7 → 1387,8
 
public final int hashCode() {
// ne pas mettre getParent car des fois null
return this.getTable().hashCode() + this.getSharedForeignFields().hashCode() + this.getPrivateForeignFields().hashCode();
return this.getTable().hashCode(); // + this.getSharedForeignFields().hashCode() +
// this.getPrivateForeignFields().hashCode();
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/element/BaseSQLComponent.java
170,7 → 170,7
}
}
 
private Tuple2<JComponent, SQLType> getComp(String field) {
protected Tuple2<JComponent, SQLType> getComp(String field) {
if (getElement().getPrivateElement(field) != null)
// we create a MutableRowItemView and need SpecParser
throw new IllegalArgumentException("Private fields not supported");
192,6 → 192,7
// regular
comp = new SQLTextCombo();
}
comp.setOpaque(false);
return new Tuple2<JComponent, SQLType>(comp, type);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/element/UISQLComponent.java
153,6 → 153,8
this.add(this.tabbedPane);
}
this.currentPanel = new JPanel();
// from Guillaume : tabs shouldn't be opaque in Windows L&F
this.currentPanel.setOpaque(false);
this.tabbedPane.addTab(tabTitle, this.currentPanel);
this.setLayouter(w, d);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/LayoutHints.java
New file
0,0 → 1,93
/*
* 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.element;
 
public class LayoutHints {
 
private boolean maximizeWidth;
private boolean maximizeHeight;
private boolean showLabel;
private boolean separatedLabel;
private boolean fill;
public static final LayoutHints DEFAULT_FIELD_HINTS = new LayoutHints(false, false, true, false);
public static final LayoutHints DEFAULT_LARGE_FIELD_HINTS = new LayoutHints(true, false, true, false);
public static final LayoutHints DEFAULT_LIST_HINTS = new LayoutHints(true, true, false, false, true);
public static final LayoutHints DEFAULT_GROUP_HINTS = new LayoutHints(false, false, false, false);
public static final LayoutHints DEFAULT_LARGE_GROUP_HINTS = new LayoutHints(true, false, false, false);
 
public LayoutHints(boolean maximizeWidth, boolean maximizeHeight, boolean showLabel, boolean separatedLabel) {
this.maximizeWidth = maximizeWidth;
this.maximizeHeight = maximizeHeight;
this.showLabel = showLabel;
this.separatedLabel = separatedLabel;
this.fill = false;
}
 
public LayoutHints(boolean maximizeWidth, boolean maximizeHeight, boolean showLabel, boolean separatedLabel, boolean fill) {
this.maximizeWidth = maximizeWidth;
this.maximizeHeight = maximizeHeight;
this.showLabel = showLabel;
this.separatedLabel = separatedLabel;
this.fill = fill;
}
 
public boolean maximizeWidth() {
return maximizeWidth;
}
 
public boolean maximizeHeight() {
return maximizeHeight;
}
 
public boolean showLabel() {
return showLabel;
}
 
public boolean separatedLabel() {
return separatedLabel;
}
 
public boolean fill() {
return fill;
}
 
@Override
public String toString() {
String r = "";
if (maximizeHeight && maximizeWidth) {
r += "MaxW&H";
} else {
if (maximizeHeight) {
r += "MaxH";
}
if (maximizeWidth) {
r += "MaxW";
}
}
if (showLabel && separatedLabel) {
r += " SeparatedLabel";
} else {
if (showLabel) {
r += " StdLabel";
} else {
r += " NoLabel";
}
 
}
if (fill) {
r += " Fill";
}
return r;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/Group.java
New file
0,0 → 1,156
/*
* 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.element;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
 
import org.openconcerto.utils.Tuple3;
 
public class Group {
 
private String id;
private int order = 0;
private List<Tuple3<Group, LayoutHints, Integer>> list = new ArrayList<Tuple3<Group, LayoutHints, Integer>>();
 
public Group(String id) {
this.id = id.trim();
 
}
 
public String getId() {
return id;
}
 
public void add(Group group) {
order += 100;
list.add(new Tuple3<Group, LayoutHints, Integer>(group, LayoutHints.DEFAULT_GROUP_HINTS, order));
}
 
public void add(Group group, LayoutHints hints) {
order += 100;
list.add(new Tuple3<Group, LayoutHints, Integer>(group, hints, order));
}
 
public void insert(Group group, LayoutHints hints, int order) {
list.add(new Tuple3<Group, LayoutHints, Integer>(group, hints, order));
}
 
public void add(String string) {
this.add(new Group(string), LayoutHints.DEFAULT_FIELD_HINTS);
 
}
 
public void add(String string, LayoutHints hints) {
this.add(new Group(string), hints);
 
}
 
public void dumpOneColumn() {
dumpOneColumn(LayoutHints.DEFAULT_GROUP_HINTS, 0, 1);
}
 
public void dumpOneColumn(LayoutHints localHint, int localOrder, int level) {
for (int i = 0; i < level - 1; i++) {
System.out.print(" ");
}
if (list.size() == 0)
System.out.print("+-- ");
else
System.out.print("+-+ ");
System.out.println(localOrder + " " + this.id + " [" + localHint + "]");
sortSubGroup();
for (Tuple3<Group, LayoutHints, Integer> tuple : list) {
 
((Group) tuple.get0()).dumpOneColumn(tuple.get1(), tuple.get2(), level + 1);
}
// System.out.println("== end" + this.id);
}
 
private void sortSubGroup() {
if (list.size() > 1) {
Collections.sort(list, new Comparator<Tuple3<Group, LayoutHints, Integer>>() {
 
@Override
public int compare(Tuple3<Group, LayoutHints, Integer> o1, Tuple3<Group, LayoutHints, Integer> o2) {
return o1.get2().compareTo(o2.get2());
}
});
}
}
 
public void dumpTwoColumn() {
dumpTwoColumn(0, LayoutHints.DEFAULT_GROUP_HINTS, 0, 1);
}
 
public int dumpTwoColumn(int x, LayoutHints localHint, int localOrder, int level) {
 
if (isEmpty()) {
System.out.print(" (" + x + ")");
 
System.out.print(localOrder + " " + this.id + "[" + localHint + "]");
 
if ((x % 2) == 1) {
System.out.println();
}
}
sortSubGroup();
for (Tuple3<Group, LayoutHints, Integer> tuple : list) {
final Group subGroup = tuple.get0();
final Integer subGroupOrder = (Integer) tuple.get2();
 
x = subGroup.dumpTwoColumn(x, tuple.get1(), subGroupOrder, level + 1);
 
}
if (isEmpty()) {
x++;
}
if (list.size() != 0 && localHint.maximizeWidth()) {
x = 0;
System.out.println();
}
return x;
}
 
public int getSize() {
return this.list.size();
}
 
public boolean isEmpty() {
return this.list.isEmpty();
}
 
public void sort() {
sortSubGroup();
for (Tuple3<Group, LayoutHints, Integer> tuple : list) {
final Group subGroup = tuple.get0();
subGroup.sort();
}
}
 
public Group getGroup(int i) {
return this.list.get(i).get0();
}
 
public LayoutHints getLayoutHints(int i) {
return this.list.get(i).get1();
}
 
public Integer getOrder(int i) {
return this.list.get(i).get2();
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/ElementMapper.java
New file
0,0 → 1,83
/*
* 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.element;
 
import org.openconcerto.utils.StringUtils;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class ElementMapper {
private final Map<String, Object> idToObject = new HashMap<String, Object>();
private final Map<Object, List<String>> objectsToIds = new HashMap<Object, List<String>>();
 
private static ElementMapper instance = new ElementMapper();
 
public ElementMapper() {
 
}
 
public static ElementMapper getInstance() {
return instance;
}
 
public void map(String id, Object obj) {
idToObject.put(id, obj);
List<String> l = objectsToIds.get(obj);
if (l == null) {
l = new ArrayList<String>(3);
l.add(id);
} else if (!l.contains(obj)) {
l.add(id);
}
}
 
public Object get(String id) {
return idToObject.get(id);
}
 
public String getString(String id) {
final Object object = idToObject.get(id);
if (object instanceof String) {
return (String) object;
}
return null;
}
 
public Group getGroup(String id) {
final Object object = idToObject.get(id);
if (object instanceof Group) {
return (Group) object;
}
return null;
}
 
public List<String> getIds(Object o) {
return objectsToIds.get(o);
}
 
public void dump() {
System.out.println(this.getClass().getName());
List<String> ids = new ArrayList<String>();
ids.addAll(this.idToObject.keySet());
Collections.sort(ids);
for (String id : ids) {
System.out.println(StringUtils.leftAlign(id, 40) + " : " + idToObject.get(id));
}
System.out.println(ids.size() + " identifiers found");
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementDirectory.java
104,7 → 104,15
for (final DirectoryListener dl : this.listeners) {
dl.elementAdded(elem);
}
String canonicalName = elem.getClass().getCanonicalName();
if (canonicalName.contains("erp.core") && canonicalName.contains(".element")) {
int i = canonicalName.indexOf("erp.core") + 9;
int j = canonicalName.indexOf(".element");
canonicalName = canonicalName.substring(i, j);
}
ElementMapper.getInstance().map(canonicalName + ".element", elem);
ElementMapper.getInstance().map(canonicalName + ".list.table", elem.getTable().getName());
}
 
public synchronized final boolean contains(SQLTable t) {
return this.elements.containsKey(t);
/trunk/OpenConcerto/src/org/openconcerto/sql/element/GroupSQLComponent.java
New file
0,0 → 1,188
/*
* 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.element;
 
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
 
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLType;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.JLabelBold;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.Tuple3;
 
public class GroupSQLComponent extends BaseSQLComponent {
 
private Group group;
private int columns = 2;
private boolean forceViewOnly = true;
 
public GroupSQLComponent(SQLElement element, Group group) {
super(element);
this.group = group;
 
}
 
@Override
protected void addViews() {
group.dumpOneColumn();
this.setLayout(new GridBagLayout());
group.sort();
GridBagConstraints c = new DefaultGridBagConstraints();
layout(group, 0, LayoutHints.DEFAULT_GROUP_HINTS, 0, 0, c);
}
 
public int layout(Group currentGroup, Integer order, LayoutHints size, int x, int level, GridBagConstraints c) {
if (currentGroup.isEmpty()) {
System.out.print(" (" + x + ")");
String id = currentGroup.getId();
System.out.print(order + " " + id + "[" + size + "]");
c.gridwidth = 1;
if (size.showLabel()) {
c.weightx = 0;
// Label
if (size.separatedLabel()) {
c.gridx = 0;
c.gridwidth = 4;
c.fill = GridBagConstraints.NONE;
} else {
c.fill = GridBagConstraints.HORIZONTAL;
}
this.add(getLabel(id), c);
if (size.separatedLabel()) {
c.gridy++;
} else {
c.gridx++;
}
}
// Editor
c.weightx = 1;
if (size.maximizeWidth() && size.maximizeHeight()) {
c.fill = GridBagConstraints.BOTH;
} else if (size.maximizeWidth()) {
c.fill = GridBagConstraints.HORIZONTAL;
} else if (size.maximizeHeight()) {
c.fill = GridBagConstraints.VERTICAL;
} else {
c.fill = GridBagConstraints.NONE;
}
if (size.fill()) {
c.weighty = 1;
c.gridwidth = columns * 2;
}
 
this.add(getEditor(id), c);
c.weighty = 0;
c.gridx++;
if ((x % columns) != 0) {
c.gridx = 0;
c.gridy++;
}
}
 
final int stop = currentGroup.getSize();
for (int i = 0; i < stop; i++) {
final Group subGroup = currentGroup.getGroup(i);
final Integer subGroupOrder = currentGroup.getOrder(i);
final LayoutHints subGroupSize = currentGroup.getLayoutHints(i);
x = layout(subGroup, subGroupOrder, subGroupSize, x, level + 1, c);
 
}
 
if (currentGroup.isEmpty()) {
x++;
} else {
if (size.maximizeWidth()) {
c.gridx = 0;
c.gridy++;
}
}
 
return x;
}
 
JComponent getEditor(String id) {
if (id.startsWith("(") && id.endsWith(")*")) {
 
try {
String table = id.substring(1, id.length() - 2).trim();
String idEditor = ElementMapper.getInstance().getIds(table).get(0) + ".editor";
System.out.println("Editor: " + idEditor);
Class cl = (Class) ElementMapper.getInstance().get(idEditor);
return (JComponent) cl.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
 
}
 
SQLField field = this.getTable().getFieldRaw(id);
if (field == null) {
final JLabel jLabel = new JLabelBold("No field " + id);
jLabel.setForeground(Color.RED.darker());
String t = "<html>";
 
final Set<SQLField> fields = this.getTable().getFields();
 
for (SQLField sqlField : fields) {
t += sqlField.getFullName() + "<br>";
}
t += "</html>";
jLabel.setToolTipText(t);
return jLabel;
}
// if (/* this.getMode().equals(Mode.VIEW) || */forceViewOnly) {
// final JLabel jLabel = new JLabel();
// jLabel.setForeground(Color.gray);
// return jLabel;
// }
 
Tuple2<JComponent, SQLType> r = getComp(id);
final JComponent editorComp = r.get0();
if (editorComp != null) {
this.addView(editorComp, id);
return editorComp;
}
 
return new JButton(id);
}
 
JLabel getLabel(String id) {
final String fieldLabel = super.getLabelFor(id);
JLabel jLabel;
if (fieldLabel == null) {
jLabel = new JLabel(id);
jLabel.setForeground(Color.RED.darker());
 
} else {
jLabel = new JLabel(fieldLabel);
}
jLabel.setHorizontalAlignment(SwingConstants.RIGHT);
return jLabel;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/RowBacked.java
21,7 → 21,6
import org.openconcerto.sql.model.SQLTable;
 
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
 
import org.apache.commons.collections.Transformer;
35,6 → 34,7
 
protected static abstract class PropExtractor implements Transformer {
 
@Override
public final Object transform(Object input) {
return this.extract((SQLRowAccessor) input);
}
51,9 → 51,7
this.propExtractors = new HashMap<String, PropExtractor>();
this.values = new HashMap<String, Object>();
 
final Iterator iter = PolymorphFK.findPolymorphFK(this.getTable()).iterator();
while (iter.hasNext()) {
final PolymorphFK f = (PolymorphFK) iter.next();
for (final PolymorphFK f : PolymorphFK.findPolymorphFK(this.getTable())) {
this.addPolymorphFK(f);
}
}
110,6 → 108,7
 
protected final void addPolymorphFK(final PolymorphFK fk) {
this.putExtractor(fk.getName(), new PropExtractor() {
@Override
public Object extract(SQLRowAccessor r) {
final String tableName = r.getString(fk.getTableField().getName());
final SQLTable foreignT = tableName == null ? null : r.getTable().getBase().getTable(tableName);
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/SQLBrowserColumn.java
22,6 → 22,7
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.sqlobject.ElementComboBox;
import org.openconcerto.ui.KeyLabel;
import org.openconcerto.ui.PopupMouseListener;
import org.openconcerto.ui.list.selection.ListSelectionState;
import org.openconcerto.utils.JImage;
import org.openconcerto.utils.cc.IPredicate;
53,6 → 54,7
import java.util.List;
import java.util.Set;
 
import javax.swing.AbstractAction;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListSelectionModel;
import javax.swing.Icon;
61,6 → 63,7
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
328,10 → 331,20
this.title.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// On trie
if (e.getButton() == MouseEvent.BUTTON1) {
getModel().sort();
setTitleIcon();
}
}
});
final JPopupMenu menu = new JPopupMenu();
menu.add(new AbstractAction("Recharger") {
@Override
public void actionPerformed(ActionEvent e) {
getModel().reload(true);
}
});
this.title.addMouseListener(new PopupMouseListener(menu));
// this.normalPanel.setBackground(new Color(239, 235, 231));
 
headerPanel.add(this.title, c2);
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/RowsSQLListModel.java
13,17 → 13,17
/*
* Créé le 21 mai 2005
*
*/
package org.openconcerto.sql.navigator;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.IResultSetHandler;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.Where;
 
import java.util.List;
31,7 → 31,7
 
import org.apache.commons.dbutils.ResultSetHandler;
 
public class RowsSQLListModel extends SQLListModel<SQLRow> implements SQLTableListener {
public class RowsSQLListModel extends SQLListModel<SQLRow> implements SQLTableModifiedListener {
 
private final SQLElement element;
private final ResultSetHandler handler;
40,11 → 40,11
super();
this.element = element;
this.handler = new SQLRowListRSH(this.getElement().getTable(), true);
this.getElement().getTable().addTableListener(this);
this.getElement().getTable().addTableModifiedListener(this);
}
 
@SuppressWarnings("unchecked")
protected void reload() {
@Override
protected void reload(final boolean noCache) {
final Set<Number> ids = getIds();
final String key = this.getElement().getParentForeignField();
 
60,9 → 60,21
 
// cannot just use a SwingWorker, cause some methods (like SQLBrowser#selectPath())
// expect reload() to by synchronous.
this.setAll((List<SQLRow>) source.execute(sel.asString(), this.handler));
@SuppressWarnings("unchecked")
final List<SQLRow> rows = (List<SQLRow>) source.execute(sel.asString(), new IResultSetHandler(this.handler) {
@Override
public boolean readCache() {
return !noCache;
}
 
@Override
public boolean writeCache() {
return true;
}
});
this.setAll(rows);
}
 
/**
* Search the row with the passed <code>id</code> and return its index.
*
86,26 → 98,18
return this.element;
}
 
public void rowAdded(SQLTable table, int id) {
this.reload();
}
 
public void rowModified(SQLTable table, int id) {
@Override
public void tableModified(SQLTableEvent evt) {
// TODO test if that concern us
this.reload();
}
 
public void rowDeleted(SQLTable table, int id) {
// TODO test if that concern us
this.reload();
}
 
public String toString() {
return this.getClass().getName() + " on " + this.getElement();
}
 
protected void die() {
this.getElement().getTable().removeTableListener(this);
this.getElement().getTable().removeTableModifiedListener(this);
}
 
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/ElementsSQLListModel.java
44,7 → 44,8
this.elements = new ArrayList<SQLElement>(elements);
}
 
protected void reload() {
@Override
protected void reload(boolean noCache) {
this.counts.clear();
final List<SQLElement> res = new ArrayList<SQLElement>(this.elements.size());
for (final SQLElement elem : this.elements) {
/trunk/OpenConcerto/src/org/openconcerto/sql/navigator/SQLListModel.java
74,8 → 74,12
// more efficient than reload())
}
 
protected abstract void reload();
protected final void reload() {
this.reload(false);
}
 
protected abstract void reload(final boolean noCache);
 
// *** items
 
public final int getSize() {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesCluster.java
836,6 → 836,7
public Link(final SQLRowValues src, final SQLField f, final SQLRowValues dest) {
if (src == null)
throw new NullPointerException("src is null");
assert (f == null && dest == null) || (dest != null && f.getTable() == src.getTable());
this.src = src;
this.f = f;
this.dest = dest;
857,8 → 858,8
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.src.hashCode();
result = prime * result + ((this.dest == null) ? 0 : this.dest.hashCode());
result = prime * result + System.identityHashCode(this.src);
result = prime * result + System.identityHashCode(this.dest);
result = prime * result + ((this.f == null) ? 0 : this.f.hashCode());
return result;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRow.java
400,10 → 400,12
}
}
 
@Override
public SQLRow getForeign(String fieldName) {
return this.getForeignRow(fieldName);
}
 
@Override
public boolean isForeignEmpty(String fieldName) {
final SQLRow foreignRow = this.getForeignRow(fieldName, SQLRowMode.NO_CHECK);
return foreignRow == null || foreignRow.isUndefined();
413,7 → 415,7
* Retourne la ligne sur laquelle pointe le champ passé. Elle peut être archivé ou indéfinie.
*
* @param field le nom de la clef externe.
* @return la ligne sur laquelle pointe le champ passé, jamais <code>null</code>.
* @return la ligne sur laquelle pointe le champ passé.
* @throws IllegalArgumentException si <code>field</code> n'est pas une clef étrangère de la
* table de cette ligne.
* @throws IllegalStateException si <code>field</code> contient l'ID d'une ligne inexistante.
949,7 → 951,8
return path.split(",");
}
 
public SQLTableListener createTableListener(SQLDataListener l) {
@Override
public SQLTableModifiedListener createTableListener(SQLDataListener l) {
return new SQLTableListenerData<SQLRow>(this, l);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesMap.java
New file
0,0 → 1,72
/*
* 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 org.openconcerto.utils.cc.IPredicate;
import org.openconcerto.utils.checks.EmptyObjFromVO;
 
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
 
/**
* Store inserted rows. This class allows to avoid having huge, slow {@link SQLRowValuesCluster} by
* replacing a foreign link with its ID.
*
* @author Sylvain CUAZ
* @param <T> type of source object.
*/
public abstract class SQLRowValuesMap<T> {
 
private final SQLTable t;
private final Map<SQLRowValues, SQLRow> map;
private final IPredicate<? super T> emptyPredicate;
 
public SQLRowValuesMap(final SQLTable t) {
this(t, EmptyObjFromVO.getDefaultPredicate());
}
 
public SQLRowValuesMap(final SQLTable t, final IPredicate<? super T> emptyPredicate) {
this.t = t;
this.map = new HashMap<SQLRowValues, SQLRow>();
this.emptyPredicate = emptyPredicate;
}
 
protected abstract void fill(final SQLRowValues vals, T obj);
 
/**
* Return a non SQLRowValues value, thus avoiding linking two graphs together. If
* <code>obj</code> is empty, returns {@link SQLRowValues#SQL_EMPTY_LINK}, else calls
* {@link #fill(SQLRowValues, Object)} and if these values haven't already been inserted, insert
* them, finally return the ID.
*
* @param obj the source object.
* @return the ID or SQL_EMPTY_LINK.
* @throws SQLException if an error occurs while inserting.
*/
public final Object getValue(final T obj) throws SQLException {
if (this.emptyPredicate.evaluateChecked(obj)) {
return SQLRowValues.SQL_EMPTY_LINK;
} else {
final SQLRowValues key = new SQLRowValues(this.t);
this.fill(key, obj);
SQLRow res = this.map.get(key);
if (res == null) {
res = key.insert();
this.map.put(key, res);
}
return res.getIDNumber();
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValues.java
27,6 → 27,7
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CopyUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
95,13 → 96,12
}
 
public static final Object SQL_DEFAULT = new Object();
// for now the default is put in the default value of the db
/**
* Empty foreign field value.
*
* @see #putEmptyLink(String)
*/
public static final Object SQL_EMPTY_LINK = SQL_DEFAULT;
public static final Object SQL_EMPTY_LINK = new Object();
 
private static boolean checkValidity = true;
 
167,26 → 167,8
*/
public SQLRowValues(SQLRowValues vals, ForeignCopyMode copyForeigns) {
this(vals.getTable());
final Map<String, Object> toAdd;
if (copyForeigns == ForeignCopyMode.COPY_ROW)
toAdd = vals.values;
else {
final Set<Entry<String, Object>> entrySet = vals.values.entrySet();
toAdd = new LinkedHashMap<String, Object>(entrySet.size());
for (final Map.Entry<String, Object> e : entrySet) {
if (!(e.getValue() instanceof SQLRowValues))
toAdd.put(e.getKey(), e.getValue());
else if (copyForeigns != ForeignCopyMode.NO_COPY) {
final SQLRowValues foreign = (SQLRowValues) e.getValue();
if (foreign.hasID())
toAdd.put(e.getKey(), foreign.getIDNumber());
else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW)
toAdd.put(e.getKey(), foreign);
}
}
}
// setAll() takes care of foreigns and referents
this.setAll(toAdd);
this.setAll(vals.getAllValues(copyForeigns));
}
 
@Override
272,7 → 254,9
* @return the foreign row, eg FAMILLE[1].
*/
public final SQLRowValues grow(String fk) {
if (!(this.getObject(fk) instanceof SQLRowValues)) {
final Object val = this.getContainedObject(fk);
// if fk is in our map with a null value, nothing to grow
if (val != null && !(val instanceof SQLRowValues)) {
final SQLRowValues vals = new SQLRowValues(this.getTable());
vals.putRowValues(fk).setAllToNull();
this.grow(vals, true);
402,16 → 386,53
 
@Override
public Map<String, Object> getAbsolutelyAll() {
return Collections.unmodifiableMap(this.values);
return getAllValues(ForeignCopyMode.COPY_ROW);
}
 
protected final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns) {
final Map<String, Object> toAdd;
if (copyForeigns == ForeignCopyMode.COPY_ROW || this.foreigns.size() == 0) {
toAdd = this.values;
} else {
final Set<Entry<String, Object>> entrySet = this.values.entrySet();
toAdd = new LinkedHashMap<String, Object>(entrySet.size());
for (final Map.Entry<String, Object> e : entrySet) {
if (!(e.getValue() instanceof SQLRowValues)) {
toAdd.put(e.getKey(), e.getValue());
} else if (copyForeigns != ForeignCopyMode.NO_COPY) {
final SQLRowValues foreign = (SQLRowValues) e.getValue();
if (foreign.hasID())
toAdd.put(e.getKey(), foreign.getIDNumber());
else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW)
toAdd.put(e.getKey(), foreign);
}
}
}
return Collections.unmodifiableMap(toAdd);
}
 
/**
* Return the foreign row, if any, for the passed field.
*
* @param fieldName name of the foreign field.
* @return if <code>null</code> or a SQLRowValues one was put at <code>fieldName</code>, return
* it ; else assume that an ID was put at <code>fieldName</code> and return a new SQLRow
* with it.
* @throws IllegalArgumentException if fieldName is not a foreign field or if it isn't contained
* in this instance.
* @throws ClassCastException if the value is neither a SQLRowValues, nor <code>null</code> nor
* a Number.
*/
@Override
public final SQLRowAccessor getForeign(String fieldName) {
public final SQLRowAccessor getForeign(String fieldName) throws IllegalArgumentException, ClassCastException {
// keep getForeignTable at the 1st line since it does the check
final SQLTable foreignTable = this.getForeignTable(fieldName);
if (this.getObject(fieldName) instanceof SQLRowAccessor) {
return (SQLRowAccessor) this.getObject(fieldName);
} else if (this.isEmptyLink(fieldName)) {
final Object val = this.getContainedObject(fieldName);
if (val instanceof SQLRowAccessor) {
return (SQLRowAccessor) val;
} else if (val == null) {
// since we used getContainedObject(), it means that a null was put in our map, not that
// fieldName wasn't there
return null;
} else if (this.isDefault(fieldName)) {
throw new IllegalStateException(fieldName + " is DEFAULT");
420,6 → 441,12
}
}
 
private Object getContainedObject(String fieldName) throws IllegalArgumentException {
if (!this.values.containsKey(fieldName))
throw new IllegalArgumentException("Field not present in this : " + this.getFields());
return this.values.get(fieldName);
}
 
/**
* Returns the foreign table of <i>fieldName</i>.
*
439,18 → 466,12
public boolean isForeignEmpty(String fieldName) {
// keep getForeignTable at the 1st line since it does the check
final SQLTable foreignTable = this.getForeignTable(fieldName);
if (this.getObject(fieldName) instanceof Number) {
return this.getInt(fieldName) == foreignTable.getUndefinedID();
} else if (this.getObject(fieldName) instanceof SQLRowValues) {
return ((SQLRowValues) this.getObject(fieldName)).getID() == foreignTable.getUndefinedID();
} else
return this.getObject(fieldName) == null || this.isEmptyLink(fieldName);
final Object val = this.getContainedObject(fieldName);
final Number id = val instanceof SQLRowValues ? ((SQLRowValues) val).getIDNumber() : (Number) val;
final Number undefID = foreignTable.getUndefinedIDNumber();
return NumberUtils.areNumericallyEqual(id, undefID);
}
 
public boolean isEmptyLink(String fieldName) {
return SQL_EMPTY_LINK.equals(this.getObject(fieldName));
}
 
public boolean isDefault(String fieldName) {
return SQL_DEFAULT.equals(this.getObject(fieldName));
}
478,7 → 499,7
public final SQLRow asRow() {
if (!this.hasID())
throw new IllegalStateException(this + " has no ID");
return new SQLRow(this.getTable(), this.values);
return new SQLRow(this.getTable(), this.getAllValues(ForeignCopyMode.COPY_ID_OR_RM));
}
 
@Override
588,6 → 609,9
}
 
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);
this.updateLinks(fieldName, this.values.put(fieldName, value), value);
635,8 → 659,6
* @return this.
*/
public SQLRowValues putEmptyLink(String fieldName) {
// keep getForeignTable at the 1st line since it does the check
this.getForeignTable(fieldName);
return this.put(fieldName, SQL_EMPTY_LINK);
}
 
1009,7 → 1031,7
// verifie l'intégrité (a rowValues is obviously correct, as is EMPTY,
// DEFAULT is the responsability of the DB)
final Object fieldVal = this.getObject(fieldName);
if (fk.contains(field) && fieldVal != SQL_DEFAULT && fieldVal != SQL_EMPTY_LINK && !(fieldVal instanceof SQLRowValues)) {
if (fk.contains(field) && fieldVal != SQL_DEFAULT && !(fieldVal instanceof SQLRowValues)) {
final SQLRow pb = this.getTable().checkValidity(field.getName(), (Number) fieldVal);
if (pb != null)
return new Object[] { fieldName, pb };
1371,7 → 1393,7
final Object toIns;
if (value instanceof SQLRowValues) {
// TODO if we already point to some row, archive it
toIns = new Integer(((SQLRowValues) value).insert().getID());
toIns = ((SQLRowValues) value).insert().getIDNumber();
} else
toIns = value;
// sql index start at 1
1390,7 → 1412,8
return value == SQL_DEFAULT ? "DEFAULT" : "?";
}
 
public SQLTableListener createTableListener(SQLDataListener l) {
@Override
public SQLTableModifiedListener createTableListener(SQLDataListener l) {
return new SQLTableListenerData<SQLRowValues>(this, l);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLImmutableRowValues.java
80,10 → 80,6
return this.delegate.isForeignEmpty(fieldName);
}
 
public boolean isEmptyLink(String fieldName) {
return this.delegate.isEmptyLink(fieldName);
}
 
public boolean isDefault(String fieldName) {
return this.delegate.isDefault(fieldName);
}
116,7 → 112,7
}
 
@Override
public SQLTableListener createTableListener(SQLDataListener l) {
public SQLTableModifiedListener createTableListener(SQLDataListener l) {
return this.delegate.createTableListener(l);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLData.java
15,7 → 15,7
 
public interface SQLData {
 
public SQLTableListener createTableListener(SQLDataListener l);
public SQLTableModifiedListener createTableListener(SQLDataListener l);
 
public SQLTable getTable();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTable.java
26,6 → 26,7
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IPredicate;
import org.openconcerto.utils.change.CollectionChangeEventCreator;
import org.openconcerto.xml.JDOMUtils;
42,6 → 43,7
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
172,9 → 174,6
// always immutable so that fire can iterate safely ; to modify it, simply copy it before
// (adding listeners is a lot less common than firing events)
private List<SQLTableModifiedListener> tableModifiedListeners;
private final List<SQLTableListener> listeners;
// copy of listeners while dispatching, so that a listener can modify it
private final List<SQLTableListener> dispatchingListeners;
// the id that foreign keys pointing to this, can use instead of NULL
// a null value meaning not yet known
private Integer undefinedID;
186,8 → 185,6
SQLTable(SQLSchema schema, String name) {
super(schema, name);
this.tableModifiedListeners = Collections.emptyList();
this.listeners = new ArrayList<SQLTableListener>();
this.dispatchingListeners = new ArrayList<SQLTableListener>();
// ne pas se soucier de la casse
this.fields = createMap();
// order matters (eg for indexes)
971,15 → 968,27
*/
 
public void addTableModifiedListener(SQLTableModifiedListener l) {
synchronized (this.listeners) {
final List<SQLTableModifiedListener> newListeners = new ArrayList<SQLTableModifiedListener>(this.tableModifiedListeners);
this.addTableModifiedListener(l, false);
}
 
public void addPremierTableModifiedListener(SQLTableModifiedListener l) {
this.addTableModifiedListener(l, true);
}
 
private void addTableModifiedListener(SQLTableModifiedListener l, final boolean before) {
synchronized (this) {
final List<SQLTableModifiedListener> newListeners = new ArrayList<SQLTableModifiedListener>(this.tableModifiedListeners.size() + 1);
if (before)
newListeners.add(l);
newListeners.addAll(this.tableModifiedListeners);
if (!before)
newListeners.add(l);
this.tableModifiedListeners = Collections.unmodifiableList(newListeners);
}
}
 
public void removeTableModifiedListener(SQLTableModifiedListener l) {
synchronized (this.listeners) {
synchronized (this) {
final List<SQLTableModifiedListener> newListeners = new ArrayList<SQLTableModifiedListener>(this.tableModifiedListeners);
if (newListeners.remove(l))
this.tableModifiedListeners = Collections.unmodifiableList(newListeners);
986,6 → 995,37
}
}
 
private static final class BridgeListener implements SQLTableModifiedListener {
 
private final SQLTableListener l;
 
private BridgeListener(SQLTableListener l) {
super();
this.l = l;
}
 
@Override
public void tableModified(SQLTableEvent evt) {
final Mode mode = evt.getMode();
if (mode == Mode.ROW_ADDED)
this.l.rowAdded(evt.getTable(), evt.getId());
else if (mode == Mode.ROW_UPDATED)
this.l.rowModified(evt.getTable(), evt.getId());
else if (mode == Mode.ROW_DELETED)
this.l.rowDeleted(evt.getTable(), evt.getId());
}
 
@Override
public int hashCode() {
return this.l.hashCode();
}
 
@Override
public boolean equals(Object obj) {
return obj instanceof BridgeListener && this.l.equals(((BridgeListener) obj).l);
}
}
 
/**
* Ajoute un listener sur cette table.
*
993,32 → 1033,13
* @deprecated use {@link #addTableModifiedListener(SQLTableModifiedListener)}
*/
public void addTableListener(SQLTableListener l) {
synchronized (this.listeners) {
if (!this.listeners.contains(l)) {
this.listeners.add(l);
} else
Log.get().fine(l + " already in");
this.addTableModifiedListener(new BridgeListener(l));
}
}
 
public void addPremierTableListener(SQLTableListener l) {
synchronized (this.listeners) {
if (!this.listeners.contains(l)) {
this.listeners.add(0, l);
} else
throw new IllegalStateException(l + " is already listener of " + this);
}
}
 
public void removeTableListener(SQLTableListener l) {
synchronized (this.listeners) {
this.listeners.remove(l);
this.removeTableModifiedListener(new BridgeListener(l));
}
}
 
private static final int NOT_DISPATCHING = -2;
private int dispatchingID = NOT_DISPATCHING;
 
/**
* Previent tous les listeners de la table qu'il y a eu une modification ou ajout si modif de
* d'une ligne particuliere.
1046,61 → 1067,55
}
 
public final void fire(SQLTableEvent evt) {
final int id = evt.getId();
synchronized (this.dispatchingListeners) {
// FIXME peut laisser tomber des changements si un notifié rechange la même ligne
if (this.dispatchingID != id) {
this.dispatchingID = id;
final Mode mode = evt.getMode();
this.dispatchingListeners.clear();
synchronized (this.listeners) {
this.dispatchingListeners.addAll(this.listeners);
this.fireTableModified(evt);
}
final int size = this.dispatchingListeners.size();
for (int i = 0; i < size; i++) {
final SQLTableListener obj = this.dispatchingListeners.get(i);
if (mode == Mode.ROW_UPDATED)
obj.rowModified(this, id);
else if (mode == Mode.ROW_ADDED)
obj.rowAdded(this, id);
else if (mode == Mode.ROW_DELETED)
obj.rowDeleted(this, id);
else
throw new IllegalArgumentException("unknown mode: " + mode);
 
static private final ThreadLocal<LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>>> events = new ThreadLocal<LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>>>() {
@Override
protected LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>> initialValue() {
return new LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>>();
}
this.fireTableModified(evt);
this.dispatchingID = NOT_DISPATCHING;
} else {
System.err.println("dropping a SQLTable.fire() : fired in the listener");
Thread.dumpStack();
};
 
// allow to maintain the dispatching of events in order when a listener itself fires an event
static private void fireTableModified(Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent> newTuple) {
final LinkedList<Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent>> linkedList = events.get();
// add new event
linkedList.addLast(newTuple);
// process all pending events
Tuple2<Iterator<SQLTableModifiedListener>, SQLTableEvent> currentTuple;
while ((currentTuple = linkedList.peekFirst()) != null) {
final Iterator<SQLTableModifiedListener> iter = currentTuple.get0();
final SQLTableEvent currentEvt = currentTuple.get1();
while (iter.hasNext()) {
final SQLTableModifiedListener l = iter.next();
l.tableModified(currentEvt);
}
// not removeFirst() since the item might have been already removed
linkedList.pollFirst();
}
}
 
private void fireTableModified(final SQLTableEvent evt) {
// no need to copy since this.tableModifiedListeners is immutable
final List<SQLTableModifiedListener> dispatchingListeners;
synchronized (this.listeners) {
synchronized (this) {
dispatchingListeners = this.tableModifiedListeners;
}
// no need to synchronize since dispatchingListeners is immutable
// even better, it also works if the same thread calls fireTableModified() in a callback
// (although in that case some listeners might have events in the wrong order)
for (final SQLTableModifiedListener l : dispatchingListeners) {
l.tableModified(evt);
fireTableModified(Tuple2.create(dispatchingListeners.iterator(), evt));
}
}
 
@SuppressWarnings("unchecked")
public String toXML() {
final StringBuilder sb = new StringBuilder(16000);
sb.append("<table name=\"");
sb.append(this.getName());
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append("\"");
 
final String schemaName = this.getSchema().getName();
if (schemaName != null) {
sb.append(" schema=\"");
sb.append(schemaName);
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(schemaName));
sb.append('"');
}
 
1114,7 → 1129,7
 
if (getType() != null) {
sb.append(" type=\"");
sb.append(getType());
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(getType()));
sb.append('"');
}
 
1152,21 → 1167,13
return sb.toString();
}
 
public SQLTableListener createTableListener(final SQLDataListener l) {
return new SQLTableListener() {
 
public void rowModified(SQLTable table, int id) {
@Override
public SQLTableModifiedListener createTableListener(final SQLDataListener l) {
return new SQLTableModifiedListener() {
@Override
public void tableModified(SQLTableEvent evt) {
l.dataChanged();
}
 
public void rowAdded(SQLTable table, int id) {
l.dataChanged();
}
 
public void rowDeleted(SQLTable table, int id) {
l.dataChanged();
}
 
};
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLType.java
17,6 → 17,7
package org.openconcerto.sql.model;
 
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.xml.JDOMUtils;
 
import java.math.BigDecimal;
import java.math.BigInteger;
303,7 → 304,7
sb.append(this.decimalDigits);
}
sb.append("\" typeName=\"");
sb.append(this.typeName);
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.typeName));
sb.append("\"/>");
this.xml = sb.toString();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesListFetcher.java
454,6 → 454,29
}
 
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.fieldCount;
result = prime * result + ((this.from == null) ? 0 : this.from.hashCode());
result = prime * result + this.linkIndex;
result = prime * result + this.t.hashCode();
return result;
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final GraphNode other = (GraphNode) obj;
return this.fieldCount == other.fieldCount && this.linkIndex == other.linkIndex && this.t.equals(other.t) && CompareUtils.equals(this.from, other.from);
}
 
@Override
public String toString() {
final String link = this.from == null ? "" : " linked to " + getLinkIndex() + " by " + this.getFromName() + (this.isBackwards() ? " backwards" : " forewards");
return this.getFieldCount() + " fields of " + this.getTable() + link;
460,44 → 483,19
}
}
 
/**
* Execute the request transformed by <code>selTransf</code> and return the result as a list of
* SQLRowValues.
*
* @return a list of SQLRowValues, one item per row, each item having the same structure as the
* SQLRowValues passed to the constructor.
*/
public final List<SQLRowValues> fetch() {
return this.fetch(true);
}
static private final class RSH implements ResultSetHandler {
private final List<String> selectFields;
private final List<GraphNode> graphNodes;
 
private final List<SQLRowValues> fetch(final boolean merge) {
final SQLSelect req = this.getReq();
// getName() would take 5% of ResultSetHandler.handle()
final List<String> selectFields = new ArrayList<String>(req.getSelectFields().size());
for (final SQLField f : req.getSelectFields())
selectFields.add(f.getName());
final SQLTable table = getGraph().getTable();
 
// create a flat list of the graph nodes, we just need the table, field count and the index
// in this list of its linked table, eg for CPI -> LOCAL -> BATIMENT -> SITE :
// <LOCAL,2,0>, <BATIMENT,2,0>, <SITE,5,1>, <CPI,4,0>
final int graphSize = this.getGraph().getGraph().size();
final List<GraphNode> l = new ArrayList<GraphNode>(graphSize);
walk(0, new ITransformer<State<Integer>, Integer>() {
@Override
public Integer transformChecked(State<Integer> input) {
final int index = l.size();
l.add(new GraphNode(input));
return index;
private RSH(List<String> selectFields, List<GraphNode> l) {
this.selectFields = selectFields;
this.graphNodes = l;
}
});
assert l.size() == graphSize : "All nodes weren't explored once : " + l.size() + " != " + graphSize;
 
@SuppressWarnings("unchecked")
final List<SQLRowValues> res = (List<SQLRowValues>) table.getBase().getDataSource().execute(req.asString(), new ResultSetHandler() {
@Override
public Object handle(final ResultSet rs) throws SQLException {
final List<GraphNode> l = this.graphNodes;
final int graphSize = l.size();
int nextToLink = 0;
final List<Future<?>> futures = new ArrayList<Future<?>>();
 
510,7 → 508,7
 
// MAYBE cancel() futures
if (Thread.currentThread().isInterrupted())
throw new RTInterruptedException("interrupted while fetching " + SQLRowValuesListFetcher.this);
throw new RTInterruptedException("interrupted while fetching");
final List<SQLRowValues> row = new ArrayList<SQLRowValues>(graphSize);
for (int i = 0; i < graphSize; i++) {
final GraphNode node = l.get(i);
523,7 → 521,7
try {
// -1 since rs starts at 1
// field names checked below
creatingVals.put(selectFields.get(rsIndex - 1), rs.getObject(rsIndex), false);
creatingVals.put(this.selectFields.get(rsIndex - 1), rs.getObject(rsIndex), false);
} catch (SQLException e) {
throw new IllegalStateException("unable to fill " + creatingVals, e);
}
568,7 → 566,70
 
return res;
}
}, false);
 
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.graphNodes.hashCode();
result = prime * result + this.selectFields.hashCode();
return result;
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final RSH other = (RSH) obj;
return this.graphNodes.equals(other.graphNodes) && this.selectFields.equals(other.selectFields);
}
 
}
 
/**
* Execute the request transformed by <code>selTransf</code> and return the result as a list of
* SQLRowValues. NOTE: this method doesn't use the cache of SQLDataSource.
*
* @return a list of SQLRowValues, one item per row, each item having the same structure as the
* SQLRowValues passed to the constructor.
*/
public final List<SQLRowValues> fetch() {
return this.fetch(true);
}
 
private final List<SQLRowValues> fetch(final boolean merge) {
final SQLSelect req = this.getReq();
// getName() would take 5% of ResultSetHandler.handle()
final List<String> selectFields = new ArrayList<String>(req.getSelectFields().size());
for (final SQLField f : req.getSelectFields())
selectFields.add(f.getName());
final SQLTable table = getGraph().getTable();
 
// create a flat list of the graph nodes, we just need the table, field count and the index
// in this list of its linked table, eg for CPI -> LOCAL -> BATIMENT -> SITE :
// <LOCAL,2,0>, <BATIMENT,2,0>, <SITE,5,1>, <CPI,4,0>
final int graphSize = this.getGraph().getGraph().size();
final List<GraphNode> l = new ArrayList<GraphNode>(graphSize);
walk(0, new ITransformer<State<Integer>, Integer>() {
@Override
public Integer transformChecked(State<Integer> input) {
final int index = l.size();
l.add(new GraphNode(input));
return index;
}
});
assert l.size() == graphSize : "All nodes weren't explored once : " + l.size() + " != " + graphSize;
 
// if we wanted to use the cache, we'd need to copy the returned list and its items (i.e.
// deepCopy()), since we modify them afterwards. Or perhaps include the code after this line
// into the result set handler.
final IResultSetHandler rsh = new IResultSetHandler(new RSH(selectFields, l), false);
@SuppressWarnings("unchecked")
final List<SQLRowValues> res = (List<SQLRowValues>) table.getBase().getDataSource().execute(req.asString(), rsh, false);
// e.g. list of batiment pointing to site
final List<SQLRowValues> merged = merge && this.fetchReferents() ? merge(res) : res;
if (this.grafts.size() > 0) {
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTableListenerData.java
13,7 → 13,7
package org.openconcerto.sql.model;
 
final class SQLTableListenerData<R extends SQLRowAccessor> implements SQLTableListener {
final class SQLTableListenerData<R extends SQLRowAccessor> implements SQLTableModifiedListener {
 
private final R row;
private final SQLDataListener l;
23,19 → 23,11
this.l = l;
}
 
public void rowAdded(SQLTable table, int id) {
// if the row id was cached as non-existant, now it is
if (this.row.getID() == id)
this.l.dataChanged();
}
 
public void rowDeleted(SQLTable table, int id) {
@Override
public void tableModified(SQLTableEvent evt) {
final int id = evt.getId();
// if the row id was cached as non-existent and evt mode is ADDED, now it is
if (id < SQLRow.MIN_VALID_ID || this.row.getID() == id)
this.l.dataChanged();
}
 
public void rowModified(SQLTable table, int id) {
if (id < SQLRow.MIN_VALID_ID || this.row.getID() == id)
this.l.dataChanged();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowAccessor.java
167,6 → 167,14
return res;
}
 
/**
* Return the 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 <code>null</code>,
* otherwise a SQLRowAccessor with the value of <code>fieldName</code> as its ID.
* @throws IllegalArgumentException if fieldName is not a foreign field.
*/
public abstract SQLRowAccessor getForeign(String fieldName);
 
public abstract boolean isForeignEmpty(String fieldName);
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSchema.java
53,7 → 53,7
sb.append(' ');
sb.append(VERSION_XMLATTR);
sb.append("=\"");
sb.append(version);
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(version));
sb.append('"');
} catch (IOException e) {
throw new IllegalStateException("Couldn't append version of " + schema, e);
218,7 → 218,7
sb.append("<schema ");
if (this.getName() != null) {
sb.append(" name=\"");
sb.append(this.getName());
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append('"');
}
getVersionAttr(this, sb);
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLField.java
21,6 → 21,7
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ExceptionUtils;
import org.openconcerto.xml.JDOMUtils;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.sql.DatabaseMetaData;
281,7 → 282,7
if (this.xml == null) {
final StringBuilder sb = new StringBuilder(2048);
sb.append("<field name=\"");
sb.append(this.getName());
sb.append(JDOMUtils.OUTPUTTER.escapeAttributeEntities(this.getName()));
sb.append("\" >");
sb.append(this.type.toXML());
sb.append(XMLCodecUtils.encodeSimple(this.metadata));
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightsManagerModel.java
19,7 → 19,9
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.Where;
 
import java.sql.SQLException;
55,26 → 57,17
List<SQLRow> rowsRights = (List<SQLRow>) Configuration.getInstance().getBase().getDataSource().execute(sel2.asString(), new SQLRowListRSH(this.tableRight, true));
this.cache.addAll(rowsRights);
 
this.tableRight.addTableListener(new SQLTableListener() {
this.tableRight.addTableModifiedListener(new SQLTableModifiedListener() {
@Override
public void rowAdded(SQLTable table, int id) {
 
UserRightsManagerModel.this.cache.add(table.getRow(id));
}
 
@Override
public void rowDeleted(SQLTable table, int id) {
 
}
 
@Override
public void rowModified(SQLTable table, int id) {
SQLRow row = table.getRow(id);
 
public void tableModified(SQLTableEvent evt) {
if (evt.getMode() == Mode.ROW_ADDED) {
UserRightsManagerModel.this.cache.add(evt.getRow());
} else {
final SQLRow row = evt.getRow();
for (int i = 0; i < UserRightsManagerModel.this.cache.size(); i++) {
SQLRow row2 = UserRightsManagerModel.this.cache.get(i);
if (row2.getID() == id) {
if (row.isArchived()) {
final SQLRow row2 = UserRightsManagerModel.this.cache.get(i);
if (row2.getID() == row.getID()) {
if (!row.isValid()) {
UserRightsManagerModel.this.cache.remove(i);
} else {
UserRightsManagerModel.this.cache.set(i, row2);
83,15 → 76,24
}
}
}
}
});
 
this.tableUserRight.addTableListener(new SQLTableListener() {
this.tableUserRight.addTableModifiedListener(new SQLTableModifiedListener() {
 
@Override
public void rowAdded(SQLTable table, int id) {
public void tableModified(SQLTableEvent evt) {
if (evt.getMode() == Mode.ROW_ADDED) {
rowAdded(evt);
} else {
rowModified(evt);
}
}
 
SQLRow row = table.getRow(id);
public void rowAdded(SQLTableEvent evt) {
final SQLRow row = evt.getRow();
if (row.getInt("ID_USER_COMMON") == UserRightsManagerModel.this.idUser) {
SQLRowValues rowVals = getSQLRowValuesForID(id);
SQLRowValues rowVals = getSQLRowValuesFor(row);
if (rowVals == null) {
UserRightsManagerModel.this.listRowValues.add(row.createUpdateRow());
fireTableRowsInserted(UserRightsManagerModel.this.listRowValues.size() - 2, UserRightsManagerModel.this.listRowValues.size() - 1);
99,17 → 101,12
}
}
 
@Override
public void rowDeleted(SQLTable table, int id) {
}
 
@Override
public void rowModified(SQLTable table, int id) {
SQLRow row = table.getRow(id);
public void rowModified(SQLTableEvent evt) {
final SQLRow row = evt.getRow();
if (row.getInt("ID_USER_COMMON") == UserRightsManagerModel.this.idUser) {
SQLRowValues rowVals = getSQLRowValuesForID(id);
SQLRowValues rowVals = getSQLRowValuesFor(row);
int index = UserRightsManagerModel.this.listRowValues.indexOf(rowVals);
if (row.isArchived()) {
if (!row.isValid()) {
UserRightsManagerModel.this.listRowValues.removeElement(rowVals);
fireTableRowsDeleted(index - 1, index + 1);
} else {
121,12 → 118,11
});
}
 
private SQLRowValues getSQLRowValuesForID(int id) {
SQLRow row = this.tableUserRight.getRow(id);
private SQLRowValues getSQLRowValuesFor(final SQLRow row) {
final String string2 = row.getString("CODE");
for (SQLRowValues rowVals : this.listRowValues) {
final String string = rowVals.getString("CODE");
if (rowVals.getID() == id || (string != null && string2 != null && string.equalsIgnoreCase(string2))) {
if (rowVals.getID() == row.getID() || (string != null && string2 != null && string.equalsIgnoreCase(string2))) {
return rowVals;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/correct/FixSharedPrivate.java
17,11 → 17,14
import org.openconcerto.sql.changer.Changer;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
 
41,7 → 44,7
super(b);
}
 
@SuppressWarnings("unchecked")
@Override
protected void changeImpl(final SQLTable t) throws SQLException {
getStream().println(t + "... ");
if (Configuration.getInstance() == null || Configuration.getInstance().getDirectory() == null)
65,9 → 68,14
sel.addBackwardJoin("INNER", "m", t.getField(pff), null);
final String req = sel.asString() + " GROUP BY " + privateTable.getKey().getFieldRef() + " HAVING count(" + privateTable.getKey().getFieldRef() + ")>1";
 
@SuppressWarnings("unchecked")
final List<Number> privateIDs = t.getDBSystemRoot().getDataSource().executeCol(req);
if (privateIDs.size() > 0) {
getStream().println("\t" + pff + " fixing " + privateIDs.size() + " ... ");
final SQLField archF = t.getArchiveField();
final SQLField privateArchF = privateTable.getArchiveField();
if ((archF == null) != (privateArchF == null))
throw new IllegalStateException("Incoherent archive field : " + archF + " / " + privateArchF);
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new SQLFactory<Object>() {
@Override
public Object create() throws SQLException {
76,11 → 84,21
final SQLSelect fixSel = new SQLSelect(t.getBase());
fixSel.setArchivedPolicy(ArchiveMode.BOTH);
fixSel.addSelect(t.getKey());
if (archF != null)
fixSel.addSelect(archF);
fixSel.setWhere(new Where(t.getField(pff), "=", privateID));
final List<Number> tIDs = t.getDBSystemRoot().getDataSource().executeCol(fixSel.asString());
final List<SQLRow> tIDs = SQLRowListRSH.execute(fixSel);
for (final SQLRow tID : tIDs) {
// the first one can keep its private
for (final Number tID : tIDs.subList(1, tIDs.size())) {
new SQLRowValues(t).setID(tID).put(pff, privateElement.createCopy(privateID.intValue())).update();
final SQLRowValues reallyPrivate;
if (tID == tIDs.get(0))
reallyPrivate = new SQLRowValues(privateElement.getTable()).setID(privateID);
else
reallyPrivate = privateElement.createCopy(privateID.intValue());
// keep archive coherence
if (archF != null)
reallyPrivate.put(privateArchF.getName(), tID.getObject(archF.getName()));
new SQLRowValues(t).setID(tID.getIDNumber()).put(pff, reallyPrivate).update();
}
}
return null;
/trunk/OpenConcerto/src/org/openconcerto/sql/view/AbstractFileTransfertHandler.java
New file
0,0 → 1,137
/*
* 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.view;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLTable;
 
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
 
import javax.swing.JComponent;
import javax.swing.TransferHandler;
 
public abstract class AbstractFileTransfertHandler extends TransferHandler {
 
static private DataFlavor URIListFlavor = null;
 
static public synchronized DataFlavor getURIListFlavor() {
if (URIListFlavor == null) {
try {
URIListFlavor = new DataFlavor("text/uri-list;class=java.lang.String");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
return URIListFlavor;
}
 
public AbstractFileTransfertHandler() {
}
 
public abstract void handleFile(File f);
 
@Override
public boolean importData(final JComponent c, final Transferable t) {
if (!canImport(c, t.getTransferDataFlavors())) {
return false;
}
final List<File> list = new ArrayList<File>();
try {
if (hasFileFlavor(t.getTransferDataFlavors())) {
list.addAll((List<File>) t.getTransferData(DataFlavor.javaFileListFlavor));
} else if (hasURIListFlavor(t.getTransferDataFlavors())) {
list.addAll(textURIListToFileList((String) t.getTransferData(getURIListFlavor())));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
 
final Thread thread = new Thread("AbstractFileTransfertHandler") {
@Override
public void run() {
for (File realFile : list) {
handleFile(realFile);
}
}
};
thread.start();
} catch (Exception e) {
e.printStackTrace();
}
 
return list.size() > 0;
 
}
 
@Override
public int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
 
@Override
public boolean canImport(JComponent c, DataFlavor[] flavors) {
if (hasFileFlavor(flavors) || hasURIListFlavor(flavors)) {
return true;
}
Log.get().config("No files or URL found in dropped object");
return false;
}
 
private boolean hasFileFlavor(DataFlavor[] flavors) {
for (int i = 0; i < flavors.length; i++) {
if (DataFlavor.javaFileListFlavor.equals(flavors[i]) || DataFlavor.javaRemoteObjectMimeType.equals(flavors[i])) {
return true;
}
}
return false;
}
 
private boolean hasURIListFlavor(DataFlavor[] flavors) {
for (int i = 0; i < flavors.length; i++) {
if (getURIListFlavor().equals(flavors[i])) {
return true;
}
}
return false;
}
 
static List<File> textURIListToFileList(String data) {
final List<File> list = new ArrayList<File>(1);
for (StringTokenizer st = new StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
String s = st.nextToken();
if (s.startsWith("#")) {
// the line is a comment (as per the RFC 2483)
continue;
}
try {
final URI uri = new URI(s);
final File file = new File(uri);
list.add(file);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
return list;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/KeyTableCellRenderer.java
15,8 → 15,9
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.sqlobject.IComboSelectionItem;
 
import java.util.HashMap;
31,7 → 32,7
private Object toSelect;
private boolean isLoading = false;
private final SQLElement el;
static final Map<SQLElement, Map<Integer, IComboSelectionItem>> cacheMap = new HashMap<SQLElement, Map<Integer, IComboSelectionItem>>();
static private final Map<SQLElement, Map<Integer, IComboSelectionItem>> cacheMap = new HashMap<SQLElement, Map<Integer, IComboSelectionItem>>();
 
public KeyTableCellRenderer(final SQLElement el) {
super();
93,19 → 94,13
m.put(comboSelectionItem.getId(), comboSelectionItem);
}
cacheMap.put(KeyTableCellRenderer.this.el, m);
KeyTableCellRenderer.this.el.getTable().addPremierTableListener(new SQLTableListener() {
KeyTableCellRenderer.this.el.getTable().addPremierTableModifiedListener(new SQLTableModifiedListener() {
@Override
public void rowAdded(SQLTable table, int id) {
m.put(id, KeyTableCellRenderer.this.el.getComboRequest().getComboItem(id));
}
 
@Override
public void rowDeleted(SQLTable table, int id) {
public void tableModified(SQLTableEvent evt) {
final int id = evt.getId();
if (evt.getMode() == Mode.ROW_DELETED)
m.remove(id);
}
 
@Override
public void rowModified(SQLTable table, int id) {
else
m.put(id, KeyTableCellRenderer.this.el.getComboRequest().getComboItem(id));
}
});
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/IListe.java
59,6 → 59,7
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.convertor.StringClobConvertor;
 
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Font;
308,6 → 309,11
// don't auto start, otherwise F2 will trigger the edition
this.jTable.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
 
// Better look
this.jTable.setShowHorizontalLines(false);
this.jTable.setGridColor(new Color(230, 230, 230));
this.jTable.setRowHeight(this.jTable.getRowHeight() + 4);
 
this.popup = new JPopupMenu();
TablePopupMouseListener.add(this.jTable, new ITransformer<MouseEvent, JPopupMenu>() {
@Override
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/RowAction.java
59,6 → 59,9
}
 
public final PredicateRowAction setPredicate(IPredicate<? super IListeEvent> pred) {
if (pred == null) {
throw new IllegalArgumentException("null predicate");
}
this.pred = pred;
return this;
}
69,6 → 72,9
 
@Override
public boolean enabledFor(IListeEvent evt) {
if (pred == null) {
throw new IllegalStateException("No predicate for action:" + this.getAction() + ":" + this.getAction().getValue(Action.NAME));
}
return this.pred.evaluateChecked(evt);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/EditFrame.java
40,6 → 40,11
 
public static final EditMode MODIFICATION = EditPanel.MODIFICATION;
public static final EditMode CREATION = EditPanel.CREATION;
/**
* If this system property is true, then the minimum size of an edit frame will match the one
* from its content pane.
*/
public final static String SMALL_MIN_SIZE = "org.openconcerto.sql.editFrame.smallMinSize";
 
private boolean frameResize;
 
103,8 → 108,17
// The minimum size of the frame must be the size when packed
this.pack();
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int w = Math.min(d.width - 100, getWidth());
int h = Math.min(d.height - 100, getHeight());
final int wantedW, wantedH;
if (Boolean.getBoolean(SMALL_MIN_SIZE)) {
final Dimension minimumSize = this.getMinimumSize();
wantedW = minimumSize.width;
wantedH = minimumSize.height;
} else {
wantedW = getWidth();
wantedH = getHeight();
}
final int w = Math.min(d.width - 100, wantedW);
final int h = Math.min(d.height - 100, wantedH);
setMinimumSize(new Dimension(w, h));
 
// View resized
/trunk/OpenConcerto/src/org/openconcerto/sql/view/EditPanel.java
257,6 → 257,7
this.p.getVerticalScrollBar().setUnitIncrement(9);
this.p.setOpaque(false);
this.p.getViewport().setOpaque(false);
this.p.setMinimumSize(new Dimension(60, 60));
 
container.add(this.p, c);
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/FileTransfertHandler.java
19,11 → 19,8
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
 
import javax.swing.JComponent;
import javax.swing.TransferHandler;
30,19 → 27,6
 
public class FileTransfertHandler extends TransferHandler {
 
static private DataFlavor URIListFlavor = null;
 
static public DataFlavor getURIListFlavor() {
if (URIListFlavor == null) {
try {
URIListFlavor = new DataFlavor("text/uri-list;class=java.lang.String");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
return URIListFlavor;
}
 
private final SQLTable tableName;
 
public FileTransfertHandler(SQLTable table) {
59,7 → 43,7
if (hasFileFlavor(t.getTransferDataFlavors())) {
list.addAll((List<File>) t.getTransferData(DataFlavor.javaFileListFlavor));
} else if (hasURIListFlavor(t.getTransferDataFlavors())) {
list.addAll(textURIListToFileList((String) t.getTransferData(getURIListFlavor())));
list.addAll(AbstractFileTransfertHandler.textURIListToFileList((String) t.getTransferData(AbstractFileTransfertHandler.getURIListFlavor())));
}
} catch (Exception e) {
e.printStackTrace();
122,7 → 106,7
 
private boolean hasURIListFlavor(DataFlavor[] flavors) {
for (int i = 0; i < flavors.length; i++) {
if (getURIListFlavor().equals(flavors[i])) {
if (AbstractFileTransfertHandler.getURIListFlavor().equals(flavors[i])) {
return true;
}
}
133,24 → 117,4
return DropManager.getInstance().getHandlerForTable(this.tableName);
}
 
private static List<File> textURIListToFileList(String data) {
final List<File> list = new ArrayList<File>(1);
for (StringTokenizer st = new StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
String s = st.nextToken();
if (s.startsWith("#")) {
// the line is a comment (as per the RFC 2483)
continue;
}
try {
final URI uri = new URI(s);
final File file = new File(uri);
list.add(file);
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
return list;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/PropsConfiguration.java
31,16 → 31,20
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.LogUtils;
import org.openconcerto.utils.MultipleOutputStream;
import org.openconcerto.utils.StreamUtils;
import org.openconcerto.utils.cc.IClosure;
 
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
512,6 → 516,10
this.setupLogging(dirName, Boolean.getBoolean(REDIRECT_TO_FILE));
}
 
protected boolean keepStandardStreamsWhenRedirectingToFile() {
return true;
}
 
public void setupLogging(final String dirName, final boolean redirectToFile) {
final File logDir;
try {
542,9 → 550,17
logFile.getParentFile().mkdirs();
try {
System.out.println("Log file: " + logFile.getAbsolutePath());
final PrintStream ps = new PrintStream(new FileOutputStream(logFile, true));
System.setErr(ps);
System.setOut(ps);
final OutputStream fileOut = new FileOutputStream(logFile, true);
final OutputStream out, err;
if (this.keepStandardStreamsWhenRedirectingToFile()) {
out = new MultipleOutputStream(fileOut, new FileOutputStream(FileDescriptor.out));
err = new MultipleOutputStream(fileOut, new FileOutputStream(FileDescriptor.err));
} else {
out = fileOut;
err = fileOut;
}
System.setErr(new PrintStream(new BufferedOutputStream(err, 128), true));
System.setOut(new PrintStream(new BufferedOutputStream(out, 128), true));
} catch (FileNotFoundException e) {
throw new IllegalStateException("unable to write to log file", e);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ElementComboBox.java
18,6 → 18,7
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.request.SQLRowItemView;
import org.openconcerto.sql.view.EditFrame;
import org.openconcerto.sql.view.EditPanel;
135,8 → 136,14
* @return this
*/
public final ElementComboBox init(SQLElement element) {
return this.init(element, element.getComboRequest());
}
 
public final ElementComboBox init(SQLElement element, final ComboSQLRequest req) {
if (element.getTable() != req.getPrimaryTable())
throw new IllegalArgumentException("Tables are different " + element.getTable().getSQLName() + " != " + req.getPrimaryTable().getSQLName());
this.element = element;
this.uiInit(this.element.getComboRequest());
this.uiInit(req);
return this;
}
 
146,7 → 153,10
if (foreignTable == null) {
throw new IllegalArgumentException("No foreign table for " + v.getField().getFullName());
}
if (this.getElement() == null)
this.init(Configuration.getInstance().getDirectory().getElement(foreignTable));
else if (this.getElement().getTable() != foreignTable)
throw new IllegalArgumentException("Tables are different " + getElement().getTable().getSQLName() + " != " + foreignTable.getSQLName());
}
 
public final SQLElement getElement() {
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ITextWithCompletion.java
15,17 → 15,16
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.ui.component.text.TextComponent;
import org.openconcerto.utils.OrderedSet;
 
import org.openconcerto.utils.checks.MutableValueObject;
import org.openconcerto.utils.text.DocumentFilterList;
import org.openconcerto.utils.text.DocumentFilterList.FilterType;
import org.openconcerto.utils.text.LimitedSizeDocumentFilter;
import org.openconcerto.utils.text.DocumentFilterList.FilterType;
 
import java.awt.Component;
import java.awt.GridLayout;
118,19 → 117,12
 
this.isLoading = true;
loadCacheAsynchronous();
this.comboRequest.addTableListener(new SQLTableListener() {
 
public void rowAdded(SQLTable table, int id) {
// FIXME never removed
this.comboRequest.addTableListener(new SQLTableModifiedListener() {
@Override
public void tableModified(SQLTableEvent evt) {
loadCacheAsynchronous();
}
 
public void rowDeleted(SQLTable table, int id) {
loadCacheAsynchronous();
}
 
public void rowModified(SQLTable table, int id) {
loadCacheAsynchronous();
}
});
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/SQLTextCombo.java
15,6 → 15,7
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.IResultSetHandler;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
85,13 → 86,25
return this.t.getDBSystemRoot().getDataSource();
}
 
@SuppressWarnings("unchecked")
public List<String> loadCache() {
public List<String> loadCache(final boolean dsCache) {
final SQLSelect sel = new SQLSelect(this.t.getBase());
sel.addSelect(this.t.getField("LABEL"));
sel.setWhere(new Where(this.t.getField("CHAMP"), "=", this.field));
// ignore DS cache to allow the fetching of rows modified by another VM
@SuppressWarnings("unchecked")
final List<String> items = (List<String>) this.getDS().execute(sel.asString(), new IResultSetHandler(SQLDataSource.COLUMN_LIST_HANDLER) {
@Override
public boolean readCache() {
return dsCache;
}
 
@Override
public boolean writeCache() {
return true;
}
});
this.cache.clear();
this.cache.addAll(this.getDS().executeCol(sel.asString()));
this.cache.addAll(items);
 
return this.cache;
}
98,7 → 111,7
 
public List<String> getCache() {
if (!this.loadedOnce) {
this.loadCache();
this.loadCache(true);
this.loadedOnce = true;
}
return this.cache;
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/IComboModel.java
16,7 → 16,8
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableListener;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.request.ComboSQLRequest;
import org.openconcerto.sql.view.search.SearchSpec;
import org.openconcerto.sql.view.search.SearchSpecUtils;
27,7 → 28,6
import org.openconcerto.utils.checks.EmptyListener;
import org.openconcerto.utils.checks.EmptyObj;
import org.openconcerto.utils.checks.MutableValueObject;
import org.openconcerto.utils.model.DefaultIListModel;
import org.openconcerto.utils.model.DefaultIMutableListModel;
 
import java.beans.PropertyChangeEvent;
52,7 → 52,7
*
* @author Sylvain CUAZ
*/
public class IComboModel extends DefaultIMutableListModel<IComboSelectionItem> implements SQLTableListener, MutableValueObject<IComboSelectionItem>, EmptyObj {
public class IComboModel extends DefaultIMutableListModel<IComboSelectionItem> implements SQLTableModifiedListener, MutableValueObject<IComboSelectionItem>, EmptyObj {
 
private final ComboSQLRequest req;
 
72,8 → 72,6
// true from when the combo is filled with the sole "dots" item until it is loaded with actual
// items, no need to synchronize (EDT)
private boolean updating;
// true if the items being changed are for display only and do not reflect the db
private boolean uiItems;
// l'id à sélectionner à la fin du updateAll
private int idToSelect;
 
100,7 → 98,6
this.search = null;
this.runnables = new ArrayList<Runnable>();
this.setWillUpdate(null);
this.uiItems = false;
this.itemsByID = new HashMap<Integer, IComboSelectionItem>();
this.addMissingItem = true;
 
166,7 → 163,7
// selection change
comboValueChanged();
} else {
itemsChanged(((DefaultIListModel) e.getSource()).getList());
itemsChanged();
}
}
});
230,14 → 227,14
* Reload this combo. This method is thread-safe.
*/
public synchronized final void fillCombo() {
this.fillCombo(null);
this.fillCombo(null, true);
}
 
public synchronized final void fillCombo(final Runnable r) {
public synchronized final void fillCombo(final Runnable r, final boolean readCache) {
// wholly synch otherwise we might get onScreen after the if
// and thus completely ignore that fillCombo()
if (!this.isSleepAllowed() || this.isOnScreen() || r != null) {
this.doUpdateAll(r);
this.doUpdateAll(r, readCache);
} else {
this.isADirtyDrityGirl = true;
}
260,14 → 257,12
 
this.setUpdating(true);
 
this.uiItems = true;
this.removeAllItems();
addItem(new IComboSelectionItem(SQLRow.NONEXISTANT_ID, "...."));
this.uiItems = false;
// Like ITableModel, don't remove all items, so that if the request fails we still
// keep old items (we used to have uiItems=true while setting the list to "Loading...")
}
}
 
private void doUpdateAll(final Runnable r) {
private void doUpdateAll(final Runnable r, final boolean readCache) {
log("entering doUpdateAll");
synchronized (this) {
this.isADirtyDrityGirl = false;
290,13 → 285,12
protected List<IComboSelectionItem> doInBackground() throws InterruptedException {
// attends 1 peu pour voir si on va pas être annulé
Thread.sleep(50);
return SearchSpecUtils.filter(IComboModel.this.req.getComboItems(), search);
return SearchSpecUtils.filter(IComboModel.this.req.getComboItems(readCache), search);
}
 
// Runs on the event-dispatching thread.
@Override
public void done() {
try {
synchronized (IComboModel.this) {
// if cancel() is called after doInBackground() nothing happens
// but updating is set to a new instance
304,38 → 298,49
// une autre maj arrive
return;
 
final List<IComboSelectionItem> items = this.get();
final boolean firstFill = !IComboModel.this.filledOnce;
// store before removing since it can trigger a selection change
final int idToSelect = IComboModel.this.idToSelect;
List<IComboSelectionItem> items = null;
try {
items = this.get();
removeAllItems();
addAllItems(items);
final boolean firstFill = !IComboModel.this.filledOnce;
IComboModel.this.filledOnce = true;
} catch (InterruptedException e) {
// ne devrait pas arriver puisque done() appelée après doInBackground()
e.printStackTrace();
} catch (CancellationException e) {
// ne devrait pas arriver puisqu'on teste isCancelled()
e.printStackTrace();
} catch (ExecutionException e) {
if (!(e.getCause() instanceof RTInterruptedException))
// pas normal
e.printStackTrace();
} finally {
// always clear willUpdate otherwise the combo can't recover
assert IComboModel.this.willUpdate == this;
IComboModel.this.setWillUpdate(null);
 
}
// check if items could be retrieved
// TODO otherwise show the error to the user so he knows that items are
// stale and he could reload them
if (items != null) {
// restaurer l'état
// if there's only one item in the list and no previous ID to select
// and org.openconcerto.sql.sqlCombo.selectSoleItem=true,select the item
final boolean noSelection = IComboModel.this.idToSelect == SQLRow.NONEXISTANT_ID;
final boolean noSelection = idToSelect == SQLRow.NONEXISTANT_ID;
if (items.size() == 1 && noSelection && Boolean.getBoolean("org.openconcerto.sql.sqlCombo.selectSoleItem"))
IComboModel.this.setSelectedItem(items.get(0));
else if (noSelection && firstFill && getFirstFillSelection() != null)
IComboModel.this.setSelectedItem(getFirstFillSelection().transformChecked(items));
else
selectID(IComboModel.this.idToSelect);
selectID(idToSelect);
 
for (final Runnable r : IComboModel.this.runnables)
r.run();
IComboModel.this.runnables.clear();
}
} catch (InterruptedException e) {
// ne devrait pas arriver puisque done() appelée après doInBackground()
e.printStackTrace();
} catch (CancellationException e) {
// ne devrait pas arriver puisqu'on teste isCancelled()
e.printStackTrace();
} catch (ExecutionException e) {
if (!(e.getCause() instanceof RTInterruptedException))
// pas normal
e.printStackTrace();
}
}
};
401,10 → 406,9
}
}
 
private final void itemsChanged(final List newVal) {
private final void itemsChanged() {
final List<IComboSelectionItem> newVal = this.getList();
this.propSupp.firePropertyChange("items", null, newVal);
if (!this.uiItems)
this.propSupp.firePropertyChange("dataItems", null, newVal);
}
 
// *** value
484,17 → 488,10
log("entering selectID " + id);
assert SwingUtilities.isEventDispatchThread();
 
// selectID() caused by the removeAll() of updateAllBegun()
// so we don't want to forget the selection
if (this.uiItems) {
assert id == SQLRow.NONEXISTANT_ID;
return;
}
 
// no need to launch another updateAll() if one is already underway
if (this.neverBeenFilled() && !isUpdating())
// don't use fillCombo() which won't really update unless we're on screen
this.doUpdateAll(null);
this.doUpdateAll(null, true);
 
if (this.isUpdating()) {
this.idToSelect = id;
624,7 → 621,8
* ones (e.g. adding a '-- loading --' item).
*/
public final void addItemsListener(PropertyChangeListener l, final boolean all) {
this.addListener(all ? "items" : "dataItems", l);
// there's no uiItems anymore, so ignore the boolean
this.addListener("items", l);
}
 
public final void rmItemsListener(PropertyChangeListener l) {
633,41 → 631,15
 
// *** une table que nous affichons a changé
 
/*
* (non-Javadoc)
*
* @see org.openconcerto.sql.model.SQLTableListener2#rowModified(org.openconcerto.sql.model.SQLTable, int)
*/
public void rowModified(SQLTable table, int id) {
if (id >= SQLRow.MIN_VALID_ID && this.getForeignTable().equals(table)) {
@Override
public void tableModified(SQLTableEvent evt) {
final int id = evt.getId();
if (id >= SQLRow.MIN_VALID_ID && this.getForeignTable().equals(evt.getTable())) {
this.reloadComboItem(id);
} else
this.fillCombo();
}
 
/*
* (non-Javadoc)
*
* @see org.openconcerto.sql.model.SQLTableListener2#rowAdded(org.openconcerto.sql.model.SQLTable, int)
*/
public void rowAdded(SQLTable table, int id) {
this.fillCombo();
}
 
/*
* (non-Javadoc)
*
* @see org.openconcerto.sql.model.SQLTableListener2#rowDeleted(org.openconcerto.sql.model.SQLTable, int)
*/
public void rowDeleted(SQLTable table, int id) {
if (this.getForeignTable().equals(table)) {
// delete even if we're not on screen, since it takes next to no time
this.reloadComboItem(id);
} else {
this.fillCombo();
}
}
 
// *** search
 
public final void search(SearchSpec spec) {
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/SQLSearchableTextCombo.java
18,18 → 18,9
import org.openconcerto.sql.sqlobject.SQLTextCombo.ITextComboCacheSQL;
import org.openconcerto.sql.sqlobject.itemview.RowItemViewComponent;
import org.openconcerto.ui.component.ComboLockedMode;
import org.openconcerto.ui.component.IComboCacheListModel;
import org.openconcerto.ui.component.combo.ISearchableTextCombo;
import org.openconcerto.utils.change.CollectionChangeEvent;
import org.openconcerto.utils.change.IListDataEvent;
import org.openconcerto.utils.model.DefaultIMutableListModel;
 
import java.util.Collection;
import java.util.List;
 
import javax.swing.SwingWorker;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
 
/**
* An ISearchableTextCombo with the cache from COMPLETION.
*
73,87 → 64,17
* @param cache the cache to set.
*/
public void initCacheLater(final ISQLListModel cache) {
cache.load(new Runnable() {
@Override
public void run() {
initCache(cache);
cache.initCacheLater(this);
}
});
}
 
public static class ISQLListModel extends DefaultIMutableListModel<String> {
public static class ISQLListModel extends IComboCacheListModel {
 
private final ITextComboCacheSQL cache;
private final ListDataListener l;
 
public ISQLListModel(final SQLField f) {
this(new ITextComboCacheSQL(f));
}
 
public ISQLListModel(final ITextComboCacheSQL c) {
this.cache = c;
this.l = new ListDataListener() {
 
@SuppressWarnings("unchecked")
public void contentsChanged(ListDataEvent e) {
// selection change, see DefaultIMutableListModel#setSelectedItem()
if (e.getIndex0() < 0)
return;
 
final CollectionChangeEvent evt = ((IListDataEvent) e).getCollectionChangeEvent();
this.remove(evt);
this.add(evt.getItemsAdded());
super(c);
}
 
public void intervalAdded(ListDataEvent e) {
this.add(getList().subList(e.getIndex0(), e.getIndex1() + 1));
}
 
public void intervalRemoved(ListDataEvent e) {
this.remove(((IListDataEvent) e).getCollectionChangeEvent());
}
 
private void add(Collection<String> toAdd) {
for (final String s : toAdd) {
ISQLListModel.this.cache.addToCache(s);
}
}
 
@SuppressWarnings("unchecked")
private void remove(CollectionChangeEvent evt) {
for (final String s : (Collection<String>) evt.getItemsRemoved())
ISQLListModel.this.cache.deleteFromCache(s);
}
};
}
 
private void load(final Runnable r) {
if (this.cache.isValid()) {
new SwingWorker<List<String>, Object>() {
 
@Override
protected List<String> doInBackground() throws Exception {
return ISQLListModel.this.cache.loadCache();
}
 
@Override
protected void done() {
// don't remove and add from the cache, items just came from it
removeListDataListener(ISQLListModel.this.l);
removeAllElements();
try {
addAll(get());
} catch (Exception e1) {
// tant pis, pas de cache
e1.printStackTrace();
}
addListDataListener(ISQLListModel.this.l);
if (r != null)
r.run();
}
 
}.execute();
}
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/SQLRequestComboBox.java
39,6 → 39,7
 
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.beans.PropertyChangeEvent;
47,6 → 48,7
import java.util.List;
 
import javax.accessibility.Accessible;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
120,6 → 122,13
 
this.combo = new ISearchableCombo<IComboSelectionItem>(ComboLockedMode.LOCKED, 1, this.stringStuff.length());
this.combo.setIncludeEmpty(addUndefined);
this.combo.getActions().add(new AbstractAction("Recharger") {
@Override
public void actionPerformed(ActionEvent e) {
// ignore cache since a user explicitly asked for an update
fillCombo(null, false);
}
});
 
this.emptySupp = new EmptyChangeSupport(this);
this.supp = new ValueChangeSupport<Integer>(this);
138,7 → 147,10
@Override
public void init(SQLRowItemView v) {
final SQLTable foreignTable = v.getField().getDBSystemRoot().getGraph().getForeignTable(v.getField());
if (!this.hasModel())
this.uiInit(Configuration.getInstance().getDirectory().getElement(foreignTable).getComboRequest());
else if (this.getRequest().getPrimaryTable() != foreignTable)
throw new IllegalArgumentException("Tables are different " + getRequest().getPrimaryTable().getSQLName() + " != " + foreignTable.getSQLName());
}
 
/**
297,9 → 309,13
}
 
public synchronized final void fillCombo(final Runnable r) {
this.req.fillCombo(r);
this.fillCombo(r, true);
}
 
public synchronized final void fillCombo(final Runnable r, final boolean readCache) {
this.req.fillCombo(r, readCache);
}
 
// combo
 
public final List<IComboSelectionItem> getItems() {
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/itemview/VWRowItemView.java
65,7 → 65,7
 
/**
* The predicate testing whether the value is empty or not. This implementation returns
* {@link EmptyObjFromVO#DEFAULT_PREDICATE}
* {@link EmptyObjFromVO#getDefaultPredicate()}
*
* @return the predicate testing whether the value is empty.
*/
/trunk/OpenConcerto/src/org/openconcerto/utils/TimeUtils.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.utils;
 
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
 
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
 
public class TimeUtils {
static private DatatypeFactory typeFactory = null;
 
static public final DatatypeFactory getTypeFactory() {
if (typeFactory == null)
try {
typeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
throw new IllegalStateException(e);
}
return typeFactory;
}
 
/**
* Convert the time part of a calendar to a duration.
*
* @param cal a calendar, e.g. 23/12/2011 11:55:33.066 GMT+02.
* @return a duration, e.g. P0Y0M0DT11H55M33.066S.
*/
public final static Duration timePartToDuration(final Calendar cal) {
final BigDecimal seconds = BigDecimal.valueOf(cal.get(Calendar.SECOND)).add(BigDecimal.valueOf(cal.get(Calendar.MILLISECOND)).movePointLeft(3));
return getTypeFactory().newDuration(true, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, BigInteger.valueOf(cal.get(Calendar.HOUR_OF_DAY)), BigInteger.valueOf(cal.get(Calendar.MINUTE)),
seconds);
}
 
/**
* Normalize <code>cal</code> so that any Calendar with the same local time have the same
* result. If you don't need a Calendar this is faster than
* {@link #copyLocalTime(Calendar, Calendar)}.
*
* @param cal a calendar, e.g. 0:00 CEST.
* @return the time in millisecond of the UTC calendar with the same local time, e.g. 0:00 UTC.
*/
public final static long normalizeLocalTime(final Calendar cal) {
return cal.getTimeInMillis() + cal.getTimeZone().getOffset(cal.getTimeInMillis());
}
 
/**
* Copy the local time from one calendar to another. Except if both calendars have the same time
* zone, from.getTimeInMillis() will be different from to.getTimeInMillis().
*
* @param from the source calendar, e.g. 23/12/2011 11:55:33.066 GMT-12.
* @param to the destination calendar, e.g. 01/01/2000 0:00 GMT+13.
* @return the modified destination calendar, e.g. 23/12/2011 11:55:33.066 GMT+13.
*/
public final static Calendar copyLocalTime(final Calendar from, final Calendar to) {
to.clear();
for (final int field : new int[] { Calendar.YEAR, Calendar.DAY_OF_YEAR, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND }) {
to.set(field, from.get(field));
}
return to;
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/model/Reloadable.java
New file
0,0 → 1,18
/*
* 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.utils.model;
 
public interface Reloadable {
void reload();
}
/trunk/OpenConcerto/src/org/openconcerto/utils/checks/EmptyObjFromVO.java
18,8 → 18,6
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
 
import org.apache.commons.collections.Predicate;
 
/**
* Implement EmptyObj with a ValueObject and a predicate.
*
28,21 → 26,20
*/
public class EmptyObjFromVO<V> implements EmptyObj {
 
/**
* A predicate returning <code>true</code> if the passed object is <code>null</code> or the
* empty string.
*
* @param <T> type of the value object.
* @return a predicate returning <code>true</code> if the object is empty.
*/
@SuppressWarnings("unchecked")
public static final <T> IPredicate<T> getDefaultPredicate() {
return new IPredicate<T>() {
@Override
public boolean evaluateChecked(T input) {
return DEFAULT_PREDICATE.evaluate(input);
return (IPredicate<T>) DEFAULT_PREDICATE;
}
};
}
 
/**
* This predicate returns <code>true</code> if the passed object is <code>null</code> or the
* empty string.
*/
public static final Predicate DEFAULT_PREDICATE = new Predicate() {
public boolean evaluate(Object object) {
private static final IPredicate<Object> DEFAULT_PREDICATE = new IPredicate<Object>() {
public boolean evaluateChecked(Object object) {
if (object instanceof String)
return ((String) object).length() == 0;
else
/trunk/OpenConcerto/src/org/openconcerto/utils/ExceptionHandler.java
71,6 → 71,14
clipboard.setContents(data, data);
}
 
/**
* Display the passed message. Note: this method doesn't block.
*
* @param comp the modal parent of the error window.
* @param msg the message to display.
* @param originalExn the cause, can be <code>null</code>.
* @return an exception.
*/
static public RuntimeException handle(Component comp, String msg, Throwable originalExn) {
return new ExceptionHandler(comp, msg, originalExn, false);
}
83,6 → 91,14
return handle(msg, null);
}
 
/**
* Display the passed message and quit. Note: this method blocks until the user closes the
* window (then exits).
*
* @param msg the message to display.
* @param originalExn the cause, can be <code>null</code>.
* @return an exception.
*/
static public RuntimeException die(String msg, Throwable originalExn) {
return new ExceptionHandler(null, msg, originalExn);
}
115,14 → 131,25
if (!GraphicsEnvironment.isHeadless() || forceUI) {
if (SwingUtilities.isEventDispatchThread()) {
showMsg(msg, error);
} else
SwingUtilities.invokeLater(new Runnable() {
} else {
final Runnable run = new Runnable() {
public void run() {
showMsg(msg, error);
}
});
};
if (error) {
try {
SwingUtilities.invokeAndWait(run);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
} else {
SwingUtilities.invokeLater(run);
}
}
}
}
 
protected final void showMsg(final String msg, final boolean quit) {
final JPanel p = new JPanel();
/trunk/OpenConcerto/src/org/openconcerto/utils/FileUtils.java
354,6 → 354,14
}
}
 
public static void copyFile(File in, File out, final boolean useTime) throws IOException {
if (!useTime || in.lastModified() != out.lastModified()) {
copyFile(in, out);
if (useTime)
out.setLastModified(in.lastModified());
}
}
 
public static void copyDirectory(File in, File out) throws IOException {
copyDirectory(in, out, Collections.<String> emptySet());
}
361,6 → 369,10
public static final Set<String> VersionControl = CollectionUtils.createSet(".svn", "CVS");
 
public static void copyDirectory(File in, File out, final Set<String> toIgnore) throws IOException {
copyDirectory(in, out, toIgnore, false);
}
 
public static void copyDirectory(File in, File out, final Set<String> toIgnore, final boolean useTime) throws IOException {
if (toIgnore.contains(in.getName()))
return;
 
371,11 → 383,11
 
String[] children = in.list();
for (int i = 0; i < children.length; i++) {
copyDirectory(new File(in, children[i]), new File(out, children[i]), toIgnore);
copyDirectory(new File(in, children[i]), new File(out, children[i]), toIgnore, useTime);
}
} else {
if (!in.getName().equals("Thumbs.db")) {
copyFile(in, out);
copyFile(in, out, useTime);
}
}
}
404,6 → 416,21
return dir.delete();
}
 
public static void rm_R(File dir) throws IOException {
if (dir.isDirectory()) {
for (final File child : dir.listFiles()) {
rmR(child);
}
}
// The directory is now empty so delete it
rm(dir);
}
 
public static void rm(File f) throws IOException {
if (f.exists() && !f.delete())
throw new IOException("cannot delete " + f);
}
 
public static final File mkdir_p(File dir) throws IOException {
if (!dir.exists()) {
if (!dir.mkdirs()) {
/trunk/OpenConcerto/src/org/openconcerto/utils/CompareUtils.java
57,6 → 57,32
}
 
/**
* Compare two objects if they're numbers or comparable.
*
* @param o1 first object.
* @param o2 second object.
* @return a negative integer, zero, or a positive integer as o1 is less than, equal to, or
* greater than o2.
* @throws ClassCastException if o1 is neither a {@link Number} nor a {@link Comparable}, or if
* o2's type prevents it from being compared to o1.
* @throws NullPointerException if o1 or o2 is <code>null</code>.
* @see Comparable#compareTo(Object)
* @see NumberUtils#compare(Number, Number)
*/
static public final int compare(final Object o1, final Object o2) throws ClassCastException {
if (o1 == null || o2 == null)
throw new NullPointerException();
if (o1 instanceof Number && o2 instanceof Number) {
return NumberUtils.compare((Number) o1, (Number) o2);
} else {
// see Arrays.mergeSort()
@SuppressWarnings({ "rawtypes", "unchecked" })
final int res = ((Comparable) o1).compareTo(o2);
return res;
}
}
 
/**
* Renvoie un comparateur qui utilise successivement la liste passée tant que les objets sont
* égaux.
*
/trunk/OpenConcerto/src/org/openconcerto/utils/StringUtils.java
222,7 → 222,7
 
if (lastString.length() == nbCharMaxLine) {
int esp = lastString.lastIndexOf(" ");
if (result.length() > 0) {
if (result.length() > 0 && result.charAt(result.length() - 1) != '\n') {
result.append("\n");
}
if (esp > 0) {
232,6 → 232,7
result.append(lastString.toString().trim());
lastString = new StringBuffer();
}
result.append("\n");
}
 
char charAt = s.charAt(i);
240,12 → 241,11
result.append(lastString);
lastString = new StringBuffer();
} else {
 
lastString.append(charAt);
}
}
 
if (result.length() > 0) {
if (result.length() > 0 && result.charAt(result.length() - 1) != '\n') {
result.append("\n");
}
 
338,4 → 338,21
}
}
 
public static String rightAlign(String s, int width) {
String r = s;
int n = width - s.length();
for (int i = 0; i < n; i++) {
r = ' ' + r;
}
return r;
}
 
public static String leftAlign(String s, int width) {
String r = s;
int n = width - s.length();
for (int i = 0; i < n; i++) {
r += ' ';
}
return r;
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/MultipleOutputStream.java
New file
0,0 → 1,68
/*
* 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.utils;
 
import java.io.IOException;
import java.io.OutputStream;
 
public class MultipleOutputStream extends OutputStream {
private OutputStream[] streams;
 
/**
* OutputStream forwarding writes to multiple OutputStreams
* */
public MultipleOutputStream(OutputStream o1, OutputStream o2) {
this(new OutputStream[] { o1, o2 });
}
 
public MultipleOutputStream(OutputStream[] outputStreams) {
this.streams = outputStreams;
}
 
@Override
public void write(int b) throws IOException {
for (int i = 0; i < streams.length; i++) {
streams[i].write(b);
}
}
 
@Override
public void write(byte[] b) throws IOException {
for (int i = 0; i < streams.length; i++) {
streams[i].write(b);
}
}
 
@Override
public void write(byte[] b, int off, int len) throws IOException {
for (int i = 0; i < streams.length; i++) {
streams[i].write(b, off, len);
}
}
 
@Override
public void close() throws IOException {
for (int i = 0; i < streams.length; i++) {
streams[i].close();
}
}
 
@Override
public void flush() throws IOException {
for (int i = 0; i < streams.length; i++) {
streams[i].flush();
}
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/task/TodoListPanel.java
236,8 → 236,9
}
 
this.addButton = new JButton("Ajouter une tâche");
 
this.addButton.setOpaque(false);
this.removeButton = new JButton("Effacer");
this.removeButton.setOpaque(false);
this.removeButton.setEnabled(false);
this.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
263,6 → 264,7
c.anchor = GridBagConstraints.EAST;
c.gridx++;
JMenuBar b = new JMenuBar();
b.setOpaque(false);
b.setBorderPainted(false);
b.add(this.comboUser);
// Pour que le menu ne disparaisse pas quand on rapetisse trop la fenetre en bas
273,6 → 275,7
c.gridx++;
c.weightx = 1;
this.detailCheckBox = new JCheckBox("Affichage détaillé");
this.detailCheckBox.setOpaque(false);
this.detailCheckBox.setSelected(false);
this.add(this.detailCheckBox, c);
 
279,6 → 282,7
//
c.gridx++;
this.hideOldCheckBox = new JCheckBox("Masquer l'historique");
this.hideOldCheckBox.setOpaque(false);
this.hideOldCheckBox.setSelected(true);
this.add(this.hideOldCheckBox, c);
 
286,6 → 290,7
 
c.weightx = 0;
c.anchor = GridBagConstraints.EAST;
this.reloadPanel.setOpaque(false);
this.add(this.reloadPanel, c);
 
// Table
503,7 → 508,10
this.t.setBlockEventOnColumn(false);
this.t.setBlockRepaint(false);
this.t.getColumnModel().getColumn(1).setCellRenderer(this.iconRenderer);
 
// Better look
this.t.setShowHorizontalLines(false);
this.t.setGridColor(new Color(230, 230, 230));
this.t.setRowHeight(this.t.getRowHeight() + 4);
AlternateTableCellRenderer.UTILS.setAllColumns(this.t);
this.t.repaint();
 
/trunk/OpenConcerto/src/org/openconcerto/erp/panel/ListeFastPrintFrame.java
15,7 → 15,9
 
import org.openconcerto.erp.generationDoc.AbstractSheetXml;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.utils.ExceptionHandler;
 
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
45,7 → 47,7
private static final long serialVersionUID = -1653555706074122489L;
private final Class<? extends AbstractSheetXml> clazz;
private final JPanel panel;
private final List<SQLRow> liste;
private final List<SQLRowAccessor> liste;
private final JLabel operation = new JLabel("");
private final JProgressBar bar = new JProgressBar();
private final JSpinner spin;
54,7 → 56,7
 
private final JButton valid, cancel;
 
public ListeFastPrintFrame(final List<SQLRow> liste, final Class<? extends AbstractSheetXml> clazz) {
public ListeFastPrintFrame(final List<SQLRowAccessor> liste, final Class<? extends AbstractSheetXml> clazz) {
this.panel = new JPanel(new GridBagLayout());
this.liste = liste;
this.clazz = clazz;
61,9 → 63,9
final GridBagConstraints c = new DefaultGridBagConstraints();
// c.gridwidth = GridBagConstraints.REMAINDER;
// FIXME Add Preferences nombre de copies par defaut
final SQLRow row = this.liste.get(0);
final SQLRowAccessor row = this.liste.get(0);
 
final AbstractSheetXml bSheet = this.createAbstractSheet(row);
final AbstractSheetXml bSheet = this.createAbstractSheet(row.asRow());
if (this.liste.size() <= 1) {
this.panel.add(new JLabel("Lancer l'impression document"), c);
} else {
151,30 → 153,27
ListeFastPrintFrame.this.bar.setString("0/" + ListeFastPrintFrame.this.liste.size());
}
});
for (final SQLRow rowAt : ListeFastPrintFrame.this.liste) {
for (final SQLRowAccessor rowAt : ListeFastPrintFrame.this.liste) {
 
final AbstractSheetXml bSheet = ListeFastPrintFrame.this.createAbstractSheet(rowAt);
if (!bSheet.isFileODSExist()) {
final AbstractSheetXml bSheet = ListeFastPrintFrame.this.createAbstractSheet(rowAt.asRow());
if (!bSheet.getGeneratedFile().exists()) {
 
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ListeFastPrintFrame.this.operation.setText("Création du document " + bSheet.getFileName());
ListeFastPrintFrame.this.operation.setText("Création du document " + bSheet.getGeneratedFile());
}
});
bSheet.genere(false, false).get();
} catch (final InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
bSheet.createDocument();
bSheet.showPrintAndExportAsynchronous(false, false, true);
} catch (Exception e) {
ExceptionHandler.handle("Erreur lors de l'impression du document " + bSheet.getGeneratedFile());
}
}
 
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ListeFastPrintFrame.this.operation.setText("Impression du document " + bSheet.getFileName());
ListeFastPrintFrame.this.operation.setText("Impression du document " + bSheet.getGeneratedFile());
}
});
bSheet.fastPrintDocument(copies);
/trunk/OpenConcerto/src/org/openconcerto/erp/model/GestionChequesModel.java
437,8 → 437,8
Number idMvt = (Number) chqTmp.get("ID_MOUVEMENT");
 
if (mode == MODE_AVOIR) {
GenerationMvtReglementAvoirChequeClient gen = new GenerationMvtReglementAvoirChequeClient(idMvt.intValue(), Long.valueOf(chqTmp.get("MONTANT").toString()), d, id
.intValue());
GenerationMvtReglementAvoirChequeClient gen = new GenerationMvtReglementAvoirChequeClient(idMvt.intValue(), Long.valueOf(chqTmp.get("MONTANT").toString()), d,
id.intValue());
gen.genere();
} else {
if (mode == MODE_ACHAT) {
469,7 → 469,8
} else {
if (mode == MODE_VENTE) {
ReleveChequeSheet sheet = new ReleveChequeSheet(listeCheque, d);
sheet.genere(true, false);
sheet.createDocumentAsynchronous();
sheet.showPrintAndExportAsynchronous(true, false, true);
}
}
}
509,7 → 510,8
} else {
if (mode == MODE_VENTE) {
ReleveChequeSheet sheet = new ReleveChequeSheet(listeCheque, new Date(), true);
sheet.genere(true, false);
sheet.createDocumentAsynchronous();
sheet.showPrintAndExportAsynchronous(true, false, true);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/erp/model/MouseSheetXmlListeListener.java
16,7 → 16,6
import org.openconcerto.erp.config.ComptaPropsConfiguration;
import org.openconcerto.erp.generationDoc.AbstractSheetXml;
import org.openconcerto.erp.panel.ListeFastPrintFrame;
import org.openconcerto.erp.preferences.DefaultNXProps;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
30,11 → 29,8
import org.openconcerto.utils.ExceptionHandler;
 
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
42,9 → 38,8
import java.util.Set;
 
import javax.swing.AbstractAction;
import javax.swing.JPopupMenu;
 
public class MouseSheetXmlListeListener implements MouseListener {
public class MouseSheetXmlListeListener {
 
private Class<? extends AbstractSheetXml> clazz;
protected IListe liste;
62,14 → 57,13
private boolean printIsVisible = true;
private boolean generateIsVisible = true;
 
public MouseSheetXmlListeListener(IListe liste, Class<? extends AbstractSheetXml> clazz) {
this.clazz = clazz;
this.liste = liste;
public MouseSheetXmlListeListener(Class<? extends AbstractSheetXml> clazz) {
this(clazz, true, true, true, true);
 
}
 
public MouseSheetXmlListeListener(IListe liste, Class<? extends AbstractSheetXml> clazz, boolean show, boolean preview, boolean print, boolean generate) {
public MouseSheetXmlListeListener(Class<? extends AbstractSheetXml> clazz, boolean show, boolean preview, boolean print, boolean generate) {
this.clazz = clazz;
this.liste = liste;
this.printIsVisible = print;
this.previewIsVisible = preview;
this.showIsVisible = show;
76,6 → 70,7
this.generateIsVisible = generate;
}
 
// FIXME clear cache if row was updated
private static Map<SQLRow, AbstractSheetXml> cache = new HashMap<SQLRow, AbstractSheetXml>();
 
protected Class<? extends AbstractSheetXml> getSheetClass() {
91,72 → 86,65
AbstractSheetXml sheet = ctor.newInstance(row);
cache.put(row, sheet);
return sheet;
} catch (IllegalArgumentException e) {
} catch (Exception e) {
// FIXME Exception Handler ??
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
}
 
public void mouseClicked(MouseEvent e) {
}
// public void mouseReleased(MouseEvent e) {
// }
//
// public void mouseClicked(MouseEvent e) {
// }
//
// public void mouseEntered(MouseEvent e) {
// }
//
// public void mouseExited(MouseEvent e) {
// }
//
// public void mousePressed(MouseEvent e) {
//
// if (this.liste.getSelectedId() > 1) {
// if (e.getButton() == MouseEvent.BUTTON3) {
// System.err.println("Display Menu");
// JPopupMenu menuDroit = new JPopupMenu();
//
// final AbstractSheetXml bSheet =
// createAbstractSheet(this.liste.getSelectedRow());
// if (bSheet != null) {
//
// for (RowAction action : getRowActions()) {
// menuDroit.add(action.getAction());
// }
//
// menuDroit.pack();
// menuDroit.show(e.getComponent(), e.getPoint().x, e.getPoint().y);
// menuDroit.setVisible(true);
// }
// } else {
// String sfe =
// DefaultNXProps.getInstance().getStringProperty("ArticleSFE");
// Boolean bSfe = Boolean.valueOf(sfe);
// boolean isSFE = bSfe != null && bSfe.booleanValue();
// if (!isSFE) {
// if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() >= 2) {
// final AbstractSheetXml bSheet =
// createAbstractSheet(this.liste.getSelectedRow());
// if (!bSheet.getGeneratedFile().exists()) {
// bSheet.createDocumentAsynchronous();
// }
// bSheet.showPrintAndExportAsynchronous(true, false, false);
//
// }
// }
// }
// }
// }
 
public void mouseEntered(MouseEvent e) {
}
 
public void mouseExited(MouseEvent e) {
}
 
public void mousePressed(MouseEvent e) {
 
if (this.liste.getSelectedId() > 1) {
if (e.getButton() == MouseEvent.BUTTON3) {
System.err.println("Display Menu");
JPopupMenu menuDroit = new JPopupMenu();
 
final AbstractSheetXml bSheet = createAbstractSheet(this.liste.getSelectedRow());
if (bSheet != null) {
 
for (RowAction action : getRowActions()) {
menuDroit.add(action.getAction());
}
 
menuDroit.pack();
menuDroit.show(e.getComponent(), e.getPoint().x, e.getPoint().y);
menuDroit.setVisible(true);
}
} else {
String sfe = DefaultNXProps.getInstance().getStringProperty("ArticleSFE");
Boolean bSfe = Boolean.valueOf(sfe);
boolean isSFE = bSfe != null && bSfe.booleanValue();
if (!isSFE) {
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() >= 2) {
final AbstractSheetXml bSheet = createAbstractSheet(this.liste.getSelectedRow());
if (bSheet.isFileODSExist()) {
if (!Boolean.getBoolean("org.openconcerto.oo.useODSViewer")) {
bSheet.showDocument();
} else {
bSheet.showPreviewDocument();
}
} else {
bSheet.genere(true, false);
}
}
}
}
}
}
 
private void sendMail(final AbstractSheetXml sheet, final boolean readOnly) {
 
SQLRow row = sheet.getSQLRow();
194,18 → 182,12
final Thread t = new Thread() {
@Override
public void run() {
final File f = sheet.getFilePDF();
final File f = sheet.getGeneratedPDFFile();
if (!f.exists()) {
try {
sheet.genere(false, false).get();
// final Component doc =
// ComptaPropsConfiguration.getOOConnexion().loadDocument(sheet.getFileODS(),
// true);
 
// Future<File> pdf = doc.saveToPDF(f);
 
EmailComposer.getInstance().compose(adresseMail, subject + (subject.trim().length() == 0 ? "" : ", ") + sheet.getFilePDF().getName(), "",
sheet.getFilePDF().getAbsoluteFile());
sheet.getOrCreateDocumentFile();
sheet.showPrintAndExport(false, false, true);
EmailComposer.getInstance().compose(adresseMail, subject + (subject.trim().length() == 0 ? "" : ", ") + f.getName(), "", f.getAbsoluteFile());
} catch (Exception e) {
e.printStackTrace();
ExceptionHandler.handle("Impossible de charger le document PDF", e);
224,7 → 206,8
t.start();
} else {
try {
EmailComposer.getInstance().compose(adresseMail, subject + (subject.trim().length() == 0 ? "" : ", ") + sheet.getFileODS().getName(), "", sheet.getFileODS().getAbsoluteFile());
EmailComposer.getInstance().compose(adresseMail, subject + (subject.trim().length() == 0 ? "" : ", ") + sheet.getGeneratedFile().getName(), "",
sheet.getGeneratedFile().getAbsoluteFile());
} catch (Exception exn) {
ExceptionHandler.handle(null, "Impossible de créer le courriel", exn);
}
232,10 → 215,7
 
}
 
public void mouseReleased(MouseEvent e) {
}
 
public List<AbstractAction> addToMenu() {
public List<RowAction> addToMenu() {
return null;
}
 
266,46 → 246,44
if (this.showIsVisible) {
l.add(new RowAction(new AbstractAction(this.showString) {
public void actionPerformed(ActionEvent ev) {
createAbstractSheet(IListe.get(ev).getSelectedRow()).showDocument();
createAbstractSheet(IListe.get(ev).getSelectedRow()).openDocument(false);
}
 
}, false) {
@Override
public boolean enabledFor(IListeEvent evt) {
return createAbstractSheet(evt.getSelectedRow().asRow()).isFileODSExist();
return createAbstractSheet(evt.getSelectedRow().asRow()).getGeneratedFile().exists();
}
});
 
// if (bSheet.isFileOOExist()) {
// item.setFont(item.getFont().deriveFont(Font.BOLD));
// }
}
} else {
if (this.previewIsVisible) {
l.add(new RowAction(new AbstractAction(this.previewString) {
public void actionPerformed(ActionEvent ev) {
createAbstractSheet(liste.getSelectedRow()).showPreviewDocument();
try {
createAbstractSheet(IListe.get(ev).getSelectedRow()).showPreviewDocument();
} catch (Exception e) {
ExceptionHandler.handle("Impossilbe d'ouvrir le fichier", e);
}
}
 
}, false) {
 
@Override
public boolean enabledFor(List<SQLRowAccessor> selection) {
return createAbstractSheet(liste.getSelectedRow()).isFileODSExist();
public boolean enabledFor(IListeEvent evt) {
return createAbstractSheet(evt.getSelectedRow().asRow()).getGeneratedFile().exists();
}
});
// if (bSheet.isFileOOExist()) {
// item.setFont(item.getFont().deriveFont(Font.BOLD));
// }
 
}
}
 
// action supplémentaire
List<AbstractAction> list = addToMenu();
List<RowAction> list = addToMenu();
if (list != null) {
for (AbstractAction abstractAction : list) {
// JMenuItem itemItalic = new JMenuItem(abstractAction);
// itemItalic.setFont(itemItalic.getFont().deriveFont(Font.ITALIC));
l.add(new PredicateRowAction(abstractAction, false).setPredicate(IListeEvent.getNonEmptySelectionPredicate()));
for (RowAction rowAction : list) {
l.add(rowAction);
}
}
 
314,12 → 292,12
if (this.showIsVisible) {
l.add(new RowAction(new AbstractAction(this.showString) {
public void actionPerformed(ActionEvent ev) {
createAbstractSheet(liste.getSelectedRow()).showDocument();
createAbstractSheet(IListe.get(ev).getSelectedRow()).openDocument(false);
}
}, false) {
@Override
public boolean enabledFor(List<SQLRowAccessor> selection) {
return createAbstractSheet(liste.getSelectedRow()).isFileODSExist();
public boolean enabledFor(IListeEvent evt) {
return createAbstractSheet(evt.getSelectedRow().asRow()).getGeneratedFile().exists();
}
});
}
329,63 → 307,55
 
l.add(new RowAction(new AbstractAction(this.fastPrintString) {
public void actionPerformed(ActionEvent ev) {
createAbstractSheet(liste.getSelectedRow()).fastPrintDocument();
createAbstractSheet(IListe.get(ev).getSelectedRow()).fastPrintDocument();
}
}, false) {
@Override
public boolean enabledFor(List<SQLRowAccessor> selection) {
return createAbstractSheet(liste.getSelectedRow()).isFileODSExist();
public boolean enabledFor(IListeEvent evt) {
return createAbstractSheet(evt.getSelectedRow().asRow()).getGeneratedFile().exists();
}
});
 
l.add(new RowAction(new AbstractAction(this.printString) {
public void actionPerformed(ActionEvent ev) {
createAbstractSheet(liste.getSelectedRow()).printDocument();
createAbstractSheet(IListe.get(ev).getSelectedRow()).printDocument();
}
}, false) {
@Override
public boolean enabledFor(List<SQLRowAccessor> selection) {
return createAbstractSheet(liste.getSelectedRow()).isFileODSExist();
public boolean enabledFor(IListeEvent evt) {
return createAbstractSheet(evt.getSelectedRow().asRow()).getGeneratedFile().exists();
}
});
 
if (this.liste.getSelection().getSelectedIDs().size() > 1) {
l.add(new RowAction(new AbstractAction(this.printAllString) {
PredicateRowAction rowAction = new PredicateRowAction(new AbstractAction(this.printAllString) {
@Override
public void actionPerformed(ActionEvent e) {
 
final int[] l = liste.getJTable().getSelectedRows();
final List<SQLRow> list = new ArrayList<SQLRow>();
 
for (int i = 0; i < l.length; i++) {
list.add(liste.getModel().getTable().getRow(liste.getLine(l[i]).getRow().getID()));
}
 
// final int[] l = liste.getJTable().getSelectedRows();
// final List<SQLRow> list = new ArrayList<SQLRow>();
List<SQLRowAccessor> list = IListe.get(e).getSelectedRows();
ListeFastPrintFrame frame = new ListeFastPrintFrame(list, clazz);
frame.setVisible(true);
}
}, false) {
@Override
public boolean enabledFor(List<SQLRowAccessor> selection) {
return createAbstractSheet(liste.getSelectedRow()).isFileODSExist();
}
});
}, false);
rowAction.setPredicate(IListeEvent.createSelectionCountPredicate(2, Integer.MAX_VALUE));
 
}
l.add(rowAction);
 
}
 
if (this.showIsVisible) {
 
// if (createAbstractSheet(liste.getSelectedRow()).getSQLRow() != null) {
// if (createAbstractSheet(liste.getSelectedRow()).getSQLRow() !=
// null) {
l.add(new RowAction(new AbstractAction(this.mailPDFString) {
public void actionPerformed(ActionEvent ev) {
sendMail(createAbstractSheet(liste.getSelectedRow()), true);
sendMail(createAbstractSheet(IListe.get(ev).getSelectedRow()), true);
}
}, false) {
@Override
public boolean enabledFor(List<SQLRowAccessor> selection) {
return createAbstractSheet(liste.getSelectedRow()).isFileODSExist();
public boolean enabledFor(IListeEvent evt) {
return createAbstractSheet(evt.getSelectedRow().asRow()).getGeneratedFile().exists();
}
});
 
392,12 → 362,12
// }
l.add(new RowAction(new AbstractAction(this.mailString) {
public void actionPerformed(ActionEvent ev) {
sendMail(createAbstractSheet(liste.getSelectedRow()), false);
sendMail(createAbstractSheet(IListe.get(ev).getSelectedRow()), false);
}
}, false) {
@Override
public boolean enabledFor(List<SQLRowAccessor> selection) {
return createAbstractSheet(liste.getSelectedRow()).isFileODSExist();
public boolean enabledFor(IListeEvent evt) {
return createAbstractSheet(evt.getSelectedRow().asRow()).getGeneratedFile().exists();
}
});
 
406,7 → 376,9
l.add(new RowAction(new AbstractAction(this.generateString) {
public void actionPerformed(ActionEvent ev) {
 
createAbstractSheet(liste.getSelectedRow()).genere(true, false);
final AbstractSheetXml sheet = createAbstractSheet(IListe.get(ev).getSelectedRow());
sheet.createDocumentAsynchronous();
sheet.showPrintAndExportAsynchronous(true, false, true);
}
}, false) {
@Override
/trunk/OpenConcerto/src/org/openconcerto/erp/utils/NXDatabaseAccessor.java
21,12 → 21,13
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
 
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
 
 
// TODO use the one from Nego
public class NXDatabaseAccessor implements DatabaseAccessor {
@SuppressWarnings("unchecked")
public List<Ville> read() {
63,6 → 64,12
// TODO Auto-generated catch block
e.printStackTrace();
}
}
 
@Override
public void delete(Ville v) {
SQLTable villeT = Configuration.getInstance().getBase().getTable("VILLE");
final Where w = new Where(villeT.getField("NOM"), "=", v.getName()).and(new Where(villeT.getField("CODE_POSTAL"), "=", v.getCodepostal()));
villeT.getDBSystemRoot().getDataSource().execute("DELETE FROM " + villeT.getSQLName().quote() + " WHERE " + w);
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/importer/CentsValueConverter.java
14,6 → 14,7
package org.openconcerto.erp.importer;
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.utils.GestionDevise;
 
public class CentsValueConverter extends ValueConverter {
public CentsValueConverter(SQLField f) {
24,18 → 25,7
long result = 0;
if (obj != null) {
try {
 
String string = obj.toString();
int index = string.indexOf(".");
if (index >= 0) {
string = string.substring(0, index) + string.substring(index + 1, string.length());
} else {
index = string.indexOf(",");
if (index >= 0) {
string = string.substring(0, index) + string.substring(index + 1, string.length());
}
}
result = Integer.parseInt(string);
return GestionDevise.parseLongCurrency(obj.toString());
} catch (Exception e) {
e.printStackTrace();
}
/trunk/OpenConcerto/src/org/openconcerto/erp/preferences/AbstractGenerationDocumentPreferencePanel.java
13,6 → 13,7
package org.openconcerto.erp.preferences;
 
import org.openconcerto.erp.generationDoc.DocumentLocalStorageManager;
import org.openconcerto.erp.generationDoc.SheetXml;
import org.openconcerto.erp.utils.FileUtility;
import org.openconcerto.ui.DefaultGridBagConstraints;
238,7 → 239,7
final Color foregroundColor = UIManager.getColor("TextField.foreground");
 
for (final Entry<String, JTextField> entry : this.mapKeyTextOO.entrySet()) {
final File f = new File(SheetXml.getLocationForTuple(Tuple2.create("GetDefault", this.mapKeyLabel.get(entry.getKey())), false));
final File f = DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory("Default");
final JTextField textField = entry.getValue();
if (f.exists()) {
textField.setForeground(foregroundColor);
254,7 → 255,7
}
 
for (final Entry<String, JTextField> entry : this.mapKeyTextPDF.entrySet()) {
final File f = new File(SheetXml.getLocationForTuple(Tuple2.create("GetDefault", this.mapKeyLabel.get(entry.getKey())), true));
final File f = DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory("Default");
final JTextField textField = entry.getValue();
if (f.exists()) {
textField.setForeground(foregroundColor);
264,7 → 265,6
try {
textField.setText(f.getCanonicalPath());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
281,8 → 281,14
 
private void updateTextFields(final Map<String, JTextField> map, final String format) throws IOException {
final Color foregroundColor = UIManager.getColor("TextField.foreground");
final DocumentLocalStorageManager storage = DocumentLocalStorageManager.getInstance();
for (final Entry<String, JTextField> entry : map.entrySet()) {
final File f = new File(SheetXml.getLocationForTuple(Tuple2.create(entry.getKey(), this.mapKeyLabel.get(entry.getKey())), format.equalsIgnoreCase("PDF")));
final File f;
if (format.equalsIgnoreCase("PDF")) {
f = storage.getPDFOutputDirectory(entry.getKey());
} else {
f = storage.getDocumentOutputDirectory(entry.getKey());
}
final JTextField textField = entry.getValue();
if (f.exists()) {
textField.setForeground(foregroundColor);
/trunk/OpenConcerto/src/org/openconcerto/erp/preferences/TemplateNXProps.java
14,6 → 14,35
package org.openconcerto.erp.preferences;
 
import org.openconcerto.erp.config.ComptaPropsConfiguration;
import org.openconcerto.erp.core.customerrelationship.customer.report.FicheClientXmlSheet;
import org.openconcerto.erp.core.finance.accounting.report.BalanceSheet;
import org.openconcerto.erp.core.finance.accounting.report.GrandLivreSheet;
import org.openconcerto.erp.core.finance.accounting.report.JournauxSheet;
import org.openconcerto.erp.core.humanresources.payroll.report.EtatChargesPayeSheet;
import org.openconcerto.erp.core.humanresources.payroll.report.FichePayeSheet;
import org.openconcerto.erp.core.humanresources.payroll.report.LivrePayeSheet;
import org.openconcerto.erp.core.sales.invoice.report.ListeFactureXmlSheet;
import org.openconcerto.erp.core.sales.invoice.report.ListeVenteXmlSheet;
import org.openconcerto.erp.core.sales.invoice.report.VenteComptoirSheet;
import org.openconcerto.erp.core.sales.invoice.report.VenteFactureXmlSheet;
import org.openconcerto.erp.core.sales.order.report.CommandeClientXmlSheet;
import org.openconcerto.erp.core.sales.quote.report.DevisXmlSheet;
import org.openconcerto.erp.core.sales.shipment.report.BonLivraisonXmlSheet;
import org.openconcerto.erp.generationDoc.DefaultLocalTemplateProvider;
import org.openconcerto.erp.generationDoc.DocumentLocalStorageManager;
import org.openconcerto.erp.generationDoc.SheetXml;
import org.openconcerto.erp.generationDoc.TemplateManager;
import org.openconcerto.erp.generationDoc.TemplateProvider;
import org.openconcerto.erp.generationDoc.gestcomm.AvoirClientXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.AvoirFournisseurXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.CommandeXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.CourrierClientSheet;
import org.openconcerto.erp.generationDoc.gestcomm.EtatVentesXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.FicheRelanceSheet;
import org.openconcerto.erp.generationDoc.gestcomm.PointageXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.RelanceSheet;
import org.openconcerto.erp.generationDoc.gestcomm.ReleveChequeEmisSheet;
import org.openconcerto.erp.generationDoc.gestcomm.ReleveChequeSheet;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.task.config.ComptaBasePropsConfiguration;
79,10 → 108,87
return conf.getWD().getAbsolutePath() + File.separator + rowSociete.getString("NOM") + "-" + rowSociete.getID();
}
 
public void initDocumentLocalStorage() {
final DocumentLocalStorageManager storage = DocumentLocalStorageManager.getInstance();
String propertyDefaultDirectory = getProperty(SheetXml.DEFAULT_PROPERTY_NAME + "OO");
if (propertyDefaultDirectory == null) {
System.out.println("Warning: no default directory stored for document output");
propertyDefaultDirectory = getDefaultStringValue();
}
storage.setDocumentDefaultDirectory(new File(propertyDefaultDirectory));
String propertyDefaultPDFDirectory = getProperty(SheetXml.DEFAULT_PROPERTY_NAME + "PDF");
if (propertyDefaultPDFDirectory == null) {
System.out.println("Warning: no default directory stored for PFD output");
propertyDefaultPDFDirectory = propertyDefaultDirectory;
}
 
storage.setPDFDefaultDirectory(new File(propertyDefaultPDFDirectory));
register(DevisXmlSheet.TEMPLATE_ID, DevisXmlSheet.TEMPLATE_PROPERTY_NAME);
register(VenteFactureXmlSheet.TEMPLATE_ID, VenteFactureXmlSheet.TEMPLATE_PROPERTY_NAME);
register(CommandeClientXmlSheet.TEMPLATE_ID, CommandeClientXmlSheet.TEMPLATE_PROPERTY_NAME);
register(BonLivraisonXmlSheet.TEMPLATE_ID, BonLivraisonXmlSheet.TEMPLATE_PROPERTY_NAME);
register(AvoirClientXmlSheet.TEMPLATE_ID, AvoirClientXmlSheet.TEMPLATE_PROPERTY_NAME);
register(AvoirFournisseurXmlSheet.TEMPLATE_ID, AvoirFournisseurXmlSheet.TEMPLATE_PROPERTY_NAME);
register(CommandeXmlSheet.TEMPLATE_ID, CommandeXmlSheet.TEMPLATE_PROPERTY_NAME);
register(EtatVentesXmlSheet.TEMPLATE_ID, EtatVentesXmlSheet.TEMPLATE_PROPERTY_NAME);
register(FicheClientXmlSheet.TEMPLATE_ID, FicheClientXmlSheet.TEMPLATE_PROPERTY_NAME);
register(FicheRelanceSheet.TEMPLATE_ID, FicheRelanceSheet.TEMPLATE_PROPERTY_NAME);
register(ReleveChequeSheet.TEMPLATE_ID, ReleveChequeSheet.TEMPLATE_PROPERTY_NAME);
register(ListeFactureXmlSheet.TEMPLATE_ID, ListeFactureXmlSheet.TEMPLATE_PROPERTY_NAME);
register(ListeVenteXmlSheet.TEMPLATE_ID, ListeVenteXmlSheet.TEMPLATE_PROPERTY_NAME);
register(BalanceSheet.TEMPLATE_ID, BalanceSheet.TEMPLATE_PROPERTY_NAME);
register(GrandLivreSheet.TEMPLATE_ID, GrandLivreSheet.TEMPLATE_PROPERTY_NAME);
register(JournauxSheet.TEMPLATE_ID, JournauxSheet.TEMPLATE_PROPERTY_NAME);
register(EtatChargesPayeSheet.TEMPLATE_ID, EtatChargesPayeSheet.TEMPLATE_PROPERTY_NAME);
register(FichePayeSheet.TEMPLATE_ID, FichePayeSheet.TEMPLATE_PROPERTY_NAME);
register(LivrePayeSheet.TEMPLATE_ID, LivrePayeSheet.TEMPLATE_PROPERTY_NAME);
register(CourrierClientSheet.TEMPLATE_ID, CourrierClientSheet.TEMPLATE_PROPERTY_NAME);
register(PointageXmlSheet.TEMPLATE_ID, PointageXmlSheet.TEMPLATE_PROPERTY_NAME);
register(RelanceSheet.TEMPLATE_ID, RelanceSheet.TEMPLATE_PROPERTY_NAME);
register(VenteComptoirSheet.TEMPLATE_ID, VenteComptoirSheet.TEMPLATE_PROPERTY_NAME);
register(ReleveChequeEmisSheet.TEMPLATE_ID, ReleveChequeEmisSheet.TEMPLATE_PROPERTY_NAME);
storage.dump();
 
}
 
private void register(String templateId, String propertyBaseName) {
if (templateId == null) {
throw new IllegalArgumentException("null template id");
}
if (propertyBaseName == null) {
throw new IllegalArgumentException("null propertyBaseName");
}
if (TemplateManager.getInstance().isKnwonTemplate(templateId)) {
System.err.println("Warning: registering known template id : " + templateId + " with property base name: " + propertyBaseName);
}
final DocumentLocalStorageManager storage = DocumentLocalStorageManager.getInstance();
final String propertyOO = getProperty(propertyBaseName + "OO");
if (propertyOO != null) {
 
storage.addDocumentDirectory(templateId, new File(propertyOO));
}
final String propertyPDF = getProperty(propertyBaseName + "PDF");
if (propertyPDF != null) {
storage.addPDFDirectory(templateId, new File(propertyPDF));
}
}
 
synchronized public static TemplateProps getInstance() {
if (instance == null) {
instance = new TemplateNXProps();
((TemplateNXProps) instance).initDocumentLocalStorage();
((TemplateNXProps) instance).initDefaulTemplateProvider();
}
return instance;
}
 
private void initDefaulTemplateProvider() {
final String property = getProperty("LocationTemplate");
final DefaultLocalTemplateProvider provider = new DefaultLocalTemplateProvider();
if (property != null) {
provider.setBaseDirectory(new File(property));
}
TemplateManager.getInstance().setDefaultProvider(provider);
TemplateManager.getInstance().dump();
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/preferences/GenerationDocumentGestCommPreferencePanel.java
17,7 → 17,6
import org.openconcerto.erp.core.sales.order.report.CommandeClientXmlSheet;
import org.openconcerto.erp.core.sales.quote.report.DevisXmlSheet;
import org.openconcerto.erp.core.sales.shipment.report.BonLivraisonXmlSheet;
import org.openconcerto.erp.generationDoc.SheetXml;
import org.openconcerto.erp.generationDoc.gestcomm.AvoirClientXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.AvoirFournisseurXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.CommandeXmlSheet;
25,27 → 24,35
import org.openconcerto.erp.generationDoc.gestcomm.RelanceSheet;
import org.openconcerto.erp.generationDoc.gestcomm.ReleveChequeEmisSheet;
import org.openconcerto.erp.generationDoc.gestcomm.ReleveChequeSheet;
import org.openconcerto.sql.Configuration;
import org.openconcerto.utils.StringUtils;
 
public class GenerationDocumentGestCommPreferencePanel extends AbstractGenerationDocumentPreferencePanel {
 
public GenerationDocumentGestCommPreferencePanel() {
super();
this.mapKeyLabel.put(DevisXmlSheet.getTuple2Location().get0(), DevisXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(AvoirClientXmlSheet.getTuple2Location().get0(), AvoirClientXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(DevisXmlSheet.getTuple2Location().get0(), DevisXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(BonLivraisonXmlSheet.getTuple2Location().get0(), BonLivraisonXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(VenteFactureXmlSheet.getTuple2Location().get0(), VenteFactureXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(RelanceSheet.getTuple2Location().get0(), RelanceSheet.getTuple2Location().get1());
this.mapKeyLabel.put(CommandeXmlSheet.getTuple2Location().get0(), CommandeXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(CommandeClientXmlSheet.getTuple2Location().get0(), CommandeClientXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(AvoirFournisseurXmlSheet.getTuple2Location().get0(), AvoirFournisseurXmlSheet.getTuple2Location().get1());
this.mapKeyLabel.put(CourrierClientSheet.getTuple2Location().get0(), CourrierClientSheet.getTuple2Location().get1());
this.mapKeyLabel.put(ReleveChequeEmisSheet.getTuple2Location().get0(), ReleveChequeEmisSheet.getTuple2Location().get1());
this.mapKeyLabel.put(ReleveChequeSheet.getTuple2Location().get0(), ReleveChequeSheet.getTuple2Location().get1());
this.mapKeyLabel.put(SheetXml.tupleDefault.get0(), SheetXml.tupleDefault.get1());
this.mapKeyLabel.put(DevisXmlSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("DEVIS"));
this.mapKeyLabel.put(AvoirClientXmlSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("AVOIR_CLIENT"));
 
this.mapKeyLabel.put(BonLivraisonXmlSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("BON_DE_LIVRAISON"));
this.mapKeyLabel.put(VenteFactureXmlSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("SAISIE_VENTE_FACTURE"));
this.mapKeyLabel.put(RelanceSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("RELANCE"));
this.mapKeyLabel.put(CommandeXmlSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("COMMANDE"));
this.mapKeyLabel.put(CommandeClientXmlSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("COMMANDE_CLIENT"));
this.mapKeyLabel.put(AvoirFournisseurXmlSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("AVOIR_FOURNISSEUR"));
this.mapKeyLabel.put(CourrierClientSheet.TEMPLATE_PROPERTY_NAME, getLabelFromTable("COURRIER_CLIENT"));
this.mapKeyLabel.put(ReleveChequeEmisSheet.TEMPLATE_PROPERTY_NAME, "Relevé chèque émis");
this.mapKeyLabel.put(ReleveChequeSheet.TEMPLATE_PROPERTY_NAME, "Relevé chèque");
// this.mapKeyLabel.put(SheetXml.tupleDefault.get0(), SheetXml.tupleDefault.get1());
// uiInit();
}
 
private String getLabelFromTable(String tableName) {
String pluralName = Configuration.getInstance().getDirectory().getElement(tableName).getPluralName();
pluralName = StringUtils.firstUp(pluralName);
return pluralName;
}
 
public String getTitleName() {
return "Destination des documents générés";
}
/trunk/OpenConcerto/src/org/openconcerto/erp/preferences/GenerationDocumentComptaPreferencePanel.java
21,9 → 21,9
 
public GenerationDocumentComptaPreferencePanel() {
super();
this.mapKeyLabel.put(GrandLivreSheet.getTuple2Location().get0(), GrandLivreSheet.getTuple2Location().get1());
this.mapKeyLabel.put(JournauxSheet.getTuple2Location().get0(), JournauxSheet.getTuple2Location().get1());
this.mapKeyLabel.put(BalanceSheet.getTuple2Location().get0(), BalanceSheet.getTuple2Location().get1());
this.mapKeyLabel.put(GrandLivreSheet.TEMPLATE_PROPERTY_NAME, GrandLivreSheet.TEMPLATE_ID);
this.mapKeyLabel.put(JournauxSheet.TEMPLATE_PROPERTY_NAME, JournauxSheet.TEMPLATE_ID);
this.mapKeyLabel.put(BalanceSheet.TEMPLATE_PROPERTY_NAME, BalanceSheet.TEMPLATE_ID);
// uiInit();
}
 
/trunk/OpenConcerto/src/org/openconcerto/erp/preferences/DefaultLocalPreferencePanel.java
22,6 → 22,7
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
 
public class DefaultLocalPreferencePanel extends DefaultPreferencePanel {
71,4 → 72,14
public static File getPrefFile(String fileName) {
return new File(Configuration.getInstance().getConfDir(), "/Configuration/" + fileName);
}
 
public static Properties getPropertiesFromFile(String fileName) throws IOException {
final Properties properties = new Properties();
if (getPrefFile(fileName).exists()) {
FileInputStream fIp = new FileInputStream(getPrefFile(fileName));
properties.load(new BufferedInputStream(fIp));
fIp.close();
}
return properties;
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/storage/StorageEngines.java
New file
0,0 → 1,40
/*
* 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.erp.storage;
 
import java.util.ArrayList;
import java.util.List;
 
public class StorageEngines {
private static final StorageEngines instance = new StorageEngines();
 
public static StorageEngines getInstance() {
return instance;
}
 
private List<StorageEngine> engines = new ArrayList<StorageEngine>();
 
public List<StorageEngine> getActiveEngines() {
// TODO use a map to store active engines;
return engines;
}
 
public void addEngine(StorageEngine e) {
engines.add(e);
}
 
public void removeEngine(StorageEngine e) {
engines.remove(e);
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/storage/StorageEngine.java
New file
0,0 → 1,30
/*
* 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.erp.storage;
 
import java.io.IOException;
import java.io.InputStream;
 
public interface StorageEngine {
public boolean isConfigured();
 
public boolean allowAutoStorage();
 
public void connect() throws IOException;
 
public void disconnect() throws IOException;
 
public void store(final InputStream inStream, String remotePath, String title, boolean synchronous) throws IOException;
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/config/ServerFinderConfig.java
31,6 → 31,7
private String ip;
private File file;
private String port;
private String systemRoot = "OpenConcerto";
 
private String dbLogin;
private String dbPassword;
40,6 → 41,14
private String product;
private String error;
 
public String getSystemRoot() {
return systemRoot;
}
 
public void setSystemRoot(String systemRoot) {
this.systemRoot = systemRoot;
}
 
public String getType() {
return type;
}
245,4 → 254,5
return this.getType() + ":" + this.getIp() + ":" + this.getPort() + " file:" + this.getFile() + " " + this.getOpenconcertoLogin() + "/" + this.getOpenconcertoPassword() + " ["
+ this.getDbLogin() + "/" + this.getDbPassword() + "]";
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/config/ComptaPropsConfiguration.java
100,6 → 100,7
import org.openconcerto.erp.core.sales.invoice.element.EcheanceClientSQLElement;
import org.openconcerto.erp.core.sales.invoice.element.SaisieVenteFactureItemSQLElement;
import org.openconcerto.erp.core.sales.invoice.element.SaisieVenteFactureSQLElement;
import org.openconcerto.erp.core.sales.invoice.ui.SaisieVenteFactureItemTable;
import org.openconcerto.erp.core.sales.order.element.CommandeClientElementSQLElement;
import org.openconcerto.erp.core.sales.order.element.CommandeClientSQLElement;
import org.openconcerto.erp.core.sales.pos.element.CaisseTicketSQLElement;
148,10 → 149,12
import org.openconcerto.erp.injector.FactureBonSQLInjector;
import org.openconcerto.erp.injector.FactureCommandeSQLInjector;
import org.openconcerto.erp.preferences.DefaultNXProps;
import org.openconcerto.erp.preferences.TemplateNXProps;
import org.openconcerto.erp.rights.ComptaTotalUserRight;
import org.jopendocument.link.OOConnexion;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.ShowAs;
import org.openconcerto.sql.element.ElementMapper;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.element.SharedSQLElement;
import org.openconcerto.sql.model.DBRoot;
341,7 → 344,7
@Override
protected String getAppIDSuffix() {
if (inWebstart())
// so we don't remove files of a normal OpenConcerto
// so we don't remove files of a normal GestionNX
return super.getAppIDSuffix() + "-webstart";
else
return super.getAppIDSuffix();
403,7 → 406,6
 
@Override
protected ShowAs createShowAs() {
System.out.println("ComptaPropsConfiguration.createShowAszzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz()");
final ShowAs showAs = super.createShowAs();
 
showAs.show("ADRESSE_COMMON", SQLRow.toList("RUE,VILLE"));
731,6 → 733,7
}
setSocieteShowAs();
setSocieteSQLInjector();
setMapper();
String sfe = DefaultNXProps.getInstance().getStringProperty("ArticleSFE");
Boolean bSfe = Boolean.valueOf(sfe);
boolean isSFE = bSfe != null && bSfe.booleanValue();
740,7 → 743,7
trans.load(rootSociete, inSFE);
}
}
 
TemplateNXProps.getInstance();
// Chargement du graphe
new Thread() {
public void run() {
750,6 → 753,21
}.start();
}
 
private void setMapper() {
ElementMapper mapper = ElementMapper.getInstance();
mapper.map("customerrelationship.name", "Relation client");
mapper.map("customerrelationship.customer.list.table", "CLIENT");
mapper.map("customerrelationship.contact.list.table", "CONTACT");
mapper.map("accounting.name", "Comptabilité");
mapper.map("sales.name", "Gestion commerciale");
mapper.map("sales.invoice.name", "Factures client");
mapper.map("sales.invoice.list.table", "SAISIE_VENTE_FACTURE");
mapper.map("sales.quote.name", "Devis client");
mapper.map("sales.quote.list.table", "DEVIS");
mapper.map("sales.invoice.list.table.editor", SaisieVenteFactureItemTable.class);
mapper.dump();
}
 
private void closeSocieteConnexion() {
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/config/MainFrame.java
59,6 → 59,8
import org.openconcerto.erp.core.humanresources.employe.action.ListeDesCommerciauxAction;
import org.openconcerto.erp.core.humanresources.employe.action.ListeDesSalariesAction;
import org.openconcerto.erp.core.humanresources.employe.action.ListeDesSecretairesAction;
import org.openconcerto.erp.core.humanresources.employe.action.N4DSAction;
import org.openconcerto.erp.core.humanresources.employe.report.N4DS;
import org.openconcerto.erp.core.humanresources.payroll.action.ClotureMensuellePayeAction;
import org.openconcerto.erp.core.humanresources.payroll.action.EditionFichePayeAction;
import org.openconcerto.erp.core.humanresources.payroll.action.ListeDesProfilsPayeAction;
456,6 → 458,7
menu.add(new JSeparator());
menu.add(new EtatChargeAction());
menu.add(new CompteResultatBilanAction());
menu.add(new N4DSAction());
if (rights.haveRight(ComptaUserRight.MENU)) {
result.add(menu);
}
/trunk/OpenConcerto/src/org/openconcerto/erp/config/Gestion.java
37,7 → 37,6
import org.openconcerto.ui.component.WaitIndeterminatePanel;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.protocol.Helper;
 
import java.awt.AWTEvent;
71,6 → 70,8
 
public class Gestion {
 
public static final File MODULES_DIR = new File("Modules");
 
/**
* When this system property is set to <code>true</code>, Gestion will hide most of its normal
* UI. E.g. no SOCIETE selection in the login panel, minimalist menu bar, etc.
212,17 → 213,7
} catch (Exception e) {
System.out.println("Init phase 1 error:" + (System.currentTimeMillis() - t4) + "ms");
ExceptionHandler.die("Erreur de connexion à la base de données", e);
// since we're not in the EDT, the previous call doesn't block,
// so return (it won't quit the VM since a dialog is displaying)
return;
}
try {
final File moduleDir = new File("Modules");
moduleDir.mkdir();
ModuleManager.getInstance().addFactories(moduleDir);
} catch (Throwable e) {
ExceptionHandler.handle("Erreur d'accès aux modules", e);
}
System.out.println("Init phase 1:" + (System.currentTimeMillis() - t1) + "ms");
SwingUtilities.invokeLater(new Runnable() {
 
283,21 → 274,12
 
// needed so that we can uninstall modules
System.setProperty(SQLBase.ALLOW_OBJECT_REMOVAL, "true");
ModuleManager.getInstance().invoke(new IClosure<ModuleManager>() {
@Override
public void executeChecked(ModuleManager mngr) {
try {
final Exception exn = mngr.setup();
// OK to continue without all modules started
if (exn != null)
ExceptionHandler.handle(MainFrame.getInstance(), "Impossible de démarrer les modules", exn);
} catch (Exception e) {
// not OK to continue without required elements
ExceptionHandler.die("Impossible de démarrer les modules requis", e);
ModuleManager.getInstance().addFactories(MODULES_DIR);
} catch (Throwable e) {
ExceptionHandler.handle("Erreur d'accès aux modules", e);
}
}
});
}
 
/**
* Si la base est 127.0.0.1 ou localhost alors on essaye de lancer postgres.
/trunk/OpenConcerto/src/org/openconcerto/erp/config/ServerFinderPanel.java
73,6 → 73,7
private JTextField textIP;
private JTextField textPort;
private JTextField textFile;
private JTextField textBase;
Properties props;
private JButton buttonDir;
private JTabbedPane tabbedPane;
146,6 → 147,7
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Impossible de lire le fichier " + this.confFile + " \n" + e.getLocalizedMessage());
}
 
String serverIp = this.props.getProperty("server.ip", "127.0.0.1:5432");
String serverDriver = this.props.getProperty("server.driver", "postgresql").toLowerCase();
if (serverDriver.startsWith("h2")) {
158,7 → 160,7
updateUIForMode(ServerFinderConfig.POSTGRESQL);
this.textPort.setText("5432");
}
 
this.textBase.setText(this.props.getProperty("systemRoot", "OpenConcerto"));
if (serverIp.contains("file:")) {
this.textFile.setText(serverIp.substring(5));
} else {
405,6 → 407,19
c.gridy++;
c.gridx = 0;
c.weightx = 0;
p.add(new JLabel("Base de données", SwingConstants.RIGHT), c);
c.gridx++;
c.weighty = 0;
c.gridwidth = 1;
this.textBase = new JTextField();
this.textBase.setEditable(false);
p.add(this.textBase, c);
 
// L4: file
c.gridy++;
c.gridx = 0;
c.weightx = 0;
c.gridwidth = 1;
p.add(new JLabel("Dossier de base de données", SwingConstants.RIGHT), c);
c.gridx++;
c.weighty = 0;
648,18 → 663,12
return p;
}
 
private JPanel createPanelInstallation() {
final JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
p.setOpaque(false);
return p;
}
 
public ServerFinderConfig getServerConfig() {
final ServerFinderConfig conf = new ServerFinderConfig();
conf.setType(ServerFinderPanel.this.comboMode.getSelectedItem().toString());
conf.setIp(ServerFinderPanel.this.textIP.getText());
conf.setPort(ServerFinderPanel.this.textPort.getText());
conf.setSystemRoot(this.textBase.getText());
return conf;
}
 
690,6 → 699,7
public ServerFinderConfig createServerFinderConfig() {
ServerFinderConfig conf = new ServerFinderConfig();
conf.setType(this.comboMode.getSelectedItem().toString());
conf.setSystemRoot(this.textBase.getText());
if (!conf.getType().equals(ServerFinderConfig.H2)) {
conf.setIp(this.textIP.getText());
conf.setPort(this.textPort.getText());
/trunk/OpenConcerto/src/org/openconcerto/erp/config/InstallationPanel.java
228,6 → 228,7
@Override
public Object create() throws SQLException {
fixUnboundedVarchar(root);
fixUnboundedNumeric(root);
updateSocieteSchema(root);
updateToV1Dot2(root);
return null;
424,10 → 425,48
 
// c.gridy++;
// this.add(bd, c);
 
c.gridy++;
c.weightx = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
c.insets = new Insets(10, 3, 2, 2);
this.add(new JLabelBold("Paramètrages de la base de données"), c);
c.gridy++;
c.weightx = 0;
c.anchor = GridBagConstraints.EAST;
c.gridwidth = GridBagConstraints.REMAINDER;
c.fill = GridBagConstraints.NONE;
c.insets = DefaultGridBagConstraints.getDefaultInsets();
JButton buttonPL = new JButton("Lancer");
buttonPL.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
if (!finderPanel.getServerConfig().getType().equals(ServerFinderConfig.POSTGRESQL)) {
 
} else {
final ComptaPropsConfiguration conf = ComptaPropsConfiguration.create(true);
try {
final SQLDataSource ds = conf.getSystemRoot().getDataSource();
ds.execute("CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler AS '$libdir/plpgsql' LANGUAGE C;" + "\n"
+ "CREATE FUNCTION plpgsql_validator(oid) RETURNS void AS '$libdir/plpgsql' LANGUAGE C;" + "\n"
+ "CREATE TRUSTED PROCEDURAL LANGUAGE plpgsql HANDLER plpgsql_call_handler VALIDATOR plpgsql_validator;");
} catch (Exception ex) {
System.err.println("Impossible d'ajouter le langage PLPGSQL. Peut etre est il déjà installé.");
}
}
JOptionPane.showConfirmDialog(null, "Paramètrage terminé.");
}
});
this.add(buttonPL, c);
 
c.gridy++;
c.gridx = 0;
c.weightx = 1;
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.WEST;
c.gridwidth = GridBagConstraints.REMAINDER;
c.insets = new Insets(10, 3, 2, 2);
this.add(new JLabelBold("Mise à niveau de la base OpenConcerto"), c);
c.gridy++;
this.add(this.bar, c);
451,6 → 490,64
this.add(comp, c);
}
 
private void fixUnboundedNumeric(DBRoot root) throws SQLException {
 
final List<AlterTable> alters = new ArrayList<AlterTable>();
{
SQLTable tableAvoir = root.getTable("AVOIR_CLIENT_ELEMENT");
final AlterTable alter = new AlterTable(tableAvoir);
SQLField fieldAcompteAvoir = tableAvoir.getField("POURCENT_ACOMPTE");
if (fieldAcompteAvoir.getType().getSize() > 500) {
final String fName = fieldAcompteAvoir.getName();
alter.alterColumn(fName, EnumSet.allOf(Properties.class), "numeric(6,2)", "100", false);
}
 
SQLField fieldRemiseAvoir = tableAvoir.getField("POURCENT_REMISE");
if (fieldRemiseAvoir.getType().getSize() > 500) {
final String fName = fieldRemiseAvoir.getName();
alter.alterColumn(fName, EnumSet.allOf(Properties.class), "numeric(6,2)", "0", false);
}
 
if (!alter.isEmpty())
alters.add(alter);
}
 
{
SQLTable tableFacture = root.getTable("SAISIE_VENTE_FACTURE_ELEMENT");
final AlterTable alter = new AlterTable(tableFacture);
SQLField fieldAcompteFacture = tableFacture.getField("POURCENT_ACOMPTE");
if (fieldAcompteFacture.getType().getSize() > 500) {
final String fName = fieldAcompteFacture.getName();
alter.alterColumn(fName, EnumSet.allOf(Properties.class), "numeric(6,2)", "100", false);
}
 
SQLField fieldRemiseFacture = tableFacture.getField("POURCENT_REMISE");
if (fieldRemiseFacture.getType().getSize() > 500) {
final String fName = fieldRemiseFacture.getName();
alter.alterColumn(fName, EnumSet.allOf(Properties.class), "numeric(6,2)", "0", false);
}
 
if (tableFacture.getFieldsName().contains("REPARTITION_POURCENT")) {
SQLField fieldRepFacture = tableFacture.getField("REPARTITION_POURCENT");
if (fieldRepFacture.getType().getSize() > 500) {
final String fName = fieldRepFacture.getName();
alter.alterColumn(fName, EnumSet.allOf(Properties.class), "numeric(6,2)", "0", false);
}
}
 
if (!alter.isEmpty())
alters.add(alter);
 
}
if (alters.size() > 0) {
final SQLDataSource ds = root.getDBSystemRoot().getDataSource();
for (final String sql : ChangeTable.cat(alters, root.getName())) {
ds.execute(sql);
}
root.refetch();
}
}
 
private void fixUnboundedVarchar(DBRoot root) throws SQLException {
final Set<String> namesSet = CollectionUtils.createSet("NOM", "PRENOM", "SURNOM", "LOGIN", "PASSWORD");
final List<AlterTable> alters = new ArrayList<AlterTable>();
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/ModulePreferencePanel.java
15,7 → 15,6
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.preferences.SQLPreferences;
import org.openconcerto.sql.sqlobject.SQLSearchableTextCombo;
import org.openconcerto.sql.sqlobject.SQLSearchableTextCombo.ISQLListModel;
import org.openconcerto.sql.sqlobject.SQLTextCombo;
27,7 → 26,6
import org.openconcerto.utils.PrefType;
 
import java.util.Date;
import java.util.prefs.Preferences;
 
import javax.swing.JCheckBox;
import javax.swing.JComponent;
34,11 → 32,11
 
public abstract class ModulePreferencePanel extends JavaPrefPreferencePanel {
 
static private DBRoot getRoot() {
static public DBRoot getRoot() {
return Configuration.getInstance().getRoot();
}
 
static private String getAppPrefPath() {
static String getAppPrefPath() {
return Configuration.getInstance().getAppID() + '/';
}
 
89,8 → 87,6
}
 
public final void init(final ModuleFactory module, final boolean local) {
final Preferences rootPrefs = local ? Preferences.userRoot() : new SQLPreferences(getRoot());
// ID is a package name, transform to path to avoid bumping into the size limit
this.setPrefs(rootPrefs.node(getAppPrefPath() + module.getID().replace('.', '/')));
this.setPrefs(module.getPreferences(local, getRoot()));
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/ModuleLauncher.java
New file
0,0 → 1,71
/*
* 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.erp.modules;
 
import org.openconcerto.erp.config.Gestion;
import org.openconcerto.utils.FileUtils;
 
import java.io.File;
import java.io.IOException;
 
/**
* Package a module from a project and launch it. The system property {@link #MODULE_DIR_PROP} must
* be defined.
*
* @author Sylvain CUAZ
* @see ModulePackager
*/
public class ModuleLauncher {
/**
* Required system property, it must point to a directory with module classes in bin/, this
* class will put the packaged module in the dist/ subdirectory.
*/
public static final String MODULE_DIR_PROP = "module.dir";
/**
* System property to use if the module properties files isn't "module.properties" (this
* property is evaluated relative to {@link #MODULE_DIR_PROP}).
*/
public static final String MODULE_PROPS_FILE_PROP = "module.propsFile";
 
public static void main(String[] args) throws IOException {
final File moduleDir = new File(System.getProperty(MODULE_DIR_PROP));
final File propsFile = new File(moduleDir, System.getProperty(MODULE_PROPS_FILE_PROP, "module.properties"));
final boolean launchFromPackage = !Boolean.getBoolean("module.fromProject");
final File classes = new File(moduleDir, "bin");
 
// always update dist/ to avoid out of date problems
final File distDir = new File(moduleDir, "dist");
FileUtils.mkdir_p(distDir);
final File jar = new ModulePackager(propsFile, classes).writeToDir(distDir);
// to avoid out of date modules from OpenConcerto (e.g. when launching this module, the jars
// of MODULES_DIR are used for dependencies)
FileUtils.copyFile(jar, new File(Gestion.MODULES_DIR, jar.getName()));
 
final ModuleFactory factory;
if (launchFromPackage) {
factory = new JarModuleFactory(jar);
} else {
factory = new RuntimeModuleFactory(propsFile);
try {
Class.forName(factory.getMainClass());
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Module classes are not in the classpath (they should be in " + classes + ")", e);
}
}
 
Gestion.main(args);
// add after main() otherwise we could be overwritten by an older jar
ModuleManager.getInstance().addFactoryAndStart(factory, false);
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/AlterTableRestricted.java
34,7 → 34,6
*/
public final class AlterTableRestricted {
 
private final DBContext ctxt;
private final SQLTable table;
private final Set<SQLField> previouslyCreatedFields;
private final AlterTable alter;
41,7 → 40,6
private final Set<String> addedColumns, removedColumns;
 
AlterTableRestricted(DBContext ctxt, String tableName) {
this.ctxt = ctxt;
this.table = ctxt.getRoot().getTable(tableName);
this.previouslyCreatedFields = ctxt.getFieldsPreviouslyCreated(tableName);
this.alter = new AlterTable(this.table);
108,14 → 106,8
}
 
public AlterTable addForeignColumn(String fk, SQLName tableName) {
SQLCreateTableBase<?> createTable;
if (tableName.getItemCount() == 1 && (createTable = this.ctxt.getCreateTables().get(tableName.getFirst())) != null) {
addCol(fk);
return this.alter.addForeignColumn(fk, createTable);
} else {
return this.addForeignColumn(fk, this.table.getDesc(tableName, SQLTable.class));
}
}
 
public AlterTable addForeignColumn(String fk, SQLTable foreignTable) {
addCol(fk);
122,6 → 114,11
return this.alter.addForeignColumn(fk, foreignTable);
}
 
public AlterTable addForeignColumn(String fk, SQLCreateTableBase<?> createTable) {
addCol(fk);
return this.alter.addForeignColumn(fk, createTable);
}
 
public AlterTable addUniqueConstraint(String name, List<String> cols) {
if (!this.addedColumns.containsAll(cols))
throw new IllegalArgumentException("Can only add constraint to added columns : " + CollectionUtils.substract(cols, this.addedColumns));
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/AbstractModule.java
14,8 → 14,11
package org.openconcerto.erp.modules;
 
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.DBRoot;
 
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
53,9 → 56,21
}
 
/**
* Should create permanent items. NOTE: all items created through <code>ctxt</code> will be
* dropped automatically, i.e. no action is necessary in {@link #uninstall()}.
* The directory this module should use while running. During installation use
* {@link DBContext#getLocalDirectory()}.
*
* @return the directory for this module.
*/
protected final File getLocalDirectory() {
return ModuleManager.getInstance().getLocalDirectory(this.getFactory().getID());
}
 
/**
* Should create permanent items. NOTE: all structure items created through <code>ctxt</code>
* will be dropped automatically, and similarly all files created in
* {@link DBContext#getLocalDirectory()} will be deleted automatically, i.e. no action is
* necessary in {@link #uninstall(DBRoot)}.
*
* @param ctxt to create database objects.
*/
protected void install(DBContext ctxt) {
64,9 → 79,9
 
/**
* Should add elements for the tables of this module. It's also the place to
* {@link SQLElement#setAction(String, org.openconcerto.sql.element.SQLElement.ReferenceAction)set actions}
* for foreign keys of this module. NOTE: this method is called as long as the module is
* installed in the database, even if it is stopped.
* {@link SQLElement#setAction(String, SQLElement.ReferenceAction) set actions} for foreign keys
* of this module. NOTE: this method is called as long as the module is installed in the
* database, even if it is stopped.
*
* @param dir the directory where to add elements.
*/
105,7 → 120,7
 
protected abstract void stop();
 
protected void uninstall() {
protected void uninstall(DBRoot root) {
 
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/JarModuleFactory.java
105,4 → 105,9
public AbstractModule createModule(Map<String, AbstractModule> alreadyCreated) throws Exception {
return createModule(new ModuleClassLoader(alreadyCreated).loadClass(this.getMainClass()));
}
 
@Override
public String toString() {
return super.toString() + " from " + this.jar;
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/ModuleFactory.java
13,6 → 13,8
package org.openconcerto.erp.modules;
 
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.preferences.SQLPreferences;
import org.openconcerto.utils.cc.IPredicate;
 
import java.io.IOException;
23,6 → 25,7
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
175,8 → 178,22
return (AbstractModule) c.getConstructor(ModuleFactory.class).newInstance(this);
}
 
public final Preferences getLocalPreferences() {
return this.getPreferences(true, null);
}
 
public final Preferences getSQLPreferences(final DBRoot root) {
return this.getPreferences(false, root);
}
 
public final Preferences getPreferences(final boolean local, final DBRoot root) {
final Preferences rootPrefs = local ? Preferences.userRoot() : new SQLPreferences(root);
// ID is a package name, transform to path to avoid bumping into the size limit
return rootPrefs.node(ModulePreferencePanel.getAppPrefPath() + this.getID().replace('.', '/'));
}
 
@Override
public String toString() {
return super.toString() + " " + getID() + " (" + getMajorVersion() + "." + getMinorVersion() + ")";
return getClass().getSimpleName() + " " + getID() + " (" + getMajorVersion() + "." + getMinorVersion() + ")";
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/ModuleManager.java
17,9 → 17,11
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.DBFileCache;
import org.openconcerto.sql.model.DBItemFileCache;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLRow;
27,6 → 29,7
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.DirectedEdge;
37,13 → 40,14
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
import org.openconcerto.sql.view.list.RowAction;
import org.openconcerto.task.config.ComptaBasePropsConfiguration;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.IdentityHashSet;
import org.openconcerto.utils.cc.IdentitySet;
 
import java.io.File;
import java.io.FileFilter;
60,10 → 64,9
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
 
import javax.swing.JMenuItem;
83,7 → 86,6
private static final Logger L = Logger.getLogger(ModuleManager.class.getPackage().getName());
 
private static final int MIN_VERSION = 0;
private static final int NO_VERSION = MIN_VERSION - 1;
private static final String MODULE_COLNAME = "MODULE_NAME";
private static final String MODULE_VERSION_COLNAME = "MODULE_VERSION";
private static final String TABLE_COLNAME = "TABLE";
105,6 → 107,9
private final Map<String, ComponentsContext> modulesComponents;
private final DirectedGraph<ModuleFactory, DirectedEdge<ModuleFactory>> dependencyGraph;
 
private DBRoot root;
private Configuration conf;
 
public ModuleManager() {
this.factories = new HashMap<String, ModuleFactory>();
this.runningModules = new HashMap<String, AbstractModule>();
116,6 → 121,9
});
this.modulesElements = new HashMap<String, Collection<SQLElement>>();
this.modulesComponents = new HashMap<String, ComponentsContext>();
 
this.root = null;
this.conf = null;
}
 
// *** factories (thread-safe)
122,7 → 130,7
 
public final int addFactories(final File dir) {
if (!dir.exists()) {
System.err.println("Warning: module factory directory not found: " + dir.getAbsolutePath());
L.warning("Module factory directory not found: " + dir.getAbsolutePath());
return 0;
}
final File[] jars = dir.listFiles(new FileFilter() {
138,7 → 146,7
this.addFactory(new JarModuleFactory(jar));
i++;
} catch (Exception e) {
System.err.println("Couldn't add " + jar);
L.warning("Couldn't add " + jar);
e.printStackTrace();
}
}
168,7 → 176,9
 
private final String addFactory(final ModuleFactory f, final boolean start, final boolean persistent) {
synchronized (this.factories) {
this.factories.put(f.getID(), f);
final ModuleFactory prev = this.factories.put(f.getID(), f);
if (prev != null)
L.info("Changing the factory for " + f.getID() + "\nfrom\t" + prev + "\nto\t" + f);
}
if (start)
this.invoke(new IClosure<ModuleManager>() {
185,11 → 195,16
}
 
public final Map<String, ModuleFactory> getFactories() {
return Collections.unmodifiableMap(this.factories);
synchronized (this.factories) {
return new HashMap<String, ModuleFactory>(this.factories);
}
}
 
private ModuleFactory getFactory(final String id) {
final ModuleFactory res = this.factories.get(id);
final ModuleFactory res;
synchronized (this.factories) {
res = this.factories.get(id);
}
if (res == null)
throw new IllegalArgumentException("No factory for " + id);
return res;
235,8 → 250,10
// *** modules (in EDT)
 
/**
* Call the passed closure at a time when modules can be started. In particular the
* {@link MainFrame#getInstance() main frame} has been created.
* Call the passed closure at a time when modules can be started. In particular this manager has
* been set up and the {@link MainFrame#getInstance() main frame} has been created.
*
* @param c the closure to execute.
*/
public void invoke(final IClosure<ModuleManager> c) {
MainFrame.invoke(new Runnable() {
251,14 → 268,14
// (e.g. if a module created a child table, as long as the table is in the database it needs to
// be archived along its parent)
private void registerRequiredModules() throws Exception {
assert SwingUtilities.isEventDispatchThread();
final List<String> modulesToStart = new ArrayList<String>();
try {
final Map<String, ModuleFactory> factories = this.getFactories();
for (final Entry<String, ModuleVersion> e : this.getDBInstalledModules().entrySet()) {
final String moduleID = e.getKey();
// modules that just add non-key fields are not required
if (this.areElementsNeeded(moduleID)) {
final ModuleFactory moduleFactory = this.getFactories().get(moduleID);
final ModuleFactory moduleFactory = factories.get(moduleID);
final String error;
if (moduleFactory == null)
error = "Module '" + moduleID + "' non disponible.";
269,8 → 286,7
error = null;
if (error != null) {
// TODO open GUI to resolve the issue
ExceptionHandler.handle(error);
return;
throw new Exception(error);
} else {
modulesToStart.add(moduleID);
}
277,30 → 293,40
}
}
} catch (Exception e) {
ExceptionHandler.die("Impossible de déterminer les modules requis", e);
return;
throw new Exception("Impossible de déterminer les modules requis", e);
}
final Tuple2<Map<String, AbstractModule>, Set<String>> modules = this.createModules(modulesToStart, false);
final Tuple2<Map<String, AbstractModule>, Set<String>> modules = this.createModules(modulesToStart, false, true);
if (modules.get1().size() > 0)
ExceptionHandler.die("Impossible de créer les modules " + modules.get1());
throw new Exception("Impossible de créer les modules " + modules.get1());
for (final AbstractModule m : modules.get0().values())
this.registerSQLElements(m);
}
 
/**
* Initialize the module manager.
* Initialise the module manager.
*
* @return the exception, if any, thrown when starting previously running modules.
* @param root the root where the modules install.
* @param conf the configuration the modules change.
* @throws Exception if required modules couldn't be registered.
*/
public final Exception setup() throws Exception {
public synchronized final void setup(final DBRoot root, final Configuration conf) throws Exception {
if (root == null || conf == null)
throw new NullPointerException();
if (this.root != null || getConf() != null)
throw new IllegalStateException("Already setup");
// modulesElements can be non empty, if a previous setup() failed
assert this.runningModules.isEmpty() && this.modulesComponents.isEmpty() : "Modules cannot start without root & conf";
this.root = root;
this.conf = conf;
try {
this.registerRequiredModules();
try {
this.startPreviouslyRunningModules();
return null;
} catch (Exception e) {
return e;
// allow setup() to be called again
this.root = null;
this.conf = null;
throw e;
}
assert this.runningModules.isEmpty() && this.modulesComponents.isEmpty() : "registerRequiredModules() should not start modules";
}
 
private Preferences getPrefs() {
307,7 → 333,7
// modules are installed per business entity (perhaps we could add a per user option, i.e.
// for all businesses of all databases)
final StringBuilder path = new StringBuilder(32);
for (final String item : DBFileCache.getJDBCAncestorNames(Configuration.getInstance().getRoot(), true)) {
for (final String item : DBFileCache.getJDBCAncestorNames(getRoot(), true)) {
path.append(StringUtils.getBoundedLengthString(DBItemFileCache.encode(item), Preferences.MAX_NAME_LENGTH));
path.append('/');
}
320,49 → 346,56
return getPrefs().node("toRun");
}
 
private Preferences getInstalledPrefs() {
return getPrefs().node("installed");
}
 
protected final boolean isModuleInstalledLocally(String id) {
return getInstalledPrefs().getLong(id, NO_VERSION) != NO_VERSION;
return getLocalVersionFile(id).exists();
}
 
protected final ModuleVersion getModuleVersionInstalledLocally(String id) {
final long v = getInstalledPrefs().getLong(id, NO_VERSION);
return v == NO_VERSION ? null : new ModuleVersion(v);
final File versionFile = getLocalVersionFile(id);
if (versionFile.exists()) {
try {
return new ModuleVersion(Long.valueOf(FileUtils.read(versionFile)));
} catch (IOException e) {
throw new IllegalStateException("Couldn't get installed version of " + id, e);
}
} else {
return null;
}
}
 
public final Collection<String> getModulesInstalledLocally() {
try {
return Arrays.asList(getInstalledPrefs().keys());
} catch (BackingStoreException e) {
throw new IllegalStateException("Couldn't fetch installed preferences", e);
return getModulesVersionInstalledLocally().keySet();
}
}
 
public final Map<String, ModuleVersion> getModulesVersionInstalledLocally() {
final Preferences prefs = getInstalledPrefs();
final Map<String, ModuleVersion> res = new HashMap<String, ModuleVersion>();
try {
for (final String key : prefs.keys())
res.put(key, new ModuleVersion(prefs.getLong(key, NO_VERSION)));
} catch (BackingStoreException e) {
throw new IllegalStateException("Couldn't fetch installed preferences", e);
final File dir = getLocalDirectory();
for (final File d : dir.listFiles()) {
final String id = d.getName();
final ModuleVersion version = getModuleVersionInstalledLocally(id);
if (version != null)
res.put(id, version);
}
return res;
}
 
private void setModuleInstalledLocally(ModuleFactory f, boolean b) {
try {
if (b) {
final ModuleVersion vers = f.getVersion();
if (vers.getMerged() < MIN_VERSION)
throw new IllegalStateException("Invalid version : " + vers);
getInstalledPrefs().putLong(f.getID(), vers.getMerged());
final File versionFile = getLocalVersionFile(f.getID());
FileUtils.mkdir_p(versionFile.getParentFile());
FileUtils.write(String.valueOf(vers.getMerged()), versionFile);
} else {
getInstalledPrefs().remove(f.getID());
// perhaps add a parameter to only remove the versionFile
FileUtils.rm_R(getLocalDirectory(f.getID()));
}
} catch (IOException e) {
throw new IllegalStateException("Couldn't change installed status of " + f, e);
}
}
 
private SQLTable getInstalledTable(final DBRoot r) throws SQLException {
if (!r.contains(FWK_MODULE_TABLENAME)) {
381,7 → 414,7
 
createTable.addUniqueConstraint("uniqModule", Arrays.asList(MODULE_COLNAME, TABLE_COLNAME, FIELD_COLNAME));
 
SQLUtils.executeAtomic(Configuration.getInstance().getSystemRoot().getDataSource(), new SQLFactory<Object>() {
SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
@Override
public Object create() throws SQLException {
r.getDBSystemRoot().getDataSource().execute(createTable.asString());
395,10 → 428,34
return r.getTable(FWK_MODULE_TABLENAME);
}
 
private DBRoot getRoot() {
return ((ComptaBasePropsConfiguration) Configuration.getInstance()).getRootSociete();
protected final DBRoot getRoot() {
return this.root;
}
 
private SQLDataSource getDS() {
return getRoot().getDBSystemRoot().getDataSource();
}
 
protected final Configuration getConf() {
return this.conf;
}
 
private SQLElementDirectory getDirectory() {
return getConf().getDirectory();
}
 
private final File getLocalDirectory() {
return new File(this.getConf().getConfDir(getRoot()), "modules");
}
 
protected final File getLocalDirectory(final String id) {
return new File(this.getLocalDirectory(), id);
}
 
private final File getLocalVersionFile(final String id) {
return new File(this.getLocalDirectory(id), "version");
}
 
public final ModuleVersion getDBInstalledModuleVersion(final String id) throws SQLException {
return getDBInstalledModules(id).get(id);
}
531,40 → 588,91
return (Boolean) installedTable.getDBSystemRoot().getDataSource().executeScalar(sel.asString());
}
 
private void install(final AbstractModule module) throws SQLException {
private void install(final AbstractModule module) throws Exception {
final ModuleFactory factory = module.getFactory();
if (!isModuleInstalledLocally(factory.getID())) {
final ModuleVersion localVersion = getModuleVersionInstalledLocally(factory.getID());
final ModuleVersion lastInstalledVersion = getDBInstalledModuleVersion(factory.getID());
if (lastInstalledVersion != null && module.getFactory().getVersion().compareTo(lastInstalledVersion) < 0)
throw new IllegalArgumentException("Module older than the one installed in the DB : " + module.getFactory().getVersion() + " < " + lastInstalledVersion);
SQLUtils.executeAtomic(Configuration.getInstance().getSystemRoot().getDataSource(), new SQLFactory<Object>() {
final ModuleVersion moduleVersion = module.getFactory().getVersion();
if (lastInstalledVersion != null && moduleVersion.compareTo(lastInstalledVersion) < 0)
throw new IllegalArgumentException("Module older than the one installed in the DB : " + moduleVersion + " < " + lastInstalledVersion);
if (localVersion != null && moduleVersion.compareTo(localVersion) < 0)
throw new IllegalArgumentException("Module older than the one installed locally : " + moduleVersion + " < " + localVersion);
if (!moduleVersion.equals(localVersion) || !moduleVersion.equals(lastInstalledVersion)) {
// local
final File localDir = getLocalDirectory(factory.getID());
// There are 2 choices to handle the update of files :
// 1. copy dir to a new one and pass it to DBContext, then either rename it to dir or
// rename it failed
// 2. copy dir to a backup, pass dir to DBContext, then either remove backup or rename
// it to dir
// Choice 2 is simpler since the module deals with the same directory in both install()
// and start()
final File backupDir;
// check if we need a backup
if (localDir.exists()) {
backupDir = FileUtils.addSuffix(localDir, ".backup");
FileUtils.rm_R(backupDir);
FileUtils.copyDirectory(localDir, backupDir);
} else {
backupDir = null;
FileUtils.mkdir_p(localDir);
}
assert localDir.exists();
try {
SQLUtils.executeAtomic(getDS(), new ConnectionHandlerNoSetup<Object, IOException>() {
@Override
public Object create() throws SQLException {
public Object handle(SQLDataSource ds) throws SQLException, IOException {
final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(factory.getID());
final DBContext ctxt = new DBContext(lastInstalledVersion, getRoot(), alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
final DBContext ctxt = new DBContext(localDir, localVersion, getRoot(), lastInstalledVersion, alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
// install local
module.install(ctxt);
 
// install local
setModuleInstalledLocally(factory, true);
if (!localDir.exists())
throw new IOException("Modules shouldn't remove their directory");
// install in DB
ctxt.execute();
updateModuleFields(factory, ctxt);
 
return null;
}
});
} catch (Exception e) {
// install did not complete successfully
if (getRoot().getServer().getSQLSystem() == SQLSystem.MYSQL)
L.warning("MySQL cannot rollback DDL statements");
// keep failed install files and restore previous files
final File failed = FileUtils.addSuffix(localDir, ".failed");
if (failed.exists() && !FileUtils.rmR(failed))
L.warning("Couldn't remove " + failed);
if (!localDir.renameTo(failed)) {
L.warning("Couldn't move " + localDir + " to " + failed);
} else {
assert !localDir.exists();
// restore if needed
if (backupDir != null && !backupDir.renameTo(localDir))
L.warning("Couldn't restore " + backupDir + " to " + localDir);
}
throw e;
}
// DB transaction was committed, remove backup files
assert localDir.exists();
if (backupDir != null)
FileUtils.rm_R(backupDir);
setModuleInstalledLocally(factory, true);
}
assert moduleVersion.equals(getModuleVersionInstalledLocally(factory.getID())) && moduleVersion.equals(getDBInstalledModuleVersion(factory.getID()));
}
 
private void registerSQLElements(final AbstractModule module) {
final String id = module.getFactory().getID();
synchronized (this.modulesElements) {
if (!this.modulesElements.containsKey(id)) {
final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
final SQLElementDirectory dir = getDirectory();
final Map<SQLTable, SQLElement> beforeElements = new HashMap<SQLTable, SQLElement>(dir.getElementsMap());
module.setupElements(dir);
final Collection<SQLElement> elements = new ArrayList<SQLElement>();
final Collection<SQLElement> newElements = CollectionUtils.substract(new IdentityHashSet<SQLElement>(dir.getElements()), new IdentityHashSet<SQLElement>(beforeElements.values()));
for (final SQLElement elem : newElements) {
// use IdentitySet so as not to call equals() since it triggers initFF()
final IdentitySet<SQLElement> beforeElementsSet = new IdentityHashSet<SQLElement>(beforeElements.values());
for (final SQLElement elem : dir.getElements()) {
if (!beforeElementsSet.contains(elem)) {
// don't let elements be replaced (it's tricky to restore in unregister())
if (beforeElements.containsKey(elem.getTable())) {
L.warning("Trying to replace element for " + elem.getTable() + " with " + elem);
573,14 → 681,16
elements.add(elem);
}
}
}
this.modulesElements.put(id, elements);
}
}
}
 
private void setupComponents(final AbstractModule module) throws SQLException {
final String id = module.getFactory().getID();
if (!this.modulesComponents.containsKey(id)) {
final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
final SQLElementDirectory dir = getDirectory();
final Tuple2<Set<String>, Set<SQLName>> alreadyCreatedItems = getCreatedItems(id);
final ComponentsContext ctxt = new ComponentsContext(dir, getRoot(), alreadyCreatedItems.get0(), alreadyCreatedItems.get1());
module.setupComponents(ctxt);
588,7 → 698,7
}
}
 
protected final void startPreviouslyRunningModules() throws Exception {
public final void startPreviouslyRunningModules() throws Exception {
final List<String> ids = Arrays.asList(getRunningIDsPrefs().keys());
startModules(ids);
}
615,15 → 725,16
}
 
private final Set<String> startModules(final Collection<String> ids) throws Exception {
return this.createModules(ids, true).get1();
return this.createModules(ids, true, false).get1();
}
 
// modules created, and the ones that couldn't
// i.e. if a module was already created it's in neither
private final Tuple2<Map<String, AbstractModule>, Set<String>> createModules(final Collection<String> ids, final boolean start) throws Exception {
assert SwingUtilities.isEventDispatchThread();
private final Tuple2<Map<String, AbstractModule>, Set<String>> createModules(final Collection<String> ids, final boolean start, final boolean inSetup) throws Exception {
// in setup we're not in the EDT, but it's OK since by definition no modules are started
assert SwingUtilities.isEventDispatchThread() || inSetup && this.runningModules.isEmpty();
// add currently running modules so that ModuleFactory can use them
final Map<String, AbstractModule> modules = new LinkedHashMap<String, AbstractModule>(this.runningModules);
final Map<String, AbstractModule> modules = inSetup ? new LinkedHashMap<String, AbstractModule>(ids.size() * 2) : new LinkedHashMap<String, AbstractModule>(this.runningModules);
final Set<String> cannotCreate = new HashSet<String>();
final LinkedHashMap<ModuleFactory, Boolean> map = new LinkedHashMap<ModuleFactory, Boolean>();
synchronized (this.factories) {
640,6 → 751,7
}
}
// only keep modules created by this method
if (!inSetup)
modules.keySet().removeAll(this.runningModules.keySet());
 
if (start) {
667,7 → 779,7
final InputStream labels = module.getClass().getResourceAsStream("labels.xml");
if (labels != null) {
try {
Configuration.getInstance().getTranslator().load(getRoot(), labels);
getConf().getTranslator().load(getRoot(), labels);
} finally {
labels.close();
}
735,13 → 847,15
 
private void unregisterSQLElements(final AbstractModule module) {
final String id = module.getFactory().getID();
synchronized (this.modulesElements) {
if (this.modulesElements.containsKey(id)) {
final Collection<SQLElement> elements = this.modulesElements.remove(id);
final SQLElementDirectory dir = Configuration.getInstance().getDirectory();
final SQLElementDirectory dir = getDirectory();
for (final SQLElement elem : elements)
dir.removeSQLElement(elem);
}
}
}
 
private void tearDownComponents(final AbstractModule module) {
final String id = module.getFactory().getID();
893,22 → 1007,22
 
final AbstractModule module;
if (!this.isModuleRunning(id)) {
module = this.createModules(Collections.singleton(id), false).get0().get(id);
module = this.createModules(Collections.singleton(id), false, false).get0().get(id);
} else {
module = this.runningModules.get(id);
this.stopModule(id, true);
}
 
SQLUtils.executeAtomic(Configuration.getInstance().getSystemRoot().getDataSource(), new SQLFactory<Object>() {
SQLUtils.executeAtomic(getDS(), new SQLFactory<Object>() {
@Override
public Object create() throws SQLException {
module.uninstall();
final DBRoot root = getRoot();
module.uninstall(root);
unregisterSQLElements(module);
setModuleInstalledLocally(module.getFactory(), false);
 
// uninstall from DB
final Tuple2<Set<String>, Set<SQLName>> createdItems = getCreatedItems(id);
final DBRoot root = getRoot();
final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
final Set<String> tableNames = createdItems.get0();
for (final SQLName field : createdItems.get1()) {
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/AvailableModulesPanel.java
13,19 → 13,25
package org.openconcerto.erp.modules;
 
import org.openconcerto.sql.view.AbstractFileTransfertHandler;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
 
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
 
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
 
public class AvailableModulesPanel extends JPanel {
private final AvailableModuleTableModel tm;
91,8 → 97,47
c.weighty = 1;
c.gridy++;
this.add(space, c);
this.setTransferHandler(new AbstractFileTransfertHandler() {
 
@Override
public void handleFile(File f) {
if (!f.getName().endsWith(".jar")) {
JOptionPane.showMessageDialog(AvailableModulesPanel.this, "Impossible d'installer le module. Le fichier n'est pas un module.");
return;
}
File dir = new File("Modules");
dir.mkdir();
File out = null;
if (dir.canWrite()) {
try {
out = new File(dir, f.getName());
FileUtils.copyFile(f, out);
} catch (IOException e) {
JOptionPane.showMessageDialog(AvailableModulesPanel.this, "Impossible d'installer le module.\n" + f.getAbsolutePath() + " vers " + dir.getAbsolutePath());
return;
}
} else {
JOptionPane.showMessageDialog(AvailableModulesPanel.this, "Impossible d'installer le module.\nVous devez disposer des droits en écriture sur le dossier:\n" + dir.getAbsolutePath());
return;
}
try {
ModuleManager.getInstance().addFactory(new JarModuleFactory(out));
} catch (IOException e) {
JOptionPane.showMessageDialog(AvailableModulesPanel.this, "Impossible d'intégrer le module.\n" + e.getMessage());
return;
}
SwingUtilities.invokeLater(new Runnable() {
 
@Override
public void run() {
reload();
}
});
 
}
});
}
 
public void reload() {
this.tm.reload();
}
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/DBContext.java
22,7 → 22,9
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.sql.utils.SQLCreateTableBase;
import org.openconcerto.utils.CollectionMap;
import org.openconcerto.utils.cc.IClosure;
 
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
39,17 → 41,23
*/
public final class DBContext {
 
private final File dir;
private final ModuleVersion localVersion;
private final ModuleVersion lastInstalledVersion;
private final DBRoot root;
private final List<ChangeTable<?>> changeTables;
private final List<AlterTableRestricted> alterTables;
// Data Manipulation
private final List<IClosure<? super DBRoot>> dm;
 
private final Set<String> tables;
private final CollectionMap<String, SQLField> fields;
 
DBContext(ModuleVersion lastInstalledVersion, DBRoot root, final Set<String> tables, final Set<SQLName> fields) {
DBContext(final File dir, final ModuleVersion localVersion, final DBRoot root, final ModuleVersion dbVersion, final Set<String> tables, final Set<SQLName> fields) {
super();
this.lastInstalledVersion = lastInstalledVersion;
this.dir = dir;
this.localVersion = localVersion;
this.lastInstalledVersion = dbVersion;
this.root = root;
this.tables = Collections.unmodifiableSet(tables);
this.fields = new CollectionMap<String, SQLField>(new HashSet<SQLField>());
59,8 → 67,17
}
this.changeTables = new ArrayList<ChangeTable<?>>();
this.alterTables = new ArrayList<AlterTableRestricted>();
this.dm = new ArrayList<IClosure<? super DBRoot>>();
}
 
public final File getLocalDirectory() {
return this.dir;
}
 
public final ModuleVersion getLocalVersion() {
return this.localVersion;
}
 
public final ModuleVersion getLastInstalledVersion() {
return this.lastInstalledVersion;
}
95,7 → 112,10
SQLTable.setUndefID(getRoot().getSchema(), addedTable, null);
getRoot().refetch();
}
for (final IClosure<? super DBRoot> closure : this.dm) {
closure.executeChecked(getRoot());
}
}
 
// DDL
 
120,6 → 140,36
this.changeTables.add(new DropTable(this.root.getTable(name)));
}
 
// DML
 
/**
* The closure will be executed after the DDL statements. Note: you generally need to undo in
* {@link AbstractModule#uninstall()} what <code>closure</code> does (exceptions include
* inserting rows in a created table, since it will be automatically dropped).
*
* @param closure what to do.
*/
public final void manipulateData(final IClosure<? super DBRoot> closure) {
this.dm.add(closure);
}
 
/**
* The closure will be executed after the DDL statements. E.g. if you called
* {@link #getCreateTable(String)} this allows you to insert rows.
*
* @param name a table name.
* @param closure what to do.
* @see #manipulateData(IClosure)
*/
public final void manipulateTable(final String name, final IClosure<SQLTable> closure) {
this.manipulateData(new IClosure<DBRoot>() {
@Override
public void executeChecked(DBRoot input) {
closure.executeChecked(input.getTable(name));
}
});
}
 
// getter
 
final List<String> getAddedTables() {
/trunk/OpenConcerto/src/org/openconcerto/erp/modules/ComponentsContext.java
85,8 → 85,12
}
 
public final SQLElement getElement(final String tableName) {
return this.dir.getElement(this.getRoot().getTable(tableName));
final SQLElement element = this.dir.getElement(this.getRoot().getTable(tableName));
if (element == null) {
throw new IllegalArgumentException("Not element found for table " + tableName);
}
return element;
}
 
public final void putAdditionalField(final String tableName, final String name) {
final SQLElement elem = checkField(tableName, name);
/trunk/OpenConcerto/src/org/openconcerto/erp/action/NouvelleConnexionAction.java
22,6 → 22,7
import org.openconcerto.erp.core.finance.tax.model.TaxeCache;
import org.openconcerto.erp.core.humanresources.payroll.element.CaisseCotisationSQLElement;
import org.openconcerto.erp.element.objet.ClasseCompte;
import org.openconcerto.erp.modules.ModuleManager;
import org.openconcerto.erp.panel.ComptaTipsFrame;
import org.openconcerto.erp.utils.NXDatabaseAccessor;
import org.openconcerto.map.model.Ville;
113,7 → 114,16
}
comptaPropsConfiguration.setUpSocieteDataBaseConnexion(selectedSociete);
 
// finish filling the configuration before going any further, otherwise the
// SQLElementDirectory is not coherent
try {
ModuleManager.getInstance().setup(comptaPropsConfiguration.getRootSociete(), comptaPropsConfiguration);
} catch (Exception e) {
// not OK to continue without required elements
ExceptionHandler.die("Impossible de démarrer les modules requis", e);
}
 
 
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
127,6 → 137,15
f.setTitle(comptaPropsConfiguration.getAppName() + " " + version + socTitle);
f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
 
// start modules before displaying the frame (e.g. avoid modifying a
// visible menu bar)
try {
ModuleManager.getInstance().startPreviouslyRunningModules();
} catch (Exception exn) {
// OK to continue without all modules started
ExceptionHandler.handle(f, "Impossible de démarrer les modules", exn);
}
 
FrameUtil.show(f);
}
});
/trunk/OpenConcerto/src/org/openconcerto/erp/action/SauvegardeBaseAction.java
28,6 → 28,7
import org.openconcerto.erp.core.sales.order.report.CommandeClientXmlSheet;
import org.openconcerto.erp.core.sales.quote.report.DevisXmlSheet;
import org.openconcerto.erp.core.sales.shipment.report.BonLivraisonXmlSheet;
import org.openconcerto.erp.generationDoc.DocumentLocalStorageManager;
import org.openconcerto.erp.generationDoc.SheetXml;
import org.openconcerto.erp.generationDoc.gestcomm.AvoirClientXmlSheet;
import org.openconcerto.erp.generationDoc.gestcomm.AvoirFournisseurXmlSheet;
74,47 → 75,15
locations.add(serverIp.substring(5));
}
 
final DocumentLocalStorageManager storage = DocumentLocalStorageManager.getInstance();
 
locations.add(defaultLocation);
locations.add(SheetXml.getLocationForTuple(DevisXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(DevisXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(VenteFactureXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(VenteFactureXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(CommandeClientXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(CommandeClientXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(BonLivraisonXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(BonLivraisonXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(AvoirClientXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(AvoirClientXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(AvoirFournisseurXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(AvoirFournisseurXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(CommandeXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(CommandeXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(EtatVentesXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(EtatVentesXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(FicheClientXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(FicheClientXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(FicheRelanceSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(FicheRelanceSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(ReleveChequeSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(ReleveChequeSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(ReleveChequeEmisSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(ReleveChequeEmisSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(ListeFactureXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(ListeFactureXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(ListeVenteXmlSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(ListeVenteXmlSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(EtatChargesPayeSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(EtatChargesPayeSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(FichePayeSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(FichePayeSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(LivrePayeSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(LivrePayeSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(BalanceSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(BalanceSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(GrandLivreSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(GrandLivreSheet.getTuple2Location(), false));
locations.add(SheetXml.getLocationForTuple(JournauxSheet.getTuple2Location(), true));
locations.add(SheetXml.getLocationForTuple(JournauxSheet.getTuple2Location(), false));
for (File f : storage.getAllDocumentDirectories()) {
locations.add(f.getAbsolutePath());
}
for (File f : storage.getAllPDFDirectories()) {
locations.add(f.getAbsolutePath());
}
 
for (String string : locations) {
 
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/AbstractSheetXMLWithDate.java
New file
0,0 → 1,47
/*
* 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.erp.generationDoc;
 
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.utils.StringUtils;
 
import java.io.File;
import java.util.Calendar;
 
public abstract class AbstractSheetXMLWithDate extends AbstractSheetXml {
 
public AbstractSheetXMLWithDate(SQLRow row) {
super(row);
}
 
protected final String getYear() {
final Calendar cal = this.row.getDate("DATE");
return cal == null ? "Date inconnue" : String.valueOf(cal.get(Calendar.YEAR));
}
 
@Override
public File getDocumentOutputDirectoryP() {
return new File(DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory(this.getTemplateId()), getYear());
}
 
@Override
public File getPDFOutputDirectoryP() {
return new File(DocumentLocalStorageManager.getInstance().getPDFOutputDirectory(this.getTemplateId()), getYear());
}
 
@Override
public String getStoragePathP() {
return StringUtils.firstUp(this.elt.getPluralName() + File.separator + getYear());
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/DocumentLocalStorageManager.java
New file
0,0 → 1,160
/*
* 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.erp.generationDoc;
 
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class DocumentLocalStorageManager {
private static DocumentLocalStorageManager instance = new DocumentLocalStorageManager();
private Map<String, File> dirs = new HashMap<String, File>();
private Map<String, File> dirsPDF = new HashMap<String, File>();
private File documentDefaultDirectory;
private File PDFDefaultDirectory;
 
public static DocumentLocalStorageManager getInstance() {
return instance;
}
 
/**
* Returns the directory to store the document created from a template
*
* @param templateId : the id of the template used to create the document
* */
public File getDocumentOutputDirectory(String templateId) {
if (templateId == null) {
throw new IllegalArgumentException("null template id");
}
final File f = dirs.get(templateId);
if (f != null) {
return f;
}
return documentDefaultDirectory;
 
}
 
/**
* Returns the directory to store the PDF document created from a template
*
* @param templateId : the id of the template used to create the document
* */
public File getPDFOutputDirectory(String templateId) {
if (templateId == null) {
throw new IllegalArgumentException("null template id");
}
final File f = dirsPDF.get(templateId);
if (f != null) {
return f;
}
return PDFDefaultDirectory;
}
 
public void addDocumentDirectory(String templateId, File directory) {
if (templateId == null) {
throw new IllegalArgumentException("null template id");
}
this.dirs.put(templateId, directory);
TemplateManager.getInstance().register(templateId);
}
 
public void removeDocumentDirectory(String templateId, File directory) {
if (templateId == null) {
throw new IllegalArgumentException("null template id");
}
this.dirs.remove(templateId);
}
 
public void addPDFDirectory(String templateId, File directory) {
if (templateId == null) {
throw new IllegalArgumentException("null template id");
}
this.dirsPDF.put(templateId, directory);
TemplateManager.getInstance().register(templateId);
}
 
public void removePDFDirectory(String templateId, File directory) {
if (templateId == null) {
throw new IllegalArgumentException("null template id");
}
this.dirsPDF.remove(templateId);
}
 
public void setDocumentDefaultDirectory(File directory) {
this.documentDefaultDirectory = directory;
}
 
public void setPDFDefaultDirectory(File directory) {
this.PDFDefaultDirectory = directory;
}
 
public List<File> getAllPDFDirectories() {
List<File> list = new ArrayList<File>();
for (String id : this.dirsPDF.keySet()) {
list.add(this.dirsPDF.get(id));
}
return list;
}
 
public List<File> getAllDocumentDirectories() {
List<File> list = new ArrayList<File>();
for (String id : this.dirs.keySet()) {
list.add(this.dirs.get(id));
}
return list;
}
 
public void dump() {
System.out.println(this.getClass().getCanonicalName());
System.out.println("Default document directory: " + getAndTest(this.documentDefaultDirectory));
System.out.println("Default PFD directory : " + getAndTest(this.PDFDefaultDirectory));
System.out.println("Document directories:");
for (String key : this.dirs.keySet()) {
System.out.println(rightAlign("'" + key + "'") + " : " + getAndTest(this.dirs.get(key)));
}
System.out.println("PDF directories:");
for (String key : this.dirsPDF.keySet()) {
System.out.println(rightAlign("'" + key + "'") + " : " + getAndTest(this.dirsPDF.get(key)));
}
}
 
private String rightAlign(String s) {
String r = s;
int n = 20 - s.length();
for (int i = 0; i < n; i++) {
r = ' ' + r;
}
return r;
}
 
private String getAndTest(File dir) {
if (dir == null) {
return "null !!!!!!";
}
String r = "'" + dir.getAbsolutePath() + "'";
if (dir.exists()) {
if (!dir.isDirectory()) {
r += " is not a directory!!";
} else if (!dir.canWrite()) {
r += " is write protected!!";
}
} else {
r += " does not exist !!";
}
return r;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOgenerationXML.java
73,8 → 73,8
 
private static int answer = JOptionPane.NO_OPTION;
 
public static synchronized File genere(String modele, String pathDest, final String fileDest, SQLRow row, SQLRow rowLanguage) {
 
public static synchronized File createDocument(String templateId, File outputDirectory, final String expectedFileName, SQLRow row, SQLRow rowLanguage) {
final String langage = rowLanguage != null ? rowLanguage.getString("CHEMIN") : null;
cacheStyle.clear();
OOXMLCache.clearCache();
rowsEltCache.clear();
81,7 → 81,7
taxe.clear();
cacheForeign.clear();
 
File fDest = new File(pathDest, fileDest + ".ods");
File fDest = new File(outputDirectory, expectedFileName);
 
if (fDest.exists()) {
 
112,20 → 112,20
SAXBuilder builder = new SAXBuilder();
try {
 
if (needAnnexe(modele, row, rowLanguage)) {
try {
if (needAnnexe(templateId, row, rowLanguage)) {
// check if it exists
getOOTemplate(modele + "_annexe", rowLanguage);
modele += "_annexe";
System.err.println("modele With annexe " + modele);
} catch (FileNotFoundException e) {
 
final String annexeTemplateId = templateId + "_annexe";
InputStream annexeStream = TemplateManager.getInstance().getTemplate(annexeTemplateId, langage, null);
if (annexeStream != null) {
templateId = annexeTemplateId;
System.err.println("modele With annexe " + templateId);
}
}
 
System.err.println("modele " + modele);
System.err.println("Using template id: " + templateId);
final InputStream xmlConfiguration = TemplateManager.getInstance().getTemplateConfiguration(templateId, langage, null);
 
Document doc = builder.build(getXmlTemplate(modele, rowLanguage));
Document doc = builder.build(xmlConfiguration);
 
// On initialise un nouvel élément racine avec l'élément racine du document.
Element racine = doc.getRootElement();
134,7 → 134,9
List<Element> listElts = racine.getChildren("element");
 
// Création et génération du fichier OO
SpreadSheet spreadSheet = SpreadSheet.create(new ODPackage(getOOTemplate(modele, rowLanguage)));
final InputStream template = TemplateManager.getInstance().getTemplate(templateId, langage, null);
 
final SpreadSheet spreadSheet = new ODPackage(template).getSpreadSheet();
try {
// On remplit les cellules de la feuille
parseElementsXML(listElts, row, spreadSheet);
147,10 → 149,10
parseTableauXML(tableChild, row, spreadSheet, rowLanguage);
}
} catch (Exception e) {
ExceptionHandler.handle("Impossible de remplir le document " + modele + " " + ((rowLanguage == null) ? "" : rowLanguage.getString("CHEMIN")), e);
ExceptionHandler.handle("Impossible de remplir le document " + templateId + " " + ((rowLanguage == null) ? "" : rowLanguage.getString("CHEMIN")), e);
}
// Sauvegarde du fichier
return saveSpreadSheet(spreadSheet, new File(pathDest), fileDest, modele, rowLanguage);
return saveSpreadSheet(spreadSheet, outputDirectory, expectedFileName, templateId, rowLanguage);
 
} catch (final JDOMException e) {
 
157,7 → 159,7
e.printStackTrace();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ExceptionHandler.handle("Erreur lors de la génération du fichier " + fileDest, e);
ExceptionHandler.handle("Erreur lors de la génération du fichier " + expectedFileName, e);
}
});
} catch (final IOException e) {
165,7 → 167,7
e.printStackTrace();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ExceptionHandler.handle("Erreur lors de la création du fichier " + fileDest, e);
ExceptionHandler.handle("Erreur lors de la création du fichier " + expectedFileName, e);
}
});
}
706,8 → 708,9
* @return un File pointant sur le fichier créé
* @throws IOException
*/
private static File saveSpreadSheet(SpreadSheet ssheet, File pathDest, String fileName, String modele, SQLRow rowLanguage) throws IOException {
 
private static File saveSpreadSheet(SpreadSheet ssheet, File pathDest, String fileName, String templateId, SQLRow rowLanguage) throws IOException {
final String langage = rowLanguage != null ? rowLanguage.getString("CHEMIN") : null;
// Test des arguments
if (ssheet == null || pathDest == null || fileName.trim().length() == 0) {
throw new IllegalArgumentException();
720,7 → 723,7
pathDest.mkdirs();
}
 
fDest = SheetUtils.getInstance().convertToOldFile(fileName, pathDest, fDest);
fDest = SheetUtils.convertToOldFile(fileName, pathDest, fDest);
 
// Sauvegarde
try {
743,7 → 746,7
// Copie de l'odsp
try {
File odspOut = new File(pathDest, fileName + ".odsp");
InputStream odspIn = getTemplate(modele + ".odsp", rowLanguage);
final InputStream odspIn = TemplateManager.getInstance().getTemplatePrintConfiguration(templateId, langage, null);
if (odspIn != null) {
StreamUtils.copy(odspIn, odspOut);
}
753,18 → 756,6
return fDest;
}
 
public static InputStream getOOTemplate(String name, SQLRow language) throws FileNotFoundException {
return OOgenerationListeXML.getOOTemplate(name, language);
}
 
public static InputStream getXmlTemplate(String name, SQLRow language) throws FileNotFoundException {
return OOgenerationListeXML.getXmlTemplate(name, language);
}
 
public static InputStream getTemplate(String name, SQLRow language) throws FileNotFoundException {
return OOgenerationListeXML.getTemplate(name, language);
}
 
/**
* parcourt l'ensemble de la feuille pour trouver les style définit
*/
827,15 → 818,16
return mapStyleDef;
}
 
public static boolean needAnnexe(String modele, SQLRow row, SQLRow rowLanguage) {
 
SAXBuilder builder = new SAXBuilder();
public static boolean needAnnexe(String templateId, SQLRow row, SQLRow rowLanguage) {
final String langage = rowLanguage != null ? rowLanguage.getString("CHEMIN") : null;
final SAXBuilder builder = new SAXBuilder();
try {
final InputStream xmlConfiguration = TemplateManager.getInstance().getTemplateConfiguration(templateId, langage, null);
final Document doc = builder.build(xmlConfiguration);
final InputStream template = TemplateManager.getInstance().getTemplate(templateId, langage, null);
 
Document doc = builder.build(getXmlTemplate(modele, rowLanguage));
final SpreadSheet spreadSheet = new ODPackage(template).getSpreadSheet();
 
SpreadSheet spreadSheet = SpreadSheet.create(new ODPackage(getOOTemplate(modele, rowLanguage)));
 
// On initialise un nouvel élément racine avec l'élément racine du document.
Element racine = doc.getRootElement();
 
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOgenerationListeXML.java
49,34 → 49,32
// Cache pour la recherche des styles
private static Map<Sheet, Map<String, Map<Integer, String>>> cacheStyle = new HashMap<Sheet, Map<String, Map<Integer, String>>>();
 
public static File genere(String modele, String pathDest, String fileDest, Map<Integer, List<Map<String, Object>>> liste, Map<Integer, Map<String, Object>> values) {
public static File genere(String modele, File pathDest, String fileDest, Map<Integer, List<Map<String, Object>>> liste, Map<Integer, Map<String, Object>> values) {
return genere(modele, pathDest, fileDest, liste, values, new HashMap<Integer, Map<Integer, String>>(), null, null);
}
 
public static File genere(String modele, String pathDest, String fileDest, Map<Integer, List<Map<String, Object>>> liste, Map<Integer, Map<String, Object>> values,
public static File genere(String templateId, File pathDest, String fileDest, Map<Integer, List<Map<String, Object>>> liste, Map<Integer, Map<String, Object>> values,
Map<Integer, Map<Integer, String>> mapStyle, List<String> sheetName, SQLRow rowLanguage) {
// SQLRow row = elt.getTable().getRow(id);
cacheStyle.clear();
SAXBuilder builder = new SAXBuilder();
final SAXBuilder builder = new SAXBuilder();
try {
Document doc = builder.build(getXmlTemplate(modele, rowLanguage));
InputStream xmlConfiguration = TemplateManager.getInstance().getTemplateConfiguration(templateId, rowLanguage != null ? rowLanguage.getString("CHEMIN") : null, null);
Document doc = builder.build(xmlConfiguration);
 
// On initialise un nouvel élément racine avec l'élément racine du
// document.
Element racine = doc.getRootElement();
final Element racine = doc.getRootElement();
 
// Création et génération du fichier OO
SpreadSheet spreadSheet = SpreadSheet.create(new ODPackage(getOOTemplate(modele, rowLanguage)));
final InputStream template = TemplateManager.getInstance().getTemplate(templateId, rowLanguage != null ? rowLanguage.getString("CHEMIN") : null, null);
 
final SpreadSheet spreadSheet = new ODPackage(template).getSpreadSheet();
Sheet sheet0 = spreadSheet.getSheet(0);
if (sheetName != null && sheetName.size() > 0) {
for (int i = 1; i < sheetName.size(); i++) {
sheet0.copy(i, (sheetName != null) ? sheetName.get(i) : "Feuille " + i);
}
 
spreadSheet.getSheet(0).setName(sheetName.get(0));
 
System.err.println("add " + sheetName.size() + " sheet");
 
}
 
for (Integer i : liste.keySet()) {
86,7 → 84,6
children = racine.getChildren("element");
}
parseElementsXML(children, sheet, values.get(i));
 
Element child = racine.getChild("table" + i);
if (child == null) {
child = racine.getChild("table");
94,15 → 91,11
parseListeXML(child, liste.get(i), sheet, mapStyle.get(i));
}
// Sauvegarde du fichier
return saveSpreadSheet(spreadSheet, new File(pathDest), fileDest, modele, rowLanguage);
return saveSpreadSheet(spreadSheet, pathDest, fileDest, templateId, rowLanguage);
 
} catch (JDOMException e) {
 
e.printStackTrace();
ExceptionHandler.handle("Erreur lors de la génération du fichier " + fileDest, e);
} catch (IOException e) {
 
e.printStackTrace();
ExceptionHandler.handle("Erreur lors de la création du fichier " + fileDest, e);
}
return null;
478,7 → 471,7
* @return un File pointant sur le fichier créé
* @throws IOException
*/
private static File saveSpreadSheet(SpreadSheet ssheet, File pathDest, String fileName, String modele, SQLRow rowLanguage) throws IOException {
private static File saveSpreadSheet(SpreadSheet ssheet, File pathDest, String fileName, String templateId, SQLRow rowLanguage) throws IOException {
 
// Test des arguments
if (ssheet == null || pathDest == null || fileName.trim().length() == 0) {
492,7 → 485,7
pathDest.mkdirs();
}
 
SheetUtils.getInstance().convertToOldFile(fileName, pathDest, fDest);
SheetUtils.convertToOldFile(fileName, pathDest, fDest);
 
// Sauvegarde
try {
515,7 → 508,7
// Copie de l'odsp
try {
File odspOut = new File(pathDest, fileName + ".odsp");
InputStream odspIn = getTemplate(modele + ".odsp", rowLanguage);
InputStream odspIn = TemplateManager.getInstance().getTemplatePrintConfiguration(templateId, rowLanguage != null ? rowLanguage.getString("CHEMIN") : null, null);
if (odspIn != null) {
StreamUtils.copy(odspIn, odspOut);
}
526,34 → 519,7
return fDest;
}
 
public static InputStream getOOTemplate(String name, SQLRow rowLanguage) throws FileNotFoundException {
return getTemplate(name + ".ods", rowLanguage);
}
 
public static InputStream getXmlTemplate(String name, SQLRow rowLanguage) throws FileNotFoundException {
return getTemplate(name + ".xml", rowLanguage);
}
 
/**
* Permet d'obtenir l'emplacement du modele passé en argument. Si le modéle ne se pas trouve
* dans le répertoire spécifié dans les préférences alors on recherche dans le répertoire par
* défaut des modéles.
*
* @param name nom du modéle à trouver
* @return le modéle
* @throws FileNotFoundException si le fichier est introuvable
*/
public static InputStream getTemplate(String name, SQLRow rowLanguage) throws FileNotFoundException {
String modelDir = TemplateNXProps.getInstance().getStringProperty("LocationTemplate");
 
if (rowLanguage != null) {
return ComptaBasePropsConfiguration.getStream(name, modelDir + File.separator + rowLanguage.getString("CHEMIN"), modelDir, SpreadSheetGenerator.defaultLocationTemplate);
} else {
return ComptaBasePropsConfiguration.getStream(name, modelDir, SpreadSheetGenerator.defaultLocationTemplate);
}
}
 
/**
* parcourt l'ensemble de la feuille pour trouver les style définit
*/
private static Map<String, Map<Integer, String>> searchStyle(Sheet sheet, int colEnd, int rowEnd) {
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/AbstractSheetXml.java
14,8 → 14,10
package org.openconcerto.erp.generationDoc;
 
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.utils.StringUtils;
 
import java.io.File;
import java.io.InputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
 
23,35 → 25,36
import javax.swing.SwingUtilities;
 
public abstract class AbstractSheetXml extends SheetXml {
private File generatedOpenDocumentFile;
 
public AbstractSheetXml(SQLRow row) {
this.row = row;
}
 
public final Future<File> genere(final boolean visu, final boolean impression) {
Callable<File> c = new Callable<File>() {
@Override
public File call() throws Exception {
public final Future<SheetXml> createDocumentAsynchronous() {
Callable<SheetXml> c = new Callable<SheetXml>() {
@Override
public SheetXml call() throws Exception {
try {
String modele = getModele();
final String modeleFinal = modele;
try {
OOgenerationXML.getOOTemplate(modele, getRowLanguage());
} catch (Exception e) {
String templateId = getTemplateId();
final String modeleFinal = templateId;
 
String langage = getRowLanguage() != null ? getRowLanguage().getString("CHEMIN") : null;
InputStream templateStream = TemplateManager.getInstance().getTemplate(templateId, langage, getType());
if (templateStream == null) {
SwingUtilities.invokeLater(new Runnable() {
 
@Override
public void run() {
// TODO Raccord de méthode auto-généré
JOptionPane.showMessageDialog(null, "Impossible de trouver le modele " + modeleFinal + ". \n Le modéle par défaut sera utilisé!");
}
});
modele = getDefaultModele();
templateId = getDefaultTemplateId();
}
File fGen = OOgenerationXML.genere(modele, AbstractSheetXml.this.locationOO, getFileName(), AbstractSheetXml.this.row, getRowLanguage());
AbstractSheetXml.this.f = fGen;
useOO(fGen, visu, impression, getFileName());
return fGen;
AbstractSheetXml.this.generatedOpenDocumentFile = OOgenerationXML.createDocument(templateId, getDocumentOutputDirectory(), getValidFileName(getName()), AbstractSheetXml.this.row,
getRowLanguage());
 
} catch (Exception e) {
DEFAULT_HANDLER.uncaughtException(null, e);
// rethrow exception so that the unsuspecting caller can use this as the
59,11 → 62,27
throw e;
} catch (Throwable e) {
DEFAULT_HANDLER.uncaughtException(null, e);
return null;
}
 
}
return AbstractSheetXml.this;
}
};
return runnableQueue.submit(c);
}
 
public String getType() {
return null;
}
 
@Override
public String getStoragePathP() {
return StringUtils.firstUp(elt.getPluralName());
}
 
@Override
public File getGeneratedFile() {
if (this.generatedOpenDocumentFile == null)
this.generatedOpenDocumentFile = new File(getDocumentOutputDirectory(), getValidFileName(getName()) + ".ods");
return generatedOpenDocumentFile;
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/SpreadSheetGenerator.java
193,7 → 193,7
return null;
}
}
final SpreadSheet res = SpreadSheet.create(new ODPackage(f));
final SpreadSheet res = new ODPackage(f).getSpreadSheet();
f.close();
return res;
}
250,9 → 250,9
 
this.modele = sheet.modele;
this.mCell = sheet.mCell;
this.destDirOO = new File(sheet.locationOO);
this.destDirOO = sheet.getDocumentOutputDirectory();
this.destDirOO.mkdirs();
this.destDirPDF = new File(sheet.locationPDF);
this.destDirPDF = sheet.getPDFOutputDirectory();
this.destDirPDF.mkdirs();
this.nbPage = sheet.nbPage;
this.nbRowsPerPage = sheet.nbRowsPerPage;
272,7 → 272,7
try {
 
f = generateWithStyle();
 
final File pdfFileToCreate = new File(this.destDirPDF.getAbsolutePath(), this.destFileName + ".pdf");
try {
 
if (!Boolean.getBoolean("org.openconcerto.oo.useODSViewer")) {
279,7 → 279,8
 
final Component doc = ComptaPropsConfiguration.getOOConnexion().loadDocument(f, !this.visu);
if (this.exportPDF) {
doc.saveToPDF(new File(this.destDirPDF.getAbsolutePath(), this.destFileName + ".pdf"));
 
doc.saveToPDF(pdfFileToCreate);
}
 
if (this.impression) {
295,7 → 296,7
PreviewFrame.show(f);
}
 
SheetUtils.getInstance().convert2PDF(doc, f, this.destFileName);
SheetUtils.convert2PDF(doc, pdfFileToCreate);
if (this.impression) {
// Print !
DefaultNXDocumentPrinter printer = new DefaultNXDocumentPrinter();
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/AbstractListeSheetXml.java
13,6 → 13,9
package org.openconcerto.erp.generationDoc;
 
import static org.openconcerto.erp.generationDoc.SheetXml.getValidFileName;
import org.openconcerto.utils.StringUtils;
 
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
35,16 → 38,21
// Nom des feuilles
protected List<String> sheetNames = new ArrayList<String>();
 
public final Future<File> genere(final boolean visu, final boolean impression) {
Callable<File> c = new Callable<File>() {
private File generatedOpenDocumentFile;
 
public AbstractListeSheetXml() {
generatedOpenDocumentFile = new File(getDocumentOutputDirectory(), getValidFileName(getName()) + ".ods");
}
 
public final Future<SheetXml> createDocumentAsynchronous() {
Callable<SheetXml> c = new Callable<SheetXml>() {
@Override
public File call() throws Exception {
public SheetXml call() throws Exception {
try {
createListeValues();
File fGen = OOgenerationListeXML.genere(getModele(), locationOO, getFileName(), listAllSheetValues, mapAllSheetValues, styleAllSheetValues, sheetNames, null);
AbstractListeSheetXml.this.f = fGen;
useOO(fGen, visu, impression, getFileName());
return fGen;
generatedOpenDocumentFile = OOgenerationListeXML.genere(getTemplateId(), getDocumentOutputDirectory(), getValidFileName(getName()), listAllSheetValues, mapAllSheetValues,
styleAllSheetValues, sheetNames, null);
return AbstractListeSheetXml.this;
} catch (Exception e) {
DEFAULT_HANDLER.uncaughtException(null, e);
// rethrow exception so that the unsuspecting caller can use this as the
57,7 → 65,7
 
}
};
return this.runnableQueue.submit(c);
return runnableQueue.submit(c);
}
 
/**
64,4 → 72,24
* To fill listAllSheetValues, styleAllSheetValues, mapAllSheetValues, sheetNames
*/
protected abstract void createListeValues();
 
@Override
public String getStoragePathP() {
return StringUtils.firstUp(elt.getPluralName());
}
 
@Override
public File getGeneratedFile() {
return generatedOpenDocumentFile;
}
 
@Override
public File getDocumentOutputDirectoryP() {
return DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory(this.getTemplateId());
}
 
@Override
public File getPDFOutputDirectoryP() {
return DocumentLocalStorageManager.getInstance().getPDFOutputDirectory(this.getTemplateId());
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/DocumentGeneratorManager.java
New file
0,0 → 1,66
/*
* 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.erp.generationDoc;
 
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
 
public class DocumentGeneratorManager {
private static DocumentGeneratorManager instance = new DocumentGeneratorManager();
private Map<String, DocumentGenerator> generators = new HashMap<String, DocumentGenerator>();
private DocumentGenerator defautGenerator;
 
public static DocumentGeneratorManager getInstance() {
return instance;
}
 
public void add(String templateId, DocumentGenerator generator) {
this.generators.put(templateId, generator);
}
 
public void remove(DocumentGenerator generator) {
final Set<String> keys = generators.keySet();
for (String key : keys) {
if (generators.get(key).equals(generator)) {
generators.remove(key);
}
}
}
 
public void setDefaultGenerator(DocumentGenerator generator) {
this.defautGenerator = generator;
}
 
/**
* Returns the document generator a specific templateId
* */
public DocumentGenerator getGenerator(String templateId) {
DocumentGenerator generator = this.generators.get(templateId);
if (generator == null) {
generator = defautGenerator;
}
return generator;
}
 
public void dump() {
System.out.println(this.getClass().getCanonicalName());
System.out.println("Default generator:" + this.defautGenerator);
Set<String> ids = generators.keySet();
for (String templateId : ids) {
System.out.println("'" + templateId + "' : " + generators.get(templateId));
}
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/AbstractJOOReportsSheet.java
41,7 → 41,6
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
 
 
public abstract class AbstractJOOReportsSheet {
private static final String defaultLocationTemplate = SpreadSheetGenerator.defaultLocationTemplate;
protected static final DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
49,8 → 48,7
protected static final DateFormat yearFormat = new SimpleDateFormat("yyyy");
private String year;
protected String locationTemplate = TemplateNXProps.getInstance().getStringProperty("LocationTemplate");
private String locationOO, locationPDF;
protected String templateFileName;
protected String templateId;
private String printer;
protected boolean askOverwriting = false;
 
61,11 → 59,9
 
abstract public String getFileName();
 
protected void init(String year, String templateFileName, String attributePrinter, Tuple2<String, String> t) {
protected void init(String year, String templateId, String attributePrinter) {
this.year = year;
this.templateFileName = templateFileName;
this.locationOO = SheetXml.getLocationForTuple(t, false) + File.separator + this.year;
this.locationPDF = SheetXml.getLocationForTuple(t, true) + File.separator + this.year;
this.templateId = templateId;
this.printer = PrinterNXProps.getInstance().getStringProperty(attributePrinter);
}
 
88,27 → 84,23
try {
 
String fileName = getFileName();
final InputStream fileTemplate = getStream(this.templateFileName, this.locationTemplate, defaultLocationTemplate);
 
File fileOutOO = new File(this.locationOO, fileName + ".odt");
// File fileOutPDF = new File(locationPropositionPDF, fileName);
 
final InputStream fileTemplate = TemplateManager.getInstance().getTemplate(this.templateId);
File outputDir = DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory(this.templateId);
File fileOutOO = getDocumentFile();
if (fileOutOO.exists() && overwrite) {
if (this.askOverwriting) {
int answer = JOptionPane.showConfirmDialog(null, "Voulez vous écraser le document ?", "Remplacement d'un document", JOptionPane.YES_NO_OPTION);
if (answer == JOptionPane.YES_OPTION) {
SheetUtils.getInstance().convertToOldFile(fileName, new File(this.locationOO), fileOutOO, ".odt");
SheetUtils.convertToOldFile(fileName, outputDir, fileOutOO, ".odt");
}
} else {
SheetUtils.getInstance().convertToOldFile(fileName, new File(this.locationOO), fileOutOO, ".odt");
SheetUtils.convertToOldFile(fileName, outputDir, fileOutOO, ".odt");
}
}
 
if (!fileOutOO.exists()) {
fileOutOO.getParentFile().mkdirs();
Template template;
// try {
template = new Template(new BufferedInputStream(fileTemplate));
Template template = new Template(new BufferedInputStream(fileTemplate));
 
// creation du document
final Map createMap = createMap();
116,11 → 108,7
 
model.putAll(createMap);
template.createDocument(model).saveAs(fileOutOO);
// template.createDocument(model, new BufferedOutputStream(new
// FileOutputStream(fileOutOO)));
// } catch (JDOMException e) {
// e.printStackTrace();
// }
 
}
 
// ouverture de OO
133,9 → 121,10
}
final Component doc = ooConnexion.loadDocument(fileOutOO, !show);
 
if (this.savePDF())
doc.saveToPDF(new File(this.locationPDF, fileName + ".pdf"), "writer_pdf_Export");
 
if (this.savePDF()) {
File pdfOutputDir = DocumentLocalStorageManager.getInstance().getPDFOutputDirectory(templateId);
doc.saveToPDF(new File(pdfOutputDir, fileName + ".pdf"), "writer_pdf_Export");
}
if (print) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("Name", printer);
164,7 → 153,7
}
 
public void showDocument() {
File fileOutOO = new File(this.locationOO, getFileName() + ".odt");
File fileOutOO = getDocumentFile();
if (fileOutOO.exists()) {
try {
final OOConnexion ooConnexion = ComptaPropsConfiguration.getOOConnexion();
183,8 → 172,13
}
}
 
private File getDocumentFile() {
File outputDir = DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory(templateId);
return new File(outputDir, getFileName() + ".odt");
}
 
public void printDocument() {
File fileOutOO = new File(this.locationOO, getFileName() + ".odt");
File fileOutOO = getDocumentFile();
if (fileOutOO.exists()) {
 
try {
211,7 → 205,7
 
public void fastPrintDocument() {
 
final File f = new File(this.locationOO, getFileName() + ".odt");
final File f = getDocumentFile();
 
if (!f.exists()) {
generate(true, false, this.printer);
239,13 → 233,6
}
}
 
public boolean exists() {
 
String fileName = getFileName();
File fileOutOO = new File(this.locationOO, fileName + ".odt");
return fileOutOO.exists();
}
 
protected String getInitiales(SQLRow row) {
String init = "";
if (row != null) {
264,9 → 251,10
public void exportToPdf() {
 
// Export vers PDF
String fileName = getFileName();
File fileOutOO = new File(this.locationOO, fileName + ".odt");
File fileOutPDF = new File(this.locationPDF, fileName);
final String fileName = getFileName();
final File fileOutOO = getDocumentFile();
final File outputPDFDirectory = DocumentLocalStorageManager.getInstance().getPDFOutputDirectory(this.templateId);
final File fileOutPDF = new File(outputPDFDirectory, fileName + ".pdf");
 
if (!fileOutOO.exists()) {
generate(false, false, "");
290,7 → 278,7
int result = JOptionPane.showOptionDialog(null, "Ouvrir le pdf ?", "Ouverture du PDF", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null);
 
if (result == JOptionPane.YES_OPTION) {
Gestion.openPDF(new File(fileOutPDF.getAbsolutePath() + ".pdf"));
Gestion.openPDF(fileOutPDF);
} else {
try {
FileUtils.openFile(fileOutPDF.getParentFile());
317,10 → 305,6
return ville.getName();
}
 
public String getLocationOO() {
return locationOO;
}
 
protected static String getVilleCP(String name) {
Ville ville = Ville.getVilleFromVilleEtCode(name);
if (ville == null) {
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/DocumentGenerator.java
New file
0,0 → 1,27
/*
* 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.erp.generationDoc;
 
import java.util.Map;
 
public interface DocumentGenerator {
 
/**
* Define the context for the creation of the document Keys of the context are defined in the
* objects implementing the interface
* */
public void setContext(Map<String, Object> context);
 
public void createDocument();
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/AbstractLocalTemplateProvider.java
New file
0,0 → 1,73
/*
* 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.erp.generationDoc;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
 
public abstract class AbstractLocalTemplateProvider implements TemplateProvider {
 
@Override
public InputStream getTemplate(String templateId, String langage, String type) {
try {
final File templateFile = getTemplateFile(templateId, langage, type);
if (templateFile == null || !templateFile.exists()) {
return null;
}
return new FileInputStream(templateFile);
} catch (FileNotFoundException e) {
return null;
}
}
 
@Override
public InputStream getTemplatePrintConfiguration(String templateId, String langage, String type) {
final File t = getTemplateFile(templateId, langage, type);
final String name = t.getName();
if (name.toLowerCase().endsWith(".ods")) {
final File file = new File(t.getParent(), name.substring(0, name.length() - 4) + ".odsp");
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
System.err.println("No print configuration " + file.getAbsolutePath() + " for template id: " + templateId);
e.printStackTrace();
}
}
return null;
}
 
@Override
public InputStream getTemplateConfiguration(String templateId, String langage, String type) {
final File t = getTemplateFile(templateId, langage, type);
final String name = t.getName();
if (name.toLowerCase().endsWith(".ods")) {
final File file = new File(t.getParent(), name.substring(0, name.length() - 4) + ".xml");
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
System.err.println("No template configuration " + file.getAbsolutePath() + " for template id: " + templateId);
e.printStackTrace();
}
}
return null;
}
 
public abstract File getTemplateFile(String templateId, String langage, String type);
 
@Override
public abstract String getTemplatePath(String templateId, String langage, String type);
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/TemplateProvider.java
New file
0,0 → 1,38
/*
* 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.erp.generationDoc;
 
import java.io.InputStream;
 
public interface TemplateProvider {
/**
* Path: ex ILM/Devis
* */
public String getTemplatePath(String templateId, String language, String type);
 
/**
* Returns the content of template file (ex: the ODS file)
* */
public InputStream getTemplate(String templateId, String language, String type);
 
/**
* Returns the content of template configuration file (ex: the odsp file)
* */
public InputStream getTemplateConfiguration(String templateId, String language, String type);
 
/**
* Returns the content of template print configuration file (ex: the odsp file)
* */
public InputStream getTemplatePrintConfiguration(String templateId, String language, String type);
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/SheetInterface.java
18,11 → 18,12
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.utils.StringUtils;
 
import java.io.File;
import java.util