OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Compare Revisions

Regard whitespace Rev 181 → Rev 182

/trunk/OpenConcerto/.classpath
37,16 → 37,16
<classpathentry kind="lib" path="lib/icudata_56.jar"/>
<classpathentry kind="lib" path="lib/javax.mail-1.6.0.jar"/>
<classpathentry kind="lib" path="lib/json-smart-2.2.1.jar"/>
<classpathentry kind="lib" path="lib/mysql-connector-java-5.1.40-bin.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry exported="true" kind="lib" path="lib/poi-3.17.jar"/>
<classpathentry kind="lib" path="lib/piccolo-1.04.jar"/>
<classpathentry kind="lib" path="lib/fontbox-2.0.19.jar"/>
<classpathentry kind="lib" path="lib/javax.activation-1.1.1.jar"/>
<classpathentry kind="lib" path="lib/pdfbox-2.0.19.jar"/>
<classpathentry kind="lib" path="lib/pdfbox.jar"/>
<classpathentry kind="lib" path="lib/flatlaf-0.41.jar"/>
<classpathentry kind="lib" path="lib/postgresql-42.2.8.jar"/>
<classpathentry kind="lib" path="lib/jsch-0.1.55.jar"/>
<classpathentry exported="true" kind="lib" path="lib/poi-5.0.0.jar"/>
<classpathentry kind="lib" path="lib/mysql-connector-java-5.1.45-bin.jar"/>
<classpathentry kind="lib" path="lib/flatlaf-1.2.jar"/>
<classpathentry kind="lib" path="lib/fontbox-2.0.22.jar"/>
<classpathentry kind="lib" path="lib/pdfbox-2.0.22.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
/trunk/OpenConcerto/lib/pdfbox-2.0.19.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/poi-3.17.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/flatlaf-0.41.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/fontbox-2.0.19.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mysql-connector-java-5.1.40-bin.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/poi-4.1.0.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/pdfbox-2.0.22.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/pdfbox-2.0.22.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/jOpenCalendar.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/flatlaf-1.2.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/flatlaf-1.2.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/mysql-connector-java-5.1.45-bin.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/mysql-connector-java-5.1.45-bin.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/jOpenDocument-1.4rc2.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/poi-5.0.0.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/poi-5.0.0.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/jOpenDocument-1.4rc2.badSecurity.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/jOpenDocument-1.4rc2.badSecurity.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/pdfbox2d.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/pdfbox2d.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/lib/fontbox-2.0.22.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/lib/fontbox-2.0.22.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/Configuration/Template/Labels/test.graphicspl
New file
0,0 → 1,30
<?xml version="1.0" encoding="UTF-8" ?>
<graphicspl width="600" height="400" dpi="300"
printratio="0.5">
<rectangle fill="true" x="0" y="380" width="600" height="20"
color="#FF000" />
 
<image x="420" y="20" width="160" height="160" file="icon.png" />
 
 
 
<text x="10" y="10" fontsize="20" font="Verdana">OpenConcerto</text>
 
<text x="390" y="60" fontsize="40" font="Verdana" align="right"
wrap="true" maxwidth="200">Atome Premium</text>
 
<text x="390" y="140" fontsize="40" font="Verdana" align="right"
color="#12000">56.00 € HT</text>
 
 
 
<barcode x="10" y="120" modulewidth="4" type="datamatrix"
width="200">OpenConcerto</barcode>
<barcode x="10" y="220" type="gs1" height="100" modulewidth="6"
fontsize="25">1234566546
</barcode>
<text x="300" y="200" fontsize="10" font="Verdana" align="center"
color="#666666">ARTICLE EN PROMOTION - NON ECHANGEABLE</text>
<rectangle fill="false" x="0" y="0" width="599" height="399" />
 
</graphicspl>
/trunk/OpenConcerto/Configuration/Template/Labels/icon.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Labels/icon.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/Configuration/Template/Default/DemandePrix.odsp
2,8 → 2,4
<spliteveryrow>
<sheet number="0">58</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
 
 
</odsp>
/trunk/OpenConcerto/Configuration/Template/Default/Commande.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Relance2.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/GrandLivre.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/RepartitionAnalytique.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FichePaye.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Commande.odsp
1,6 → 1,6
<odsp>
<spliteveryrow>
<sheet number="0">57</sheet>
<sheet number="0">58</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
/trunk/OpenConcerto/Configuration/Template/Default/DepotCheque.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/DepotCheque.ods
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/Configuration/Template/Default/BalanceAgee.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Devis.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/BonLivraison.xml
24,7 → 24,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/EtatStockInventaire.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatStockInventaire.ods
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/Configuration/Template/Default/VenteFactureTicket.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/VenteFactureTicket.ods
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/Configuration/Template/Default/LivrePaye.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FichePayeSimplifiee.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatChargesPaye.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Avoir.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/AvoirF.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/CommandeClient.xml
24,7 → 24,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
62,7 → 62,7
<field name="NOM" />
</element>
 
<element location="I10" type="fill">
<element location="H10" type="fill">
<field name="ID_CLIENT">
<field name="FORME_JURIDIQUE" />
<field name="NOM" />
69,7 → 69,7
</field>
</element>
 
<element location="I11" type="address.customer.full">
<element location="H11" type="address.customer.full">
 
</element>
 
/trunk/OpenConcerto/Configuration/Template/Default/FactureFournisseur.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/DemandePrix.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/BonReception.xml
25,7 → 25,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/JournauxAnalytique.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ReleveCheque.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/DIPE.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatVentes.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ReportingClient.xml
New file
0,0 → 1,36
<?xml version="1.0" encoding="UTF-8" ?>
<contentDocument>
<element location="C2" type="Value" ValueName="DATE">
</element>
 
<element location="C5" type="Value" ValueName="CLIENT" >
</element>
<table firstLine="8" endLine="305" endPageLine="305" lastColumn="H" >
<element location="B" type="fill">
<field base="Societe" name="NUMERO_FACTURE"/>
</element>
<element location="C" type="fill">
<field base="Societe" name="DATE"/>
</element>
<element location="D" type="fill" cellSize="24">
<field base="Societe" name="NOM"/>
</element>
<element location="E" type="fill">
<field base="Societe" name="ECHEANCE"/>
</element>
<element location="F" type="fill">
<field base="Societe" name="T_TTC"/>
</element>
<element location="G" type="fill">
<field base="Societe" name="REGLE"/>
</element>
 
<element location="H" type="fill">
<field base="Societe" name="DU"/>
</element>
</table>
</contentDocument>
/trunk/OpenConcerto/Configuration/Template/Default/FichePayeSimplifiee.odsp
1,6 → 1,7
<odsp>
<spliteveryrow>
<sheet number="0">61</sheet>
<sheet number="0">65</sheet>
</spliteveryrow>
<offset x="20" y ="20"/>
<resize percent="92"/>
<offset x="23" y ="0"/>
</odsp>
/trunk/OpenConcerto/Configuration/Template/Default/Balance.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/SituationCompte.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/SituationCompte.ods
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/Configuration/Template/Default/Courrier.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Commande.xml
25,7 → 25,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/JournauxMois.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/BonReception.odsp
1,6 → 1,6
<odsp>
<spliteveryrow>
<sheet number="0">57</sheet>
<sheet number="0">58</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
/trunk/OpenConcerto/Configuration/Template/Default/DepotCheque.xml
New file
0,0 → 1,60
<?xml version="1.0" encoding="UTF-8" ?>
<contentDocument>
 
<element location="H1" type="fill">
<field base="Societe" name="DATE">
</field>
</element>
<element location="K56" type="fill">
<field base="Societe" name="MONTANT" type="devise">
</field>
</element>
<element location="A55" type="fill">
<field base="Societe" name="NOM">
</field>
</element>
<element location="A56" type="fill">
<field base="Societe" name="ID_BANQUE">
<field base="Societe" name="NOM" prefix ="Dépôt sur la banque "/>
</field>
</element>
 
<table endPageLine="56" firstLine="5" endLine="53" lastColumn="L" base="Societe" table="DEPOT_CHEQUE_ELEMENT">
 
<element location="A" type="fill">
<field name="BANQUE" />
</element>
<element location="B" type="fill">
<field name="DATE" />
</element>
 
<element location="C" type="fill">
<field name="NUMERO" />
</element>
 
<element location="E" type="fill">
<field name="ID_CLIENT" >
<field name="NOM" />
</field>
</element>
 
<element location="J" type="fill">
<field name="PIECE" />
</element>
 
<element location="K" type="fill">
<field name="MONTANT" type="devise"/>
</element>
<element location="L" type="LineReference">
<field name="MONTANT" type="LineReference"/>
</element>
</table>
</contentDocument>
/trunk/OpenConcerto/Configuration/Template/Default/FicheArticle.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Devis.xml
23,7 → 23,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
62,7 → 62,7
<field name="OBJET" />
</element>
 
<element location="I10" type="fill">
<element location="H10" type="fill">
<field name="ID_CLIENT">
<field name="FORME_JURIDIQUE" />
<field name="NOM" />
69,7 → 69,7
</field>
</element>
 
<element location="I11" type="address.customer.full">
<element location="H11" type="address.customer.full">
</element>
 
<element location="L63" type="fill">
/trunk/OpenConcerto/Configuration/Template/Default/DepotCheque.odsp
New file
0,0 → 1,9
<odsp>
<spliteveryrow>
<sheet number="0">56</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
 
 
</odsp>
/trunk/OpenConcerto/Configuration/Template/Default/Relance1.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/VenteFacture.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/VenteFactureSituation.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Relance3.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatStockInventaire.xml
New file
0,0 → 1,33
<?xml version="1.0" encoding="UTF-8" ?>
<contentDocument>
<!-- <element location="A3" type="Value" ValueName="DATE">
</element>
-->
<table firstLine="2" endLine="17000" endPageLine="25000" lastColumn="E" table="SAISIE_VENTE_FACTURE">
<element location="A" type="fill">
<field name="FAMILLE" />
</element>
<element location="B" type="fill">
<field name="CODE" />
</element>
 
<element location="C" type="fill">
<field name="NOM" />
</element>
<element location="D" type="fill">
<field name="TAILLE" />
</element>
<element location="E" type="fill">
<field name="COULEUR" />
</element>
<element location="F" type="fill">
<field name="QTE" />
</element>
 
</table>
</contentDocument>
/trunk/OpenConcerto/Configuration/Template/Default/EtatStockInventaire.odsp
New file
0,0 → 1,9
<odsp>
<spliteveryrow>
<sheet number="0">57</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
 
 
</odsp>
/trunk/OpenConcerto/Configuration/Template/Default/VenteFactureTicket.xml
New file
0,0 → 1,90
<?xml version="1.0" encoding="UTF-8" ?>
<contentDocument>
 
<element0 location="B1" type="Value" ValueName="SOCIETE_NOM">
</element0>
 
<element0 location="B2" type="Value" ValueName="SOCIETE_RUE">
</element0>
 
<element0 location="B3" type="Value" ValueName="SOCIETE_CODE_POSTAL_VILLE">
</element0>
 
<element0 location="B5" type="Value" ValueName="SOCIETE_TYPE_CAPITAL">
</element0>
 
<element0 location="B6" type="Value" ValueName="SOCIETE_SIRET" prefix="N° de SIREN ">
</element0>
 
<element0 location="B7" type="Value" ValueName="SOCIETE_TVA" prefix="N° de TVA ">
</element0>
 
<element0 location="B8" type="Value" ValueName="SOCIETE_TEL" prefix="N° de Téléphone ">
</element0>
 
<element0 location="B10" type="Value" ValueName="SOCIETE_MAIL" prefix="Email ">
</element0>
 
<element0 location="B13" type="Value" ValueName="NUMERO">
</element0>
 
<element0 location="C13" type="Value" ValueName="DATE">
</element0>
 
<element0 location="H10" type="Value" ValueName="CLIENT">
</element0>
 
<element0 location="H11" type="Value" ValueName="ADRESSE">
</element0>
 
<element0 location="L62" type="Value" ValueName="T_HT">
</element0>
<element0 location="L65" type="Value" ValueName="T_TTC">
</element0>
 
<element0 location="L63" type="Value" ValueName="T_TVA_1">
</element0>
<element0 location="I63" type="Value" ValueName="TVA_1">
</element0>
 
<element0 location="L64" type="Value" ValueName="T_TVA_2">
</element0>
<element0 location="I64" type="Value" ValueName="TVA_2">
</element0>
 
<table0 endPageLine="66" firstLine="20" endLine="60" blankLineBeforeStyle="Titre 1,Titre 2" lastColumn="K"
base="Societe" table="SAISIE_VENTE_FACTURE_ELEMENT" pageRef="L16">
<element location="B" type="fill" cellSize="45">
<field name="CODE" />
</element>
<element location="C" type="fill" cellSize="45">
<field name="NOM" />
</element>
<element location="H" type="fill">
<field name="POURCENT_REMISE">
<exclude value="0.000000" />
</field>
</element>
<element location="I" type="fill">
<field name="PV_HT" type="devise">
<exclude value="0.000000" />
</field>
</element>
<element location="J" type="fill">
<field name="QTE" />
</element>
<element location="K" type="fill">
<field name="TVA">
</field>
</element>
<element location="L" type="fill">
<field name="T_HT_REMISE" type="devise">
<exclude value="0.000000" />
</field>
</element>
 
</table0>
</contentDocument>
/trunk/OpenConcerto/Configuration/Template/Default/Journaux.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FichePayeSimplifiee.xml
47,7 → 47,7
<field name="AU" prefix=" au " type="date" datePattern="dd/MM/yy" />
</element>
 
<element location="D54" type="fill">
<element location="D58" type="fill">
<field name="DU" type="datePaye" />
</element>
 
89,7 → 89,7
</element>
<element location="F12" type="fill">
<field name="ID_CUMULS_CONGES">
<field name="ACQUIS" />
<field name="ACQUIS" type="cumulConges" />
</field>
</element>
<element location="B6" type="replace" replacePattern="_">
122,79 → 122,159
</element>
 
 
<element location="I54" type="fill">
<field name="NET_A_PAYER" />
<element location="H54" type="fill">
<field name="NET_AVANT_PAS" />
</element>
 
<element location="I55" type="fill">
<field name="REDUCTION_GVT" />
</element>
 
<element location="D58" type="fill">
<element location="E57" type="fill">
<field name="NET_IMP" />
</element>
<element location="F57" type="fill">
<field name="ID_PAS">
<field name="TAUX_PAS" />
</field>
</element>
<element location="I57" type="fill">
<field name="TOTAL_PAS" />
</element>
 
 
 
 
<element location="I58" type="fill">
<field name="NET_A_PAYER" />
</element>
 
<element location="D62" type="fill">
<field name="SAL_BRUT" />
</element>
<element location="E58" type="fill">
<element location="E62" type="fill">
<field name="COT_SAL" />
</element>
<element location="F58" type="fill">
<element location="F62" type="fill">
<field name="COT_PAT" />
</element>
 
<element location="G58" type="fill">
<element location="G62" type="fill">
<field name="AVANTAGE_NATURE" />
</element>
 
<element location="H58" type="fill">
<element location="H62" type="fill">
<field name="NET_IMP" />
</element>
 
<element location="I58" type="fill">
<element location="I62" type="fill">
<field name="ALLEGEMENT_COTISATION" />
</element>
 
<element location="D59" type="fill">
<element location="D63" type="fill">
<field name="SAL_BRUT" type="cumulPaye" />
</element>
<element location="E59" type="fill">
<element location="E63" type="fill">
<field name="COT_SAL" type="cumulPaye" />
</element>
<element location="F59" type="fill">
<element location="F63" type="fill">
<field name="COT_PAT" type="cumulPaye" />
</element>
<element location="G59" type="fill">
<element location="G63" type="fill">
<field name="AVANTAGE_NATURE" type="cumulPaye" />
</element>
 
<element location="H59" type="fill">
<element location="H63" type="fill">
<field name="NET_IMP" type="cumulPaye" />
</element>
 
<element location="I59" type="fill">
<element location="I63" type="fill">
<field name="ALLEGEMENT_COTISATION" type="cumulPaye" />
</element>
 
<table endPageLine="61" firstLine="15" endLine="51" lastColumn="J" base="Societe" table="FICHE_PAYE_ELEMENT"
<element location="J16" type="fill">
<field name="ID_SALARIE">
<field name="CODE" />
</field>
</element>
<element location="J18" type="fill">
<field name="ID_SALARIE">
<field name="ID_INFOS_SALARIE_PAYE">
<field name="DATE_ARRIVE">
</field>
</field>
</field>
</element>
<element location="J21" type="fichepaye.smic" />
<element location="J23" type="fichepaye.plafond" />
 
<element location="J28" type="fill">
<field name="ID_VARIABLE_SALARIE">
<field name="HEURE_TRAV" />
</field>
</element>
 
<element location="J30" type="fichepaye.heure.sup.total">
</element>
<element location="J32" type="fichepaye.heure.total">
</element>
 
<element location="J35" type="fill">
<field name="ID_VARIABLE_SALARIE">
<field name="HEURE_TRAV_CUMUL_VAL" />
</field>
</element>
<element location="J37" type="fichepaye.heure.sup.cumul.total">
</element>
 
<element location="J39" type="fichepaye.heure.cumul.total">
</element>
 
 
<table endPageLine="65" firstLine="15" endLine="51" lastColumn="J" base="Societe" table="FICHE_PAYE_ELEMENT"
blankLineBeforeStyle="Titre 1,Titre 2" fieldWhere="IMPRESSION" orderBy="POSITION">
<element location="B" type="fill" cellSize="52">
<field name="NOM" />
</element>
<element location="E" type="fill">
<element location="D" type="fill">
<field name="NB_BASE" />
</element>
<element location="E" type="fill">
<field name="TAUX_SAL">
<exclude value="0.00" />
<exclude value="0.00%" />
<exclude value="0.000000" />
</field>
</element>
<element location="F" type="fill">
<field name="TAUX_SAL" />
<field name="MONTANT_SAL_AJ">
<exclude value="0.00" />
<exclude value="0.00%" />
<exclude value="0.000000" />
</field>
</element>
<element location="G" type="fill">
<field name="MONTANT_SAL_AJ" />
<field name="MONTANT_SAL_DED">
<exclude value="0.00" />
<exclude value="0.00%" />
<exclude value="0.000000" />
</field>
</element>
<element location="H" type="fill">
<field name="MONTANT_SAL_DED" />
<field name="TAUX_PAT">
<exclude value="0.00" />
<exclude value="0.00%" />
<exclude value="0.000000" />
</field>
</element>
<!-- <element location="I" type="fill">
<field name="TAUX_PAT" type="Devise" />
</element> -->
<element location="J" type="fill">
<field name="MONTANT_PAT" />
<element location="I" type="fill">
<field name="MONTANT_PAT">
<exclude value="0.00" />
<exclude value="0.00%" />
<exclude value="0.000000" />
</field>
</element>
 
 
/trunk/OpenConcerto/Configuration/Template/Default/Avoir.xml
26,7 → 26,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
 
/trunk/OpenConcerto/Configuration/Template/Default/ReleveChequeEmis.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/AvoirF.xml
27,7 → 27,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/BonLivraison.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FicheRelance.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FactureFournisseur.xml
25,7 → 25,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN " />
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET " />
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/DemandePrix.xml
25,7 → 25,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/FicheClient.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FactureFournisseur.odsp
1,6 → 1,6
<odsp>
<spliteveryrow>
<sheet number="0">57</sheet>
<sheet number="0">58</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
/trunk/OpenConcerto/Configuration/Template/Default/VenteFactureTicket.odsp
New file
0,0 → 1,9
<odsp>
<spliteveryrow>
<sheet number="0">66</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
 
 
</odsp>
/trunk/OpenConcerto/Configuration/Template/Default/EtatVentes.xml
124,4 → 124,28
</element>
</table3>
<element4 location="A4" type="Value" ValueName="DATE">
</element4>
<table4 firstLine="7" endLine="52" endPageLine="54" lastColumn="F" base="Societe" table="SAISIE_VENTE_FACTURE_ELEMENT">
<element location="A" type="fill">
<field base="Societe" name="CODE" />
</element>
 
<element location="B" type="fill">
<field base="Societe" name="NOM" />
</element>
 
<element location="D" type="fill">
<field base="Societe" name="QTE" />
</element>
 
<element location="E" type="fill">
<field base="Societe" name="QTE_REEL" />
</element>
<element location="F" type="fill">
<field base="Societe" name="QTE_MIN" />
</element>
</table4>
</contentDocument>
/trunk/OpenConcerto/Configuration/Template/Default/SituationCompte.xml
New file
0,0 → 1,57
<?xml version="1.0" encoding="UTF-8" ?>
<contentDocument>
 
<element0 location="A1" type="Value" ValueName="SOCIETE_NOM">
</element0>
<element0 location="A2" type="Value" ValueName="SOCIETE_ADRESSE">
</element0>
<element0 location="A4" type="Value" ValueName="SOCIETE_VILLE">
</element0>
<element0 location="A5" type="Value" prefix="Tél:" ValueName="SOCIETE_TEL">
</element0>
 
<element0 location="D6" type="Value" ValueName="NOM_CLIENT">
</element0>
<element0 location="F8" type="Value" ValueName="DATE">
</element0>
<element0 location="G51" type="Value" ValueName="TOTAL_ECHEANCE">
</element0>
<element0 location="G52" type="Value" ValueName="TOTAL_FUTUR">
</element0>
<element0 location="G53" type="Value" ValueName="TOTAL_PASSE">
</element0>
 
<table0 firstLine="17" endLine="50" endPageLine="58" lastColumn="G" base="Societe" table="ECHEANCE_CLIENT" pageRef="F1">
<element location="A" type="fill" useOOFormat="false">
<field base="Societe" name="DATE"/>
</element>
 
<element location="B" type="fill" useOOFormat="false">
<field base="Societe" name="PIECE"/>
</element>
 
 
<element location="C" type="fill" useOOFormat="false" maxChar="25">
<field base="Societe" name="LIBELLE"/>
</element>
 
<element location="D" type="fill" useOOFormat="false">
<field base="Societe" name="DATE_ECHEANCE"/>
</element>
 
<element location="E" type="fill" >
<field base="Societe" name="DU"/>
</element>
 
<element location="F" type="fill" >
<field base="Societe" name="REGLE"/>
</element>
<element location="G" type="fill" >
<field base="Societe" name="SOLDE"/>
</element>
</table0>
</contentDocument>
/trunk/OpenConcerto/Configuration/Template/Default/CommandeClient.odsp
1,6 → 1,6
<odsp>
<spliteveryrow>
<sheet number="0">65</sheet>
<sheet number="0">66</sheet>
</spliteveryrow>
<offset x="40" y ="20"/>
<resize percent="85"/>
/trunk/OpenConcerto/Configuration/Template/Default/ListeDebiteur.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Operations Report.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ReportingTaxeComplementaire.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/CommandeClient.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatStocks.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ExportArticle.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/BonReception.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/VentilationAnalytique.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ListeFacture.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ReportingVentes.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ReportingEcoContribution.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/VenteFactureSituation.xml
24,7 → 24,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/VenteFacture.xml
25,7 → 25,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/SituationCompte.odsp
New file
0,0 → 1,9
<odsp>
<spliteveryrow>
<sheet number="0">58</sheet>
</spliteveryrow>
<offset x="0" y ="0"/>
<resize percent="100"/>
 
 
</odsp>
/trunk/OpenConcerto/Configuration/Template/Default/ReportingClient.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ReportingClient.ods
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/src/org/openconcerto/sql/PropsConfiguration.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
981,7 → 981,7
}
 
protected SQLElementDirectory createDirectory() {
return new SQLElementDirectory();
return new SQLElementDirectory(getSystemRoot());
}
 
// Use resource name to be able to use absolute (beginning with /) or relative path (to this
1201,6 → 1201,7
}
}
 
@Override
public final UserManager getUserManager() {
synchronized (this.treeLock) {
getRoot();
1208,6 → 1209,7
}
}
 
@Override
public final UserRightsManager getUserRightsManager() {
synchronized (this.treeLock) {
getRoot();
/trunk/OpenConcerto/src/org/openconcerto/sql/ui/light/LightUISQLComboRequest.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ITextArticleWithCompletion.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,7 → 14,6
package org.openconcerto.sql.sqlobject;
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
44,8 → 43,10
import java.beans.PropertyChangeSupport;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
 
97,12 → 98,15
private Stack<String> searchStack = new Stack<String>();
private boolean autoselectIfMatch;
private static final int PAUSE_MS = 150;
private final boolean hasDeclinaison;
 
public ITextArticleWithCompletion(SQLTable tableArticle, SQLTable tableARticleFournisseur) {
public ITextArticleWithCompletion(SQLTable tableArticle, SQLTable tableARticleFournisseur, boolean withDeclinaison) {
this.tableArticle = tableArticle;
this.tableArticleFournisseur = tableARticleFournisseur;
this.hasDeclinaison = withDeclinaison;
this.supp = new PropertyChangeSupport(this);
this.popup = new ITextWithCompletionPopUp(this.model, this);
this.popup.setMinWith(450);
this.text = new JTextField();
this.setLayout(new GridLayout(1, 1));
this.add(this.text);
285,16 → 289,31
if (aText.length() > 0) {
 
List<SQLSelect> listSel = new ArrayList<SQLSelect>();
 
// CODE ARTICLE = aText
SQLSelect selMatchingCode = new SQLSelect();
// selMatchingCode.addSelectStar(this.tableArticle);
 
selMatchingCode.addSelect(this.tableArticle.getKey());
selMatchingCode.addSelect(this.tableArticle.getField("CODE"));
selMatchingCode.addSelect(this.tableArticle.getField("NOM"));
selMatchingCode.addSelect(this.tableArticle.getField("CODE_BARRE"));
if (this.hasDeclinaison) {
for (String fieldName : this.tableArticle.getFieldsName()) {
if (fieldName.startsWith("ID_ARTICLE_DECLINAISON_")) {
selMatchingCode.addSelect(this.tableArticle.getField(fieldName));
 
SQLSelect selDecl = new SQLSelect();
SQLTable tableDecl = this.tableArticle.getForeignTable(fieldName);
selDecl.addSelect(tableDecl.getKey());
selDecl.addSelect(tableDecl.getField("NOM"));
listSel.add(selDecl);
}
}
}
Where wMatchingCode = new Where(this.tableArticle.getField("CODE"), "=", aText);
wMatchingCode = wMatchingCode.or(new Where(this.tableArticle.getField("NOM"), "=", aText));
wMatchingCode = wMatchingCode.or(new Where(this.tableArticle.getField("CODE_BARRE"), "=", aText));
wMatchingCode = wMatchingCode.and(new Where(this.tableArticle.getField("VIRTUEL"), "=", Boolean.FALSE));
if (this.whereAdditionnal != null) {
wMatchingCode = wMatchingCode.and(this.whereAdditionnal);
}
311,9 → 330,17
selContains.addSelect(this.tableArticle.getField("CODE"));
selContains.addSelect(this.tableArticle.getField("NOM"));
selContains.addSelect(this.tableArticle.getField("CODE_BARRE"));
if (this.hasDeclinaison) {
for (String fieldName : this.tableArticle.getFieldsName()) {
if (fieldName.startsWith("ID_ARTICLE_DECLINAISON_")) {
selContains.addSelect(this.tableArticle.getField(fieldName));
}
}
}
Where wContains = new Where(this.tableArticle.getField("CODE"), "LIKE", "%" + aText + "%");
wContains = wContains.or(new Where(this.tableArticle.getField("NOM"), "LIKE", "%" + aText + "%"));
wContains = wContains.or(new Where(this.tableArticle.getField("CODE_BARRE"), "LIKE", "%" + aText + "%"));
wContains = wContains.and(new Where(this.tableArticle.getField("VIRTUEL"), "=", Boolean.FALSE));
if (this.whereAdditionnal != null) {
wContains = wContains.and(this.whereAdditionnal);
}
366,8 → 393,15
SQLTable tableCodeArt = this.tableArticle.getDBRoot().getTable("CODE_FOURNISSEUR");
SQLRowValues rowValsCodeF = new SQLRowValues(tableCodeArt);
rowValsCodeF.putNulls("CODE");
rowValsCodeF.putRowValues("ID_ARTICLE").putNulls(this.tableArticle.getFieldsName());
 
final SQLRowValues putRowValuesArt = rowValsCodeF.putRowValues("ID_ARTICLE");
putRowValuesArt.putNulls(this.tableArticle.getFieldsName());
if (this.hasDeclinaison) {
for (String fieldName : this.tableArticle.getFieldsName()) {
if (fieldName.startsWith("ID_ARTICLE_DECLINAISON_")) {
putRowValuesArt.putRowValues(fieldName).putNulls("NOM");
}
}
}
final String codeText = aText;
SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(rowValsCodeF);
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
383,9 → 417,18
List<SQLRowValues> resultCodeF = fetcher.fetch();
resultList.add(2, resultCodeF);
 
Map<String, Map<Number, SQLRowAccessor>> mapDecl = new HashMap<>();
for (List<? extends SQLRowAccessor> list : resultList) {
 
if (!list.isEmpty()) {
if (list.get(0).getTable().getName().startsWith("ARTICLE_DECLINAISON")) {
Map<Number, SQLRowAccessor> map = new HashMap<>();
for (SQLRowAccessor sqlRow : list) {
map.put(sqlRow.getIDNumber(), sqlRow);
}
mapDecl.put("ID_" + list.get(0).getTable().getName(), map);
} else {
for (SQLRowAccessor sqlRow : list) {
 
StringBuffer buf = new StringBuffer();
if (sqlRow.getTable().getName().equals("CODE_FOURNISSEUR")) {
393,6 → 436,23
buf.append(sqlRow.getString("CODE") + " -- ");
buf.append(rArt.getString("CODE") + " -- ");
buf.append(rArt.getString("NOM"));
if (this.hasDeclinaison) {
for (String fieldName : rArt.getFields()) {
if (fieldName.startsWith("ID_ARTICLE_DECLINAISON")) {
Number rowDecl = rArt.getObject(fieldName) == null ? null : rArt.getNonEmptyForeignIDNumber(fieldName);
if (rowDecl != null) {
final Map<Number, SQLRowAccessor> mapIdDecl = mapDecl.get(fieldName);
if (mapIdDecl != null) {
final SQLRowAccessor sqlRowAccessor = mapIdDecl.get(rowDecl);
if (sqlRowAccessor != null) {
buf.append(" -- " + sqlRowAccessor.getString("NOM"));
}
}
}
}
}
}
 
result.add(new IComboSelectionItem(rArt, buf.toString()));
} else {
if (sqlRow.getString("CODE_BARRE") != null && sqlRow.getString("CODE_BARRE").trim().length() > 0) {
400,10 → 460,28
}
buf.append(sqlRow.getString("CODE") + " -- ");
buf.append(sqlRow.getString("NOM"));
if (this.hasDeclinaison) {
for (String fieldName : sqlRow.getFields()) {
if (fieldName.startsWith("ID_ARTICLE_DECLINAISON")) {
Number rowDecl = sqlRow.getObject(fieldName) == null ? null : sqlRow.getNonEmptyForeignIDNumber(fieldName);
if (rowDecl != null) {
final Map<Number, SQLRowAccessor> mapIdDecl = mapDecl.get(fieldName);
if (mapIdDecl != null) {
final SQLRowAccessor sqlRowAccessor = mapIdDecl.get(rowDecl);
if (sqlRowAccessor != null) {
buf.append(" -- " + sqlRowAccessor.getString("NOM"));
}
}
}
}
}
}
result.add(new IComboSelectionItem(sqlRow, buf.toString()));
}
}
}
}
}
 
}
 
502,10 → 580,21
 
private synchronized void showPopup() {
if (this.model.getSize() > 0) {
if (this.popupInvoker.isShowing())
if (this.popupInvoker.isShowing()) {
 
String max = "";
for (IComboSelectionItem item : this.model.getList()) {
if (max.length() < item.getLabel().length()) {
max = item.getLabel();
}
}
final int stringWidth = this.text.getGraphics().getFontMetrics().stringWidth(max);
this.popup.setMinWith(Math.max(450, stringWidth + 20));
 
this.popup.show(this.popupInvoker, 0, this.text.getBounds().height);
}
}
}
 
public void changedUpdate(DocumentEvent e) {
updateAutoCompletion(false);
/trunk/OpenConcerto/src/org/openconcerto/sql/sqlobject/ITextArticleWithCompletionCellEditor.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
34,8 → 34,8
private final ITextArticleWithCompletion text;
private boolean listenersInited = false;
 
public ITextArticleWithCompletionCellEditor(SQLTable tableArticle, SQLTable tableARticleFournisseur) {
this.text = new ITextArticleWithCompletion(tableArticle, tableARticleFournisseur);
public ITextArticleWithCompletionCellEditor(SQLTable tableArticle, SQLTable tableARticleFournisseur, boolean withDeclinaison) {
this.text = new ITextArticleWithCompletion(tableArticle, tableARticleFournisseur,withDeclinaison);
this.text.setBorder(BorderFactory.createEmptyBorder());
this.text.getTextComp().setBorder(BorderFactory.createEmptyBorder());
}
/trunk/OpenConcerto/src/org/openconcerto/sql/Configuration.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
32,6 → 32,10
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.sql.users.User;
import org.openconcerto.sql.users.UserManager;
import org.openconcerto.sql.users.rights.UserRights;
import org.openconcerto.sql.users.rights.UserRightsManager;
import org.openconcerto.utils.BaseDirs;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.StringUtils;
123,6 → 127,18
 
public abstract DBRoot getRoot();
 
public abstract UserManager getUserManager();
 
public final User getCurrentUser() {
return UserManager.getCurrentUser(getUserManager());
}
 
public abstract UserRightsManager getUserRightsManager();
 
public final UserRights getCurrentUserRights() {
return UserRightsManager.getCurrentUserRights(getUserRightsManager(), getUserManager());
}
 
public abstract DBSystemRoot getSystemRoot();
 
public abstract SQLFilter getFilter();
/trunk/OpenConcerto/src/org/openconcerto/sql/request/UpdateBuilder.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
25,7 → 25,7
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.TableRef;
import org.openconcerto.sql.model.Where;
import org.openconcerto.utils.Tuple3.List3;
import org.openconcerto.sql.users.User;
 
import java.util.ArrayList;
import java.util.HashMap;
42,21 → 42,64
*/
public class UpdateBuilder {
 
public final class VirtualJoin {
private final String alias, definition, updateTableField, op, joinedTableField;
 
protected VirtualJoin(final String alias, final String definition, final String updateTableField, final String op, final String joinedTableField) {
super();
this.alias = alias;
this.definition = definition;
this.updateTableField = updateTableField;
this.op = op;
this.joinedTableField = joinedTableField;
}
 
public final String getAlias() {
return this.alias;
}
 
public final String getDefinition() {
return this.definition;
}
 
public final String getUpdateTableField() {
return this.updateTableField;
}
 
public final String getJoinedTableField() {
return this.joinedTableField;
}
 
protected final String getWhere() {
final SQLName joinedTableFieldName = new SQLName(getAlias(), this.getJoinedTableField());
return Where.comparison(getSyntax(), getTable().getField(this.getUpdateTableField()).getSQLNameUntilDBRoot(false).quote(), this.op, joinedTableFieldName.quote());
}
 
protected final String getSelect(final String value) {
return "( select " + value + " from " + this.getDefinition() + " where " + this.getWhere() + " )";
}
}
 
private final SQLTable t;
private final Map<String, String> fields;
private boolean addMetaData;
private User user;
private final List<String> tables;
private Where where;
// alias -> definition, t field, field of the joined table
private final Map<String, List3<String>> virtualJoins;
// alias -> VirtualJoin
private final Map<String, VirtualJoin> virtualJoins;
private final Map<String, Boolean> virtualJoinsOptimized;
 
public UpdateBuilder(SQLTable t) {
public UpdateBuilder(final SQLTable t) {
super();
this.t = t;
this.fields = new LinkedHashMap<String, String>();
this.tables = new ArrayList<String>();
this.virtualJoins = new HashMap<String, List3<String>>(4);
this.virtualJoinsOptimized = new HashMap<String, Boolean>(4);
this.fields = new LinkedHashMap<>();
// this class is low-level and callers don't expect it to automatically add fields
this.addMetaData = false;
this.user = null;
this.tables = new ArrayList<>();
this.virtualJoins = new HashMap<>(4);
this.virtualJoinsOptimized = new HashMap<>(4);
}
 
public final SQLTable getTable() {
63,6 → 106,17
return this.t;
}
 
public final UpdateBuilder setAddMetaData(final boolean addMetaData) {
this.addMetaData = addMetaData;
return this;
}
 
public final UpdateBuilder setUser(final User user) {
this.setAddMetaData(true);
this.user = user;
return this;
}
 
public final SQLSyntax getSyntax() {
return SQLSyntax.get(this.getTable());
}
130,8 → 184,8
public final UpdateBuilder setFromVirtualJoin(final String field, final String joinAlias, final String value) {
final String val;
if (this.isJoinVirtual(joinAlias)) {
final List3<String> virtualJoin = this.virtualJoins.get(joinAlias);
val = "( select " + value + " from " + virtualJoin.get0() + " where " + getWhere(joinAlias, virtualJoin) + " )";
final VirtualJoin virtualJoin = this.virtualJoins.get(joinAlias);
val = virtualJoin.getSelect(value);
} else {
val = value;
}
138,12 → 192,6
return this.set(field, val);
}
 
private final String getWhere(final String joinAlias, final List3<String> virtualJoin) {
assert this.virtualJoins.get(joinAlias) == virtualJoin;
final SQLName joinedTableFieldName = new SQLName(joinAlias, virtualJoin.get2());
return getTable().getField(virtualJoin.get1()).getSQLNameUntilDBRoot(false) + " = " + joinedTableFieldName.quote();
}
 
public final Set<String> getFieldsNames() {
return this.fields.keySet();
}
152,7 → 200,7
return this.fields.isEmpty();
}
 
public final void setWhere(Where where) {
public final void setWhere(final Where where) {
this.where = where;
}
 
179,6 → 227,7
*
* @param definition the table to add, ie either a table name or a sub-select.
* @param rawAlias the SQL alias, can be <code>null</code>, e.g. <code>"t"</code>.
* @see #addVirtualJoin(String, String, boolean, String, String, boolean)
*/
public final void addRawTable(final String definition, final String rawAlias) {
this.tables.add(definition + (rawAlias == null ? "" : " " + rawAlias));
199,9 → 248,17
}
 
public final void addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String field) {
this.addVirtualJoin(definition, alias, aliasAlreadyDefined, joinedTableField, field, true);
this.addVirtualJoin(definition, alias, aliasAlreadyDefined, joinedTableField, "=", field, true);
}
 
public final VirtualJoin addVirtualJoin(final TableRef t, final String joinedTableField, final String op, final String field) {
return this.addVirtualJoin(t.getSQL(), t.getAlias(), true, joinedTableField, op, field, true);
}
 
public final VirtualJoin addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String op, final String field) {
return this.addVirtualJoin(definition, alias, aliasAlreadyDefined, joinedTableField, op, field, true);
}
 
/**
* Add a virtual join to this UPDATE. Some systems don't support
* {@link #addRawTable(String, String) multiple tables}, this method is virtual in the sense
215,11 → 272,14
* is already inside the definition, e.g. ( VALUES ... ) as "constTable"(field1, ...) .
* @param joinedTableField the field in the joined table that will match <code>field</code> of
* the update {@link #getTable() table}.
* @param op the operator to compare <code>joinedTableField</code> and <code>field</code>.
* @param field the field in the update {@link #getTable() table}.
* @param optimize if <code>true</code> and if the system supports it, the virtual join will use
* the multiple table support.
* @return the new join.
*/
public final void addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String field, final boolean optimize) {
public final VirtualJoin addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String op, final String field,
final boolean optimize) {
if (alias == null)
throw new NullPointerException("No alias");
if (this.virtualJoins.containsKey(alias))
226,30 → 286,47
throw new IllegalStateException("Alias already exists : " + alias);
this.checkField(field);
final String completeDef = aliasAlreadyDefined ? definition : definition + ' ' + SQLBase.quoteIdentifier(alias);
this.virtualJoins.put(alias, new List3<String>(completeDef, field, joinedTableField));
final VirtualJoin res = new VirtualJoin(alias, completeDef, field, op, joinedTableField);
this.virtualJoins.put(alias, res);
this.virtualJoinsOptimized.put(alias, optimize);
return res;
}
 
public final String asString() {
// add tables and where for virtual joins
Where computedWhere = this.where;
final List<String> computedTables = new ArrayList<String>(this.tables);
for (final Entry<String, List3<String>> e : this.virtualJoins.entrySet()) {
final List<String> computedTables = new ArrayList<>(this.tables);
for (final Entry<String, VirtualJoin> e : this.virtualJoins.entrySet()) {
final String joinAlias = e.getKey();
final List3<String> virtualJoin = e.getValue();
final VirtualJoin virtualJoin = e.getValue();
final Where w;
if (this.isJoinVirtual(joinAlias)) {
w = Where.createRaw(SQLBase.quoteIdentifier(virtualJoin.get1()) + " in ( select " + SQLBase.quoteIdentifier(virtualJoin.get2()) + " from " + virtualJoin.get0() + " )");
// use same WHERE as setFromVirtualJoin()
w = Where.createRaw("EXISTS " + virtualJoin.getSelect("1"));
} else {
w = Where.createRaw(getWhere(joinAlias, virtualJoin));
computedTables.add(virtualJoin.get0());
w = Where.createRaw(virtualJoin.getWhere());
computedTables.add(virtualJoin.getDefinition());
}
computedWhere = w.and(computedWhere);
}
final String w = computedWhere == null ? "" : "\nWHERE " + computedWhere.getClause();
return "UPDATE " + this.getSyntax().getUpdate(this.getTable(), unmodifiableList(computedTables), unmodifiableMap(this.fields)) + w;
final Map<String, String> execFields;
if (this.addMetaData) {
execFields = new HashMap<>(this.fields);
setFieldValue(execFields, this.getTable().getModifUserField(), this.user == null ? null : this.user.getId());
setFieldValue(execFields, this.getTable().getModifDateField(), System.currentTimeMillis());
} else {
execFields = unmodifiableMap(this.fields);
}
return "UPDATE " + this.getSyntax().getUpdate(this.getTable(), unmodifiableList(computedTables), execFields) + w;
}
 
static private void setFieldValue(final Map<String, String> vals, final SQLField f, final Object val) {
if (f == null)
return;
vals.put(f.getName(), val == null ? "DEFAULT" : f.getType().toString(val));
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + ": " + this.asString();
/trunk/OpenConcerto/src/org/openconcerto/sql/request/FilteredFillSQLRequest.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
179,6 → 179,10
return getSole(res, id);
}
 
public final List<SQLRowValues> getValuesFromIDs(Collection<? extends Number> ids) {
return getValues(ids == null ? null : Where.inValues(this.getPrimaryTable().getKey(), ids));
}
 
protected final <T> T getSole(final List<T> res, int id) {
if (res.size() > 1)
throw new IllegalStateException("there's more than one line which has ID " + id + " for " + this + " : " + res);
/trunk/OpenConcerto/src/org/openconcerto/sql/request/ListSQLRequest.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
23,11 → 23,14
 
import java.util.List;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
@ThreadSafe
public class ListSQLRequest extends FilteredFillSQLRequest {
 
static private final boolean FETCH_METADATA = Boolean.getBoolean("listRequest.fetchMD");
 
private static final FieldExpander getExpander(final FieldExpander showAs) {
final FieldExpander res;
if (showAs != null) {
43,6 → 46,9
return res;
}
 
@GuardedBy("this")
private boolean fetchMD = FETCH_METADATA;
 
public ListSQLRequest(SQLTable table, List<String> fieldss) {
this(table, fieldss, null);
}
80,14 → 86,24
return new ListSQLRequest(this, forFreeze);
}
 
public synchronized final void setMetadataFetched(boolean fetchMD) {
this.fetchMD = fetchMD;
}
 
public synchronized final boolean isMetadataFetched() {
return this.fetchMD;
}
 
// MAYBE use changeGraphToFetch()
@Override
protected final void customizeToFetch(SQLRowValues graphToFetch) {
super.customizeToFetch(graphToFetch);
if (this.isMetadataFetched()) {
addField(graphToFetch, getPrimaryTable().getCreationDateField());
addField(graphToFetch, getPrimaryTable().getCreationUserField());
addField(graphToFetch, getPrimaryTable().getModifDateField());
addField(graphToFetch, getPrimaryTable().getModifUserField());
}
addField(graphToFetch, getPrimaryTable().getFieldRaw(SQLComponent.READ_ONLY_FIELD));
addField(graphToFetch, getPrimaryTable().getFieldRaw(SQLComponent.READ_ONLY_USER_FIELD));
}
/trunk/OpenConcerto/src/org/openconcerto/sql/request/BaseFillSQLRequest.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
18,6 → 18,7
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.OrderComparator;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
109,6 → 110,8
@GuardedBy("this")
private List<Path> order;
@GuardedBy("this")
private Map<Object, Where> wheres;
@GuardedBy("this")
private Where where;
@GuardedBy("this")
private Map<IFieldPath, SearchField> searchFields;
139,7 → 142,7
throw new NullPointerException();
this.primaryTable = graph.getTable();
this.setOrder(null);
this.where = w;
this.setWhere(w);
this.searchFields = Collections.emptyMap();
this.searchLimit = 35;
this.selTransf = null;
153,6 → 156,7
this.primaryTable = req.getPrimaryTable();
synchronized (req) {
this.order = req.order;
this.wheres = req.wheres;
this.where = req.where;
this.searchFields = req.searchFields;
this.searchLimit = req.searchLimit;
381,6 → 385,48
return 0;
}
 
static private final VirtualFields FIELDS_FOR_ORDER = VirtualFields.PRIMARY_KEY.union(VirtualFields.ORDER);
 
// allow to save memory by only keeping trimmed SQLRow
public final OrderValue createOrderValue(final SQLRowValues r) {
if (!r.isFrozen())
throw new IllegalArgumentException("Row not frozen : " + r);
final List<Path> order = getOrder();
final List<SQLRow> rows = new ArrayList<>(order.size());
for (final Path p : order) {
rows.add(r.followPath(p).trimmedRow(FIELDS_FOR_ORDER));
}
return new OrderValue(Collections.unmodifiableList(rows));
}
 
static public final class OrderValue implements Comparable<OrderValue> {
private final List<SQLRow> rows;
 
OrderValue(List<SQLRow> rows) {
super();
this.rows = rows;
}
 
@Override
public int compareTo(OrderValue o2) {
if (this == o2)
return 0;
final int size = this.rows.size();
if (size != o2.rows.size())
throw new IllegalArgumentException("Not same state");
// same behaviour as SQLSelect
final Comparator<SQLRowAccessor> comp = OrderComparator.getFallbackToPKInstance();
for (int i = 0; i < size; i++) {
final SQLRow r1 = this.rows.get(i);
final SQLRow r2 = o2.rows.get(i);
final int res = comp.compare(r1, r2);
if (res != 0)
return res;
}
return 0;
}
}
 
protected List<Path> getDefaultOrder() {
return getTableOrder();
}
400,9 → 446,35
this.order = l == null ? null : Collections.unmodifiableList(new ArrayList<Path>(l));
}
 
/**
* Set a where to be AND'd.
*
* @param o a key, <code>null</code> to change the where set by {@link #setWhere(Where)}.
* @param w the new value, <code>null</code> to remove.
*/
public final void putWhere(final Object o, final Where w) {
synchronized (this) {
checkFrozen();
final Map<Object, Where> newValue = new HashMap<>(this.wheres);
if (w == null)
newValue.remove(o);
else
newValue.put(o, w);
this.wheres = Collections.unmodifiableMap(newValue);
this.where = Where.and(this.wheres.values());
}
fireWhereChange();
}
 
/**
* Set the where, replacing any other set by {@link #putWhere(Object, Where)}.
*
* @param w the new value, <code>null</code> to remove.
*/
public final void setWhere(final Where w) {
synchronized (this) {
checkFrozen();
this.wheres = w == null ? Collections.<Object, Where> emptyMap() : Collections.singletonMap(null, w);
this.where = w;
}
fireWhereChange();
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementLink.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
15,13 → 15,35
 
import org.openconcerto.sql.element.SQLElement.ReferenceAction;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLFieldRowProcessor;
import org.openconcerto.sql.model.SQLResultSet;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLType;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.model.graph.Step;
import org.openconcerto.utils.CollectionUtils;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
import net.jcip.annotations.Immutable;
 
/**
* A logical link between two elements. It can be a direct foreign {@link Link} or two links through
* a {@link JoinSQLElement join}. The {@link #getOwner()} needs the {@link #getOwned()}, i.e. if the
193,6 → 215,193
return this.getPathToChild().getStep(-1);
}
 
public final List<SQLRow> getRowsUntilRoot(final Number id) {
return this.getRowsUntilRoot(id, null).getRows();
}
 
/**
* Get all rows above the passed row (including it).
*
* @param id the first row.
* @param fields which fields to fetch, <code>null</code> to fetch all.
* @return all rows from the passed one to the root.
*/
public final RecursiveRows getRowsUntilRoot(final Number id, Set<String> fields) {
return getRecursiveRows(true, id, fields, -1, null);
}
 
public final List<SQLRow> getSubTreeRows(final Number id) {
return this.getSubTreeRows(id, null).getRows();
}
 
public final RecursiveRows getSubTreeRows(final Number id, final Set<String> fields) {
return getSubTreeRows(id, fields, -1);
}
 
/**
* Get all rows beneath the passed root (including it).
*
* @param id the root row.
* @param fields which fields to fetch, <code>null</code> to fetch all.
* @param maxLevel the max number of times to go through the link.
* @return all rows are deterministically ordered (by level, parent order, order ; i.e. root
* first).
*/
public final RecursiveRows getSubTreeRows(final Number id, final Set<String> fields, final int maxLevel) {
return getRecursiveRows(false, id, fields, maxLevel, getOwned().getTable().getOrderField());
}
 
static private final String findUnusedName(final Collection<String> usedNames, final String base) {
String res = base;
int i = 0;
while (usedNames.contains(res)) {
res = base + i++;
}
return res;
}
 
@Immutable
static public final class RecursiveRows {
 
static public final RecursiveRows ZERO_LEVEL = new RecursiveRows(0, Collections.emptyList(), Collections.emptyMap());
 
private final int maxLevel;
private final List<SQLRow> rows;
private final Map<SQLRow, List<Number>> cycles;
 
RecursiveRows(final int maxLevel, final List<SQLRow> rows, final Map<SQLRow, List<Number>> cycles) {
super();
this.maxLevel = maxLevel;
this.rows = Collections.unmodifiableList(rows);
// OK since List<Number> are already immutable
this.cycles = Collections.unmodifiableMap(cycles);
}
 
public final int getMaxLevelRequested() {
return this.maxLevel;
}
 
public final List<SQLRow> getRows() {
if (this.getCycles().isEmpty())
return this.getPartialRows();
else
throw new IllegalStateException("Cycle detected : " + this.getCycles());
}
 
public final List<SQLRow> getPartialRows() {
return this.rows;
}
 
public final Map<SQLRow, List<Number>> getCycles() {
return this.cycles;
}
}
 
private final RecursiveRows getRecursiveRows(final boolean foreign, final Number id, Set<String> fields, final int maxLevel, final SQLField orderField) {
if (this.getOwner() != this.getOwned() || this.isJoin())
throw new IllegalStateException("Not a recurive link : " + this);
final SQLTable t = getOwned().getTable();
final Link singleLink = this.getSingleLink();
final SQLField singleField = singleLink.getSingleField();
if (singleField == null)
throw new UnsupportedOperationException("Multiple fields not yet supported : " + singleLink);
Objects.requireNonNull(id, "id is null");
 
if (maxLevel == 0)
return RecursiveRows.ZERO_LEVEL;
 
final SQLSyntax syntax = t.getDBSystemRoot().getSyntax();
 
if (fields == null)
fields = t.getFieldsName();
final String recursiveT = "recT";
// use array to prevent infinite loop
final String visitedIDsF = findUnusedName(fields, "visitedIDs");
final String visitedIDsRef = recursiveT + '.' + visitedIDsF;
final String visitedIDsCount = syntax.getSQLArrayLength(visitedIDsRef);
// boolean to know about endless loops : we don't stop before visiting a row a second time,
// but just after
final String loopF = findUnusedName(fields, "loop");
 
// firstly visitedIDsF, secondly optional order, then the asked fields
final SQLSelect selNonRec = new SQLSelect();
selNonRec.addRawSelect(syntax.getSQLArray(Collections.singletonList(t.getKey().getFieldRef())), visitedIDsF);
selNonRec.addRawSelect(SQLType.getBoolean(syntax).toString(Boolean.FALSE), loopF);
final boolean useOrder = !foreign && orderField != null;
if (useOrder)
selNonRec.addRawSelect(syntax.cast("null", orderField.getType().getJavaType()), "parentOrder");
selNonRec.addAllSelect(t, fields);
if (!fields.contains(singleField.getName()))
selNonRec.addSelect(singleField);
// need PK for SQLRow
if (!fields.contains(t.getKey().getName()))
selNonRec.addSelect(t.getKey());
selNonRec.setWhere(new Where(t.getKey(), "=", id));
 
// recursive SELECT
final StringBuilder recSelect = new StringBuilder("SELECT ");
recSelect.append(syntax.getSQLArrayAppend(visitedIDsRef, t.getKey().getFieldRef())).append(", ");
recSelect.append(syntax.getSQLArrayContains(visitedIDsRef, t.getKey().getFieldRef())).append(", ");
final int index;
if (useOrder) {
recSelect.append(recursiveT).append('.').append(orderField.getName()).append(", ");
index = 3;
} else {
index = 2;
}
recSelect.append(CollectionUtils.join(selNonRec.getSelect().subList(index, selNonRec.getSelect().size()), ", "));
recSelect.append("\nFROM ").append(t.getSQLName().quote()).append(", ").append(recursiveT);
recSelect.append("\nWHERE ");
if (foreign) {
recSelect.append(t.getKey().getFieldRef()).append(" = ").append(recursiveT + '.' + singleField.getName());
} else {
recSelect.append(singleField.getFieldRef()).append(" = ").append(recursiveT + '.' + t.getKey().getName());
}
// avoid infinite loop
recSelect.append(" and not (").append(recursiveT).append('.').append(loopF).append(')');
if (t.getUndefinedIDNumber() != null) {
recSelect.append(" and ").append(new Where(t.getKey(), "!=", t.getUndefinedID()).getClause());
}
if (maxLevel > 0) {
recSelect.append(" and ").append(visitedIDsCount).append(" < ").append(maxLevel);
}
 
String cte = "with recursive " + recursiveT + "(" + CollectionUtils.join(selNonRec.getSelectNames(), ", ") + ") as (\n" + selNonRec.asString() + "\nunion all\n" + recSelect
+ ")\nSELECT * from " + recursiveT + " ORDER BY " + visitedIDsCount;
if (useOrder) {
cte += ", 2, " + recursiveT + '.' + orderField.getName();
}
 
final List<String> rsNames = new ArrayList<>(selNonRec.getSelectNames());
// int[] visited IDs
rsNames.set(0, null);
// boolean loop
rsNames.set(1, null);
if (useOrder)
rsNames.set(2, null);
 
final List<SQLRow> res = new ArrayList<>();
final Map<SQLRow, List<Number>> cycleRows = new HashMap<>();
final Class<? extends Number> keyType = t.getKey().getType().getJavaType().asSubclass(Number.class);
t.getDBSystemRoot().getDataSource().execute(cte, new ResultSetHandler() {
@Override
public Object handle(ResultSet rs) throws SQLException {
final SQLFieldRowProcessor rowProc = new SQLFieldRowProcessor(t, rsNames);
while (rs.next()) {
final SQLRow row = SQLRow.createFromRS(t, rs, rowProc, true);
final boolean looped = rs.getBoolean(2);
if (looped) {
cycleRows.put(row, SQLResultSet.getList(rs, 1, keyType));
} else {
res.add(row);
}
}
return null;
}
});
return new RecursiveRows(maxLevel, res, cycleRows);
}
 
public final LinkType getLinkType() {
return this.type;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElementDirectory.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
17,6 → 17,7
import org.openconcerto.sql.ShowAs;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.DBStructureItemNotFound;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLName;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.SQLFieldTranslator;
23,6 → 24,7
import org.openconcerto.utils.CollectionMap2;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.cc.Cookies;
import org.openconcerto.utils.cc.ITransformer;
 
import java.lang.reflect.InvocationTargetException;
38,12 → 40,16
import net.jcip.annotations.GuardedBy;
 
/**
* Directory of SQLElement by table.
* Directory of SQLElement by table. <code>final</code> so that {@link SQLElement} can't depend on a
* subclass and can be shared between applications (e.g. using
* {@link #putAll(SQLElementDirectory)}). If some elements needs shared state, use
* {@link #getCookies()}.
*
* @author Sylvain CUAZ
*/
public final class SQLElementDirectory {
 
private final DBSystemRoot systemRoot;
private final Map<SQLTable, SQLElement> elements;
private final SetMap<String, SQLTable> tableNames;
private final SetMap<String, SQLTable> byCode;
56,7 → 62,10
private SQLFieldTranslator translator;
private final ShowAs showAs;
 
public SQLElementDirectory() {
private final Cookies cookies;
 
public SQLElementDirectory(DBSystemRoot systemRoot) {
this.systemRoot = systemRoot;
this.elements = new HashMap<SQLTable, SQLElement>();
// to mimic elements behaviour, if we add twice the same table
// the second one should replace the first one
69,6 → 78,7
this.phrasesPkgName = null;
 
this.showAs = new ShowAs((DBRoot) null);
this.cookies = new Cookies();
}
 
public synchronized final void destroy() {
91,6 → 101,10
return this.translator;
}
 
public final Cookies getCookies() {
return this.cookies;
}
 
private static <K> SQLTable getSoleTable(SetMap<K, SQLTable> m, K key) throws IllegalArgumentException {
final Collection<SQLTable> res = m.getNonNull(key);
if (res.size() > 1)
104,7 → 118,10
}
 
public synchronized final void putAll(SQLElementDirectory o) {
for (final SQLElement elem : o.getElements()) {
// Cookies are needed in addSQLElement() (e.g. by directoryChanged() and getShowAs())
this.cookies.putAll(o.getCookies());
// copy since addSQLElement() removes elements from their previous directory
for (final SQLElement elem : new ArrayList<>(o.getElements())) {
if (!this.contains(elem.getTable()))
this.addSQLElement(elem);
}
147,6 → 164,9
* @return the previously added element.
*/
public synchronized final SQLElement addSQLElement(SQLElement elem) {
if (elem.getTable().getDBSystemRoot() != this.systemRoot) {
System.err.println("SQLElementDirectory.addSQLElement() warning : Not in this system root : " + elem + " " + elem.getTable().getDBSystemRoot() + " != " + this.systemRoot);
}
final SQLElement res = this.removeSQLElement(elem.getTable());
this.elements.put(elem.getTable(), elem);
this.tableNames.add(elem.getTable().getName(), elem.getTable());
/trunk/OpenConcerto/src/org/openconcerto/sql/element/TreesOfSQLRows.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
342,7 → 342,7
public SQLSelect transformChecked(SQLSelect input) {
final FieldRef refField = input.getAlias(pathToTableWithFK.getLast()).getField(ffName);
// eg where RECEPTEUR.ID_LOCAL in (3,12)
return input.andWhere(new Where(refField, valsMap.keySet()));
return input.andWhere(Where.inValues(refField, valsMap.keySet()));
}
});
for (final SQLRowValues newVals : fetcher.fetch()) {
/trunk/OpenConcerto/src/org/openconcerto/sql/element/SQLElement.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
67,12 → 67,15
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
import org.openconcerto.sql.view.EditFrame;
import org.openconcerto.sql.view.EditPanel.EditMode;
import org.openconcerto.sql.view.list.IListe.ConvertedAction;
import org.openconcerto.sql.view.list.IListeAction;
import org.openconcerto.sql.view.list.RowAction;
import org.openconcerto.sql.view.list.SQLTableModelColumn;
import org.openconcerto.sql.view.list.SQLTableModelColumnPath;
import org.openconcerto.sql.view.list.SQLTableModelSource;
import org.openconcerto.sql.view.list.SQLTableModelSourceOffline;
import org.openconcerto.sql.view.list.SQLTableModelSourceOnline;
import org.openconcerto.sql.view.list.action.SQLRowValuesAction;
import org.openconcerto.ui.group.Group;
import org.openconcerto.ui.light.ComboValueConvertor;
import org.openconcerto.ui.light.IntValueConvertor;
79,7 → 82,6
import org.openconcerto.ui.light.LightUIComboBox;
import org.openconcerto.ui.light.LightUIElement;
import org.openconcerto.ui.light.LightUIFrame;
import org.openconcerto.ui.light.LightUIPanel;
import org.openconcerto.ui.light.StringValueConvertor;
import org.openconcerto.utils.CollectionMap2Itf.SetMapItf;
import org.openconcerto.utils.CollectionUtils;
127,8 → 129,8
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.function.Supplier;
 
import javax.swing.JComponent;
import javax.swing.JOptionPane;
192,7 → 194,9
private ComboSQLRequest combo;
private ListSQLRequest list;
private SQLTableModelSourceOnline tableSrc;
@Deprecated
private final ListChangeRecorder<IListeAction> rowActions;
private final ListChangeRecorder<SQLRowValuesAction> rowValuesActions;
private final LinkedListMap<String, ITransformer<Tuple2<SQLElement, String>, SQLComponent>> components;
// links
private SQLElementLinks ownedLinks;
236,7 → 240,38
this.code = code == null ? createCode() : code;
this.combo = null;
this.list = null;
this.rowActions = new ListChangeRecorder<IListeAction>(new ArrayList<IListeAction>());
this.rowActions = new ListChangeRecorder<>(Collections.synchronizedList(new ArrayList<>()));
this.rowActions.getRecipe().addListener(new IClosure<ListChangeIndex<IListeAction>>() {
private final Map<IListeAction, SQLRowValuesAction> map = new IdentityHashMap<>();
 
@Override
public void executeChecked(ListChangeIndex<IListeAction> listChange) {
final List<SQLRowValuesAction> oldActions = new ArrayList<>();
for (final IListeAction action : listChange.getItemsRemoved()) {
synchronized (this) {
final SQLRowValuesAction ra = this.map.remove(action);
if (ra != null)
oldActions.add(ra);
}
}
getRowValuesActions().removeAll(oldActions);
 
final List<SQLRowValuesAction> newActions = new ArrayList<>();
for (final IListeAction action : listChange.getItemsAdded()) {
if (action instanceof RowAction) {
synchronized (this) {
if (!this.map.containsKey(action)) {
final ConvertedAction converted = new ConvertedAction((RowAction) action);
this.map.put(action, converted);
newActions.add(converted);
}
}
}
}
getRowValuesActions().addAll(newActions);
}
});
this.rowValuesActions = new ListChangeRecorder<>(Collections.synchronizedList(new ArrayList<>()));
this.resetRelationships();
 
this.components = new LinkedListMap<String, ITransformer<Tuple2<SQLElement, String>, SQLComponent>>();
1945,18 → 1980,23
this.additionalListCols.add(col);
}
 
@Deprecated
public final Collection<IListeAction> getRowActions() {
return this.rowActions;
}
 
public final void addRowActionsListener(final IClosure<ListChangeIndex<IListeAction>> listener) {
this.rowActions.getRecipe().addListener(listener);
public final List<SQLRowValuesAction> getRowValuesActions() {
return this.rowValuesActions;
}
 
public final void removeRowActionsListener(final IClosure<ListChangeIndex<IListeAction>> listener) {
this.rowActions.getRecipe().rmListener(listener);
public final void addRowActionsListener(final IClosure<? super ListChangeIndex<SQLRowValuesAction>> listener) {
this.rowValuesActions.getRecipe().addListener(listener);
}
 
public final void removeRowActionsListener(final IClosure<? super ListChangeIndex<SQLRowValuesAction>> listener) {
this.rowValuesActions.getRecipe().rmListener(listener);
}
 
public String getDescription(SQLRow fromRow) {
return fromRow.toString();
}
2176,11 → 2216,11
* @return a copy ready to be inserted, or <code>null</code> if <code>row</code> cannot be
* copied.
*/
public SQLRowValues createCopy(SQLRowAccessor row, SQLRowAccessor parent) {
public final SQLRowValues createCopy(SQLRowAccessor row, SQLRowAccessor parent) {
return createCopy(row, false, parent);
}
 
public SQLRowValues createCopy(SQLRowAccessor row, final boolean full, SQLRowAccessor parent) {
public final SQLRowValues createCopy(SQLRowAccessor row, final boolean full, SQLRowAccessor parent) {
return this.createCopy(row, full, parent, null, null);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_en.properties
97,6 → 97,8
ilist.lockRows=Lock rows
ilist.unlockRows=Unlock rows
ilist.metadata={0,choice,0#Modified|1#Created}{1,choice,0#|1# by {2} {3}}{4,choice,0#|1#, {5,date,long} at {5,time,medium}}
ilist.metadata.loading=Metadata loading
ilist.metadata.na=Metadata unvailable
 
sqlComp.stringValueTooLong=The value is {0, plural, one {# character too long} other {# characters too long}}
sqlComp.bdTooHigh=Number too high, {0, plural,\
/trunk/OpenConcerto/src/org/openconcerto/sql/translation/messages_fr.properties
97,6 → 97,8
ilist.lockRows=Verrouiller les lignes
ilist.unlockRows=Déverrouiller les lignes
ilist.metadata={0,choice,0#Modifiée|1#Créée}{1,choice,0#|1# par {2} {3}}{4,choice,0#|1# le {5,date,long} à {5,time,medium}}
ilist.metadata.loading=Métadonnées en chargement
ilist.metadata.na=Métadonnées non disponibles
 
sqlComp.stringValueTooLong=La valeur fait {0, plural, one {# caractère de trop} other {# caractères de trop}}
sqlComp.bdTooHigh=Nombre trop grand, {0, plural,\
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/SQLUtils.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
22,6 → 22,7
import org.openconcerto.sql.model.SQLResultSet;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.cc.ITransformerExn;
 
import java.sql.Connection;
import java.sql.ResultSet;
99,6 → 100,15
return executeAtomic(ds, h, false);
}
 
public static final <T, X extends Exception> T executeAtomic(final SQLDataSource ds, final ITransformerExn<? super SQLDataSource, T, X> run) throws SQLException, X {
return executeAtomic(ds, new ConnectionHandlerNoSetup<T, X>() {
@Override
public T handle(SQLDataSource ds) throws X {
return run.transformChecked(ds);
}
});
}
 
/**
* Execute <code>h</code> in a transaction. Only the outer most call to
* <code>executeAtomic()</code> commit or roll back a transaction, for recursive calls if
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/Diff.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
24,6 → 24,7
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLSchema;
import org.openconcerto.sql.model.SQLServer;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.model.SQLTable;
179,6 → 180,7
final AlterTable alterTable = new AlterTable(aT);
final SQLSystem aSystem = aT.getServer().getSQLSystem();
final SQLSystem bSystem = bT.getServer().getSQLSystem();
final SQLSyntax bSyntax = bT.getDBSystemRoot().getSyntax();
{
final Set<String> aFields = aT.getFieldsName();
final Set<String> bFields = bT.getFieldsName();
191,7 → 193,7
for (final String common : CollectionUtils.inter(aFields, bFields)) {
final SQLField aF = aT.getField(common);
final SQLField bF = bT.getField(common);
final Map<Properties, String> diff = aF.getDiffMap(bF, bSystem, true);
final Map<Properties, String> diff = aF.getDiffMap(bF, bSyntax, true);
alterTable.alterColumn(common, bF, diff.keySet());
}
}
259,7 → 261,7
if (alterTable.isEmpty()) {
final String exactDiff = aT.equalsDesc(bT, null, true);
assert exactDiff != null : "Why bother if exactly equals";
final String lenientDiff = aT.equalsDesc(bT, bT.getServer().getSQLSystem(), true);
final String lenientDiff = aT.equalsDesc(bT, bSyntax, true);
if (lenientDiff == null)
Log.get().info("Tables " + aT.getSQLName() + " and " + bT.getSQLName() + " are not exactly equal, but due to diferring DB system features can't be :\n" + exactDiff);
else
/trunk/OpenConcerto/src/org/openconcerto/sql/utils/ChangeTable.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
279,6 → 279,10
return cat(cts, new ChangeRootNameTransformer(r), true).get(0);
}
 
static public String getForeignColumDefaultValue(final SQLTable foreignTable) {
return foreignTable.getKey().getType().toString(foreignTable.getUndefinedIDNumber());
}
 
// allow to factor column name from table and FCSpec
public static final class ForeignColSpec {
 
296,7 → 300,7
static public ForeignColSpec fromTable(SQLTable foreignTable, final boolean absolute) {
if (foreignTable == null)
throw new NullPointerException("null table");
final String defaultVal = foreignTable.getKey().getType().toString(foreignTable.getUndefinedIDNumber());
final String defaultVal = getForeignColumDefaultValue(foreignTable);
final SQLName n = absolute ? foreignTable.getSQLName() : new SQLName(foreignTable.getName());
return new ForeignColSpec(null, n, foreignTable.getKey().getName(), defaultVal);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/UndefinedRowValuesCache.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
57,6 → 57,10
return rv;
}
 
public void setDefaultRowValues(final SQLRowValues rowVals) {
this.map.put(rowVals.getTable(), rowVals);
}
 
public void preload(List<SQLTable> tablesToCache) {
if (tablesToCache.size() <= 0) {
throw new IllegalArgumentException("Empty list");
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowListRSH.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
13,7 → 13,6
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.utils.Tuple2;
 
import java.sql.ResultSet;
20,10 → 19,7
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
 
import org.apache.commons.dbutils.ResultSetHandler;
33,12 → 29,13
// hashCode()/equals() needed for data source cache
public static final class RSH implements ResultSetHandler {
private final Tuple2<SQLTable, List<String>> names;
private final boolean immutableRows;
 
// allow to create rows from arbitrary columns (and not just directly from actual fields of
// the same table)
// ATTN doesn't check that the types of columns are coherent with the types of the fields
public RSH(final SQLTable t, final List<String> names) {
this(Tuple2.create(t, names));
this(Tuple2.create(t, names), true);
// null are OK (they're ignored)
final List<String> unknown = names.stream().filter(n -> n != null && !t.getFieldsName().contains(n)).collect(Collectors.toList());
if (!unknown.isEmpty())
45,8 → 42,9
throw new IllegalArgumentException("Not all names are fields of " + t + " : " + unknown);
}
 
private RSH(final Tuple2<SQLTable, List<String>> names) {
private RSH(final Tuple2<SQLTable, List<String>> names, final boolean immutableRows) {
this.names = names;
this.immutableRows = immutableRows;
}
 
@Override
53,7 → 51,7
public List<SQLRow> handle(final ResultSet rs) throws SQLException {
// since the result will be cached, disallow its modification (e.g.avoid
// ConcurrentModificationException)
return Collections.unmodifiableList(SQLRow.createListFromRS(this.names.get0(), rs, this.names.get1()));
return SQLRow.createListFromRS(this.names.get0(), rs, this.names.get1(), this.immutableRows);
}
 
@Override
130,7 → 128,7
*/
@Deprecated
static public ResultSetHandler createFromSelect(final SQLSelect sel) {
return create(getIndexes(sel, null, true));
return create(getIndexes(sel, null, true), false);
}
 
/**
144,13 → 142,17
*/
@Deprecated
static public ResultSetHandler createFromSelect(final SQLSelect sel, final TableRef t) {
return create(getIndexes(sel, t, false));
return create(getIndexes(sel, t, false), false);
}
 
static ResultSetHandler create(final Tuple2<SQLTable, List<String>> names) {
return new RSH(names);
static ResultSetHandler create(final Tuple2<SQLTable, List<String>> names, final boolean immutableRows) {
return new RSH(names, immutableRows);
}
 
static public List<SQLRow> fetch(final SQLTable t) throws IllegalArgumentException {
return fetch(t, null);
}
 
static public List<SQLRow> fetch(final SQLTable t, final Collection<? extends Number> ids) throws IllegalArgumentException {
return fetch(t, ids, null);
}
161,6 → 163,9
sel.addSelectStar(t);
else
sel.addAllSelect(t, fields);
// deterministic
sel.addOrder(t, false);
if (ids != null)
sel.setWhere(new Where(t.getKey(), ids));
return execute(sel);
}
194,24 → 199,6
return new SQLSelectHandlerBuilder(sel).setTableRef(t).execute();
}
 
static IResultSetHandler createFromSelect(final SQLSelect sel, final Tuple2<SQLTable, List<String>> indexes, final boolean readCache, final boolean writeCache) {
final Set<SQLTable> tables = new HashSet<SQLTable>();
// not just tables of the fields of the SELECT clause since inner joins can change the rows
// returned
for (final TableRef ref : sel.getTableRefs().values()) {
tables.add(ref.getTable());
}
 
// the SELECT requesting a lock means the caller expects the DB to be accessed
final boolean acquireLock = sel.getLockStrength() != LockStrength.NONE;
return new IResultSetHandler(create(indexes), readCache && !acquireLock, writeCache && !acquireLock) {
@Override
public Set<? extends SQLData> getCacheModifiers() {
return tables;
}
};
}
 
private final SQLTable t;
private final boolean tableOnly;
 
/trunk/OpenConcerto/src/org/openconcerto/sql/model/RowRef.java
New file
0,0 → 1,106
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.CollectionUtils;
import org.openconcerto.utils.convertor.NumberConvertor;
 
import java.util.Collections;
import java.util.List;
import java.util.Objects;
 
import net.jcip.annotations.Immutable;
 
/**
* Allow to store a reference to a row with less memory than {@link SQLRow}.
*
* @author sylvain
* @see SQLTable#createRowRef(Number)
* @see SQLRowAccessor#getRowRef()
*/
@Immutable
public final class RowRef {
 
// need to convert so that equals() work (because Number.equals() doesn't exist)
static private Number convert(final SQLTable table, final Number n) {
// avoid method calls if null
return n == null ? null : NumberConvertor.convertExact(n, table.getKey().getType().getJavaType().asSubclass(Number.class));
}
 
private final SQLTable table;
private final List<?> pk;
private final Number id;
 
RowRef(final SQLTable table, final List<?> pk) {
this(table, pk, false);
}
 
RowRef(final SQLTable table, final List<?> pk, final boolean safe) {
this(table, Objects.requireNonNull(pk), null, safe);
}
 
RowRef(final SQLTable table, final Number id) {
this(table, null, Objects.requireNonNull(id), false);
}
 
private RowRef(final SQLTable table, final List<?> pk, final Number id, final boolean safe) {
assert (pk == null) != (id == null);
if (id != null && !table.isRowable())
throw new IllegalStateException("Not rowable : " + table.getSQLName());
else if (!safe && pk != null && pk.size() != table.getPrimaryKeyFields().size())
throw new IllegalArgumentException("Wrong number of items");
this.table = Objects.requireNonNull(table);
if (id != null || table.isRowable()) {
this.id = convert(table, id != null ? id : (Number) pk.get(0));
this.pk = Collections.singletonList(this.id);
} else {
this.id = null;
this.pk = safe ? pk : CollectionUtils.toImmutableList(pk);
}
}
 
public final SQLTable getTable() {
return this.table;
}
 
public final List<?> getPK() {
return this.pk;
}
 
public final Number getID() {
return this.id;
}
 
@Override
public int hashCode() {
return Objects.hash(this.pk, this.table);
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final RowRef other = (RowRef) obj;
return this.table.equals(other.table) && this.pk.equals(other.pk);
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " " + this.getTable() + " " + this.getPK();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSystem.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
16,6 → 16,7
import org.openconcerto.sql.utils.SQL_URL;
import org.openconcerto.utils.EnumOrderedSet;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.Tuple2.List2;
import org.openconcerto.utils.cc.ITransformer;
 
import java.sql.Statement;
23,8 → 24,10
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
 
import org.h2.engine.ConnectionInfo;
import org.h2.engine.Constants;
import org.h2.util.StringUtils;
 
135,7 → 138,7
H2("H2") {
 
private static final String TCP_PREFIX = "tcp://";
private static final String SSL_PREFIX = "ssl://";
private static final String ARBITRARY_BASE_NAME = "foo";
 
ITransformer<String, String> getURLTransf(final SQLServer s) {
if (s.getSQLSystem() != this)
144,21 → 147,10
return new ITransformer<String, String>() {
@Override
public String transformChecked(String base) {
final String sep;
// allow one to use mem for server name
// otherwise just cat name and base
// (eg "tcp://127.0.0.1/" + "sample", "file:~/" + "sample" or "" + "sample" )
if (s.getName().equals("mem"))
sep = ":";
else {
// for file, pass either file:, or file:/someDir/
// jdbc:h2:~/test
sep = "";
}
// by default h2 convert database name to upper case (we used to work around it
// with SQLSystem.getMDName() but in r2251 an equalsIgnoreCase() was replaced by
// equals()) see http://code.google.com/p/h2database/issues/detail?id=204
return s.getName() + sep + base + ";DATABASE_TO_UPPER=false";
return s.getName() + base + ";DATABASE_TO_UPPER=false";
}
};
}
187,26 → 179,33
}
 
@Override
public String getHostname(final String server) {
final String prefix;
if (server.startsWith(TCP_PREFIX))
prefix = TCP_PREFIX;
else if (server.startsWith(SSL_PREFIX))
prefix = SSL_PREFIX;
else
return null;
public List2<String> getHostnameAndPath(final String server) {
// append base name to server name to get a valid value
final ConnectionInfo info = new ConnectionInfo(server + ARBITRARY_BASE_NAME);
final String name = info.getName();
final String hostName;
final int pathIndex;
if (info.isRemote()) {
// tcp:// or ssl://server/path
assert name.startsWith("//");
final int slashIndex = name.indexOf('/', 2);
hostName = name.substring(2, slashIndex);
pathIndex = slashIndex + 1;
} else {
// mem: or file:/data/sample or ~/test
hostName = null;
pathIndex = 0;
}
 
// check that our name doesn't contain a path, otherwise we would loose it
// eg dbserv:8084/~/sample
final String hostAndPath = server.substring(prefix.length());
final int firstSlash = hostAndPath.indexOf('/');
if (firstSlash == hostAndPath.lastIndexOf('/')) {
return hostAndPath.substring(0, firstSlash);
} else
return null;
return new List2<>(hostName, name.substring(pathIndex, name.length() - ARBITRARY_BASE_NAME.length()));
}
 
@Override
public boolean isPermanent(final String server) {
return !server.startsWith(H2_IN_MEMORY);
}
 
@Override
public Map<String, String> getConnectionInfo(final String url) {
final Tuple2<String, Map<String, String>> settings = readSettingsFromURL(url);
final Map<String, String> res = new HashMap<String, String>();
316,6 → 315,8
}
}
 
public static final String H2_IN_MEMORY = "mem:";
 
private final String label;
private final EnumOrderedSet<HierarchyLevel> levels;
 
422,15 → 423,37
/**
* The host name for the passed server.
*
* @param server the name of an {@link SQLServer}.
* @return its host or <code>null</code> if <code>server</code> has no host or is too complex
* (eg tcp://127.0.0.1/a/b/c).
* @param server the name of an {@link SQLServer}, e.g. tcp://127.0.0.1/dir/.
* @return its host and its path, both can be <code>null</code> (but not at the same time), e.g.
* [127.0.0.1, dir].
*/
public String getHostname(String server) {
return server;
public List2<String> getHostnameAndPath(String server) {
Objects.requireNonNull(server, "Null server");
return new List2<>(server, null);
}
 
/**
* Whether the passed server runs inside the VM.
*
* @param server a server, e.g. {@link SQLServer#getName()}.
* @return <code>true</code> if <code>server</code> runs inside the VM.
*/
public final boolean isEmbedded(final String server) {
return getHostnameAndPath(server).get0() == null;
}
 
/**
* Whether the passed server survives when all connections to it are closed.
*
* @param server a server, e.g. {@link SQLServer#getName()}.
* @return <code>true</code> if <code>server</code> survives when all connections to it are
* closed.
*/
public boolean isPermanent(String server) {
return true;
}
 
/**
* Parse <code>url</code> to find info needed by {@link SQL_URL}.
*
* @param url a jdbc url, eg
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLFieldRowProcessor.java
New file
0,0 → 1,131
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.model;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
 
import org.apache.commons.dbutils.RowProcessor;
 
import net.jcip.annotations.Immutable;
 
@Immutable
public final class SQLFieldRowProcessor implements RowProcessor {
 
private final List<String> names;
private final int rsOffset;
private Boolean uniqueNames;
 
public SQLFieldRowProcessor(final SQLTable table, final List<String> fields) {
this(0, fields, table::getField);
}
 
public SQLFieldRowProcessor(final List<? extends FieldRef> fields) {
this(0, fields);
}
 
public SQLFieldRowProcessor(final int rsOffset, final List<? extends FieldRef> fields) {
this(rsOffset, fields, FieldRef::getField);
}
 
private <F> SQLFieldRowProcessor(final int rsOffset, final List<? extends F> fields, final Function<? super F, SQLField> func) {
final List<String> names = new ArrayList<>(fields.size());
for (final F ref : fields) {
if (ref == null) {
names.add(null);
} else {
final SQLField f = func.apply(ref);
names.add(f.getName());
}
}
this.names = Collections.unmodifiableList(names);
this.rsOffset = rsOffset;
this.uniqueNames = null;
}
 
public final List<String> getFieldNames() {
return this.names;
}
 
public final boolean areNamesUnique() {
if (this.uniqueNames == null) {
this.uniqueNames = new HashSet<>(this.names).size() == this.names.size();
}
return this.uniqueNames.booleanValue();
}
 
public final Object getObject(final ResultSet rs, final int rsIndex) throws SQLException {
return rs.getObject(rsIndex + this.rsOffset);
}
 
@Override
public Object[] toArray(ResultSet rs) throws SQLException {
final int count = this.names.size();
final Object[] result = new Object[count];
for (int i = 0; i < count; i++) {
result[i] = getObject(rs, i + 1);
}
return result;
}
 
@SuppressWarnings("rawtypes")
@Override
public Object toBean(ResultSet rs, Class type) throws SQLException {
throw new UnsupportedOperationException();
}
 
@SuppressWarnings("rawtypes")
@Override
public List toBeanList(ResultSet rs, Class type) throws SQLException {
throw new UnsupportedOperationException();
}
 
@Override
public Map<String, Object> toMap(ResultSet rs) throws SQLException {
final int count = this.names.size();
final Map<String, Object> result = new HashMap<>();
for (int i = 0; i < count; i++) {
final String name = this.names.get(i);
if (name != null) {
result.put(name, getObject(rs, i + 1));
}
}
return result;
}
 
@Override
public int hashCode() {
return Objects.hash(this.names, this.rsOffset);
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final SQLFieldRowProcessor other = (SQLFieldRowProcessor) obj;
return this.rsOffset == other.rsOffset && this.names.equals(other.names);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMySQL.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
24,10 → 24,13
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.NumberUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.beans.DefaultPersistenceDelegate;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
101,6 → 104,8
CAST_TYPES.put(java.sql.Time.class, "time");
CAST_TYPES.put(Blob.class, "binary");
CAST_TYPES.put(String.class, "char");
 
XMLCodecUtils.register(SQLSyntaxMySQL.class, new DefaultPersistenceDelegate(new String[] { "noBackslashEscapes" }));
}
 
private final boolean noBackslashEscapes;
123,6 → 128,10
this.typeNames.addAll(String.class, "varchar", "char");
}
 
public final boolean isNoBackslashEscapes() {
return this.noBackslashEscapes;
}
 
@Override
public final String quoteString(String s) {
final String res = super.quoteString(s);
282,6 → 291,23
}
 
@Override
public boolean isTableNotFoundException(Exception exn) {
return SQLUtils.findWithSQLState(exn).getErrorCode() == 1146;
}
 
@Override
public String getSetLockTimeoutQuery(int millis) {
// Wait at least the passed amount.
// 0ms -> 0s, 1ms -> 1s, 1000ms -> 1s, 1001ms -> 2s
return "set innodb_lock_wait_timeout = " + NumberUtils.divideRoundUp(millis, 1000);
}
 
@Override
public String getShowLockTimeoutQuery() {
return "select @@SESSION.innodb_lock_wait_timeout * 1000";
}
 
@Override
public Map<ClauseType, List<String>> getAlterField(SQLField f, Set<Properties> toAlter, String type, String defaultVal, Boolean nullable) {
final boolean newNullable = toAlter.contains(Properties.NULLABLE) ? nullable : getNullable(f);
final String newType = toAlter.contains(Properties.TYPE) ? type : getType(f);
608,4 → 634,30
}
};
}
 
@Override
public String getSessionIDExpression() {
return "CONNECTION_ID()";
}
 
@Override
public String getSessionsQuery(final DBSystemRoot sysRoot, final boolean includeSelf) {
final String allRows = "SELECT \"ID\", \"Info\" as \"QUERY\", \"USER\" as \"USER_NAME\" FROM INFORMATION_SCHEMA.PROCESSLIST";
if (includeSelf)
return allRows;
return allRows + " WHERE \"ID\" != " + this.getSessionIDExpression();
}
 
@Override
protected String getAllUsersForGrant() {
return "'%'@'%'";
}
 
@Override
public String getAllowConnectionsQuery(String sysRootName, final boolean allow) {
return null;
// MAYBE use this (but still allow super users)
// https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_offline_mode
// return "set GLOBAL offline_mode = " + (allow ? "off" : "on");
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLInjector.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,6 → 14,7
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.graph.SQLKey;
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.preferences.SQLPreferences;
224,7 → 225,13
SQLRowValues rowValsHeader = new SQLRowValues(UndefinedRowValuesCache.getInstance().getDefaultRowValues(tableElementDestination));
// FIXME Style forcé en titre 1 via l'ID
rowValsHeader.put("ID_STYLE", 3);
String elementName = StringUtils.firstUp(Configuration.getInstance().getDirectory().getElement(getSource()).getName().getVariant(org.openconcerto.utils.i18n.Grammar.SINGULAR));
SQLElement element = Configuration.getInstance().getDirectory().getElement(getSource());
//Utiliser pour les transfert du module commande interne
if (element == null) {
element = Configuration.getInstance().getDirectory().getElement(getSource().getName());
}
 
String elementName = StringUtils.firstUp(element.getName().getVariant(org.openconcerto.utils.i18n.Grammar.SINGULAR));
rowValsHeader.put("NOM", elementName + "\n N° " + srcRow.getString("NUMERO") + " du " + dateFormat.format(srcRow.getDate("DATE").getTime()));
rowValsHeader.put(refField, rowVals);
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSelect.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
22,6 → 22,7
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
47,18 → 48,6
public static final ArchiveMode ARCHIVED = ArchiveMode.ARCHIVED;
public static final ArchiveMode BOTH = ArchiveMode.BOTH;
 
/**
* Quote %-escaped parameters. %% : %, %s : quoteString, %i : quote, %f : quote(getFullName()),
* %n : quote(getName()).
*
* @param pattern a string with %, eg "SELECT * FROM %n where %f like '%%a%%'".
* @param params the parameters, eg [ /TENSION/, |TENSION.LABEL| ].
* @return pattern with % replaced, eg SELECT * FROM "TENSION" where "TENSION.LABEL" like '%a%'.
*/
public static final String quote(final String pattern, final Object... params) {
return SQLBase.quoteStd(pattern, params);
}
 
// [String], eg : [SITE.ID_SITE, AVG(AGE)]
private final List<String> select;
// names of columns (explicit aliases and field names), e.g. [ID_SITE, null]
511,6 → 500,10
return this.addSelect(f, null);
}
 
public final SQLSelect addAllSelect(final FieldRef... s) {
return this.addAllSelect(Arrays.asList(s));
}
 
/**
* Permet d'ajouter plusieurs champs.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxH2.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
17,10 → 17,14
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.utils.ChangeTable.ClauseType;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.NetUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.Transformer;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.beans.DefaultPersistenceDelegate;
import java.io.File;
import java.math.BigDecimal;
import java.sql.Blob;
28,6 → 32,7
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
35,7 → 40,7
 
import org.h2.constant.ErrorCode;
 
class SQLSyntaxH2 extends SQLSyntax {
public class SQLSyntaxH2 extends SQLSyntax {
 
static protected final IdentityHashMap<String, String> DATE_SPECS;
 
42,10 → 47,30
static {
DATE_SPECS = new IdentityHashMap<String, String>(DateProp.JAVA_DATE_SPECS_PURE);
DATE_SPECS.put(DateProp.MICROSECOND, "SSS000");
 
XMLCodecUtils.register(SQLSyntaxH2.class, new DefaultPersistenceDelegate(new String[] {}));
}
 
SQLSyntaxH2() {
final static SQLSyntax create(DBSystemRoot sysRoot) {
boolean standardArray = true;
if (sysRoot != null) {
try {
standardArray = Arrays.equals(new String[] { "foo" }, (Object[]) sysRoot.getDataSource().executeScalar("SELECT ARRAY['foo']"));
} catch (RuntimeException e) {
if (SQLUtils.findWithSQLState(e).getErrorCode() == ErrorCode.COLUMN_NOT_FOUND_1)
standardArray = false;
else
throw e;
}
}
return new SQLSyntaxH2(standardArray);
}
 
private final boolean standardArray;
 
public SQLSyntaxH2(final boolean standardArray) {
super(SQLSystem.H2, DATE_SPECS);
this.standardArray = standardArray;
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Integer.class, "integer", "int", "int4", "mediumint");
this.typeNames.addAll(Byte.class, "tinyint");
189,20 → 214,6
}
 
@Override
protected boolean isServerLocalhost(SQLServer s) {
return s.getName().startsWith("mem") || s.getName().startsWith("file") || NetUtils.isSelfAddr(getAddr(s));
}
 
private String getAddr(SQLServer s) {
if (s.getName().startsWith("tcp") || s.getName().startsWith("ssl")) {
final int startIndex = "tcp://".length();
final int endIndex = s.getName().indexOf('/', startIndex);
return s.getName().substring(startIndex, endIndex < 0 ? s.getName().length() : endIndex);
} else
return null;
}
 
@Override
public String getCreateSynonym(SQLTable t, SQLName newName) {
return null;
}
324,4 → 335,78
// otherwise, the second one will timeout while waiting for the table lock
return stateExn.getErrorCode() == ErrorCode.DEADLOCK_1 || stateExn.getErrorCode() == ErrorCode.LOCK_TIMEOUT_1;
}
 
@Override
public boolean isTableNotFoundException(Exception exn) {
final SQLException stateExn = SQLUtils.findWithSQLState(exn);
return stateExn.getErrorCode() == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1 || stateExn.getErrorCode() == ErrorCode.SCHEMA_NOT_FOUND_1;
}
 
@Override
public String getSessionIDExpression() {
return "SESSION_ID()";
}
 
@Override
public String getSessionsQuery(final DBSystemRoot sysRoot, final boolean includeSelf) {
final String allRows = "SELECT \"ID\", \"STATEMENT\" as \"QUERY\", \"USER_NAME\" FROM INFORMATION_SCHEMA.SESSIONS";
if (includeSelf)
return allRows;
return allRows + " WHERE \"ID\" != " + this.getSessionIDExpression();
}
 
@Override
public String getVersionFunction() {
return "H2VERSION()";
}
 
@Override
public String getAllowConnectionsQuery(String sysRootName, final boolean allow) {
return "SET EXCLUSIVE " + (allow ? "0" : "1");
}
 
@Override
public boolean isConnectionDisallowedException(SQLException exn) {
return exn.getErrorCode() == ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE;
}
 
@Override
public String getSQLArray(final List<String> sqlExpressions, final String type) {
// H2 cannot specify type in SQL, so cast each item
final ITransformer<? super String, ?> tf = type == null ? Transformer.nopTransformer() : (s) -> {
return this.cast(s, type);
};
final String items = CollectionUtils.join(sqlExpressions, ", ", tf);
if (this.standardArray) {
return "ARRAY[" + items + ']';
} else {
// For the comma, see http://h2database.com/html/grammar.html#array
return '(' + items + (sqlExpressions.size() == 1 ? ",)" : ")");
}
}
 
@Override
public String getSQLArrayContains(String arrayExpression, String itemExpression) {
return "ARRAY_CONTAINS(" + arrayExpression + ", " + itemExpression + ")";
}
 
@Override
public String getSQLArrayLength(final String arrayExpression) {
return "ARRAY_LENGTH(" + arrayExpression + ")";
}
 
@Override
public String getSQLArrayConcat(final String arrayExpression, final String array2Expression) {
return "array_cat(" + arrayExpression + ", " + array2Expression + ")";
}
 
@Override
public String getSQLArrayAppend(final String arrayExpression, final String itemExpression) {
return "array_append(" + arrayExpression + ", " + itemExpression + ")";
}
 
@Override
public String getSQLArraySlice(final String arrayExpression, final String index1Expression, final String index2Expression) {
return "ARRAY_SLICE(" + arrayExpression + ", " + index1Expression + ", " + index2Expression + ")";
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLType.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
269,7 → 269,7
return equals(obj, null);
}
 
public boolean equals(Object obj, SQLSystem otherSystem) {
public boolean equals(Object obj, SQLSyntax otherSyntax) {
if (obj instanceof SQLType) {
final SQLType o = (SQLType) obj;
final boolean javaTypeOK = this.getJavaType().equals(o.getJavaType());
292,8 → 292,8
if (this.getType() == Types.DATE || this.getJavaType() == Float.class || this.getJavaType() == Double.class) {
sizeOK = true;
} else {
if (otherSystem == null && o.getSyntax() != null)
otherSystem = o.getSyntax().getSystem();
if (otherSyntax == null)
otherSyntax = o.getSyntax();
final SQLSystem thisSystem = this.getSyntax() == null ? null : this.getSyntax().getSystem();
final boolean isTime = this.getType() == Types.TIME || this.getType() == Types.TIMESTAMP;
final boolean decDigitsOK;
302,7 → 302,7
decDigitsOK = true;
} else if (this.isNumeric() ||
// isTime() : if we don't know the system, play it safe and compare
thisSystem == null || otherSystem == null || thisSystem.isFractionalSecondsSupported() && otherSystem.isFractionalSecondsSupported()) {
thisSystem == null || otherSyntax == null || thisSystem.isFractionalSecondsSupported() && otherSyntax.getSystem().isFractionalSecondsSupported()) {
decDigitsOK = CompareUtils.equals(this.getDecimalDigits(), o.getDecimalDigits());
} else {
decDigitsOK = true;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLField.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
488,8 → 488,8
 
// compareDefault useful when fields' default are functions containing the name of the table (eg
// serial)
public String equalsDesc(SQLField o, SQLSystem otherSystem, boolean compareDefault) {
final Map<Properties, String> res = getDiffMap(o, otherSystem, compareDefault);
public String equalsDesc(SQLField o, SQLSyntax otherSyntax, boolean compareDefault) {
final Map<Properties, String> res = getDiffMap(o, otherSyntax, compareDefault);
if (res.size() == 0)
return null;
else
504,7 → 504,7
* @param compareDefault <code>true</code> if defaults should be compared.
* @return a map containing properties that differs and their values.
*/
public synchronized Map<Properties, String> getDiffMap(SQLField o, SQLSystem otherSystem, boolean compareDefault) {
public synchronized Map<Properties, String> getDiffMap(SQLField o, SQLSyntax otherSystem, boolean compareDefault) {
if (o == null)
return Collections.singletonMap(null, "other field is null");
final Map<Properties, String> res = new HashMap<Properties, String>();
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesListFetcher.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxMS.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
27,7 → 27,9
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.beans.DefaultPersistenceDelegate;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
51,7 → 53,7
import java.util.Set;
import java.util.regex.Pattern;
 
class SQLSyntaxMS extends SQLSyntax {
public class SQLSyntaxMS extends SQLSyntax {
 
static private final IdentityHashMap<String, String> DATE_SPECS;
 
66,9 → 68,11
DATE_SPECS.put(DateProp.MINUTE, "mm");
DATE_SPECS.put(DateProp.SECOND, "ss");
DATE_SPECS.put(DateProp.MICROSECOND, "ffffff");
 
XMLCodecUtils.register(SQLSyntaxMS.class, new DefaultPersistenceDelegate(new String[] {}));
}
 
SQLSyntaxMS() {
public SQLSyntaxMS() {
super(SQLSystem.MSSQL, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "bit");
// tinyint is unsigned
240,6 → 244,22
}
 
@Override
public boolean isTableNotFoundException(Exception exn) {
// TODO
throw new UnsupportedOperationException();
}
 
@Override
public String getSetLockTimeoutQuery(int millis) {
return "SET LOCK_TIMEOUT " + millis;
}
 
@Override
public String getShowLockTimeoutQuery() {
return "SELECT @@LOCK_TIMEOUT";
}
 
@Override
public Map<ClauseType, List<String>> getAlterField(SQLField f, Set<Properties> toAlter, String type, String defaultVal, Boolean nullable) {
final ListMap<ClauseType, String> res = new ListMap<ClauseType, String>();
if (toAlter.contains(Properties.TYPE) || toAlter.contains(Properties.NULLABLE)) {
615,4 → 635,27
public String quoteForTimestampFormat(String text) {
return StringUtils.doubleQuote(text);
}
 
@Override
public String getSessionIDExpression() {
return "@@SPID";
}
 
@Override
public String getSessionsQuery(final DBSystemRoot sysRoot, final boolean includeSelf) {
final String allRows = "SELECT ps.spid as \"ID\", cmd as \"QUERY\", users.name as \"USER_NAME\" FROM sys.sysprocesses ps\n"
//
+ "JOIN sys.sysdatabases db on db.dbid = ps.dbid\nJOIN sys.sysusers users on users.uid = ps.uid\\n"
//
+ " WHERE db.name=" + quoteString(sysRoot.getName());
if (includeSelf)
return allRows;
return allRows + " and ps.spid != " + this.getSessionIDExpression();
}
 
@Override
public String getVersionFunction() {
// if this doesn't work, try SERVERPROPERTY('ProductVersion')
return "@@VERSION";
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLBase.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
46,6 → 46,7
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
769,19 → 770,13
* @return pattern with % replaced, eg SELECT * FROM "TENSION" where "TENSION.LABEL" like '%a%'.
*/
public final String quote(final String pattern, Object... params) {
return quote(this, pattern, params);
return quote(this.getDBSystemRoot().getSyntax(), pattern, params);
}
 
// since Strings might not be quoted correctly
@Deprecated
public final static String quoteStd(final String pattern, Object... params) {
return quote(null, pattern, params);
}
 
static private final Pattern percent = Pattern.compile("%.");
 
private final static String quote(final SQLBase b, final String pattern, Object... params) {
final SQLSyntax s = b == null ? null : SQLSyntax.get(b);
final static String quote(final SQLSyntax s, final String pattern, Object... params) {
Objects.requireNonNull(s, "Missing syntax");
final Matcher m = percent.matcher(pattern);
final StringBuffer sb = new StringBuffer();
int i = 0;
794,7 → 789,7
} else {
final Object param = params[i++];
if (modifier == 's') {
replacement = SQLSyntax.quoteString(s, param.toString());
replacement = s.quoteString(param.toString());
} else if (modifier == 'i') {
if (param instanceof SQLName)
replacement = ((SQLName) param).quote();
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSelectHandlerBuilder.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
13,10 → 13,13
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.Value;
 
import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
public final class SQLSelectHandlerBuilder {
 
23,6 → 26,7
private final SQLSelect sel;
private Value<TableRef> t;
private boolean readCache, writeCache;
private boolean immutableRows;
 
public SQLSelectHandlerBuilder(final SQLSelect sel) {
if (sel == null)
30,6 → 34,7
this.sel = sel;
this.t = Value.getNone();
this.setUseCache(true);
this.immutableRows = true;
}
 
public SQLSelectHandlerBuilder setUseCache(final boolean b) {
56,6 → 61,15
return this.writeCache;
}
 
public final SQLSelectHandlerBuilder setImmutableRows(boolean immutableRows) {
this.immutableRows = immutableRows;
return this;
}
 
public final boolean isImmutableRows() {
return this.immutableRows;
}
 
/**
* Set the table of the rows to be created. Must be used if the query has more than one table.
*
77,9 → 91,23
 
public IResultSetHandler createHandler() {
final Tuple2<SQLTable, List<String>> indexes = SQLRowListRSH.getIndexes(this.sel, this.t.toNonNull(), !this.t.hasValue());
return SQLRowListRSH.createFromSelect(this.sel, indexes, isReadCache(), isWriteCache());
final Set<SQLTable> tables = new HashSet<SQLTable>();
// not just tables of the fields of the SELECT clause since inner joins can change the rows
// returned
for (final TableRef ref : this.sel.getTableRefs().values()) {
tables.add(ref.getTable());
}
 
// the SELECT requesting a lock means the caller expects the DB to be accessed
final boolean acquireLock = this.sel.getLockStrength() != LockStrength.NONE;
return new IResultSetHandler(SQLRowListRSH.create(indexes, this.isImmutableRows()), isReadCache() && !acquireLock, isWriteCache() && !acquireLock) {
@Override
public Set<? extends SQLData> getCacheModifiers() {
return tables;
}
};
}
 
@SuppressWarnings("unchecked")
public List<SQLRow> execute() {
return (List<SQLRow>) this.sel.getSystemRoot().getDataSource().execute(this.sel.asString(), createHandler());
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValues.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
36,6 → 36,7
import org.openconcerto.utils.RecursionType;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.Value;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.cc.IdentitySet;
109,6 → 110,10
COPY_ROW
}
 
public static enum SQLDefaultCopyMode {
COPY, REMOVE, PARSE_ELSE_REMOVE, PARSE_ELSE_ERROR;
}
 
static public enum CreateMode {
/**
* Never create rows.
150,6 → 155,24
}
};
 
public static final class SQLExpression {
private final String sql;
 
public SQLExpression(String sql) {
super();
this.sql = sql;
}
 
public final String getSQL() {
return this.sql;
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " " + this.sql;
}
}
 
static public enum ValidityCheck {
/**
* The check is never performed.
341,6 → 364,7
*
* @return this if already frozen, otherwise a frozen copy of this.
*/
@Override
public final SQLRowValues toImmutable() {
if (this.isFrozen())
return this;
461,14 → 485,20
* @return the foreign row, eg FAMILLE[1].
*/
public final SQLRowValues grow(String fk) {
// growUndefined to make sure we can cast (otherwise if fk is undefined
// getForeign() returns a row)
return (SQLRowValues) this.grow(fk, true);
}
 
public final SQLRowAccessor grow(String fk, final boolean growUndefined) {
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);
this.grow(vals, true, growUndefined);
}
return (SQLRowValues) this.getForeign(fk);
return this.getForeign(fk);
}
 
public final SQLRowValues grow(SQLRowValues graph) {
681,17 → 711,48
}
 
@Override
public final Object getObjectNoCheck(String fieldName) {
return this.values.get(fieldName);
}
public final Value<Object> getNonDefaultObject(String fieldName) {
return getNonDefaultObject(fieldName, Object.class);
}
 
/**
* Never return {@link #SQL_DEFAULT}.
*
* @param fieldName the name of the field.
* @param clazz the type of value expected.
* @return {@link Value#getNone()} if the field is DEFAULT and the
* {@link SQLField#getParsedDefaultValue() default value} cannot be parsed, otherwise
* either the non DEFAULT field value or the parsed DEFAULT.
*/
public final <T> Value<T> getNonDefaultObject(String fieldName, Class<T> clazz) {
final Object res = this.getObject(fieldName);
if (res == SQL_DEFAULT) {
return this.getTable().getField(fieldName).getParsedDefaultValue().cast(clazz);
} else {
return Value.getSome(clazz.cast(res));
}
}
 
@Override
public Map<String, Object> getAbsolutelyAll() {
return getAllValues(ForeignCopyMode.COPY_ROW);
}
 
protected final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns) {
return this.getAllValues(copyForeigns, false);
return this.getAllValues(copyForeigns, SQLDefaultCopyMode.COPY);
}
 
private final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, final boolean copy) {
public final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, SQLDefaultCopyMode copyDefaults) {
return this.getAllValues(copyForeigns, copyDefaults, false);
}
 
private final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, SQLDefaultCopyMode copyDefaults, final boolean copy) {
final Map<String, Object> toAdd;
if (copyForeigns == ForeignCopyMode.COPY_ROW || this.foreigns.size() == 0) {
if ((copyForeigns == ForeignCopyMode.COPY_ROW || this.foreigns.size() == 0) && (copyDefaults == SQLDefaultCopyMode.COPY || !this.values.values().contains(SQL_DEFAULT))) {
if (copy) {
toAdd = createLinkedHashMap(this.size());
toAdd.putAll(this.values);
702,19 → 763,46
final Set<Entry<String, Object>> entrySet = this.values.entrySet();
toAdd = createLinkedHashMap(entrySet.size());
for (final Map.Entry<String, Object> e : entrySet) {
if (!(e.getValue() instanceof SQLRowValues)) {
toAdd.put(e.getKey(), e.getValue());
final String fieldName = e.getKey();
boolean add = true;
Object val = e.getValue();
if (e.getValue() == SQL_DEFAULT) {
if (copyDefaults == SQLDefaultCopyMode.REMOVE) {
add = false;
} else if (copyDefaults == SQLDefaultCopyMode.COPY) {
add = true;
} else {
final Value<Object> parsedDefaultValue = this.getTable().getField(fieldName).getParsedDefaultValue();
if (parsedDefaultValue.hasValue()) {
val = parsedDefaultValue.getValue();
} else if (copyDefaults == SQLDefaultCopyMode.PARSE_ELSE_ERROR) {
throw new IllegalStateException("Couldn't parse default for " + fieldName);
} else {
assert copyDefaults == SQLDefaultCopyMode.PARSE_ELSE_REMOVE;
add = false;
}
}
} else if (e.getValue() instanceof SQLRowValues) {
if (copyForeigns == ForeignCopyMode.NO_COPY) {
add = false;
} else if (copyForeigns == ForeignCopyMode.COPY_ROW) {
add = true;
} else if (copyForeigns == ForeignCopyMode.COPY_NULL) {
toAdd.put(e.getKey(), null);
} else if (copyForeigns != ForeignCopyMode.NO_COPY) {
val = null;
} else {
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);
val = foreign.getIDNumber();
else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_RM)
add = false;
else
assert copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW;
}
}
if (add)
toAdd.put(fieldName, val);
}
}
return copy ? toAdd : Collections.unmodifiableMap(toAdd);
}
 
797,15 → 885,23
}
 
@Override
public final SQLRow asRow() {
public final SQLRow asRow(final Boolean immutable) {
if (!this.hasID())
throw new IllegalStateException(this + " has no ID");
return new SQLRow(this.getTable(), this.getAllValues(ForeignCopyMode.COPY_ID_OR_RM));
// keep the most information without throwing an exception
final SQLRow res = new SQLRow(this.getTable(), this.getAllValues(ForeignCopyMode.COPY_ID_OR_RM, SQLDefaultCopyMode.PARSE_ELSE_REMOVE, false));
// if the caller doesn't care about immutable, keep the same
if (Boolean.TRUE.equals(immutable) || (immutable == null && this.isFrozen()))
res.freeze();
return res;
}
 
@Override
public final SQLRowValues asRowValues() {
public final SQLRowValues asRowValues(final Boolean immutable) {
if (immutable == null || this.isFrozen() == immutable)
return this;
else
return this.getGraph().deepCopy(this, immutable);
}
 
// *** set
815,6 → 911,7
*
* @return <code>true</code> if this (and its graph) is not modifiable.
*/
@Override
public final boolean isFrozen() {
final SQLRowValuesCluster g = this.getGraph(false);
return g != null && g.isFrozen();
1010,10 → 1107,7
final ValueConvertor<T, U> conv = ValueConvertorFactory.find(source, dest);
if (conv == null)
throw new IllegalArgumentException("No convertor to " + dest + " from " + source);
assert source == value.getClass();
@SuppressWarnings("unchecked")
final T tVal = (T) value;
return conv.convert(tVal);
return conv.convert(source.cast(value));
}
 
private void _put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
1023,7 → 1117,7
if (value == SQL_EMPTY_LINK) {
// keep getForeignTable since it does the check
value = this.getForeignTable(fieldName).getUndefinedIDNumber();
} else if (value != null && value != SQL_DEFAULT && checkValue != ValueOperation.PASS) {
} else if (value != null && value != SQL_DEFAULT && !(value instanceof SQLExpression) && checkValue != ValueOperation.PASS) {
final SQLField field = this.getTable().getField(fieldName);
if (value instanceof SQLRowValues) {
if (!field.isForeignKey())
1032,7 → 1126,11
final Class<?> javaType = field.getType().getJavaType();
if (!javaType.isInstance(value)) {
if (checkValue == ValueOperation.CONVERT) {
try {
value = convert(value.getClass(), value, javaType);
} catch (Exception e) {
throw new IllegalArgumentException("Couldn't convert " + SQLBase.quoteIdentifier(fieldName) + " " + value + getClassName(value.getClass()) + " to " + javaType, e);
}
} else {
throw new IllegalArgumentException("Wrong type for " + fieldName + ", expected " + javaType + " but got " + value.getClass());
}
1062,6 → 1160,17
}
 
/**
* Put an SQL expression into this.
*
* @param fieldName the field name.
* @param sqlExpression an SQL expression, e.g. "now()" or "'a'".
* @return this.
*/
public SQLRowValues putSQL(final String fieldName, final String sqlExpression) {
return this.put(fieldName, new SQLExpression(sqlExpression));
}
 
/**
* Set a new {@link SQLRowValues} as the value of <code>fieldName</code>. ATTN contrary to many
* methods this one do not return <code>this</code>.
*
1083,17 → 1192,23
/**
* Create or follow the passed path and put the passed row at the end.
*
* @param p the {@link Path#isSingleLink() single link} path.
* @param p the {@link Path#isSingleLink() single link} path, can only be {@link Path#length()
* empty} if <code>vals</code> is <code>null</code>.
* @param createPath <code>true</code> if new rows must {@link #createPathToOne(Path) always be
* created}, <code>false</code> if existing rows can be {@link #assurePath(Path) used}.
* @param vals the row to {@link #put(Step, SQLRowValues) put}, <code>null</code> to create a
* new one.
* @return the row that was added.
* @return the row at the end of the path.
* @throws IllegalArgumentException if the path is invalid.
*/
public final SQLRowValues put(final Path p, final boolean createPath, final SQLRowValues vals) throws IllegalArgumentException {
if (p.length() == 0)
throw new IllegalArgumentException("Empty path");
if (p.length() == 0) {
if (vals == null) {
return this;
} else {
throw new IllegalArgumentException("Empty path, won't merge " + vals + " into " + this);
}
}
if (!p.isSingleLink())
throw new IllegalArgumentException("Multi-link path " + p);
// checks first table
1277,6 → 1392,10
return this.putAll(m, null);
}
 
public final SQLRowValues putAllAbsent(Map<String, ?> m) {
return this.loadAll(m, FillMode.DONT_OVERWRITE);
}
 
public final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys) {
return this.putAll(m, keys, FillMode.OVERWRITE);
}
1839,6 → 1958,8
// 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 (fieldVal instanceof SQLExpression)
return new Object[] { fieldName, null };
if (fieldVal != null && fieldVal != SQL_DEFAULT && !(fieldVal instanceof SQLRowValues)) {
final SQLRow pb = foreignTable.checkValidity(((Number) fieldVal).intValue());
if (pb != null)
2030,11 → 2151,12
String result = this.getClass().getSimpleName() + " on " + this.getTable() + " : {";
result += CollectionUtils.join(this.values.entrySet(), ", ", new ITransformer<Entry<String, ?>, String>() {
public String transformChecked(final Entry<String, ?> e) {
final String className = e.getValue() == null ? "" : "(" + e.getValue().getClass() + ")";
final Object fieldVal = e.getValue();
final String className = getClassName(fieldVal);
final String value;
// avoid infinite loop (and overly verbose string)
if (e.getValue() instanceof SQLRowValues) {
final SQLRowValues foreignVals = (SQLRowValues) e.getValue();
if (fieldVal instanceof SQLRowValues) {
final SQLRowValues foreignVals = (SQLRowValues) fieldVal;
if (foreignVals == SQLRowValues.this) {
value = "this";
} else if (foreignVals.hasID()) {
2043,8 → 2165,9
// so that if the same vals is referenced multiple times, we can see it
value = "@" + System.identityHashCode(foreignVals);
}
} else
value = String.valueOf(e.getValue());
} else {
value = String.valueOf(fieldVal);
}
return e.getKey() + "=" + value + className;
}
});
2052,6 → 2175,11
return result;
}
 
protected String getClassName(final Object val) {
// SQL_DEFAULT already has our class name in toString()
return val == null || val == SQL_DEFAULT || val instanceof SQLExpression ? "" : "(" + val.getClass() + ")";
}
 
/**
* Return a graphical representation (akin to the result of a query) of the tree rooted at
* <code>this</code>.
2172,8 → 2300,8
}
// fields are already checked so if IDs are not wanted, just omit foreign rows
final ForeignCopyMode copyMode = useForeignID ? ForeignCopyMode.COPY_ID_OR_RM : ForeignCopyMode.NO_COPY;
final Map<String, Object> thisVals = this.getAllValues(copyMode, !usePK);
final Map<String, Object> oVals = o.getAllValues(copyMode, !usePK);
final Map<String, Object> thisVals = this.getAllValues(copyMode, SQLDefaultCopyMode.COPY, !usePK);
final Map<String, Object> oVals = o.getAllValues(copyMode, SQLDefaultCopyMode.COPY, !usePK);
if (!usePK) {
final List<String> pk = this.getTable().getPKsNames();
thisVals.keySet().removeAll(pk);
2243,6 → 2371,7
String req = (insert ? "INSERT INTO " : "UPDATE ") + tableQuoted + " ";
if (insert) {
assert fieldsNames.size() == values.size();
final List<String> insertValues = new ArrayList<>(values.size());
// remove DEFAULT since they are useless and prevent us from using
// INSERT INTO "TABLEAU_ELECTRIQUE" ("ID_OBSERVATION", ...) select DEFAULT, ?,
// MAX("ORDRE") + 1 FROM "TABLEAU_ELECTRIQUE"
2250,16 → 2379,18
if (values.get(i) == SQL_DEFAULT) {
fieldsNames.remove(i);
values.remove(i);
} else {
insertValues.add(getFieldValue(values.get(i)));
}
}
assert fieldsNames.size() == values.size();
 
// ajout de l'ordre
final SQLField order = table.getOrderField();
final SQLField orderF = table.getOrderField();
final boolean selectOrder;
if (order != null && !fieldsNames.contains(order.getName())) {
if (orderF != null && !fieldsNames.contains(orderF.getName())) {
// si l'ordre n'est pas spécifié, ajout à la fin
fieldsNames.add(order.getName());
fieldsNames.add(orderF.getName());
selectOrder = true;
} else {
selectOrder = false;
2274,8 → 2405,7
return SQLBase.quoteIdentifier(input);
}
}) + ")";
// no DEFAULT thus only ?
final String questionMarks = CollectionUtils.join(Collections.nCopies(values.size(), "?"), ", ");
final String questionMarks = CollectionUtils.join(insertValues, ", ");
if (selectOrder) {
// needed since VALUES ( (select MAX("ORDRE") from "LOCAL") ) on MySQL yield
// "You can't specify target table 'LOCAL' for update in FROM clause"
2284,7 → 2414,7
if (values.size() > 0)
req += ", ";
// COALESCE for empty tables, MIN_ORDER + 1 since MIN_ORDER cannot be moved
req += "COALESCE(MAX(" + SQLBase.quoteIdentifier(order.getName()) + "), " + ReOrder.MIN_ORDER + ") + 1 FROM " + tableQuoted;
req += "COALESCE(MAX(" + SQLBase.quoteIdentifier(orderF.getName()) + "), " + ReOrder.MIN_ORDER + ") + 1 FROM " + tableQuoted;
} else {
req += " VALUES (";
req += questionMarks;
2314,7 → 2444,7
int i = 0;
for (final Object value : values) {
// nothing to set if there's no corresponding '?'
if (value != SQL_DEFAULT) {
if (value != SQL_DEFAULT && !(value instanceof SQLExpression)) {
final Object toIns;
if (value instanceof SQLRowValues) {
// TODO if we already point to some row, archive it
2330,7 → 2460,12
}
 
private static String getFieldValue(final Object value) {
return value == SQL_DEFAULT ? "DEFAULT" : "?";
if (value == SQL_DEFAULT)
return "DEFAULT";
else if (value instanceof SQLExpression)
return ((SQLExpression) value).getSQL();
else
return "?";
}
 
@Override
2521,4 → 2656,14
public static final SQLRowValues trim(final SQLRowValues r) {
return new SQLRowValues(r, ForeignCopyMode.COPY_ID_OR_RM);
}
 
public static final List<SQLRowValues> toImmutableList(final Collection<SQLRowValues> rows) {
return toImmutableList(rows, new ArrayList<>(rows.size()));
}
 
public static final List<SQLRowValues> toImmutableList(final Collection<SQLRowValues> rows, final List<SQLRowValues> res) {
for (final SQLRowValues v : rows)
res.add(v.toImmutable());
return Collections.unmodifiableList(res);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowValuesCluster.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
/trunk/OpenConcerto/src/org/openconcerto/sql/model/Where.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
15,7 → 15,6
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.ArrayList;
import java.util.Arrays;
24,9 → 23,9
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
 
import org.apache.commons.collections.functors.InstanceofPredicate;
 
import net.jcip.annotations.Immutable;
 
/**
127,6 → 126,25
return subqueries(ref, false, subQueries);
}
 
static public Where inValues(final FieldRef ref, final Collection<?> values) {
return compareValues(ref, RowComparison.IN, values);
}
 
static public Where notInValues(final FieldRef ref, final Collection<?> values) {
return compareValues(ref, RowComparison.NOT_IN, values);
}
 
static public Where compareValues(final FieldRef ref, final RowComparison cmp, final Collection<?> values) {
return compareValues(ref, cmp, NullValue.IS_FORBIDDEN, values);
}
 
static public Where compareValues(final FieldRef ref, final RowComparison cmp, final NullValue nullMode, final Collection<?> values) {
if (values.isEmpty()) {
return cmp == RowComparison.IN ? FALSE : TRUE;
}
return createRaw(getCompareValuesClause(ref.getFieldRef(), cmp, nullMode, values, ref.getField().getType()), ref);
}
 
/**
* Create a Where for a field value contained or not contained in sub-queries.
*
153,7 → 171,8
/**
* To create complex Where not possible with constructors.
*
* @param pattern a pattern to be passed to {@link SQLSelect#quote(String, Object...)}, eg
* @param s the syntax to use.
* @param pattern a pattern to be passed to {@link SQLBase#quoteStd(String, Object...)}, eg
* "EXTRACT(YEAR FROM %n) = 3007".
* @param params the params to be passed to <code>quote()</code>, eg [|MISSION.DATE_DBT|].
* @return a new Where with the result from <code>quote()</code> as its clause, and all
160,10 → 179,15
* <code>FieldRef</code> in params as its fields, eg {EXTRACT(YEAR FROM "DATE_DBT") =
* 3007 , |MISSION.DATE_DBT|}.
*/
@SuppressWarnings("unchecked")
static public Where quote(final String pattern, final Object... params) {
return new Where(SQLSelect.quote(pattern, params), org.apache.commons.collections.CollectionUtils.select(Arrays.asList(params), new InstanceofPredicate(FieldRef.class)));
static public Where quote(final SQLSyntax s, final String pattern, final Object... params) {
final List<FieldRef> fields = new ArrayList<>();
for (final Object param : params) {
if (param instanceof FieldRef) {
fields.add((FieldRef) param);
}
}
return new Where(SQLBase.quote(s, pattern, params), fields);
}
 
static private final String normalizeOperator(final String op) {
String res = op.trim();
172,19 → 196,70
return res;
}
 
static private final String comparison(final FieldRef ref, final String op, final String y) {
static public final String comparison(final FieldRef ref, final String op, final String y) {
return comparison(null, ref.getField(), ref.getFieldRef(), op, y);
}
 
static public final String comparison(final SQLSyntax s, final String x, final String op, final String y) {
return comparison(Objects.requireNonNull(s, "Missing syntax"), null, x, op, y);
}
 
static private final String comparison(SQLSyntax s, final DBStructureItem<?> syntaxSupplier, final String x, final String op, final String y) {
Objects.requireNonNull(op, "Missing operator");
if (op == NULL_IS_DATA_EQ || op == NULL_IS_DATA_NEQ) {
return ref.getField().getDBSystemRoot().getSyntax().getNullIsDataComparison(ref.getFieldRef(), op == NULL_IS_DATA_EQ, y);
if (s == null)
s = syntaxSupplier.getDBSystemRoot().getSyntax();
return s.getNullIsDataComparison(x, op == NULL_IS_DATA_EQ, y);
} else {
return ref.getFieldRef() + " " + op + " " + y;
return x + ' ' + op + ' ' + y;
}
}
 
static private final String getInClause(final FieldRef field1, final boolean in, final String inParens) {
final String op = in ? " in (" : " not in (";
return field1.getFieldRef() + op + inParens + ")";
return getInClause(field1.getFieldRef(), in ? RowComparison.IN : RowComparison.NOT_IN, inParens);
}
 
static private final String getInClause(final String expr, final RowComparison cmp, final String inParens) {
final String op = cmp == RowComparison.IN ? " in (" : " not in (";
return expr + op + inParens + ')';
}
 
static public enum RowComparison {
IN, NOT_IN
}
 
static public enum NullValue {
IS_DATA, IS_FORBIDDEN, IS_UNKNOWN
}
 
static public final String getCompareValuesClause(final String expr, final RowComparison cmp, final Collection<?> values, final SQLType type) {
return getCompareValuesClause(expr, cmp, NullValue.IS_FORBIDDEN, values, type);
}
 
static public final String getCompareValuesClause(final String expr, final RowComparison cmp, final NullValue nullMode, Collection<?> values, final SQLType type) {
final boolean addNull;
if (nullMode != NullValue.IS_UNKNOWN && values.contains(null)) {
if (nullMode == NullValue.IS_FORBIDDEN)
throw new IllegalArgumentException("Values contains a null value : " + values);
assert nullMode == NullValue.IS_DATA;
addNull = true;
} else {
addNull = false;
}
if (addNull) {
values = values.stream().filter((i) -> i != null).collect(Collectors.toList());
}
String res = getInClause(expr, cmp, CollectionUtils.join(values, ",", (input) -> type.toString(input)));
if (addNull) {
if (cmp == RowComparison.IN) {
res = expr + " is null or " + res;
} else {
res = expr + " is not null and " + res;
}
}
return res;
}
 
private final List<FieldRef> fields;
private final String clause;
 
226,6 → 301,7
*
* @param field1 le champs à tester.
* @param values les valeurs.
* @deprecated use {@link #inValues(FieldRef, Collection)}
*/
public Where(final FieldRef field1, final Collection<?> values) {
this(field1, true, values);
237,6 → 313,8
* @param field1 le champs à tester.
* @param in <code>true</code> for "in", <code>false</code> for "not in".
* @param values les valeurs.
* @deprecated use {@link #inValues(FieldRef, Collection)} or
* {@link #notInValues(FieldRef, Collection)}
*/
public Where(final FieldRef field1, final boolean in, final Collection<?> values) {
if (values.isEmpty()) {
244,14 → 322,9
this.clause = in ? FALSE.getClause() : TRUE.getClause();
} else {
this.fields = Collections.singletonList(field1);
this.clause = getInClause(field1, in, CollectionUtils.join(values, ",", new ITransformer<Object, String>() {
@Override
public String transformChecked(final Object input) {
return field1.getField().getType().toString(input);
this.clause = getCompareValuesClause(field1.getFieldRef(), in ? RowComparison.IN : RowComparison.NOT_IN, values, field1.getField().getType());
}
}));
}
}
 
public Where(final FieldRef field1, final boolean in, final SQLSelect subQuery) {
this.fields = Collections.singletonList(field1);
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLResultSet.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
34,8 → 34,12
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
46,6 → 50,9
*/
public class SQLResultSet implements ResultSet {
 
// useful since some JDBC drivers (e.g. PgResultSet) implement getObject(col, clazz) by the
// equivalent of clazz.cast(getObject(col)). I.e. getObject(col, Long.class) will fail if col
// is an int.
static public final <T> T getValue(final ResultSet rs, final Class<T> clz, final int columnIndex) throws SQLException {
final Object res;
if (clz == Object.class)
73,10 → 80,11
else if (Time.class.isAssignableFrom(clz))
res = rs.getTime(columnIndex);
else
res = rs.getObject(columnIndex);
res = rs.getObject(columnIndex, clz);
return clz.cast(res);
}
 
// see above comment
static public final <T> T getValue(final ResultSet rs, final Class<T> clz, final String columnLabel) throws SQLException {
final Object res;
if (clz == Object.class)
104,7 → 112,7
else if (Time.class.isAssignableFrom(clz))
res = rs.getTime(columnLabel);
else
res = rs.getObject(columnLabel);
res = rs.getObject(columnLabel, clz);
return clz.cast(res);
}
 
117,6 → 125,24
}
}
 
static public final <T> List<T> getList(final ResultSet rs, int column, final Class<? extends T> itemType) throws SQLException {
final Array array = rs.getArray(column);
try {
return Collections.unmodifiableList(readArray(array, itemType, new ArrayList<>()));
} finally {
array.free();
}
}
 
static public final <T, C extends Collection<T>> C readArray(final Array array, final Class<? extends T> itemType, final C res) throws SQLException {
final ResultSet rs = array.getResultSet();
while (rs.next()) {
// 1 is the index
res.add(getValue(rs, itemType, 2));
}
return res;
}
 
private final ResultSet delegate;
private final ResultSetFullnameHelper helper;
private final CachedTransformer<String, Integer, SQLException> indexes;
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRowAccessor.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,6 → 14,7
package org.openconcerto.sql.model;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
26,6 → 27,7
import java.math.BigDecimal;
import java.sql.Clob;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
196,6 → 198,25
public abstract Number getIDNumber();
 
/**
* Get the row reference.
*
* @return the row reference, <code>null</code> if some fields of the
* {@link SQLTable#getPrimaryKeyFields() primary key} are missing or <code>null</code>.
*/
public final RowRef getRowRef() {
final List<String> pkFields = this.getTable().getPKsNames();
final List<Object> pk = new ArrayList<>(pkFields.size());
for (final String f : pkFields) {
final Object o = this.getObject(f);
// Don't need contains() because a PK cannot contain NULL in the DB.
if (o == null)
return null;
pk.add(o);
}
return new RowRef(this.getTable(), Collections.unmodifiableList(pk), true);
}
 
/**
* Whether this row is the undefined row. Return <code>false</code> if both the
* {@link #getIDNumber() ID} and {@link SQLTable#getUndefinedIDNumber()} are <code>null</code>
* since no row can have <code>null</code> primary key in the database. IOW when
231,21 → 252,69
return ((Number) archiveVal).intValue() > 0;
}
 
public abstract SQLRowAccessor toImmutable();
 
public abstract boolean isFrozen();
 
/**
* Creates an SQLRow from these values, without any DB access.
*
* @return an SQLRow with the same values as this.
*/
public abstract SQLRow asRow();
public final SQLRow asRow() {
return this.asRow(null);
}
 
public abstract SQLRow asRow(final Boolean immutable);
 
/**
* Return an immutable SQLRow with only the passed fields retained. I.e. the returned instance
* can consume less memory.
*
* @param fields which fields to retain.
* @return a {@link #isFrozen() frozen} SQLRow.
*/
public final SQLRow trimmedRow(final VirtualFields fields) {
final Set<String> fieldsNames = this.getTable().getFieldsNames(fields);
if (this instanceof SQLRow && this.isFrozen() && fieldsNames.containsAll(this.getFields()))
return (SQLRow) this;
return SQLRow.trim(this, SQLRowAccessor::getValues, fieldsNames);
}
 
public final SQLRow fetchNewRow() {
return this.fetchNewRow(true);
}
 
/**
* Return a new instance with up-to-date values.
*
* @param useCache <code>true</code> to use the {@link SQLDataSource#isCacheEnabled() cache}.
* @return a new instance.
*/
public final SQLRow fetchNewRow(final boolean useCache) {
return new SQLRow(this.getTable(), this.getID()).fetchValues(useCache);
}
 
/**
* Creates an SQLRowValues from these values, without any DB access.
*
* @return an SQLRowValues with the same values as this.
*/
public abstract SQLRowValues asRowValues();
public final SQLRowValues asRowValues() {
return this.asRowValues(null);
}
 
/**
* Creates an SQLRowValues from these values, without any DB access.
*
* @param immutable <code>true</code> if the result must be
* {@link SQLRowValuesCluster#isFrozen() frozen}, <code>false</code> if it must not,
* <code>null</code> if the caller doesn't care and just wants the fastest result.
* @return an SQLRowValues with the same values as this.
*/
public abstract SQLRowValues asRowValues(final Boolean immutable);
 
/**
* Creates an SQLRowValues with just this ID, and no other values.
*
* @return an empty SQLRowValues.
267,6 → 336,8
 
public abstract Object getObject(String fieldName);
 
public abstract Object getObjectNoCheck(String fieldName);
 
/**
* Return the value for the passed field only if already present in this instance.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLServer.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
20,6 → 20,8
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.utils.SQL_URL;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.NetUtils;
import org.openconcerto.utils.Tuple2.List2;
import org.openconcerto.utils.cc.CopyOnWriteMap;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
284,6 → 286,7
 
// fire once all the bases are loaded so that the graph is coherent
return this.getDBSystemRoot().getGraph().atomicRefresh(new Callable<Map<String, TablesMap>>() {
 
@Override
public Map<String, TablesMap> call() throws Exception {
final Map<String, TablesMap> res = new HashMap<String, TablesMap>();
573,7 → 576,41
return DBFileCache.create(this);
}
 
/**
* The host name of this server.
*
* @return the host name, <code>null</code> if not accessed through network.
*/
public final String getHostname() {
return this.getSQLSystem().getHostname(this.getName());
return this.getHostnameAndPath().get0();
}
 
public final List2<String> getHostnameAndPath() {
return this.getSQLSystem().getHostnameAndPath(this.getName());
}
 
public final boolean isPermanent() {
return this.getSQLSystem().isPermanent(this.getName());
}
 
/**
* Whether this server runs on the local machine.
*
* @return <code>true</code> if the JVM runs on the same machine than <code>this</code>.
*/
protected final boolean isLocalhost() {
// this method cannot be in SQLSyntax since it is used in the constructor of SQLDataSource
// (which is needed by SQLSyntax.create())
 
final String host = this.getHostname();
if (host == null)
return true;
final int colonIndex = host.indexOf(':');
final String hostWOPort = colonIndex < 0 ? host : host.substring(0, colonIndex);
return NetUtils.isSelfAddr(hostWOPort);
}
 
public final boolean isPersistent() {
return this.getSQLSystem() != SQLSystem.H2 || !this.getName().equals(SQLSystem.H2_IN_MEMORY);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/DBRoot.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
27,6 → 27,7
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.sql.utils.SQL_URL;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.Tuple2.List2;
 
import java.net.URISyntaxException;
import java.sql.SQLException;
335,7 → 336,7
return this.equalsDesc(o, null);
}
 
public final String equalsDesc(final DBRoot o, final SQLSystem otherSystem) {
public final String equalsDesc(final DBRoot o, final SQLSyntax otherSystem) {
if (this == o)
return null;
if (null == o)
364,8 → 365,12
* jdbc:h2:file:/a/b/c).
*/
public final SQL_URL getURL() {
final String hostname = this.getServer().getHostname();
if (hostname == null)
// check that our name doesn't contain a path, otherwise we would lose it
// e.g. dbserv:8084/~/sample
final List2<String> hostAndPath = this.getServer().getHostnameAndPath();
final String hostname = hostAndPath.get0();
// TODO return JDBCUrl
if (hostname == null || hostAndPath.get1() != null)
return null;
final SQLSystem system = this.getServer().getSQLSystem();
String url = system.name().toLowerCase() + "://" + this.getDBSystemRoot().getDataSource().getUsername() + "@" + hostname + "/";
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntaxPG.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
26,7 → 26,9
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.xml.XMLCodecUtils;
 
import java.beans.DefaultPersistenceDelegate;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
67,7 → 69,7
*
* @author Sylvain CUAZ
*/
class SQLSyntaxPG extends SQLSyntax {
public class SQLSyntaxPG extends SQLSyntax {
 
// From http://www.postgresql.org/docs/9.0/interactive/multibyte.html
static final short MAX_BYTES_PER_CHAR = 4;
91,9 → 93,11
DATE_SPECS.put(DateProp.MINUTE, "MI");
DATE_SPECS.put(DateProp.SECOND, "SS");
DATE_SPECS.put(DateProp.MICROSECOND, "US");
 
XMLCodecUtils.register(SQLSyntaxPG.class, new DefaultPersistenceDelegate(new String[] {}));
}
 
SQLSyntaxPG() {
public SQLSyntaxPG() {
super(SQLSystem.POSTGRESQL, DATE_SPECS);
this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
this.typeNames.addAll(Short.class, "smallint", "int2");
295,6 → 299,22
return SQLUtils.findWithSQLState(exn).getSQLState().equals("40P01");
}
 
@Override
public boolean isTableNotFoundException(Exception exn) {
return SQLUtils.findWithSQLState(exn).getSQLState().equals("42P01");
}
 
@Override
public String getSetLockTimeoutQuery(int millis) {
return "SET lock_timeout to " + millis;
}
 
@Override
public String getShowLockTimeoutQuery() {
final String interval = cast("setting||unit", "INTERVAL");
return "select CAST( EXTRACT(milliseconds from " + interval + ") as int) from pg_settings where \"name\" = 'lock_timeout'";
}
 
private static final Pattern NOW_PTRN = Pattern.compile("\\(?'now'::text\\)?(::timestamp)");
 
@Override
564,4 → 584,74
public String getDropTrigger(Trigger t) {
return "DROP TRIGGER " + SQLBase.quoteIdentifier(t.getName()) + " on " + t.getTable().getSQLName().quote();
}
 
@Override
public String getSessionIDExpression() {
return "pg_backend_pid()";
}
 
@Override
public String getSessionsQuery(final DBSystemRoot sysRoot, final boolean includeSelf) {
final String allRows = "SELECT pid as \"ID\", query as \"QUERY\", usename as \"USER_NAME\" FROM pg_stat_activity WHERE datname=" + quoteString(sysRoot.getName());
if (includeSelf)
return allRows;
return allRows + " and pid != " + this.getSessionIDExpression();
}
 
@Override
public String getVersionFunction() {
// shorter than version() : "PostgreSQL 9.6.9 on x86_64-pc-linux-gnu (Ubuntu
// 9.6.9-2.pgdg16.04+1), compiled by gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609,
// 64-bit"
return "current_setting('server_version')";
}
 
@Override
public String getAllowConnectionsQuery(String sysRootName, boolean allow) {
// REVOKE CONNECT ON DATABASE "ControleKD" TO public : superusers can still connect
 
// to recover if all DB (even postgres and templates) run postgres --single
// (single user / standalone mode ignores datallowconn)
return "UPDATE pg_database SET datallowconn=" + SQLType.getBoolean(this).toString(allow) + " WHERE datname=" + quoteString(sysRootName);
// "ALTER DATABASE WITH ALLOW_CONNECTIONS" isn't allowed on our own base
}
 
@Override
public boolean isConnectionDisallowedException(SQLException exn) {
return "55000".equals(exn.getSQLState());
}
 
@Override
public String getSQLArray(final List<String> sqlExpressions, final String type) {
if (type == null)
return "ARRAY[" + CollectionUtils.join(sqlExpressions, ", ") + ']';
else
return this.quoteString('{' + CollectionUtils.join(sqlExpressions, ", ") + '}') + "::" + type + "[]";
}
 
@Override
public String getSQLArrayContains(String arrayExpression, String itemExpression) {
return itemExpression + " = ANY(" + arrayExpression + ")";
}
 
@Override
public String getSQLArrayLength(final String arrayExpression) {
return "ARRAY_LENGTH(" + arrayExpression + ", 1)";
}
 
@Override
public String getSQLArrayConcat(final String arrayExpression, final String array2Expression) {
return "array_cat(" + arrayExpression + ", " + array2Expression + ")";
}
 
@Override
public String getSQLArrayAppend(final String arrayExpression, final String itemExpression) {
// don't use || as it doesn't handle varchar literal
return "array_append(" + arrayExpression + ", " + itemExpression + ")";
}
 
@Override
public String getSQLArraySlice(final String arrayExpression, final String index1Expression, final String index2Expression) {
return '(' + arrayExpression + ")[" + index1Expression + ":" + index2Expression + "]";
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLSyntax.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
27,7 → 27,6
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.NetUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.ITransformer;
37,6 → 36,7
import java.io.FileFilter;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
319,7 → 319,7
if (sys == SQLSystem.POSTGRESQL)
res = new SQLSyntaxPG();
else if (sys == SQLSystem.H2)
res = new SQLSyntaxH2();
res = SQLSyntaxH2.create(sysRoot);
else if (sys == SQLSystem.MYSQL)
res = SQLSyntaxMySQL.create(sysRoot);
else if (sys == SQLSystem.MSSQL)
571,6 → 571,35
public abstract boolean isDeadLockException(final SQLException exn);
 
/**
* Whether the cause of the passed exception is a table not being found.
*
* @param exn an exception.
* @return <code>true</code> if the cause is a table not being found.
*/
public abstract boolean isTableNotFoundException(final Exception exn);
 
/**
* How to set the amount of time any statement waits while attempting to acquire a lock.
* <p>
* NOTE : some systems only support seconds precision, in that case the amount will be
* {@link RoundingMode#UP rounded up} so that the statement waits at least the passed amount.
* </p>
* <p>
* <strong>Warning</strong> : some systems will wait 2 or 3 times the passed amount.
* </p>
*
* @param millis the number of milliseconds.
* @return the SQL query.
*/
public String getSetLockTimeoutQuery(final int millis) {
return "SET lock_timeout " + millis;
}
 
public String getShowLockTimeoutQuery() {
return "SELECT lock_timeout()";
}
 
/**
* Something to be appended to CREATE TABLE statements, like "ENGINE = InnoDB".
*
* @return a String that need to be appended to CREATE TABLE statements.
710,9 → 739,8
sqlType = type + "(" + size + ")";
} else {
Log.get().warning("Unbounded varchar for " + f.getSQLName());
// if (this.getSystem() == SQLSystem.MYSQL)
// throw new IllegalStateException("MySQL doesn't support unbounded varchar and
// might truncate data if reducing size of " + f.getSQLName());
if (this.getSystem() == SQLSystem.MYSQL)
throw new IllegalStateException("MySQL doesn't support unbounded varchar and might truncate data if reducing size of " + f.getSQLName());
// don't specify size
sqlType = type;
}
1030,7 → 1058,7
if (delete)
t.getBase().getDataSource().execute("DELETE FROM " + t.getSQLName().quote());
_loadData(f, t);
t.fireTableModified(SQLRow.NONEXISTANT_ID);
t.fireTableModified();
if (level != null)
Log.get().log(level, "done loading " + f);
}
1066,18 → 1094,8
 
protected abstract void _storeData(SQLTable t, File f) throws IOException;
 
/**
* Whether the passed server runs on this machine.
*
* @param s the server to test.
* @return <code>true</code> if this jvm runs on the same machine than <code>s</code>.
*/
protected boolean isServerLocalhost(SQLServer s) {
return NetUtils.isSelfAddr(s.getName());
}
 
protected final void checkServerLocalhost(DBStructureItem<?> t) {
if (!this.isServerLocalhost(t.getServer()))
if (!t.getServer().isLocalhost())
throw new IllegalArgumentException("the server of " + t + " is not this computer: " + t.getServer());
}
 
1534,4 → 1552,98
}
};
}
 
/**
* The expression that returns the current session.
*
* @return the expression to know the current session ID.
* @see #getSessionsQuery(DBSystemRoot, boolean)
*/
public abstract String getSessionIDExpression();
 
public final String getSessionsQuery(final DBSystemRoot sysRoot) {
return this.getSessionsQuery(sysRoot, true);
}
 
/**
* Return a query to list the sessions in the passed system root.
*
* @param sysRoot the system root.
* @param includeSelf <code>true</code> if the session executing the query should be returned.
* @return a query returning ID, QUERY and USER_NAME columns.
*/
public abstract String getSessionsQuery(final DBSystemRoot sysRoot, final boolean includeSelf);
 
/**
* Return a query to grant privileges on a table.
*
* @param privileges which privileges, <code>null</code> for ALL, e.g. "SELECT".
* @param tableName which table.
* @param role which role, <code>null</code> for everyone, e.g. 'joe'.
* @return the query.
*/
public String getGrantQuery(final List<String> privileges, final SQLName tableName, final String role) {
final String priv = privileges == null ? "ALL" : CollectionUtils.join(privileges, ", ");
return "GRANT " + priv + " ON " + tableName + " TO " + (role == null ? getAllUsersForGrant() : role);
}
 
protected String getAllUsersForGrant() {
return "PUBLIC";
}
 
public String getVersionFunction() {
return "version()";
}
 
/**
* Return a query to allow/disallow connections to the passed database. I.e. allow exclusive
* access to the database.
*
* @param sysRootName the database name.
* @param allow <code>true</code> to allow connections, <code>false</code> to disallow.
* @return the query, <code>null</code> if not supported.
*/
public String getAllowConnectionsQuery(String sysRootName, final boolean allow) {
return null;
}
 
public boolean isConnectionDisallowedException(final SQLException exn) {
throw new UnsupportedOperationException();
}
 
public final String getSQLArray(final List<String> sqlExpressions) {
return this.getSQLArray(sqlExpressions, null);
}
 
/**
* Return an SQL expression of an array.
*
* @param sqlExpressions the items.
* @param type the component type, can be <code>null</code> to infer it from items (i.e. when
* <code>sqlExpressions</code> isn't empty).
* @return an SQL expression.
*/
public String getSQLArray(final List<String> sqlExpressions, final String type) {
throw new UnsupportedOperationException();
}
 
public String getSQLArrayContains(final String arrayExpression, final String itemExpression) {
throw new UnsupportedOperationException();
}
 
public String getSQLArrayLength(final String arrayExpression) {
throw new UnsupportedOperationException();
}
 
public String getSQLArrayConcat(final String arrayExpression, final String array2Expression) {
throw new UnsupportedOperationException();
}
 
public String getSQLArrayAppend(final String arrayExpression, final String itemExpression) {
throw new UnsupportedOperationException();
}
 
public String getSQLArraySlice(final String arrayExpression, final String index1Expression, final String index2Expression) {
throw new UnsupportedOperationException();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLTable.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
20,6 → 20,7
import org.openconcerto.sql.model.SQLSelect.LockStrength;
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.Where.RowComparison;
import org.openconcerto.sql.model.graph.DatabaseGraph;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Rule;
30,6 → 31,7
import org.openconcerto.sql.utils.ChangeTable;
import org.openconcerto.sql.utils.PartialUniqueTrigger;
import org.openconcerto.sql.utils.SQLCreateMoveableTable;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.sql.utils.UniqueConstraintCreatorHelper;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;
48,6 → 50,8
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
63,6 → 67,7
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
122,7 → 127,7
};
 
@SuppressWarnings("unchecked")
public static final Map<String, Number> getUndefIDs(final SQLSchema schema) {
private static final Map<String, Number> getUndefIDs(final SQLSchema schema) {
assert Thread.holdsLock(UNDEFINED_IDs);
if (!UNDEFINED_IDs.containsKey(schema)) {
final Map<String, Number> r;
255,12 → 260,51
}
final int res = toInsert.size() + toUpdate.size();
if (res > 0) {
undefT.fireTableModified(SQLRow.NONEXISTANT_ID);
undefT.fireTableModified();
}
return res;
}
}
 
public static final boolean unsetUndefIDs(SQLSchema schema, Set<String> tableNames) throws SQLException {
final boolean tableLoaded = schema.getTable(undefTable) != null;
final boolean tableExists = unsetUndefIDs(schema.getDBSystemRoot(), schema.getDBRoot().getName(), tableNames);
if (tableLoaded != tableExists)
throw new IllegalStateException("Root not up to date, table loaded : " + tableLoaded + ", table exists : " + tableExists);
return tableExists;
}
 
public static final boolean unsetUndefIDs(final DBSystemRoot sysRoot, final String rootName, Set<String> tableNames) throws SQLException {
final SQLName undefSQLName = new SQLName(Objects.requireNonNull(rootName, "Missing root name"), undefTable);
final int deletedCount;
try {
// If already in a transaction, don't risk aborting it if a table doesn't exist.
// (it's not strictly required for H2 and MySQL, since the transaction is *not*
// aborted)
deletedCount = SQLUtils.executeAtomic(sysRoot.getDataSource(), (ds) -> {
try (final Statement stmt = ds.getConnection().createStatement()) {
final int res = stmt.executeUpdate("DELETE FROM " + undefSQLName + " WHERE "
+ Where.getCompareValuesClause(SQLBase.quoteIdentifier(UNDEF_TABLE_TABLENAME_FIELD), RowComparison.IN, tableNames, SQLType.getFromSyntax(sysRoot.getSyntax(), Types.VARCHAR, 250)));
assert res >= 0;
return res;
}
});
} catch (SQLException e) {
// nothing to unset
if (sysRoot.getSyntax().isTableNotFoundException(e))
return false;
throw e;
}
if (deletedCount > 0) {
// rootName might exist and thus the above query might succeed, but the root might not
// be loaded or up to date.
final SQLTable undefT = sysRoot.getDescLenient(undefSQLName, SQLTable.class);
if (undefT != null)
undefT.fireTableModified();
}
return true;
}
 
static private boolean AFTER_TX_DEFAULT = true;
 
static public void setDefaultAfterTransaction(final boolean val) {
799,14 → 843,42
}
 
/**
* Return the primary keys of this table.
* Return the fields of the primary key.
*
* @return the fields (SQLField) which are the keys of this table, can be empty.
* @return the fields of the primary key of this table, can be empty.
*/
public synchronized Set<SQLField> getPrimaryKeys() {
public synchronized Set<SQLField> getPrimaryKeyFields() {
return this.primaryKeys;
}
 
@Deprecated
public final Set<SQLField> getPrimaryKeys() {
return this.getPrimaryKeyFields();
}
 
public final List<String> getPKsNames() {
return this.getPKsNames(new ArrayList<String>());
}
 
public final <C extends Collection<String>> C getPKsNames(C pks) {
for (final SQLField f : this.getPrimaryKeys()) {
pks.add(f.getName());
}
return pks;
}
 
public final RowRef createRowRef(final Object... pk) {
return this.createRowRef(Arrays.asList(pk));
}
 
public final RowRef createRowRef(final List<?> pk) {
return new RowRef(this, pk);
}
 
public final RowRef createRowRef(final Number id) {
return new RowRef(this, id);
}
 
public final Set<Link> getForeignLinks() {
return this.getDBSystemRoot().getGraph().getForeignLinks(this);
}
1668,11 → 1740,14
this.removeTableModifiedListener(new BridgeListener(l));
}
 
public final void fireTableModified() {
this.fireTableModified(SQLRow.NONEXISTANT_ID);
}
 
/**
* Previent tous les listeners de la table qu'il y a eu une modification ou ajout si modif de
* d'une ligne particuliere.
* Previent tous les listeners de la table qu'il y a eu une modification.
*
* @param id -1 signifie tout est modifié.
* @param id which ID was modified, {@link SQLRow#NONEXISTANT_ID} meaning all rows.
*/
public void fireTableModified(final int id) {
this.fire(Mode.ROW_UPDATED, id);
1915,7 → 1990,7
// are the closest possible. NOTE that otherSystem is not required to be the system of the other
// table, it might be something else if the other table was loaded into a system different than
// the one which created the dump.
public synchronized String equalsDesc(SQLTable o, SQLSystem otherSystem, boolean compareName) {
public synchronized String equalsDesc(SQLTable o, SQLSyntax otherSyntax, boolean compareName) {
if (o == null)
return "other table is null";
final boolean name = !compareName || this.getName().equals(o.getName());
1930,17 → 2005,17
// if (!this.getTriggers().keySet().equals(o.getTriggers().keySet()))
// return "triggers names unequal : " + this.getTriggers() + " " + o.getTriggers();
// }
final boolean checkComment = otherSystem == null || this.getServer().getSQLSystem().isTablesCommentSupported() && otherSystem.isTablesCommentSupported();
final boolean checkComment = otherSyntax == null || this.getServer().getSQLSystem().isTablesCommentSupported() && otherSyntax.getSystem().isTablesCommentSupported();
if (checkComment && !CompareUtils.equals(this.getComment(), o.getComment()))
return "comment unequal : " + SQLBase.quoteStringStd(this.getComment()) + " != " + SQLBase.quoteStringStd(o.getComment());
return this.equalsChildren(o, otherSystem);
return this.equalsChildren(o, otherSyntax);
}
 
private synchronized String equalsChildren(SQLTable o, SQLSystem otherSystem) {
private synchronized String equalsChildren(SQLTable o, SQLSyntax otherSyntax) {
if (!this.getChildrenNames().equals(o.getChildrenNames()))
return "fields differences: " + this.getChildrenNames() + "\n" + o.getChildrenNames();
 
final String noLink = equalsChildrenNoLink(o, otherSystem);
final String noLink = equalsChildrenNoLink(o, otherSyntax);
if (noLink != null)
return noLink;
 
1950,6 → 2025,7
if (thisLinks.size() != oLinks.size())
return "different number of foreign keys " + thisLinks + " != " + oLinks;
final SQLSystem thisSystem = this.getServer().getSQLSystem();
final SQLSystem otherSystem = otherSyntax == null ? null : otherSyntax.getSystem();
for (final Link l : thisLinks) {
final Link ol = o.getDBSystemRoot().getGraph().getForeignLink(o, l.getCols());
if (ol == null)
2037,7 → 2113,7
* @param otherSystem the system <code>o</code> originates from, can be <code>null</code>.
* @return <code>null</code> if each fields of this exists in <code>o</code> and is equal to it.
*/
public synchronized final String equalsChildrenNoLink(SQLTable o, SQLSystem otherSystem) {
public synchronized final String equalsChildrenNoLink(SQLTable o, SQLSyntax otherSystem) {
for (final SQLField f : this.getFields()) {
final SQLField oField = o.getField(f.getName());
final boolean isPrimary = this.getPrimaryKeys().contains(f);
2106,21 → 2182,6
return res;
}
 
public final List<String> getPKsNames() {
return this.getPKsNames(new ArrayList<String>());
}
 
public synchronized final <C extends Collection<String>> C getPKsNames(C pks) {
for (final SQLField f : this.getPrimaryKeys()) {
pks.add(f.getName());
}
return pks;
}
 
public final String[] getPKsNamesArray() {
return getPKsNames().toArray(new String[0]);
}
 
/**
* Return the indexes mapped by column names. Ie a key will have as value every index that
* mentions it, and a multi-column index will be in several entries. Note: this is not robust
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLDataSource.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
25,6 → 25,7
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cache.CacheResult;
import org.openconcerto.utils.cache.ICacheSupport;
import org.openconcerto.utils.cc.ITransformerExn;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
285,7 → 286,7
 
// if TCP isn't used or is used to connect to localhost, TCP should be quite robust
private final Level getLogLevelForIgnoredTCPParam() {
return SQLSyntax.get(this.sysRoot).isServerLocalhost(this.sysRoot.getServer()) ? Level.CONFIG : Level.WARNING;
return this.sysRoot.getServer().isLocalhost() ? Level.CONFIG : Level.WARNING;
}
 
public final void setTCPKeepAlive(final boolean b) {
836,6 → 837,15
return this.handlers.get(Thread.currentThread());
}
 
public final <T, X extends Exception> T useConnection(final ITransformerExn<? super SQLDataSource, T, X> run) throws SQLException, X {
return this.useConnection(new ConnectionHandlerNoSetup<T, X>() {
@Override
public T handle(SQLDataSource ds) throws X {
return run.transformChecked(ds);
}
});
}
 
/**
* Use a single connection to execute <code>handler</code>.
*
/trunk/OpenConcerto/src/org/openconcerto/sql/model/SQLRow.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
23,6 → 23,7
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.SetMap;
import org.openconcerto.utils.Tuple2.List2;
42,6 → 43,7
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.logging.Level;
 
import org.apache.commons.dbutils.ResultSetHandler;
102,15 → 104,17
* @see SQLRow#SQLRow(SQLTable, Map)
* @deprecated use {@link SQLRowListRSH} or {@link SQLRowValuesListFetcher} instead or if you
* must use a {@link ResultSet} call
* {@link #createFromRS(SQLTable, ResultSet, ResultSetMetaData, boolean)} thus
* avoiding the potentially costly {@link ResultSet#getMetaData()}
* {@link #createListFromRS(SQLTable, ResultSet, boolean)} thus avoiding the
* potentially costly {@link ResultSet#getMetaData()}
*/
public static final SQLRow createFromRS(SQLTable table, ResultSet rs, final boolean onlyTable) throws SQLException {
return createFromRS(table, rs, rs.getMetaData(), onlyTable);
}
 
// see createListFromRS()
@Deprecated
public static final SQLRow createFromRS(SQLTable table, ResultSet rs, final ResultSetMetaData rsmd, final boolean onlyTable) throws SQLException {
return createFromRS(table, rs, getFieldNames(table, rsmd, onlyTable));
return createFromRS(table, rs, new SQLFieldRowProcessor(table, getFieldNames(table, rsmd, onlyTable)), true);
}
 
private static final List<String> getFieldNames(SQLTable table, final ResultSetMetaData rsmd, final boolean tableOnly) throws SQLException {
130,18 → 134,10
return names;
}
 
// MAYBE create an opaque class holding names so that we can make this method, getFieldNames()
// and createListFromRS() public
static final SQLRow createFromRS(SQLTable table, ResultSet rs, final List<String> names) throws SQLException {
final int indexCount = names.size();
// ATTN doesn't check that names are fields of table
public static final SQLRow createFromRS(SQLTable table, ResultSet rs, final SQLFieldRowProcessor rowProc, final boolean immutable) throws SQLException {
final Map<String, Object> m = rowProc.toMap(rs);
 
final Map<String, Object> m = new HashMap<String, Object>(indexCount);
for (int i = 0; i < indexCount; i++) {
final String colName = names.get(i);
if (colName != null)
m.put(colName, rs.getObject(i + 1));
}
 
final Number id = getID(m, table, true);
// e.g. LEFT JOIN : missing values are null
if (id == null)
148,7 → 144,10
return null;
 
// pass already found ID
return new SQLRow(table, id, m);
final SQLRow res = new SQLRow(table, id, Collections.unmodifiableMap(m));
if (immutable)
res.freeze();
return res;
}
 
/**
162,7 → 161,7
* @throws SQLException if an error occurs while reading <code>rs</code>.
*/
public static final List<SQLRow> createListFromRS(SQLTable table, ResultSet rs, final boolean tableOnly) throws SQLException {
return createListFromRS(table, rs, getFieldNames(table, rs.getMetaData(), tableOnly));
return createListFromRS(table, rs, getFieldNames(table, rs.getMetaData(), tableOnly), true);
}
 
/**
172,17 → 171,19
* @param rs the result set.
* @param names the name of the field for each column, nulls are ignored, e.g. ["DESIGNATION",
* null, "ID"].
* @param immutable <code>true</code> if the result should be immutable.
* @return the data of the result set as SQLRows.
* @throws SQLException if an error occurs while reading <code>rs</code>.
*/
static final List<SQLRow> createListFromRS(SQLTable table, ResultSet rs, final List<String> names) throws SQLException {
static final List<SQLRow> createListFromRS(SQLTable table, ResultSet rs, final List<String> names, final boolean immutable) throws SQLException {
final List<SQLRow> res = new ArrayList<SQLRow>();
final SQLFieldRowProcessor rowProc = new SQLFieldRowProcessor(table, names);
while (rs.next()) {
final SQLRow row = createFromRS(table, rs, names);
final SQLRow row = createFromRS(table, rs, rowProc, immutable);
if (row != null)
res.add(row);
}
return res;
return immutable ? Collections.unmodifiableList(res) : res;
}
 
static final SQLRow createFromSelect(final SQLTable t, final VirtualFields vfs, final int id, final LockStrength l) {
190,8 → 191,9
sel.setLockStrength(l);
sel.setWhere(new Where(t.getKey(), "=", id));
@SuppressWarnings("unchecked")
final Map<String, ?> map = (Map<String, ?>) t.getDBSystemRoot().getDataSource().execute(sel.asString(), new IResultSetHandler(SQLDataSource.MAP_HANDLER, l.equals(LockStrength.NONE)));
return new SQLRow(t, id, map);
final Map<String, Object> map = (Map<String, Object>) t.getDBSystemRoot().getDataSource().execute(sel.asString(),
new IResultSetHandler(SQLDataSource.MAP_HANDLER, l.equals(LockStrength.NONE)));
return new SQLRow(t, id, map == null ? null : Collections.unmodifiableMap(map));
}
 
/**
199,17 → 201,26
*
* @param t the table.
* @param id the ID.
* @return a new {@link #exists() existing} {@link #isFilled() filled} {@link #getFields()
* empty} row.
* @return a new {@link #exists() existing} {@link #isFilled() filled} {@link #isFrozen()
* frozen} {@link #getFields() empty} row.
*/
static final SQLRow createEmpty(final SQLTable t, final int id) {
return new SQLRow(t, id, Collections.<String, Object> emptyMap());
public static final SQLRow createEmpty(final SQLTable t, final int id) {
final SQLRow res = new SQLRow(t, id, Collections.<String, Object> emptyMap());
res.freeze();
return res;
}
 
public static final <R extends SQLRowAccessor, A> SQLRow trim(final R r, final BiFunction<? super R, ? super A, ? extends Map<String, Object>> getVals, final A arg) {
final SQLRow res = new SQLRow(r.getTable(), null, CollectionUtils.toImmutableMap(getVals.apply(r, arg)));
res.freeze();
return res;
}
 
private final int ID;
private final Number idNumber;
private Map<String, Object> values;
private boolean fetched;
private boolean frozen = false;
 
private SQLRow(SQLTable table, Number id) {
super(table);
239,14 → 250,14
* @throws IllegalArgumentException si values ne contient pas la clef de la table.
*/
public SQLRow(SQLTable table, Map<String, ?> values) {
this(table, null, values);
this(table, null, values == null ? null : CollectionUtils.toImmutableMap(values));
}
 
// allow to call getID() only once
private SQLRow(SQLTable table, final Number id, Map<String, ?> values) {
// Private since it doesn't make sure the map is immutable.
private SQLRow(SQLTable table, final Number id, Map<String, Object> values) {
this(table, id == null ? getID(values, table, false) : id);
// faire une copie, sinon backdoor pour changer les valeurs sans qu'on s'en aperçoive
this.setValues(values == null ? null : new HashMap<String, Object>(values));
this.setValues(values);
}
 
// return ID, must always be present but may be null if <code>nullAllowed</code>
265,7 → 276,50
}
}
 
public final SQLRow copy(final boolean freeze) {
final SQLRow res = new SQLRow(this.getTable(), this.getIDNumber());
if (this.isFilled())
// safe to share map, since it is immutable
res.setValues(this.getValues());
assert res.isFilled() == this.isFilled();
if (freeze)
res.freeze();
assert res.isFrozen() == freeze;
return res;
}
 
@Override
public final boolean isFrozen() {
return this.frozen;
}
 
private void checkFrozen() {
if (this.isFrozen())
throw new IllegalStateException("SQLRow is not modifiable");
}
 
/**
* Freeze this instance so that no modification can be made. Once this method returns, this
* instance can be safely published (e.g. stored into a field that is properly guarded by a
* lock) to other threads without further synchronizations.
*
* @return <code>true</code> if this call changed the frozen status.
*/
public final boolean freeze() {
if (this.frozen)
return false;
this.frozen = true;
return true;
}
 
@Override
public final SQLRow toImmutable() {
if (this.isFrozen())
return this;
return this.copy(true);
}
 
/**
* Whether this contains values or just the {@link #getIDNumber() id}. NOTE that
* {@link #getObject(String)} (and thus any other methods that call it) will access the DB if
* the requested field is {@link #getFields() missing} even if this returns <code>true</code>.
313,11 → 367,13
* @return a new instance.
*/
public final SQLRow fetchNew(final boolean useCache) {
return new SQLRow(this.getTable(), this.getIDNumber()).fetchValues(useCache);
return this.fetchNewRow(useCache);
}
 
@SuppressWarnings("unchecked")
SQLRow fetchValues(final boolean readCache, final boolean writeCache) {
// not necessary here, but allows to fail early and avoid a request
// implique trop de regression dans la branche : checkFrozen();
final IResultSetHandler handler = new IResultSetHandler(SQLDataSource.MAP_HANDLER, readCache, writeCache) {
@Override
public Set<SQLRow> getCacheModifiers() {
324,12 → 380,15
return Collections.singleton(SQLRow.this);
}
};
this.setValues((Map<String, Object>) this.getTable().getBase().getDataSource().execute(this.getQuery(), handler, false));
final Map<String, Object> values = (Map<String, Object>) this.getTable().getBase().getDataSource().execute(this.getQuery(), handler, false);
this.setValues(values == null ? null : Collections.unmodifiableMap(values));
return this;
}
 
// attention ne vérifie pas que tous les champs soient présents
// Attention ne vérifie pas que tous les champs soient présents.
// Private since it doesn't make sure the map is immutable.
private final void setValues(Map<String, Object> values) {
// implique trop de regression dans la branche : checkFrozen();
this.values = values;
if (!this.fetched)
this.fetched = true;
342,13 → 401,13
*/
@Override
public Set<String> getFields() {
return this.fetched ? Collections.unmodifiableSet(this.getValues().keySet()) : Collections.<String> emptySet();
return this.isFilled() ? this.getValues().keySet() : Collections.<String> emptySet();
}
 
// avoid Collections.unmodifiableSet() allocation
@Override
public boolean contains(String fieldName) {
return this.fetched ? this.getValues().containsKey(fieldName) : false;
return this.isFilled() ? this.getValues().containsKey(fieldName) : false;
}
 
private String getQuery() {
411,6 → 470,11
return this.getValues().get(field);
}
 
@Override
public final Object getObjectNoCheck(String field) {
return this.values.get(field);
}
 
/**
* Fetch from the DB this row and the next/previous one. ATTN the rows are locked
* {@link LockStrength#UPDATE for update}, but if this method is not called from within a
1040,7 → 1104,7
*/
@Override
public Map<String, Object> getAbsolutelyAll() {
return Collections.unmodifiableMap(this.getValues());
return this.getValues();
}
 
private static final VirtualFields ALL_VALUES_FIELDS = VirtualFields.ALL.difference(VirtualFields.KEYS, VirtualFields.ARCHIVE, VirtualFields.ORDER);
1093,13 → 1157,19
}
 
@Override
public SQLRow asRow() {
public SQLRow asRow(final Boolean immutable) {
if (immutable == null || this.isFrozen() == immutable)
return this;
else
return this.copy(immutable);
}
 
@Override
public final SQLRowValues asRowValues() {
return this.createUpdateRow();
public final SQLRowValues asRowValues(final Boolean immutable) {
final SQLRowValues res = this.createUpdateRow();
if (Boolean.TRUE.equals(immutable))
res.getGraph().freeze();
return res;
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightCopySelection.java
New file
0,0 → 1,64
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.users.rights;
 
import org.openconcerto.sql.model.SQLRowAccessor;
 
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.util.List;
 
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
 
public class UserRightCopySelection implements Transferable {
 
// JSOnArray[ID Right, object,hasRight]
public final JSONObject rights;
public final static DataFlavor rightsFlavor = new DataFlavor(JSONObject.class, "RightsDataFlavor");
 
public UserRightCopySelection(List<? extends SQLRowAccessor> list) {
this.rights = new JSONObject();
 
for (SQLRowAccessor sqlRowValues : list) {
JSONArray array = new JSONArray();
array.add(sqlRowValues.getForeignIDNumber("ID_RIGHT"));
array.add(sqlRowValues.getString("OBJECT"));
array.add(sqlRowValues.getBoolean("HAVE_RIGHT"));
this.rights.put(String.valueOf(sqlRowValues.getID()), array);
}
 
}
 
@Override
public DataFlavor[] getTransferDataFlavors() {
DataFlavor[] ret = { rightsFlavor };
return ret;
}
 
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return rightsFlavor.equals(flavor);
}
 
@Override
public synchronized Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (isDataFlavorSupported(flavor)) {
return this.rights;
} else {
throw new UnsupportedFlavorException(rightsFlavor);
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightsPanel.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
17,19 → 17,39
import org.openconcerto.sql.TM;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.SQLInsert;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.model.SQLTableEvent.Mode;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.sql.sqlobject.IComboSelectionItem;
import org.openconcerto.sql.users.UserManager;
import org.openconcerto.sql.view.ListeModifyPanel;
import org.openconcerto.sql.view.list.RowAction;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
 
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
38,6 → 58,9
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
 
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
 
public class UserRightsPanel extends JPanel {
 
// Liste des utilisateurs
130,8 → 153,40
c3.weighty = 1;
c3.fill = GridBagConstraints.BOTH;
this.add(pane, c3);
 
RowAction actionPaste = new RowAction(new AbstractAction("Coller") {
 
@Override
public void actionPerformed(ActionEvent e) {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable clipboardContent = clipboard.getContents(this);
 
if (clipboardContent.isDataFlavorSupported(UserRightCopySelection.rightsFlavor)) {
JSONObject rights;
try {
rights = (JSONObject) clipboardContent.getTransferData(UserRightCopySelection.rightsFlavor);
 
addRights(rights);
 
} catch (UnsupportedFlavorException | IOException e1) {
 
e1.printStackTrace();
}
}
}
 
}, true) {
@Override
public boolean enabledFor(List<SQLRowValues> selection) {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable clipboardContent = clipboard.getContents(this);
 
return clipboardContent.isDataFlavorSupported(UserRightCopySelection.rightsFlavor);
}
};
this.modifPanel.getListe().addIListeAction(actionPaste);
}
 
private void updateListFromSelection() {
final int selectedIndex = this.list.getSelectedIndex();
 
153,6 → 208,64
((UserRightSQLComponent) this.modifPanel.getAddComp()).setUserID(userID);
}
 
private void addRights(JSONObject rights) {
// Récupération des droits courants
final ListSQLRequest req = this.modifPanel.getListe().getRequest();
List<SQLRowValues> vals = req.getValues();
Map<Tuple2<Number, String>, SQLRowValues> currentUserRight = new HashMap<>();
for (SQLRowValues sqlRowValues : vals) {
currentUserRight.put(Tuple2.create(sqlRowValues.getForeignIDNumber("ID_RIGHT"), sqlRowValues.getString("OBJECT")), sqlRowValues);
}
 
try {
List<SQLInsert> inserts = new ArrayList<SQLInsert>();
 
final int selectedIndex = this.list.getSelectedIndex();
final boolean b = selectedIndex >= 0;
 
final int userID;
if (b) {
userID = this.list.getModel().getRowAt(selectedIndex).getID();
} else {
// since we don't display user in the list (to avoid undef)
// we need to always display at most one user
userID = this.list.getModel().getTable().getUndefinedID();
}
 
BigDecimal order = getTable().getMaxOrder().add(BigDecimal.ONE);
for (Object a : rights.values()) {
JSONArray array = (JSONArray) a;
Number right = (Number) array.get(0);
String obj = (String) array.get(1);
Boolean hasRight = (Boolean) array.get(2);
 
final Tuple2<Number, String> key = Tuple2.create(right, obj);
if (currentUserRight.containsKey(key)) {
SQLRowValues rowVals = currentUserRight.get(key);
if (!rowVals.getBoolean("HAVE_RIGHT").equals(hasRight)) {
rowVals.createEmptyUpdateRow().put("HAVE_RIGHT", hasRight).commit();
}
} else {
SQLInsert insert = new SQLInsert();
insert.add(getTable().getField("ID_RIGHT"), right);
insert.add(getTable().getField("HAVE_RIGHT"), hasRight);
insert.add(getTable().getField("OBJECT"), obj);
insert.add(getTable().getField("ID_USER_COMMON"), userID);
insert.add(getTable().getOrderField(), order);
 
inserts.add(insert);
order = order.add(BigDecimal.ONE);
}
}
if (!inserts.isEmpty()) {
SQLInsert.executeMultiple(getTable().getDBSystemRoot(), inserts);
getTable().fire(new SQLTableEvent(getTable(), SQLRow.NONEXISTANT_ID, Mode.ROW_ADDED));
}
} catch (SQLException e1) {
ExceptionHandler.handle("Erreur lors de la duplication des droits", e1);
}
}
 
public final SQLTable getTable() {
return this.modifPanel.getElement().getTable();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightsManager.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
128,8 → 128,12
 
public static final UserRights getCurrentUserRights() {
final UserManager mngr = UserManager.getInstance();
final UserRightsManager rightsMngr = getInstance();
return getCurrentUserRights(rightsMngr, mngr);
}
 
public static final UserRights getCurrentUserRights(final UserRightsManager rightsMngr, final UserManager mngr) {
// if right table doesn't exist, give access to everything
final UserRightsManager rightsMngr = getInstance();
if (rightsMngr == null)
return UserRights.ALLOW_ALL;
// else if there are rights (and thus users) but no user is defined, use the default rights
/trunk/OpenConcerto/src/org/openconcerto/sql/users/rights/UserRightSQLElement.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,14 → 14,19
package org.openconcerto.sql.users.rights;
 
import static java.util.Arrays.asList;
 
import org.openconcerto.sql.element.GlobalMapper;
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 org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.sql.view.list.action.ListEvent;
import org.openconcerto.sql.view.list.action.SQLRowValuesAction;
import org.openconcerto.sql.view.list.action.SQLRowValuesAction.PredicateRowAction;
 
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
55,11 → 60,19
return res;
}
 
static private final SQLRowValuesAction COPY_ACTION = new PredicateRowAction(true, (le) -> {
final Toolkit toolkit = Toolkit.getDefaultToolkit();
final Clipboard systemClipboard = toolkit.getSystemClipboard();
systemClipboard.setContents(new UserRightCopySelection(le.getSelectedRowAccessors()), null);
}).setPredicate(ListEvent.getNonEmptySelectionPredicate()).setName("Copier");
 
public UserRightSQLElement(final DBRoot r) {
super(r.findTable(TABLE_NAME), null, "sql.user-right");
 
final UserRightGroup group = new UserRightGroup();
GlobalMapper.getInstance().map(UserRightSQLComponent.ID, group);
setDefaultGroup(group);
getRowValuesActions().add(COPY_ACTION);
}
 
protected List<String> getListFields() {
/trunk/OpenConcerto/src/org/openconcerto/sql/users/UserCommonSQLElement.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
123,7 → 123,7
@Override
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
final JLabel res = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
final boolean isCurrentUser = ITableModel.getLine(table.getModel(), row).getRow().getID() == UserManager.getUserID();
final boolean isCurrentUser = ITableModel.getLine(table.getModel(), row).getRowAccessor().getID() == UserManager.getUserID();
final int targetStyle = isCurrentUser ? Font.BOLD : Font.PLAIN;
if ((res.getFont().getStyle() & targetStyle) == 0) {
res.setFont(res.getFont().deriveFont(targetStyle));
519,6 → 519,7
final String pass = String.valueOf(this.getPassField().getPassword());
final String dbPass = Boolean.getBoolean(LEGACY_PASSWORDS) ? '"' + pass + '"' : pass;
this.encryptedPass.setText(Login.encodePassword(dbPass));
System.err.println("UserCommonSQLElement.UserSQLComponent.updateEncrypted() encode:"+dbPass+":"+this.encryptedPass.getText());
}
 
private boolean checkValidityPassword() {
/trunk/OpenConcerto/src/org/openconcerto/sql/users/UserManager.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
67,6 → 67,10
 
public static final User getUser() {
final UserManager mngr = getInstance();
return getCurrentUser(mngr);
}
 
public static User getCurrentUser(final UserManager mngr) {
return mngr == null ? null : mngr.getCurrentUser();
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/TextDefault.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
17,7 → 17,6
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.model.SQLTable;
 
49,8 → 48,8
@SuppressWarnings("unchecked")
@Override
protected void changeImpl(SQLTable t) throws SQLException {
final String infoSchema = SQLSelect.quote("SELECT TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE from information_schema.COLUMNS where TABLE_SCHEMA=%s and TABLE_NAME=%s", t.getBase()
.getName(), t.getName());
final String infoSchema = t.getBase().quote("SELECT TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE from information_schema.COLUMNS where TABLE_SCHEMA=%s and TABLE_NAME=%s",
t.getBase().getName(), t.getName());
final Map<String, Object> defaults = (Map<String, Object>) this.getDS().execute(infoSchema, new ResultSetHandler() {
public Object handle(ResultSet rs) throws SQLException {
final Map<String, Object> res = new HashMap<String, Object>();
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/RenamePK.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,11 → 14,11
package org.openconcerto.sql.changer.convert;
 
import static java.util.Collections.singletonList;
 
import org.openconcerto.sql.changer.Changer;
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
import org.openconcerto.sql.model.DBSystemRoot;
import org.openconcerto.sql.model.SQLDataSource;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSystem;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Link;
67,7 → 67,7
getDS().execute(dropIndex);
}
 
final String alter = SQLSelect.quote("ALTER TABLE %n CHANGE COLUMN %n %i " + getSyntax().getPrimaryIDDefinitionShort(), t, t.getKey(), newName);
final String alter = t.getBase().quote("ALTER TABLE %n CHANGE COLUMN %n %i " + getSyntax().getPrimaryIDDefinitionShort(), t, t.getKey(), newName);
getStream().println(alter);
getDS().execute(alter);
t.fetchFields();
/trunk/OpenConcerto/src/org/openconcerto/sql/changer/convert/MergeTable.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,6 → 14,7
package org.openconcerto.sql.changer.convert;
 
import static org.openconcerto.utils.CollectionUtils.substract;
 
import org.openconcerto.sql.changer.Changer;
import org.openconcerto.sql.model.AliasedTable;
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
27,6 → 28,7
import org.openconcerto.sql.model.SQLSchema;
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.DatabaseGraph;
34,6 → 36,7
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.request.UpdateBuilder;
import org.openconcerto.sql.utils.AlterTable;
import org.openconcerto.sql.utils.ChangeTable;
import org.openconcerto.sql.utils.ChangeTable.FCSpec;
import org.openconcerto.sql.utils.SQLCreateTable;
import org.openconcerto.sql.utils.SQLUtils;
134,7 → 137,8
// if we copy no rows, no need to check constraints
final boolean noRowsToMerge = oldIDs.size() == 0;
final DatabaseGraph graph = t.getDBSystemRoot().getGraph();
// check that transferred data from t still points to the same rows
// Check that transferred data from t still points to the same rows, i.e. that each foreign
// key of t exists in this.destTable and points to the same table.
final Set<Link> selfRefLinks = new HashSet<Link>();
for (final Link l : graph.getForeignLinks(t)) {
final Link destLink = graph.getForeignLink(this.destTable, l.getCols());
161,11 → 165,16
}
}
 
if (this.isDryRun())
return;
 
final SQLSyntax syntax = t.getDBSystemRoot().getSyntax();
final TablesMap tables = new TablesMap();
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Void, SQLException>() {
@Override
public Void handle(final SQLDataSource ds) throws SQLException {
final Set<SQLTable> toRefresh = new HashSet<SQLTable>();
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
@Override
public Object handle(final SQLDataSource ds) throws SQLException {
 
// drop self reference links before inserting
final AlterTable dropSelfFK = new AlterTable(MergeTable.this.destTable);
for (final Link selfRef : selfRefLinks) {
175,7 → 184,24
ds.execute(dropSelfFK.asString());
 
// copy all data of t into destTable
final List<Number> insertedIDs = SQLRowValues.insertIDs(MergeTable.this.destTable, fields + " " + sel.asString());
final List<Number> insertedIDs;
// In H2 v1.3 Statement.getGeneratedKeys() only returns the first ID
if (getSyntax().getSystem() == SQLSystem.H2) {
final SQLField destPK = MergeTable.this.destTable.getKey();
// null if table is empty
final Number maxID = (Number) getDS().executeScalar(new SQLSelect().addSelect(destPK, "max").asString());
final int insertedCount = SQLRowValues.insertCount(destPK.getTable(), fields + " " + sel.asString());
 
final SQLSelect selNewIDs = createSelect(destPK.getTable());
selNewIDs.addSelect(destPK);
if (maxID != null)
selNewIDs.setWhere(new Where(destPK, ">", maxID));
insertedIDs = getDS().executeCol(selNewIDs.asString());
if (insertedIDs.size() != insertedCount)
throw new IllegalStateException("Expected " + insertedCount + " new IDs but got " + insertedIDs.size());
} else {
insertedIDs = SQLRowValues.insertIDs(MergeTable.this.destTable, fields + " " + sel.asString());
}
// handle undefined
insertedIDs.add(0, MergeTable.this.destTable.getUndefinedIDNumber());
oldIDs.add(0, t.getUndefinedIDNumber());
217,9 → 243,20
ds.execute(t.getBase().quote("DROP TABLE %f", t));
ds.execute("DROP TABLE " + mapName.quote());
 
SQLTable.unsetUndefIDs(t.getSchema(), CollectionUtils.createSet(t.getName(), mapName.getName()));
 
toRefresh.add(t);
toRefresh.add(mapT);
 
final Set<SQLSchema> schemas = new HashSet<SQLSchema>();
for (final SQLTable table : toRefresh) {
tables.add(table.getDBRoot().getName(), table.getName());
schemas.add(table.getSchema());
}
for (final SQLSchema schema : schemas) {
schema.updateVersion();
}
 
return null;
}
 
245,18 → 282,19
// update the field using the map
final UpdateBuilder update = new UpdateBuilder(refTable);
final AliasedTable alias1 = new AliasedTable(mapT, "m");
update.addTable(alias1);
update.set(refKey.getName(), alias1.getField("NEW_ID").getFieldRef());
update.setWhere(new Where(refKey, Where.NULL_IS_DATA_EQ, alias1.getField("OLD_ID")));
// Where.NULL_IS_DATA_EQ since we want to be able to map a null undefined
// ID, i.e. rows pointing to null into 1.
update.addVirtualJoin(alias1, "OLD_ID", Where.NULL_IS_DATA_EQ, refKey.getName());
update.setFromVirtualJoinField(refKey.getName(), alias1.getAlias(), "NEW_ID");
if (selfLink) {
// only update new rows (old rows can have the same IDs but they point to old
// foreign rows, they must not be updated)
final AliasedTable onlyNew = new AliasedTable(mapT, "onlyNew");
update.addTable(onlyNew);
// we added the undefined to NEW_ID, but it wasn't copied from t so don't update
final Where w = new Where(refTable.getKey(), Where.NULL_IS_DATA_EQ, onlyNew.getField("NEW_ID")).and(new Where(refTable.getKey(), Where.NULL_IS_DATA_NEQ, refTable
.getUndefinedIDNumber()));
update.setWhere(update.getWhere().and(w));
final Where w = new Where(refTable.getKey(), true, new SQLSelect().addSelect(onlyNew.getField("NEW_ID")))
// we added the undefined to NEW_ID, but it wasn't copied from t so
// don't update
.and(new Where(refTable.getKey(), Where.NULL_IS_DATA_NEQ, refTable.getUndefinedIDNumber()));
update.setWhere(w.and(update.getWhere()));
}
ds.execute(update.asString());
 
265,21 → 303,14
// don't create an index : if there was one it's still there, if there wasn't
// don't alter the table silently (use AddFK if you want that)
addFK.addForeignConstraint(FCSpec.createFromLink(refLink, MergeTable.this.destTable), false);
// e.g. change from 'integer DEFAULT 1' to 'integer'
addFK.alterColumnDefault(refLink.getSingleField().getName(), ChangeTable.getForeignColumDefaultValue(MergeTable.this.destTable));
ds.execute(addFK.asString());
return refTable;
}
});
final TablesMap tables = new TablesMap();
final Set<SQLSchema> schemas = new HashSet<SQLSchema>();
for (final SQLTable table : toRefresh) {
tables.add(table.getDBRoot().getName(), table.getName());
schemas.add(table.getSchema());
}
t.getDBSystemRoot().refresh(tables, false);
for (final SQLSchema schema : schemas) {
schema.updateVersion();
}
}
 
private final SQLSelect createSelect(final SQLTable t) {
final SQLSelect sel = new SQLSelect(true);
/trunk/OpenConcerto/src/org/openconcerto/sql/replication/MemoryRep.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
28,11 → 28,11
import org.openconcerto.sql.model.graph.TablesMap;
import org.openconcerto.sql.utils.CSVHandler;
import org.openconcerto.sql.utils.ChangeTable;
import org.openconcerto.sql.utils.ChangeTable.FCSpec;
import org.openconcerto.sql.utils.SQLCreateMoveableTable;
import org.openconcerto.sql.utils.SQLCreateRoot;
import org.openconcerto.sql.utils.SQLCreateTableBase;
import org.openconcerto.sql.utils.SQLUtils;
import org.openconcerto.sql.utils.ChangeTable.FCSpec;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.ThreadFactory;
46,8 → 46,8
import java.util.HashMap;
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.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
58,11 → 58,11
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
 
import org.apache.commons.dbutils.ResultSetHandler;
 
/**
* Allow to replicate some tables in memory.
*
104,7 → 104,7
this.singleRootName = null;
}
// private in-memory database
this.slave = new SQLServer(SQLSystem.H2, "mem", null, null, null, new IClosure<DBSystemRoot>() {
this.slave = new SQLServer(SQLSystem.H2, SQLSystem.H2_IN_MEMORY, null, null, null, new IClosure<DBSystemRoot>() {
@Override
public void executeChecked(DBSystemRoot input) {
input.setRootsToMap(tables.keySet());
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/UpdateQueue.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,9 → 14,10
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.Log;
import org.openconcerto.sql.model.RowRef;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTableEvent;
23,6 → 24,7
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.view.list.UpdateRunnable.RmAllRunnable;
import org.openconcerto.sql.view.list.search.SearchOne;
import org.openconcerto.sql.view.list.search.SearchOne.Mode;
47,6 → 49,7
import java.util.List;
import java.util.Set;
import java.util.concurrent.RunnableFuture;
import java.util.function.BiConsumer;
import java.util.logging.Level;
 
import net.jcip.annotations.GuardedBy;
199,21 → 202,25
}
 
/**
* The lines and their path affected by a change of the passed row.
* The lines affected by a change of the passed row.
*
* @param r the row that has changed.
* @return the refreshed lines and their changed paths.
* @return the refreshed lines.
*/
protected final ListMap<ListSQLLine, Path> getAffectedLines(final SQLRow r) {
return this.getAffected(r, new ListMap<ListSQLLine, Path>(), true);
protected final Set<Integer> getAffectedLines(final SQLRow r) {
final Set<Integer> res = new HashSet<>();
this.getAffected(r, (p, l) -> res.add(l.getID()));
return res;
}
 
protected final ListMap<Path, ListSQLLine> getAffectedPaths(final SQLRow r) {
return this.getAffected(r, new ListMap<Path, ListSQLLine>(), false);
final ListMap<Path, ListSQLLine> res = new ListMap<>();
this.getAffected(r, res::add);
return res;
}
 
// must be called from within this queue, as this method use fullList
private <K, V> ListMap<K, V> getAffected(final SQLRow r, final ListMap<K, V> res, final boolean byLine) {
private void getAffected(final SQLRow r, final BiConsumer<Path, ListSQLLine> cons) {
final List<ListSQLLine> fullList = this.getFullList();
synchronized (fullList) {
final SQLTable t = r.getTable();
221,6 → 228,7
if (id < SQLRow.MIN_VALID_ID)
throw new IllegalArgumentException("invalid ID: " + id);
if (!fullList.isEmpty()) {
final boolean keepGraph = this.getModel().getReq().getKeepMode() == KeepMode.GRAPH;
final SQLRowValues proto = this.getState().getReq().getGraphToFetch();
final List<Path> pathsToT = new ArrayList<Path>();
proto.getGraph().walk(proto, pathsToT, new ITransformer<State<List<Path>>, List<Path>>() {
232,46 → 240,64
return input.getAcc();
}
}, RecursionType.BREADTH_FIRST, Direction.ANY);
// paths aren't stored when !keepGraph
if (!keepGraph) {
final RowRef idRef = r.getRowRef();
for (final ListSQLLine line : fullList) {
if (line.getPKs().contains(idRef))
cons.accept(null, line);
}
}
for (final Path p : pathsToT) {
final String lastReferentField = SearchQueue.getLastReferentField(p);
final RowRef foreignRef;
if (lastReferentField != null && r.exists()) {
final SQLRowAccessor foreign = r.getNonEmptyForeign(lastReferentField);
foreignRef = foreign != null ? foreign.getRowRef() : null;
} else {
foreignRef = null;
}
// fullList already checked above, so if there's no foreignRef, there's nothing
// to do
if (!keepGraph && foreignRef == null)
continue;
for (final ListSQLLine line : fullList) {
boolean put = false;
for (final SQLRowValues current : line.getRow().followPath(p, CreateMode.CREATE_NONE, false)) {
if (keepGraph) {
for (final SQLRowValues current : line.getRow().getDistantRows(p)) {
// works for rowValues w/o any ID
if (current != null && current.getID() == id) {
put = true;
}
}
// if the modified row isn't in the existing line, it might still affect it
// if it's a referent row insertion
if (!put && lastReferentField != null && r.exists() && !r.isForeignEmpty(lastReferentField)) {
// no NPE, even without an undefined ID since we tested isForeignEmpty()
final int foreignID = r.getInt(lastReferentField);
for (final SQLRowValues current : line.getRow().followPath(p.minusLast(), CreateMode.CREATE_NONE, false)) {
if (current.getID() == foreignID) {
}
/*
* If the modified row isn't in the existing line, it might still affect it
* if it's a referent row insertion (a referent row update or deletion would
* mean than the current line contains the modified row and so "put" would
* already be true).
*/
if (!put && foreignRef != null) {
if (keepGraph) {
for (final SQLRowValues current : line.getRow().getDistantRows(p.minusLast())) {
if (current.getID() == foreignRef.getID().intValue()) {
put = true;
}
}
} else {
put = line.getPKs().contains(foreignRef);
}
}
if (put) {
// add to the list of paths that have been refreshed
add(byLine, res, p, line);
cons.accept(p, line);
}
}
}
}
}
return res;
}
 
@SuppressWarnings("unchecked")
<V, K> void add(boolean byLine, ListMap<K, V> res, final Path p, final ListSQLLine line) {
if (byLine)
res.add((K) line, (V) p);
else
res.add((K) p, (V) line);
}
 
final void setFullList(final List<ListSQLLine> tmp, final SQLTableModelColumns cols) {
final List<ListSQLLine> fullList = this.getFullList();
synchronized (fullList) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/IListe.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
20,6 → 20,7
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.RowRef;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
26,6 → 27,7
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.sql.request.UpdateBuilder;
import org.openconcerto.sql.users.User;
33,11 → 35,15
import org.openconcerto.sql.users.rights.TableAllRights;
import org.openconcerto.sql.view.FileTransfertHandler;
import org.openconcerto.sql.view.IListener;
import org.openconcerto.sql.view.RowMetadata;
import org.openconcerto.sql.view.RowMetadataCache;
import org.openconcerto.sql.view.list.IListeAction.ButtonsBuilder;
import org.openconcerto.sql.view.list.IListeAction.IListeEvent;
import org.openconcerto.sql.view.list.IListeAction.PopupBuilder;
import org.openconcerto.sql.view.list.IListeAction.PopupEvent;
import org.openconcerto.sql.view.list.RowAction.PredicateRowAction;
import org.openconcerto.sql.view.list.action.ListEvent;
import org.openconcerto.sql.view.list.action.SQLRowValuesAction;
import org.openconcerto.ui.FontUtils;
import org.openconcerto.ui.FormatEditor;
import org.openconcerto.ui.MenuUtils;
91,7 → 97,6
import java.text.DateFormat;
import java.text.Format;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
98,6 → 103,7
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
105,6 → 111,7
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
 
import javax.swing.AbstractAction;
import javax.swing.Action;
146,16 → 153,13
*/
public final class IListe extends JPanel {
 
static private final class LockAction extends RowAction {
static private final class LockAction extends SQLRowValuesAction {
private final boolean lock;
 
public LockAction(final boolean lock) {
super(new AbstractAction(TM.tr(lock ? "ilist.lockRows" : "ilist.unlockRows")) {
@Override
public void actionPerformed(ActionEvent e) {
final IListe list = IListe.get(e);
final List<Integer> ids = list.getSelection().getSelectedIDs();
final SQLTable t = list.getSource().getPrimaryTable();
super(false, true, (e) -> {
final List<Number> ids = e.getSelectedIDs();
final SQLTable t = e.getTable();
final UpdateBuilder update = new UpdateBuilder(t);
update.setObject(SQLComponent.READ_ONLY_FIELD, lock ? SQLComponent.READ_ONLY_VALUE : SQLComponent.READ_WRITE_VALUE);
final User user = UserManager.getUser();
168,15 → 172,15
final Collection<? extends Number> fireIDs = ids.size() < 12 ? ids : Collections.singleton(SQLRow.NONEXISTANT_ID);
for (final Number fireID : fireIDs)
t.fireTableModified(fireID.intValue(), update.getFieldsNames());
}
}, false, true);
});
this.setName(TM.tr(lock ? "ilist.lockRows" : "ilist.unlockRows"));
this.lock = lock;
}
 
@Override
public boolean enabledFor(IListeEvent evt) {
public boolean enabledFor(ListEvent evt) {
boolean hasRight = TableAllRights.currentUserHasRight(this.lock ? TableAllRights.USER_UI_LOCK_ROW : TableAllRights.USER_UI_UNLOCK_ROW, evt.getTable());
return !evt.getSelectedRows().isEmpty() && hasRight;
return !evt.getSelectedRowAccessors().isEmpty() && hasRight;
}
}
 
199,6 → 203,9
return UNLOCK_ACTION;
}
 
private static final int MD_BATCH_SIZE = 100;
private static final RowMetadataCache MD_CACHE = new RowMetadataCache(120, 5000, IListe.class.getName());
 
/**
* When this system property is set, table {@link JTableStateManager state} is never read nor
* written. I.e. the user can change the table state but it will be reset at each launch.
333,6 → 340,11
 
this.sorter = new TableSorter();
this.jTable = new JTable(this.sorter) {
 
// By default the tooltip doesn't follow the mouse if the string remains the same
// (probably for performance reasons)
private final boolean followMouseWorkaround = !Boolean.getBoolean("jtable.tooltip_follow_mouse.disable");
 
@Override
public String getToolTipText(MouseEvent event) {
final String original = super.getToolTipText(event);
353,37 → 365,73
infoL.add(original);
}
 
final SQLRowValues row = ITableModel.getLine(this.getModel(), rowIndex).getRow();
final SQLRowAccessor row = ITableModel.getLine(this.getModel(), rowIndex).getRowAccessor();
 
final String create = getLine(true, row, getSource().getPrimaryTable().getCreationUserField(), getSource().getPrimaryTable().getCreationDateField());
final RowRef cacheKey = row.getRowRef();
final RowMetadata md = MD_CACHE.get(cacheKey);
if (md != null) {
final String create = getLine(true, md);
final String modif = getLine(false, md);
if (create == null && modif == null) {
infoL.add(TM.tr("ilist.metadata.na"));
} else {
if (create != null)
infoL.add(create);
final String modif = getLine(false, row, getSource().getPrimaryTable().getModifUserField(), getSource().getPrimaryTable().getModifDateField());
if (modif != null)
infoL.add(modif);
}
// TODO locked by
 
} else {
final int half = MD_BATCH_SIZE / 2;
final int firstIndex = Math.max(0, rowIndex - half);
final int lastIndex = Math.min(getRowCount(), rowIndex + half);
final Set<Number> ids = CollectionUtils.newHashSet(MD_BATCH_SIZE);
for (int i = firstIndex; i < lastIndex; i++) {
ids.add(ITableModel.getLine(this.getModel(), i).getRowAccessor().getIDNumber());
}
MD_CACHE.fetch(cacheKey, ids);
 
infoL.add(TM.tr("ilist.metadata.loading"));
}
 
final String info;
if (infoL.size() == 0)
if (infoL.size() == 0) {
info = null;
else
info = "<html>" + CollectionUtils.join(infoL, "<br/>") + "</html>";
// ATTN doesn't follow the mouse if info remains the same, MAYBE add an identifier
} else {
final StringBuilder sb = new StringBuilder(256);
sb.append("<html>");
sb.append(CollectionUtils.join(infoL, "<br/>"));
sb.append("</html>");
if (this.followMouseWorkaround) {
// This force the JRE to repaint the tooltip at the mouse location :
// 1. even without mainInfo changing (e.g. "unavailable")
// 2. but only when changing row
// Otherwise (e.g. adding or not a space at the end, for each call) the
// tooltip is drawn continuously and CPU load is quite heavy.
sb.append("<!--");
sb.append(rowIndex);
sb.append("-->");
}
info = sb.toString();
}
 
return info;
}
 
public String getLine(final boolean created, final SQLRowValues row, final SQLField userF, final SQLField dateF) {
final Calendar date = dateF == null ? null : row.getDate(dateF.getName());
final SQLRowAccessor user = userF == null || row.getObject(userF.getName()) == null || row.isForeignEmpty(userF.getName()) ? null : row.getForeign(userF.getName());
if (user == null && date == null)
public String getLine(final boolean created, final RowMetadata md) {
final Date date = created ? md.getCreation() : md.getModification();
final Integer userID = created ? md.getUserCreate() : md.getUserModify();
if (userID == null && date == null)
return null;
 
final int userParam;
final String firstName, lastName;
if (user != null) {
if (userID != null) {
userParam = 1;
firstName = user.getString("PRENOM");
lastName = user.getString("NOM");
final User user = UserManager.getInstance().getUser(userID);
firstName = user.getFirstName();
lastName = user.getName();
} else {
userParam = 0;
firstName = null;
390,7 → 438,7
lastName = null;
}
 
return TM.tr("ilist.metadata", created ? 1 : 0, userParam, firstName, lastName, date == null ? 0 : 1, date == null ? null : date.getTime());
return TM.tr("ilist.metadata", created ? 1 : 0, userParam, firstName, lastName, date == null ? 0 : 1, date);
}
 
@Override
601,6 → 649,65
return res;
}
 
// Transitional class while we convert RowAction to SQLRowValuesAction
@Deprecated
static public final class ConvertedAction extends SQLRowValuesAction {
private final RowAction rowAction;
 
public ConvertedAction(final RowAction a) {
super(a.inHeader(), a.inPopupMenu(), a.getID(), (evt) -> {
a.getAction().actionPerformed(new ActionEvent(evt.getSource(), ActionEvent.ACTION_PERFORMED, null));
});
this.rowAction = a;
if (a.getAction().getValue(Action.NAME) != null)
this.setName(String.valueOf(a.getAction().getValue(Action.NAME)));
}
 
@Override
public boolean enabledFor(ListEvent evt) {
return this.getRowAction().enabledFor(evt);
}
 
public final RowAction getRowAction() {
return this.rowAction;
}
}
 
public final RowAction addRowValuesAction(SQLRowValuesAction a) {
final RowAction action;
if (a instanceof ConvertedAction) {
action = ((ConvertedAction) a).getRowAction();
} else {
action = new RowAction(new AbstractAction(a.getName()) {
@Override
public void actionPerformed(ActionEvent e) {
a.getAction().accept(IListe.get(e).createListEvent());
}
}, a.inHeader(), a.inPopupMenu(), a.getID()) {
 
@Override
public boolean enabledFor(ListEvent evt) {
return a.enabledFor(evt);
}
 
};
}
if (this.addIListeAction(action))
return action;
else
return null;
}
 
public final Map<SQLRowValuesAction, IListeAction> addRowValuesActions(Collection<? extends SQLRowValuesAction> actions) {
final Map<SQLRowValuesAction, IListeAction> res = new IdentityHashMap<>();
for (final SQLRowValuesAction a : actions) {
final RowAction action = addRowValuesAction(a);
if (action != null)
res.put(a, action);
}
return res;
}
 
public final void addIListeActions(Collection<? extends IListeAction> actions) {
for (final IListeAction a : actions)
this.addIListeAction(a);
619,14 → 726,14
return -1;
}
 
public final void addIListeAction(IListeAction action) {
public final boolean addIListeAction(IListeAction action) {
// we need to handle addition of an already added action at least for setDefaultRowAction()
if (this.rowActions.containsKey(action))
return;
return false;
final ButtonsBuilder headerBtns = action.getHeaderButtons();
this.rowActions.put(action, headerBtns);
if (headerBtns.getContent().size() > 0) {
updateButton(headerBtns, new IListeEvent(this));
updateButton(headerBtns, this.createListEvent());
for (final JButton headerBtn : headerBtns.getContent().keySet()) {
headerBtn.setOpaque(false);
this.btnPanel.add(headerBtn, findGroupIndex((String) headerBtn.getClientProperty(ButtonsBuilder.GROUPNAME_PROPNAME)));
633,6 → 740,7
}
this.btnPanel.setVisible(true);
}
return true;
}
 
public final void removeIListeActions(Collection<? extends IListeAction> actions) {
656,7 → 764,7
}
 
private void updateButtons() {
final IListeEvent evt = new IListeEvent(this);
final IListeEvent evt = this.createListEvent();
for (final ButtonsBuilder btns : this.rowActions.values()) {
this.updateButton(btns, evt);
}
670,7 → 778,7
 
private JPopupMenu updatePopupMenu(final boolean onRows) {
this.popup.removeAll();
final PopupEvent evt = new PopupEvent(this, onRows);
final PopupEvent evt = this.createPopupEvent(onRows);
final Action defaultAction = this.defaultRowAction != null ? this.defaultRowAction.getDefaultAction(evt) : null;
final VirtualMenu menu = VirtualMenu.createRoot(null);
for (final IListeAction a : this.rowActions.keySet()) {
721,12 → 829,33
// special method needed since sometimes getPopupContent() can access the DB (optionally
// creating threads) or be slow
if (this.defaultRowAction != null) {
final Action defaultAction = this.defaultRowAction.getDefaultAction(new IListeEvent(this));
final Action defaultAction = this.defaultRowAction.getDefaultAction(this.createListEvent());
if (defaultAction != null)
defaultAction.actionPerformed(new ActionEvent(e.getSource(), e.getID(), null, e.getWhen(), e.getModifiers()));
}
}
 
final IListeEvent createListEvent() {
return createEvent((vals, accessors) -> new IListeEvent(this, vals, accessors));
}
 
final PopupEvent createPopupEvent(final boolean onRows) {
return createEvent((vals, accessors) -> new PopupEvent(this, vals, accessors, onRows));
}
 
private final <E extends ListEvent> E createEvent(final BiFunction<List<SQLRowValues>, List<? extends SQLRowAccessor>, E> ctor) {
final List<SQLRowValues> vals;
final List<? extends SQLRowAccessor> accessors;
if (this.getSource().getKeepMode() == KeepMode.GRAPH) {
vals = this.getSelectedRows();
accessors = vals;
} else {
vals = null;
accessors = this.getSelectedRowAccessors();
}
return ctor.apply(vals, accessors);
}
 
private void uiInit() {
// * filter
this.filter.addMouseListener(new MouseAdapter() {
780,6 → 909,9
final int columnIndex = ((Number) cb.getClientProperty(COL_INDEX_KEY)).intValue();
final TableColumn col = colModel.getColumn(columnIndex, false);
final boolean newValue = !colModel.isColumnVisible(col);
// Workaround for crash on linux Java 16 with Nimbus L&F or Flat L&F
IListe.this.jTable.getTableHeader().setDraggedColumn(null);
 
// don't remove last column
if (newValue || colModel.getColumnCount(true) > 1)
colModel.setColumnVisible(col, newValue);
949,8 → 1081,8
this.setTransferHandler(new FileTransfertHandler(getSource().getPrimaryTable()));
 
if (this.getSource().getPrimaryTable().getFieldRaw(SQLComponent.READ_ONLY_FIELD) != null) {
this.addIListeAction(getUnlockAction());
this.addIListeAction(getLockAction());
this.addRowValuesAction(getUnlockAction());
this.addRowValuesAction(getLockAction());
}
}
 
1149,7 → 1281,9
if (clazz == SQLRowValues.class) {
toCast = line.getRow().toImmutable();
} else if (clazz == SQLRow.class) {
toCast = line.getRow().asRow();
toCast = line.getRowAccessor().asRow();
} else if (clazz == SQLRowAccessor.class) {
toCast = line.getRowAccessor();
} else if (clazz == ListSQLLine.class) {
toCast = line;
} else {
1173,6 → 1307,10
return this.getSelectedRow(SQLRowValues.class);
}
 
public SQLRowAccessor getSelectedRowAccessor() {
return this.getSelectedRow(SQLRowAccessor.class);
}
 
// selected row cannot be inferred from iterateSelectedRows() since the user might have selected
// the last row anywhere in the selection
private final <R extends SQLRowAccessor> R getSelectedRow(final Class<R> clazz) {
1195,6 → 1333,10
return iterateSelectedRows(ListSQLLine.class);
}
 
public final List<SQLRowAccessor> getSelectedRowAccessors() {
return iterateSelectedRows(SQLRowAccessor.class);
}
 
private final <R> List<R> iterateSelectedRows(final Class<R> clazz) {
final ListSelectionModel selectionModel = this.getJTable().getSelectionModel();
if (selectionModel.isSelectionEmpty())
1471,6 → 1613,13
}
}
 
public final boolean saveTableState() throws IOException {
final boolean hasFile = this.getConfigFile() != null;
if (hasFile)
this.tableStateManager.saveState();
return hasFile;
}
 
private boolean loadTableState() {
// - if configFile changes setConfigFile() calls us
// - if the model changes, fireTableStructureChanged() is called and thus
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/AutoCompletionManager.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
228,7 → 228,7
// SQLRowValues(AutoCompletionManager.this.fillFrom.getTable());
final SQLRow rowV;
if (r != null) {
rowV = r.asRow();
rowV = r.fetchNewRow(false);
} else {
rowV = AutoCompletionManager.this.fillFrom.getTable().getRow(id);
}
292,6 → 292,10
return this.fillBy.keySet();
}
 
public String getToField(String fromField) {
return this.fillBy.get(fromField);
}
 
public void fillRowValues(SQLRowAccessor from, Set<String> fields, SQLRowValues to) {
for (String fromField : fields) {
String toField = AutoCompletionManager.this.fillBy.get(fromField);
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/action/SQLRowValuesAction.java
New file
0,0 → 1,157
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.list.action;
 
import org.openconcerto.utils.cc.IPredicate;
import org.openconcerto.utils.i18n.TranslationManager;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
 
/**
* An action that act on rows of a list. Either {@link #enabledFor(ListEvent)} or
* {@link #enabledFor(List)} must be overloaded.
*
* @author Sylvain CUAZ
*/
public abstract class SQLRowValuesAction {
 
public static class PredicateRowAction extends SQLRowValuesAction {
private IPredicate<? super ListEvent> pred = null;
 
public PredicateRowAction(boolean header, Consumer<? super ListEvent> action) {
super(header, action);
}
 
public PredicateRowAction(boolean header, boolean popupMenu, Consumer<? super ListEvent> action) {
super(header, popupMenu, action);
}
 
public PredicateRowAction(boolean header, final String id, Consumer<? super ListEvent> action) {
super(header, id, action);
}
 
public PredicateRowAction(boolean header, boolean popupMenu, final String id, Consumer<? super ListEvent> action) {
super(header, popupMenu, id, action);
}
 
public final PredicateRowAction setPredicate(IPredicate<? super ListEvent> pred) {
if (pred == null) {
throw new IllegalArgumentException("null predicate");
}
this.pred = pred;
return this;
}
 
public final IPredicate<? super ListEvent> getPredicate() {
return this.pred;
}
 
@Override
public boolean enabledFor(ListEvent evt) {
if (this.pred == null) {
throw new IllegalStateException("No predicate for " + this);
}
return this.pred.evaluateChecked(evt);
}
}
 
private final Consumer<? super ListEvent> action;
private final boolean header, popupMenu;
private List<String> path;
private final String id;
private String name;
 
public SQLRowValuesAction(boolean header, Consumer<? super ListEvent> action) {
this(header, true, action);
}
 
public SQLRowValuesAction(boolean header, boolean popupMenu, Consumer<? super ListEvent> action) {
this(header, popupMenu, null, action);
}
 
public SQLRowValuesAction(boolean header, final String id, Consumer<? super ListEvent> action) {
this(header, true, id, action);
}
 
public SQLRowValuesAction(boolean header, boolean popupMenu, final String id, Consumer<? super ListEvent> action) {
super();
this.action = action;
this.header = header;
this.popupMenu = popupMenu;
this.setGroup(null);
this.id = id;
if (id != null)
this.setName(TranslationManager.getInstance().getTranslationForAction(id));
}
 
public final String getID() {
return this.id;
}
 
public final SQLRowValuesAction setName(String name) {
this.name = name;
return this;
}
 
public final String getName() {
return this.name;
}
 
public final Consumer<? super ListEvent> getAction() {
return this.action;
}
 
public final boolean inHeader() {
return this.header;
}
 
public final boolean inPopupMenu() {
return this.popupMenu;
}
 
public final SQLRowValuesAction setGroup(String groupName) {
this.path = Arrays.asList(groupName);
return this;
}
 
public final SQLRowValuesAction setPath(List<String> path) {
this.path = Collections.unmodifiableList(new ArrayList<String>(path));
return this;
}
 
public final List<String> getPath() {
return this.path;
}
 
/**
* Whether the action should be enabled in the header or in the popup.
*
* @param evt the state of the IListe.
* @return <code>true</code> if the action can be performed.
*/
public abstract boolean enabledFor(ListEvent evt);
 
public Consumer<? super ListEvent> getDefaultAction(ListEvent evt) {
return this.enabledFor(evt) ? this.getAction() : null;
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + (this.getID() == null ? "" : " ID '" + this.getID()) + "'";
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/action/ListEvent.java
New file
0,0 → 1,127
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.list.action;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.cc.IPredicate;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
 
public class ListEvent {
 
static private final IPredicate<ListEvent> emptyTotalRowCountPredicate = createTotalRowCountPredicate(0, 0);
 
static public final IPredicate<ListEvent> getEmptyListPredicate() {
return emptyTotalRowCountPredicate;
}
 
static public final IPredicate<ListEvent> createTotalRowCountPredicate(final int min, final int max) {
return new IPredicate<ListEvent>() {
@Override
public boolean evaluateChecked(ListEvent e) {
return e.getTotalRowCount() >= min && e.getTotalRowCount() <= max;
}
};
}
 
static private final IPredicate<ListEvent> singleSelectionPredicate = createSelectionCountPredicate(1, 1);
static private final IPredicate<ListEvent> nonEmptySelectionPredicate = createNonEmptySelectionPredicate(Integer.MAX_VALUE);
 
static public final IPredicate<ListEvent> getSingleSelectionPredicate() {
return singleSelectionPredicate;
}
 
static public final IPredicate<ListEvent> getNonEmptySelectionPredicate() {
return nonEmptySelectionPredicate;
}
 
static public final IPredicate<ListEvent> createNonEmptySelectionPredicate(final int max) {
return createSelectionCountPredicate(1, max);
}
 
static public final IPredicate<ListEvent> createSelectionCountPredicate(final int min, final int max) {
return new IPredicate<ListEvent>() {
@Override
public boolean evaluateChecked(ListEvent e) {
// this is the fastest since it involves no object creation
final List<?> selectedIDs = e.getSelectedRowAccessors();
return selectedIDs.size() >= min && selectedIDs.size() <= max;
}
};
}
 
static public final ListEvent createFromRowValues(final Object source, final SQLElement elem, final int totalRowCount, final List<SQLRowValues> selection) {
return new ListEvent(source, elem, totalRowCount, selection, selection);
}
 
static public final ListEvent createFromRowAccessors(final Object source, final SQLElement elem, final int totalRowCount, final List<? extends SQLRowAccessor> selection) {
return new ListEvent(source, elem, totalRowCount, null, selection);
}
 
private final Object source;
private final SQLElement elem;
private final int totalRowCount;
private final List<SQLRowValues> selection;
private final List<? extends SQLRowAccessor> selectionAccessor;
 
protected ListEvent(final Object source, final SQLElement elem, final int totalRowCount, final List<SQLRowValues> selection, final List<? extends SQLRowAccessor> selectionAccessor) {
super();
this.source = source;
this.elem = elem;
this.totalRowCount = totalRowCount;
assert selection == null || selection == selectionAccessor : "Wasting memory";
this.selection = selection;
this.selectionAccessor = Objects.requireNonNull(selectionAccessor);
}
 
public final Object getSource() {
return this.source;
}
 
public final SQLRowAccessor getSelectedRow() {
return CollectionUtils.getFirst(this.getSelectedRowAccessors());
}
 
public final List<SQLRowValues> getSelectedRows() {
if (this.selection == null)
throw new IllegalStateException("SQLRowValues unavailable, use getSelectedRowAccessors()");
return this.selection;
}
 
public final List<? extends SQLRowAccessor> getSelectedRowAccessors() {
return this.selectionAccessor;
}
 
public final List<Number> getSelectedIDs() {
return SQLRowAccessor.getIDs(this.getSelectedRowAccessors(), new ArrayList<>());
}
 
public final int getTotalRowCount() {
return this.totalRowCount;
}
 
public final SQLTable getTable() {
return this.getElement().getTable();
}
 
public final SQLElement getElement() {
return this.elem;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/AbstractUpdateOneRunnable.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
29,8 → 29,10
import org.openconcerto.utils.cc.ITransformer;
 
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
 
abstract class AbstractUpdateOneRunnable extends UpdateRunnable {
 
134,5 → 136,27
}
}
 
protected final void updateLines(final Set<Integer> affectedLines, final Integer primaryID) {
final Set<Integer> idsToFetch;
if (primaryID != null && !affectedLines.contains(primaryID)) {
idsToFetch = new HashSet<>(affectedLines);
idsToFetch.add(primaryID);
} else {
idsToFetch = affectedLines;
}
final List<ListSQLLine> newLines = getModel().getLinesSource().get(idsToFetch);
 
final Set<Integer> notUpdated = new HashSet<>(affectedLines);
synchronized (this.getUpdateQ().getFullList()) {
for (final ListSQLLine newLine : newLines) {
this.getUpdateQ().replaceLine(newLine.getID(), newLine);
notUpdated.remove(newLine.getID());
}
for (final Integer toRemove : notUpdated) {
this.getUpdateQ().replaceLine(toRemove, null);
}
}
}
 
protected abstract Collection<String> getModifedFields();
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSource.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
24,6 → 24,7
import java.beans.PropertyChangeListener;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Future;
69,9 → 70,20
return this.getModel().getUpdateQ().getState().getReq();
}
 
public abstract List<ListSQLLine> getAll();
public final List<ListSQLLine> getAll() {
return this.get(null);
}
 
/**
* Fetch up to date values from the DB.
*
* @param ids which rows to fetch, <code>null</code> meaning all.
* @return the new values from the DB, some changes in the DB might be ignored if there's
* pending changes in this.
*/
public abstract List<ListSQLLine> get(final Collection<? extends Number> ids);
 
/**
* A row in the DB has been changed, fetch its current value.
*
* @param id a valid ID of a database row.
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/RowAction.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
15,6 → 15,7
 
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.view.list.action.ListEvent;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.IPredicate;
import org.openconcerto.utils.i18n.TranslationManager;
50,7 → 51,7
}
 
public static class PredicateRowAction extends RowAction {
private IPredicate<? super IListeEvent> pred = null;
private IPredicate<? super ListEvent> pred = null;
 
public PredicateRowAction(Action action, boolean header) {
super(action, header);
68,7 → 69,7
super(action, header, popupMenu, id);
}
 
public final PredicateRowAction setPredicate(IPredicate<? super IListeEvent> pred) {
public final PredicateRowAction setPredicate(IPredicate<? super ListEvent> pred) {
if (pred == null) {
throw new IllegalArgumentException("null predicate");
}
81,7 → 82,7
}
 
@Override
public boolean enabledFor(IListeEvent evt) {
public boolean enabledFor(ListEvent evt) {
if (this.pred == null) {
throw new IllegalStateException("No predicate for " + this);
}
148,7 → 149,7
}
 
public boolean enabledFor(List<SQLRowValues> selection) {
throw new UnsupportedOperationException("Should overload this method or enabledFor(IListeEvent)");
throw new UnsupportedOperationException("Should overload this method or enabledFor(IListeEvent) on : " + this);
}
 
/**
157,7 → 158,7
* @param evt the state of the IListe.
* @return <code>true</code> if the action can be performed.
*/
public boolean enabledFor(IListeEvent evt) {
public boolean enabledFor(ListEvent evt) {
return this.enabledFor(evt.getSelectedRows());
}
 
172,7 → 173,7
}
 
@Override
public Action getDefaultAction(IListeEvent evt) {
public Action getDefaultAction(ListEvent evt) {
return this.enabledFor(evt) ? this.getAction() : null;
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceOnline.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,6 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.request.ListSQLRequest;
 
/**
24,15 → 25,30
*/
public class SQLTableModelSourceOnline extends SQLTableModelSource {
 
private final KeepMode keepMode;
 
public SQLTableModelSourceOnline(final ListSQLRequest req, final SQLElement elem) {
this(req, elem, KeepMode.GRAPH);
}
 
public SQLTableModelSourceOnline(final ListSQLRequest req, final SQLElement elem, final KeepMode keepMode) {
super(req, elem);
if (keepMode == null || keepMode == KeepMode.NONE)
throw new IllegalArgumentException("Invalid mode : " + keepMode);
this.keepMode = keepMode;
}
 
public SQLTableModelSourceOnline(SQLTableModelSourceOnline src) {
super(src);
this.keepMode = src.keepMode;
}
 
@Override
public final KeepMode getKeepMode() {
return this.keepMode;
}
 
@Override
protected boolean allowBiggerGraph() {
return true;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/RowValuesTableModel.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
144,6 → 144,10
this.requiredFields.add(f);
}
 
public SQLField getValidationField() {
return this.validationField;
}
 
public SQLRowValues getDefaultRowValues() {
return this.defautRow;
}
233,8 → 237,10
}
rowVal.put(fieldName, value);
for (SQLTableElement sqlTableElem : this.list) {
if (sqlTableElem.getField().getName().equals(fieldName)) {
sqlTableElem.fireModification(rowVal);
}
}
fireTableModelModified(rowIndex);
}
 
707,7 → 713,7
checkEDT();
final List<SQLRowValues> rows = new ArrayList<SQLRowValues>(1);
rows.add(row);
addRows(rows, true);
addRows(rows, fireModified);
}
 
public void submit(Runnable r) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ITableModel.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
20,7 → 20,6
import org.openconcerto.sql.Log;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.users.rights.TableAllRights;
import org.openconcerto.sql.users.rights.UserRights;
492,7 → 491,7
final ListSQLLine line = getRow(rowIndex);
if (!line.getSrc().isCellEditable(line, columnIndex, col))
return true;
final SQLRowValues r = line.getRow();
final SQLRowAccessor r = line.getRowAccessor();
return r.getTable().contains(SQLComponent.READ_ONLY_FIELD) && SQLComponent.isReadOnly(r);
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/search/SearchQueue.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
65,9 → 65,14
* exist.
*/
public static String getLastReferentField(final Path p) {
final Step lastRefStep = getLastReferentStep(p);
return lastRefStep == null ? null : lastRefStep.getSingleField().getName();
}
 
public static Step getLastReferentStep(final Path p) {
final Step lastStep = p.length() == 0 ? null : p.getStep(-1);
final boolean lastIsForeign = lastStep == null || lastStep.getDirection() == Direction.FOREIGN;
return lastIsForeign ? null : lastStep.getSingleField().getName();
return lastIsForeign ? null : lastStep;
}
 
private final ITableModel model;
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/IListeAction.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
15,9 → 15,8
 
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.view.list.action.ListEvent;
import org.openconcerto.ui.list.selection.ListSelection;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.cc.IPredicate;
 
import java.util.Collections;
36,79 → 35,19
*/
public interface IListeAction {
 
static public class IListeEvent {
static public class IListeEvent extends ListEvent {
 
static private final IPredicate<IListeEvent> emptyTotalRowCountPredicate = createTotalRowCountPredicate(0, 0);
 
static public final IPredicate<IListeEvent> getEmptyListPredicate() {
return emptyTotalRowCountPredicate;
}
 
static public final IPredicate<IListeEvent> createTotalRowCountPredicate(final int min, final int max) {
return new IPredicate<IListeEvent>() {
@Override
public boolean evaluateChecked(IListeEvent e) {
return e.getTotalRowCount() >= min && e.getTotalRowCount() <= max;
}
};
}
 
static private final IPredicate<IListeEvent> singleSelectionPredicate = createSelectionCountPredicate(1, 1);
static private final IPredicate<IListeEvent> nonEmptySelectionPredicate = createNonEmptySelectionPredicate(Integer.MAX_VALUE);
 
static public final IPredicate<IListeEvent> getSingleSelectionPredicate() {
return singleSelectionPredicate;
}
 
static public final IPredicate<IListeEvent> getNonEmptySelectionPredicate() {
return nonEmptySelectionPredicate;
}
 
static public final IPredicate<IListeEvent> createNonEmptySelectionPredicate(final int max) {
return createSelectionCountPredicate(1, max);
}
 
static public final IPredicate<IListeEvent> createSelectionCountPredicate(final int min, final int max) {
return new IPredicate<IListeEvent>() {
@Override
public boolean evaluateChecked(IListeEvent e) {
// this is the fastest since it involves no object creation
final List<?> selectedIDs = e.getSelectedRows();
return selectedIDs.size() >= min && selectedIDs.size() <= max;
}
};
}
 
private final IListe list;
private final List<SQLRowValues> selection;
 
IListeEvent(final IListe list) {
super();
IListeEvent(final IListe list, final List<SQLRowValues> selection, final List<? extends SQLRowAccessor> selectionAccessor) {
super(list, list.getSource().getElem(), list.getTotalRowCount(), selection, selectionAccessor);
this.list = list;
// this create instances so cache it
this.selection = list.getSelectedRows();
}
 
public final SQLRowAccessor getSelectedRow() {
return CollectionUtils.getFirst(this.getSelectedRows());
}
 
public final List<SQLRowValues> getSelectedRows() {
return this.selection;
}
 
public final int getTotalRowCount() {
return this.list.getTotalRowCount();
}
 
public final ListSelection getSelection() {
return this.list.getSelection();
}
 
public final SQLTable getTable() {
return this.list.getSource().getPrimaryTable();
}
}
 
/**
* Allow to build a list of buttons and when to enable/disable them.
180,8 → 119,8
 
private final boolean clickOnRows;
 
PopupEvent(final IListe list, final boolean clickOnRows) {
super(list);
PopupEvent(final IListe list, final List<SQLRowValues> selection, final List<? extends SQLRowAccessor> selectionAccessor, final boolean clickOnRows) {
super(list, selection, selectionAccessor);
this.clickOnRows = clickOnRows;
}
 
285,7 → 224,7
* @param evt the state of the <code>IListe</code>.
* @return the default action to perform, can be <code>null</code>.
*/
Action getDefaultAction(IListeEvent evt);
Action getDefaultAction(ListEvent evt);
 
// never null
PopupBuilder getPopupContent(PopupEvent evt);
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ListSQLLine.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,12 → 14,16
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.FieldPath;
import org.openconcerto.sql.model.RowRef;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.BaseFillSQLRequest.OrderValue;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.view.list.search.SearchQueue;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.NumberUtils;
68,7 → 72,10
private final SQLTableModelLinesSource src;
// unmodifiable
@GuardedBy("this")
private SQLRowValues row;
private SQLRowAccessor row;
// If the whole graph of this.row isn't kept then we need to store the primary keys so that when
// a SQLTableEvent is fired, we can know which lines is affected.
private final Set<RowRef> pks;
// Immutable
private final SQLTableModelSourceState state;
private final int id;
75,6 → 82,9
// allow to order by something not in the row
@GuardedBy("this")
private Number order;
// If the whole graph of this.row isn't kept then we need to store rows needed to order the
// lines. Otherwise don't allocate more memory just use this.rows.
private final OrderValue reqOrderVal;
// lists are accessed by Swing (model.getValueAt()) and
// by the search queue (SearchRunnable#matchFilter(ListSQLLine line))
// immutable
87,8 → 97,24
this.setRow(row);
this.id = id;
this.state = state;
final boolean isGraph = src.getParent().getKeepMode() == KeepMode.GRAPH;
this.reqOrderVal = isGraph ? null : state.getReq().createOrderValue(row);
if (isGraph) {
this.clearCache();
this.pks = null;
} else {
// compute all cells,
this.list = Collections.emptyList();
this.loadCache(state.getAllColumns().getAllColumns().size());
// then free memory by keeping a single SQLRow and only RowIDRef
this.setRow(row.asRow());
final Set<RowRef> tmpRefs = new HashSet<>(row.getGraphSize(), 1.0f);
for (final SQLRowValues v : row.getGraph().getItems()) {
tmpRefs.add(v.getRowRef());
}
this.pks = Collections.unmodifiableSet(tmpRefs);
}
}
 
// load at least columnCount values
// (to avoid loading debug columns, which took more time than the regular columns, ie more than
109,7 → 135,7
return this.src;
}
 
private final void setRow(SQLRowValues v) {
private final void setRow(SQLRowAccessor v) {
if (!v.isFrozen())
throw new IllegalArgumentException("Not frozen : " + v);
synchronized (this) {
117,10 → 143,18
}
}
 
public synchronized final SQLRowValues getRow() {
public final SQLRowValues getRow() {
return (SQLRowValues) this.getRowAccessor();
}
 
public synchronized final SQLRowAccessor getRowAccessor() {
return this.row;
}
 
public final Set<RowRef> getPKs() {
return this.pks;
}
 
@Override
public int compareTo(ListSQLLine o) {
if (this.src != o.src)
148,9 → 182,18
} else {
if (order2 != null)
throw new IllegalStateException("Order mismatch :\n" + order1 + " for " + l1 + " not coherent with\n" + order2 + " for " + l2);
 
final OrderValue orderVal1 = l1.getRequestOrderValue();
if (orderVal1 != null) {
final OrderValue orderVal2 = l2.getRequestOrderValue();
return orderVal1.compareTo(orderVal2);
} else {
// assert because we already checked same state above
assert l2.getRequestOrderValue() == null;
return l1.getState().getReq().order(l1.getRow(), l2.getRow());
}
}
}
 
public int getID() {
return this.id;
164,6 → 207,10
return this.order;
}
 
public final OrderValue getRequestOrderValue() {
return this.reqOrderVal;
}
 
public synchronized List<Object> getList(int columnCount) {
this.loadCache(columnCount);
return this.list;
220,6 → 267,8
}
 
public void clearCache() {
if (this.getSrc().getParent().getKeepMode() != KeepMode.GRAPH)
throw new IllegalStateException("Wouldn't be able to compute cell values");
synchronized (this) {
this.list = Collections.emptyList();
}
329,6 → 378,6
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " on " + this.getRow();
return this.getClass().getSimpleName() + " on " + this.getRowAccessor();
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/ChangeAllRunnable.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSourceOffline.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
31,6 → 31,7
 
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
231,8 → 232,10
* @return the new lines.
*/
@Override
public List<ListSQLLine> getAll() {
public List<ListSQLLine> get(Collection<? extends Number> ids) {
assert isUpdateThread();
if (ids != null)
throw new UnsupportedOperationException("Refreshing a subset of rows is not yet supported");
final List<SQLRowValues> dbRows = this.fetch();
 
if (this.lines.isEmpty()) {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSource.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
25,6 → 25,7
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.request.BaseFillSQLRequest;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.request.ListSQLRequest;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.change.ListChangeIndex;
118,6 → 119,8
return this.elem;
}
 
protected abstract KeepMode getKeepMode();
 
// lazy initialization since this method calls colsChanged() which subclasses overload and
// they need their own attribute that aren't set yet since super() must be the first statement.
public void init() {
127,6 → 130,7
 
final SQLRowValues graph = this.inited;
 
final boolean moreDebugCols = this.getKeepMode() == KeepMode.GRAPH;
graph.walkFields(new IClosure<FieldPath>() {
@Override
public void executeChecked(final FieldPath input) {
134,7 → 138,7
if (f.getTable().getLocalContentFields().contains(f)) {
final SQLTableModelColumnPath col = new SQLTableModelColumnPath(input, null, getElem().getDirectory());
SQLTableModelSource.this.cols.add(col);
} else
} else if (moreDebugCols) {
SQLTableModelSource.this.debugCols.add(new SQLTableModelColumnPath(input.getPath(), f.getName(), f.toString()) {
// don't show the rowValues since it's very verbose (and all content fields
// are already displayed as normal columns) and unsortable
145,8 → 149,10
}
});
}
}
}, true);
 
if (moreDebugCols)
this.debugCols.add(new DebugRow(getPrimaryTable()));
final SQLField orderField = getPrimaryTable().getOrderField();
if (orderField != null)
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelLinesSourceOnline.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
23,6 → 23,7
 
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
75,8 → 76,8
}
 
@Override
public List<ListSQLLine> getAll() {
final List<SQLRowValues> values = this.getUpdateQueueReq().getValues();
public List<ListSQLLine> get(Collection<? extends Number> ids) {
final List<SQLRowValues> values = this.getUpdateQueueReq().getValuesFromIDs(ids);
final List<ListSQLLine> res = new ArrayList<ListSQLLine>(values.size());
for (final SQLRowValues v : values) {
final ListSQLLine newLine = createLine(v);
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/UpdateOneRunnable.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,6 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.model.SQLTableEvent;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.utils.Value;
 
import java.util.Collection;
28,7 → 29,9
 
@Override
public void run() {
if (this.getTable() == this.getReq().getParent().getPrimaryTable()) {
final boolean isPrimaryTable = this.getTable() == this.getReq().getParent().getPrimaryTable();
if (this.getReq().getParent().getKeepMode() == KeepMode.GRAPH) {
if (isPrimaryTable) {
final Value<ListSQLLine> val = this.getReq().get(this.getID());
if (!val.hasValue())
return;
37,7 → 40,10
// eg CONTACT[3] has changed
updateLines(this.getAffectedPaths());
}
} else {
this.updateLines(this.getUpdateQ().getAffectedLines(this.getRow()), isPrimaryTable ? this.getID() : null);
}
}
 
@Override
protected Collection<String> getModifedFields() {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/MoveQueue.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
82,7 → 82,7
SQLUtils.executeAtomic(getTable().getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, Exception>() {
@Override
public Object handle(SQLDataSource ds) throws Exception {
moveQuick(rows, after, destID.get().getRow().asRow());
moveQuick(rows, after, destID.get().getRowAccessor().asRow());
return null;
}
});
107,7 → 107,7
final int rowCount = this.tableModel.getRowCount();
final boolean after = rowIndex >= rowCount;
final int index = after ? rowCount - 1 : rowIndex;
final SQLRowValues line = this.tableModel.getRow(index).getRow();
final SQLRowAccessor line = this.tableModel.getRow(index).getRowAccessor();
assert line.isFrozen() : "row could change by the time move() is called";
 
return this.put(new Runnable() {
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/SQLTableModelSourceOffline.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,6 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.request.ComboSQLRequest.KeepMode;
import org.openconcerto.sql.request.ListSQLRequest;
 
/**
37,4 → 38,9
protected SQLTableModelLinesSourceOffline _createLinesSource(final ITableModel model) {
return new SQLTableModelLinesSourceOffline(this, model);
}
 
@Override
public final KeepMode getKeepMode() {
return KeepMode.GRAPH;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/list/RowValuesTableControlPanel.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
14,6 → 14,7
package org.openconcerto.sql.view.list;
 
import org.openconcerto.sql.TM;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.view.IListFrame;
import org.openconcerto.ui.DefaultGridBagConstraints;
130,7 → 131,20
if (cellEditor != null) {
cellEditor.cancelCellEditing();
}
SQLField validationField = RowValuesTableControlPanel.this.model.getValidationField();
if (validationField != null) {
boolean canDelete = true;
for (int i : table.getSelectedRows()) {
SQLRowValues rowVals = RowValuesTableControlPanel.this.model.getRowValuesAt(i);
canDelete &= (!rowVals.contains(validationField.getName()) || rowVals.getObject(validationField.getName()) == null || !rowVals.getBoolean(validationField.getName()));
}
if (canDelete) {
RowValuesTableControlPanel.this.model.removeRowsAt(table.getSelectedRows());
}
// MAYBE show popup if can't delete
} else {
RowValuesTableControlPanel.this.model.removeRowsAt(table.getSelectedRows());
}
table.clearSelection();
}
});
/trunk/OpenConcerto/src/org/openconcerto/sql/view/IListFrame.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
31,6 → 31,7
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.function.Function;
 
import javax.swing.JFrame;
import javax.swing.event.TableModelEvent;
47,6 → 48,21
public static final String SHORT_TITLE = "org.openconcerto.listframe.shortTitle";
private static final String FILE_STRUCT_VERSION = "20160923";
 
private static Function<? super SQLElement, File> CONF_DIR_FUNCTION = (elem) -> {
final Configuration conf = Configuration.getInstance();
return conf == null ? null : conf.getConfDir();
};
 
static synchronized public final void setConfDirFunction(final Function<? super SQLElement, File> f) {
CONF_DIR_FUNCTION = f;
}
 
static synchronized public final File getConfDir(final SQLElement elem) {
if (CONF_DIR_FUNCTION == null)
return null;
return CONF_DIR_FUNCTION.apply(elem);
}
 
// windowState-20160923/IListFrame/ARTICLE.xml
static public final File getConfigFile(final SQLElement elem, final Class<? extends JFrame> c) {
return getConfigFile(elem, null, c);
59,15 → 75,18
 
static private final File getConfigFile(final SQLElement elem, final SQLComponent comp, final Class<? extends Window> c) {
final String compName = comp == null ? "" : "-" + comp.getCode();
return getConfigFile(c, elem.getCode() + compName);
return getConfigFile(getConfDir(elem), c, elem.getCode() + compName);
}
 
// windowState-20160923/WindowClass/code.xml
static public final File getConfigFile(final Class<? extends Window> c, final String code) {
final Configuration conf = Configuration.getInstance();
if (conf == null)
return getConfigFile(getConfDir(null), c, code);
}
 
static public final File getConfigFile(final File rootDir, final Class<? extends Window> c, final String code) {
if (rootDir == null)
return null;
final File structFile = new File(conf.getConfDir(), "windowState-" + FILE_STRUCT_VERSION);
final File structFile = new File(rootDir, "windowState-" + FILE_STRUCT_VERSION);
return new File(structFile, c.getSimpleName() + File.separator + getConfigFileName(code));
}
 
/trunk/OpenConcerto/src/org/openconcerto/sql/view/IListPanel.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
35,6 → 35,7
import org.openconcerto.sql.view.list.ITableModel;
import org.openconcerto.sql.view.list.RowAction;
import org.openconcerto.sql.view.list.RowAction.PredicateRowAction;
import org.openconcerto.sql.view.list.action.SQLRowValuesAction;
import org.openconcerto.sql.view.search.SearchListComponent;
import org.openconcerto.ui.ContinuousButtonModel;
import org.openconcerto.ui.FrameUtil;
69,6 → 70,7
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
109,15 → 111,18
 
static public final File getConfigFile(final SQLElement elem, final Class<? extends Container> c, final String variant) {
final String suffix = StringUtils.isEmpty(variant, true) ? "" : "-" + variant;
return getConfigFile(c, elem.getCode() + suffix);
return getConfigFile(IListFrame.getConfDir(elem), c, elem.getCode() + suffix);
}
 
static public final File getConfigFile(final Class<? extends Container> c, String code) {
final Configuration conf = Configuration.getInstance();
if (conf == null)
return getConfigFile(IListFrame.getConfDir(null), c, code);
}
 
static public final File getConfigFile(final File rootDir, final Class<? extends Container> c, String code) {
if (rootDir == null)
return null;
 
final File structFile = new File(conf.getConfDir(), "jtableState-" + FILE_STRUCT_VERSION);
final File structFile = new File(rootDir, "jtableState-" + FILE_STRUCT_VERSION);
return new File(structFile, c.getSimpleName() + File.separator + IListFrame.getConfigFileName(code));
}
 
167,6 → 172,7
private JButton buttonPlus;
private JButton buttonMoins;
protected final JPanel searchPanel = new JPanel(new GridBagLayout());
 
private static final JButton createBtn(Icon i) {
final JButton res = new JButton(i);
res.setMargin(new Insets(1, 1, 1, 1));
219,23 → 225,33
list.setConfigFile(config);
}
this.liste = list;
final IClosure<ListChangeIndex<IListeAction>> l = new IClosure<ListChangeIndex<IListeAction>>() {
final Map<SQLRowValuesAction, IListeAction> actionsMap = new IdentityHashMap<>();
final IClosure<ListChangeIndex<SQLRowValuesAction>> l = new IClosure<ListChangeIndex<SQLRowValuesAction>>() {
@Override
public void executeChecked(ListChangeIndex<IListeAction> input) {
getListe().removeIListeActions(input.getItemsRemoved());
getListe().addIListeActions(input.getItemsAdded());
public void executeChecked(ListChangeIndex<SQLRowValuesAction> input) {
SwingThreadUtils.invoke(() -> {
for (final SQLRowValuesAction a : input.getItemsRemoved()) {
final IListeAction la = actionsMap.remove(a);
if (la != null)
getListe().removeIListeAction(la);
}
actionsMap.putAll(getListe().addRowValuesActions(getElement().getRowValuesActions()));
});
}
};
// remove listener if non displayable since getElement() never dies
this.addHierarchyListener(new HierarchyListener() {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0)
if (isDisplayable()) {
getListe().addIListeActions(getElement().getRowActions());
assert actionsMap.isEmpty();
actionsMap.putAll(getListe().addRowValuesActions(getElement().getRowValuesActions()));
getElement().addRowActionsListener(l);
} else {
getElement().removeRowActionsListener(l);
getListe().removeIListeActions(getElement().getRowActions());
getListe().removeIListeActions(actionsMap.values());
actionsMap.clear();
}
}
});
785,7 → 801,7
}
 
private boolean isRO() {
final SQLRowAccessor r = getListe().getSelectedRow();
final SQLRowAccessor r = getListe().getSelectedRowAccessor();
return r != null && SQLComponent.isReadOnly(r);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/RowMetadataCache.java
New file
0,0 → 1,103
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.RowRef;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.SQLCache;
import org.openconcerto.utils.RTInterruptedException;
import org.openconcerto.utils.ThreadFactory;
import org.openconcerto.utils.cache.CacheResult;
 
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
 
public final class RowMetadataCache {
private static RowMetadata createMD(final SQLRowAccessor r, final SQLField creation, final SQLField userCreate, final SQLField modification, final SQLField userModify) {
return new RowMetadata(getDateMD(r, creation), getForeignIntegerMD(r, userCreate), getDateMD(r, modification), getForeignIntegerMD(r, userModify));
}
 
private static Date getDateMD(final SQLRowAccessor row, final SQLField f) {
return f == null ? null : row.getObjectAs(f.getName(), Date.class);
}
 
private static Integer getForeignIntegerMD(final SQLRowAccessor row, final SQLField f) {
if (f == null)
return null;
final Number n = row.getNonEmptyForeignIDNumber(f.getName());
return n == null ? null : n.intValue();
}
 
private final SQLCache<RowRef, RowMetadata> cache;
private final ExecutorService exec;
 
public RowMetadataCache(final int timeoutInSeconds, final int maxCount, final String name) {
this.cache = new SQLCache<>(timeoutInSeconds, maxCount, "Cache of metadata for " + name);
this.exec = Executors.newSingleThreadExecutor(new ThreadFactory("Metadata cache thread for " + name, true));
}
 
public final RowMetadata get(final RowRef ref) {
final CacheResult<RowMetadata> cacheRes = this.cache.get(ref);
if (cacheRes.getState() == CacheResult.State.INTERRUPTED) {
// shouldn't happen since we don't use check()/removeRunning()
throw new RTInterruptedException("interrupted while waiting for the cache");
} else if (cacheRes.getState() == CacheResult.State.VALID) {
final RowMetadata res = cacheRes.getRes();
assert res != null : "Null ambiguity";
return res;
} else {
return null;
}
}
 
public final void fetch(final RowRef ref, final Set<Number> withAdditionalIDs) {
this.exec.submit(() -> {
final CacheResult<RowMetadata> cacheResult = this.cache.get(ref);
if (cacheResult.getState() == CacheResult.State.VALID)
return;
final SQLTable table = ref.getTable();
final SQLField creationDateField = table.getCreationDateField();
final SQLField creationUserField = table.getCreationUserField();
final SQLField modifDateField = table.getModifDateField();
final SQLField modifUserField = table.getModifUserField();
final SQLSelect sel = new SQLSelect();
sel.addAllSelect(table.getPrimaryKeyFields());
sel.addAllSelect(creationDateField, creationUserField, modifDateField, modifUserField);
sel.setWhere(Where.inValues(table.getKey(), withAdditionalIDs));
 
final Set<SQLTable> data = Collections.singleton(table);
 
// wait :
// 1. check errors
// 2. avoid fetching the same IDs more than once
try {
for (final SQLRowAccessor v : SQLRowListRSH.execute(sel)) {
this.cache.put(v.getRowRef(), createMD(v, creationDateField, creationUserField, modifDateField, modifUserField), data);
}
} catch (Exception exn) {
Log.get().log(Level.WARNING, "Couldn't fetch metadata for " + ref, exn);
}
});
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/RowMetadata.java
New file
0,0 → 1,48
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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 java.util.Date;
 
import net.jcip.annotations.Immutable;
 
@Immutable
public final class RowMetadata {
private final Integer userCreate, userModify;
private final Date creation, modification;
 
public RowMetadata(Date creation, Integer userCreate, Date modification, Integer userModify) {
super();
this.creation = creation;
this.userCreate = userCreate;
this.modification = modification;
this.userModify = userModify;
}
 
public final Integer getUserCreate() {
return this.userCreate;
}
 
public final Integer getUserModify() {
return this.userModify;
}
 
public final Date getCreation() {
return this.creation;
}
 
public final Date getModification() {
return this.modification;
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/SearchListComponent.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
43,8 → 43,8
public SearchListComponent(ITableModel model) {
super();
this.r = null;
this.items = new ArrayList<SearchItemComponent>();
this.formats = new HashMap<Class<?>, FormatGroup>();
this.items = new ArrayList<>();
this.formats = new HashMap<>();
 
this.setLayout(new GridBagLayout());
this.c = new GridBagConstraints();
175,7 → 175,7
this.checkEDT();
 
for (int i = 0; i < this.items.size(); i++) {
SearchItemComponent component = this.items.get(i);
final SearchItemComponent component = this.items.get(i);
component.setSearchFullMode(b);
}
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/TextSearchSpec.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
55,8 → 55,8
this.mode = mode;
this.filterString = filterString;
this.normalizedFilterString = normalize(filterString);
this.formats = new HashMap<Class<?>, FormatGroup>();
this.parsedFilter = new HashMap<Format, Object>();
this.formats = new HashMap<>();
this.parsedFilter = new HashMap<>();
}
 
private String normalize(String s) {
88,7 → 88,7
private final String format(final Format fmt, final Object cell) {
try {
return fmt.format(cell);
} catch (Exception e) {
} catch (final Exception e) {
throw new IllegalStateException("Couldn't format " + cell + '(' + getClass(cell) + ") with " + fmt, e);
}
}
101,7 → 101,7
if (!this.parsedFilterD_tried) {
try {
this.parsedFilterD = Double.valueOf(this.filterString);
} catch (NumberFormatException e) {
} catch (final NumberFormatException e) {
this.parsedFilterD = null;
}
this.parsedFilterD_tried = true;
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/SearchItemComponent.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
52,6 → 52,7
import javax.swing.event.DocumentEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import javax.swing.text.BadLocationException;
 
import net.jcip.annotations.Immutable;
89,30 → 90,38
}
}
 
private JTextField textFieldRecherche = new JTextField(10);
private final JComboBox comboColonnePourRecherche;
private final JComboBox searchMode;
private JCheckBox invertSearch = new JCheckBox(TM.tr("toReverse"));
private JButton buttonAdd = new JButton("+");
private JButton buttonRemove = new JButton();
private final JTextField textFieldRecherche = new JTextField(10);
private final JComboBox<Column> comboColonnePourRecherche;
private final JComboBox<String> searchMode;
private final JCheckBox invertSearch = new JCheckBox(TM.tr("toReverse"));
private final JButton buttonAdd = new JButton("+");
private final JButton buttonRemove = new JButton();
final SearchListComponent list;
// final to ease removing listener, SearchListComponent.reset() removes every item
// when the TableModel changes.
private final TableModel tableModel;
private String text = "";
 
public SearchItemComponent(final SearchListComponent list) {
super();
this.list = list;
this.tableModel = this.list.getTableModel();
this.setOpaque(false);
// Initialisation de l'interface graphique
this.searchMode = new JComboBox(new String[] { TM.tr("contains"), TM.tr("contains.exactly"), TM.tr("isLessThan"), TM.tr("isEqualTo"), TM.tr("isExactlyEqualTo"), TM.tr("isGreaterThan"),
TM.tr("isEmpty") });
final ListComboBoxModel comboModel = new ListComboBoxModel(Arrays.asList(TOUT));
this.searchMode = new JComboBox<>(
new String[] { TM.tr("contains"), TM.tr("contains.exactly"), TM.tr("isLessThan"), TM.tr("isEqualTo"), TM.tr("isExactlyEqualTo"), TM.tr("isGreaterThan"), TM.tr("isEmpty") });
final ListComboBoxModel<Column> comboModel = new ListComboBoxModel<>(Arrays.asList(TOUT));
comboModel.setSelectOnAdd(false);
// allow getColIndex() and thus getSearchItem() to work from now on
assert comboModel.getSelectedItem() != null;
this.comboColonnePourRecherche = new JComboBox(comboModel);
this.comboColonnePourRecherche = new JComboBox<>(comboModel);
uiInit();
}
 
final TableModel getTableModel() {
return this.tableModel;
}
 
private void uiInit() {
this.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
123,11 → 132,12
 
// designation
// don't just use DefaultListCellRenderer, it fails on some l&f
final ListCellRenderer old = this.comboColonnePourRecherche.getRenderer();
this.comboColonnePourRecherche.setRenderer(new ListCellRenderer() {
@SuppressWarnings("unchecked")
final ListCellRenderer<Object> old = (ListCellRenderer<Object>) this.comboColonnePourRecherche.getRenderer();
this.comboColonnePourRecherche.setRenderer(new ListCellRenderer<Column>() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
return old.getListCellRendererComponent(list, ((Column) value).getLabel(), index, isSelected, cellHasFocus);
public Component getListCellRendererComponent(JList<? extends Column> list, Column value, int index, boolean isSelected, boolean cellHasFocus) {
return old.getListCellRendererComponent(list, value.getLabel(), index, isSelected, cellHasFocus);
}
});
// hand tuned for a IListPanel width of 1024px
171,11 → 181,13
initInvertSearch();
 
this.buttonAdd.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SearchItemComponent.this.list.addNewSearchItem();
}
});
this.buttonRemove.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SearchItemComponent.this.list.removeSearchItem(SearchItemComponent.this);
}
184,6 → 196,7
 
private void initInvertSearch() {
this.invertSearch.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateSearchList();
}
192,12 → 205,13
 
private void initSearchText() {
this.textFieldRecherche.getDocument().addDocumentListener(new SimpleDocumentListener() {
@Override
public void update(DocumentEvent e) {
try {
// One ne peut pas appeler chercher() car le texte n'est pas encore a jour
SearchItemComponent.this.text = e.getDocument().getText(0, e.getDocument().getLength()).trim();
updateSearchList();
} catch (BadLocationException exn) {
} catch (final BadLocationException exn) {
// impossible
exn.printStackTrace();
}
207,6 → 221,7
 
private void initCombo() {
final ItemListener listener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
updateSearchList();
}
223,13 → 238,14
};
// allow the TableModel to die
this.addHierarchyListener(new HierarchyListener() {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
if (e.getChanged().isDisplayable()) {
columnsChanged(listener);
SearchItemComponent.this.list.getTableModel().addTableModelListener(tableModelL);
SearchItemComponent.this.getTableModel().addTableModelListener(tableModelL);
} else {
SearchItemComponent.this.list.getTableModel().removeTableModelListener(tableModelL);
SearchItemComponent.this.getTableModel().removeTableModelListener(tableModelL);
}
}
}
244,7 → 260,7
 
private void fillColumnCombo(ItemListener listener) {
// sort column names alphabetically
final int columnCount = this.list.getTableModel().getColumnCount();
final int columnCount = this.getTableModel().getColumnCount();
final String[][] names = new String[columnCount][];
final int[] indexes = new int[columnCount];
for (int i = 0; i < columnCount; i++) {
253,7 → 269,7
}
// use column index as columns names are not unique
final SortedMap<String, Integer> map = solve(names, indexes);
final List<Column> cols = new ArrayList<Column>(columnCount);
final List<Column> cols = new ArrayList<>(columnCount);
cols.add(TOUT);
for (final Entry<String, Integer> e : map.entrySet()) {
final int colIndex = e.getValue().intValue();
263,7 → 279,7
 
// don't fire when filling, we will fire when selecting
this.comboColonnePourRecherche.removeItemListener(listener);
final ListComboBoxModel comboModel = (ListComboBoxModel) this.comboColonnePourRecherche.getModel();
final ListComboBoxModel<Column> comboModel = (ListComboBoxModel<Column>) this.comboColonnePourRecherche.getModel();
assert !comboModel.isSelectOnAdd() : "Otherwise our following select might not fire";
comboModel.removeAllElements();
comboModel.addAll(cols);
273,7 → 289,7
private void columnsChanged(final ItemListener listener) {
final String currentID = ((Column) this.comboColonnePourRecherche.getSelectedItem()).getID();
fillColumnCombo(listener);
final ListComboBoxModel comboModel = (ListComboBoxModel) this.comboColonnePourRecherche.getModel();
final ListComboBoxModel<Column> comboModel = (ListComboBoxModel<Column>) this.comboColonnePourRecherche.getModel();
// no selection since the model was just emptied
assert this.comboColonnePourRecherche.getSelectedIndex() == -1 && this.comboColonnePourRecherche.getSelectedItem() == null;
// try to reselect the same column if it's still there
296,7 → 312,7
private SortedMap<String, Integer> solve(final String[][] names, final int[] indexes) {
final int columnCount = names.length;
// columns' index by name
final ListMap<String, Integer> collisions = new ListMap<String, Integer>(columnCount);
final ListMap<String, Integer> collisions = new ListMap<>(columnCount);
for (int i = 0; i < columnCount; i++) {
final int index = indexes[i];
if (index >= names[i].length)
304,8 → 320,8
final String columnName = names[i][index];
collisions.add(columnName, i);
}
final SortedMap<String, Integer> res = new TreeMap<String, Integer>();
for (Entry<String, ? extends Collection<Integer>> e : collisions.entrySet()) {
final SortedMap<String, Integer> res = new TreeMap<>();
for (final Entry<String, ? extends Collection<Integer>> e : collisions.entrySet()) {
final Collection<Integer> indexesWithCollision = e.getValue();
if (indexesWithCollision.size() > 1) {
// increment only the minimum indexes to try to solve the conflict with the lowest
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/DateSearchSpec.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
18,10 → 18,10
 
public class DateSearchSpec implements SearchSpec {
// include or exclude filterString
private boolean excludeFilterString;
private int columnIndex;
private Date fromDate;
private Date toDate;
private final boolean excludeFilterString;
private final int columnIndex;
private final Date fromDate;
private final Date toDate;
 
public DateSearchSpec(Date from, Date to, int columnIndex) {
this(false, from, to, columnIndex);
44,8 → 44,7
* @return <code>true</code> si la ligne contient.
*/
static private boolean contains(Object line, Date startDate, Date stopDate, int index) {
 
List list = (List) line;
final List<?> list = (List<?>) line;
final int start;
final int stop;
if (index < 0) {
62,7 → 61,7
final Object cell = list.get(i);
if (cell != null) {
if (cell instanceof Date) {
Date date = (Date) cell;
final Date date = (Date) cell;
if (date.after(startDate) && date.before(stopDate)) {
return true;
}
75,6 → 74,7
return false;
}
 
@Override
public boolean match(Object line) {
return this.excludeFilterString ^ contains(line, this.fromDate, this.toDate, this.columnIndex);
}
83,6 → 83,7
System.out.println(this.excludeFilterString + ":" + this.fromDate + "->" + this.toDate + " col:" + this.columnIndex);
}
 
@Override
public boolean isEmpty() {
return true;
}
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/SearchList.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
31,7 → 31,7
 
public SearchList() {
super();
this.items = new OrderedSet<SearchSpec>();
this.items = new OrderedSet<>();
}
 
public void addSearchItem(SearchSpec item) {
42,6 → 42,7
this.items.remove(item);
}
 
@Override
public boolean isEmpty() {
for (final SearchSpec s : this.items)
if (!s.isEmpty())
49,12 → 50,13
return true;
}
 
@Override
public boolean match(Object line) {
return this.match((List) line);
return this.match((List<?>) line);
}
 
// fait un AND de tous les éléments
private boolean match(List line) {
private boolean match(List<?> line) {
// List de String, cad 1 String par colonne
 
boolean result = false;
69,6 → 71,7
return result;
}
 
@Override
public String toString() {
final StringBuffer sb = new StringBuffer(this.items.size() * 32);
sb.append("SearchList:" + this.items.size() + " items");
/trunk/OpenConcerto/src/org/openconcerto/sql/view/search/SearchSpecUtils.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
23,7 → 23,7
if (search == null || search.isEmpty())
result = l;
else {
result = new ArrayList<T>(l.size());
result = new ArrayList<>(l.size());
for (final T item : l) {
if (search.match(item))
result.add(item);
/trunk/OpenConcerto/src/org/openconcerto/utils/OutlookEmail.vbs
File deleted
/trunk/OpenConcerto/src/org/openconcerto/utils/reentrantEventsNaive.seq.violet.html
New file
0,0 → 1,800
<HTML>
<HEAD>
<META name="description"
content="Violet UML Editor cross format document" />
<META name="keywords" content="Violet, UML" />
<META charset="UTF-8" />
<SCRIPT type="text/javascript">
function switchVisibility() {
var obj = document.getElementById("content");
obj.style.display = (obj.style.display == "block") ? "none" : "block";
}
</SCRIPT>
</HEAD>
<BODY>
This file was generated with Violet UML Editor 3.0.0.
&nbsp;&nbsp;(&nbsp;<A href=# onclick="switchVisibility()">View Source</A>&nbsp;/&nbsp;<A href="http://sourceforge.net/projects/violet/files/violetumleditor/" target="_blank">Download Violet</A>&nbsp;)
<BR />
<BR />
<SCRIPT id="content" type="text/xml"><![CDATA[<SequenceDiagramGraph id="1">
<nodes id="2">
<LifelineNode id="3">
<id id="4" value="72e47375-469f-4a88-abb6-e40e46c324ce"/>
<revision>0</revision>
<children id="5">
<ActivationBarNode id="6">
<id id="7" value="a94abf1e-083c-482c-8940-d77d8a5e62a3"/>
<revision>1</revision>
<children id="8"/>
<parent class="LifelineNode" reference="3"/>
<location class="Point2D.Double" id="9" x="112.5" y="75.0"/>
<backgroundColor id="10">
<red>255</red>
<green>255</green>
<blue>255</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="11">
<red>191</red>
<green>191</green>
<blue>191</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="12">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="13" x="50.0" y="0.0"/>
<textColor reference="12"/>
<name id="14">
<text>evt 1</text>
</name>
<type id="15">
<text>SQLElementEvent</text>
</type>
<endOfLife>true</endOfLife>
</LifelineNode>
<LifelineNode id="16">
<id id="17" value="2da76d69-e422-46b0-a548-1d7b83093d9f"/>
<revision>1</revision>
<children id="18">
<ActivationBarNode id="19">
<id id="20" value="030a1385-4bfe-4889-8ec1-ffa386c5db22"/>
<revision>0</revision>
<children id="21"/>
<parent class="LifelineNode" reference="16"/>
<location class="Point2D.Double" id="22" x="112.5" y="110.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor id="23">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="24" x="980.0" y="10.0"/>
<textColor reference="23"/>
<name id="25">
<text>evt 2</text>
</name>
<type id="26">
<text>SQLElementEvent</text>
</type>
<endOfLife>true</endOfLife>
</LifelineNode>
<LifelineNode id="27">
<id id="28" value="d3539181-e292-45d4-92ee-bb25e1cd9aaf"/>
<revision>1</revision>
<children id="29">
<ActivationBarNode id="30">
<id id="31" value="4f050be9-7427-4789-b824-b1f78f32cd07"/>
<revision>0</revision>
<children id="32"/>
<parent class="LifelineNode" reference="27"/>
<location class="Point2D.Double" id="33" x="133.0" y="90.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
<ActivationBarNode id="34">
<id id="35" value="3af702fc-e885-4000-b97b-bd4921b5fc56"/>
<revision>0</revision>
<children id="36"/>
<parent class="LifelineNode" reference="27"/>
<location class="Point2D.Double" id="37" x="133.0" y="140.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="38" x="370.0" y="10.0"/>
<textColor reference="23"/>
<name id="39">
<text>l1</text>
</name>
<type id="40">
<text>SQLElementEventListener</text>
</type>
<endOfLife>false</endOfLife>
</LifelineNode>
<LifelineNode id="41">
<id id="42" value="66c2c3a4-4956-4a4b-b819-07da280e5582"/>
<revision>1</revision>
<children id="43">
<ActivationBarNode id="44">
<id id="45" value="bf8e10a3-b20f-4568-af06-4c8ffe13924e"/>
<revision>0</revision>
<children id="46"/>
<parent class="LifelineNode" reference="41"/>
<location class="Point2D.Double" id="47" x="133.0" y="200.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
<ActivationBarNode id="48">
<id id="49" value="ac5b4a69-b3fa-4a3b-8c4a-570c49d3b4cb"/>
<revision>0</revision>
<children id="50"/>
<parent class="LifelineNode" reference="41"/>
<location class="Point2D.Double" id="51" x="133.0" y="310.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="52" x="680.0" y="0.0"/>
<textColor reference="23"/>
<name id="53">
<text>l2</text>
</name>
<type id="54">
<text>SQLElementEventListener</text>
</type>
<endOfLife>false</endOfLife>
</LifelineNode>
<NoteNode id="55">
<id id="56" value="d4154bcf-2a20-4ffc-aaf5-43d47a5a34f7"/>
<revision>1</revision>
<children id="57"/>
<location class="Point2D.Double" id="58" x="730.0" y="410.0"/>
<backgroundColor id="59">
<red>254</red>
<green>222</green>
<blue>188</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="60">
<red>253</red>
<green>186</green>
<blue>113</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="61">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
<text id="62">
<text>l2 is notified of evt 2 before evt 1</text>
</text>
</NoteNode>
</nodes>
<edges id="63">
<SynchronousCallEdge id="64">
<id id="65" value="69d0c441-b334-4eb1-8b1e-89a9d77ad913"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="6"/>
<startLocation class="Point2D.Double" id="66" x="7.5" y="15.0"/>
<endNode class="ActivationBarNode" reference="30"/>
<endLocation class="Point2D.Double" id="67" x="150.0" y="90.0"/>
<transitionPoints id="68"/>
<backgroundColor id="69">
<red>255</red>
<green>255</green>
<blue>255</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="70">
<red>191</red>
<green>191</green>
<blue>191</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="71">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="72">
<text></text>
</startLabel>
<centerLabel id="73">
<text>modification(evt1)</text>
</centerLabel>
<endLabel id="74">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="75">
<id id="76" value="2a43a89d-a435-46ef-ba93-fe869e1af5f0"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="30"/>
<startLocation class="Point2D.Double" id="77" x="7.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="78" x="120.0" y="100.0"/>
<transitionPoints id="79"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="80">
<text></text>
</startLabel>
<centerLabel id="81">
<text>fire</text>
</centerLabel>
<endLabel id="82">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="83">
<id id="84" value="0c060aa6-6c2b-4238-a9ae-0b4417cdcbda"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="85" x="7.5" y="20.0"/>
<endNode class="ActivationBarNode" reference="34"/>
<endLocation class="Point2D.Double" id="86" x="130.0" y="130.0"/>
<transitionPoints id="87"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="88">
<text></text>
</startLabel>
<centerLabel id="89">
<text>modification(evt2)</text>
</centerLabel>
<endLabel id="90">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="91">
<id id="92" value="7866f976-b5c5-4dff-b55e-817e0acd0851"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="93" x="7.5" y="70.0"/>
<endNode class="ActivationBarNode" reference="44"/>
<endLocation class="Point2D.Double" id="94" x="140.0" y="200.0"/>
<transitionPoints id="95"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="96">
<text></text>
</startLabel>
<centerLabel id="97">
<text>modification(evt2)</text>
</centerLabel>
<endLabel id="98">
<text></text>
</endLabel>
</SynchronousCallEdge>
<ReturnEdge id="99">
<id id="100" value="8a1ad364-cc2f-43ce-99d4-77158785be3d"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="101" x="7.5" y="130.0"/>
<endNode class="ActivationBarNode" reference="30"/>
<endLocation class="Point2D.Double" id="102" x="7.0" y="160.0"/>
<transitionPoints id="103"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="104">
<text></text>
</startLabel>
<centerLabel id="105">
<text></text>
</centerLabel>
<endLabel id="106">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="107">
<id id="108" value="5e7f4a16-1fc3-4fc6-9dac-1d228ab268f8"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="34"/>
<startLocation class="Point2D.Double" id="109" x="7.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="110" x="7.5" y="60.0"/>
<transitionPoints id="111"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="112">
<text></text>
</startLabel>
<centerLabel id="113">
<text></text>
</centerLabel>
<endLabel id="114">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="115">
<id id="116" value="5fe641a1-312d-4394-96df-a684b18f6a1e"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="44"/>
<startLocation class="Point2D.Double" id="117" x="7.0" y="30.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="118" x="7.5" y="110.0"/>
<transitionPoints id="119"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="120">
<text></text>
</startLabel>
<centerLabel id="121">
<text></text>
</centerLabel>
<endLabel id="122">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="123">
<id id="124" value="5769cc8e-d632-4921-9aea-5d2e0e15d351"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="30"/>
<startLocation class="Point2D.Double" id="125" x="7.0" y="170.0"/>
<endNode class="ActivationBarNode" reference="6"/>
<endLocation class="Point2D.Double" id="126" x="7.5" y="185.0"/>
<transitionPoints id="127"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="128">
<text></text>
</startLabel>
<centerLabel id="129">
<text></text>
</centerLabel>
<endLabel id="130">
<text></text>
</endLabel>
</ReturnEdge>
<SynchronousCallEdge id="131">
<id id="132" value="1334344f-9a27-4aff-b5d4-5ddfda00b259"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="6"/>
<startLocation class="Point2D.Double" id="133" x="7.5" y="215.0"/>
<endNode class="ActivationBarNode" reference="48"/>
<endLocation class="Point2D.Double" id="134" x="140.0" y="290.0"/>
<transitionPoints id="135"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="136">
<text></text>
</startLabel>
<centerLabel id="137">
<text>modification(evt1)</text>
</centerLabel>
<endLabel id="138">
<text></text>
</endLabel>
</SynchronousCallEdge>
<ReturnEdge id="139">
<id id="140" value="397b25a5-6d9e-43d2-9c80-f1ec604ef31c"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="48"/>
<startLocation class="Point2D.Double" id="141" x="7.0" y="30.0"/>
<endNode class="ActivationBarNode" reference="6"/>
<endLocation class="Point2D.Double" id="142" x="7.5" y="255.0"/>
<transitionPoints id="143"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="144">
<text></text>
</startLabel>
<centerLabel id="145">
<text></text>
</centerLabel>
<endLabel id="146">
<text></text>
</endLabel>
</ReturnEdge>
</edges>
</SequenceDiagramGraph>]]></SCRIPT>
<BR />
<BR />
<IMG alt="embedded diagram image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABJQAAAHDCAIAAAD9e9f6AABLXklEQVR4Xu3d7ZscV33n/zxP/oA8
C8/zUDYxtgO2g43txDiA8cbceSGQJb84sF7uQkiWkIQENkuW+PczBLi4AiGEu2Agu5hkCWhmNLeS
LM8wI41lyyNL1vgGjSVL49GMpJme35cpVDqqqu4+n65T1ae73q+rL1/Vpz91uqrnfNX9dU/3/MJu
YOhsN0b2zAGUkC2w0rJ3AKCEbIF5yE4BDL5f2N1TMQDRatSSbtTJApWqopqqmBNopt6qqbe9gGjR
vGEINWpJN+pkgUpVUU1VzAk0U2/V1NteQLRo3jCEGrWkG3WyQKWqqKYq5gSaqbdq6m0vIFo0bxhC
jVrSjTpZoFJVVFMVcwLN1Fs19bYXEC2aNwyhRi3pRp0sUKkqqqmKOYFm6q2aetsLiBbNG4ZQo5Z0
o04WqFQV1VTFnEAz9VZNve0FRIvmDUOoUUu6UScLVKqKaqpiTqCZequm3vYCokXzhiHUqCXdqJMF
KlVFNVUxJ9BMvVVTb3sB0aJ5wxBq1JJu1MkClaqimqqYE2im3qqpt72AaNG8YQg1akk36mSBSlVR
TVXMCTRTb9XU215AtGjeMIQataQbdbJApaqopirmBJqpt2rqbS8gWjRvGEKNWtKNOlmgUlVUUxVz
As3UWzX1thcQLZo3DKFGLelGnSxQqSqqqYo5gWbqrZp62wuIFs0bhlCjlnSjThaoVBXVVMWcQDP1
Vk297QVEi+YNQ6hRS7pRJwtUqopqqmJOoJl6q6be9gKiRfOGIdSoJd2okwUqVUU1VTEn0Ey9VVNv
ewHRonnDEGrUkm7UyQKVqqKaqpgTaKbeqqm3vYBo0bxhCDVqSTfqZIFKVVFNVcwJNFNv1dTbXkC0
aN4whBq1pBt1skClqqimKuYEmqm3auptLyBaNG8YQo1a0o06WaBSVVRTFXMCzdRbNfW2FxAtmjcM
oUYt6UadLFCpKqqpijmBZuqtmnrbC4gWzVt9du3IjqICjVrSPifbnLXXnDONxJA94D7VpPKZc8ge
xg6ac6aRGLIH3Kea8nrbC6ohW2wxq6958/yhHj169B/+4R/uvffeq666yiff1cLCwv3333/77bdf
d911d91111/+5V8eOnQok1ldXf3c5z73O7/zO9dff73F7r777gcffPDMmTOZWOdTSG7NywScPWJU
eJCXT+ZKmVilpHusZ0lHwudkM48eVdZfhQd5+WSulIlVqvAeCwdTnW9Necb6zqeaVD5zuo/PY489
9slPfvKNb3zjtddeazX1vve97+DBg1fGZVSopPAgL5/MlTKxShXeY+FgqvOtKc9Y3/lUU15ve6l8
HkOqu+8KD/LyyVwpE6uUdI/RNW+XHzO/fGc//vGPr7766sycmWmXlpZ+8zd/M5vYteu222574okn
3GQy7o64rtz7skzA2SNGhQd5+WSulIlVSrrHepZ0JHxONvPoXf4RXjneG6pMVXiQl0/mSplYpQrv
sXBQFWSSGvhUk8pnTvfxSbZdVl8/+tGPrtxDQIWqCg/y8slcKROrVOE9Fg6qgkxSA59qyuttL5XP
Y5hkXFR3zQoP8vLJXCkTq5R0j9E1b2984xs/97nPPfnkk575zmw2m+S+++47fPjw+fPnl5eXH374
4XvvvTcNrK+vv+51r7PMPffcMzExsba2du7cuampKbtqg3fccYeNpOHOh9T51m2PQAwKD7JwsGbS
MdSzpCPhc7KZR48q66/CgywcrFnhMRQOqoJMUgOfalL5zOk+Pm95y1seeuihp556amNjw2rq7W9/
u930hje84co9BFSoqvAgCwdrVngMhYOqIJPUwKea8nrbS+XzGFLdfVd4kIWDNZOOQWjevv/977/r
Xe961atedc0119x5551/93d/t7q6auOnT5+2EWv9V1ZW3LytmFe+8pU333zzxYsXk2NyuclCnrHO
XvGKV9gkL7zwQvaGS/75n//ZAnfddZe7fI1dtXKym7785S+ng50PqfOt20WBdg9pIsnbkfzZn/2Z
PZK33HKLHa2Nnzlz5k/+5E9uuOGGm2666cEHH2y1Wpdn9JvTfjQf+9jHbE6b4YEHHkhnSG51uePp
JC6fn34yUsWBteO5pIeDz8m2e9DajUuossI51cXsbmf0t8rc7Tz3Vns58tnPfvb1r3/9ddddd+ut
t77nPe/Zv39/mnGlu5c54ITPDHZgn/zkJ+2BevnLX+7sWsCnmlQ+c2YeFpe9GrObrMqyN3ijQgvn
bLeukltd7ng6iYsKLTzghM8MVVeo517tDtXn53v5sbvETbZDde+iup1bXemOhbyaN5vdHsfsxLt2
Wcf/0ksvWeDDH/6wXf3qV7/q7vXd737XBj/96U9v64e13fGhTHXN2Hq1wBe+8AV79LO37bAH2gL/
/u//nr1he9sG7aZ3vOMd6Ujnu+t86/aVga4PaZp///vf7wZGR0ff9ra3uSO2XNQ5P/jBD7qBb3zj
G+6tLnc82c7r+tOv7sDa6bqkh4nPybZ70NqNu7pmqLJdRXOqi9ndzutjlbnbee6tH/3oRy/vfEma
yQyWP2D/GT7wgQ8kG1dddVUy3o5PNal85kwOLzu649SpU3bT3Xffnb3hkg77JqjQXUVztltX7mDC
HU+286jQVIQV2nWvrofa9eeb3bP9T8RFde+iup1bXekdFfJq3r7zne/s2vnt2B/+8Ie2PjY2Nubm
5t761rfaoDWOFti7d69tv/nNb3b3SgJLS0vJVZ+jcfnku2aSpZmwPth+AA8//HDaJZsbb7xxV5v/
aWFNtt1kgXSk892ld5SRCSTbXR/SNH/PPfc8/vjj9uP/xCc+YVetp8+MpIXnP6f9pI4cObK6uvrx
j3/crlomuTUNpFfdwbzk1q4//eoOrJ2uS3qY+Jxsu0ev3bira4YqK5xTXczJYF5yax+rrHAw5d56
ww032PY//dM/nTlzZm1t7cCBA/fdd18+lih/wP4zWGZ8fPzcuXPJYAc+1aTymTM5zuzoDjsXu+mL
X/xi9oZLOuyboEIL52y3rtJAetUdzEtupULzB+w/Q9UV2nWvrofa9ee7XfQYdkV176K6L3HvsSuv
5u3ee++1GR999FF38MSJEzb4ute9bnun6Ux+pzZdxIuLi3b17W9/e5qXDmtbz7ezb9+++++///rr
r08m3LWzUE5f+l8UL3/5y21kc3Pzyp1+Jnkf3H07u/MhpfNnZALJdteHdPtS/ic/+Uly9eTJk4Uj
N998c3LVf875+fnk6k9/+lO7ag9Omk8C6VV3MC+5tetPv7oDa6frkh4mPifb7tFrN66iyvJzqos5
GcxLbu1jlRUOptxbkyN873vf+/nPf/6RRx7Z2toqjCXKH7D/DD/+8Y/dTAc+1aTymTM5zuzo9vYP
fvCDq666yk7HXg1kb1NQofk5262rNJBedQfzklup0O3cAfvPUHWFdt2r66F2/fluFz2GnVHdSZ7q
dgPp1c52+zRv1113XTJp3jXXXJNkvvzlL9vVBx98MLma9JTf+9730kmSfHq1KzXfmf0j9eSTT37z
m9+84447bFo7vGS8w/+TsMFdO5/mTEc6H1LnW7evDPg8pMnV8+fPJ1dt9RSOpL9mUGbO5GoaSK92
GHR1/ulXd2DtdF3Sw8TnZNs9eu3Ge0OVdZ4zuZoG0qsdBl39qrLCwZR768zMjD3LJiPmt3/7tw8f
PpyPJcofsP8M+W/EbsenmlQ+cybHmRm013ZXX321PbtnvhGuZ1Ro5zmTq2kgvdph0EWFRluhXffy
OdTOP9/tosewA6o7zefXD9XdlVfzdu211yaTFkoyp06dskO0RWAHtLa29us7/L/EJk/Ne1paWrJp
b7/99uTqO9/5zl0dfxv4/vvvT0c6H1LnW7evDPg8pO62z0iQOfOBdoOuzj/96g6sna5Lepj4nGy7
R6/deElUWbpdOJIPtBt09avKCgdTmVs3NjbGx8c/9alP3XrrrTZuP/rC2HaIA+5ths58qknlM2f+
OB966KGrd4yMjLjjQVCh6XbhSD7QbtBFhWZGepuhM59qyuu6l8+hdv75bivnQnXn8z4jQebMB9oN
ujr/9Ks7sHa8mrfkFze7/iXBD33oQ7t23jf8l3/5F9v42Mc+5t6a/Dlg9236zqTT8Je8U5m2wl/9
6ld37XymMPPL1nb1jTtfwLpnz550sPMhdb51+8qAz0Oan7DzSJA5C39M+b3yOvz0qzuwdrou6WHi
c7L5h7fzeElUWYcZttss5vxeeX2psvxerna3Jr/uct111yVX8zOXP+DeZujMp5pUPnNmjjMpGXvQ
3P+vHxAV2mGG7aLlup3LFKJC3ZHeZujMp5ryuu7lc6jbHX++20WPYSGqO5WfsPNIkDkLf0z5vfI6
/PSrO7B2dvs0b7bCbMZbbrnlO9/5zvLy8sbGxurq6uHDh7/+9a+/6U1vSmMzMzO7dt6rTf58xNzc
nDPH9k033WSDk5OTnkeWP88e2JHYwrVDtQO2FtkOKfniHVuyScCW72tf+9pdOx8inJqasqvr6+t2
InZ118534LScryjtfEidb92+MuDzkOYn7DwSZM7CH1N+r7wOP/3qDqydrkt6mPicbP7h7TwuocrU
OQsXc36vvL5UWX4vl3vr7/3e742Pj58+fdqWwbe+9a1dzqcp8jOXP+DeZujMp5pUPnO6x/nFL35x
185ru29/+9tXpnpEhapz5pdrPlOICnVHepuhM59qyuu6l8+hbnf8+W4XPYZ5VHfnn37nkSBzFv6Y
8nvldfjpV3dg7ez2ad7M3/zN3yR3k5dmbAXceeedyR+dyP/Nwcz3Y2ZuTbkZVza3o8NNiSvn+Lmr
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
S3a9ArjS/w9VgMdaZn7aJwAAAABJRU5ErkJg" />
</BODY>
</HTML>
/trunk/OpenConcerto/src/org/openconcerto/utils/io/MailAccount.java
15,6 → 15,7
 
import org.openconcerto.utils.ExceptionUtils;
 
import java.util.Objects;
import java.util.Properties;
 
import javax.mail.Address;
23,6 → 24,8
import javax.mail.PasswordAuthentication;
import javax.mail.SendFailedException;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
 
/**
* A mail account.
107,6 → 110,20
return sb.toString();
}
 
public static final MailAccount create(final String fromAddr, String smtpServer, String smtpLogin, String smtpPassword) throws AddressException {
Objects.requireNonNull(fromAddr, "Missing 'From:' address");
final InternetAddress fromInetAddr = new InternetAddress(fromAddr, true);
if (smtpServer == null) {
smtpServer = "smtp." + fromInetAddr.getAddress().substring(fromInetAddr.getAddress().indexOf('@') + 1);
}
if (smtpLogin == null) {
smtpLogin = fromInetAddr.getAddress().substring(0, fromInetAddr.getAddress().indexOf('@'));
}
final MailAccount res = new MailAccount(fromInetAddr.getPersonal(), fromInetAddr.getAddress(), smtpServer);
res.setAuth(new PasswordAuthentication(smtpLogin, smtpPassword));
return res;
}
 
// nullable
private final String name;
private final String address;
182,7 → 199,8
 
public final void send(final Message msg) throws MessagingException {
final PasswordAuthentication auth = this.getAuth();
msg.getSession().getProperties().setProperty("mail." + getSMTPProtocol() + ".auth", auth == null ? "false" : "true");
final Properties props = msg.getSession().getProperties();
props.setProperty("mail." + getSMTPProtocol() + ".auth", auth == null ? "false" : "true");
try (final Transport mailTransport = msg.getSession().getTransport()) {
if (auth == null)
mailTransport.connect();
189,6 → 207,8
else
mailTransport.connect(auth.getUserName(), auth.getPassword());
mailTransport.sendMessage(msg, msg.getAllRecipients());
} catch (MessagingException e) {
throw new MessagingException("Couldn't send as " + auth.getUserName() + " using\n" + props, e);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/utils/io/JSONConverter.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
73,7 → 73,11
} else if (param instanceof Calendar) {
result = formatCalendar(((Calendar) param));
} else if (param instanceof Class<?>) {
if (param == String.class) {
result = "string";
} else {
result = ((Class<?>) param).getName();
}
} else if (param instanceof Iterable) {
final Iterable<?> tmp = (Iterable<?>) param;
final JSONArray jsonArray = new JSONArray();
82,14 → 86,11
}
result = jsonArray;
} else if (param instanceof Color) {
if (param != null) {
final Color paramColor = (Color) param;
final JSONObject jsonColor = new JSONObject();
jsonColor.put("r", paramColor.getRed());
jsonColor.put("g", paramColor.getGreen());
jsonColor.put("b", paramColor.getBlue());
result = jsonColor;
String hexString = Integer.toHexString(((Color) param).getRGB());
if (hexString.length() > 6) {
hexString = hexString.substring(2, hexString.length());
}
result = "#" + hexString;
} else if (param instanceof BigDecimal) {
result = ((BigDecimal) param).doubleValue();
} else {
132,11 → 133,7
throw new IllegalArgumentException("object (" + o.getClass().getName() + ") is not assignable for '" + type + "', the format is not valid", e);
}
} else if (type.equals(Color.class)) {
final JSONObject jsonColor = (JSONObject) o;
final int r = JSONConverter.getParameterFromJSON(jsonColor, "r", Integer.class);
final int g = JSONConverter.getParameterFromJSON(jsonColor, "g", Integer.class);
final int b = JSONConverter.getParameterFromJSON(jsonColor, "b", Integer.class);
result = type.cast(new Color(r, g, b));
result = type.cast(Color.decode(o.toString()));
} else {
result = type.cast(o);
}
/trunk/OpenConcerto/src/org/openconcerto/utils/SystemInfo.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
47,6 → 47,8
}
 
public static final String CLASS_PROTOCOL = "class";
public static final String PROPS_PROTOCOL = "props";
public static final String ENV_PROTOCOL = "env";
 
static public NavigableMap<Info, String> get(final boolean html, final Locale locale) {
final TM tm = TM.getInstance(locale);
67,12 → 69,22
e1.printStackTrace();
}
final Runtime rt = Runtime.getRuntime();
final String stats = "<i>" + tm.translate("memory") + " :</i> " + formatBytes(tm, rt.freeMemory()) + " / " + formatBytes(tm, rt.totalMemory()) + " ; "
final long totalMemory = rt.totalMemory();
final String stats = "<i>" + tm.translate("memory.used") + " :</i> " + formatBytes(tm, totalMemory - rt.freeMemory()) + " / " + formatBytes(tm, totalMemory) + " ; "
+ tm.translate("processors", rt.availableProcessors());
final String lafDesc = lookAndFeel == null ? tm.translate("no.laf") : getLink(lookAndFeel.getName(), lafURI, html) + ", " + lookAndFeel.getDescription();
URI propsURI = null;
URI envURI = null;
try {
propsURI = new URI(PROPS_PROTOCOL, "/", null);
envURI = new URI(ENV_PROTOCOL, "/", null);
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
final String propsDesc = envURI == null ? "" : lineBreak + getLink(tm.translate("properties.all"), propsURI, html) + ", " + getLink(tm.translate("env.all"), envURI, html);
 
final String p = tm.translate("javaVersion", version, getLink(getProperty("java.vendor"), vendorURI, html)) + " ; "
+ getLink(tm.translate("javaHome"), new File(getProperty("java.home")).toURI(), html) + lineBreak + stats + lineBreak + lafDesc;
+ getLink(tm.translate("javaHome"), new File(getProperty("java.home")).toURI(), html) + lineBreak + stats + lineBreak + lafDesc + propsDesc;
 
res.put(Info.JAVA, p);
 
/trunk/OpenConcerto/src/org/openconcerto/utils/change/ListChangeRecorder.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
21,7 → 21,7
 
/**
* A class that wraps a list, to detect every change made to it. The changes are available with
* {@link #getRecipe()}.
* {@link #getRecipe()}. Thread-safe if the delegate is itself thread-safe and not keeping history.
*
* @author Sylvain
*
32,11 → 32,11
private final List<E> delegate;
private final ListChangeRecipe<E> recipe;
 
public ListChangeRecorder(List<E> delegate) {
public ListChangeRecorder(final List<E> delegate) {
this(delegate, false);
}
 
public ListChangeRecorder(List<E> delegate, final boolean keepHistory) {
public ListChangeRecorder(final List<E> delegate, final boolean keepHistory) {
super();
this.delegate = delegate;
this.recipe = new ListChangeRecipe<E>(keepHistory);
52,47 → 52,58
 
// ** read only
 
public E get(int index) {
@Override
public E get(final int index) {
return this.delegate.get(index);
}
 
@Override
public int size() {
return this.delegate.size();
}
 
@Override
public Object[] toArray() {
return this.delegate.toArray();
}
 
public <T> T[] toArray(T[] a) {
@Override
public <T> T[] toArray(final T[] a) {
return this.delegate.toArray(a);
}
 
public boolean contains(Object o) {
@Override
public boolean contains(final Object o) {
return this.delegate.contains(o);
}
 
public boolean containsAll(Collection<?> c) {
@Override
public boolean containsAll(final Collection<?> c) {
return this.delegate.containsAll(c);
}
 
public boolean equals(Object o) {
@Override
public boolean equals(final Object o) {
return this.delegate.equals(o);
}
 
@Override
public int hashCode() {
return this.delegate.hashCode();
}
 
public int indexOf(Object o) {
@Override
public int indexOf(final Object o) {
return this.delegate.indexOf(o);
}
 
@Override
public boolean isEmpty() {
return this.delegate.isEmpty();
}
 
public int lastIndexOf(Object o) {
@Override
public int lastIndexOf(final Object o) {
return this.delegate.lastIndexOf(o);
}
 
99,7 → 110,8
// ** write, always change the delegate before notifying the recipe
// otherwise the listeners will be told of the changes before they even happened
 
public boolean add(E e) {
@Override
public synchronized boolean add(final E e) {
final boolean res = this.delegate.add(e);
// -1 since this just grew by one
this.recipe.add(this.size() - 1, Collections.singleton(e));
106,12 → 118,14
return res;
}
 
public void add(int index, E e) {
@Override
public synchronized void add(final int index, final E e) {
this.delegate.add(index, e);
this.recipe.add(index, Collections.singleton(e));
}
 
public boolean addAll(Collection<? extends E> c) {
@Override
public synchronized boolean addAll(final Collection<? extends E> c) {
final int size = this.size();
final boolean res = this.delegate.addAll(c);
this.recipe.add(size, c);
118,25 → 132,29
return res;
}
 
public boolean addAll(int index, Collection<? extends E> c) {
@Override
public synchronized boolean addAll(final int index, final Collection<? extends E> c) {
final boolean res = this.delegate.addAll(index, c);
this.recipe.add(index, c);
return res;
}
 
public void clear() {
@Override
public synchronized void clear() {
final List<E> copy = (List<E>) ListChangeIndex.copy(this);
this.delegate.clear();
this.recipe.remove(0, copy.size() - 1, copy);
}
 
public E set(int index, E element) {
@Override
public synchronized E set(final int index, final E element) {
final E res = this.delegate.set(index, element);
this.recipe.set(index, res, element);
return res;
}
 
public E remove(int index) {
@Override
public synchronized E remove(final int index) {
final E res = this.delegate.remove(index);
this.recipe.remove(index, index, Collections.singletonList(res));
return res;
144,7 → 162,8
 
// objects
 
public boolean remove(Object o) {
@Override
public synchronized boolean remove(final Object o) {
final int index = this.indexOf(o);
if (index < 0)
return false;
154,17 → 173,19
}
}
 
public boolean removeAll(Collection<?> c) {
@Override
public boolean removeAll(final Collection<?> c) {
return this.changeAll(c, true);
}
 
public boolean retainAll(Collection<?> c) {
@Override
public boolean retainAll(final Collection<?> c) {
return this.changeAll(c, false);
}
 
private boolean changeAll(Collection<?> c, boolean remove) {
private synchronized boolean changeAll(final Collection<?> c, final boolean remove) {
boolean modified = false;
Iterator<?> e = iterator();
final Iterator<?> e = iterator();
while (e.hasNext()) {
if (c.contains(e.next()) == remove) {
e.remove();
/trunk/OpenConcerto/src/org/openconcerto/utils/change/ListChangeIndex.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
23,12 → 23,12
 
public abstract class ListChangeIndex<T> implements ListChange<T> {
 
protected final static <T> Collection<T> copy(Collection<T> col) {
protected final static <T> Collection<T> copy(final Collection<T> col) {
Collection<T> res;
try {
// tries to keep the same class
res = CopyUtils.copy(col);
} catch (RuntimeException e) {
} catch (final RuntimeException e) {
// but this doesn't always work (see sublist())
// so just use a plain ArrayList
res = new ArrayList<T>(col);
39,7 → 39,7
private final int index0;
private final int index1;
 
public ListChangeIndex(int index0, int index1) {
public ListChangeIndex(final int index0, final int index1) {
super();
this.index0 = index0;
this.index1 = index1;
61,7 → 61,7
 
private final List<T> removed;
 
public Rm(int index0, int index1, final List<T> removed) {
public Rm(final int index0, final int index1, final List<T> removed) {
super(index0, index1);
// ok to cast : either it copies it : List<T>, either it uses an arrayList which
// implements List<T>
68,15 → 68,18
this.removed = (List<T>) copy(removed);
}
 
public <U> void apply(List<U> l, ITransformer<T, U> transf) {
@Override
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) {
// sublist exclusive
l.subList(this.getIndex0(), this.getIndex1() + 1).clear();
}
 
@Override
public List<? extends T> getItemsAdded() {
return Collections.emptyList();
}
 
@Override
public List<T> getItemsRemoved() {
return this.removed;
}
91,12 → 94,13
 
private final Collection<? extends T> added;
 
public Add(int index0, Collection<? extends T> added) {
public Add(final int index0, final Collection<? extends T> added) {
super(index0, index0);
this.added = copy(added);
}
 
public <U> void apply(List<U> l, ITransformer<T, U> transf) {
@Override
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) {
final List<U> toAdd = new ArrayList<U>();
for (final T t : this.added) {
toAdd.add(transf.transformChecked(t));
104,10 → 108,12
l.addAll(this.getIndex0(), toAdd);
}
 
@Override
public Collection<? extends T> getItemsAdded() {
return this.added;
}
 
@Override
public List<? extends T> getItemsRemoved() {
return Collections.emptyList();
}
123,20 → 129,23
private final T removed;
private final T added;
 
public Set(int index, T removed, T added) {
public Set(final int index, final T removed, final T added) {
super(index, index);
this.removed = removed;
this.added = added;
}
 
public <U> void apply(List<U> l, ITransformer<T, U> transf) {
@Override
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) {
l.set(this.getIndex0(), transf.transformChecked(this.added));
}
 
@Override
public List<? extends T> getItemsAdded() {
return Collections.singletonList(this.added);
}
 
@Override
public List<? extends T> getItemsRemoved() {
return Collections.singletonList(this.removed);
}
/trunk/OpenConcerto/src/org/openconcerto/utils/change/ListChangeRecipe.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
22,7 → 22,10
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
 
import net.jcip.annotations.GuardedBy;
 
/**
* Allow to propagate ListChange to listeners and bound lists. Can also store all changes that it
* was notified and replay them with {@link #apply(List, ITransformer)}.
39,10 → 42,12
// don't use PropertyChangeSupport since it isn't type safe and we're only interested in the
// last change (and not the whole property, i.e. the whole list)
private final List<IClosure<? super ListChangeIndex<T>>> listeners;
@GuardedBy("this")
private final Map<List<?>, Pair<?>> boundLists;
 
/**
* Create a new instance. Recording is only necessary for {@link #apply(List, ITransformer)}.
* Thread-safe only if not recording.
*
* @param record <code>true</code> if all changes should be kept (this will leak memory until
* {@link #clear()} is called).
50,7 → 55,7
public ListChangeRecipe(final boolean record) {
super();
this.changes = record ? new ArrayList<ListChangeIndex<T>>() : null;
this.listeners = new ArrayList<IClosure<? super ListChangeIndex<T>>>();
this.listeners = new CopyOnWriteArrayList<>();
// need IdentityHashMap since List.equals() depend on its items
// which will change
this.boundLists = new IdentityHashMap<List<?>, Pair<?>>();
60,6 → 65,11
return this.changes != null;
}
 
/**
* The list of changes since the last {@link #clear()}. Not thread-safe.
*
* @return the list of changes
*/
public final List<ListChangeIndex<T>> getChanges() {
if (!this.recordChanges())
throw new IllegalStateException("This instance wasn't created to record changes");
66,15 → 76,15
return this.changes;
}
 
public void addListener(IClosure<? super ListChangeIndex<T>> l) {
public void addListener(final IClosure<? super ListChangeIndex<T>> l) {
this.listeners.add(l);
}
 
public void rmListener(IClosure<? super ListChangeIndex<T>> l) {
public void rmListener(final IClosure<? super ListChangeIndex<T>> l) {
this.listeners.remove(l);
}
 
public void bind(List<T> l) {
public void bind(final List<T> l) {
this.bind(l, Transformer.<T> nopTransformer());
}
 
85,18 → 95,19
* @param l the list to keep in sync.
* @param transf the transformer.
*/
public <U> void bind(List<U> l, ITransformer<T, U> transf) {
public synchronized <U> void bind(final List<U> l, final ITransformer<T, U> transf) {
this.boundLists.put(l, new Pair<U>(l, transf));
}
 
public <U> void unbind(List<U> l) {
public synchronized <U> void unbind(final List<U> l) {
this.boundLists.remove(l);
}
 
private final void add(ListChangeIndex<T> change) {
private final void add(final ListChangeIndex<T> change) {
if (this.recordChanges())
this.changes.add(change);
// must change bounded lists first, otherwise listeners couldn't access them
synchronized (this) {
for (final Pair<?> p : this.boundLists.values()) {
p.apply(change);
}
103,16 → 114,17
for (final IClosure<? super ListChangeIndex<T>> l : this.listeners)
l.executeChecked(change);
}
}
 
public void add(int index0, Collection<? extends T> c) {
public void add(final int index0, final Collection<? extends T> c) {
this.add(new ListChangeIndex.Add<T>(index0, c));
}
 
public void remove(int index0, int index1, List<T> removed) {
public void remove(final int index0, final int index1, final List<T> removed) {
this.add(new ListChangeIndex.Rm<T>(index0, index1, removed));
}
 
public void set(int index0, T old, T newItem) {
public void set(final int index0, final T old, final T newItem) {
this.add(new ListChangeIndex.Set<T>(index0, old, newItem));
}
 
134,7 → 146,7
* changes}.
*/
@Override
public <U> void apply(List<U> l, ITransformer<T, U> transf) throws IllegalStateException {
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) throws IllegalStateException {
for (final ListChange<T> change : this.getChanges()) {
change.apply(l, transf);
}
145,13 → 157,13
private final List<U> l;
private final ITransformer<T, U> transf;
 
public Pair(List<U> l, ITransformer<T, U> transf) {
public Pair(final List<U> l, final ITransformer<T, U> transf) {
super();
this.l = l;
this.transf = transf;
}
 
void apply(ListChange<T> change) {
void apply(final ListChange<T> change) {
change.apply(this.l, this.transf);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cache/ICacheSupport.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
13,12 → 13,12
package org.openconcerto.utils.cache;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.IScheduledFutureTask;
 
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
178,7 → 178,7
}
 
final synchronized Map<D, CacheWatcher<? super D>> watch(Set<? extends D> data, final CacheItem<?, ?, D> item) {
final Map<D, CacheWatcher<? super D>> res = new LinkedHashMap<D, CacheWatcher<? super D>>(data.size(), 1.0f);
final Map<D, CacheWatcher<? super D>> res = CollectionUtils.newLinkedHashMap(data.size());
for (final D d : data) {
final CacheWatcher<? super D> watcher = this.watch(d, item);
if (watcher != null)
/trunk/OpenConcerto/src/org/openconcerto/utils/cache/ICache.java
32,6 → 32,8
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
 
import net.jcip.annotations.GuardedBy;
 
/**
* To keep results computed from some data. The results will be automatically invalidated after some
* period of time or when the data is modified.
52,7 → 54,9
private final ICacheSupport<D> supp;
// linked to fifo, ATTN the values in this map can be invalid since clear() is called without
// the lock on CacheValue
@GuardedBy("this")
private final LinkedHashMap<K, CacheItem<K, V, D>> cache;
@GuardedBy("this")
private final Map<K, CacheItem<K, V, D>> running;
private final int delay;
private final int size;
224,17 → 228,21
removeRunning(getRunningValFromRes(res));
}
 
private final synchronized void removeRunning(final CacheItem<K, V, D> val) {
private final synchronized boolean removeRunning(final CacheItem<K, V, D> val) {
if (val == null)
return;
return false;
final K key = val.getKey();
if (this.running.get(key) == val)
final boolean removed;
if (this.running.get(key) == val) {
this.removeRunning(key);
else
removed = true;
} else {
// either val wasn't created in this cache or another value was already put in this
// cache
val.setRemovalType(RemovalType.EXPLICIT);
removed = val.setRemovalType(RemovalType.EXPLICIT);
}
assert val.getRemovalType() != null;
return removed;
}
 
private final synchronized void removeRunning(K key) {
427,10 → 435,10
final boolean clear(final CacheItem<K, V, D> val) {
if (val.getRemovalType() == null)
throw new IllegalStateException("Not yet removed : " + val);
final boolean toBeRemoved;
final boolean removedFromRunning, toBeRemoved;
synchronized (this) {
log("clear", val);
this.removeRunning(val);
removedFromRunning = this.removeRunning(val);
toBeRemoved = this.cache.get(val.getKey()) == val;
if (toBeRemoved) {
this.cache.remove(val.getKey());
437,18 → 445,28
}
}
// NOTE these events are often fired with our monitor since this method is called with it
if (removedFromRunning || toBeRemoved) {
this.propSupp.firePropertyChange(new Event<K, V, D>(this, ITEMS_CHANGED, null, null));
this.propSupp.firePropertyChange(this.createItemEvent(ITEM_REMOVED, val, null));
}
return toBeRemoved;
}
 
public final synchronized void clear() {
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.cache.values()))
val.setRemovalType(RemovalType.EXPLICIT);
assert this.size() == 0;
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.cache.values())) {
// We have our monitor so if val is still in us but setRemovalType() was already called,
// then it means another thread is waiting on our monitor in clear(CacheItem). In that
// case, just call it now so that this is empty at the end of this method.
if (!val.setRemovalType(RemovalType.EXPLICIT)) {
final boolean removed = this.clear(val);
assert removed;
}
}
assert this.size() == 0 : this + " expected to be empty but contains : " + this.cache.keySet();
 
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.running.values()))
val.setRemovalType(RemovalType.EXPLICIT);
assert this.running.size() == 0;
assert this.running.size() == 0 : this + " expected to have no running but contains : " + this.running.keySet();
}
 
private final void log(String msg, Object subject) {
/trunk/OpenConcerto/src/org/openconcerto/utils/prog/ExtractFromBEncoding.java
New file
0,0 → 1,234
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.prog;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.text.CSVReader;
 
import java.io.IOException;
import java.io.Writer;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
 
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.ParseException;
 
import com.zimbra.common.util.BEncoding;
import com.zimbra.common.util.BEncoding.BEncodingException;
 
/**
* Load a CSV file and replace some {@link BEncoding zimbra benconded map} columns by some of their
* toplevel values. Sample SQL to generate the CSV :
*
* <pre>
*SELECT 'ID', 'sujet', 'date', 'metadonnées'
UNION ALL
SELECT id, subject, from_unixtime(floor(date)), metadata
FROM mail_item
WHERE mailbox_id=70 and type=5
ORDER BY date DESC
INTO OUTFILE '/tmp/zimbraMails.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\\' LINES TERMINATED BY '\n' ;
* </pre>
*
* @author sylvain
*/
public class ExtractFromBEncoding {
 
static public final class ExtractInfo {
private final int fieldIndex;
private final List<String> keys, names;
 
public ExtractInfo(int fieldIndex, String[] keysAndNames) {
super();
this.fieldIndex = fieldIndex;
final List<String> keys = new ArrayList<>(keysAndNames.length);
final List<String> names = new ArrayList<>(keysAndNames.length);
for (final String kAn : keysAndNames) {
final int colonIndex = kAn.indexOf(':');
keys.add(kAn.substring(0, colonIndex));
names.add(kAn.substring(colonIndex + 1));
}
this.keys = Collections.unmodifiableList(keys);
this.names = Collections.unmodifiableList(names);
assert this.keys.size() > 0 && this.keys.size() == this.names.size();
}
 
public final int getFieldIndex() {
return this.fieldIndex;
}
 
public final List<String> getKeys() {
return this.keys;
}
 
public final List<String> getNames() {
return this.names;
}
}
 
private static final boolean OVERWRITE_FILE = Boolean.getBoolean("overwrite");
private static final Pattern DOUBLE_QUOTE_PATTERN = Pattern.compile("\"", Pattern.LITERAL);
private static final Pattern SINGLE_QUOTED_ENCODEDWORD_PATTERN = Pattern.compile("'(=\\?.+?\\?=)'");
 
public static void main(String[] args) throws IOException, BEncodingException, ParseException {
if (args.length == 0) {
System.out.println("Load a CSV file and replace some {@link BEncoding zimbra benconded map} columns by some of their toplevel values");
System.out.println("The bencoded colums are removed and the new columns are added at the end");
System.out.println("inputFile outputFile [bencodedIndex key1:label1,key2:label2,...]...");
System.exit(1);
}
 
final Path in = Paths.get(args[0]);
final Path out = Paths.get(args[1]);
if (Files.exists(out) && Files.isSameFile(in, out))
throw new IllegalArgumentException("Same file");
 
final List<ExtractInfo> extractInfos = new ArrayList<>();
// can't have the same index more than one time because we remove it
final Set<Integer> indexes = new HashSet<>();
for (int i = 2; i < args.length; i += 2) {
final int index = Integer.parseInt(args[i]);
if (!indexes.add(index))
throw new IllegalArgumentException("Duplicate index " + index);
final String[] keys = args[i + 1].split(",");
extractInfos.add(new ExtractInfo(index, keys));
}
 
System.out.println("From " + in + " to " + out);
final int lines = replace(in, out, extractInfos);
System.out.println("Processed " + lines + " line(s)");
}
 
/**
* Read from <code>in</code> and write to <code>out</code>.
*
* @param in the input CSV.
* @param out the output CSV.
* @param extractInfos what to extract.
* @return number of processed lines.
* @throws IOException if an error occurs while reading or writing.
* @throws BEncodingException if a value isn't b-encoded.
* @throws ParseException if a value isn't a valid "encoded-word".
*/
public static int replace(final Path in, final Path out, final List<ExtractInfo> extractInfos) throws IOException, ParseException, BEncodingException {
if (extractInfos.isEmpty()) {
Files.copy(in, out, OVERWRITE_FILE ? new CopyOption[] { StandardCopyOption.REPLACE_EXISTING } : new CopyOption[0]);
return 0;
}
 
int totalLines = 0;
final OpenOption[] openOptions = OVERWRITE_FILE ? new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING } : new OpenOption[] { StandardOpenOption.CREATE_NEW };
final StringBuilder sb = new StringBuilder(512);
try (final CSVReader r = new CSVReader(Files.newBufferedReader(in), ',', '"', '\\', 0, true);
// CSVWriter doesn't handle escaping quotes by doubling them (as needed by
// LibreOffice), so output CSV ourselves
final Writer w = Files.newBufferedWriter(out, openOptions)) {
 
// column names
String[] line = r.readNext();
final int itemsCount = line.length;
 
int additionalCols = 0;
 
final List<String> listToWrite = new ArrayList<>(itemsCount);
listToWrite.addAll(Arrays.asList(line));
for (final ExtractInfo e : extractInfos) {
listToWrite.remove(e.getFieldIndex());
for (final String n : e.getNames()) {
listToWrite.add(n);
}
// we remove the metadata
additionalCols += e.getKeys().size() - 1;
}
final int outputColCount = itemsCount + additionalCols;
writeCSV(sb, listToWrite, outputColCount);
w.append(sb);
 
while ((line = r.readNext()) != null) {
totalLines++;
if (itemsCount != line.length) {
throw new IllegalStateException("Expected " + itemsCount + " items but got " + line.length + " : " + Arrays.asList(line));
}
listToWrite.clear();
listToWrite.addAll(Arrays.asList(line));
 
for (final ExtractInfo e : extractInfos) {
String item = line[e.getFieldIndex()];
if (item.startsWith("=?")) {
item = MimeUtility.decodeWord(item);
}
final Map<?, ?> bdecoded = (Map<?, ?>) BEncoding.decode(item);
 
listToWrite.remove(e.getFieldIndex());
for (final String key : e.getKeys()) {
// BEncoding cannot encode null but a key can be missing
final Object val = bdecoded.get(key);
final String itemToWrite;
if (val == null) {
itemToWrite = null;
} else {
String toParse = val.toString();
// illegal single quoting, replace with double quote
if (toParse.contains("'=?"))
toParse = SINGLE_QUOTED_ENCODEDWORD_PATTERN.matcher(toParse).replaceAll("\"$1\"");
// strict=false because we don't care if there's "illegal character"
// (e.g. annabelle@testaud@ac-paris.fr or
// <'jeromebarral83@yahoo.fr'>), we want it to be legible
final InternetAddress[] mimeDecoded = InternetAddress.parseHeader(toParse, false);
 
itemToWrite = CollectionUtils.join(Arrays.asList(mimeDecoded), ", ", InternetAddress::toUnicodeString);
}
listToWrite.add(itemToWrite);
}
}
 
writeCSV(sb, listToWrite, outputColCount);
w.append(sb);
}
}
return totalLines;
}
 
private static void writeCSV(final StringBuilder sb, final List<String> l, final int outputColCount) {
if (l.size() != outputColCount)
throw new IllegalStateException("Wrong column count");
sb.setLength(0);
for (final String i : l) {
if (i == null) {
sb.append("\\N");
} else {
sb.append('"');
sb.append(DOUBLE_QUOTE_PATTERN.matcher(i).replaceAll("\"\""));
sb.append('"');
}
sb.append(',');
}
// replace last field separator
sb.setCharAt(sb.length() - 1, '\n');
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/BaseDirs.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
35,6 → 35,18
super(info, subdir);
}
 
private String getHomePath() {
// ATTN on FreeBSD man 8 service : "service command sets HOME to /" thus one should
// probably not use this class (perhaps Portable).
return StringUtils.coalesce(System.getenv("HOME"), System.getProperty("user.home"));
}
 
private String getInHomePath(final String subPath) {
final String homePath = getHomePath();
// If no home, use current working directory
return homePath == null ? subPath : homePath + '/' + subPath;
}
 
@Override
protected File _getAppDataFolder() {
/*
42,7 → 54,7
* should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
* $HOME/.local/share should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_DATA_HOME"), System.getenv("HOME") + "/.local/share"), this.getAppID());
return new File(StringUtils.coalesce(System.getenv("XDG_DATA_HOME"), getInHomePath(".local/share")), this.getAppID());
}
 
@Override
52,7 → 64,7
* configuration files should be stored. If $XDG_CONFIG_HOME is either not set or empty,
* a default equal to $HOME/.config should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_CONFIG_HOME"), System.getenv("HOME") + "/.config"), this.getAppID());
return new File(StringUtils.coalesce(System.getenv("XDG_CONFIG_HOME"), getInHomePath(".config")), this.getAppID());
}
 
@Override
62,7 → 74,7
* non-essential data files should be stored. If $XDG_CACHE_HOME is either not set or
* empty, a default equal to $HOME/.cache should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_CACHE_HOME"), System.getenv("HOME") + "/.cache"), this.getAppID());
return new File(StringUtils.coalesce(System.getenv("XDG_CACHE_HOME"), getInHomePath(".cache")), this.getAppID());
}
}
 
211,7 → 223,8
return res != null ? res : this.getAppID();
}
 
protected File getFolderToWrite(final File dir) throws IOException {
static public final File getFolderToWrite(final File dir) throws IOException {
// MAYBE test for symlink
if (dir.isDirectory() && dir.canWrite())
return dir;
if (dir.exists())
246,7 → 259,7
}
 
protected File _getPreferencesFolder() {
return this.getAppDataFolder();
return this._getAppDataFolder();
}
 
// where to write configuration
271,6 → 284,23
return getFolderToWrite(this.getCacheFolder());
}
 
protected File _getStateFolder() {
return this._getCacheFolder();
}
 
// where to write data that is non-essential but cannot be recreated
// - logfiles
// - state of application windows on exit
// - recently opened files
// See STATE directory in https://wiki.debian.org/XDGBaseDirectorySpecification
public final File getStateFolder() {
return getSubDir(_getStateFolder());
}
 
public final File getStateFolderToWrite() throws IOException {
return getFolderToWrite(this.getStateFolder());
}
 
@Override
public String toString() {
return BaseDirs.class.getSimpleName() + " " + this.getClass().getSimpleName();
/trunk/OpenConcerto/src/org/openconcerto/utils/ProductInfo.java
30,9 → 30,12
public static final String PROPERTIES_NAME = "/product.properties";
public static final String ORGANIZATION_NAME = "ORGANIZATION_NAME";
public static final String ORGANIZATION_ID = "ORGANIZATION_ID";
// e.g. LibreOffice
public static final String NAME = "NAME";
public static final String ID = "ID";
public static final String VERSION = "VERSION";
// Allow multiple end-user applications (e.g. icons) to share a product name, e.g. Writer
public static final String LAUNCHER = "LAUNCHER";
 
private static ProductInfo INSTANCE;
 
156,6 → 159,18
return orgID == null ? null : orgID + '.' + this.getID();
}
 
public final String getLauncher() {
return this.getProperty(LAUNCHER);
}
 
public final String getFullLauncherID() {
final String launcher = this.getLauncher();
final String fullID = this.getFullID();
if (launcher == null || fullID == null)
return fullID;
return fullID + '-' + sanitizeDomainPart(launcher);
}
 
public final String getVersion() {
return this.getProperty(VERSION);
}
162,10 → 177,14
 
public final void store(final Path p) throws IOException {
try (final OutputStream out = Files.newOutputStream(p)) {
this.getProps().store(out, this.getClass().getName());
store(out);
}
}
 
public final void store(final OutputStream out) throws IOException {
this.getProps().store(out, this.getClass().getName());
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " for " + getName() + " " + getVersion();
/trunk/OpenConcerto/src/org/openconcerto/utils/jsonrpc/JSONRPCClient.java
New file
0,0 → 1,125
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.jsonrpc;
 
import org.openconcerto.utils.Base64;
import org.openconcerto.utils.NetUtils;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
 
import javax.net.ssl.HttpsURLConnection;
 
import net.minidev.json.JSONObject;
import net.minidev.json.JSONStyle;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
 
public class JSONRPCClient {
private boolean trustAllContext;
private String token;
private String login;
private String encodedPassword;
 
public JSONRPCClient(boolean trustAllContext) {
this.trustAllContext = trustAllContext;
}
 
public void setAuthToken(String token) {
this.token = token;
}
 
/**
* @param login : login
* @param encodedPassword : Base64 encoded hash of the password
*/
public void setCredentials(String login, String encodedPassword) {
this.login = login;
this.encodedPassword = encodedPassword;
}
 
public Object rpcCall(String url, String method, JSONObject params) throws IOException {
final JSONObject request = new JSONObject();
request.put("jsonrpc", "2.0");
request.put("method", method);
request.put("id", UUID.randomUUID().toString());
request.put("params", params);
 
final HttpURLConnection con;
if (url.startsWith("https")) {
HttpsURLConnection cons = (HttpsURLConnection) new URL(url).openConnection();
if (this.trustAllContext) {
try {
cons.setSSLSocketFactory(NetUtils.createTrustAllContext().getSocketFactory());
cons.setHostnameVerifier(NetUtils.HostnameNonVerifier);
} catch (KeyManagementException | NoSuchAlgorithmException e1) {
throw new IOException(e1);
}
}
con = cons;
} else {
con = (HttpURLConnection) new URL(url).openConnection();
}
con.setRequestProperty("Accept-Encoding", "gzip");
con.setDoOutput(true);
 
final String str = request.toJSONString(JSONStyle.NO_COMPRESS);
 
final byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
 
con.setRequestProperty("Content-Type", "application/json; charset=utf-8");
if (this.token != null) {
con.setRequestProperty("Authorization", "Bearer " + this.token);
} else {
// X_LOGIN and X_PASSWORD_HASH are Base64 encoded
if (this.login != null) {
con.setRequestProperty("X_LOGIN", Base64.encodeBytes(this.login.getBytes(StandardCharsets.UTF_8)));
}
if (this.encodedPassword != null) {
con.setRequestProperty("X_PASSWORD_HASH", this.encodedPassword);
}
}
 
try (final OutputStream out = con.getOutputStream()) {
out.write(bytes);
}
 
final JSONParser p = new JSONParser(JSONParser.MODE_PERMISSIVE);
 
try (InputStream inputStream = "gzip".equals(con.getContentEncoding()) ? new GZIPInputStream(con.getInputStream()) : con.getInputStream()) {
final JSONObject response = (JSONObject) p.parse(inputStream);
if (response.containsKey("error")) {
final JSONObject jsonObject = (JSONObject) response.get("error");
if (jsonObject.getAsNumber("code").intValue() == -32601) {
throw new IOException("method not found on server :" + method + " code : " + jsonObject.getAsNumber("code") + " : " + jsonObject.getAsString("message"));
} else {
throw new IOException("rpc call error, code : " + jsonObject.getAsNumber("code") + " : " + jsonObject.getAsString("message"));
}
}
return response.get("result");
} catch (final ParseException e) {
throw new IOException(e);
}
 
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/IExnRunnable.java
New file
0,0 → 1,20
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.cc;
 
public interface IExnRunnable<X extends Exception> {
 
public abstract void run() throws X;
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/Cookies.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
21,6 → 21,7
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
 
import com.google.gson.reflect.TypeToken;
 
187,6 → 188,21
return res;
}
 
public final <T> Collection<T> computeCollectionIfAbsent(Object k, Class<T> clazz, Function<Object, ? extends Collection<T>> mappingFunction) {
Collection<T> res;
synchronized (this) {
res = this.getCollection(k, clazz);
// same algorithm as java.util.Map
if (res == null) {
res = mappingFunction.apply(k);
if (res != null)
this.putCollection(k, res, clazz);
}
}
return res;
}
 
 
public final <T> List<T> getList(Object k, Class<T> clazz) {
return (List<T>) this.getCollection(k, clazz);
}
202,6 → 218,20
return res;
}
 
public final <K, V> Map<K, V> computeMapIfAbsent(Object k, Class<K> keyClass, Class<V> valueClass, Function<Object, ? extends Map<K, V>> mappingFunction) {
Map<K, V> res;
synchronized (this) {
res = this.getMap(k, keyClass, valueClass);
// same algorithm as java.util.Map
if (res == null) {
res = mappingFunction.apply(k);
if (res != null)
this.putMap(k, res, keyClass, valueClass);
}
}
return res;
}
 
public final <T> T getGeneric(Object k, TypeToken<T> clazz) {
final Object object = checkType(k, clazz, "putGeneric()");
@SuppressWarnings("unchecked")
209,6 → 239,20
return res;
}
 
public final <T> T computeIfAbsent(Object k, TypeToken<T> clazz, Function<Object, ? extends T> mappingFunction) {
T res;
synchronized (this) {
res = this.getGeneric(k, clazz);
// same algorithm as java.util.Map
if (res == null) {
res = mappingFunction.apply(k);
if (res != null)
this.putGeneric(k, res, clazz);
}
}
return res;
}
 
@Override
public synchronized String toString() {
return this.getClass().getSimpleName() + " of " + this.map.keySet();
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/LinkedIdentitySet.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
13,12 → 13,8
package org.openconcerto.utils.cc;
 
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
 
/**
* An IdentitySet maintaining insertion order.
26,119 → 22,25
* @param <E> the type of elements maintained by this set
* @see IdentityHashSet
*/
public final class LinkedIdentitySet<E> extends AbstractSet<E> implements IdentitySet<E>, Cloneable {
public final class LinkedIdentitySet<E> extends ListIdentitySet<E> implements Cloneable {
 
private final LinkedList<E> list;
 
public LinkedIdentitySet() {
this.list = new LinkedList<E>();
super();
}
 
public LinkedIdentitySet(Collection<? extends E> c) {
this.list = new LinkedList<E>();
this.addAll(c);
public LinkedIdentitySet(int expectedSize) {
super(expectedSize);
}
 
public final List<E> getList() {
return Collections.unmodifiableList(this.list);
public LinkedIdentitySet(Collection<? extends E> c) {
super(c);
}
 
@Override
public boolean add(E e) {
if (this.contains(e))
return false;
else {
this.list.add(e);
return true;
protected LinkedList<E> newList(int expectedSize) {
return new LinkedList<>();
}
}
 
@Override
public boolean addAll(Collection<? extends E> c) {
// let the super which calls add() since we don't want to build a map to use Map.addAll()
return super.addAll(c);
}
 
@Override
public void clear() {
this.list.clear();
}
 
@Override
public int size() {
return this.list.size();
}
 
@Override
public boolean isEmpty() {
return this.list.isEmpty();
}
 
@Override
public Iterator<E> iterator() {
return this.list.iterator();
}
 
@Override
public boolean contains(Object o) {
Iterator<E> e = iterator();
while (e.hasNext())
if (o == e.next())
return true;
return false;
}
 
@Override
public boolean containsAll(Collection<?> c) {
// let the super which calls contains()
return super.containsAll(c);
}
 
@Override
public boolean remove(Object o) {
Iterator<E> e = iterator();
while (e.hasNext()) {
if (o == e.next()) {
e.remove();
return true;
}
}
return false;
}
 
/*
* (From IdentityHashMap) Must revert from AbstractSet's impl to AbstractCollection's, as the
* former contains an optimization that results in incorrect behavior when c is a smaller
* "normal" (non-identity-based) Set.
*/
@Override
public boolean removeAll(Collection<?> c) {
boolean modified = false;
for (Iterator<E> i = iterator(); i.hasNext();) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
return modified;
}
 
@Override
public boolean retainAll(Collection<?> c) {
// let the super which calls remove()
return super.retainAll(c);
}
 
@Override
public Object[] toArray() {
return this.list.toArray();
}
 
@Override
public <T> T[] toArray(T[] a) {
return this.list.toArray(a);
}
 
/**
* Returns a shallow copy of this <tt>HashSet</tt> instance: the elements themselves are not
* cloned.
145,6 → 47,7
*
* @return a shallow copy of this set
*/
@Override
public Object clone() {
return new LinkedIdentitySet<E>(this);
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/IncludeExclude.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
50,13 → 50,40
 
@SafeVarargs
public static <T> IncludeExclude<T> getNormalized(final T... includes) {
return getNormalized(Arrays.asList(includes));
return getNormalizedInclude(Arrays.asList(includes));
}
 
@Deprecated
public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes) {
return getNormalizedInclude(includes);
}
 
/**
* Return the normalized version including only the passed values. E.g. if <code>includes</code>
* is empty or <code>null</code>, this won't allocate any memory and {@link #getEmpty()} or
* {@link #getFull()} will be returned.
*
* @param <T> type of items
* @param includes which values to include.
* @return the normalized version.
*/
public static <T> IncludeExclude<T> getNormalizedInclude(final Collection<? extends T> includes) {
return getNormalized(includes, Collections.<T> emptySet());
}
 
/**
* Return the normalized version excluding only the passed values. E.g. if <code>excludes</code>
* is empty or <code>null</code>, this won't allocate any memory and {@link #getFull()} or
* {@link #getEmpty()} will be returned.
*
* @param <T> type of items
* @param excludes which values to exclude.
* @return the normalized version.
*/
public static <T> IncludeExclude<T> getNormalizedExclude(final Collection<? extends T> excludes) {
return getNormalized(null, excludes);
}
 
public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
return new IncludeExclude<T>(includes, excludes).normal;
}
74,6 → 101,7
*
* @param includes which objects to include, <code>null</code> meaning all.
* @param excludes which objects to exclude, <code>null</code> meaning all.
* @see #isIncluded(Object)
*/
public IncludeExclude(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
this(includes, excludes, false);
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/ListIdentitySet.java
New file
0,0 → 1,224
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.cc;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.IntHashSet;
 
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
 
/**
* An IdentitySet maintaining insertion order.
*
* @param <E> the type of elements maintained by this set
* @see IdentityHashSet
*/
public abstract class ListIdentitySet<E> extends AbstractSet<E> implements IdentitySet<E> {
 
static final int THRESHOLD = 16;
 
private final List<E> list;
private IntHashSet identityHashCodes;
 
protected ListIdentitySet(final Collection<? extends E> c) {
this(c.size());
if (c instanceof ListIdentitySet) {
final ListIdentitySet<? extends E> s = (ListIdentitySet<? extends E>) c;
this.list.addAll(c);
this.identityHashCodes = s.identityHashCodes;
} else {
this.addAll(c);
}
}
 
protected ListIdentitySet() {
this(16);
}
 
protected ListIdentitySet(final int expectedSize) {
this.list = this.newList(expectedSize);
this.identityHashCodes = null;
}
 
public final boolean hasHashCodes() {
return this.identityHashCodes != null;
}
 
public final void initHashCodes() {
if (this.identityHashCodes == null) {
this.identityHashCodes = new IntHashSet(this.size());
for (final E i : this.list) {
this.identityHashCodes.add(System.identityHashCode(i));
}
}
}
 
final boolean sameSize() {
return this.identityHashCodes == null || this.identityHashCodes.size() == this.list.size();
}
 
protected abstract List<E> newList(int expectedSize);
 
public final List<E> getList() {
return Collections.unmodifiableList(this.list);
}
 
@Override
public final boolean add(E e) {
if (this.identityHashCodes != null) {
final boolean added = this.identityHashCodes.add(System.identityHashCode(e));
if (added) {
this.list.add(e);
}
assert sameSize();
return added;
} else {
final boolean contains = this.contains(e);
if (!contains) {
this.list.add(e);
if (this.size() > THRESHOLD) {
initHashCodes();
}
}
assert sameSize();
return !contains;
}
}
 
@Override
public final boolean addAll(Collection<? extends E> c) {
// let the super which calls add() since we don't want to build a map to use Map.addAll()
return super.addAll(c);
}
 
@Override
public final void clear() {
this.list.clear();
this.identityHashCodes = null;
}
 
@Override
public final int size() {
return this.list.size();
}
 
@Override
public final boolean isEmpty() {
return this.list.isEmpty();
}
 
@Override
public final Iterator<E> iterator() {
if (this.identityHashCodes == null)
return this.list.iterator();
 
return new Iterator<E>() {
 
private final Iterator<E> iter = ListIdentitySet.this.list.iterator();
private E lastReturned = null;
 
@Override
public boolean hasNext() {
return this.iter.hasNext();
}
 
@Override
public E next() {
this.lastReturned = this.iter.next();
return this.lastReturned;
}
 
@Override
public void remove() {
this.iter.remove();
ListIdentitySet.this.identityHashCodes.remove(System.identityHashCode(this.lastReturned));
assert sameSize();
}
};
}
 
@Override
public boolean contains(Object o) {
if (this.identityHashCodes != null)
return this.identityHashCodes.contains(System.identityHashCode(o));
else
return CollectionUtils.identityContains(this.list, o);
}
 
@Override
public final boolean containsAll(Collection<?> c) {
// let the super which calls contains()
return super.containsAll(c);
}
 
@Override
public boolean remove(Object o) {
final boolean res;
if (this.identityHashCodes != null) {
res = this.identityHashCodes.remove(System.identityHashCode(o));
if (res)
CollectionUtils.identityRemove(this.list, o);
} else {
res = CollectionUtils.identityRemove(this.list, o);
}
assert sameSize();
return res;
}
 
/*
* (From IdentityHashMap) Must revert from AbstractSet's impl to AbstractCollection's, as the
* former contains an optimization that results in incorrect behavior when c is a smaller
* "normal" (non-identity-based) Set.
*/
@Override
public final boolean removeAll(Collection<?> c) {
boolean modified = false;
for (Iterator<E> i = iterator(); i.hasNext();) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
assert sameSize();
return modified;
}
 
@Override
public final boolean retainAll(Collection<?> c) {
// let the super which calls remove()
return super.retainAll(c);
}
 
@Override
public final Object[] toArray() {
return this.list.toArray();
}
 
@Override
public final <T> T[] toArray(T[] a) {
return this.list.toArray(a);
}
 
@Override
public final int hashCode() {
int result = 0;
for (E key : this.list)
result += System.identityHashCode(key);
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/CopyOnWriteMap.java
96,28 → 96,28
}
 
@Override
public synchronized int size() {
return this.immutable.size();
public int size() {
return this.getImmutable().size();
}
 
@Override
public synchronized boolean isEmpty() {
return this.immutable.isEmpty();
public boolean isEmpty() {
return this.getImmutable().isEmpty();
}
 
@Override
public synchronized boolean containsKey(final Object key) {
return this.immutable.containsKey(key);
public boolean containsKey(final Object key) {
return this.getImmutable().containsKey(key);
}
 
@Override
public synchronized boolean containsValue(final Object value) {
return this.immutable.containsValue(value);
public boolean containsValue(final Object value) {
return this.getImmutable().containsValue(value);
}
 
@Override
public synchronized V get(final Object key) {
return this.immutable.get(key);
public V get(final Object key) {
return this.getImmutable().get(key);
}
 
@Override
177,12 → 177,12
// equals
 
@Override
public synchronized boolean equals(final Object o) {
return this.immutable.equals(o);
public boolean equals(final Object o) {
return this.getImmutable().equals(o);
}
 
@Override
public synchronized int hashCode() {
return this.immutable.hashCode();
public int hashCode() {
return this.getImmutable().hashCode();
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/ArrayIdentitySet.java
New file
0,0 → 1,53
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.cc;
 
import java.util.ArrayList;
import java.util.Collection;
 
/**
* This class implements the <tt>Set</tt> interface with a list, using reference-equality in place
* of object-equality when comparing items.
*
* @param <E> the type of elements maintained by this set
*/
public final class ArrayIdentitySet<E> extends ListIdentitySet<E> implements Cloneable {
 
public ArrayIdentitySet() {
super();
}
 
public ArrayIdentitySet(int expectedMaxSize) {
super(expectedMaxSize);
}
 
public ArrayIdentitySet(Collection<? extends E> c) {
super(c);
}
 
@Override
protected ArrayList<E> newList(int expectedSize) {
return new ArrayList<>(expectedSize);
}
 
/**
* Returns a shallow copy of this instance: the elements themselves are not cloned.
*
* @return a shallow copy of this set
*/
@Override
public Object clone() {
return new ArrayIdentitySet<E>(this);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/IdentityHashSet.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 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
29,6 → 29,8
*/
public final class IdentityHashSet<E> extends AbstractSet<E> implements IdentitySet<E>, Cloneable {
 
static private final Object VALUE = new Object();
 
private final IdentityHashMap<E, Object> map;
 
public IdentityHashSet() {
46,13 → 48,8
 
@Override
public boolean add(E e) {
if (this.contains(e))
return false;
else {
this.map.put(e, null);
return true;
return this.map.put(e, VALUE) != VALUE;
}
}
 
@Override
public boolean addAll(Collection<? extends E> c) {
82,7 → 79,7
 
@Override
public boolean contains(Object o) {
return this.map.keySet().contains(o);
return this.map.containsKey(o);
}
 
@Override
92,7 → 89,7
 
@Override
public boolean remove(Object o) {
return this.map.keySet().remove(o);
return this.map.remove(o) == VALUE;
}
 
@Override
115,6 → 112,14
return this.map.keySet().toArray(a);
}
 
// use System.identityHashCode()
@Override
public int hashCode() {
return this.map.keySet().hashCode();
}
 
// equals() uses containsAll which is correct
 
/**
* Returns a shallow copy of this <tt>HashSet</tt> instance: the elements themselves are not
* cloned.
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/CopyOnWriteList.java
New file
0,0 → 1,239
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 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.cc;
 
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.UnaryOperator;
 
import net.jcip.annotations.ThreadSafe;
 
/**
* A thread-safe list in which all mutative operations (set, remove, and so on) are implemented by
* making a fresh copy of the underlying list. The instance of the underlying list can be customized
* with {@link #copy(Collection)} and {@link #create(int)}. The underlying immutable list is
* available with {@link #getImmutable()}.
*
* @author Sylvain
*
* @param <T> the type of elements in this list
*/
@ThreadSafe
public class CopyOnWriteList<T> extends AbstractList<T> {
 
private List<T> immutable;
 
public CopyOnWriteList() {
this.immutable = Collections.emptyList();
}
 
public CopyOnWriteList(final T single) {
this.immutable = Collections.singletonList(single);
}
 
public CopyOnWriteList(final Collection<? extends T> m) {
this.immutable = Collections.unmodifiableList(copy(m));
}
 
/**
* Create a copy of the passed collection.
*
* @param src the collection to copy, not <code>null</code>.
* @return a shallow copy of <code>src</code>.
*/
protected List<T> copy(final Collection<? extends T> src) {
return new ArrayList<>(src);
}
 
protected List<T> create(int capacity) {
return new ArrayList<>(capacity);
}
 
// Like CopyOnWriteArrayList, iterate on immutable (otherwise would have to at least increment
// this.modCount).
 
@Override
public Iterator<T> iterator() {
return this.getImmutable().iterator();
}
 
@Override
public ListIterator<T> listIterator(int index) {
return this.getImmutable().listIterator(index);
}
 
// write
 
private void rangeCheck(final int index, final boolean forAdd, final int size) {
if (index < 0 || index > size || (!forAdd && index == size))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
 
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size();
}
 
@Override
public synchronized T set(int index, T element) {
final List<T> copy = copy(this.immutable);
final T res = copy.set(index, element);
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized boolean add(T e) {
return super.add(e);
}
 
@Override
public void add(int index, T element) {
this.addAll(index, Collections.singletonList(element));
}
 
@Override
public synchronized boolean addAll(Collection<? extends T> c) {
return this.addAll(this.size(), c);
}
 
@Override
public synchronized boolean addAll(int index, Collection<? extends T> c) {
final int size = this.size();
this.rangeCheck(index, true, size);
final int cSize = c.size();
if (cSize == 0)
return false;
final List<T> copy = create(size + cSize);
for (int i = 0; i <= size; i++) {
if (i == index)
copy.addAll(c);
if (i < size)
copy.add(this.get(i));
}
assert copy.size() == size + cSize;
this.immutable = Collections.unmodifiableList(copy);
return true;
}
 
@Override
public synchronized T remove(final int index) {
final int size = this.size();
this.rangeCheck(index, false, size);
final List<T> copy = create(size - 1);
T res = null;
for (int i = 0; i < size; i++) {
if (i == index)
res = this.get(i);
else
copy.add(this.get(i));
}
assert copy.size() == size - 1;
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized boolean remove(Object o) {
final int indexOf = this.indexOf(o);
if (indexOf < 0)
return false;
this.remove(indexOf);
return true;
}
 
@Override
public synchronized boolean removeAll(Collection<?> c) {
final List<T> copy = copy(this.immutable);
final boolean res = copy.removeAll(c);
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized void clear() {
this.immutable = Collections.emptyList();
}
 
@Override
public synchronized boolean retainAll(Collection<?> c) {
final List<T> copy = copy(this.immutable);
final boolean res = copy.retainAll(c);
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized void replaceAll(UnaryOperator<T> operator) {
final List<T> copy = copy(this.immutable);
copy.replaceAll(operator);
this.immutable = Collections.unmodifiableList(copy);
}