OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Compare Revisions

Ignore whitespace Rev 181 → Rev 182

/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/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/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/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/Journaux.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/Avoir.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/ReleveChequeEmis.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/Operations Report.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/GrandLivre.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/FichePayeSimplifiee.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/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/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/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/VenteFactureSituation.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/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/DemandePrix.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/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/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/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/JournauxMois.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/FicheArticle.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/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/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/VenteFacture.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/FactureFournisseur.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/LivrePaye.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/JournauxAnalytique.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/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/EtatVentes.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/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/ListeDebiteur.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/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/ExportArticle.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/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/EtatChargesPaye.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/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/BonReception.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/Courrier.odt
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/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/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/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/Devis.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/ReportingTaxeComplementaire.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/Balance.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/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/Relance1.odt
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/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/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/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 Set<SQLTable> toRefresh = new HashSet<SQLTable>();
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
final TablesMap tables = new TablesMap();
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Void, SQLException>() {
@Override
public Object handle(final SQLDataSource ds) throws SQLException {
public Void handle(final SQLDataSource ds) throws SQLException {
final Set<SQLTable> toRefresh = new HashSet<SQLTable>();
 
// 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,20 → 303,13
// 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) {
/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/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/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/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/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/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/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/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/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();
}
RowValuesTableControlPanel.this.model.removeRowsAt(table.getSelectedRows());
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/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,7 → 237,9
}
rowVal.put(fieldName, value);
for (SQLTableElement sqlTableElem : this.list) {
sqlTableElem.fireModification(rowVal);
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/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,37 → 153,34
*/
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();
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();
if (user != null)
update.setObject(SQLComponent.READ_ONLY_USER_FIELD, user.getId());
update.setWhere(new Where(t.getKey(), ids));
t.getDBSystemRoot().getDataSource().execute(update.asString());
// don't fire too many times, as each one will cause UpdateQueue to issue a
// request
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);
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();
if (user != null)
update.setObject(SQLComponent.READ_ONLY_USER_FIELD, user.getId());
update.setWhere(new Where(t.getKey(), ids));
t.getDBSystemRoot().getDataSource().execute(update.asString());
// don't fire too many times, as each one will cause UpdateQueue to issue a
// request
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());
});
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());
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
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);
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/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,7 → 97,23
this.setRow(row);
this.id = id;
this.state = state;
this.clearCache();
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
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,7 → 182,16
} else {
if (order2 != null)
throw new IllegalStateException("Order mismatch :\n" + order1 + " for " + l1 + " not coherent with\n" + order2 + " for " + l2);
return l1.getState().getReq().order(l1.getRow(), l2.getRow());
 
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());
}
}
}
 
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/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/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
144,10 → 148,12
return res instanceof SQLRowValues ? ((SQLRowValues) res).getID() : res;
}
});
}
}
}, true);
 
this.debugCols.add(new DebugRow(getPrimaryTable()));
if (moreDebugCols)
this.debugCols.add(new DebugRow(getPrimaryTable()));
final SQLField orderField = getPrimaryTable().getOrderField();
if (orderField != null)
this.debugCols.add(new SQLTableModelColumnPath(Path.get(getPrimaryTable()), orderField.getName(), "Order"));
/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,14 → 29,19
 
@Override
public void run() {
if (this.getTable() == this.getReq().getParent().getPrimaryTable()) {
final Value<ListSQLLine> val = this.getReq().get(this.getID());
if (!val.hasValue())
return;
updateLines(this.getAffectedPaths(), val);
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;
updateLines(this.getAffectedPaths(), val);
} else {
// eg CONTACT[3] has changed
updateLines(this.getAffectedPaths());
}
} else {
// eg CONTACT[3] has changed
updateLines(this.getAffectedPaths());
this.updateLines(this.getUpdateQ().getAffectedLines(this.getRow()), isPrimaryTable ? this.getID() : null);
}
}
 
/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/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)) {
// works for rowValues w/o any ID
if (current != null && current.getID() == id) {
put = true;
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) {
put = true;
/*
* 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/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,78 → 35,18
*/
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();
}
}
 
/**
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/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/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/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/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/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"));
Where wContains = new Where(this.tableArticle.getField("CODE"), "LIKE", "%" + aText + "%");
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,24 → 417,68
List<SQLRowValues> resultCodeF = fetcher.fetch();
resultList.add(2, resultCodeF);
 
Map<String, Map<Number, SQLRowAccessor>> mapDecl = new HashMap<>();
for (List<? extends SQLRowAccessor> list : resultList) {
 
for (SQLRowAccessor sqlRow : list) {
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")) {
SQLRowAccessor rArt = sqlRow.getForeign("ID_ARTICLE");
buf.append(sqlRow.getString("CODE") + " -- ");
buf.append(rArt.getString("CODE") + " -- ");
buf.append(rArt.getString("NOM"));
result.add(new IComboSelectionItem(rArt, buf.toString()));
} else {
if (sqlRow.getString("CODE_BARRE") != null && sqlRow.getString("CODE_BARRE").trim().length() > 0) {
buf.append(sqlRow.getString("CODE_BARRE") + " -- ");
StringBuffer buf = new StringBuffer();
if (sqlRow.getTable().getName().equals("CODE_FOURNISSEUR")) {
SQLRowAccessor rArt = sqlRow.getForeign("ID_ARTICLE");
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) {
buf.append(sqlRow.getString("CODE_BARRE") + " -- ");
}
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()));
}
}
buf.append(sqlRow.getString("CODE") + " -- ");
buf.append(sqlRow.getString("NOM"));
result.add(new IComboSelectionItem(sqlRow, buf.toString()));
}
}
}
502,8 → 580,19
 
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);
}
}
}
 
/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/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);
addField(graphToFetch, getPrimaryTable().getCreationDateField());
addField(graphToFetch, getPrimaryTable().getCreationUserField());
addField(graphToFetch, getPrimaryTable().getModifDateField());
addField(graphToFetch, getPrimaryTable().getModifUserField());
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/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/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/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/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/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/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/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,7 → 163,10
sel.addSelectStar(t);
else
sel.addAllSelect(t, fields);
sel.setWhere(new Where(t.getKey(), ids));
// 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/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,7 → 91,21
 
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")
/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/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/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/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/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/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/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/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/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/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/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,9 → 179,14
* <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) {
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,12 → 322,7
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());
}
}
 
/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/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() {
return this;
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/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/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/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/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/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/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/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) {
679,7 → 709,34
public final Object getObject(String fieldName) {
return this.values.get(fieldName);
}
@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);
686,12 → 743,16
}
 
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,17 → 763,44
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());
} else if (copyForeigns == ForeignCopyMode.COPY_NULL) {
toAdd.put(e.getKey(), null);
} else if (copyForeigns != ForeignCopyMode.NO_COPY) {
final SQLRowValues foreign = (SQLRowValues) e.getValue();
if (foreign.hasID())
toAdd.put(e.getKey(), foreign.getIDNumber());
else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW)
toAdd.put(e.getKey(), foreign);
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) {
val = null;
} else {
final SQLRowValues foreign = (SQLRowValues) e.getValue();
if (foreign.hasID())
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() {
return this;
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) {
value = convert(value.getClass(), value, javaType);
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/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/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/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/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/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,6 → 153,38
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() {
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/utils/OutlookEmail.vbs
File deleted
/trunk/OpenConcerto/src/org/openconcerto/utils/EmailClient.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,21 → 20,23
import org.openconcerto.utils.DesktopEnvironment.XFCE;
import org.openconcerto.utils.OSFamily.Unix;
import org.openconcerto.utils.io.PercentEncoder;
import org.openconcerto.utils.system.Powershell;
 
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Writer;
import java.lang.ProcessBuilder.Redirect;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
 
public abstract class EmailClient {
 
344,26 → 346,34
public static final EmailClient Outlook = new EmailClient(EmailClientType.Outlook) {
@Override
protected boolean composeNative(String to, String subject, String body, File... attachments) throws IOException, InterruptedException {
final DesktopEnvironment de = DesktopEnvironment.getDE();
final File vbs = FileUtils.getFile(EmailClient.class.getResource("OutlookEmail.vbs"));
final List<String> l = new ArrayList<String>(6);
l.add("cscript");
l.add(de.quoteParamForExec(vbs.getAbsolutePath()));
if (to != null)
l.add(createVBParam("to", to));
if (subject != null)
l.add(createVBParam("subject", subject));
// at least set a parameter otherwise the usage get displayed
l.add(createVBParam("unicodeStdIn", "1"));
for (File attachment : attachments) {
l.add(de.quoteParamForExec(attachment.getAbsolutePath()));
return composePowershell(to, subject, body, attachments);
}
 
// only tested with powershell 5.1
protected boolean composePowershell(String to, String subject, String body, File... attachments) throws IOException, InterruptedException {
final Powershell pwsh = Powershell.getInstance();
 
// Don't create temporary file :
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.1
String template = new String(StreamUtils.read(EmailClient.class.getResourceAsStream("Outlook.powershell")), StandardCharsets.UTF_8);
template = template.replace("@to@", pwsh.quote(to == null ? "" : to));
template = template.replace("@subject@", pwsh.quote(subject == null ? "" : subject));
template = template.replace("@attachments@", pwsh.quoteArray(Arrays.asList(attachments).stream().map(File::getAbsolutePath).collect(Collectors.toList())));
 
final ProcessBuilder pb = new ProcessBuilder();
pb.command().add("powershell");
// Apparently piping (i.e. "-Command -") only supports ASCII (and would require
// embedding the body in the script).
pb.command().add("-EncodedCommand");
pb.command().add(pwsh.getEncodedCommand(template));
 
pb.inheritIO();
pb.redirectInput(Redirect.PIPE);
final Process process = pb.start();
try (final OutputStream in = process.getOutputStream()) {
in.write(body.getBytes(StandardCharsets.UTF_8));
}
 
final Process process = new ProcessBuilder(l).start();
// VBScript only knows ASCII and UTF-16
final Writer writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StringUtils.UTF16));
writer.write(body);
writer.close();
final int returnCode = process.waitFor();
if (returnCode != 0)
throw new IllegalStateException("Non zero return code: " + returnCode);
/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<?>) {
result = ((Class<?>) param).getName();
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/SetMap.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,8
}
 
static public <K, V> SetMapItf<K, V> unmodifiableMap(SetMapItf<K, V> map) {
if (map.isEmpty())
return empty();
return new Unmodifiable<K, V>(map);
}
 
/trunk/OpenConcerto/src/org/openconcerto/utils/FileUtils.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
277,6 → 277,7
* @return the relative path, eg "../x/y.txt".
* @throws IOException if an error occurs while canonicalizing the files.
* @throws IllegalArgumentException if fromDir exists and is not directory.
* @see {@link Path#relativize(Path)}
*/
public static final String relative(File fromDir, File to) throws IOException {
if (fromDir.exists() && !fromDir.isDirectory())
/trunk/OpenConcerto/src/org/openconcerto/utils/Action.java
New file
0,0 → 1,34
/*
* 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;
 
public abstract class Action {
 
private final String name;
 
public Action(String name) {
this.name = name;
}
 
public String getName() {
return this.name;
}
 
@Override
public String toString() {
return this.name;
}
 
public abstract void run(Object source);
}
/trunk/OpenConcerto/src/org/openconcerto/utils/ExceptionUtils.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,9
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.util.Objects;
import java.util.function.BiPredicate;
 
/**
* Utilitaires pour les exceptions.
92,4 → 95,27
Log.get().warning("Error while writing " + cause);
return res.toString();
}
 
static public void requireEquals(final BigDecimal a, final BigDecimal b, final String msg) {
require((bd1, bd2) -> bd1.compareTo(bd2) == 0, a, b, msg);
}
 
static public void requireEquals(final Object a, final Object b, final String msg) {
require(Objects::equals, a, b, msg);
}
 
static public <T> void require(final BiPredicate<T, T> pred, final T a, final T b, final String msg) {
if (!pred.test(a, b))
throw new IllegalArgumentException(msg + ", expected " + a + " but got " + b);
}
 
static public void requireSame(final Object a, final Object b, final String msg) {
if (a != b)
throw new IllegalArgumentException(msg + ", expected same reference to " + a + " but got " + b);
}
 
static public void requireEquals(final int a, final int b, final String msg) {
if (a != b)
throw new IllegalArgumentException(msg + ", expected " + a + " but got " + b);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/sync/SimpleSyncClient.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 java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
52,15 → 53,15
 
public final class SimpleSyncClient extends HTTPClient {
 
static protected final long getLong(final Object o) {
protected static final long getLong(final Object o) {
return ((Number) o).longValue();
}
 
static protected final Instant getInstant(final Object o) {
protected static final Instant getInstant(final Object o) {
return Instant.ofEpochMilli(getLong(o));
}
 
static protected abstract class BaseAttrs {
public static abstract class BaseAttrs implements Serializable {
private final String path;
private final String name;
private final Instant lastModified;
90,8 → 91,8
}
}
 
static public final class DirAttrs extends BaseAttrs {
static protected DirAttrs fromJSON(final String path, final JSONArray array) {
public static final class DirAttrs extends BaseAttrs {
protected static DirAttrs fromJSON(final String path, final JSONArray array) {
return new DirAttrs(path, (String) array.get(0), getInstant(array.get(1)));
}
 
100,9 → 101,9
}
}
 
static public final class FileAttrs extends BaseAttrs {
public static final class FileAttrs extends BaseAttrs {
 
static protected FileAttrs fromJSON(final String path, final JSONArray array) {
protected static FileAttrs fromJSON(final String path, final JSONArray array) {
return new FileAttrs(path, (String) array.get(0), getInstant(array.get(2)), getLong(array.get(1)), (String) array.get(3));
}
 
158,7 → 159,7
}
}
 
static public final class DirContent {
public static final class DirContent {
private final String path;
private final JSONObject json;
 
219,11 → 220,11
}
 
@FunctionalInterface
static public interface FileConsumer {
public static interface FileConsumer {
public void accept(FileAttrs attrs, InputStream fileStream) throws IOException;
}
 
static private final Set<Integer> GETFILE_OK_CODES = CollectionUtils.createSet(200, 404);
private static final Set<Integer> GETFILE_OK_CODES = CollectionUtils.createSet(200, 404);
 
public Response getFile(final String path, final String fileName, final FileConsumer fileConsumer) throws IOException {
if (path == null) {
246,6 → 247,26
return res;
}
 
public Integer getCounter(final String key) throws IOException {
if (key == null) {
throw new IllegalArgumentException("null key");
}
final HttpsURLConnection con = openConnection("/getCounter");
send(con, NetUtils.urlEncode("key", key), false);
final Response res = checkResponseCode(con, GETFILE_OK_CODES);
if (res.getCode() == 200) {
byte[] bytes = new byte[20];
try (final InputStream in = getInputStream(con)) {
int r = in.read(bytes);
String str = new String(bytes, 0, r);
return Integer.parseInt(str);
}
 
}
return null;
 
}
 
// ATTN contrary to other methods, the result isn't if the request was OK : it ignores
// throwsException() and always throws. The return value is true if the file existed and was
// saved.
281,7 → 302,7
}
 
public final Response renameFile(final String path, final String fileName, final String newFileName) throws IOException {
return this.renameFile(path, fileName, null, newFileName);
return this.renameFile(path, fileName, path, newFileName);
}
 
public final Response renameFile(final String path, final String fileName, final String newPath, final String newFileName) throws IOException {
331,4 → 352,15
 
return checkResponseCode(send(con, ba.toByteArray(), true));
}
 
public Response createDir(String path, String fileName) throws IOException {
if (path == null) {
throw new IllegalArgumentException("null path");
}
if (fileName == null) {
throw new IllegalArgumentException("null fileName");
}
final HttpsURLConnection con = openConnection("/mkdir");
return checkResponseCode(send(con, NetUtils.urlEncode("rn", fileName, "rp", path), false));
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/sync/SyncClient.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 java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
529,7 → 530,16
wr.close();
 
// Get the response ASAP in order to not block the server while computing locally hash256
DataInputStream in = new DataInputStream(new BufferedInputStream(conn.getInputStream()));
 
// Copy the entire data first because DataInputStream will fail on partial buffer (mainly
// readUTF)
InputStream ins = conn.getInputStream();
final ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.copy(ins, out);
ins.close();
 
DataInputStream in = new DataInputStream(new ByteArrayInputStream(out.toByteArray()));
 
int fileCount = in.readInt();
this.byteReceived += 4;
ArrayList<FileProperty> list = new ArrayList<FileProperty>();
875,4 → 885,6
public void setVerifyHost(boolean verify) {
this.verifyHost = verify;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/i18n/translation/messages_sp.properties
File deleted
\ No newline at end of file
/trunk/OpenConcerto/src/org/openconcerto/utils/i18n/translation/messages_pl.properties
27,3 → 27,17
cut=Wytnij
copy=Kopi\u0119
paste=Wklej
 
# search
all=All
toReverse=Reverse
 
contains=Contains
contains.exactly=Contains exactly
isLessThan=Is less than
isLessThanOrEqualTo=Is less than or equal to
isEqualTo=Is equal to
isExactlyEqualTo=Is exactly equal to
isGreaterThan=Is greater than
isGreaterThanOrEqualTo=Is greater than or equal to
isEmpty=Is empty
/trunk/OpenConcerto/src/org/openconcerto/utils/i18n/translation/messages_en.properties
6,6 → 6,7
linkOpenError=Error while opening {0}
 
memory=memory
memory.used=memory used
megabytes={0} MB
processors={0, plural, one { # processor } other { # processors } }
os=Operating system
12,6 → 13,8
javaVersion=Version <b>{0}</b> of {1}
javaHome=installation directory
no.laf=No look and feel
properties.all=All properties
env.all=Whole environment
 
user=User
home.dir=home directory
26,4 → 29,18
 
cut=Cut
copy=Copy
paste=Paste
paste=Paste
 
# search
all=All
toReverse=Reverse
 
contains=Contains
contains.exactly=Contains exactly
isLessThan=Is less than
isLessThanOrEqualTo=Is less than or equal to
isEqualTo=Is equal to
isExactlyEqualTo=Is exactly equal to
isGreaterThan=Is greater than
isGreaterThanOrEqualTo=Is greater than or equal to
isEmpty=Is empty
/trunk/OpenConcerto/src/org/openconcerto/utils/i18n/translation/messages_es.properties
New file
0,0 → 1,22
true_key=verdadero
false_key=falso
yes_key=sí
no_key=no
 
cut=Cortar
copy=Copiar
paste=Pegar
 
# search
all=Todo
toReverse=Revertir
 
contains=Contiene
contains.exactly=Contiene exactamente
isLessThan=Es menos de
isLessThanOrEqualTo=Es menor o igual a
isEqualTo=Es igual a
isExactlyEqualTo=Es exactamente igual a
isGreaterThan=Es más de
isGreaterThanOrEqualTo=Es más o igual a
isEmpty=Esta vacio
/trunk/OpenConcerto/src/org/openconcerto/utils/i18n/translation/messages_fr.properties
6,6 → 6,7
linkOpenError=Impossible d''ouvrir {0}
 
memory=mémoire
memory.used=mémoire utilisée
megabytes={0} Mo
processors={0, plural, one { # processeur } other { # processeurs } }
os=Système d''exploitation
12,6 → 13,8
javaVersion=Version <b>{0}</b> de {1}
javaHome=dossier d'installation
no.laf=Aucun thème
properties.all=Toutes les propriétés
env.all=Tout l'environnement
 
user=Utilisateur
home.dir=dossier utilisateur
27,3 → 30,20
cut=Couper
copy=Copier
paste=Coller
 
# search
all=Tout
toReverse=inverser
 
contains=Contient
contains.exactly=Contient exactement
startsWith=Commence par
endsWith=Finit par
isLessThan=Est inférieur à
isLessThanOrEqualTo=Est inférieur ou égal à
isEqualTo=Est égal à
isExactlyEqualTo=Est exactement égal à
isGreaterThan=Est supérieur à
isGreaterThanOrEqualTo=Est supérieur ou égal à
isEmpty=Est vide
matchRegexp=Correspond à l'expression régulière
/trunk/OpenConcerto/src/org/openconcerto/utils/reentrantEventsIn-order.seq.violet.html
New file
0,0 → 1,1181
<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="320.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="150.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="1180.0" y="0.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="120.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
<ActivationBarNode id="34">
<id id="35" value="bbede6d5-d0e1-434f-a671-95e1bbc6de05"/>
<revision>0</revision>
<children id="36"/>
<parent class="LifelineNode" reference="27"/>
<location class="Point2D.Double" id="37" x="133.0" y="300.0"/>
<backgroundColor id="38">
<red>255</red>
<green>255</green>
<blue>255</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="39">
<red>191</red>
<green>191</green>
<blue>191</blue>
<alpha>255</alpha>
</borderColor>
<textColor reference="23"/>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="40" x="580.0" y="10.0"/>
<textColor reference="23"/>
<name id="41">
<text>l1</text>
</name>
<type id="42">
<text>SQLElementEventListener</text>
</type>
<endOfLife>false</endOfLife>
</LifelineNode>
<LifelineNode id="43">
<id id="44" value="66c2c3a4-4956-4a4b-b819-07da280e5582"/>
<revision>1</revision>
<children id="45">
<ActivationBarNode id="46">
<id id="47" value="f993e4c8-6b9f-4be1-ad82-c3f04af74f39"/>
<revision>0</revision>
<children id="48"/>
<parent class="LifelineNode" reference="43"/>
<location class="Point2D.Double" id="49" x="133.0" y="220.0"/>
<backgroundColor reference="38"/>
<borderColor reference="39"/>
<textColor reference="23"/>
</ActivationBarNode>
<ActivationBarNode id="50">
<id id="51" value="27fc63e2-922c-4899-bf6c-017a35d6e97f"/>
<revision>0</revision>
<children id="52"/>
<parent class="LifelineNode" reference="43"/>
<location class="Point2D.Double" id="53" x="133.0" y="360.0"/>
<backgroundColor reference="38"/>
<borderColor reference="39"/>
<textColor reference="23"/>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="54" x="880.0" y="0.0"/>
<textColor reference="23"/>
<name id="55">
<text>l2</text>
</name>
<type id="56">
<text>SQLElementEventListener</text>
</type>
<endOfLife>false</endOfLife>
</LifelineNode>
<NoteNode id="57">
<id id="58" value="d4154bcf-2a20-4ffc-aaf5-43d47a5a34f7"/>
<revision>1</revision>
<children id="59"/>
<location class="Point2D.Double" id="60" x="920.0" y="540.0"/>
<backgroundColor id="61">
<red>254</red>
<green>222</green>
<blue>188</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="62">
<red>253</red>
<green>186</green>
<blue>113</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="63">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
<text id="64">
<text>l2 is notified of evt 1 before evt 2</text>
</text>
</NoteNode>
<LifelineNode id="65">
<id id="66" value="163a5ef7-6b32-4747-8b4d-fe2335ca3610"/>
<revision>1</revision>
<children id="67">
<ActivationBarNode id="68">
<id id="69" value="d7791be4-637c-448a-b7a7-3651c074b60e"/>
<revision>0</revision>
<children id="70"/>
<parent class="LifelineNode" reference="65"/>
<location class="Point2D.Double" id="71" x="127.0" y="90.0"/>
<backgroundColor id="72">
<red>255</red>
<green>255</green>
<blue>255</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="73">
<red>191</red>
<green>191</green>
<blue>191</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="74">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
</ActivationBarNode>
<ActivationBarNode id="75">
<id id="76" value="b8bfffe0-2ac3-4ae4-98a4-e9551538026a"/>
<revision>0</revision>
<children id="77"/>
<parent class="LifelineNode" reference="65"/>
<location class="Point2D.Double" id="78" x="127.0" y="180.0"/>
<backgroundColor reference="38"/>
<borderColor reference="39"/>
<textColor reference="74"/>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="79" x="30.0" y="0.0"/>
<textColor reference="74"/>
<name id="80">
<text></text>
</name>
<type id="81">
<text>ReentrantEventDispatcher</text>
</type>
<endOfLife>false</endOfLife>
</LifelineNode>
<NoteNode id="82">
<id id="83" value="511fb94e-4412-4622-93b3-f262f1546e87"/>
<revision>1</revision>
<children id="84"/>
<location class="Point2D.Double" id="85" x="640.0" y="540.0"/>
<backgroundColor reference="61"/>
<borderColor reference="62"/>
<textColor reference="63"/>
<text id="86">
<text>When evt2.fire() returns,
its listeners have been notitied</text>
</text>
</NoteNode>
</nodes>
<edges id="87">
<SynchronousCallEdge id="88">
<id id="89" value="dba1c89a-89af-42c6-b657-904452a6d3c3"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="6"/>
<startLocation class="Point2D.Double" id="90" x="7.5" y="25.0"/>
<endNode class="ActivationBarNode" reference="68"/>
<endLocation class="Point2D.Double" id="91" x="120.0" y="110.0"/>
<transitionPoints id="92"/>
<backgroundColor reference="38"/>
<borderColor reference="39"/>
<textColor id="93">
<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="94">
<text></text>
</startLabel>
<centerLabel id="95">
<text>fire(evt1)</text>
</centerLabel>
<endLabel id="96">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="97">
<id id="98" value="24a957d6-a98c-46af-bc95-d25b3b90a8cd"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="68"/>
<startLocation class="Point2D.Double" id="99" x="3.0" y="30.0"/>
<endNode class="ActivationBarNode" reference="30"/>
<endLocation class="Point2D.Double" id="100" x="7.0" y="20.0"/>
<transitionPoints id="101"/>
<backgroundColor reference="38"/>
<borderColor reference="39"/>
<textColor reference="93"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="102">
<text></text>
</startLabel>
<centerLabel id="103">
<text>modification(evt1)</text>
</centerLabel>
<endLabel id="104">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="105">
<id id="106" value="dece2a55-78fe-4e6f-92fb-df870ae50d7e"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="30"/>
<startLocation class="Point2D.Double" id="107" x="7.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="108" x="7.5" y="20.0"/>
<transitionPoints id="109"/>
<backgroundColor reference="38"/>
<borderColor reference="39"/>
<textColor reference="93"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="110">
<text></text>
</startLabel>
<centerLabel id="111">
<text>fire</text>
</centerLabel>
<endLabel id="112">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="113">
<id id="114" value="cdfe85c3-922f-4901-93bf-137cb825fa48"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="115" x="7.5" y="20.0"/>
<endNode class="ActivationBarNode" reference="75"/>
<endLocation class="Point2D.Double" id="116" x="130.0" y="290.0"/>
<transitionPoints id="117"/>
<backgroundColor id="118">
<red>255</red>
<green>255</green>
<blue>255</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="119">
<red>191</red>
<green>191</green>
<blue>191</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="120">
<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="121">
<text></text>
</startLabel>
<centerLabel id="122">
<text>fire(evt2)</text>
</centerLabel>
<endLabel id="123">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="124">
<id id="125" value="6459bc49-5f19-42a3-89a6-6f9cec6ef186"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="75"/>
<startLocation class="Point2D.Double" id="126" x="3.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="46"/>
<endLocation class="Point2D.Double" id="127" x="140.0" y="220.0"/>
<transitionPoints id="128"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="129">
<text></text>
</startLabel>
<centerLabel id="130">
<text>modification(evt1)</text>
</centerLabel>
<endLabel id="131">
<text></text>
</endLabel>
</SynchronousCallEdge>
<ReturnEdge id="132">
<id id="133" value="aeba7c53-ec88-4436-8c73-4076539f3ff4"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="46"/>
<startLocation class="Point2D.Double" id="134" x="7.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="75"/>
<endLocation class="Point2D.Double" id="135" x="3.0" y="60.0"/>
<transitionPoints id="136"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="137">
<text></text>
</startLabel>
<centerLabel id="138">
<text></text>
</centerLabel>
<endLabel id="139">
<text></text>
</endLabel>
</ReturnEdge>
<SynchronousCallEdge id="140">
<id id="141" value="707e1877-ea13-4950-a9ed-fa4f626a42a9"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="75"/>
<startLocation class="Point2D.Double" id="142" x="13.0" y="80.0"/>
<endNode class="ActivationBarNode" reference="34"/>
<endLocation class="Point2D.Double" id="143" x="140.0" y="340.0"/>
<transitionPoints id="144"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="145">
<text></text>
</startLabel>
<centerLabel id="146">
<text>modification(evt2)</text>
</centerLabel>
<endLabel id="147">
<text></text>
</endLabel>
</SynchronousCallEdge>
<ReturnEdge id="148">
<id id="149" value="c3f811d3-2375-4e8b-8a63-62e9671368db"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="34"/>
<startLocation class="Point2D.Double" id="150" x="7.0" y="30.0"/>
<endNode class="ActivationBarNode" reference="75"/>
<endLocation class="Point2D.Double" id="151" x="13.0" y="180.0"/>
<transitionPoints id="152"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="153">
<text></text>
</startLabel>
<centerLabel id="154">
<text></text>
</centerLabel>
<endLabel id="155">
<text></text>
</endLabel>
</ReturnEdge>
<SynchronousCallEdge id="156">
<id id="157" value="4c0dc965-8e0e-468c-a95f-e12f99e7635f"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="75"/>
<startLocation class="Point2D.Double" id="158" x="3.0" y="160.0"/>
<endNode class="ActivationBarNode" reference="50"/>
<endLocation class="Point2D.Double" id="159" x="140.0" y="360.0"/>
<transitionPoints id="160"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="161">
<text></text>
</startLabel>
<centerLabel id="162">
<text>modification(evt2)</text>
</centerLabel>
<endLabel id="163">
<text></text>
</endLabel>
</SynchronousCallEdge>
<ReturnEdge id="164">
<id id="165" value="1317c4c3-a12a-4319-b23a-5dfa9c228004"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="50"/>
<startLocation class="Point2D.Double" id="166" x="7.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="75"/>
<endLocation class="Point2D.Double" id="167" x="3.0" y="210.0"/>
<transitionPoints id="168"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="169">
<text></text>
</startLabel>
<centerLabel id="170">
<text></text>
</centerLabel>
<endLabel id="171">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="172">
<id id="173" value="dec23fcb-69ae-4795-a9fb-f0e8f300384d"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="75"/>
<startLocation class="Point2D.Double" id="174" x="3.0" y="230.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="175" x="7.5" y="270.0"/>
<transitionPoints id="176"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="177">
<text></text>
</startLabel>
<centerLabel id="178">
<text></text>
</centerLabel>
<endLabel id="179">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="180">
<id id="181" value="b1771709-2dfc-4b9c-b2f3-fd73233602aa"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="182" x="7.5" y="280.0"/>
<endNode class="ActivationBarNode" reference="30"/>
<endLocation class="Point2D.Double" id="183" x="7.0" y="310.0"/>
<transitionPoints id="184"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="185">
<text></text>
</startLabel>
<centerLabel id="186">
<text></text>
</centerLabel>
<endLabel id="187">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="188">
<id id="189" value="a019b2a9-840d-4fad-9f9a-e9abb7dfa4f2"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="30"/>
<startLocation class="Point2D.Double" id="190" x="7.0" y="330.0"/>
<endNode class="ActivationBarNode" reference="68"/>
<endLocation class="Point2D.Double" id="191" x="3.0" y="360.0"/>
<transitionPoints id="192"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="193">
<text></text>
</startLabel>
<centerLabel id="194">
<text></text>
</centerLabel>
<endLabel id="195">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="196">
<id id="197" value="407a6a66-f746-43fa-83da-524d9ae14c1c"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="68"/>
<startLocation class="Point2D.Double" id="198" x="3.0" y="380.0"/>
<endNode class="ActivationBarNode" reference="6"/>
<endLocation class="Point2D.Double" id="199" x="7.5" y="405.0"/>
<transitionPoints id="200"/>
<backgroundColor reference="118"/>
<borderColor reference="119"/>
<textColor reference="120"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="201">
<text></text>
</startLabel>
<centerLabel id="202">
<text></text>
</centerLabel>
<endLabel id="203">
<text></text>
</endLabel>
</ReturnEdge>
</edges>
</SequenceDiagramGraph>]]></SCRIPT>
<BR />
<BR />
<IMG alt="embedded diagram image" src="data:image/png;base64,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
24XJ3/7t3x4dHd3c3NzZ2fnWt751xvllJxt729veVvihjMc+vfmL/PrXv36m+/sy3/zmN2/cuGHO
ac5c8oE97kjc81auSlOOnRtywZ3SpdIpeg7Lqc9AIXMl5uXBXKq5YPM9MpdkP8LXvDDYgHmR+IVf
+IUz3Y9+mZiYMDd3d3fNF2Junul+mq67z1h+SeX3dk5iDZSPnMg5C79N+Vl5Jd/9/l1YL8cu6V5C
JuYvtXxcwopVz1m4MPKz8mpZsflZLvfekpez/JmrX3DcGcqFtCkvZJZ7JV/5ylfOdN9vuP+Eq4IO
qufML8h8phAddEfizlAupE15x84KudRO6fe3U/Qc5tHu8u9++ciJnLPw25SflVfy3e/fhfXSithQ
yF9BXj5jv+zXvOY1P/7xj7PBv/iLv7DJvMCAe1w44v09jMKM9clPftJNZn81JAt4N/MjJY+Vl83q
dH9x5e1vf7v9263mLb17V6fPT0LeW9/6Vm9Py473mpX98Q570/trK2++/2cjj3168xd5cHDwO7/z
O+5gdlf2cNn0/EjE81ZObYorZO6xF9w5bqnkn8NCbsbl57pK7rIePsf/eeyxx37wgx9kmR/96Edm
Jfih7uIxLyTOyYrPdqb38rO86dnNY59S9zhwpPo5C79N7ogrm9U57rvfpwvrJWRJFwqZ6F2Ae1Uu
Z8YDJXdZD5/j/7Bis3x+pHBhuCOubFanphXrjrjyZ3j4/v+VvZwVnrn6BUecoVxIm/JCZuWvKi/7
lDWPvdcfdTx8mv9DB7N8fqRwQbojrmxWhw7mRiLOUC6kTXkhs4691M5x39/C59DjBly0O3Ck+jkL
v03uiCub1Tnuu9+nC+uldWobCob9j9LmnaQ7ODU1Za7YLKxXvvKVr3/969///vf/1V/91cWLFwMD
+QfyRn784x+bx33d615nf3KjMGOZ972f+tSnTPK1r33tpz/96Z2dHS+Wn+WNlDxWXjbL+vu//3s7
/tWvftW7q9PPJ8F6/PHHzck/8pGPfOtb38p+Ayfjnm1+fv4Tn/jEW97yFjPlne9855e//OW9+x8G
a2N379790z/9U/NYr3rVq37v935vbW3N3nvs01t4kQcHB1/72tfe+973msL87M/+7Ic+9KHp6Wl7
lze9cER93sqpTXEFzi2/YKtkqRQ+h3l2ep6f6yq5y3r++ec///nPf+ADHzCPay7bXLy5Bu+jZTvd
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" />
</BODY>
</HTML>
/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,12 → 48,7
 
@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
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);
}
 
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public synchronized void sort(Comparator<? super T> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
final List<T> copy = create(this.size());
for (Object e : a) {
copy.add((T) e);
}
this.immutable = Collections.unmodifiableList(copy);
}
 
// read
 
public synchronized final List<T> getImmutable() {
return this.immutable;
}
 
@Override
public T get(int index) {
return this.getImmutable().get(index);
}
 
@Override
public int size() {
return this.getImmutable().size();
}
 
@Override
public boolean isEmpty() {
return this.getImmutable().isEmpty();
}
 
@Override
public boolean contains(Object o) {
return this.getImmutable().contains(o);
}
 
// equals
 
@Override
public boolean equals(final Object o) {
return this.getImmutable().equals(o);
}
 
@Override
public int hashCode() {
return this.getImmutable().hashCode();
}
}
/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/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;
 
186,7 → 187,22
final Collection<T> res = (Collection<T>) object;
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/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/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/CustomEquals.java
31,6 → 31,15
*/
public class CustomEquals {
 
static public final <T> boolean nullSafeEquals(final T o1, final T o2, final BiPredicate<? super T, ? super T> equal) {
if (o1 == null)
return o2 == null;
else if (o2 == null)
return false;
else
return equal.test(o1, o2);
}
 
static private final HashingStrategy<Object> DEFAULT = new HashingStrategy<Object>() {
@Override
public boolean equals(Object object1, Object object2) {
/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/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/FormatGroup.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,7
*/
public class FormatGroup extends Format {
 
private final List<? extends Format> formats;
private final List<Format> formats;
private int formatIndex;
 
public FormatGroup(Format... formats) {
44,11 → 44,11
public FormatGroup(final List<? extends Format> formats) {
if (formats.size() == 0)
throw new IllegalArgumentException("formats must not be empty");
this.formats = formats;
this.formats = CollectionUtils.toImmutableList(formats);
this.formatIndex = 0;
}
 
public final List<? e