OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Compare Revisions

Regard whitespace Rev 181 → Rev 182

/trunk/OpenConcerto/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-3.17.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/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/pdfbox-2.0.19.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/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/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/VenteFactureSituation.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatVentes.xml
124,4 → 124,28
</element>
</table3>
<element4 location="A4" type="Value" ValueName="DATE">
</element4>
<table4 firstLine="7" endLine="52" endPageLine="54" lastColumn="F" base="Societe" table="SAISIE_VENTE_FACTURE_ELEMENT">
<element location="A" type="fill">
<field base="Societe" name="CODE" />
</element>
 
<element location="B" type="fill">
<field base="Societe" name="NOM" />
</element>
 
<element location="D" type="fill">
<field base="Societe" name="QTE" />
</element>
 
<element location="E" type="fill">
<field base="Societe" name="QTE_REEL" />
</element>
<element location="F" type="fill">
<field base="Societe" name="QTE_MIN" />
</element>
</table4>
</contentDocument>
/trunk/OpenConcerto/Configuration/Template/Default/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/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/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/CommandeClient.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatStocks.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/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/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/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/Courrier.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Commande.xml
25,7 → 25,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/JournauxMois.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FichePayeSimplifiee.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/EtatChargesPaye.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FicheArticle.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/Relance1.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/VenteFacture.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/GrandLivre.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Relance3.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/DemandePrix.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/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/Journaux.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/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/ExportArticle.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Balance.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/SituationCompte.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/SituationCompte.ods
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/OpenConcerto/Configuration/Template/Default/VentilationAnalytique.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/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/BonLivraison.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/FicheRelance.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/AvoirF.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/CommandeClient.xml
24,7 → 24,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
62,7 → 62,7
<field name="NOM" />
</element>
 
<element location="I10" type="fill">
<element location="H10" type="fill">
<field name="ID_CLIENT">
<field name="FORME_JURIDIQUE" />
<field name="NOM" />
69,7 → 69,7
</field>
</element>
 
<element location="I11" type="address.customer.full">
<element location="H11" type="address.customer.full">
 
</element>
 
/trunk/OpenConcerto/Configuration/Template/Default/FichePaye.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/RepartitionAnalytique.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/FactureFournisseur.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/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/JournauxAnalytique.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/Devis.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/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/EtatVentes.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/BonReception.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/ListeFacture.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/ReportingEcoContribution.ods
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/OpenConcerto/Configuration/Template/Default/VenteFacture.xml
25,7 → 25,7
</element>
 
<element location="B6" type="fill">
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIREN "/>
<field base="Common" table="SOCIETE_COMMON" name="NUM_SIRET" prefix="N° de SIRET "/>
</element>
 
<element location="B7" type="replace" replacePattern="_">
/trunk/OpenConcerto/Configuration/Template/Default/SituationCompte.odsp
New file
0,0 → 1,9
<odsp>
<spliteveryrow>
<sheet number="0">58</sheet>
</spliteveryrow>
<offset x="0" y ="0"/>
<resize percent="100"/>
 
 
</odsp>
/trunk/OpenConcerto/Configuration/Template/Default/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/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/Avoir.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/Relance2.odt
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/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/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/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/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/ReportingTaxeComplementaire.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/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/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/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/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.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/src/product.properties
1,5 → 1,5
NAME=OpenConcerto
VERSION=1.7.0
VERSION=1.7.1
ORGANIZATION_NAME=OpenConcerto
ORGANIZATION_ID=org.openconcerto
/trunk/OpenConcerto/src/com/zimbra/common/util/BEncoding.java
New file
0,0 → 1,140
/*
*
*/
 
/*
* Created on Jul 7, 2005
*/
package com.zimbra.common.util;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
 
/**
* @author dkarp
*/
public class BEncoding {
 
public static final class BEncodingException extends Exception {
BEncodingException(String msg) { super(msg); }
BEncodingException(Exception e) { super(e); }
}
 
public static String encode(Map object) {
return encode(new StringBuffer(), object).toString();
}
public static String encode(List object) {
return encode(new StringBuffer(), object).toString();
}
 
public static Object decode(String data) throws BEncodingException {
if (data == null)
return null;
try {
Offset offset = new Offset();
Object result = decode(data.toCharArray(), offset);
if (offset.offset != data.length())
throw new BEncodingException("extra characters at end of encoded string");
return result;
} catch (BEncodingException e) {
throw e;
} catch (Exception e) {
throw new BEncodingException(e);
}
}
 
 
private static StringBuffer encode(StringBuffer sb, Object object) {
if (object instanceof Map) {
SortedMap tree = (object instanceof SortedMap ? (SortedMap) object : new TreeMap((Map) object));
sb.append('d');
if (!tree.isEmpty())
for (Iterator it = tree.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
if (entry.getKey() != null && entry.getValue() != null) {
encode(sb, entry.getKey().toString());
encode(sb, entry.getValue());
}
}
sb.append('e');
} else if (object instanceof List) {
Object value;
sb.append('l');
for (Iterator it = ((List) object).iterator(); it.hasNext(); )
if ((value = it.next()) != null)
encode(sb, value);
sb.append('e');
} else if (object instanceof Long || object instanceof Integer || object instanceof Short || object instanceof Byte) {
sb.append('i').append(object).append('e');
} else if (object != null) {
String value = object.toString();
sb.append(value.length()).append(':').append(value);
}
return sb;
}
 
private static final class Offset {
int offset;
}
 
private static Object decode(char[] buffer, Offset offset) throws BEncodingException {
Object key, value;
char c = buffer[offset.offset++];
switch (c) {
case 'd':
Map map = new HashMap();
while ((key = decode(buffer, offset)) != null) {
if ((value = decode(buffer, offset)) == null)
throw new BEncodingException("missing dictionary value for key " + key.toString());
map.put(key.toString(), value);
}
return map;
 
case 'l':
List list = new ArrayList();
while ((key = decode(buffer, offset)) != null)
list.add(key);
return list;
 
case 'e':
return null;
 
case 'i':
return new Long(readLong(buffer, offset, 'e'));
 
default:
offset.offset--;
long length = readLong(buffer, offset, ':');
int start = offset.offset;
offset.offset += length;
return new String(buffer, start, (int) length);
}
}
 
private static long readLong(char[] buffer, Offset offset, char terminator) {
int start = offset.offset;
while (buffer[offset.offset++] != terminator) ;
return Long.parseLong(new String(buffer, start, offset.offset - start - 1));
}
 
public static void main(String[] args) throws BEncodingException {
List list = new ArrayList();
list.add(new Integer(654));
list.add("hwhergk");
list.add(new StringBuffer("74x"));
Map map = new HashMap();
map.put("testing", new Long(5));
map.put("foo2", "bar");
map.put("herp", list);
map.put("Foo", new Float(6.7));
map.put("yy", new TreeMap());
String encoded = encode(map);
System.out.println(encoded);
System.out.println(decode(encoded));
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/OutlookEmail.vbs
File deleted
/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/system/Powershell.java
New file
0,0 → 1,78
/*
* 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.system;
 
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public final class Powershell {
 
private static final Powershell INSTANCE = new Powershell();
 
// No static methods to future-proof if the syntax changes in the future
static public final Powershell getInstance() {
return INSTANCE;
}
 
static private final Pattern PS_CHARS = Pattern.compile("\\R+|'");
 
private Powershell() {
}
 
public final String quote(String s) {
final StringBuffer sb = new StringBuffer((int) (s.length() * 1.1));
quote(s, sb);
return sb.toString();
}
 
public final void quote(String s, final StringBuffer sb) {
sb.append("'");
final Matcher m = PS_CHARS.matcher(s);
while (m.find()) {
final String replacement;
if (m.group().equals("'")) {
replacement = "''";
} else {
// `n isn't expanded in single quoted strings and it's complicated to handle all
// needed escaping in double quoted strings, so just concatenate a single newline.
replacement = "' + \"`n\" + '";
}
m.appendReplacement(sb, replacement);
}
m.appendTail(sb);
sb.append("'");
}
 
// @("C:\Users\ILM\Documents", "2")
public final String quoteArray(List<String> l) {
final StringBuffer sb = new StringBuffer(l.size() * 64);
sb.append("@(");
final Iterator<String> iter = l.iterator();
while (iter.hasNext()) {
final String s = iter.next();
quote(s, sb);
if (iter.hasNext())
sb.append(", ");
}
sb.append(")");
return sb.toString();
}
 
public final String getEncodedCommand(final String s) {
return java.util.Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_16LE));
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/system/package-info.java
New file
0,0 → 1,17
/*
* 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.
*/
/**
* Contains class to interact with the OS outside the VM.
*/
package org.openconcerto.utils.system;
/trunk/OpenConcerto/src/org/openconcerto/utils/cache/ICacheSupport.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
13,12 → 13,12
package org.openconcerto.utils.cache;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.IScheduledFutureTask;
 
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
178,7 → 178,7
}
 
final synchronized Map<D, CacheWatcher<? super D>> watch(Set<? extends D> data, final CacheItem<?, ?, D> item) {
final Map<D, CacheWatcher<? super D>> res = new LinkedHashMap<D, CacheWatcher<? super D>>(data.size(), 1.0f);
final Map<D, CacheWatcher<? super D>> res = CollectionUtils.newLinkedHashMap(data.size());
for (final D d : data) {
final CacheWatcher<? super D> watcher = this.watch(d, item);
if (watcher != null)
/trunk/OpenConcerto/src/org/openconcerto/utils/cache/ICache.java
32,6 → 32,8
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
 
import net.jcip.annotations.GuardedBy;
 
/**
* To keep results computed from some data. The results will be automatically invalidated after some
* period of time or when the data is modified.
52,7 → 54,9
private final ICacheSupport<D> supp;
// linked to fifo, ATTN the values in this map can be invalid since clear() is called without
// the lock on CacheValue
@GuardedBy("this")
private final LinkedHashMap<K, CacheItem<K, V, D>> cache;
@GuardedBy("this")
private final Map<K, CacheItem<K, V, D>> running;
private final int delay;
private final int size;
224,17 → 228,21
removeRunning(getRunningValFromRes(res));
}
 
private final synchronized void removeRunning(final CacheItem<K, V, D> val) {
private final synchronized boolean removeRunning(final CacheItem<K, V, D> val) {
if (val == null)
return;
return false;
final K key = val.getKey();
if (this.running.get(key) == val)
final boolean removed;
if (this.running.get(key) == val) {
this.removeRunning(key);
else
removed = true;
} else {
// either val wasn't created in this cache or another value was already put in this
// cache
val.setRemovalType(RemovalType.EXPLICIT);
removed = val.setRemovalType(RemovalType.EXPLICIT);
}
assert val.getRemovalType() != null;
return removed;
}
 
private final synchronized void removeRunning(K key) {
427,10 → 435,10
final boolean clear(final CacheItem<K, V, D> val) {
if (val.getRemovalType() == null)
throw new IllegalStateException("Not yet removed : " + val);
final boolean toBeRemoved;
final boolean removedFromRunning, toBeRemoved;
synchronized (this) {
log("clear", val);
this.removeRunning(val);
removedFromRunning = this.removeRunning(val);
toBeRemoved = this.cache.get(val.getKey()) == val;
if (toBeRemoved) {
this.cache.remove(val.getKey());
437,18 → 445,28
}
}
// NOTE these events are often fired with our monitor since this method is called with it
if (removedFromRunning || toBeRemoved) {
this.propSupp.firePropertyChange(new Event<K, V, D>(this, ITEMS_CHANGED, null, null));
this.propSupp.firePropertyChange(this.createItemEvent(ITEM_REMOVED, val, null));
}
return toBeRemoved;
}
 
public final synchronized void clear() {
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.cache.values()))
val.setRemovalType(RemovalType.EXPLICIT);
assert this.size() == 0;
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.cache.values())) {
// We have our monitor so if val is still in us but setRemovalType() was already called,
// then it means another thread is waiting on our monitor in clear(CacheItem). In that
// case, just call it now so that this is empty at the end of this method.
if (!val.setRemovalType(RemovalType.EXPLICIT)) {
final boolean removed = this.clear(val);
assert removed;
}
}
assert this.size() == 0 : this + " expected to be empty but contains : " + this.cache.keySet();
 
for (final CacheItem<K, V, D> val : new ArrayList<CacheItem<K, V, D>>(this.running.values()))
val.setRemovalType(RemovalType.EXPLICIT);
assert this.running.size() == 0;
assert this.running.size() == 0 : this + " expected to have no running but contains : " + this.running.keySet();
}
 
private final void log(String msg, Object subject) {
/trunk/OpenConcerto/src/org/openconcerto/utils/BaseDirs.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
35,6 → 35,18
super(info, subdir);
}
 
private String getHomePath() {
// ATTN on FreeBSD man 8 service : "service command sets HOME to /" thus one should
// probably not use this class (perhaps Portable).
return StringUtils.coalesce(System.getenv("HOME"), System.getProperty("user.home"));
}
 
private String getInHomePath(final String subPath) {
final String homePath = getHomePath();
// If no home, use current working directory
return homePath == null ? subPath : homePath + '/' + subPath;
}
 
@Override
protected File _getAppDataFolder() {
/*
42,7 → 54,7
* should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
* $HOME/.local/share should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_DATA_HOME"), System.getenv("HOME") + "/.local/share"), this.getAppID());
return new File(StringUtils.coalesce(System.getenv("XDG_DATA_HOME"), getInHomePath(".local/share")), this.getAppID());
}
 
@Override
52,7 → 64,7
* configuration files should be stored. If $XDG_CONFIG_HOME is either not set or empty,
* a default equal to $HOME/.config should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_CONFIG_HOME"), System.getenv("HOME") + "/.config"), this.getAppID());
return new File(StringUtils.coalesce(System.getenv("XDG_CONFIG_HOME"), getInHomePath(".config")), this.getAppID());
}
 
@Override
62,7 → 74,7
* non-essential data files should be stored. If $XDG_CACHE_HOME is either not set or
* empty, a default equal to $HOME/.cache should be used.
*/
return new File(StringUtils.coalesce(System.getenv("XDG_CACHE_HOME"), System.getenv("HOME") + "/.cache"), this.getAppID());
return new File(StringUtils.coalesce(System.getenv("XDG_CACHE_HOME"), getInHomePath(".cache")), this.getAppID());
}
}
 
211,7 → 223,8
return res != null ? res : this.getAppID();
}
 
protected File getFolderToWrite(final File dir) throws IOException {
static public final File getFolderToWrite(final File dir) throws IOException {
// MAYBE test for symlink
if (dir.isDirectory() && dir.canWrite())
return dir;
if (dir.exists())
246,7 → 259,7
}
 
protected File _getPreferencesFolder() {
return this.getAppDataFolder();
return this._getAppDataFolder();
}
 
// where to write configuration
271,6 → 284,23
return getFolderToWrite(this.getCacheFolder());
}
 
protected File _getStateFolder() {
return this._getCacheFolder();
}
 
// where to write data that is non-essential but cannot be recreated
// - logfiles
// - state of application windows on exit
// - recently opened files
// See STATE directory in https://wiki.debian.org/XDGBaseDirectorySpecification
public final File getStateFolder() {
return getSubDir(_getStateFolder());
}
 
public final File getStateFolderToWrite() throws IOException {
return getFolderToWrite(this.getStateFolder());
}
 
@Override
public String toString() {
return BaseDirs.class.getSimpleName() + " " + this.getClass().getSimpleName();
/trunk/OpenConcerto/src/org/openconcerto/utils/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<? extends Format> getFormats() {
public final List<Format> getFormats() {
return this.formats;
}
 
/trunk/OpenConcerto/src/org/openconcerto/utils/ProcessStreams.java
21,10 → 21,14
import java.io.PrintStream;
import java.lang.ProcessBuilder.Redirect;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
 
/**
32,55 → 36,41
*
* @author Sylvain
*/
public class ProcessStreams {
public class ProcessStreams implements AutoCloseable {
 
static public enum Action {
/**
* Redirect process streams to ours.
*
* @deprecated use {@link ProcessStreams#redirect(ProcessBuilder)} (or
* {@link Redirect#INHERIT} directly) as it makes sure that
* {@link Process#waitFor()} only returns once all streams are flushed.
*/
REDIRECT,
/**
* Consume streams.
*/
CONSUME,
/**
* Close process streams. NOTE : some programs might fail (e.g. route on FreeBSD), in those
* cases use {@link #CONSUME}.
*/
CLOSE,
/**
* Do nothing, which is dangerous as the process will hang until its output is read.
*/
DO_NOTHING
}
// Don't set too low, this is a maximum, it would only fail on slow computers.
private static final int MAX_SHUTTING_DOWN_DELAY = 500;
 
// Added to Java 9
public static final Redirect DISCARD = Redirect.to(StreamUtils.NULL_FILE);
 
static public final ProcessBuilder redirect(final ProcessBuilder pb) {
return pb.redirectErrorStream(true).redirectOutput(Redirect.INHERIT);
// ATTN don't use redirectErrorStream(true) as this would merge the error and output of pb
// into the VM output.
return pb.redirectError(Redirect.INHERIT).redirectOutput(Redirect.INHERIT);
}
 
static public final Process handle(final Process p, final Action action) throws IOException {
if (action == Action.CLOSE) {
p.getInputStream().close();
p.getErrorStream().close();
} else if (action == Action.REDIRECT) {
new ProcessStreams(p, System.out, System.err);
} else if (action == Action.CONSUME) {
new ProcessStreams(p, StreamUtils.NULL_OS, StreamUtils.NULL_OS);
/**
* Consume all streams. Needed since some programs might fail (e.g. route on FreeBSD) if the
* streams are just closed.
*
* @param proc the process.
* @return the passed process.
*/
static public final Process consume(final Process proc) {
try (final ProcessStreams streams = new ProcessStreams(proc)) {
streams.start(StreamUtils.NULL_OS, StreamUtils.NULL_OS);
streams.awaitTermination();
} catch (Exception e) {
throw new IllegalStateException("Couldn't consume output of " + proc, e);
}
return p;
return proc;
}
 
private final ExecutorService exec = Executors.newFixedThreadPool(2);
private final CountDownLatch latch;
private final Future<?> out;
private final Future<?> err;
private final Process process;
private Future<?> out;
private Future<?> err;
 
/**
* Create a new instance and start reading from the passed process. If a passed
92,23 → 82,35
* @param out where to write the {@link Process#getInputStream() standard output}.
* @param err where to write the {@link Process#getErrorStream() standard error}.
*/
public ProcessStreams(final Process p, final OutputStream out, final OutputStream err) {
this.latch = new CountDownLatch(2);
this.out = writeToAsync(p::getInputStream, out);
this.err = writeToAsync(p::getErrorStream, err);
public ProcessStreams(final Process p) {
this.process = p;
}
 
public final ProcessStreams start(final OutputStream out, final OutputStream err) {
if (this.out != null)
throw new IllegalStateException("Already started");
this.out = writeToAsync(this.process::getInputStream, out);
this.err = writeToAsync(this.process::getErrorStream, err);
assert this.out != null && this.err != null;
this.exec.submit(new Runnable() {
@Override
public void run() {
try {
ProcessStreams.this.latch.await();
} catch (final InterruptedException e) {
// ne rien faire
e.printStackTrace();
// OK even if the future is cancelled before having started
try {
ProcessStreams.this.out.get();
} catch (Exception e) {
}
try {
ProcessStreams.this.err.get();
} catch (Exception e) {
}
} finally {
ProcessStreams.this.exec.shutdown();
}
}
});
return this;
}
 
protected final void stopOut() {
121,16 → 123,42
 
private final void stop(final Future<?> f) {
if (f == null)
return;
// TODO
// ATTN don't interrupt, hangs in readLine()
f.cancel(false);
throw new IllegalStateException("Not started");
// ATTN Process returns InputStream which are not interruptible.
f.cancel(true);
}
 
// From AutoCloseable : close() shouldn't throw InterruptedException
@Override
public void close() {
this.exec.shutdownNow();
}
 
public final void awaitTermination() throws InterruptedException, ExecutionException {
this.out.get();
this.err.get();
if (!this.exec.awaitTermination(MAX_SHUTTING_DOWN_DELAY, TimeUnit.MILLISECONDS))
throw new IllegalStateException("Executor still not terminated after " + MAX_SHUTTING_DOWN_DELAY);
}
 
public final void awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
try {
this.out.get(timeout, unit);
} catch (CancellationException e) {
// OK
}
try {
this.err.get(timeout, unit);
} catch (CancellationException e) {
// OK
}
if (!this.exec.awaitTermination(MAX_SHUTTING_DOWN_DELAY, TimeUnit.MILLISECONDS))
throw new TimeoutException("Executor still not terminated after " + MAX_SHUTTING_DOWN_DELAY);
}
 
private final Future<?> writeToAsync(final Supplier<InputStream> insSupplier, final Object outs) {
if (outs == null) {
this.latch.countDown();
return null;
return CompletableFuture.completedFuture(null);
}
return this.exec.submit(new Callable<Object>() {
@Override
142,8 → 170,6
else
StreamUtils.copy(ins, (OutputStream) outs);
return null;
} finally {
ProcessStreams.this.latch.countDown();
}
}
});
/trunk/OpenConcerto/src/org/openconcerto/utils/TableSorter.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
468,8 → 468,13
} else if (o2 == null) {
comparison = 1;
} else {
comparison = getComparator(column).compare(o1, o2);
final Comparator comparator = getComparator(column);
try {
comparison = comparator.compare(o1, o2);
} catch (Exception e) {
throw new IllegalStateException("Couldn't compare column " + column + " using " + comparator + " for objects " + o1 + " and " + o2, e);
}
}
if (comparison != 0) {
return directive.direction == DESCENDING ? -comparison : comparison;
}
645,6 → 650,11
 
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (column < 0) {
Log.get().severe("out of bound renderer, column :" + column);
return new JLabel("");
}
 
Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (c instanceof JLabel) {
JLabel l = (JLabel) c;
/trunk/OpenConcerto/src/org/openconcerto/utils/io/MailAccount.java
15,6 → 15,7
 
import org.openconcerto.utils.ExceptionUtils;
 
import java.util.Objects;
import java.util.Properties;
 
import javax.mail.Address;
23,6 → 24,8
import javax.mail.PasswordAuthentication;
import javax.mail.SendFailedException;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
 
/**
* A mail account.
107,6 → 110,20
return sb.toString();
}
 
public static final MailAccount create(final String fromAddr, String smtpServer, String smtpLogin, String smtpPassword) throws AddressException {
Objects.requireNonNull(fromAddr, "Missing 'From:' address");
final InternetAddress fromInetAddr = new InternetAddress(fromAddr, true);
if (smtpServer == null) {
smtpServer = "smtp." + fromInetAddr.getAddress().substring(fromInetAddr.getAddress().indexOf('@') + 1);
}
if (smtpLogin == null) {
smtpLogin = fromInetAddr.getAddress().substring(0, fromInetAddr.getAddress().indexOf('@'));
}
final MailAccount res = new MailAccount(fromInetAddr.getPersonal(), fromInetAddr.getAddress(), smtpServer);
res.setAuth(new PasswordAuthentication(smtpLogin, smtpPassword));
return res;
}
 
// nullable
private final String name;
private final String address;
182,7 → 199,8
 
public final void send(final Message msg) throws MessagingException {
final PasswordAuthentication auth = this.getAuth();
msg.getSession().getProperties().setProperty("mail." + getSMTPProtocol() + ".auth", auth == null ? "false" : "true");
final Properties props = msg.getSession().getProperties();
props.setProperty("mail." + getSMTPProtocol() + ".auth", auth == null ? "false" : "true");
try (final Transport mailTransport = msg.getSession().getTransport()) {
if (auth == null)
mailTransport.connect();
189,6 → 207,8
else
mailTransport.connect(auth.getUserName(), auth.getPassword());
mailTransport.sendMessage(msg, msg.getAllRecipients());
} catch (MessagingException e) {
throw new MessagingException("Couldn't send as " + auth.getUserName() + " using\n" + props, e);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/utils/io/JSONConverter.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
73,7 → 73,11
} else if (param instanceof Calendar) {
result = formatCalendar(((Calendar) param));
} else if (param instanceof Class<?>) {
if (param == String.class) {
result = "string";
} else {
result = ((Class<?>) param).getName();
}
} else if (param instanceof Iterable) {
final Iterable<?> tmp = (Iterable<?>) param;
final JSONArray jsonArray = new JSONArray();
82,14 → 86,11
}
result = jsonArray;
} else if (param instanceof Color) {
if (param != null) {
final Color paramColor = (Color) param;
final JSONObject jsonColor = new JSONObject();
jsonColor.put("r", paramColor.getRed());
jsonColor.put("g", paramColor.getGreen());
jsonColor.put("b", paramColor.getBlue());
result = jsonColor;
String hexString = Integer.toHexString(((Color) param).getRGB());
if (hexString.length() > 6) {
hexString = hexString.substring(2, hexString.length());
}
result = "#" + hexString;
} else if (param instanceof BigDecimal) {
result = ((BigDecimal) param).doubleValue();
} else {
132,11 → 133,7
throw new IllegalArgumentException("object (" + o.getClass().getName() + ") is not assignable for '" + type + "', the format is not valid", e);
}
} else if (type.equals(Color.class)) {
final JSONObject jsonColor = (JSONObject) o;
final int r = JSONConverter.getParameterFromJSON(jsonColor, "r", Integer.class);
final int g = JSONConverter.getParameterFromJSON(jsonColor, "g", Integer.class);
final int b = JSONConverter.getParameterFromJSON(jsonColor, "b", Integer.class);
result = type.cast(new Color(r, g, b));
result = type.cast(Color.decode(o.toString()));
} else {
result = type.cast(o);
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/DateTimeFormatBuilder.java
New file
0,0 → 1,82
/*
* 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.text;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.text.DateTimeFormat.Literal;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
 
public final class DateTimeFormatBuilder {
 
static public final DateTimeFormat build(final Object... components) {
final DateTimeFormatBuilder res = new DateTimeFormatBuilder(components.length);
for (final Object o : components) {
if (o instanceof DateTimeFormatComponent)
res.add((DateTimeFormatComponent) o);
else
res.addLiteral(o.toString());
}
return res.build();
}
 
static public final DateTimeSkeleton buildSkeleton(final DateProp... components) {
return new DateTimeSkeleton(CollectionUtils.toImmutableList(components));
}
 
private final List<DateTimeFormatComponent> components;
private boolean onlyDateProp = true;
 
public DateTimeFormatBuilder() {
this(8);
}
 
public DateTimeFormatBuilder(int initialCapacity) {
super();
this.components = new ArrayList<>(initialCapacity);
}
 
public final boolean isEmpty() {
return this.components.isEmpty();
}
 
public final void clear() {
this.components.clear();
this.onlyDateProp = true;
}
 
public final DateTimeFormatBuilder add(DateTimeFormatComponent comp) {
this.components.add(comp);
return this;
}
 
public final DateTimeFormatBuilder addLiteral(final String s) {
this.components.add(new Literal(s));
this.onlyDateProp = false;
return this;
}
 
public final DateTimeFormat build() {
return new DateTimeFormat(this.components);
}
 
public final DateTimeSkeleton buildSkeleton() {
if (!this.onlyDateProp)
throw new IllegalStateException("Not only DateProp");
return new DateTimeSkeleton(Collections.unmodifiableList(CollectionUtils.castList(Objects.requireNonNull(this.components), DateProp.class)));
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/DateProp.java
New file
0,0 → 1,244
/*
* 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.text;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.StringUtils;
import org.openconcerto.utils.text.DateTimeFormat.Literal;
 
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.SignStyle;
import java.time.format.TextStyle;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
import com.ibm.icu.text.DateTimePatternGenerator;
 
public final class DateProp extends DateTimeFormatComponent {
static public final DateProp YEAR = new DateProp("year with four digits");
static public final DateProp MONTH_NAME = new DateProp("full name of the month");
static public final DateProp MONTH_NUMBER = new DateProp("2 digits number of the month (starting at 1)");
static public final DateProp DAY_IN_MONTH = new DateProp("2 digits day number in the month");
static public final DateProp DAY_NAME_IN_WEEK = new DateProp("full name of day");
static public final DateProp HOUR = new DateProp("hour in day (00-23)");
static public final DateProp MINUTE = new DateProp("minute in hour");
static public final DateProp SECOND = new DateProp("second in minute");
static public final DateProp MICROSECOND = new DateProp("microseconds (000000-999999)");
 
static public final Set<DateProp> ALL_INSTANCES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(YEAR, MONTH_NAME, MONTH_NUMBER, DAY_IN_MONTH, DAY_NAME_IN_WEEK, HOUR, MINUTE, SECOND, MICROSECOND)));
static public final Set<DateProp> LOCALE_SENSITIVE_INSTANCES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(MONTH_NAME, DAY_NAME_IN_WEEK)));
 
static public final DateTimeSkeleton TIME_SKELETON = DateTimeFormatBuilder.buildSkeleton(HOUR, MINUTE, SECOND);
static public final DateTimeSkeleton SHORT_DATE_SKELETON = DateTimeFormatBuilder.buildSkeleton(DAY_IN_MONTH, MONTH_NUMBER, YEAR);
static public final DateTimeSkeleton LONG_DATE_SKELETON = DateTimeFormatBuilder.buildSkeleton(DAY_NAME_IN_WEEK, DAY_IN_MONTH, MONTH_NAME, YEAR);
static public final DateTimeSkeleton SHORT_DATETIME_SKELETON = DateTimeFormatBuilder.buildSkeleton(DAY_IN_MONTH, MONTH_NUMBER, YEAR, HOUR, MINUTE);
static public final DateTimeSkeleton LONG_DATETIME_SKELETON = DateTimeFormatBuilder.buildSkeleton(DAY_NAME_IN_WEEK, DAY_IN_MONTH, MONTH_NAME, YEAR, HOUR, MINUTE, SECOND);
 
// pure format (i.e. no literal string)
static private final Map<DateProp, String> JAVA_DATE_SPECS_PURE;
static public final Map<DateProp, String> DATE_PROP_TO_JAVA;
static private final SortedMap<String, DateProp> REVERSE_JAVA_SPEC;
static private final Pattern REVERSE_SPEC_PATTERN;
 
static {
JAVA_DATE_SPECS_PURE = new HashMap<DateProp, String>();
JAVA_DATE_SPECS_PURE.put(YEAR, "yyyy");
JAVA_DATE_SPECS_PURE.put(MONTH_NAME, "MMMM");
JAVA_DATE_SPECS_PURE.put(MONTH_NUMBER, "MM");
JAVA_DATE_SPECS_PURE.put(DAY_IN_MONTH, "dd");
JAVA_DATE_SPECS_PURE.put(DAY_NAME_IN_WEEK, "EEEE");
JAVA_DATE_SPECS_PURE.put(HOUR, "HH");
JAVA_DATE_SPECS_PURE.put(MINUTE, "mm");
JAVA_DATE_SPECS_PURE.put(SECOND, "ss");
 
final Map<DateProp, String> tmp = new HashMap<>(DateProp.JAVA_DATE_SPECS_PURE);
tmp.put(DateProp.MICROSECOND, "SSS000");
DATE_PROP_TO_JAVA = Collections.unmodifiableMap(tmp);
assert DATE_PROP_TO_JAVA.keySet().equals(ALL_INSTANCES);
 
// reverse, so longer strings come first (e.g. MMMM|MM to match the longer one)
final SortedMap<String, DateProp> m = new TreeMap<>(Collections.reverseOrder());
REVERSE_JAVA_SPEC = CollectionUtils.invertMap(m, JAVA_DATE_SPECS_PURE);
assert REVERSE_JAVA_SPEC.size() == JAVA_DATE_SPECS_PURE.size() : "Duplicate values";
assert !JAVA_DATE_SPECS_PURE.containsKey(null) : "Null spec";
assert !JAVA_DATE_SPECS_PURE.containsValue(null) : "Null value";
 
REVERSE_SPEC_PATTERN = Pattern.compile(CollectionUtils.join(REVERSE_JAVA_SPEC.keySet(), "|"));
}
 
/**
* Convert the passed pattern to a {@link SimpleDateFormat} pattern.
*
* @param simpleFormat either {@link #ALL_INSTANCES} or arbitrary literal string that will be
* quoted.
* @return the {@link SimpleDateFormat java} pattern.
*/
public static String toJavaPattern(final List<? extends DateTimeFormatComponent> simpleFormat) {
return toStringPattern(simpleFormat, DATE_PROP_TO_JAVA::get, StringUtils::singleQuote);
}
 
public static final String toStringPattern(final List<? extends DateTimeFormatComponent> simpleFormat, final Function<? super DateProp, String> datePropToString,
final Function<String, String> literalToString) {
final StringBuilder sb = new StringBuilder(simpleFormat.size() * 6);
// needed because if there's 2 consecutive literal text then 'text1''text2' which evaluates
// to text1'text2.
final StringBuilder literalText = new StringBuilder(64);
for (final DateTimeFormatComponent p : simpleFormat) {
if (p instanceof Literal) {
literalText.append(((Literal) p).getValue());
} else {
final String javaComp = Objects.requireNonNull(datePropToString.apply((DateProp) p));
if (literalText.length() > 0) {
sb.append(literalToString.apply(literalText.toString()));
literalText.setLength(0);
}
sb.append(javaComp);
}
}
if (literalText.length() > 0) {
sb.append(literalToString.apply(literalText.toString()));
}
return sb.toString();
}
 
public static DateTimeFormatterBuilder createJavaFormatterBuilder(final List<? extends DateTimeFormatComponent> simpleFormat) {
final DateTimeFormatterBuilder res = new DateTimeFormatterBuilder();
for (final DateTimeFormatComponent p : simpleFormat) {
if (p instanceof Literal) {
res.appendLiteral(((Literal) p).getValue());
} else {
// from DateTimeFormatterBuilder.appendPattern()
if (p == YEAR)
res.appendValue(ChronoField.YEAR, 4, 19, SignStyle.EXCEEDS_PAD);
else if (p == MONTH_NAME)
res.appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL);
else if (p == MONTH_NUMBER)
res.appendValue(ChronoField.MONTH_OF_YEAR, 2);
else if (p == DAY_IN_MONTH)
res.appendValue(ChronoField.DAY_OF_MONTH, 2);
else if (p == DAY_NAME_IN_WEEK)
res.appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL);
else if (p == HOUR)
res.appendValue(ChronoField.HOUR_OF_DAY, 2);
else if (p == MINUTE)
res.appendValue(ChronoField.MINUTE_OF_HOUR, 2);
else if (p == SECOND)
res.appendValue(ChronoField.SECOND_OF_MINUTE, 2);
else if (p == MICROSECOND)
res.appendValue(ChronoField.MICRO_OF_SECOND, 6);
else
throw new IllegalArgumentException("Unknown " + p);
}
}
return res;
}
 
/**
* Return the best pattern matching the input skeleton.
*
* @param simpleFormat the fields needed, e.g. [YEAR, DAY_IN_MONTH, MONTH_NUMBER].
* @param l the locale needed.
* @return the best match, e.g. "dd/MM/yyyy" for {@link Locale#FRANCE} , "MM/dd/yyyy" for
* {@link Locale#US}.
*/
public static String getBestJavaPattern(final List<DateProp> simpleFormat, final Locale l) {
final StringBuilder sb = new StringBuilder(128);
for (final DateProp p : simpleFormat) {
final String javaComp = JAVA_DATE_SPECS_PURE.get(p);
if (javaComp != null)
sb.append(javaComp);
else
throw new IllegalArgumentException("Unsupported spec : " + p);
}
// needs same length so our pattern works
return DateTimePatternGenerator.getInstance(l).getBestPattern(sb.toString(), DateTimePatternGenerator.MATCH_ALL_FIELDS_LENGTH);
}
 
/**
* Return the best pattern matching the input skeleton.
*
* @param simpleFormat the fields needed, e.g. [YEAR, DAY_IN_MONTH, MONTH_NUMBER].
* @param l the locale needed.
* @return the best match, e.g. [DAY_IN_MONTH, "/", MONTH_NUMBER, "/", YEAR] for
* {@link Locale#FRANCE} , [MONTH_NUMBER, "/", DAY_IN_MONTH, "/", YEAR] for
* {@link Locale#US}.
*/
public static DateTimeFormat getBestPattern(final List<DateProp> simpleFormat, final Locale l) {
return parseJavaPattern(getBestJavaPattern(simpleFormat, l));
}
 
static DateTimeFormat parseJavaPattern(final String bestPattern) {
final Matcher matcher = REVERSE_SPEC_PATTERN.matcher(bestPattern);
final Matcher quotedMatcher = StringUtils.SINGLE_QUOTED_PATTERN.matcher(bestPattern);
final DateTimeFormatBuilder res = new DateTimeFormatBuilder();
int index = 0;
while (index < bestPattern.length()) {
final int quoteIndex = bestPattern.indexOf('\'', index);
final int endSansQuote = quoteIndex < 0 ? bestPattern.length() : quoteIndex;
 
// parse quote-free string
matcher.region(index, endSansQuote);
while (matcher.find()) {
if (index < matcher.start())
res.addLiteral(bestPattern.substring(index, matcher.start()));
res.add(REVERSE_JAVA_SPEC.get(matcher.group()));
index = matcher.end();
}
assert index <= endSansQuote : "region() failed";
if (index < endSansQuote)
res.addLiteral(bestPattern.substring(index, endSansQuote));
index = endSansQuote;
 
// parse quoted string
if (index < bestPattern.length()) {
quotedMatcher.region(index, bestPattern.length());
if (!quotedMatcher.find() || quotedMatcher.start() != quotedMatcher.regionStart())
throw new IllegalStateException("Quoted string error : " + bestPattern.substring(quoteIndex));
res.addLiteral(StringUtils.unSingleQuote(quotedMatcher));
index = quotedMatcher.end();
}
}
return res.build();
}
 
private final String name;
 
private DateProp(String name) {
this.name = name;
}
 
public final String getName() {
return this.name;
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + ' ' + this.getName();
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/BooleanFormat.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,9 → 18,13
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.Locale;
import java.util.ResourceBundle;
 
import net.jcip.annotations.Immutable;
 
@Immutable
public class BooleanFormat extends Format {
static private final boolean[] BOOLEAN_VALUES = new boolean[] { false, true };
static private final BooleanFormat NUMBER_INSTANCE = new BooleanFormat("0", "1");
39,10 → 43,18
return new BooleanFormat(getFormattedBooleans(l, true));
}
 
static public BooleanFormat createTrueFalse(final Locale l) {
return new BooleanFormat(getFormattedBooleans(l, false));
}
 
static public final BooleanFormat getNumberInstance() {
return NUMBER_INSTANCE;
}
 
static public final BooleanFormat[] getAll(final Locale l) {
return new BooleanFormat[] { createTrueFalse(l), createYesNo(l), getNumberInstance() };
}
 
private final String[] formattedValues;
 
public BooleanFormat() {
53,6 → 65,7
this(new String[] { falseValue, trueValue });
}
 
// not public because parameter isn't copied
private BooleanFormat(final String[] formattedValues) {
this.formattedValues = formattedValues;
}
88,4 → 101,29
pos.setErrorIndex(pos.getIndex());
return null;
}
 
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(this.formattedValues);
return result;
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final BooleanFormat other = (BooleanFormat) obj;
return Arrays.equals(this.formattedValues, other.formattedValues);
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " with " + Arrays.asList(this.formattedValues);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/DateTimeFormatComponent.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.text;
 
public abstract class DateTimeFormatComponent {
 
DateTimeFormatComponent() {
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/TypesFormats.java
New file
0,0 → 1,51
/*
* 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.text;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.i18n.I18nUtils;
 
import java.util.List;
 
import net.jcip.annotations.Immutable;
 
@Immutable
public final class TypesFormats {
private final boolean yesNoBoolean;
private final List<DateTimeSkeleton> dateSkeletons, timeSkeletons, datetimeSkeletons;
 
public TypesFormats(boolean yesNoBoolean, List<DateTimeSkeleton> dateSkeletons, List<DateTimeSkeleton> timeSkeletons, List<DateTimeSkeleton> datetimeSkeletons) {
super();
this.yesNoBoolean = yesNoBoolean;
this.dateSkeletons = CollectionUtils.toImmutableList(dateSkeletons);
this.timeSkeletons = CollectionUtils.toImmutableList(timeSkeletons);
this.datetimeSkeletons = CollectionUtils.toImmutableList(datetimeSkeletons);
}
 
public final String getBooleanKey(final boolean b) {
return this.yesNoBoolean ? I18nUtils.getYesNoKey(b) : I18nUtils.getBooleanKey(b);
}
 
public final List<DateTimeSkeleton> getDateSkeletons() {
return this.dateSkeletons;
}
 
public final List<DateTimeSkeleton> getTimeSkeletons() {
return this.timeSkeletons;
}
 
public final List<DateTimeSkeleton> getDatetimeSkeletons() {
return this.datetimeSkeletons;
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/DateTimeSkeleton.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.text;
 
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
 
import net.jcip.annotations.Immutable;
 
@Immutable
public final class DateTimeSkeleton extends DateTimeComponents {
 
private final List<DateProp> components;
 
DateTimeSkeleton(List<DateProp> components) {
super();
this.components = components;
}
 
@Override
public final List<DateProp> getComponents() {
return this.components;
}
 
public final String getBestJavaPattern(Locale l) {
return DateProp.getBestJavaPattern(this.getComponents(), l);
}
 
public final SimpleDateFormat createBestJavaFormat(Locale l) {
return new SimpleDateFormat(this.getBestJavaPattern(l), l);
}
 
public final DateTimeFormat getBestPattern(Locale l) {
return DateProp.getBestPattern(this.getComponents(), l);
}
 
public final DateTimeFormatter createBestJavaFormatter(Locale l) {
return this.getBestPattern(l).createJavaFormatter(l);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/DateTimeComponents.java
New file
0,0 → 1,49
/*
* 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.text;
 
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Objects;
 
abstract class DateTimeComponents {
 
public abstract List<? extends DateTimeFormatComponent> getComponents();
 
/**
* Convert this pattern to a {@link SimpleDateFormat} pattern.
*
* @return the {@link SimpleDateFormat java} pattern.
*/
public final String toJavaPattern() {
return DateProp.toJavaPattern(this.getComponents());
}
 
@Override
public int hashCode() {
return Objects.hash(this.getComponents());
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final DateTimeComponents other = (DateTimeComponents) obj;
return this.getComponents().equals(other.getComponents());
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/text/DateTimeFormat.java
New file
0,0 → 1,83
/*
* 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.text;
 
import org.openconcerto.utils.CollectionUtils;
 
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
 
import net.jcip.annotations.Immutable;
 
@Immutable
public final class DateTimeFormat extends DateTimeComponents {
 
public static final class Literal extends DateTimeFormatComponent {
private final String value;
 
Literal(String value) {
super();
this.value = Objects.requireNonNull(value);
}
 
public final String getValue() {
return this.value;
}
 
@Override
public int hashCode() {
return Objects.hash(this.value);
}
 
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Literal other = (Literal) obj;
return this.value.equals(other.value);
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + ' ' + this.getValue();
}
}
 
private final List<DateTimeFormatComponent> components;
 
DateTimeFormat(List<? extends DateTimeFormatComponent> components) {
super();
this.components = CollectionUtils.toImmutableList(Objects.requireNonNull(components));
}
 
@Override
public final List<DateTimeFormatComponent> getComponents() {
return this.components;
}
 
public final DateTimeFormatterBuilder createJavaFormatterBuilder() {
return DateProp.createJavaFormatterBuilder(this.getComponents());
}
 
public final DateTimeFormatter createJavaFormatter(final Locale locale) {
return this.createJavaFormatterBuilder().toFormatter(locale);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/change/ListChangeRecorder.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
21,7 → 21,7
 
/**
* A class that wraps a list, to detect every change made to it. The changes are available with
* {@link #getRecipe()}.
* {@link #getRecipe()}. Thread-safe if the delegate is itself thread-safe and not keeping history.
*
* @author Sylvain
*
32,11 → 32,11
private final List<E> delegate;
private final ListChangeRecipe<E> recipe;
 
public ListChangeRecorder(List<E> delegate) {
public ListChangeRecorder(final List<E> delegate) {
this(delegate, false);
}
 
public ListChangeRecorder(List<E> delegate, final boolean keepHistory) {
public ListChangeRecorder(final List<E> delegate, final boolean keepHistory) {
super();
this.delegate = delegate;
this.recipe = new ListChangeRecipe<E>(keepHistory);
52,47 → 52,58
 
// ** read only
 
public E get(int index) {
@Override
public E get(final int index) {
return this.delegate.get(index);
}
 
@Override
public int size() {
return this.delegate.size();
}
 
@Override
public Object[] toArray() {
return this.delegate.toArray();
}
 
public <T> T[] toArray(T[] a) {
@Override
public <T> T[] toArray(final T[] a) {
return this.delegate.toArray(a);
}
 
public boolean contains(Object o) {
@Override
public boolean contains(final Object o) {
return this.delegate.contains(o);
}
 
public boolean containsAll(Collection<?> c) {
@Override
public boolean containsAll(final Collection<?> c) {
return this.delegate.containsAll(c);
}
 
public boolean equals(Object o) {
@Override
public boolean equals(final Object o) {
return this.delegate.equals(o);
}
 
@Override
public int hashCode() {
return this.delegate.hashCode();
}
 
public int indexOf(Object o) {
@Override
public int indexOf(final Object o) {
return this.delegate.indexOf(o);
}
 
@Override
public boolean isEmpty() {
return this.delegate.isEmpty();
}
 
public int lastIndexOf(Object o) {
@Override
public int lastIndexOf(final Object o) {
return this.delegate.lastIndexOf(o);
}
 
99,7 → 110,8
// ** write, always change the delegate before notifying the recipe
// otherwise the listeners will be told of the changes before they even happened
 
public boolean add(E e) {
@Override
public synchronized boolean add(final E e) {
final boolean res = this.delegate.add(e);
// -1 since this just grew by one
this.recipe.add(this.size() - 1, Collections.singleton(e));
106,12 → 118,14
return res;
}
 
public void add(int index, E e) {
@Override
public synchronized void add(final int index, final E e) {
this.delegate.add(index, e);
this.recipe.add(index, Collections.singleton(e));
}
 
public boolean addAll(Collection<? extends E> c) {
@Override
public synchronized boolean addAll(final Collection<? extends E> c) {
final int size = this.size();
final boolean res = this.delegate.addAll(c);
this.recipe.add(size, c);
118,25 → 132,29
return res;
}
 
public boolean addAll(int index, Collection<? extends E> c) {
@Override
public synchronized boolean addAll(final int index, final Collection<? extends E> c) {
final boolean res = this.delegate.addAll(index, c);
this.recipe.add(index, c);
return res;
}
 
public void clear() {
@Override
public synchronized void clear() {
final List<E> copy = (List<E>) ListChangeIndex.copy(this);
this.delegate.clear();
this.recipe.remove(0, copy.size() - 1, copy);
}
 
public E set(int index, E element) {
@Override
public synchronized E set(final int index, final E element) {
final E res = this.delegate.set(index, element);
this.recipe.set(index, res, element);
return res;
}
 
public E remove(int index) {
@Override
public synchronized E remove(final int index) {
final E res = this.delegate.remove(index);
this.recipe.remove(index, index, Collections.singletonList(res));
return res;
144,7 → 162,8
 
// objects
 
public boolean remove(Object o) {
@Override
public synchronized boolean remove(final Object o) {
final int index = this.indexOf(o);
if (index < 0)
return false;
154,17 → 173,19
}
}
 
public boolean removeAll(Collection<?> c) {
@Override
public boolean removeAll(final Collection<?> c) {
return this.changeAll(c, true);
}
 
public boolean retainAll(Collection<?> c) {
@Override
public boolean retainAll(final Collection<?> c) {
return this.changeAll(c, false);
}
 
private boolean changeAll(Collection<?> c, boolean remove) {
private synchronized boolean changeAll(final Collection<?> c, final boolean remove) {
boolean modified = false;
Iterator<?> e = iterator();
final Iterator<?> e = iterator();
while (e.hasNext()) {
if (c.contains(e.next()) == remove) {
e.remove();
/trunk/OpenConcerto/src/org/openconcerto/utils/change/ListChangeIndex.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
23,12 → 23,12
 
public abstract class ListChangeIndex<T> implements ListChange<T> {
 
protected final static <T> Collection<T> copy(Collection<T> col) {
protected final static <T> Collection<T> copy(final Collection<T> col) {
Collection<T> res;
try {
// tries to keep the same class
res = CopyUtils.copy(col);
} catch (RuntimeException e) {
} catch (final RuntimeException e) {
// but this doesn't always work (see sublist())
// so just use a plain ArrayList
res = new ArrayList<T>(col);
39,7 → 39,7
private final int index0;
private final int index1;
 
public ListChangeIndex(int index0, int index1) {
public ListChangeIndex(final int index0, final int index1) {
super();
this.index0 = index0;
this.index1 = index1;
61,7 → 61,7
 
private final List<T> removed;
 
public Rm(int index0, int index1, final List<T> removed) {
public Rm(final int index0, final int index1, final List<T> removed) {
super(index0, index1);
// ok to cast : either it copies it : List<T>, either it uses an arrayList which
// implements List<T>
68,15 → 68,18
this.removed = (List<T>) copy(removed);
}
 
public <U> void apply(List<U> l, ITransformer<T, U> transf) {
@Override
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) {
// sublist exclusive
l.subList(this.getIndex0(), this.getIndex1() + 1).clear();
}
 
@Override
public List<? extends T> getItemsAdded() {
return Collections.emptyList();
}
 
@Override
public List<T> getItemsRemoved() {
return this.removed;
}
91,12 → 94,13
 
private final Collection<? extends T> added;
 
public Add(int index0, Collection<? extends T> added) {
public Add(final int index0, final Collection<? extends T> added) {
super(index0, index0);
this.added = copy(added);
}
 
public <U> void apply(List<U> l, ITransformer<T, U> transf) {
@Override
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) {
final List<U> toAdd = new ArrayList<U>();
for (final T t : this.added) {
toAdd.add(transf.transformChecked(t));
104,10 → 108,12
l.addAll(this.getIndex0(), toAdd);
}
 
@Override
public Collection<? extends T> getItemsAdded() {
return this.added;
}
 
@Override
public List<? extends T> getItemsRemoved() {
return Collections.emptyList();
}
123,20 → 129,23
private final T removed;
private final T added;
 
public Set(int index, T removed, T added) {
public Set(final int index, final T removed, final T added) {
super(index, index);
this.removed = removed;
this.added = added;
}
 
public <U> void apply(List<U> l, ITransformer<T, U> transf) {
@Override
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) {
l.set(this.getIndex0(), transf.transformChecked(this.added));
}
 
@Override
public List<? extends T> getItemsAdded() {
return Collections.singletonList(this.added);
}
 
@Override
public List<? extends T> getItemsRemoved() {
return Collections.singletonList(this.removed);
}
/trunk/OpenConcerto/src/org/openconcerto/utils/change/ListChangeRecipe.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
22,7 → 22,10
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
 
import net.jcip.annotations.GuardedBy;
 
/**
* Allow to propagate ListChange to listeners and bound lists. Can also store all changes that it
* was notified and replay them with {@link #apply(List, ITransformer)}.
39,10 → 42,12
// don't use PropertyChangeSupport since it isn't type safe and we're only interested in the
// last change (and not the whole property, i.e. the whole list)
private final List<IClosure<? super ListChangeIndex<T>>> listeners;
@GuardedBy("this")
private final Map<List<?>, Pair<?>> boundLists;
 
/**
* Create a new instance. Recording is only necessary for {@link #apply(List, ITransformer)}.
* Thread-safe only if not recording.
*
* @param record <code>true</code> if all changes should be kept (this will leak memory until
* {@link #clear()} is called).
50,7 → 55,7
public ListChangeRecipe(final boolean record) {
super();
this.changes = record ? new ArrayList<ListChangeIndex<T>>() : null;
this.listeners = new ArrayList<IClosure<? super ListChangeIndex<T>>>();
this.listeners = new CopyOnWriteArrayList<>();
// need IdentityHashMap since List.equals() depend on its items
// which will change
this.boundLists = new IdentityHashMap<List<?>, Pair<?>>();
60,6 → 65,11
return this.changes != null;
}
 
/**
* The list of changes since the last {@link #clear()}. Not thread-safe.
*
* @return the list of changes
*/
public final List<ListChangeIndex<T>> getChanges() {
if (!this.recordChanges())
throw new IllegalStateException("This instance wasn't created to record changes");
66,15 → 76,15
return this.changes;
}
 
public void addListener(IClosure<? super ListChangeIndex<T>> l) {
public void addListener(final IClosure<? super ListChangeIndex<T>> l) {
this.listeners.add(l);
}
 
public void rmListener(IClosure<? super ListChangeIndex<T>> l) {
public void rmListener(final IClosure<? super ListChangeIndex<T>> l) {
this.listeners.remove(l);
}
 
public void bind(List<T> l) {
public void bind(final List<T> l) {
this.bind(l, Transformer.<T> nopTransformer());
}
 
85,18 → 95,19
* @param l the list to keep in sync.
* @param transf the transformer.
*/
public <U> void bind(List<U> l, ITransformer<T, U> transf) {
public synchronized <U> void bind(final List<U> l, final ITransformer<T, U> transf) {
this.boundLists.put(l, new Pair<U>(l, transf));
}
 
public <U> void unbind(List<U> l) {
public synchronized <U> void unbind(final List<U> l) {
this.boundLists.remove(l);
}
 
private final void add(ListChangeIndex<T> change) {
private final void add(final ListChangeIndex<T> change) {
if (this.recordChanges())
this.changes.add(change);
// must change bounded lists first, otherwise listeners couldn't access them
synchronized (this) {
for (final Pair<?> p : this.boundLists.values()) {
p.apply(change);
}
103,16 → 114,17
for (final IClosure<? super ListChangeIndex<T>> l : this.listeners)
l.executeChecked(change);
}
}
 
public void add(int index0, Collection<? extends T> c) {
public void add(final int index0, final Collection<? extends T> c) {
this.add(new ListChangeIndex.Add<T>(index0, c));
}
 
public void remove(int index0, int index1, List<T> removed) {
public void remove(final int index0, final int index1, final List<T> removed) {
this.add(new ListChangeIndex.Rm<T>(index0, index1, removed));
}
 
public void set(int index0, T old, T newItem) {
public void set(final int index0, final T old, final T newItem) {
this.add(new ListChangeIndex.Set<T>(index0, old, newItem));
}
 
134,7 → 146,7
* changes}.
*/
@Override
public <U> void apply(List<U> l, ITransformer<T, U> transf) throws IllegalStateException {
public <U> void apply(final List<U> l, final ITransformer<T, U> transf) throws IllegalStateException {
for (final ListChange<T> change : this.getChanges()) {
change.apply(l, transf);
}
145,13 → 157,13
private final List<U> l;
private final ITransformer<T, U> transf;
 
public Pair(List<U> l, ITransformer<T, U> transf) {
public Pair(final List<U> l, final ITransformer<T, U> transf) {
super();
this.l = l;
this.transf = transf;
}
 
void apply(ListChange<T> change) {
void apply(final ListChange<T> change) {
change.apply(this.l, this.transf);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/ListMap.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> ListMapItf<K, V> unmodifiableMap(ListMapItf<K, V> map) {
if (map.isEmpty())
return empty();
return new Unmodifiable<K, V>(map);
}
 
/trunk/OpenConcerto/src/org/openconcerto/utils/ReentrantEventDispatcher.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,12
 
/**
* Allow to maintain the dispatching of events in order when a listener itself fires an event.
* <p>
* If each notification simply goes through a list of listeners, the events are delivered out of
* order : <img src="doc-files/reentrantEventsNaive.png" />
* <p>
* This class adds new notifications at the end of a list, but resume notifying from the start of
* the list. <img src="doc-files/reentrantEventsIn-order.png" />
*
* @author sylvain
*
30,12 → 36,30
*/
public final class ReentrantEventDispatcher<L, E, X extends Exception> {
 
private final class DispatchingState extends Tuple3<Iterator<L>, BiConsumerExn<L, E, X>, E> {
public DispatchingState(final Iterator<L> iter, BiConsumerExn<L, E, X> callback, final E evt) {
super(Objects.requireNonNull(iter, "Missing iterator"), Objects.requireNonNull(callback, "Missing callback"), evt);
public final class DispatchingState {
private final Iterator<L> iter;
private final BiConsumerExn<L, E, X> callback;
private final E evt;
 
private DispatchingState(final Iterator<L> iter, BiConsumerExn<L, E, X> callback, final E evt) {
this.iter = Objects.requireNonNull(iter, "Missing iterator");
this.callback = Objects.requireNonNull(callback, "Missing callback");
this.evt = evt;
}
 
private final Iterator<L> getIterator() {
return this.iter;
}
 
private final BiConsumerExn<L, E, X> getCallback() {
return this.callback;
}
 
public final E getEvt() {
return this.evt;
}
}
 
private final ThreadLocal<LinkedList<DispatchingState>> events = new ThreadLocal<LinkedList<DispatchingState>>() {
@Override
protected LinkedList<DispatchingState> initialValue() {
55,14 → 79,22
}
 
public final void fire(final Iterator<L> iter, final E evt) throws X {
this.fire(iter, this.callback, evt);
this.fire(this.createDispatchingState(iter, evt));
}
 
public final void fire(final Iterator<L> iter, final BiConsumerExn<L, E, X> callback, final E evt) throws X {
this.fire(new DispatchingState(iter, callback, evt));
this.fire(this.createDispatchingState(iter, callback, evt));
}
 
private final void fire(final DispatchingState newTuple) throws X {
public final DispatchingState createDispatchingState(final Iterator<L> iter, final E evt) {
return this.createDispatchingState(iter, this.callback, evt);
}
 
public final DispatchingState createDispatchingState(final Iterator<L> iter, final BiConsumerExn<L, E, X> callback, final E evt) {
return new DispatchingState(iter, callback, evt);
}
 
public final void fire(final DispatchingState newTuple) throws X {
final LinkedList<DispatchingState> linkedList = this.events.get();
// add new event
linkedList.addLast(newTuple);
69,16 → 101,34
// process all pending events
DispatchingState currentTuple;
while ((currentTuple = linkedList.peekFirst()) != null) {
final Iterator<L> currentIter = currentTuple.get0();
final BiConsumerExn<L, E, X> currentCallback = currentTuple.get1();
final E currentEvt = currentTuple.get2();
final Iterator<L> currentIter = currentTuple.getIterator();
final BiConsumerExn<L, E, X> currentCallback = currentTuple.getCallback();
final E currentEvt = currentTuple.getEvt();
while (currentIter.hasNext()) {
final L l = currentIter.next();
currentCallback.accept(l, currentEvt);
}
/*
* It isn't because one callback failed that the event itself didn't happen or should be
* reverted : we should still notify the other callbacks. So don't use a finally block
* to remove currentTuple. But if the event should indeed be reverted (e.g. the callback
* was a check), #remove(DispatchingState) should be called.
*/
// not removeFirst() since the item might have been already removed
linkedList.pollFirst();
}
}
 
/**
* Remove a dispatching state. Useful because if there's an exception while notifying the
* listeners, then the next {@link #fire(DispatchingState)} in the same thread will by default
* resume notifying the listeners. That behaviour is not valid for a reverted DB transaction.
*
* @param dispatchingState what to remove.
* @return {@code true} if the item was actually removed.
* @see #createDispatchingState(Iterator, Object)
*/
public final boolean remove(DispatchingState dispatchingState) {
return this.events.get().remove(dispatchingState);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/doml/DOMLStreamReader.java
New file
0,0 → 1,138
/*
* 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.doml;
 
import java.io.BufferedReader;
import java.io.IOException;
 
public class DOMLStreamReader {
public static final int EOF = 0;
public static final int TAG_START = 1;
public static final int TAG_END = 2;
public static final int TAG_END_COMPACT = 3;
public static final int NO_TAG_CHARACTER = 4;
private final PushBackReader br;
final StringBuilder bElementName = new StringBuilder(2048);
 
public DOMLStreamReader(BufferedReader r) {
this.br = new PushBackReader(r, 2);
}
 
public final int readNextToken() throws IOException {
char c = (char) this.br.read();
while (c > 0) {
if (c == ' ' || c == '\t' || c == '\n') {
c = (char) this.br.read();
} else {
if (c == '<') {
return TAG_START;
} else if (c == '>') {
return TAG_END;
} else if (c == '/') {
c = (char) this.br.read();
if (c == '>') {
return TAG_END_COMPACT;
} else {
unread(c);
}
} else {
this.br.unread(c);
return NO_TAG_CHARACTER;
}
}
}
return EOF;
}
 
public final String readTagName() throws IOException {
this.bElementName.setLength(0);
char c = (char) this.br.read();
while (c > 0) {
if (c == '>') {
this.br.unread('>');
return this.bElementName.toString();
} else if (c == ' ' || c == '\t') {
return this.bElementName.toString();
} else {
this.bElementName.append(c);
}
c = (char) this.br.read();
}
 
return this.bElementName.toString();
}
 
public final void unread(char c) throws IOException {
this.br.unread(c);
}
 
public final char readNextChar() throws IOException {
return (char) this.br.read();
}
 
public final String readAttributeName() throws IOException {
this.bElementName.setLength(0);
char c = (char) this.br.read();
while (c > 0) {
if (c == '>') {
this.br.unread('>');
return this.bElementName.toString();
} else if (c == '=' || c == ' ' || c == '\t' || c == '\n') {
return this.bElementName.toString();
} else {
this.bElementName.append(c);
}
c = (char) this.br.read();
}
 
return this.bElementName.toString();
}
 
public final String readAttributeValue() throws IOException {
this.bElementName.setLength(0);
boolean open = false;
char c = (char) this.br.read();
if (c != '\"') {
throw new IllegalStateException("missing \"");
}
while (c > 0) {
if (c == '\\') {
c = (char) this.br.read();
if (c == '\\' || c == '"') {
this.bElementName.append(c);
} else if (c == 'r') {
this.bElementName.append('\r');
} else if (c == 't') {
this.bElementName.append('\t');
} else if (c == 'n') {
this.bElementName.append('\n');
} else {
throw new IllegalStateException("invalid escaped character :" + c);
}
} else if (c == '"') {
if (open) {
return this.bElementName.toString();
}
open = true;
} else if (c < 32) {
continue;
} else {
this.bElementName.append(c);
}
c = (char) this.br.read();
}
return this.bElementName.toString();
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/doml/Document.java
New file
0,0 → 1,286
/*
* 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.doml;
 
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
 
public class Document {
 
private Element root;
private byte[] buffer = new byte[64];
 
public Document() {
}
 
public Document(String rootName) {
this.root = new Element(rootName);
}
 
public Document(Element root) {
this.root = root;
}
 
public void loadFrom(BufferedReader br) throws IOException {
this.root = readElement(new DOMLStreamReader(br), 0);
}
 
private Element readElement(DOMLStreamReader r, int depth) throws IOException {
int t = r.readNextToken();
if (t == DOMLStreamReader.EOF || t == DOMLStreamReader.NO_TAG_CHARACTER) {
return null;
}
if (t != DOMLStreamReader.TAG_START) {
throw new IOException("not element start found (" + t + ")");
}
final String tagName = r.readTagName();
final Element current = new Element(tagName);
t = r.readNextToken();
if (t == DOMLStreamReader.TAG_END_COMPACT) {
return current;
} else if (t == DOMLStreamReader.TAG_END) {
readChildren(r, depth, tagName, current);
return current;
} else if (t == DOMLStreamReader.NO_TAG_CHARACTER) {
while (t == DOMLStreamReader.NO_TAG_CHARACTER) {
final String name = r.readAttributeName();
final String value = r.readAttributeValue();
current.setAttribute(name, value);
t = r.readNextToken();
}
if (t == DOMLStreamReader.TAG_END) {
readChildren(r, depth, tagName, current);
}
return current;
 
}
return null;
}
 
private void readChildren(DOMLStreamReader r, int depth, String tagName, Element current) throws IOException {
int t;
boolean tryReadNextChild = false;
do {
char n1 = r.readNextChar();
while (n1 < 33) {
// Optimization for : n1 == ' ' || n1 == '\n' || n1 == '\r' || n1 == '\t'
n1 = r.readNextChar();
}
if (n1 == 65535) {
// EOF
return;
}
 
tryReadNextChild = false;
if (n1 == '<') {
char n2 = r.readNextChar();
if (n2 != '/') {
tryReadNextChild = true;
r.unread(n2);
r.unread(n1);
Element e = readElement(r, depth + 1);
if (e != null) {
current.addChild(e);
}
} else {
r.unread(n2);
r.unread(n1);
}
}
 
} while (tryReadNextChild);
 
// end tag
t = r.readNextToken();
 
if (t != DOMLStreamReader.TAG_START) {
throw new IOException("not closing element start found for " + tagName + " (" + t + ") : '" + r.readNextChar());
}
 
final String ctagName = r.readTagName();
 
t = r.readNextToken();
if (t != DOMLStreamReader.TAG_END) {
throw new IOException("closing element end tag for '" + ctagName + "' is missing " + t);
}
}
 
public void writeTo(BufferedWriter writer) throws IOException {
if (this.root == null)
return;
writeTo(writer, 0, this.root);
writer.flush();
}
 
private Element readElement(BufferedInputStream in) throws IOException {
 
final String name = readUTF(in);
final int attributeCount = readInt(in);
final int childrenCount = readInt(in);
final Element e = new Element(name, attributeCount, childrenCount);
for (int i = 0; i < attributeCount; i++) {
final String n = readUTF(in);
final String v = readUTF(in);
e.addAttributeNoCheck(n, v);
}
for (int i = 0; i < childrenCount; i++) {
e.addChildNoCheck(readElement(in));
}
return e;
}
 
public void loadFromBinary(BufferedInputStream in) throws IOException {
this.root = readElement(in);
}
 
public void loadFromBinary(File file) throws IOException {
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
this.root = readElement(in);
}
}
 
public void loadFromBinary(byte[] bytes) throws IOException {
try (BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(bytes))) {
this.root = readElement(in);
}
}
 
public void writeToBinary(BufferedOutputStream out) throws IOException {
if (this.root == null) {
return;
}
writeTo(out, this.root);
out.flush();
}
 
public void writeToBinary(File file) throws IOException {
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
this.writeToBinary(out);
}
}
 
private void writeTo(BufferedOutputStream out, Element e) throws IOException {
// ObjectStream are not used because readUTF allocates too much char[]
writeUTF(out, e.getName());
final int attributeCount = e.getAttributeCount();
writeInt(out, attributeCount);
writeInt(out, e.getChildrenCount());
// write attributes
if (attributeCount > 0) {
e.writeAttributes(out);
}
// write children
for (Element c : e.getChildren()) {
writeTo(out, c);
}
}
 
public byte[] toByteArray() throws IOException {
if (this.root == null) {
return new byte[0];
}
final ByteArrayOutputStream out2 = new ByteArrayOutputStream(1024 + this.root.getChildrenCount() * 32);
try (BufferedOutputStream out = new BufferedOutputStream(out2)) {
this.writeToBinary(out);
}
return out2.toByteArray();
}
 
private static final void writeInt(BufferedOutputStream out, int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v) & 0xFF);
}
 
static final void writeUTF(BufferedOutputStream out, String str) throws IOException {
final byte[] buf = str.getBytes(StandardCharsets.UTF_8);
writeInt(out, buf.length);
out.write(buf);
}
 
private final int readInt(BufferedInputStream in) throws IOException {
final int ch1 = in.read();
final int ch2 = in.read();
final int ch3 = in.read();
final int ch4 = in.read();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
}
 
private final String readUTF(BufferedInputStream in) throws IOException {
final int size = readInt(in);
if (this.buffer.length < size) {
this.buffer = new byte[size + 32];
}
final int s = in.read(this.buffer, 0, size);
return new String(this.buffer, 0, s, StandardCharsets.UTF_8);
}
 
@Override
public String toString() {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
writeTo(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
return out.toString(StandardCharsets.UTF_8.name());
} catch (IOException e) {
return e.getMessage();
}
}
 
private void writeTo(final BufferedWriter writer, int depth, Element e) throws IOException {
if (e.getChildrenCount() == 0) {
for (int i = 0; i < depth; i++) {
writer.append(' ');
}
writer.append('<');
writer.append(e.getName());
e.appendAttributes(writer);
writer.append(" />\n");
} else {
for (int i = 0; i < depth; i++) {
writer.append(' ');
}
writer.append('<');
writer.append(e.getName());
e.appendAttributes(writer);
writer.append(">\n");
 
int d = depth + 1;
for (Element child : e.getChildren()) {
writeTo(writer, d, child);
}
for (int i = 0; i < depth; i++) {
writer.append(' ');
}
writer.append("</");
writer.append(e.getName());
writer.append(">\n");
}
}
 
public Element getRoot() {
return this.root;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/doml/PushBackReader.java
New file
0,0 → 1,130
/*
* 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.doml;
 
import java.io.IOException;
import java.io.Reader;
 
public class PushBackReader extends Reader {
/**
* The underlying character-input stream.
*/
protected Reader in;
/** Pushback buffer */
private char[] buf;
 
/** Current position in buffer */
private int pos;
 
/**
* Creates a new pushback reader with a pushback buffer of the given size.
*
* @param in The reader from which characters will be read
* @param size The size of the pushback buffer
* @exception IllegalArgumentException if {@code size <= 0}
*/
public PushBackReader(Reader in, int size) {
this.in = in;
if (size <= 0) {
throw new IllegalArgumentException("size <= 0");
}
this.buf = new char[size];
this.pos = size;
}
 
/**
* Creates a new pushback reader with a one-character pushback buffer.
*
* @param in The reader from which characters will be read
*/
public PushBackReader(Reader in) {
this(in, 1);
}
 
/**
* Reads a single character.
*
* @return The character read, or -1 if the end of the stream has been reached
*
* @exception IOException If an I/O error occurs
*/
@Override
public int read() throws IOException {
if (pos < this.buf.length)
return this.buf[pos++];
else
return this.in.read();
}
 
/**
* Pushes back a single character by copying it to the front of the pushback buffer. After this
* method returns, the next character to be read will have the value <code>(char)c</code>.
*
* @param c The int value representing a character to be pushed back
*
* @exception IOException If the pushback buffer is full, or if some other I/O error occurs
*/
public void unread(int c) throws IOException {
this.buf[--pos] = (char) c;
}
 
/**
* Marks the present position in the stream. The <code>mark</code> for class
* <code>PushbackReader</code> always throws an exception.
*
* @exception IOException Always, since mark is not supported
*/
@Override
public void mark(int readAheadLimit) throws IOException {
throw new IOException("mark/reset not supported");
}
 
/**
* Resets the stream. The <code>reset</code> method of <code>PushbackReader</code> always throws
* an exception.
*
* @exception IOException Always, since reset is not supported
*/
@Override
public void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
 
/**
* Tells whether this stream supports the mark() operation, which it does not.
*/
@Override
public boolean markSupported() {
return false;
}
 
/**
* Closes the stream and releases any system resources associated with it. Once the stream has
* been closed, further read(), unread(), ready(), or skip() invocations will throw an
* IOException. Closing a previously closed stream has no effect.
*
* @exception IOException If an I/O error occurs
*/
@Override
public void close() throws IOException {
this.in.close();
this.buf = null;
}
 
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
throw new IOException("read not supported");
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/doml/Element.java
New file
0,0 → 1,586
/*
* 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.doml;
 
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
 
public class Element {
private String name;
 
private List<String> attributes;
private List<Element> children;
 
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = ThreadLocal.withInitial(() -> {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return dateFormat;
});
 
private static final ThreadLocal<DecimalFormat> DOUBLE_FORMAT = ThreadLocal.withInitial(() -> {
final DecimalFormat f = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
f.setMaximumFractionDigits(340); // 340 = DecimalFormat.DOUBLE_FRACTION_DIGITS
return f;
});
 
private static final ThreadLocal<DecimalFormat> FLOAT_FORMAT = ThreadLocal.withInitial(() -> {
final DecimalFormat f = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
f.setMaximumFractionDigits(170);
return f;
});
 
/**
* Element names are case-sensitive
*
* Element names can contain letters, digits,colons, hyphens, underscores, and periods.
*
* Element names cannot contain spaces
*/
 
public Element(String name) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
this.name = name;
}
 
public Element(String name, int expectedAttributesCount) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
this.name = name;
if (expectedAttributesCount > 0) {
this.attributes = new ArrayList<>(expectedAttributesCount * 2);
}
}
 
public Element(String name, int expectedAttributesCount, int expecteChildrenCount) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
this.name = name;
if (expectedAttributesCount > 0) {
this.attributes = new ArrayList<>(expectedAttributesCount * 2);
}
if (expecteChildrenCount > 0) {
this.children = new ArrayList<>(expecteChildrenCount);
}
}
 
public String getName() {
return this.name;
}
 
public void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
this.name = name;
}
 
public int getAttributeCount() {
if (this.attributes == null)
return 0;
return this.attributes.size() / 2;
}
 
// String
 
public String getAttribute(String name) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
if (this.attributes == null) {
return null;
}
final int size = this.attributes.size();
for (int i = 0; i < size; i += 2) {
if (this.attributes.get(i).equals(name)) {
return this.attributes.get(i + 1);
}
}
return null;
}
 
public String getAttribute(String name, String defaultValue) {
final String v = getAttribute(name);
if (v == null) {
return defaultValue;
}
return v;
}
 
public void setAttribute(String name, String value) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
if (value == null) {
throw new IllegalArgumentException("null value for attribute " + name);
}
if (this.attributes == null) {
this.attributes = new ArrayList<>();
} else {
final int size = this.attributes.size();
for (int i = 0; i < size; i += 2) {
if (this.attributes.get(i).equals(name)) {
this.attributes.set(i + 1, value);
return;
}
}
}
this.attributes.add(name);
this.attributes.add(value);
}
 
// Integer
 
public Integer getAttributeAsInteger(String name) {
final String v = getAttribute(name);
if (v == null) {
return null;
}
return Integer.parseInt(v);
}
 
public int getAttributeAsInteger(String name, int defaultValue) {
final String v = getAttribute(name);
if (v == null) {
return defaultValue;
}
return Integer.parseInt(v);
}
 
public void setAttribute(String name, int value) {
setAttribute(name, String.valueOf(value));
}
 
// Long
 
public Long getAttributeAsLong(String name) {
final String v = getAttribute(name);
if (v == null) {
return null;
}
return Long.parseLong(v);
}
 
public long getAttributeAsLong(String name, long defaultValue) {
final String v = getAttribute(name);
if (v == null) {
return defaultValue;
}
return Long.parseLong(v);
}
 
public void setAttribute(String name, long value) {
setAttribute(name, String.valueOf(value));
}
 
// Float
 
public Float getAttributeAsFloat(String name) {
final String v = getAttribute(name);
if (v == null) {
return null;
}
return parseFloat(v);
}
 
public float getAttributeAsFloat(String name, float defaultValue) {
final String v = getAttribute(name);
if (v == null) {
return defaultValue;
}
return parseFloat(v);
}
 
public void setAttribute(String name, float value) {
setAttribute(name, floatToString(value));
}
 
// Double
 
public Double getAttributeAsDouble(String name) {
final String v = getAttribute(name);
if (v == null) {
return null;
}
return parseDouble(name);
}
 
public double getAttributeAsLong(String name, double defaultValue) {
final String v = getAttribute(name);
if (v == null) {
return defaultValue;
}
return parseDouble(name);
}
 
public void setAttribute(String name, double value) {
setAttribute(name, doubleToString(value));
}
 
public static String doubleToString(final double d) {
if (StrictMath.rint(d) == d && Double.isFinite(d)) {
return Long.toString((long) d);
} else {
return DOUBLE_FORMAT.get().format(d);
}
}
 
private static double parseDouble(String v) {
boolean neg = false;
int start = 0;
if (v.charAt(0) == '-') {
neg = true;
start = 1;
}
double result = 0;
int length = v.length();
int pointIndex = length;
for (int i = start; i < length; i++) {
char c = v.charAt(i);
if (c == '.') {
pointIndex = i;
break;
}
}
for (int i = start; i < pointIndex; i++) {
result = 10 * result + (v.charAt(i) - '0');
}
int n = 0;
int div = 1;
for (int i = pointIndex + 1; i < length; i++) {
n = 10 * n + (v.charAt(i) - '0');
div = div * 10;
}
 
result += ((double) n) / div;
if (neg) {
result = (result * -1f);
}
return result;
}
 
public static String floatToString(final double d) {
if (StrictMath.rint(d) == d && Double.isFinite(d)) {
return Long.toString((long) d);
} else {
return FLOAT_FORMAT.get().format(d);
 
}
}
 
private static float parseFloat(String v) {
boolean neg = false;
int start = 0;
if (v.charAt(0) == '-') {
neg = true;
start = 1;
}
float result = 0;
int length = v.length();
int pointIndex = length;
for (int i = start; i < length; i++) {
char c = v.charAt(i);
if (c == '.') {
pointIndex = i;
break;
}
}
for (int i = start; i < pointIndex; i++) {
result = 10 * result + (v.charAt(i) - '0');
}
int n = 0;
int div = 1;
for (int i = pointIndex + 1; i < length; i++) {
n = 10 * n + (v.charAt(i) - '0');
div = div * 10;
}
 
result += ((float) n) / div;
if (neg) {
result = (result * -1f);
}
return result;
}
// BigDecimal
 
public BigDecimal getAttributeAsBigDecimal(String name) {
final String v = getAttribute(name);
if (v == null) {
return null;
}
return new BigDecimal(v);
}
 
public BigDecimal getAttributeAsBigDecimal(String name, BigDecimal defaultValue) {
final String v = getAttribute(name);
if (v == null) {
return defaultValue;
}
return new BigDecimal(v);
}
 
public void setAttribute(String name, BigDecimal value) {
setAttribute(name, String.valueOf(value));
}
 
// Boolean
 
public Boolean getAttributeAsBoolean(String name) {
final String v = getAttribute(name);
if (v == null) {
return null;
}
if (name.equals("true")) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
 
public boolean getAttributeAsBoolean(String name, boolean defaultValue) {
final String v = getAttribute(name);
if (v == null) {
return defaultValue;
}
return name.equals("true");
}
 
public void setAttribute(String name, boolean value) {
setAttribute(name, value ? "true" : "false");
}
 
// Date
 
public Date getAttributeAsDate(String name) {
final String v = getAttribute(name);
if (v == null)
return null;
try {
 
return DATE_FORMAT.get().parse(v);
} catch (ParseException e) {
throw new IllegalStateException("cannot parse date : '" + v + "' : " + e);
}
}
 
public Date getAttributeAsDate(String name, Date defaultValue) {
final Date v = getAttributeAsDate(name);
if (v == null)
return defaultValue;
return v;
}
 
public void setAttribute(String name, Date value) {
setAttribute(name, value.toInstant().toString());
}
 
// Instant
 
public Instant getAttributeAsInstant(String name) {
String v = getAttribute(name);
if (v == null)
return null;
return Instant.parse(v);
 
}
 
public void setAttribute(String name, Instant value) {
setAttribute(name, value.toString());
}
 
public void addChild(Element child) {
if (child == null) {
throw new IllegalArgumentException("null child");
}
if (this.children == null) {
this.children = new ArrayList<>();
}
this.children.add(child);
}
 
public void addChildren(List<Element> children) {
if (children == null) {
throw new IllegalArgumentException("null children");
}
if (this.children == null) {
this.children = new ArrayList<>(children.size() * 2);
}
this.children.addAll(children);
}
 
/**
* This returns the first child element within this element with the given name. If no elements
* exist for the specified name and namespace, null is returned.
*
* @param name of child element to match
* @return the first matching child element, or null if not found
*/
public Element getChild(String name) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
if (this.children == null) {
return null;
}
for (Element e : this.children) {
if (e.getName().equals(name)) {
return e;
}
}
return null;
}
 
public List<Element> getChildren(String name) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
if (this.children == null) {
return Collections.emptyList();
}
final List<Element> result = new ArrayList<>();
for (Element e : this.children) {
if (e.getName().equals(name)) {
result.add(e);
}
}
return result;
}
 
/**
* This returns a <code>List</code> of all the child elements nested directly (one level deep)
* within this element, as <code>Element</code> objects. If this target element has no nested
* elements, an empty List is returned. The returned list is unmodifiable.
*
* No recursion is performed.
*
* @return list of child <code>XXMLElement</code> objects for this element
*/
public List<Element> getChildren() {
if (this.children == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(children);
}
 
/**
* <p>
* This removes the first child element (one level deep) with the given name. Returns true if a
* child was removed.
* </p>
*
* @param name of child element to remove
* @return whether deletion occurred
*/
public boolean removeChild(final String name) {
if (name == null) {
throw new IllegalArgumentException("null name");
}
if (this.children == null) {
return false;
}
final int size = this.children.size();
for (int i = 0; i < size; i++) {
if (this.children.get(i).getName().equals(name)) {
this.children.remove(i);
return true;
}
}
return false;
}
 
public int getChildrenCount() {
if (this.children == null) {
return 0;
}
return this.children.size();
}
 
void appendAttributes(BufferedWriter writer) throws IOException {
if (this.attributes == null) {
return;
}
final int size = this.attributes.size();
for (int i = 0; i < size; i += 2) {
writer.append(' ');
writer.append(this.attributes.get(i));
writer.append('=');
writer.append('\"');
writer.append(escapeAttributeValue(this.attributes.get(i + 1)));
writer.append('\"');
}
}
 
void writeAttributes(BufferedOutputStream out) throws IOException {
final int size = this.attributes.size();
for (int i = 0; i < size; i += 2) {
Document.writeUTF(out, this.attributes.get(i));
Document.writeUTF(out, this.attributes.get(i + 1));
}
}
 
private String escapeAttributeValue(String string) {
StringBuilder b = new StringBuilder(string.length());
int length = string.length();
for (int i = 0; i < length; i++) {
char c = string.charAt(i);
if (c == '\n') {
b.append('\\');
b.append('n');
} else if (c == '\r') {
b.append('\\');
b.append('r');
} else if (c == '\t') {
b.append('\\');
b.append('t');
} else if (c == '\\') {
b.append('\\');
b.append('\\');
} else if (c == '"') {
b.append('\\');
b.append('"');
} else {
b.append(c);
}
}
return b.toString();
}
 
void addAttributeNoCheck(String name, String value) {
this.attributes.add(name);
this.attributes.add(value);
}
 
void addChildNoCheck(Element child) {
if (child == null) {
throw new IllegalArgumentException("null child");
}
this.children.add(child);
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/BloomFilter.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
41,7 → 41,7
public BloomFilter(int bitArraySize, int expectedElements) {
this.bitArraySize = bitArraySize;
this.expectedElements = expectedElements;
this.k = (int) Math.ceil((bitArraySize / expectedElements) * Math.log(2.0));
this.k = (int) Math.ceil(((double) bitArraySize / expectedElements) * Math.log(2.0));
bitSet = new BitSet(bitArraySize);
}
 
148,7 → 148,7
}
 
public static void main(String[] args) {
BloomFilter<String> set = new BloomFilter<String>(100, 5);
BloomFilter<String> set = new BloomFilter<>(100, 5);
 
// Add some things to the bloom filter
set.add("dog");
/trunk/OpenConcerto/src/org/openconcerto/utils/model/ListComboBoxModel.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,8
*/
public class ListComboBoxModel<T> extends DefaultIMutableListModel<T> implements MutableComboBoxModel<T> {
 
private boolean truncated;
 
public ListComboBoxModel() {
this(Collections.<T> emptyList());
}
44,4 → 46,12
this.removeForJRE(obj);
}
 
public void setTruncated(boolean truncated) {
this.truncated = truncated;
}
 
public boolean isTruncated() {
return this.truncated;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/Platform.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.utils.cc.ITransformer;
 
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
25,6 → 26,7
import java.net.InetAddress;
import java.net.SocketException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
40,6 → 42,8
*/
public abstract class Platform {
 
public static final String PROCESS_ALLOW_AMBIGUOUS_COMMANDS = "jdk.lang.Process.allowAmbiguousCommands";
 
private static final int PING_TIMEOUT = 250;
 
public static final Platform getInstance() {
55,6 → 59,19
}
}
 
public static class CannotPassArgumentException extends RuntimeException {
private final String arg;
 
private CannotPassArgumentException(String arg, String message) {
super("Cannot pass " + arg + message);
this.arg = arg;
}
 
public final String getArg() {
return this.arg;
}
}
 
public abstract boolean supportsPID();
 
public abstract boolean isRunning(final int pid) throws IOException;
61,6 → 78,14
 
public abstract String getPath(final File f);
 
public String getPath(final Path p) {
return this.getPath(p.toFile());
}
 
public String getProcessArg(final String arg) {
return arg;
}
 
public final String getPID() throws IOException {
// TODO remove reflection and getPreJava9PID() once on java 11
try {
153,7 → 178,19
// Keep errors out of cmdSubstitution() (e.g. "ping: sendto: Message too long" when
// setDontFragment(true))
final Process proc = evalPB(command).redirectErrorStream(false).start();
final String output = cmdSubstitution(proc);
 
// some programs won't write anything until they read everything
proc.getOutputStream().close();
final ByteArrayOutputStream out = new ByteArrayOutputStream(5 * 1024);
final ByteArrayOutputStream err = new ByteArrayOutputStream(2 * 1024);
try (final ProcessStreams streams = new ProcessStreams(proc)) {
streams.start(out, err);
streams.awaitTermination();
} catch (Exception e) {
throw new IllegalStateException("Couldn't capture output of ping", e);
}
 
final String output = out.toString();
try {
this.waitForSuccess(proc, "ping");
final List<String> countAndLastLine = StringUtils.splitIntoLines(output);
164,7 → 201,7
final BigDecimal averageRTT = replied == 0 ? null : parsePingAverageRT(countAndLastLine.get(1).trim());
return new PingResult(totalCount, replied, requiredCount, averageRTT);
} catch (Exception e) {
throw new IllegalStateException("Couldn't use output :<<<\n" + output + "\n<<<", e);
throw new IllegalStateException("Couldn't use output :<<<\n" + output + "\n<<<\nerr:<<<\n" + err.toString() + "\n<<<", e);
}
}
 
230,11 → 267,17
return this.exitStatus(p) == 0;
}
 
@Override
public String getPath(final File f) {
return f.getPath();
}
 
@Override
public String getPath(final Path f) {
return f.toString();
}
 
@Override
protected String getBash() {
return "bash";
}
381,9 → 424,129
}
};
 
private static final class CygwinPlatform extends Platform {
// return 171 for "1.8.0_171"
// return 11 for "11.0.11"
public static final int getUpdateVersion(final char sep) {
final String vers = System.getProperty("java.version");
final int lastIndexOf = vers.lastIndexOf(sep);
// e.g. "13"
if (lastIndexOf < 0)
return 0;
return Integer.parseInt(vers.substring(lastIndexOf + 1));
}
 
public static abstract class WindowsPlatform extends Platform {
// on Windows program themselves are required to parse the command line, thus a lot of them
// do it differently, see "How Command Line Parameters Are Parsed"
// https://daviddeley.com/autohotkey/parameters/parameters.htm
 
static private final Pattern quotePatrn = Pattern.compile("([\\\\]*)\"");
static private final Pattern endSlashPatrn = Pattern.compile("([\\\\]+)\\z");
 
static private boolean needsQuoting(String s) {
final int len = s.length();
if (len == 0) // empty string have to be quoted
return true;
for (int i = 0; i < len; i++) {
switch (s.charAt(i)) {
case ' ':
case '\t':
case '"':
return true;
}
}
return false;
}
 
// see http://bugs.sun.com/view_bug.do?bug_id=6468220
// e.g. find.exe, choice.exe
public String quoteParamForMsftC(String s) {
if (!needsQuoting(s))
return s;
if (s.length() > 0) {
// replace '(\*)"' by '$1$1\"', e.g. '\quote " \"' by '\quote \" \\\"'
// $1 needed so that the backslash we add isn't escaped itself by a preceding
// backslash
s = quotePatrn.matcher(s).replaceAll("$1$1\\\\\"");
// replace '(\*)\z' by '$1$1', e.g. 'foo\' by 'foo\\'
// needed to not escape closing quote
s = endSlashPatrn.matcher(s).replaceAll("$1$1");
}
return '"' + s + '"';
}
 
@Override
public String getProcessArg(String arg) {
return this.getProcessArg(arg, false);
}
 
public final String getScriptProcessArg(String arg) {
return this.getProcessArg(arg, true);
}
 
private String getProcessArg(String arg, final boolean script) {
// Perhaps should have one method for .exe and one for .cmd/.bat (ProcessImpl checks
// with isShellFile() and isExe()).
 
if (script && arg.indexOf('"') >= 0)
throw new CannotPassArgumentException(arg, ", it contains a double quote which is always removed by wscript!SplitCommandLine()");
 
/*
* If has VERIFICATION_WIN32_SAFE && !allowAmbiguousCommands, then ProcessImpl behaves
* almost correctly : the argument we pass arrives as-is to the process (so the caller
* shouldn't quote, ProcessImpl will fail with "Malformed argument has embedded quote"
* if a character was escaped). Otherwise ProcessImpl does nothing if arg begins and
* ends with double quotes (it's the caller responsibility to correctly quote).
*/
final boolean doubleQuote;
// 1.8 or 11
final String specVers = System.getProperty("java.specification.version");
// 8 or 11
final int jreFamilyVersion = Integer.parseInt(specVers.startsWith("1.") ? specVers.substring(2) : specVers);
final String javaVendor = System.getProperty("java.vendor");
// VERIFICATION_WIN32_SAFE thanks to https://nvd.nist.gov/vuln/detail/CVE-2019-2958
// For Oracle and OpenJDK :
// https://www.oracle.com/java/technologies/javase/8u231-relnotes.html#JDK-8221858
// https://github.com/openjdk/jdk/commit/5a98b8cfb0cd4c5ce84746e9fa5e42a86d1b2d24#diff-71e1dde85b2937bbf08d0bfd9e8a2e3553c578a3f58907ba6f4f30ec350db184
// For AdoptOpenJDK :
// https://github.com/AdoptOpenJDK/openjdk-jdk11u/commit/0074850a15405a676a492d09e4955d4053966bbc#diff-71e1dde85b2937bbf08d0bfd9e8a2e3553c578a3f58907ba6f4f30ec350db184
if (jreFamilyVersion >= 14 || (javaVendor.equals("AdoptOpenJDK") && (jreFamilyVersion == 11 && getUpdateVersion('.') >= 5 || jreFamilyVersion == 13 && getUpdateVersion('.') >= 1))
|| (jreFamilyVersion == 8 && getUpdateVersion('_') >= 231)) {
// copied from ProcessImpl
final SecurityManager security = System.getSecurityManager();
final String value = System.getProperty(PROCESS_ALLOW_AMBIGUOUS_COMMANDS, (security == null ? "true" : "false"));
final boolean allowAmbiguousCommands = !"false".equalsIgnoreCase(value);
if ((script || allowAmbiguousCommands) && arg.endsWith("\\")) {
// with script, we always do our own quoting since ProcessBuilder will double
// back slashes.
if (script || needsQuoting(arg)) {
throw new CannotPassArgumentException(arg,
", if we'd double quote, then the string would end with 2 back slashes and 1 double quote, but ProcessImpl.unQuote() : 'not properly quoted, treat as unquoted'. So it would get quoted a second time. Try setting '"
+ PROCESS_ALLOW_AMBIGUOUS_COMMANDS + "' to 'false'.");
} else {
doubleQuote = false;
}
} else if (!allowAmbiguousCommands && arg.length() >= 2 && arg.charAt(0) == '"' && arg.charAt(arg.length() - 1) == '"') {
throw new CannotPassArgumentException(arg,
"\nif we pass it as-is then ProcessImpl will consider the string already quoted and needsEscaping() will return false, thus the quotes will be removed by CommandLineToArgvW()"
+ "\nif we quote it then ProcessImpl.needsEscaping() will throw 'Malformed argument has embedded quote'");
} else {
doubleQuote = allowAmbiguousCommands;
}
} else {
doubleQuote = true;
}
 
if (script)
return '"' + arg + '"';
else
return doubleQuote ? quoteParamForMsftC(arg) : arg;
}
}
 
private static final class CygwinPlatform extends WindowsPlatform {
 
@Override
public boolean supportsPID() {
return false;
}
/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/DesktopEnvironment.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
160,62 → 160,12
}
 
static public final class Windows extends DEisOS {
static private boolean needsQuoting(String s) {
final int len = s.length();
if (len == 0) // empty string have to be quoted
return true;
for (int i = 0; i < len; i++) {
switch (s.charAt(i)) {
case ' ':
case '\t':
case '\\':
case '"':
return true;
}
}
return false;
}
 
// on Windows program themselves are required to parse the command line, thus a lot of them
// do it differently, see http://www.autohotkey.net/~deleyd/parameters/parameters.htm
 
static private final Pattern quotePatrn = Pattern.compile("([\\\\]*)\"");
static private final Pattern endSlashPatrn = Pattern.compile("([\\\\]*)\\z");
 
// see http://bugs.sun.com/view_bug.do?bug_id=6468220
// e.g. find.exe, choice.exe
public String quoteParamForMsftC(String s) {
if (!needsQuoting(s))
return s;
if (s.length() > 0) {
// replace '(\*)"' by '$1$1\"', e.g. '\quote " \"' by '\quote \" \\\"'
// $1 needed so that the backslash we add isn't escaped itself by a preceding
// backslash
s = quotePatrn.matcher(s).replaceAll("$1$1\\\\\"");
// replace '(\*)\z' by '$1$1', e.g. 'foo\' by 'foo\\'
// needed to not escape closing quote
s = endSlashPatrn.matcher(s).replaceAll("$1$1");
}
return '"' + s + '"';
}
 
// e.g. bash.exe
public String quoteParamForGCC(String s) {
return StringUtils.doubleQuote(s);
}
 
public String quoteParamForScript(final String s) {
if (s.indexOf('"') >= 0)
throw new IllegalArgumentException("Can not pass a double quote as part of a parameter");
return '"' + s + '"';
}
 
@Override
public String quoteParamForExec(final String s) {
return quoteParamForMsftC(s);
}
}
 
static public final class Mac extends DEisOS {
 
// From CarbonCore/Folders.h
398,8 → 348,8
}
 
// on some systems arguments are not passed correctly by ProcessBuilder
public String quoteParamForExec(String s) {
return s;
public final String quoteParamForExec(String s) {
return Platform.getInstance().getProcessArg(s);
}
 
@Override
/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/Cookies.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
21,6 → 21,7
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
 
import com.google.gson.reflect.TypeToken;
 
187,6 → 188,21
return res;
}
 
public final <T> Collection<T> computeCollectionIfAbsent(Object k, Class<T> clazz, Function<Object, ? extends Collection<T>> mappingFunction) {
Collection<T> res;
synchronized (this) {
res = this.getCollection(k, clazz);
// same algorithm as java.util.Map
if (res == null) {
res = mappingFunction.apply(k);
if (res != null)
this.putCollection(k, res, clazz);
}
}
return res;
}
 
 
public final <T> List<T> getList(Object k, Class<T> clazz) {
return (List<T>) this.getCollection(k, clazz);
}
202,6 → 218,20
return res;
}
 
public final <K, V> Map<K, V> computeMapIfAbsent(Object k, Class<K> keyClass, Class<V> valueClass, Function<Object, ? extends Map<K, V>> mappingFunction) {
Map<K, V> res;
synchronized (this) {
res = this.getMap(k, keyClass, valueClass);
// same algorithm as java.util.Map
if (res == null) {
res = mappingFunction.apply(k);
if (res != null)
this.putMap(k, res, keyClass, valueClass);
}
}
return res;
}
 
public final <T> T getGeneric(Object k, TypeToken<T> clazz) {
final Object object = checkType(k, clazz, "putGeneric()");
@SuppressWarnings("unchecked")
209,6 → 239,20
return res;
}
 
public final <T> T computeIfAbsent(Object k, TypeToken<T> clazz, Function<Object, ? extends T> mappingFunction) {
T res;
synchronized (this) {
res = this.getGeneric(k, clazz);
// same algorithm as java.util.Map
if (res == null) {
res = mappingFunction.apply(k);
if (res != null)
this.putGeneric(k, res, clazz);
}
}
return res;
}
 
@Override
public synchronized String toString() {
return this.getClass().getSimpleName() + " of " + this.map.keySet();
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/LinkedIdentitySet.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
13,12 → 13,8
package org.openconcerto.utils.cc;
 
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
 
/**
* An IdentitySet maintaining insertion order.
26,119 → 22,25
* @param <E> the type of elements maintained by this set
* @see IdentityHashSet
*/
public final class LinkedIdentitySet<E> extends AbstractSet<E> implements IdentitySet<E>, Cloneable {
public final class LinkedIdentitySet<E> extends ListIdentitySet<E> implements Cloneable {
 
private final LinkedList<E> list;
 
public LinkedIdentitySet() {
this.list = new LinkedList<E>();
super();
}
 
public LinkedIdentitySet(Collection<? extends E> c) {
this.list = new LinkedList<E>();
this.addAll(c);
public LinkedIdentitySet(int expectedSize) {
super(expectedSize);
}
 
public final List<E> getList() {
return Collections.unmodifiableList(this.list);
public LinkedIdentitySet(Collection<? extends E> c) {
super(c);
}
 
@Override
public boolean add(E e) {
if (this.contains(e))
return false;
else {
this.list.add(e);
return true;
protected LinkedList<E> newList(int expectedSize) {
return new LinkedList<>();
}
}
 
@Override
public boolean addAll(Collection<? extends E> c) {
// let the super which calls add() since we don't want to build a map to use Map.addAll()
return super.addAll(c);
}
 
@Override
public void clear() {
this.list.clear();
}
 
@Override
public int size() {
return this.list.size();
}
 
@Override
public boolean isEmpty() {
return this.list.isEmpty();
}
 
@Override
public Iterator<E> iterator() {
return this.list.iterator();
}
 
@Override
public boolean contains(Object o) {
Iterator<E> e = iterator();
while (e.hasNext())
if (o == e.next())
return true;
return false;
}
 
@Override
public boolean containsAll(Collection<?> c) {
// let the super which calls contains()
return super.containsAll(c);
}
 
@Override
public boolean remove(Object o) {
Iterator<E> e = iterator();
while (e.hasNext()) {
if (o == e.next()) {
e.remove();
return true;
}
}
return false;
}
 
/*
* (From IdentityHashMap) Must revert from AbstractSet's impl to AbstractCollection's, as the
* former contains an optimization that results in incorrect behavior when c is a smaller
* "normal" (non-identity-based) Set.
*/
@Override
public boolean removeAll(Collection<?> c) {
boolean modified = false;
for (Iterator<E> i = iterator(); i.hasNext();) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
return modified;
}
 
@Override
public boolean retainAll(Collection<?> c) {
// let the super which calls remove()
return super.retainAll(c);
}
 
@Override
public Object[] toArray() {
return this.list.toArray();
}
 
@Override
public <T> T[] toArray(T[] a) {
return this.list.toArray(a);
}
 
/**
* Returns a shallow copy of this <tt>HashSet</tt> instance: the elements themselves are not
* cloned.
145,6 → 47,7
*
* @return a shallow copy of this set
*/
@Override
public Object clone() {
return new LinkedIdentitySet<E>(this);
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/IncludeExclude.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
50,13 → 50,40
 
@SafeVarargs
public static <T> IncludeExclude<T> getNormalized(final T... includes) {
return getNormalized(Arrays.asList(includes));
return getNormalizedInclude(Arrays.asList(includes));
}
 
@Deprecated
public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes) {
return getNormalizedInclude(includes);
}
 
/**
* Return the normalized version including only the passed values. E.g. if <code>includes</code>
* is empty or <code>null</code>, this won't allocate any memory and {@link #getEmpty()} or
* {@link #getFull()} will be returned.
*
* @param <T> type of items
* @param includes which values to include.
* @return the normalized version.
*/
public static <T> IncludeExclude<T> getNormalizedInclude(final Collection<? extends T> includes) {
return getNormalized(includes, Collections.<T> emptySet());
}
 
/**
* Return the normalized version excluding only the passed values. E.g. if <code>excludes</code>
* is empty or <code>null</code>, this won't allocate any memory and {@link #getFull()} or
* {@link #getEmpty()} will be returned.
*
* @param <T> type of items
* @param excludes which values to exclude.
* @return the normalized version.
*/
public static <T> IncludeExclude<T> getNormalizedExclude(final Collection<? extends T> excludes) {
return getNormalized(null, excludes);
}
 
public static <T> IncludeExclude<T> getNormalized(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
return new IncludeExclude<T>(includes, excludes).normal;
}
74,6 → 101,7
*
* @param includes which objects to include, <code>null</code> meaning all.
* @param excludes which objects to exclude, <code>null</code> meaning all.
* @see #isIncluded(Object)
*/
public IncludeExclude(final Collection<? extends T> includes, final Collection<? extends T> excludes) {
this(includes, excludes, false);
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/ListIdentitySet.java
New file
0,0 → 1,224
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils.cc;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.IntHashSet;
 
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
 
/**
* An IdentitySet maintaining insertion order.
*
* @param <E> the type of elements maintained by this set
* @see IdentityHashSet
*/
public abstract class ListIdentitySet<E> extends AbstractSet<E> implements IdentitySet<E> {
 
static final int THRESHOLD = 16;
 
private final List<E> list;
private IntHashSet identityHashCodes;
 
protected ListIdentitySet(final Collection<? extends E> c) {
this(c.size());
if (c instanceof ListIdentitySet) {
final ListIdentitySet<? extends E> s = (ListIdentitySet<? extends E>) c;
this.list.addAll(c);
this.identityHashCodes = s.identityHashCodes;
} else {
this.addAll(c);
}
}
 
protected ListIdentitySet() {
this(16);
}
 
protected ListIdentitySet(final int expectedSize) {
this.list = this.newList(expectedSize);
this.identityHashCodes = null;
}
 
public final boolean hasHashCodes() {
return this.identityHashCodes != null;
}
 
public final void initHashCodes() {
if (this.identityHashCodes == null) {
this.identityHashCodes = new IntHashSet(this.size());
for (final E i : this.list) {
this.identityHashCodes.add(System.identityHashCode(i));
}
}
}
 
final boolean sameSize() {
return this.identityHashCodes == null || this.identityHashCodes.size() == this.list.size();
}
 
protected abstract List<E> newList(int expectedSize);
 
public final List<E> getList() {
return Collections.unmodifiableList(this.list);
}
 
@Override
public final boolean add(E e) {
if (this.identityHashCodes != null) {
final boolean added = this.identityHashCodes.add(System.identityHashCode(e));
if (added) {
this.list.add(e);
}
assert sameSize();
return added;
} else {
final boolean contains = this.contains(e);
if (!contains) {
this.list.add(e);
if (this.size() > THRESHOLD) {
initHashCodes();
}
}
assert sameSize();
return !contains;
}
}
 
@Override
public final boolean addAll(Collection<? extends E> c) {
// let the super which calls add() since we don't want to build a map to use Map.addAll()
return super.addAll(c);
}
 
@Override
public final void clear() {
this.list.clear();
this.identityHashCodes = null;
}
 
@Override
public final int size() {
return this.list.size();
}
 
@Override
public final boolean isEmpty() {
return this.list.isEmpty();
}
 
@Override
public final Iterator<E> iterator() {
if (this.identityHashCodes == null)
return this.list.iterator();
 
return new Iterator<E>() {
 
private final Iterator<E> iter = ListIdentitySet.this.list.iterator();
private E lastReturned = null;
 
@Override
public boolean hasNext() {
return this.iter.hasNext();
}
 
@Override
public E next() {
this.lastReturned = this.iter.next();
return this.lastReturned;
}
 
@Override
public void remove() {
this.iter.remove();
ListIdentitySet.this.identityHashCodes.remove(System.identityHashCode(this.lastReturned));
assert sameSize();
}
};
}
 
@Override
public boolean contains(Object o) {
if (this.identityHashCodes != null)
return this.identityHashCodes.contains(System.identityHashCode(o));
else
return CollectionUtils.identityContains(this.list, o);
}
 
@Override
public final boolean containsAll(Collection<?> c) {
// let the super which calls contains()
return super.containsAll(c);
}
 
@Override
public boolean remove(Object o) {
final boolean res;
if (this.identityHashCodes != null) {
res = this.identityHashCodes.remove(System.identityHashCode(o));
if (res)
CollectionUtils.identityRemove(this.list, o);
} else {
res = CollectionUtils.identityRemove(this.list, o);
}
assert sameSize();
return res;
}
 
/*
* (From IdentityHashMap) Must revert from AbstractSet's impl to AbstractCollection's, as the
* former contains an optimization that results in incorrect behavior when c is a smaller
* "normal" (non-identity-based) Set.
*/
@Override
public final boolean removeAll(Collection<?> c) {
boolean modified = false;
for (Iterator<E> i = iterator(); i.hasNext();) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
assert sameSize();
return modified;
}
 
@Override
public final boolean retainAll(Collection<?> c) {
// let the super which calls remove()
return super.retainAll(c);
}
 
@Override
public final Object[] toArray() {
return this.list.toArray();
}
 
@Override
public final <T> T[] toArray(T[] a) {
return this.list.toArray(a);
}
 
@Override
public final int hashCode() {
int result = 0;
for (E key : this.list)
result += System.identityHashCode(key);
return result;
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/CopyOnWriteMap.java
96,28 → 96,28
}
 
@Override
public synchronized int size() {
return this.immutable.size();
public int size() {
return this.getImmutable().size();
}
 
@Override
public synchronized boolean isEmpty() {
return this.immutable.isEmpty();
public boolean isEmpty() {
return this.getImmutable().isEmpty();
}
 
@Override
public synchronized boolean containsKey(final Object key) {
return this.immutable.containsKey(key);
public boolean containsKey(final Object key) {
return this.getImmutable().containsKey(key);
}
 
@Override
public synchronized boolean containsValue(final Object value) {
return this.immutable.containsValue(value);
public boolean containsValue(final Object value) {
return this.getImmutable().containsValue(value);
}
 
@Override
public synchronized V get(final Object key) {
return this.immutable.get(key);
public V get(final Object key) {
return this.getImmutable().get(key);
}
 
@Override
177,12 → 177,12
// equals
 
@Override
public synchronized boolean equals(final Object o) {
return this.immutable.equals(o);
public boolean equals(final Object o) {
return this.getImmutable().equals(o);
}
 
@Override
public synchronized int hashCode() {
return this.immutable.hashCode();
public int hashCode() {
return this.getImmutable().hashCode();
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/ArrayIdentitySet.java
New file
0,0 → 1,53
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils.cc;
 
import java.util.ArrayList;
import java.util.Collection;
 
/**
* This class implements the <tt>Set</tt> interface with a list, using reference-equality in place
* of object-equality when comparing items.
*
* @param <E> the type of elements maintained by this set
*/
public final class ArrayIdentitySet<E> extends ListIdentitySet<E> implements Cloneable {
 
public ArrayIdentitySet() {
super();
}
 
public ArrayIdentitySet(int expectedMaxSize) {
super(expectedMaxSize);
}
 
public ArrayIdentitySet(Collection<? extends E> c) {
super(c);
}
 
@Override
protected ArrayList<E> newList(int expectedSize) {
return new ArrayList<>(expectedSize);
}
 
/**
* Returns a shallow copy of this instance: the elements themselves are not cloned.
*
* @return a shallow copy of this set
*/
@Override
public Object clone() {
return new ArrayIdentitySet<E>(this);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/IdentityHashSet.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
29,6 → 29,8
*/
public final class IdentityHashSet<E> extends AbstractSet<E> implements IdentitySet<E>, Cloneable {
 
static private final Object VALUE = new Object();
 
private final IdentityHashMap<E, Object> map;
 
public IdentityHashSet() {
46,13 → 48,8
 
@Override
public boolean add(E e) {
if (this.contains(e))
return false;
else {
this.map.put(e, null);
return true;
return this.map.put(e, VALUE) != VALUE;
}
}
 
@Override
public boolean addAll(Collection<? extends E> c) {
82,7 → 79,7
 
@Override
public boolean contains(Object o) {
return this.map.keySet().contains(o);
return this.map.containsKey(o);
}
 
@Override
92,7 → 89,7
 
@Override
public boolean remove(Object o) {
return this.map.keySet().remove(o);
return this.map.remove(o) == VALUE;
}
 
@Override
115,6 → 112,14
return this.map.keySet().toArray(a);
}
 
// use System.identityHashCode()
@Override
public int hashCode() {
return this.map.keySet().hashCode();
}
 
// equals() uses containsAll which is correct
 
/**
* Returns a shallow copy of this <tt>HashSet</tt> instance: the elements themselves are not
* cloned.
/trunk/OpenConcerto/src/org/openconcerto/utils/cc/CopyOnWriteList.java
New file
0,0 → 1,239
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils.cc;
 
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.UnaryOperator;
 
import net.jcip.annotations.ThreadSafe;
 
/**
* A thread-safe list in which all mutative operations (set, remove, and so on) are implemented by
* making a fresh copy of the underlying list. The instance of the underlying list can be customized
* with {@link #copy(Collection)} and {@link #create(int)}. The underlying immutable list is
* available with {@link #getImmutable()}.
*
* @author Sylvain
*
* @param <T> the type of elements in this list
*/
@ThreadSafe
public class CopyOnWriteList<T> extends AbstractList<T> {
 
private List<T> immutable;
 
public CopyOnWriteList() {
this.immutable = Collections.emptyList();
}
 
public CopyOnWriteList(final T single) {
this.immutable = Collections.singletonList(single);
}
 
public CopyOnWriteList(final Collection<? extends T> m) {
this.immutable = Collections.unmodifiableList(copy(m));
}
 
/**
* Create a copy of the passed collection.
*
* @param src the collection to copy, not <code>null</code>.
* @return a shallow copy of <code>src</code>.
*/
protected List<T> copy(final Collection<? extends T> src) {
return new ArrayList<>(src);
}
 
protected List<T> create(int capacity) {
return new ArrayList<>(capacity);
}
 
// Like CopyOnWriteArrayList, iterate on immutable (otherwise would have to at least increment
// this.modCount).
 
@Override
public Iterator<T> iterator() {
return this.getImmutable().iterator();
}
 
@Override
public ListIterator<T> listIterator(int index) {
return this.getImmutable().listIterator(index);
}
 
// write
 
private void rangeCheck(final int index, final boolean forAdd, final int size) {
if (index < 0 || index > size || (!forAdd && index == size))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
 
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size();
}
 
@Override
public synchronized T set(int index, T element) {
final List<T> copy = copy(this.immutable);
final T res = copy.set(index, element);
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized boolean add(T e) {
return super.add(e);
}
 
@Override
public void add(int index, T element) {
this.addAll(index, Collections.singletonList(element));
}
 
@Override
public synchronized boolean addAll(Collection<? extends T> c) {
return this.addAll(this.size(), c);
}
 
@Override
public synchronized boolean addAll(int index, Collection<? extends T> c) {
final int size = this.size();
this.rangeCheck(index, true, size);
final int cSize = c.size();
if (cSize == 0)
return false;
final List<T> copy = create(size + cSize);
for (int i = 0; i <= size; i++) {
if (i == index)
copy.addAll(c);
if (i < size)
copy.add(this.get(i));
}
assert copy.size() == size + cSize;
this.immutable = Collections.unmodifiableList(copy);
return true;
}
 
@Override
public synchronized T remove(final int index) {
final int size = this.size();
this.rangeCheck(index, false, size);
final List<T> copy = create(size - 1);
T res = null;
for (int i = 0; i < size; i++) {
if (i == index)
res = this.get(i);
else
copy.add(this.get(i));
}
assert copy.size() == size - 1;
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized boolean remove(Object o) {
final int indexOf = this.indexOf(o);
if (indexOf < 0)
return false;
this.remove(indexOf);
return true;
}
 
@Override
public synchronized boolean removeAll(Collection<?> c) {
final List<T> copy = copy(this.immutable);
final boolean res = copy.removeAll(c);
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized void clear() {
this.immutable = Collections.emptyList();
}
 
@Override
public synchronized boolean retainAll(Collection<?> c) {
final List<T> copy = copy(this.immutable);
final boolean res = copy.retainAll(c);
this.immutable = Collections.unmodifiableList(copy);
return res;
}
 
@Override
public synchronized void replaceAll(UnaryOperator<T> operator) {
final List<T> copy = copy(this.immutable);
copy.replaceAll(operator);
this.immutable = Collections.unmodifiableList(copy);
}
 
@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/IntHashSet.java
New file
0,0 → 1,675
/*
* 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;
 
import java.util.Arrays;
import java.util.Collection;
 
/**
* A type-specific hash set with with a fast, small-footprint implementation.
*
* <p>
* Instances of this class use a hash table to represent a set. The table is filled up to a
* specified <em>load factor</em>, and then doubled in size to accommodate new entries. If the table
* is emptied below <em>one fourth</em> of the load factor, it is halved in size; however, the table
* is never reduced to a size smaller than that at creation time: this approach makes it possible to
* create sets with a large capacity in which insertions and deletions do not cause immediately
* rehashing. Moreover, halving is not performed when deleting entries from an iterator, as it would
* interfere with the iteration process.
*
* <p>
* Note that {@link #clear()} does not modify the hash table size. Rather, a family of
* {@linkplain #trim() trimming methods} lets you control the size of the table; this is
* particularly useful if you reuse instances of this class.
*
*/
public class IntHashSet {
 
static final float DEFAULT_LOAD_FACTOR = 0.75f;
private static final int DEFAULT_INITIAL_SIZE = 16;
 
/** 2<sup>32</sup> &middot; &phi;, &phi; = (&#x221A;5 &minus; 1)/2. */
private static final int INT_PHI = 0x9E3779B9;
/** The reciprocal of {@link #INT_PHI} modulo 2<sup>32</sup>. */
private static final int INV_INT_PHI = 0x144cbc89;
 
/** The array of keys. */
protected transient int[] key;
/** The mask for wrapping a position counter. */
protected transient int mask;
/** Whether this set contains the null key. */
protected transient boolean containsNull;
/**
* The current table size. Note that an additional element is allocated for storing the null
* key.
*/
protected transient int n;
/** Threshold after which we rehash. It must be the table size times {@link #f}. */
protected transient int maxFill;
/** We never resize below this threshold, which is the construction-time {#n}. */
protected final transient int minN;
/** Number of entries in the set (including the null key, if present). */
protected int size;
/** The acceptable load factor. */
protected final float f;
 
/**
* Creates a new hash set.
*
* <p>
* The actual table size will be the least power of two greater than {@code expected}/{@code f}.
*
* @param expected the expected number of elements in the hash set.
* @param f the load factor.
*/
 
public IntHashSet(final int expected, final float f) {
if (f <= 0 || f >= 1)
throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than 1");
if (expected < 0)
throw new IllegalArgumentException("The expected number of elements must be nonnegative");
this.f = f;
minN = n = arraySize(expected, f);
mask = n - 1;
maxFill = maxFill(n, f);
key = new int[n + 1];
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor.
*
* @param expected the expected number of elements in the hash set.
*/
public IntHashSet(final int expected) {
this(expected, DEFAULT_LOAD_FACTOR);
}
 
/**
* Creates a new hash set with initial expected {@link Hash#DEFAULT_INITIAL_SIZE} elements and
* {@link Hash#DEFAULT_LOAD_FACTOR} as load factor.
*/
public IntHashSet() {
this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR);
}
 
/**
* Creates a new hash set copying a given collection.
*
* @param c a {@link Collection} to be copied into the new hash set.
* @param f the load factor.
*/
public IntHashSet(final Collection<? extends Integer> c, final float f) {
this(c.size(), f);
addAll(c);
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor copying a given
* collection.
*
* @param c a {@link Collection} to be copied into the new hash set.
*/
public IntHashSet(final Collection<? extends Integer> c) {
this(c, DEFAULT_LOAD_FACTOR);
}
 
/**
* Creates a new hash set and fills it with the elements of a given array.
*
* @param a an array whose elements will be used to fill the set.
* @param offset the first element to use.
* @param length the number of elements to use.
* @param f the load factor.
*/
public IntHashSet(final int[] a, final int offset, final int length, final float f) {
this(length < 0 ? 0 : length, f);
ensureOffsetLength(a.length, offset, length);
for (int i = 0; i < length; i++)
add(a[offset + i]);
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor and fills it with
* the elements of a given array.
*
* @param a an array whose elements will be used to fill the set.
* @param offset the first element to use.
* @param length the number of elements to use.
*/
public IntHashSet(final int[] a, final int offset, final int length) {
this(a, offset, length, DEFAULT_LOAD_FACTOR);
}
 
/**
* Creates a new hash set copying the elements of an array.
*
* @param a an array to be copied into the new hash set.
* @param f the load factor.
*/
public IntHashSet(final int[] a, final float f) {
this(a, 0, a.length, f);
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor copying the
* elements of an array.
*
* @param a an array to be copied into the new hash set.
*/
public IntHashSet(final int[] a) {
this(a, DEFAULT_LOAD_FACTOR);
}
 
/**
* Creates a new empty hash set.
*
* @return a new empty hash set.
*/
public static IntHashSet of() {
return new IntHashSet();
}
 
/**
* Returns the least power of two smaller than or equal to 2<sup>30</sup> and larger than or
* equal to {@code Math.ceil(expected / f)}.
*
* @param expected the expected number of elements in a hash table.
* @param f the load factor.
* @return the minimum possible size for a backing array.
* @throws IllegalArgumentException if the necessary size is larger than 2<sup>30</sup>.
*/
public static int arraySize(final int expected, final float f) {
final long s = Math.max(2, nextPowerOfTwo((long) Math.ceil(expected / f)));
if (s > (1 << 30))
throw new IllegalArgumentException("Too large (" + expected + " expected elements with load factor " + f + ")");
return (int) s;
}
 
/**
* Returns the maximum number of entries that can be filled before rehashing.
*
* @param n the size of the backing array.
* @param f the load factor.
* @return the maximum number of entries before rehashing.
*/
public static int maxFill(final int n, final float f) {
/*
* We must guarantee that there is always at least one free entry (even with pathological
* load factors).
*/
return Math.min((int) Math.ceil(n * f), n - 1);
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor using the given
* element.
*
* @param e the element that the returned set will contain.
* @return a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor containing
* {@code e}.
*/
public static IntHashSet of(final int e) {
IntHashSet result = new IntHashSet(1, DEFAULT_LOAD_FACTOR);
result.add(e);
return result;
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor using the
* elements given.
*
* @param e0 the first element.
* @param e1 the second element.
* @return a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor containing
* {@code e0} and {@code e1}.
* @throws IllegalArgumentException if there were duplicate entries.
*/
public static IntHashSet of(final int e0, final int e1) {
IntHashSet result = new IntHashSet(2, DEFAULT_LOAD_FACTOR);
result.add(e0);
if (!result.add(e1)) {
throw new IllegalArgumentException("Duplicate element: " + e1);
}
return result;
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor using the
* elements given.
*
* @param e0 the first element.
* @param e1 the second element.
* @param e2 the third element.
* @return a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor containing
* {@code e0}, {@code e1}, and {@code e2}.
* @throws IllegalArgumentException if there were duplicate entries.
*/
public static IntHashSet of(final int e0, final int e1, final int e2) {
IntHashSet result = new IntHashSet(3, DEFAULT_LOAD_FACTOR);
result.add(e0);
if (!result.add(e1)) {
throw new IllegalArgumentException("Duplicate element: " + e1);
}
if (!result.add(e2)) {
throw new IllegalArgumentException("Duplicate element: " + e2);
}
return result;
}
 
/**
* Creates a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor using a list of
* elements.
*
* @param a a list of elements that will be used to initialize the new hash set.
* @return a new hash set with {@link Hash#DEFAULT_LOAD_FACTOR} as load factor containing the
* elements of {@code a}.
* @throws IllegalArgumentException if a duplicate entry was encountered.
*/
 
public static IntHashSet of(final int... a) {
IntHashSet result = new IntHashSet(a.length, DEFAULT_LOAD_FACTOR);
for (int element : a) {
if (!result.add(element)) {
throw new IllegalArgumentException("Duplicate element " + element);
}
}
return result;
}
 
private int realSize() {
return containsNull ? size - 1 : size;
}
 
private void ensureCapacity(final int capacity) {
final int needed = arraySize(capacity, f);
if (needed > n)
rehash(needed);
}
 
private void tryCapacity(final long capacity) {
final int needed = (int) Math.min(1 << 30, Math.max(2, nextPowerOfTwo((long) Math.ceil(capacity / f))));
if (needed > n)
rehash(needed);
}
 
public void addAll(Collection<? extends Integer> c) {
// The resulting collection will be at least c.size() big
if (f <= .5)
ensureCapacity(c.size()); // The resulting collection will be sized for c.size()
// elements
else
tryCapacity(size() + c.size()); // The resulting collection will be tentatively sized
// for size() + c.size() elements
for (Integer i : c) {
add(i);
}
}
 
/**
* Quickly mixes the bits of an integer.
*
* <p>
* This method mixes the bits of the argument by multiplying by the golden ratio and xorshifting
* the result. It is borrowed from <a href="https://github.com/OpenHFT/Koloboke">Koloboke</a>,
* and it has slightly worse behaviour than {@link #murmurHash3(int)} (in open-addressing hash
* tables the average number of probes is slightly larger), but it's much faster.
*
* @param x an integer.
* @return a hash value obtained by mixing the bits of {@code x}.
* @see #invMix(int)
*/
public static int mix(final int x) {
final int h = x * INT_PHI;
return h ^ (h >>> 16);
}
 
/**
* The inverse of {@link #mix(int)}. This method is mainly useful to create unit tests.
*
* @param x an integer.
* @return a value that passed through {@link #mix(int)} would give {@code x}.
*/
public static int invMix(final int x) {
return (x ^ x >>> 16) * INV_INT_PHI;
}
 
public boolean add(final int k) {
int pos;
if (k == 0) {
if (containsNull)
return false;
containsNull = true;
} else {
int curr;
final int[] key = this.key;
// The starting point.
if (!((curr = key[pos = (mix((k))) & mask]) == 0)) {
if (curr == k)
return false;
while (!((curr = key[pos = (pos + 1) & mask]) == 0))
if (((curr) == (k)))
return false;
}
key[pos] = k;
}
if (size++ >= maxFill)
rehash(arraySize(size + 1, f));
 
return true;
}
 
/**
* Shifts left entries with the specified hash code, starting at the specified position, and
* empties the resulting free entry.
*
* @param pos a starting position.
*/
protected final void shiftKeys(int pos) {
// Shift entries with the same hash.
int last;
int slot;
int curr;
final int[] key = this.key;
for (;;) {
pos = ((last = pos) + 1) & mask;
for (;;) {
if (((curr = key[pos]) == (0))) {
key[last] = (0);
return;
}
slot = (mix((curr))) & mask;
if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos)
break;
pos = (pos + 1) & mask;
}
key[last] = curr;
}
}
 
private boolean removeEntry(final int pos) {
size--;
shiftKeys(pos);
if (n > minN && size < maxFill / 4 && n > DEFAULT_INITIAL_SIZE)
rehash(n / 2);
return true;
}
 
private boolean removeNullEntry() {
containsNull = false;
key[n] = (0);
size--;
if (n > minN && size < maxFill / 4 && n > DEFAULT_INITIAL_SIZE)
rehash(n / 2);
return true;
}
 
public boolean remove(final int k) {
if (((k) == (0))) {
if (containsNull)
return removeNullEntry();
return false;
}
int curr;
final int[] key = this.key;
int pos;
// The starting point.
if (((curr = key[pos = (mix((k))) & mask]) == (0)))
return false;
if (((k) == (curr)))
return removeEntry(pos);
while (true) {
if (((curr = key[pos = (pos + 1) & mask]) == (0)))
return false;
if (((k) == (curr)))
return removeEntry(pos);
}
}
 
public boolean contains(final int k) {
if (((k) == (0)))
return containsNull;
int curr;
final int[] key = this.key;
int pos;
// The starting point.
if (((curr = key[pos = (mix((k))) & mask]) == (0)))
return false;
if (((k) == (curr)))
return true;
while (true) {
if (((curr = key[pos = (pos + 1) & mask]) == (0)))
return false;
if (((k) == (curr)))
return true;
}
}
 
/*
* Removes all elements from this set.
*
* <p>To increase object reuse, this method does not change the table size. If you want to
* reduce the table size, you must use {@link #trim()}.
*
*/
 
public void clear() {
if (size == 0)
return;
size = 0;
containsNull = false;
Arrays.fill(key, (0));
}
 
public int size() {
return size;
}
 
public boolean isEmpty() {
return size == 0;
}
 
/**
* Rehashes this set, making the table as small as possible.
*
* <p>
* This method rehashes the table to the smallest size satisfying the load factor. It can be
* used when the set will not be changed anymore, so to optimize access speed and size.
*
* <p>
* If the table size is already the minimum possible, this method does nothing.
*
* @return true if there was enough memory to trim the set.
* @see #trim(int)
*/
public boolean trim() {
return trim(size);
}
 
/**
* Rehashes this set if the table is too large.
*
* <p>
* Let <var>N</var> be the smallest table size that can hold <code>max(n,{@link #size()})</code>
* entries, still satisfying the load factor. If the current table size is smaller than or equal
* to <var>N</var>, this method does nothing. Otherwise, it rehashes this set in a table of size
* <var>N</var>.
*
* <p>
* This method is useful when reusing sets. {@linkplain #clear() Clearing a set} leaves the
* table size untouched. If you are reusing a set many times, you can call this method with a
* typical size to avoid keeping around a very large table just because of a few large transient
* sets.
*
* @param n the threshold for the trimming.
* @return true if there was enough memory to trim the set.
* @see #trim()
*/
public boolean trim(final int n) {
final int l = nextPowerOfTwo((int) Math.ceil(n / f));
if (l >= this.n || size > maxFill(l, f))
return true;
try {
rehash(l);
} catch (OutOfMemoryError cantDoIt) {
return false;
}
return true;
}
 
/**
* Rehashes the set.
*
* <p>
* This method implements the basic rehashing strategy, and may be overriden by subclasses
* implementing different rehashing strategies (e.g., disk-based rehashing). However, you should
* not override this method unless you understand the internal workings of this class.
*
* @param newN the new size
*/
 
protected void rehash(final int newN) {
final int key[] = this.key;
final int mask = newN - 1; // Note that this is used by the hashing macro
final int newKey[] = new int[newN + 1];
int i = n, pos;
for (int j = realSize(); j-- != 0;) {
while (((key[--i]) == (0)))
;
if (!((newKey[pos = (mix((key[i]))) & mask]) == (0)))
while (!((newKey[pos = (pos + 1) & mask]) == (0)))
;
newKey[pos] = key[i];
}
n = newN;
this.mask = mask;
maxFill = maxFill(n, f);
this.key = newKey;
}
 
/**
* Returns a deep copy of this set.
*
* <p>
* This method performs a deep copy of this hash set; the data stored in the set, however, is
* not cloned. Note that this makes a difference only for object keys.
*
* @return a deep copy of this set.
*/
@Override
 
public IntHashSet clone() {
IntHashSet c;
try {
c = (IntHashSet) super.clone();
} catch (CloneNotSupportedException cantHappen) {
throw new InternalError();
}
c.key = key.clone();
c.containsNull = containsNull;
return c;
}
 
/**
* Returns a hash code for this set.
*
* This method overrides the generic method provided by the superclass. Since {@code equals()}
* is not overriden, it is important that the value returned by this method is the same value as
* the one returned by the overriden method.
*
* @return a hash code for this set.
*/
@Override
public int hashCode() {
int h = 0;
for (int j = realSize(), i = 0; j-- != 0;) {
while (((key[i]) == (0)))
i++;
h += (key[i]);
i++;
}
// Zero / null have hash zero.
return h;
}
 
/**
* Returns the least power of two greater than or equal to the specified value.
*
* <p>
* Note that this function will return 1 when the argument is 0.
*
* @param x an integer smaller than or equal to 2<sup>30</sup>.
* @return the least power of two greater than or equal to the specified value.
*/
public static int nextPowerOfTwo(int x) {
if (x == 0)
return 1;
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
return (x | x >> 16) + 1;
}
 
/**
* Returns the least power of two greater than or equal to the specified value.
*
* <p>
* Note that this function will return 1 when the argument is 0.
*
* @param x a long integer smaller than or equal to 2<sup>62</sup>.
* @return the least power of two greater than or equal to the specified value.
*/
public static long nextPowerOfTwo(long x) {
if (x == 0)
return 1;
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return (x | x >> 32) + 1;
}
 
/**
* Ensures that a range given by an offset and a length fits an array of given length.
*
* <p>
* This method may be used whenever an array range check is needed.
*
* <p>
* In Java 9 and up, this method should be considered deprecated in favor of the
* {@link java.util.Objects#checkFromIndexSize(int, int, int)} method, which may be intrinsified
* in recent JVMs.
*
* @param arrayLength an array length.
* @param offset a start index for the fragment
* @param length a length (the number of elements in the fragment).
* @throws IllegalArgumentException if {@code length} is negative.
* @throws ArrayIndexOutOfBoundsException if {@code offset} is negative or
* {@code offset}+{@code length} is greater than {@code arrayLength}.
*/
public static void ensureOffsetLength(final int arrayLength, final int offset, final int length) {
// When Java 9 becomes the minimum, use Objects#checkFromIndexSize​, as that can be an
// intrinsic
if (offset < 0)
throw new ArrayIndexOutOfBoundsException("Offset (" + offset + ") is negative");
if (length < 0)
throw new IllegalArgumentException("Length (" + length + ") is negative");
if (offset + length > arrayLength)
throw new ArrayIndexOutOfBoundsException("Last index (" + (offset + length) + ") is greater than array length (" + arrayLength + ")");
}
 
}
/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/ExceptionHandler.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,21 → 128,26
clipboard.setContents(data, data);
}
 
static public Future<Boolean> handle(Component comp, String msg, Throwable originalExn) {
return handle(comp, msg, null, originalExn);
}
 
/**
* Display the passed message. Note: this method doesn't block.
*
* @param comp the modal parent of the error window.
* @param msg the message to display.
* @param title the title, can be <code>null</code>.
* @param originalExn the cause, can be <code>null</code>.
* @return a future completed when the error is handled (e.g. the user clicked on the dialog),
* <code>false</code> if the error couldn't be displayed to the user.
*/
static public Future<Boolean> handle(Component comp, String msg, Throwable originalExn) {
static public Future<Boolean> handle(Component comp, String msg, String title, Throwable originalExn) {
final Future<Boolean> res;
if (tHandler != null && tHandler.handle(msg, originalExn)) {
res = TRUE_FUTURE;
} else {
res = new ExceptionHandler(comp, msg, originalExn, false).display();
res = new ExceptionHandler(comp, msg, title, originalExn, false).display();
}
assert res != null;
return res;
165,7 → 170,7
* @return an exception.
*/
static public RuntimeException die(String msg, Throwable originalExn) {
final ExceptionHandler res = new ExceptionHandler(null, msg, originalExn);
final ExceptionHandler res = new ExceptionHandler(null, msg, null, originalExn);
res.display();
return res;
}
180,6 → 185,7
 
// the comp on which to display the popup, may be null
private final Component comp;
private final String title;
private final boolean quit;
protected static AtomicInteger openedWindows = new AtomicInteger(0);
private static boolean forceUI;
241,7 → 247,10
return Boolean.FALSE;
}
 
protected final void showMsg(final String msg, final boolean quit) {
protected final void showMsg(String msg, final boolean quit) {
if (msg == null) {
msg = "";
}
final JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
406,9 → 415,9
final Window window = this.comp == null ? null : SwingUtilities.getWindowAncestor(this.comp);
final JDialog f;
if (window instanceof Frame) {
f = new JDialog((Frame) window, "Erreur", true);
f = new JDialog((Frame) window, this.getTitle(), true);
} else {
f = new JDialog((Dialog) window, "Erreur", true);
f = new JDialog((Dialog) window, this.getTitle(), true);
}
f.setContentPane(p);
f.pack();
451,10 → 460,11
*
* @param comp the component upon which to display the popup.
* @param msg le message d'erreur à afficher.
* @param title the title, can be <code>null</code>.
* @param cause la cause de l'exception (peut être <code>null</code>).
*/
private ExceptionHandler(Component comp, String msg, Throwable cause) {
this(comp, msg, cause, true);
private ExceptionHandler(Component comp, String msg, String title, Throwable cause) {
this(comp, msg, title, cause, true);
}
 
/**
462,15 → 472,23
*
* @param comp the component upon which to display the popup.
* @param msg the error message to display.
* @param title the title, can be <code>null</code>.
* @param cause the cause of the exception (maybe <code>null</code>).
* @param quit if the VM must exit.
*/
private ExceptionHandler(Component comp, String msg, Throwable cause, boolean quit) {
private ExceptionHandler(Component comp, String msg, String title, Throwable cause, boolean quit) {
super(msg, cause);
this.comp = comp;
this.title = title;
this.quit = quit;
}
 
public String getTitle() {
if (this.title != null)
return this.title;
return this.quit ? "Erreur fatale" : "Erreur";
}
 
private void submitError(String error) {
final Charset cs = StringUtils.UTF8;
try {
/trunk/OpenConcerto/src/org/openconcerto/utils/SystemInfo.java
1,7 → 1,7
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
47,6 → 47,8
}
 
public static final String CLASS_PROTOCOL = "class";
public static final String PROPS_PROTOCOL = "props";
public static final String ENV_PROTOCOL = "env";
 
static public NavigableMap<Info, String> get(final boolean html, final Locale locale) {
final TM tm = TM.getInstance(locale);
67,12 → 69,22
e1.printStackTrace();
}
final Runtime rt = Runtime.getRuntime();
final String stats = "<i>" + tm.translate("memory") + " :</i> " + formatBytes(tm, rt.freeMemory()) + " / " + formatBytes(tm, rt.totalMemory()) + " ; "
final long totalMemory = rt.totalMemory();
final String stats = "<i>" + tm.translate("memory.used") + " :</i> " + formatBytes(tm, totalMemory - rt.freeMemory()) + " / " + formatBytes(tm, totalMemory) + " ; "
+ tm.translate("processors", rt.availableProcessors());
final String lafDesc = lookAndFeel == null ? tm.translate("no.laf") : getLink(lookAndFeel.getName(), lafURI, html) + ", " + lookAndFeel.getDescription();
URI propsURI = null;
URI envURI = null;
try {
propsURI = new URI(PROPS_PROTOCOL, "/", null);
envURI = new URI(ENV_PROTOCOL, "/", null);
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
final String propsDesc = envURI == null ? "" : lineBreak + getLink(tm.translate("properties.all"), propsURI, html) + ", " + getLink(tm.translate("env.all"), envURI, html);
 
final String p = tm.translate("javaVersion", version, getLink(getProperty("java.vendor"), vendorURI, html)) + " ; "
+ getLink(tm.translate("javaHome"), new File(getProperty("java.home")).toURI(), html) + lineBreak + stats + lineBreak + lafDesc;
+ getLink(tm.translate("javaHome"), new File(getProperty("java.home")).toURI(), html) + lineBreak + stats + lineBreak + lafDesc + propsDesc;
 
res.put(Info.JAVA, p);
 
/trunk/OpenConcerto/src/org/openconcerto/utils/NetUtils.java
32,6 → 32,7
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
 
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
184,10 → 185,11
return content;
}
 
/**
* Encode for POST message application/x-www-form-urlencoded
*/
static public final String urlEncode(final String... kv) {
return urlEncode(false, kv);
}
 
static public final String urlEncode(final boolean spaceAsPlus, final String... kv) {
final int size = kv.length;
if (size % 2 != 0)
throw new IllegalArgumentException("Odd number of items : " + size);
195,13 → 197,22
for (int i = 0; i < size; i += 2) {
map.put(kv[i], kv[i + 1]);
}
return urlEncode(map);
return urlEncode(map, spaceAsPlus);
}
 
/**
* Encode for POST message application/x-www-form-urlencoded
* Encode pairs in application/x-www-form-urlencoded format. Although both the
* <a href="https://url.spec.whatwg.org/#urlencoded-serializing">URL standard</a> and the
* <a href="https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1">HTML standard</a>
* specifies <code>spaceAsPlus</code> some applications expect the opposite (e.g. POST with
* jetty).
*
* @param map the values to encode.
* @param spaceAsPlus <code>true</code> to use '+' for a space, <code>false</code> to use '%20'.
* @return the encoded string.
* @see #percentEncode(String, String, boolean)
*/
static public final String urlEncode(final Map<String, ?> map) {
static public final String urlEncode(final Map<String, ?> map, final boolean spaceAsPlus) {
if (map.isEmpty())
return "";
final String charset = StandardCharsets.UTF_8.name();
211,9 → 222,9
// Avoid null and "null" confusion.
if (value != null) {
try {
sb.append(URLEncoder.encode(e.getKey(), charset).replace("+", "%20"));
sb.append(percentEncode(e.getKey(), charset, spaceAsPlus));
sb.append('=');
sb.append(URLEncoder.encode(String.valueOf(value), charset).replace("+", "%20"));
sb.append(percentEncode(String.valueOf(value), charset, spaceAsPlus));
sb.append('&');
} catch (UnsupportedEncodingException exn) {
throw new IllegalStateException("UTF-8 should be standard", exn);
225,6 → 236,31
return sb.toString();
}
 
static private final Pattern SPACE_PATTERN = Pattern.compile("+", Pattern.LITERAL);
 
/**
* Percent encode a string.
*
* @param s the string to encode.
* @param charset the charset to use.
* @param spaceAsPlus <code>true</code> to use '+' for a space (as per
* <a href="https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1">HTML</a> and
* <a href=
* "https://url.spec.whatwg.org/#urlencoded-serializing">application/x-www-form-urlencoded</a>
* of URL Standard), <code>false</code> to use '%20' (as per
* <a href="https://url.spec.whatwg.org/#path-state">Path part</a> of URL Standard).
* @return the encoded string.
* @throws UnsupportedEncodingException if charset is unknown.
* @see <a href="https://url.spec.whatwg.org/#string-percent-encode-after-encoding">URL
* standard</a>
*/
static public final String percentEncode(final String s, final String charset, final boolean spaceAsPlus) throws UnsupportedEncodingException {
final String withPlus = URLEncoder.encode(s, charset);
if (spaceAsPlus)
return withPlus;
return SPACE_PATTERN.matcher(withPlus).replaceAll("%20");
}
 
// Create a trust manager that does not validate certificate chains
static private final TrustManager[] TRUSTALL_MANAGERS = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
/trunk/OpenConcerto/src/org/openconcerto/utils/checks/MutableValueObject.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,8
* Set the value as if this object was newly created. Ie if this object had a way of computing
* its initial value, reuse it (eg setValue(currentTime)).
*/
public void resetValue();
public default void resetValue() {
this.setValue(null);
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/StringUtils.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.nio.charset.Charset;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
359,6 → 360,39
return res;
}
 
static private final Pattern singleQuote = Pattern.compile("'", Pattern.LITERAL);
static public final Pattern SINGLE_QUOTED_PATTERN = Pattern.compile("'(('')|[^'])*'");
static private final Pattern twoSingleQuote = Pattern.compile("''", Pattern.LITERAL);
 
/**
* Quote a string with single quotes ( e.g. for {@link SimpleDateFormat}).
*
* @param s an arbitrary string, eg "salut\ l'ami".
* @return the quoted form, eg "'salut\ l''ami'".
*/
static public final String singleQuote(String s) {
if (s.isEmpty())
return "''";
else
return "'" + singleQuote.matcher(s).replaceAll("''") + "'";
}
 
static public final String unSingleQuote(String s) {
if (!SINGLE_QUOTED_PATTERN.matcher(s).matches())
throw new IllegalArgumentException("Invalid quoted string " + s);
return unSingleQuoteUnsafe(s);
}
 
private static String unSingleQuoteUnsafe(String s) {
return twoSingleQuote.matcher(s.substring(1, s.length() - 1)).replaceAll("'");
}
 
static public final String unSingleQuote(Matcher m) {
if (m.pattern() != SINGLE_QUOTED_PATTERN)
throw new IllegalArgumentException("Matcher not from SINGLE_QUOTED_PATTERN : " + m);
return unSingleQuoteUnsafe(m.group());
}
 
static private final Pattern quotePatrn = Pattern.compile("\"", Pattern.LITERAL);
static private final Pattern slashPatrn = Pattern.compile("(\\\\+)");
 
740,10 → 774,13
}
 
/**
* convert a byte array to its hexa representation
* Convert a byte array to its hexa representation (uppercase)
*
* @param bytes to converts
* @param size of the byte array to use
* @return the hexa representation
*/
public static String bytesToHexString(byte[] bytes) {
final int length = bytes.length;
public static String bytesToHexString(byte[] bytes, int length) {
char[] hexChars = new char[length * 2];
for (int j = 0; j < length; j++) {
int v = bytes[j] & 0xFF;
753,7 → 790,25
return new String(hexChars);
}
 
public static String charToHex(char c) {
int v = c & 0xFF;
char[] hexChars = new char[2];
hexChars[0] = hexArray[v >>> 4];
hexChars[1] = hexArray[v & 0x0F];
return new String(hexChars);
}
 
/**
* Convert a byte array to its hexa representation (uppercase)
*
* @param bytes to converts
* @return the hexa representation
*/
public static String bytesToHexString(byte[] bytes) {
return bytesToHexString(bytes, bytes.length);
}
 
/**
* Whether the parameter is empty.
*
* @param s the string to test.
768,22 → 823,11
}
 
/**
* Return the first parameter that is non-empty.
*
* @param s1 the string to test.
* @param s2 the alternate value.
* @return <code>s1</code> if not <code>null</code> and not {@link String#isEmpty() empty},
* <code>s2</code> otherwise.
*/
public static String coalesce(String s1, String s2) {
return isEmpty(s1) ? s2 : s1;
}
 
/**
* Return the first value that is non-empty.
*
* @param values values to test for emptiness.
* @return the first value that is neither <code>null</code> nor {@link String#isEmpty() empty}.
* @return the first value that is neither <code>null</code> nor {@link String#isEmpty() empty},
* <code>null</code> if none.
*/
public static String coalesce(String... values) {
return coalesce(false, values);
790,9 → 834,10
}
 
public static String coalesce(final boolean trim, String... values) {
for (final String s : values)
for (final String s : values) {
if (!isEmpty(s, trim))
return s;
}
return null;
}
 
/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/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/jsonrpc/JSONRPCClient.java
New file
0,0 → 1,125
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils.jsonrpc;
 
import org.openconcerto.utils.Base64;
import org.openconcerto.utils.NetUtils;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
 
import javax.net.ssl.HttpsURLConnection;
 
import net.minidev.json.JSONObject;
import net.minidev.json.JSONStyle;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
 
public class JSONRPCClient {
private boolean trustAllContext;
private String token;
private String login;
private String encodedPassword;
 
public JSONRPCClient(boolean trustAllContext) {
this.trustAllContext = trustAllContext;
}
 
public void setAuthToken(String token) {
this.token = token;
}
 
/**
* @param login : login
* @param encodedPassword : Base64 encoded hash of the password
*/
public void setCredentials(String login, String encodedPassword) {
this.login = login;
this.encodedPassword = encodedPassword;
}
 
public Object rpcCall(String url, String method, JSONObject params) throws IOException {
final JSONObject request = new JSONObject();
request.put("jsonrpc", "2.0");
request.put("method", method);
request.put("id", UUID.randomUUID().toString());
request.put("params", params);
 
final HttpURLConnection con;
if (url.startsWith("https")) {
HttpsURLConnection cons = (HttpsURLConnection) new URL(url).openConnection();
if (this.trustAllContext) {
try {
cons.setSSLSocketFactory(NetUtils.createTrustAllContext().getSocketFactory());
cons.setHostnameVerifier(NetUtils.HostnameNonVerifier);
} catch (KeyManagementException | NoSuchAlgorithmException e1) {
throw new IOException(e1);
}
}
con = cons;
} else {
con = (HttpURLConnection) new URL(url).openConnection();
}
con.setRequestProperty("Accept-Encoding", "gzip");
con.setDoOutput(true);
 
final String str = request.toJSONString(JSONStyle.NO_COMPRESS);
 
final byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
 
con.setRequestProperty("Content-Type", "application/json; charset=utf-8");
if (this.token != null) {
con.setRequestProperty("Authorization", "Bearer " + this.token);
} else {
// X_LOGIN and X_PASSWORD_HASH are Base64 encoded
if (this.login != null) {
con.setRequestProperty("X_LOGIN", Base64.encodeBytes(this.login.getBytes(StandardCharsets.UTF_8)));
}
if (this.encodedPassword != null) {
con.setRequestProperty("X_PASSWORD_HASH", this.encodedPassword);
}
}
 
try (final OutputStream out = con.getOutputStream()) {
out.write(bytes);
}
 
final JSONParser p = new JSONParser(JSONParser.MODE_PERMISSIVE);
 
try (InputStream inputStream = "gzip".equals(con.getContentEncoding()) ? new GZIPInputStream(con.getInputStream()) : con.getInputStream()) {
final JSONObject response = (JSONObject) p.parse(inputStream);
if (response.containsKey("error")) {
final JSONObject jsonObject = (JSONObject) response.get("error");
if (jsonObject.getAsNumber("code").intValue() == -32601) {
throw new IOException("method not found on server :" + method + " code : " + jsonObject.getAsNumber("code") + " : " + jsonObject.getAsString("message"));
} else {
throw new IOException("rpc call error, code : " + jsonObject.getAsNumber("code") + " : " + jsonObject.getAsString("message"));
}
}
return response.get("result");
} catch (final ParseException e) {
throw new IOException(e);
}
 
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/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/SystemUtils.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 java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Pattern;
 
public class SystemUtils {
123,4 → 124,18
return true;
}
}
 
static public final <T extends Enum<T>> T getEnumFromProperty(final String propName, final Class<T> clazz, final T def) {
final String prop = System.getProperty(propName);
if (prop == null)
return def;
return Enum.valueOf(clazz, propName);
}
 
static public final <T> T getProperty(final String propName, final Function<String, T> func, final T def) {
final String prop = System.getProperty(propName);
if (prop == null)
return def;
return func.apply(prop);
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/CompareUtils.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
282,7 → 282,7
}
};
 
static public final <T> boolean equals(List<T> l1, List<T> l2, Equalizer<? super T> comp) {
static public final <T> boolean equals(List<? extends T> l1, List<? extends T> l2, Equalizer<? super T> comp) {
return compare(l1, l2, comp, null) == null;
}
 
297,7 → 297,7
* @return <code>null</code> if the two lists are equal, otherwise a String explaining the
* difference.
*/
static public final <T> String compare(List<T> l1, List<T> l2, Equalizer<? super T> comp, final ITransformer<? super T, String> toString) {
static public final <T> String compare(List<? extends T> l1, List<? extends T> l2, Equalizer<? super T> comp, final ITransformer<? super T, String> toString) {
final int size = l1.size();
if (size != l2.size())
return "unequal size";
/trunk/OpenConcerto/src/org/openconcerto/utils/TinyMap.java
13,187 → 13,223
package org.openconcerto.utils;
 
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
 
public class TinyMap<K, V> implements Map<K, V> {
private final ArrayList<K> keys;
private final ArrayList<V> values;
public class TinyMap<K, V> extends AbstractMap<K, V> {
private static final Function<Entry<?, ?>, Object> KEY_GETTER = Entry::getKey;
private static final Function<Entry<?, ?>, Object> VALUE_GETTER = Entry::getValue;
 
private final ArrayList<Entry<K, V>> entries;
private transient Set<Map.Entry<K, V>> entrySet;
/**
* The number of times this HashMap has been structurally modified Structural modifications are
* those that change the number of mappings in the HashMap or otherwise modify its internal
* structure (e.g., rehash). This field is used to make iterators on Collection-views of the
* HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount = 0;
 
public TinyMap() {
this(10);
}
 
public TinyMap(int initialCapacity) {
keys = new ArrayList<K>(initialCapacity);
values = new ArrayList<V>(initialCapacity);
public TinyMap(final int initialCapacity) {
this.entries = new ArrayList<>(initialCapacity);
}
 
public TinyMap(final Map<? extends K, ? extends V> m) {
this.entries = new ArrayList<>(m.size());
// don't call putAll() to avoid indexOfKey()
for (final Entry<? extends K, ? extends V> e : m.entrySet()) {
this.entries.add(new SimpleEntry<>(e));
}
++this.modCount;
}
 
@Override
public int size() {
return keys.size();
return this.entries.size();
}
 
@Override
public boolean isEmpty() {
return keys.isEmpty();
return this.entries.isEmpty();
}
 
@Override
public boolean containsKey(Object key) {
return keys.contains(key);
public boolean containsKey(final Object key) {
return indexOfKey(key) >= 0;
}
 
@Override
public boolean containsValue(Object value) {
return values.contains(value);
public boolean containsValue(final Object value) {
return this.indexOf(value, VALUE_GETTER) >= 0;
}
 
@Override
public V get(Object key) {
final int size = this.keys.size();
for (int i = 0; i < size; i++) {
if (this.keys.get(i).equals(key)) {
return this.values.get(i);
private int indexOfKey(final Object key) {
return this.indexOf(key, KEY_GETTER);
}
 
private final int indexOf(final Object o, final Function<Entry<?, ?>, Object> getter) {
final int stop = this.entries.size();
for (int i = 0; i < stop; i++) {
final Entry<K, V> e = this.entries.get(i);
if (Objects.equals(o, getter.apply(e)))
return i;
}
return -1;
}
 
@Override
public V get(final Object key) {
final int i = indexOfKey(key);
if (i < 0)
return null;
return this.entries.get(i).getValue();
}
 
@Override
public V put(K key, V value) {
final int size = this.keys.size();
for (int i = 0; i < size; i++) {
if (this.keys.get(i).equals(key)) {
final V old = this.values.get(i);
this.values.set(i, value);
return old;
public V put(final K key, final V value) {
final int i = this.indexOfKey(key);
final V res;
if (i < 0) {
this.entries.add(new SimpleEntry<>(key, value));
++this.modCount;
res = null;
} else {
res = this.entries.get(i).setValue(value);
}
return res;
}
this.keys.add(key);
this.values.add(value);
return null;
}
 
@Override
public V remove(Object key) {
final int size = this.keys.size();
for (int i = 0; i < size; i++) {
if (this.keys.get(i).equals(key)) {
this.keys.remove(i);
return this.values.remove(i);
public V remove(final Object key) {
return this.remove(indexOfKey(key));
}
}
 
private V remove(final int i) {
if (i < 0)
return null;
final Entry<K, V> res = this.entries.remove(i);
++this.modCount;
return res.getValue();
}
 
@Override
public void putAll(Map<? extends K, ? extends V> m) {
final Set<? extends K> keySet = m.keySet();
for (Iterator<? extends K> iterator = keySet.iterator(); iterator.hasNext();) {
K key = (K) iterator.next();
put(key, m.get(key));
public boolean remove(final Object key, final Object value) {
final int i = indexOfKey(key);
if (i < 0)
return false;
final boolean eqVal = Objects.equals(this.entries.get(i).getValue(), value);
if (eqVal) {
this.remove(i);
}
return eqVal;
}
 
@Override
public void clear() {
this.keys.clear();
this.values.clear();
this.entries.clear();
++this.modCount;
}
 
// Views
 
@Override
public Set<K> keySet() {
return new HashSet<K>(this.keys) {
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> es;
return (es = this.entrySet) == null ? (this.entrySet = new EntrySet()) : es;
}
 
final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public boolean remove(Object o) {
TinyMap.this.remove(o);
return super.remove(o);
public final int size() {
return TinyMap.this.size();
}
 
@Override
public void clear() {
clear();
super.clear();
public final void clear() {
TinyMap.this.clear();
}
};
}
 
@Override
public Collection<V> values() {
public final Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
 
return new ArrayList<V>(this.values()) {
private int expectedModCount = TinyMap.this.modCount;
/**
* The index last returned, i.e. initial value just before first item.
*
* <pre>
* a b c
* -1 0 1 2
* </pre>
*/
private int currentPos = -1;
private Entry<K, V> lastReturned = null;
 
@Override
public V remove(int index) {
keys.remove(index);
values.remove(index);
return super.remove(index);
public boolean hasNext() {
final int nextIndex = this.currentPos + 1;
return nextIndex < size();
}
 
@Override
public boolean remove(Object o) {
int index = values.indexOf(o);
if (index >= 0) {
keys.remove(index);
values.remove(index);
public Entry<K, V> next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
this.currentPos++;
final Entry<K, V> res = TinyMap.this.entries.get(this.currentPos);
this.lastReturned = res;
return res;
}
return super.remove(o);
}
 
@Override
public void clear() {
clear();
super.clear();
public void remove() {
checkForComodification();
if (this.lastReturned == null)
throw new IllegalStateException();
TinyMap.this.remove(this.currentPos);
this.currentPos--;
// per doc : cannot be called twice
this.lastReturned = null;
this.expectedModCount = TinyMap.this.modCount;
}
};
}
 
@Override
public Set<Entry<K, V>> entrySet() {
final Set<Entry<K, V>> set = new HashSet<Map.Entry<K, V>>() {
@Override
public boolean remove(Object o) {
Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
int index = values.indexOf(entry.getValue());
if (index >= 0) {
keys.remove(index);
values.remove(index);
final void checkForComodification() {
if (TinyMap.this.modCount != this.expectedModCount)
throw new ConcurrentModificationException();
}
return super.remove(o);
};
}
 
@Override
public boolean removeAll(Collection<?> c) {
for (Iterator iterator = c.iterator(); iterator.hasNext();) {
Entry<K, V> entry = (Entry<K, V>) iterator.next();
int index = values.indexOf(entry.getValue());
if (index >= 0) {
keys.remove(index);
values.remove(index);
public final boolean contains(final Object o) {
if (!(o instanceof Map.Entry))
return false;
final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
return TinyMap.this.entries.contains(e);
}
}
return super.removeAll(c);
}
 
@Override
public void clear() {
clear();
super.clear();
public final boolean remove(final Object o) {
if (o instanceof Map.Entry) {
final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
final Object key = e.getKey();
final Object value = e.getValue();
return TinyMap.this.remove(key, value);
}
 
};
final int size = this.keys.size();
for (int i = 0; i < size; i++) {
set.add(new SimpleImmutableEntry<K, V>(this.keys.get(i), this.values.get(i)));
return false;
}
return set;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/utils/reentrantEventsNaive.seq.violet.html
New file
0,0 → 1,800
<HTML>
<HEAD>
<META name="description"
content="Violet UML Editor cross format document" />
<META name="keywords" content="Violet, UML" />
<META charset="UTF-8" />
<SCRIPT type="text/javascript">
function switchVisibility() {
var obj = document.getElementById("content");
obj.style.display = (obj.style.display == "block") ? "none" : "block";
}
</SCRIPT>
</HEAD>
<BODY>
This file was generated with Violet UML Editor 3.0.0.
&nbsp;&nbsp;(&nbsp;<A href=# onclick="switchVisibility()">View Source</A>&nbsp;/&nbsp;<A href="http://sourceforge.net/projects/violet/files/violetumleditor/" target="_blank">Download Violet</A>&nbsp;)
<BR />
<BR />
<SCRIPT id="content" type="text/xml"><![CDATA[<SequenceDiagramGraph id="1">
<nodes id="2">
<LifelineNode id="3">
<id id="4" value="72e47375-469f-4a88-abb6-e40e46c324ce"/>
<revision>0</revision>
<children id="5">
<ActivationBarNode id="6">
<id id="7" value="a94abf1e-083c-482c-8940-d77d8a5e62a3"/>
<revision>1</revision>
<children id="8"/>
<parent class="LifelineNode" reference="3"/>
<location class="Point2D.Double" id="9" x="112.5" y="75.0"/>
<backgroundColor id="10">
<red>255</red>
<green>255</green>
<blue>255</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="11">
<red>191</red>
<green>191</green>
<blue>191</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="12">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="13" x="50.0" y="0.0"/>
<textColor reference="12"/>
<name id="14">
<text>evt 1</text>
</name>
<type id="15">
<text>SQLElementEvent</text>
</type>
<endOfLife>true</endOfLife>
</LifelineNode>
<LifelineNode id="16">
<id id="17" value="2da76d69-e422-46b0-a548-1d7b83093d9f"/>
<revision>1</revision>
<children id="18">
<ActivationBarNode id="19">
<id id="20" value="030a1385-4bfe-4889-8ec1-ffa386c5db22"/>
<revision>0</revision>
<children id="21"/>
<parent class="LifelineNode" reference="16"/>
<location class="Point2D.Double" id="22" x="112.5" y="110.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor id="23">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="24" x="980.0" y="10.0"/>
<textColor reference="23"/>
<name id="25">
<text>evt 2</text>
</name>
<type id="26">
<text>SQLElementEvent</text>
</type>
<endOfLife>true</endOfLife>
</LifelineNode>
<LifelineNode id="27">
<id id="28" value="d3539181-e292-45d4-92ee-bb25e1cd9aaf"/>
<revision>1</revision>
<children id="29">
<ActivationBarNode id="30">
<id id="31" value="4f050be9-7427-4789-b824-b1f78f32cd07"/>
<revision>0</revision>
<children id="32"/>
<parent class="LifelineNode" reference="27"/>
<location class="Point2D.Double" id="33" x="133.0" y="90.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
<ActivationBarNode id="34">
<id id="35" value="3af702fc-e885-4000-b97b-bd4921b5fc56"/>
<revision>0</revision>
<children id="36"/>
<parent class="LifelineNode" reference="27"/>
<location class="Point2D.Double" id="37" x="133.0" y="140.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="38" x="370.0" y="10.0"/>
<textColor reference="23"/>
<name id="39">
<text>l1</text>
</name>
<type id="40">
<text>SQLElementEventListener</text>
</type>
<endOfLife>false</endOfLife>
</LifelineNode>
<LifelineNode id="41">
<id id="42" value="66c2c3a4-4956-4a4b-b819-07da280e5582"/>
<revision>1</revision>
<children id="43">
<ActivationBarNode id="44">
<id id="45" value="bf8e10a3-b20f-4568-af06-4c8ffe13924e"/>
<revision>0</revision>
<children id="46"/>
<parent class="LifelineNode" reference="41"/>
<location class="Point2D.Double" id="47" x="133.0" y="200.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
<ActivationBarNode id="48">
<id id="49" value="ac5b4a69-b3fa-4a3b-8c4a-570c49d3b4cb"/>
<revision>0</revision>
<children id="50"/>
<parent class="LifelineNode" reference="41"/>
<location class="Point2D.Double" id="51" x="133.0" y="310.0"/>
<backgroundColor reference="10"/>
<borderColor reference="11"/>
<textColor reference="23"/>
</ActivationBarNode>
</children>
<location class="Point2D.Double" id="52" x="680.0" y="0.0"/>
<textColor reference="23"/>
<name id="53">
<text>l2</text>
</name>
<type id="54">
<text>SQLElementEventListener</text>
</type>
<endOfLife>false</endOfLife>
</LifelineNode>
<NoteNode id="55">
<id id="56" value="d4154bcf-2a20-4ffc-aaf5-43d47a5a34f7"/>
<revision>1</revision>
<children id="57"/>
<location class="Point2D.Double" id="58" x="730.0" y="410.0"/>
<backgroundColor id="59">
<red>254</red>
<green>222</green>
<blue>188</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="60">
<red>253</red>
<green>186</green>
<blue>113</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="61">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
<text id="62">
<text>l2 is notified of evt 2 before evt 1</text>
</text>
</NoteNode>
</nodes>
<edges id="63">
<SynchronousCallEdge id="64">
<id id="65" value="69d0c441-b334-4eb1-8b1e-89a9d77ad913"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="6"/>
<startLocation class="Point2D.Double" id="66" x="7.5" y="15.0"/>
<endNode class="ActivationBarNode" reference="30"/>
<endLocation class="Point2D.Double" id="67" x="150.0" y="90.0"/>
<transitionPoints id="68"/>
<backgroundColor id="69">
<red>255</red>
<green>255</green>
<blue>255</blue>
<alpha>255</alpha>
</backgroundColor>
<borderColor id="70">
<red>191</red>
<green>191</green>
<blue>191</blue>
<alpha>255</alpha>
</borderColor>
<textColor id="71">
<red>51</red>
<green>51</green>
<blue>51</blue>
<alpha>255</alpha>
</textColor>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="72">
<text></text>
</startLabel>
<centerLabel id="73">
<text>modification(evt1)</text>
</centerLabel>
<endLabel id="74">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="75">
<id id="76" value="2a43a89d-a435-46ef-ba93-fe869e1af5f0"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="30"/>
<startLocation class="Point2D.Double" id="77" x="7.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="78" x="120.0" y="100.0"/>
<transitionPoints id="79"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="80">
<text></text>
</startLabel>
<centerLabel id="81">
<text>fire</text>
</centerLabel>
<endLabel id="82">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="83">
<id id="84" value="0c060aa6-6c2b-4238-a9ae-0b4417cdcbda"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="85" x="7.5" y="20.0"/>
<endNode class="ActivationBarNode" reference="34"/>
<endLocation class="Point2D.Double" id="86" x="130.0" y="130.0"/>
<transitionPoints id="87"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="88">
<text></text>
</startLabel>
<centerLabel id="89">
<text>modification(evt2)</text>
</centerLabel>
<endLabel id="90">
<text></text>
</endLabel>
</SynchronousCallEdge>
<SynchronousCallEdge id="91">
<id id="92" value="7866f976-b5c5-4dff-b55e-817e0acd0851"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="93" x="7.5" y="70.0"/>
<endNode class="ActivationBarNode" reference="44"/>
<endLocation class="Point2D.Double" id="94" x="140.0" y="200.0"/>
<transitionPoints id="95"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="96">
<text></text>
</startLabel>
<centerLabel id="97">
<text>modification(evt2)</text>
</centerLabel>
<endLabel id="98">
<text></text>
</endLabel>
</SynchronousCallEdge>
<ReturnEdge id="99">
<id id="100" value="8a1ad364-cc2f-43ce-99d4-77158785be3d"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="19"/>
<startLocation class="Point2D.Double" id="101" x="7.5" y="130.0"/>
<endNode class="ActivationBarNode" reference="30"/>
<endLocation class="Point2D.Double" id="102" x="7.0" y="160.0"/>
<transitionPoints id="103"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="104">
<text></text>
</startLabel>
<centerLabel id="105">
<text></text>
</centerLabel>
<endLabel id="106">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="107">
<id id="108" value="5e7f4a16-1fc3-4fc6-9dac-1d228ab268f8"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="34"/>
<startLocation class="Point2D.Double" id="109" x="7.0" y="20.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="110" x="7.5" y="60.0"/>
<transitionPoints id="111"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="112">
<text></text>
</startLabel>
<centerLabel id="113">
<text></text>
</centerLabel>
<endLabel id="114">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="115">
<id id="116" value="5fe641a1-312d-4394-96df-a684b18f6a1e"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="44"/>
<startLocation class="Point2D.Double" id="117" x="7.0" y="30.0"/>
<endNode class="ActivationBarNode" reference="19"/>
<endLocation class="Point2D.Double" id="118" x="7.5" y="110.0"/>
<transitionPoints id="119"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="120">
<text></text>
</startLabel>
<centerLabel id="121">
<text></text>
</centerLabel>
<endLabel id="122">
<text></text>
</endLabel>
</ReturnEdge>
<ReturnEdge id="123">
<id id="124" value="5769cc8e-d632-4921-9aea-5d2e0e15d351"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="30"/>
<startLocation class="Point2D.Double" id="125" x="7.0" y="170.0"/>
<endNode class="ActivationBarNode" reference="6"/>
<endLocation class="Point2D.Double" id="126" x="7.5" y="185.0"/>
<transitionPoints id="127"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="128">
<text></text>
</startLabel>
<centerLabel id="129">
<text></text>
</centerLabel>
<endLabel id="130">
<text></text>
</endLabel>
</ReturnEdge>
<SynchronousCallEdge id="131">
<id id="132" value="1334344f-9a27-4aff-b5d4-5ddfda00b259"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="6"/>
<startLocation class="Point2D.Double" id="133" x="7.5" y="215.0"/>
<endNode class="ActivationBarNode" reference="48"/>
<endLocation class="Point2D.Double" id="134" x="140.0" y="290.0"/>
<transitionPoints id="135"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>0</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>3</selectedEndArrowhead>
<startLabel id="136">
<text></text>
</startLabel>
<centerLabel id="137">
<text>modification(evt1)</text>
</centerLabel>
<endLabel id="138">
<text></text>
</endLabel>
</SynchronousCallEdge>
<ReturnEdge id="139">
<id id="140" value="397b25a5-6d9e-43d2-9c80-f1ec604ef31c"/>
<revision>1</revision>
<startNode class="ActivationBarNode" reference="48"/>
<startLocation class="Point2D.Double" id="141" x="7.0" y="30.0"/>
<endNode class="ActivationBarNode" reference="6"/>
<endLocation class="Point2D.Double" id="142" x="7.5" y="255.0"/>
<transitionPoints id="143"/>
<backgroundColor reference="69"/>
<borderColor reference="70"/>
<textColor reference="71"/>
<selectedBentStyle>1</selectedBentStyle>
<selectedLineStyle>1</selectedLineStyle>
<selectedStartArrowhead>0</selectedStartArrowhead>
<selectedEndArrowhead>1</selectedEndArrowhead>
<startLabel id="144">
<text></text>
</startLabel>
<centerLabel id="145">
<text></text>
</centerLabel>
<endLabel id="146">
<text></text>
</endLabel>
</ReturnEdge>
</edges>
</SequenceDiagramGraph>]]></SCRIPT>
<BR />
<BR />
<IMG alt="embedded diagram image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABJQAAAHDCAIAAAD9e9f6AABLXklEQVR4Xu3d7ZscV33n/zxP/oA8
C8/zUDYxtgO2g43txDiA8cbceSGQJb84sF7uQkiWkIQENkuW+PczBLi4AiGEu2Agu5hkCWhmNLeS
LM8wI41lyyNL1vgGjSVL49GMpJme35cpVDqqqu4+n65T1ae73q+rL1/Vpz91uqrnfNX9dU/3/MJu
YOhsN0b2zAGUkC2w0rJ3AKCEbIF5yE4BDL5f2N1TMQDRatSSbtTJApWqopqqmBNopt6qqbe9gGjR
vGEINWpJN+pkgUpVUU1VzAk0U2/V1NteQLRo3jCEGrWkG3WyQKWqqKYq5gSaqbdq6m0vIFo0bxhC
jVrSjTpZoFJVVFMVcwLN1Fs19bYXEC2aNwyhRi3pRp0sUKkqqqmKOYFm6q2aetsLiBbNG4ZQo5Z0
o04WqFQV1VTFnEAz9VZNve0FRIvmDUOoUUu6UScLVKqKaqpiTqCZequm3vYCokXzhiHUqCXdqJMF
KlVFNVUxJ9BMvVVTb3sB0aJ5wxBq1JJu1MkClaqimqqYE2im3qqpt72AaNG8YQg1akk36mSBSlVR
TVXMCTRTb9XU215AtGjeMIQataQbdbJApaqopirmBJqpt2rqbS8gWjRvGEKNWtKNOlmgUlVUUxVz
As3UWzX1thcQLZo3DKFGLelGnSxQqSqqqYo5gWbqrZp62wuIFs0bhlCjlnSjThaoVBXVVMWcQDP1
Vk297QVEi+YNQ6hRS7pRJwtUqopqqmJOoJl6q6be9gKiRfOGIdSoJd2okwUqVUU1VTEn0Ey9VVNv
ewHRonnDEGrUkm7UyQKVqqKaqpgTaKbeqqm3vYBo0bxhCDVqSTfqZIFKVVFNVcwJNFNv1dTbXkC0
aN4whBq1pBt1skClqqimKuYEmqm3auptLyBaNG8YQo1a0o06WaBSVVRTFXMCzdRbNfW2FxAtmjcM
oUYt6UadLFCpKqqpijmBZuqtmnrbC4gWzVt9du3IjqICjVrSPifbnLXXnDONxJA94D7VpPKZc8ge
xg6ac6aRGLIH3Kea8nrbC6ohW2wxq6958/yhHj169B/+4R/uvffeq666yiff1cLCwv3333/77bdf
d911d91111/+5V8eOnQok1ldXf3c5z73O7/zO9dff73F7r777gcffPDMmTOZWOdTSG7NywScPWJU
eJCXT+ZKmVilpHusZ0lHwudkM48eVdZfhQd5+WSulIlVqvAeCwdTnW9Necb6zqeaVD5zuo/PY489
9slPfvKNb3zjtddeazX1vve97+DBg1fGZVSopPAgL5/MlTKxShXeY+FgqvOtKc9Y3/lUU15ve6l8
HkOqu+8KD/LyyVwpE6uUdI/RNW+XHzO/fGc//vGPr7766sycmWmXlpZ+8zd/M5vYteu222574okn
3GQy7o64rtz7skzA2SNGhQd5+WSulIlVSrrHepZ0JHxONvPoXf4RXjneG6pMVXiQl0/mSplYpQrv
sXBQFWSSGvhUk8pnTvfxSbZdVl8/+tGPrtxDQIWqCg/y8slcKROrVOE9Fg6qgkxSA59qyuttL5XP
Y5hkXFR3zQoP8vLJXCkTq5R0j9E1b2984xs/97nPPfnkk575zmw2m+S+++47fPjw+fPnl5eXH374
4XvvvTcNrK+vv+51r7PMPffcMzExsba2du7cuampKbtqg3fccYeNpOHOh9T51m2PQAwKD7JwsGbS
MdSzpCPhc7KZR48q66/CgywcrFnhMRQOqoJMUgOfalL5zOk+Pm95y1seeuihp556amNjw2rq7W9/
u930hje84co9BFSoqvAgCwdrVngMhYOqIJPUwKea8nrbS+XzGFLdfVd4kIWDNZOOQWjevv/977/r
Xe961atedc0119x5551/93d/t7q6auOnT5+2EWv9V1ZW3LytmFe+8pU333zzxYsXk2NyuclCnrHO
XvGKV9gkL7zwQvaGS/75n//ZAnfddZe7fI1dtXKym7785S+ng50PqfOt20WBdg9pIsnbkfzZn/2Z
PZK33HKLHa2Nnzlz5k/+5E9uuOGGm2666cEHH2y1Wpdn9JvTfjQf+9jHbE6b4YEHHkhnSG51uePp
JC6fn34yUsWBteO5pIeDz8m2e9DajUuossI51cXsbmf0t8rc7Tz3Vns58tnPfvb1r3/9ddddd+ut
t77nPe/Zv39/mnGlu5c54ITPDHZgn/zkJ+2BevnLX+7sWsCnmlQ+c2YeFpe9GrObrMqyN3ijQgvn
bLeukltd7ng6iYsKLTzghM8MVVeo517tDtXn53v5sbvETbZDde+iup1bXemOhbyaN5vdHsfsxLt2
Wcf/0ksvWeDDH/6wXf3qV7/q7vXd737XBj/96U9v64e13fGhTHXN2Hq1wBe+8AV79LO37bAH2gL/
/u//nr1he9sG7aZ3vOMd6Ujnu+t86/aVga4PaZp///vf7wZGR0ff9ra3uSO2XNQ5P/jBD7qBb3zj
G+6tLnc82c7r+tOv7sDa6bqkh4nPybZ70NqNu7pmqLJdRXOqi9ndzutjlbnbee6tH/3oRy/vfEma
yQyWP2D/GT7wgQ8kG1dddVUy3o5PNal85kwOLzu649SpU3bT3Xffnb3hkg77JqjQXUVztltX7mDC
HU+286jQVIQV2nWvrofa9eeb3bP9T8RFde+iup1bXekdFfJq3r7zne/s2vnt2B/+8Ie2PjY2Nubm
5t761rfaoDWOFti7d69tv/nNb3b3SgJLS0vJVZ+jcfnku2aSpZmwPth+AA8//HDaJZsbb7xxV5v/
aWFNtt1kgXSk892ld5SRCSTbXR/SNH/PPfc8/vjj9uP/xCc+YVetp8+MpIXnP6f9pI4cObK6uvrx
j3/crlomuTUNpFfdwbzk1q4//eoOrJ2uS3qY+Jxsu0ev3bira4YqK5xTXczJYF5yax+rrHAw5d56
ww032PY//dM/nTlzZm1t7cCBA/fdd18+lih/wP4zWGZ8fPzcuXPJYAc+1aTymTM5zuzoDjsXu+mL
X/xi9oZLOuyboEIL52y3rtJAetUdzEtupULzB+w/Q9UV2nWvrofa9ee7XfQYdkV176K6L3HvsSuv
5u3ee++1GR999FF38MSJEzb4ute9bnun6Ux+pzZdxIuLi3b17W9/e5qXDmtbz7ezb9+++++///rr
r08m3LWzUE5f+l8UL3/5y21kc3Pzyp1+Jnkf3H07u/MhpfNnZALJdteHdPtS/ic/+Uly9eTJk4Uj
N998c3LVf875+fnk6k9/+lO7ag9Omk8C6VV3MC+5tetPv7oDa6frkh4mPifb7tFrN66iyvJzqos5
GcxLbu1jlRUOptxbkyN873vf+/nPf/6RRx7Z2toqjCXKH7D/DD/+8Y/dTAc+1aTymTM5zuzo9vYP
fvCDq666yk7HXg1kb1NQofk5262rNJBedQfzklup0O3cAfvPUHWFdt2r66F2/fluFz2GnVHdSZ7q
dgPp1c52+zRv1113XTJp3jXXXJNkvvzlL9vVBx98MLma9JTf+9730kmSfHq1KzXfmf0j9eSTT37z
m9+84447bFo7vGS8w/+TsMFdO5/mTEc6H1LnW7evDPg8pMnV8+fPJ1dt9RSOpL9mUGbO5GoaSK92
GHR1/ulXd2DtdF3Sw8TnZNs9eu3Ge0OVdZ4zuZoG0qsdBl39qrLCwZR768zMjD3LJiPmt3/7tw8f
PpyPJcofsP8M+W/EbsenmlQ+cybHmRm013ZXX321PbtnvhGuZ1Ro5zmTq2kgvdph0EWFRluhXffy
OdTOP9/tosewA6o7zefXD9XdlVfzdu211yaTFkoyp06dskO0RWAHtLa29us7/L/EJk/Ne1paWrJp
b7/99uTqO9/5zl0dfxv4/vvvT0c6H1LnW7evDPg8pO62z0iQOfOBdoOuzj/96g6sna5Lepj4nGy7
R6/deElUWbpdOJIPtBt09avKCgdTmVs3NjbGx8c/9alP3XrrrTZuP/rC2HaIA+5ths58qknlM2f+
OB966KGrd4yMjLjjQVCh6XbhSD7QbtBFhWZGepuhM59qyuu6l8+hdv75bivnQnXn8z4jQebMB9oN
ujr/9Ks7sHa8mrfkFze7/iXBD33oQ7t23jf8l3/5F9v42Mc+5t6a/Dlg9236zqTT8Je8U5m2wl/9
6ld37XymMPPL1nb1jTtfwLpnz550sPMhdb51+8qAz0Oan7DzSJA5C39M+b3yOvz0qzuwdrou6WHi
c7L5h7fzeElUWYcZttss5vxeeX2psvxerna3Jr/uct111yVX8zOXP+DeZujMp5pUPnNmjjMpGXvQ
3P+vHxAV2mGG7aLlup3LFKJC3ZHeZujMp5ryuu7lc6jbHX++20WPYSGqO5WfsPNIkDkLf0z5vfI6
/PSrO7B2dvs0b7bCbMZbbrnlO9/5zvLy8sbGxurq6uHDh7/+9a+/6U1vSmMzMzO7dt6rTf58xNzc
nDPH9k033WSDk5OTnkeWP88e2JHYwrVDtQO2FtkOKfniHVuyScCW72tf+9pdOx8inJqasqvr6+t2
InZ118534LScryjtfEidb92+MuDzkOYn7DwSZM7CH1N+r7wOP/3qDqydrkt6mPicbP7h7TwuocrU
OQsXc36vvL5UWX4vl3vr7/3e742Pj58+fdqWwbe+9a1dzqcp8jOXP+DeZujMp5pUPnO6x/nFL35x
185ru29/+9tXpnpEhapz5pdrPlOICnVHepuhM59qyuu6l8+hbnf8+W4XPYZ5VHfnn37nkSBzFv6Y
8nvldfjpV3dg7ez2ad7M3/zN3yR3k5dmbAXceeedyR+dyP/Nwcz3Y2ZuTbkZVza3o8NNiSvn+Lmr
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
S3a9ArjS/w9VgMdaZn7aJwAAAABJRU5ErkJg" />
</BODY>
</HTML>
/trunk/OpenConcerto/src/org/openconcerto/utils/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/prog/ExtractFromBEncoding.java
New file
0,0 → 1,234
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils.prog;
 
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.text.CSVReader;
 
import java.io.IOException;
import java.io.Writer;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
 
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;
import javax.mail.internet.ParseException;
 
import com.zimbra.common.util.BEncoding;
import com.zimbra.common.util.BEncoding.BEncodingException;
 
/**
* Load a CSV file and replace some {@link BEncoding zimbra benconded map} columns by some of their
* toplevel values. Sample SQL to generate the CSV :
*
* <pre>
*SELECT 'ID', 'sujet', 'date', 'metadonnées'
UNION ALL
SELECT id, subject, from_unixtime(floor(date)), metadata
FROM mail_item
WHERE mailbox_id=70 and type=5
ORDER BY date DESC
INTO OUTFILE '/tmp/zimbraMails.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '\"' ESCAPED BY '\\' LINES TERMINATED BY '\n' ;
* </pre>
*
* @author sylvain
*/
public class ExtractFromBEncoding {
 
static public final class ExtractInfo {
private final int fieldIndex;
private final List<String> keys, names;
 
public ExtractInfo(int fieldIndex, String[] keysAndNames) {
super();
this.fieldIndex = fieldIndex;
final List<String> keys = new ArrayList<>(keysAndNames.length);
final List<String> names = new ArrayList<>(keysAndNames.length);
for (final String kAn : keysAndNames) {
final int colonIndex = kAn.indexOf(':');
keys.add(kAn.substring(0, colonIndex));
names.add(kAn.substring(colonIndex + 1));
}
this.keys = Collections.unmodifiableList(keys);
this.names = Collections.unmodifiableList(names);
assert this.keys.size() > 0 && this.keys.size() == this.names.size();
}
 
public final int getFieldIndex() {
return this.fieldIndex;
}
 
public final List<String> getKeys() {
return this.keys;
}
 
public final List<String> getNames() {
return this.names;
}
}
 
private static final boolean OVERWRITE_FILE = Boolean.getBoolean("overwrite");
private static final Pattern DOUBLE_QUOTE_PATTERN = Pattern.compile("\"", Pattern.LITERAL);
private static final Pattern SINGLE_QUOTED_ENCODEDWORD_PATTERN = Pattern.compile("'(=\\?.+?\\?=)'");
 
public static void main(String[] args) throws IOException, BEncodingException, ParseException {
if (args.length == 0) {
System.out.println("Load a CSV file and replace some {@link BEncoding zimbra benconded map} columns by some of their toplevel values");
System.out.println("The bencoded colums are removed and the new columns are added at the end");
System.out.println("inputFile outputFile [bencodedIndex key1:label1,key2:label2,...]...");
System.exit(1);
}
 
final Path in = Paths.get(args[0]);
final Path out = Paths.get(args[1]);
if (Files.exists(out) && Files.isSameFile(in, out))
throw new IllegalArgumentException("Same file");
 
final List<ExtractInfo> extractInfos = new ArrayList<>();
// can't have the same index more than one time because we remove it
final Set<Integer> indexes = new HashSet<>();
for (int i = 2; i < args.length; i += 2) {
final int index = Integer.parseInt(args[i]);
if (!indexes.add(index))
throw new IllegalArgumentException("Duplicate index " + index);
final String[] keys = args[i + 1].split(",");
extractInfos.add(new ExtractInfo(index, keys));
}
 
System.out.println("From " + in + " to " + out);
final int lines = replace(in, out, extractInfos);
System.out.println("Processed " + lines + " line(s)");
}
 
/**
* Read from <code>in</code> and write to <code>out</code>.
*
* @param in the input CSV.
* @param out the output CSV.
* @param extractInfos what to extract.
* @return number of processed lines.
* @throws IOException if an error occurs while reading or writing.
* @throws BEncodingException if a value isn't b-encoded.
* @throws ParseException if a value isn't a valid "encoded-word".
*/
public static int replace(final Path in, final Path out, final List<ExtractInfo> extractInfos) throws IOException, ParseException, BEncodingException {
if (extractInfos.isEmpty()) {
Files.copy(in, out, OVERWRITE_FILE ? new CopyOption[] { StandardCopyOption.REPLACE_EXISTING } : new CopyOption[0]);
return 0;
}
 
int totalLines = 0;
final OpenOption[] openOptions = OVERWRITE_FILE ? new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING } : new OpenOption[] { StandardOpenOption.CREATE_NEW };
final StringBuilder sb = new StringBuilder(512);
try (final CSVReader r = new CSVReader(Files.newBufferedReader(in), ',', '"', '\\', 0, true);
// CSVWriter doesn't handle escaping quotes by doubling them (as needed by
// LibreOffice), so output CSV ourselves
final Writer w = Files.newBufferedWriter(out, openOptions)) {
 
// column names
String[] line = r.readNext();
final int itemsCount = line.length;
 
int additionalCols = 0;
 
final List<String> listToWrite = new ArrayList<>(itemsCount);
listToWrite.addAll(Arrays.asList(line));
for (final ExtractInfo e : extractInfos) {
listToWrite.remove(e.getFieldIndex());
for (final String n : e.getNames()) {
listToWrite.add(n);
}
// we remove the metadata
additionalCols += e.getKeys().size() - 1;
}
final int outputColCount = itemsCount + additionalCols;
writeCSV(sb, listToWrite, outputColCount);
w.append(sb);
 
while ((line = r.readNext()) != null) {
totalLines++;
if (itemsCount != line.length) {
throw new IllegalStateException("Expected " + itemsCount + " items but got " + line.length + " : " + Arrays.asList(line));
}
listToWrite.clear();
listToWrite.addAll(Arrays.asList(line));
 
for (final ExtractInfo e : extractInfos) {
String item = line[e.getFieldIndex()];
if (item.startsWith("=?")) {
item = MimeUtility.decodeWord(item);
}
final Map<?, ?> bdecoded = (Map<?, ?>) BEncoding.decode(item);
 
listToWrite.remove(e.getFieldIndex());
for (final String key : e.getKeys()) {
// BEncoding cannot encode null but a key can be missing
final Object val = bdecoded.get(key);
final String itemToWrite;
if (val == null) {
itemToWrite = null;
} else {
String toParse = val.toString();
// illegal single quoting, replace with double quote
if (toParse.contains("'=?"))
toParse = SINGLE_QUOTED_ENCODEDWORD_PATTERN.matcher(toParse).replaceAll("\"$1\"");
// strict=false because we don't care if there's "illegal character"
// (e.g. annabelle@testaud@ac-paris.fr or
// <'jeromebarral83@yahoo.fr'>), we want it to be legible
final InternetAddress[] mimeDecoded = InternetAddress.parseHeader(toParse, false);
 
itemToWrite = CollectionUtils.join(Arrays.asList(mimeDecoded), ", ", InternetAddress::toUnicodeString);
}
listToWrite.add(itemToWrite);
}
}
 
writeCSV(sb, listToWrite, outputColCount);
w.append(sb);
}
}
return totalLines;
}
 
private static void writeCSV(final StringBuilder sb, final List<String> l, final int outputColCount) {
if (l.size() != outputColCount)
throw new IllegalStateException("Wrong column count");
sb.setLength(0);
for (final String i : l) {
if (i == null) {
sb.append("\\N");
} else {
sb.append('"');
sb.append(DOUBLE_QUOTE_PATTERN.matcher(i).replaceAll("\"\""));
sb.append('"');
}
sb.append(',');
}
// replace last field separator
sb.setCharAt(sb.length() - 1, '\n');
}
}
/trunk/OpenConcerto/src/org/openconcerto/utils/ProductInfo.java
30,9 → 30,12
public static final String PROPERTIES_NAME = "/product.properties";
public static final String ORGANIZATION_NAME = "ORGANIZATION_NAME";
public static final String ORGANIZATION_ID = "ORGANIZATION_ID";
// e.g. LibreOffice
public static final String NAME = "NAME";
public static final String ID = "ID";
public static final String VERSION = "VERSION";
// Allow multiple end-user applications (e.g. icons) to share a product name, e.g. Writer
public static final String LAUNCHER = "LAUNCHER";
 
private static ProductInfo INSTANCE;
 
156,6 → 159,18
return orgID == null ? null : orgID + '.' + this.getID();
}
 
public final String getLauncher() {
return this.getProperty(LAUNCHER);
}
 
public final String getFullLauncherID() {
final String launcher = this.getLauncher();
final String fullID = this.getFullID();
if (launcher == null || fullID == null)
return fullID;
return fullID + '-' + sanitizeDomainPart(launcher);
}
 
public final String getVersion() {
return this.getProperty(VERSION);
}
162,10 → 177,14
 
public final void store(final Path p) throws IOException {
try (final OutputStream out = Files.newOutputStream(p)) {
this.getProps().store(out, this.getClass().getName());
store(out);
}
}
 
public final void store(final OutputStream out) throws IOException {
this.getProps().store(out, this.getClass().getName());
}
 
@Override
public String toString() {
return this.getClass().getSimpleName() + " for " + getName() + " " + getVersion();
/trunk/OpenConcerto/src/org/openconcerto/utils/Outlook.powershell
New file
0,0 → 1,31
Add-Type -assembly "Microsoft.Office.Interop.Outlook"
Add-Type -assembly "System.Runtime.Interopservices"
try {
$outlook = [Runtime.Interopservices.Marshal]::GetActiveObject('Outlook.Application')
$outlookWasAlreadyRunning = $true
} catch {
try {
$Outlook = New-Object -comobject Outlook.Application
$outlookWasAlreadyRunning = $false
} catch {
write-host "You must exit Outlook first."
exit
}
}
 
$mail = $Outlook.CreateItem( [Microsoft.Office.Interop.Outlook.OlItemType]::olMailItem)
 
$stdin = [Console]::OpenStandardInput()
try {
$sr = New-Object -TypeName 'System.IO.StreamReader' -ArgumentList $stdin,[Text.UTF8Encoding]
$mail.body = $sr.ReadToEnd()
} finally {
$stdin.Close()
}
 
$mail.subject = @subject@
$mail.to = @to@
foreach ( $att in @attachments@ ) {
$mail.attachments.add($att)
}
$mail.Display()
/trunk/OpenConcerto/src/org/openconcerto/utils/CollectionUtils.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,6 → 13,7
package org.openconcerto.utils;
 
import org.openconcerto.utils.cc.CustomEquals;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.IPredicate;
import org.openconcerto.utils.cc.ITransformer;
47,7 → 48,10
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
/**
* Une classe regroupant des méthodes utilitaires pour les collections.
403,13 → 407,53
}
 
static public final boolean identityContains(final Collection<?> coll, final Object item) {
final int size = coll.size();
if (size > 0) {
if (coll instanceof RandomAccess && coll instanceof List) {
final List<?> list = (List<?>) coll;
for (int i = 0; i < size; i++) {
final Object v = list.get(i);
if (item == v)
return true;
}
} else {
for (final Object v : coll) {
if (item == v)
return true;
}
}
}
return false;
}
 
static public final boolean identityRemove(final Collection<?> coll, final Object item) {
final int size = coll.size();
if (size > 0) {
if (coll instanceof RandomAccess && coll instanceof List) {
final List<?> list = (List<?>) coll;
for (int i = 0; i < size; i++) {
final Object v = list.get(i);
if (item == v) {
list.remove(i);
return true;
}
}
} else {
for (Iterator<?> i = coll.iterator(); i.hasNext();) {
if (item == i.next()) {
i.remove();
return true;
}
}
}
}
return false;
}
 
static public final <T> int identityIndexOf(final List<T> coll, final T item) {
return CustomEquals.indexOf(coll, item, CustomEquals.getIdentity());
}
 
static public final boolean identityEquals(final List<?> coll1, final List<?> coll2) {
if (coll1 == coll2)
return true;
463,11 → 507,19
return null;
}
 
final List<E> result;
if (list instanceof RandomAccess) {
final int size = list.size();
final List<E> result = new ArrayList<E>(size);
for (int i = 0; i < list.size(); i++) {
result = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
result.add(c.cast(list.get(i)));
}
} else {
result = new LinkedList<>();
for (final Object o : list) {
result.add(c.cast(o));
}
}
return result;
}
 
484,11 → 536,15
* a <code>E</code> or a value which the type is not a <code>F</code>.
*/
public static <E, F> Map<E, F> castMap(final Map<?, ?> map, Class<E> cKey, Class<F> cValue) throws ClassCastException {
return castMap(map, cKey, cValue, HashMap::new);
}
 
public static <E, F> Map<E, F> castMap(final Map<?, ?> map, Class<E> cKey, Class<F> cValue, final Supplier<Map<E, F>> ctor) throws ClassCastException {
if (map == null) {
return null;
}
 
final Map<E, F> result = new HashMap<E, F>();
final Map<E, F> result = ctor.get();
for (final Entry<?, ?> mapEntry : map.entrySet()) {
final E key;
try {
507,6 → 563,14
return result;
}
 
public static <T> Stream<T> subclassFilter(final Collection<? super T> coll, final Class<T> subclass) {
return coll.stream().filter(subclass::isInstance).map(subclass::cast);
}
 
public static <T> List<T> subclassFilterToList(final Collection<? super T> coll, final Class<T> subclass) {
return subclassFilter(coll, subclass).collect(Collectors.toList());
}
 
/**
* The number of equals item between a and b, starting from the end.
*
948,13 → 1012,42
}
}
 
@SafeVarargs
public static final <T> List<T> toImmutableList(final T... items) {
final int length = items.length;
if (length == 0)
return Collections.emptyList();
else if (length == 1)
return Collections.singletonList(items[0]);
return Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(items, length)));
}
 
public static final <T> List<T> toImmutableList(final Collection<? extends T> coll) {
return toImmutableList(coll, ArrayList::new);
}
 
public static final <T, C extends Collection<? extends T>> List<T> toImmutableList(final C coll, final Function<? super C, ? extends List<T>> createColl) {
if (coll.isEmpty())
return toImmutableList(coll, createColl, true);
}
 
/**
* Create a new immutable list.
*
* @param <T> type of items
* @param <C> type of collection
* @param coll the collection holding the items.
* @param createColl how to copy the collection into a new list.
* @param std <code>true</code> if the created <code>createColl</code> use
* {@link Object#equals(Object)}, in that case {@link Collections#singletonList(Object)}
* can be returned.
* @return an immutable list with the same items as <code>coll</code>.
*/
public static final <T, C extends Collection<? extends T>> List<T> toImmutableList(final C coll, final Function<? super C, ? extends List<T>> createColl, final boolean std) {
final int size = coll.size();
if (size == 0)
return Collections.emptyList();
else if (std && size == 1)
return Collections.singletonList(coll.iterator().next());
return Collections.unmodifiableList(createColl.apply(coll));
}
 
966,7 → 1059,7
// but not at runtime.
return toImmutableSet((SortedSet<T>) coll, TreeSet::new);
} else if (coll instanceof IdentitySet) {
return toImmutableSet((IdentitySet<? extends T>) coll, LinkedIdentitySet::new);
return toImmutableSet((IdentitySet<? extends T>) coll, LinkedIdentitySet::new, false);
} else {
// In doubt, keep order
// ATTN LinkedHashSet extends HashSet
975,8 → 1068,28
}
 
public static final <T, C extends Collection<? extends T>> Set<T> toImmutableSet(final C coll, final Function<? super C, ? extends Set<T>> createColl) {
if (coll.isEmpty())
return toImmutableSet(coll, createColl, true);
 
}
 
/**
* Create a new immutable set.
*
* @param <T> type of items
* @param <C> type of collection
* @param coll the collection holding the items.
* @param createColl how to copy the collection into a new list.
* @param std <code>true</code> if the created <code>createColl</code> use
* {@link Object#equals(Object)}, in that case {@link Collections#singleton(Object)} can
* be returned.
* @return an immutable set with the same items as <code>coll</code>.
*/
public static final <T, C extends Collection<? extends T>> Set<T> toImmutableSet(final C coll, final Function<? super C, ? extends Set<T>> createColl, final boolean std) {
final int size = coll.size();
if (size == 0)
return Collections.emptySet();
else if (std && size == 1)
return Collections.singleton(coll.iterator().next());
final Set<T> res = createColl.apply(coll);
return Collections.unmodifiableSet(res);
}
995,7 → 1108,7
// ATTN see eclipse bug below about wrong constructor
return toImmutableMap((SortedMap<K, ? extends V>) map, TreeMap::new);
} else if (map instanceof IdentityHashMap) {
return toImmutableMap((IdentityHashMap<? extends K, ? extends V>) map, IdentityHashMap::new);
return toImmutableMap((IdentityHashMap<? extends K, ? extends V>) map, IdentityHashMap::new, false);
} else {
// In doubt, keep order
// ATTN LinkedHashMap extends HashMap
1003,6 → 1116,10
}
}
 
public static final <K, V, InMap extends Map<? extends K, ? extends V>> Map<K, V> toImmutableMap(final InMap map, final Function<? super InMap, ? extends Map<K, V>> copyFunction) {
return toImmutableMap(map, copyFunction, true);
}
 
/**
* Return an immutable map with the same entries as the passed one. NOTE: <code>copyMap</code>
* <strong>must</strong> copy the entries so that a modification of <code>map</code> doesn't
1016,11 → 1133,20
* {@link TreeMap#TreeMap(Map)} is (correctly) executed at runtime.
* @param map the map.
* @param copyFunction how to copy the passed map.
* @param std <code>true</code> if the created <code>createColl</code> use
* {@link Object#equals(Object)}, in that case
* {@link Collections#singletonMap(Object, Object)} can be returned.
* @return an immutable map.
*/
public static final <K, V, InMap extends Map<? extends K, ? extends V>> Map<K, V> toImmutableMap(final InMap map, final Function<? super InMap, ? extends Map<K, V>> copyFunction) {
if (map.isEmpty())
public static final <K, V, InMap extends Map<? extends K, ? extends V>> Map<K, V> toImmutableMap(final InMap map, final Function<? super InMap, ? extends Map<K, V>> copyFunction,
final boolean std) {
final int size = map.size();
if (size == 0) {
return Collections.emptyMap();
} else if (std && size == 1) {
final Entry<? extends K, ? extends V> e = map.entrySet().iterator().next();
return Collections.singletonMap(e.getKey(), e.getValue());
}
return Collections.unmodifiableMap(copyFunction.apply(map));
}
 
1055,7 → 1181,77
return res;
}
 
// same as HashMap and HashSet
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
 
public static interface MapConstructor<K, V, M extends Map<K, V>> {
M create(int initialCapacity, float loadFactor);
}
 
public static final int getCapacity(final int plannedSize, final float loadFactor) {
return Math.max((int) Math.ceil(plannedSize / loadFactor), 4);
}
 
public static final <K, V> LinkedHashMap<K, V> newLinkedHashMap(final int plannedSize) {
return newMap(plannedSize, DEFAULT_LOAD_FACTOR, (MapConstructor<K, V, LinkedHashMap<K, V>>) LinkedHashMap::new);
}
 
public static final <K, V> HashMap<K, V> newHashMap(final int plannedSize) {
return newHashMap(plannedSize, DEFAULT_LOAD_FACTOR);
}
 
/**
* Create a new map with no rehash operations until at least a certain size.
*
* @param <K> the type of keys maintained by the new map
* @param <V> the type of mapped values
* @param plannedSize the number of entries expected to be added to the returned map.
* @param loadFactor the load factor of the returned map, not advised to be <code>1.0f</code>
* because it increases the chances of hash collisions.
* @return a new map that won't rehash until at least <code>plannedSize</code> items.
*/
public static final <K, V> HashMap<K, V> newHashMap(final int plannedSize, final float loadFactor) {
return newMap(plannedSize, loadFactor, (MapConstructor<K, V, HashMap<K, V>>) HashMap::new);
}
 
/**
* Create a new map with no rehash operations until at least a certain size.
*
* @param <K> the type of keys maintained by the new map
* @param <V> the type of mapped values
* @param <M> the type of the new map
* @param plannedSize the number of entries expected to be added to the returned map
* @param loadFactor the load factor, passed to <code>ctor</code>.
* @param ctor how to create the new map, will be passed the computed initial capacity and
* <code>loadFactor</code>, e.g. {@link LinkedHashMap#LinkedHashMap(int, float)
* LinkedHashMap::new}.
* @return a new map that won't rehash until at least <code>plannedSize</code> items.
*/
public static final <K, V, M extends Map<K, V>> M newMap(final int plannedSize, final float loadFactor, final MapConstructor<K, V, M> ctor) {
return ctor.create(getCapacity(plannedSize, loadFactor), loadFactor);
}
 
public static interface SetConstructor<V, S extends Set<V>> {
S create(int initialCapacity, float loadFactor);
}
 
public static final <V> LinkedHashSet<V> newLinkedHashSet(final int plannedSize) {
return newSet(plannedSize, DEFAULT_LOAD_FACTOR, (SetConstructor<V, LinkedHashSet<V>>) LinkedHashSet::new);
}
 
public static final <V> HashSet<V> newHashSet(final int plannedSize) {
return newHashSet(plannedSize, DEFAULT_LOAD_FACTOR);
}
 
public static final <V> HashSet<V> newHashSet(final int plannedSize, final float loadFactor) {
return newSet(plannedSize, loadFactor, (SetConstructor<V, HashSet<V>>) HashSet::new);
}
 
public static final <V, S extends Set<V>> S newSet(final int plannedSize, final float loadFactor, final SetConstructor<V, S> ctor) {
return ctor.create(getCapacity(plannedSize, loadFactor), loadFactor);
}
 
/**
* Creates a map with null values.
*
* @param <K> type of key.
/trunk/OpenConcerto/src/org/openconcerto/utils/TimeUtils.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,9
 
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
424,4 → 427,30
}
return finerAmount == finer.convert(coarserAmount, coarser);
}
 
public static LocalDateTime toLocalDateTime(Calendar calendar) {
if (calendar == null)
return null;
return LocalDateTime.ofInstant(calendar.toInstant(), getTZ(calendar));
}
 
private static ZoneId getTZ(Calendar calendar) {
final TimeZone tz = calendar.getTimeZone();
return tz == null ? ZoneId.systemDefault() : tz.toZoneId();
}
 
public static final Calendar toCalendar(final LocalDateTime dt) {
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(dt.atZone(getTZ(cal)).toInstant().toEpochMilli());
return cal;
}
 
public static LocalDate toLocalDate(Calendar calendar) {
if (calendar == null)
return null;
if (calendar instanceof GregorianCalendar)
return LocalDate.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH));
else
return toLocalDateTime(calendar).toLocalDate();
}
}
/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);
}
 
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();
// 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 int returnCode = process.waitFor();
if (returnCode != 0)
throw new IllegalStateException("Non zero return code: " + returnCode);
/trunk/OpenConcerto/src/org/openconcerto/laf/IScrollBarUI.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/laf/ILookAndFeel.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/laf/IComboBoxUI.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/laf/IButtonUI.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/task/config/ComptaOptions.java
New file
0,0 → 1,59
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.task.config;
 
 
import net.jcip.annotations.Immutable;
 
@Immutable
public class ComptaOptions {
 
private final Customer customer;
 
public ComptaOptions(final Customer customer) {
this.customer = customer;
}
 
public final Customer getCustomer() {
return this.customer;
}
 
public final boolean customerUseControle() {
return customerIsPreventec() || customerIsKD();
}
 
public final boolean customerIsKD() {
final boolean kd = getCustomer() == Customer.KD;
return kd;
}
 
public final boolean customerIsODT() {
final boolean kd = getCustomer() == Customer.ODT;
return kd;
}
 
public final boolean customerIsTCP() {
final boolean kd = getCustomer() == Customer.TCP;
return kd;
}
 
public final boolean customerIsPreventec() {
final boolean prev = getCustomer() == Customer.PREVENTEC;
return prev;
}
 
public final boolean customerIsProfilbat() {
return getCustomer() == Customer.PROFILBAT;
}
}
/trunk/OpenConcerto/src/org/openconcerto/task/config/Customer.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.task.config;
 
public enum Customer {
GESTION_DEFAULT("Gestion_Default"), IDEATION("Ideation"), TCP("TCP"), KD("KD"), PREVENTEC("Preventec"), PROFILBAT("Profilbat"), CTD("CTD"), ODT("ODT");
 
private final String name;
 
private Customer(String name) {
if (name == null)
throw new NullPointerException();
this.name = name;
}
 
public String getName() {
return this.name;
}
 
public static Customer fromName(String n) {
return Customer.valueOf(n.toUpperCase());
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationEcritures/GenerationMvtSaisieAchat.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
49,7 → 49,7
 
// iniatilisation des valeurs de la map
this.date = (Date) saisieRow.getObject("DATE");
this.nom = "Achat : " + rowFournisseur.getString("NOM") + " Facture : " + this.saisieRow.getObject("NUMERO_FACTURE").toString() + " " + saisieRow.getObject("NOM").toString();
this.nom = " Fourn. : " + rowFournisseur.getString("NOM") + " " + saisieRow.getObject("NOM").toString() + " Facture : " + this.saisieRow.getObject("NUMERO_FACTURE").toString();
this.putValue("DATE", this.date);
AccountingRecordsProvider provider = AccountingRecordsProviderManager.get(ID);
provider.putLabel(saisieRow, this.mEcritures);
/trunk/OpenConcerto/src/org/openconcerto/erp/generationEcritures/GenerationMvtSaisieVenteFacture.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.erp.core.finance.accounting.element.ComptePCESQLElement;
import org.openconcerto.erp.core.finance.accounting.element.JournalSQLElement;
import org.openconcerto.erp.generationDoc.SheetXml;
import org.openconcerto.erp.core.finance.tax.model.TaxeCache;
import org.openconcerto.erp.generationEcritures.provider.AccountingRecordsProvider;
import org.openconcerto.erp.generationEcritures.provider.AccountingRecordsProviderManager;
import org.openconcerto.erp.model.PrixTTC;
140,13 → 141,20
SQLTable tableEchantillon = null;
BigDecimal portHT = BigDecimal.valueOf(saisieRow.getLong("PORT_HT")).movePointLeft(2);
BigDecimal fraisDocHT = BigDecimal.valueOf(saisieRow.getLong("FRAIS_DOCUMENT_HT")).movePointLeft(2);
SQLRow taxeDoc = saisieRow.getNonEmptyForeign("ID_TAXE_FRAIS_DOCUMENT") == null ? TaxeCache.getCache().getFirstTaxe()
: TaxeCache.getCache().getRowFromId(saisieRow.getForeignID("ID_TAXE_FRAIS_DOCUMENT")).asRow();
 
TotalCalculator calc;
SQLRow taxePort = saisieRow.getNonEmptyForeign("ID_TAXE_PORT") == null ? TaxeCache.getCache().getFirstTaxe()
: TaxeCache.getCache().getRowFromId(saisieRow.getForeignID("ID_TAXE_PORT")).asRow();
 
if (clientRow.getTable().contains("ID_COMPTE_PCE_PRODUIT") && !clientRow.isForeignEmpty("ID_COMPTE_PCE_PRODUIT")) {
calc = getValuesFromElement(false, false, "T_PV_HT", saisieRow, saisieVFTable.getTable("SAISIE_VENTE_FACTURE_ELEMENT"), portHT, saisieRow.getForeign("ID_TAXE_PORT"), fraisDocHT,
saisieRow.getForeign("ID_TAXE_FRAIS_DOCUMENT"), tableEchantillon, clientRow.getForeign("ID_COMPTE_PCE_PRODUIT"));
 
calc = getValuesFromElement(false, false, "T_PV_HT", saisieRow, saisieVFTable.getTable("SAISIE_VENTE_FACTURE_ELEMENT"), portHT, taxePort, fraisDocHT, taxeDoc, tableEchantillon,
clientRow.getForeign("ID_COMPTE_PCE_PRODUIT"));
} else {
calc = getValuesFromElement(saisieRow, saisieVFTable.getTable("SAISIE_VENTE_FACTURE_ELEMENT"), portHT, saisieRow.getForeign("ID_TAXE_PORT"), fraisDocHT,
saisieRow.getForeign("ID_TAXE_FRAIS_DOCUMENT"), tableEchantillon);
 
calc = getValuesFromElement(saisieRow, saisieVFTable.getTable("SAISIE_VENTE_FACTURE_ELEMENT"), portHT, taxePort, fraisDocHT, taxeDoc, tableEchantillon);
}
 
// On génére les ecritures si la facture n'est pas un acompte
/trunk/OpenConcerto/src/org/openconcerto/erp/generationEcritures/Mouvement.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
101,6 → 101,25
 
}
 
public String asString() {
StringBuilder s = new StringBuilder();
BigDecimal d = BigDecimal.ZERO;
BigDecimal c = BigDecimal.ZERO;
s.append("Mouvement N°");
s.append(this.numero);
s.append("\n");
for (Ecriture e : this.ecritures) {
 
s.append("D : " + e.getDebit());
s.append("\tC : " + e.getCredit());
 
d = d.add(e.getDebit());
c = c.add(e.getCredit());
s.append("\tS : " + (d.subtract(c)) + "\n");
}
return s.toString();
}
 
public boolean isEmpty() {
return this.ecritures.isEmpty();
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationEcritures/GenerationMvtReglementAchat.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
80,7 → 80,7
// this.nom = saisieRow.getObject("NOM").toString();
 
// si paiement comptant
if ((modeRegRow.getInt("AJOURS") == 0) && (modeRegRow.getInt("LENJOUR") == 0)) {
if (typeRegRow.getID() != TypeReglementSQLElement.INDEFINI && (modeRegRow.getInt("AJOURS") == 0) && (modeRegRow.getInt("LENJOUR") == 0)) {
 
System.out.println("Règlement Comptant");
// test Cheque
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOgenerationListeColumnXML.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
152,49 → 152,8
}
Map<String, Map<Integer, String>> mapStyle = searchStyle(sheet, lastColumn, endPageLine);
 
// int nbPage = fillTable(tableau, liste, sheet, mapStyle, true, style);
// int firstLine = Integer.valueOf(tableau.getAttributeValue("firstLine"));
// int endLine = Integer.valueOf(tableau.getAttributeValue("endLine"));
// Object printRangeObj = sheet.getPrintRanges();
//
// System.err.println("Nombre de page == " + nbPage);
// if (nbPage == 1) {
// fillTable(tableau, liste, sheet, mapStyle, false, style);
// } else {
//
// if (printRangeObj != null) {
// String s = printRangeObj.toString();
// String[] range = s.split(":");
//
// for (int i = 0; i < range.length; i++) {
// String string = range[i];
// range[i] = string.subSequence(string.indexOf('.') + 1, string.length()).toString();
// }
//
// int rowEnd = -1;
// if (range.length > 1) {
// rowEnd = sheet.resolveHint(range[1]).y + 1;
// int rowEndNew = rowEnd * (nbPage + 1);
// String sNew = s.replaceAll(String.valueOf(rowEnd), String.valueOf(rowEndNew));
// sheet.setPrintRanges(sNew);
// System.err.println(" ****** Replace print ranges; Old:" + rowEnd + "--" + s + " New:" +
// rowEndNew + "--" + sNew);
// }
// }
//
// // le nombre d'éléments ne tient pas dans le tableau du modéle
// sheet.duplicateFirstRows(endLine, 1);
//
// int lineToAdd = endPageLine - endLine;
// sheet.insertDuplicatedRows(firstLine, lineToAdd);
//
// // On duplique la premiere page si on a besoin de plus de deux pages
// System.err.println("nbPage == " + nbPage);
// if (nbPage > 2) {
// sheet.duplicateFirstRows(endPageLine, nbPage - 2);
// }
fillTable(tableau, liste, sheet, mapStyle, false, style);
// }
}
 
/**
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/gestcomm/ReleveChequeEmisSheet.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
83,5 → 83,6
 
// Total
this.mCell.put("L45", new Double(GestionDevise.currencyToString(montantTotal, false)));
this.nbPage = 1;
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/gestcomm/AvoirClientXmlSheet.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
38,6 → 38,7
super(row);
this.printer = PrinterNXProps.getInstance().getStringProperty("BonPrinter");
this.elt = Configuration.getInstance().getDirectory().getElement("AVOIR_CLIENT");
this.setPostProcess(new OptionDocProcessor());
}
 
 
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/gestcomm/EtatVentesXmlSheet.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
30,6 → 30,7
import org.openconcerto.sql.model.SQLSelectJoin;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.utils.ListMap;
import org.openconcerto.utils.Tuple2;
 
import java.math.BigDecimal;
146,13 → 147,19
// Stock
SQLRowValues rowValsArtStock = new SQLRowValues(foreignTableArticle);
rowValsArtStock.putNulls("ID", "CODE", "NOM");
rowValsArtStock.putRowValues("ID_STOCK").putNulls("QTE_REEL", "QTE_TH", "QTE_MIN", "QTE_RECEPT_ATTENTE", "QTE_LIV_ATTENTE");
SQLRowValues rowValsStock = new SQLRowValues(tableFactureElement.getTable("STOCK"));
rowValsStock.putNulls("QTE_REEL", "QTE_TH", "QTE_MIN", "QTE_RECEPT_ATTENTE", "QTE_LIV_ATTENTE").put("ID_ARTICLE", rowValsArtStock);
SQLRowValuesListFetcher fetcherStock = SQLRowValuesListFetcher.create(rowValsArtStock);
List<SQLRowValues> resultStock = fetcherStock.fetch();
Map<Integer, SQLRowValues> mapStock = new HashMap<>();
 
ListMap<Integer, SQLRowValues> mapStock = new ListMap<>();
for (SQLRowValues sqlRowValues : resultStock) {
mapStock.put(sqlRowValues.getID(), sqlRowValues);
final Set<SQLRowValues> referentRows = sqlRowValues.getReferentRows(tableFactureElement.getTable("STOCK").getField("ID_ARTICLE"));
for (SQLRowValues sqlRowValues2 : referentRows) {
 
mapStock.add(sqlRowValues.getID(), sqlRowValues2);
}
}
 
// Requete Pour obtenir les quantités pour chaque type de réglement
SQLSelect sel = new SQLSelect();
324,17 → 331,28
mValuesStock.put("NOM", nom);
mValuesStock.put("QTE", a.qte);
if (mapStock.containsKey(articleID)) {
SQLRowValues rowValsArt = mapStock.get(articleID);
if (rowValsArt.getObject("ID_STOCK") != null && !rowValsArt.isForeignEmpty("ID_STOCK")) {
SQLRowAccessor rowValsStock = rowValsArt.getForeign("ID_STOCK");
mValuesStock.put("QTE_TH", rowValsStock.getObject("QTE_TH"));
mValuesStock.put("QTE_REEL", rowValsStock.getObject("QTE_REEL"));
mValuesStock.put("QTE_MIN", rowValsStock.getObject("QTE_MIN"));
mValuesStock.put("QTE_RECEPT_ATTENTE", rowValsStock.getObject("QTE_RECEPT_ATTENTE"));
mValuesStock.put("QTE_LIV_ATTENTE", rowValsStock.getObject("QTE_LIV_ATTENTE"));
List<SQLRowValues> rowValsArt = mapStock.get(articleID);
BigDecimal totalTh = BigDecimal.ZERO;
BigDecimal totalReel = BigDecimal.ZERO;
BigDecimal min = BigDecimal.ZERO;
BigDecimal attentR = BigDecimal.ZERO;
BigDecimal attenteL = BigDecimal.ZERO;
for (SQLRowValues rowStock : rowValsArt) {
totalTh = totalTh.add(new BigDecimal(rowStock.getFloat("QTE_TH")));
totalReel = totalReel.add(new BigDecimal(rowStock.getFloat("QTE_REEL")));
min = min.add(new BigDecimal(rowStock.getFloat("QTE_MIN")));
attentR = attentR.add(new BigDecimal(rowStock.getFloat("QTE_RECEPT_ATTENTE")));
attenteL = attenteL.add(new BigDecimal(rowStock.getFloat("QTE_LIV_ATTENTE")));
}
 
mValuesStock.put("QTE_TH", totalTh);
mValuesStock.put("QTE_REEL", totalReel);
mValuesStock.put("QTE_MIN", min);
mValuesStock.put("QTE_RECEPT_ATTENTE", attentR);
mValuesStock.put("QTE_LIV_ATTENTE", attenteL);
styleStock.put(listValuesStock.size(), "Normal");
listValuesStock.add(mValuesStock);
 
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/gestcomm/OptionDocProcessor.java
New file
0,0 → 1,78
/*
* 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.erp.generationDoc.gestcomm;
 
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.DecimalUtils;
 
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
public class OptionDocProcessor implements ITransformer<List<SQLRowAccessor>, List<SQLRowAccessor>> {
 
@Override
public List<SQLRowAccessor> transformChecked(List<SQLRowAccessor> input) {
 
List<SQLRowValues> resultRowValues = new ArrayList(input.size());
SQLRowValues lastRowNonOptionnal = null;
Set<SQLRowValues> recalculPrixUnitaire = new HashSet<SQLRowValues>();
for (SQLRowAccessor sqlRowAccessor : input) {
final SQLRowAccessor nonEmptyForeign = sqlRowAccessor.getNonEmptyForeign("ID_ARTICLE");
final SQLRowValues asRowValues = sqlRowAccessor.asRowValues();
if (lastRowNonOptionnal != null && nonEmptyForeign != null && nonEmptyForeign.getBoolean("OPTION")) {
lastRowNonOptionnal.put("T_PV_HT", lastRowNonOptionnal.getBigDecimal("T_PV_HT").add(asRowValues.getBigDecimal("T_PV_HT")));
lastRowNonOptionnal.put("T_PV_TTC", lastRowNonOptionnal.getBigDecimal("T_PV_TTC").add(asRowValues.getBigDecimal("T_PV_TTC")));
recalculPrixUnitaire.add(lastRowNonOptionnal);
asRowValues.put("PRIX_METRIQUE_VT_1", null);
asRowValues.put("PV_HT", null);
asRowValues.put("T_PV_HT", null);
asRowValues.put("T_PV_TTC", null);
asRowValues.putEmptyLink("ID_TAXE");
} else {
lastRowNonOptionnal = asRowValues;
}
resultRowValues.add(asRowValues);
}
List<SQLRowAccessor> result = new ArrayList(input.size());
 
for (SQLRowValues sqlRowValues : resultRowValues) {
if (recalculPrixUnitaire.contains(sqlRowValues)) {
 
String fieldQte = sqlRowValues.getTable().getName().startsWith("BON_DE_LIVRAISON") ? "QTE_LIVREE" : "QTE";
 
BigDecimal qte = sqlRowValues.getBigDecimal("QTE_UNITAIRE").multiply(new BigDecimal(sqlRowValues.getInt(fieldQte), DecimalUtils.HIGH_PRECISION));
if (qte.signum() != 0) {
 
BigDecimal totalHT = sqlRowValues.getBigDecimal("T_PV_HT");
final BigDecimal remisePercent = sqlRowValues.getBigDecimal("POURCENT_REMISE");
if (remisePercent != null && remisePercent.signum() > 0) {
totalHT = totalHT.divide(BigDecimal.ONE.subtract(remisePercent.movePointLeft(2)), org.openconcerto.utils.DecimalUtils.HIGH_PRECISION);
}
BigDecimal unitPrice = totalHT.divide(qte, DecimalUtils.HIGH_PRECISION);
sqlRowValues.put("PV_HT", unitPrice);
sqlRowValues.put("PRIX_METRIQUE_VT_1", unitPrice);
}
}
result.add(sqlRowValues);
}
 
return result;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOXMLElement.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
129,6 → 129,10
if (rmBreakLines) {
res = (res == null ? res : res.toString().replaceAll("\n", ","));
}
final String brk = this.elt.getAttributeValue("replaceWithBreakLine");
if (brk != null && brk.trim().length() > 0) {
res = (res == null ? res : res.toString().replaceAll(brk, "\n"));
}
return res;
}
 
145,8 → 149,24
 
protected String getStringProposition(SQLRowAccessor rowProp) {
 
if (rowProp.getTable().getName().equalsIgnoreCase("AFFAIRE")) {
final SQLRowAccessor nonEmptyForeignProp = rowProp.getNonEmptyForeign("ID_PROPOSITION");
String result = "";
if (rowProp.getString("NUMERO_PROPOSITION").trim().length() > 0) {
return "Notre proposition " + rowProp.getString("NUMERO_PROPOSITION");
}
if (nonEmptyForeignProp != null && !nonEmptyForeignProp.isUndefined()) {
if (result.length() == 0) {
result = "Notre proposition " + nonEmptyForeignProp.getString("NUMERO_PROPOSITION");
}
result += " du " + format.format(rowProp.getObject("DATE"));
}
return result;
} else {
 
return "Notre proposition " + rowProp.getString("NUMERO") + " du " + format.format(rowProp.getObject("DATE"));
}
}
 
 
public Double getTotalHTTable(SQLRowAccessor rowFact) {
215,4 → 235,8
public boolean isImage() {
return this.elt.getAttributeValue("type").equalsIgnoreCase("image");
}
 
public SQLElement getSQLElement() {
return this.sqlElt;
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOgenerationXML.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
257,16 → 257,21
lastColumn = sheet.resolveHint(oLastColTmp.toString() + 1).x + 1;
}
 
Map<String, Map<Integer, String>> mapStyle = searchStyle(sheet, lastColumn, endPageLine);
int firstLine = Integer.parseInt(tableau.getAttributeValue("firstLine"));
int endLine = Integer.parseInt(tableau.getAttributeValue("endLine"));
Object printRangeObj = sheet.getPrintRanges();
 
final Map<String, Map<Integer, String>> mapStyle;
 
if (tableau.getAttributeValue("table").equalsIgnoreCase("TVA")) {
fillTaxeDocumentMap(tableau, sheet, mapStyle, false);
mapStyle = null;
fillTaxeDocumentMap(tableau, sheet, false);
return;
} else {
mapStyle = searchStyle(sheet, firstLine, lastColumn, endLine);
}
 
int nbPage = fillTable(tableau, row, sheet, mapStyle, true, rowLanguage);
int firstLine = Integer.parseInt(tableau.getAttributeValue("firstLine"));
int endLine = Integer.parseInt(tableau.getAttributeValue("endLine"));
Object printRangeObj = sheet.getPrintRanges();
 
System.err.println("Nombre de page == " + nbPage);
if (nbPage == 1) {
281,10 → 286,9
range[i] = string.subSequence(string.indexOf('.') + 1, string.length()).toString();
}
 
int rowEnd = -1;
if (range.length > 1) {
rowEnd = sheet.resolveHint(range[1]).y + 1;
int rowEndNew = rowEnd * (nbPage);
int rowEnd = sheet.resolveHint(range[1]).y + 1;
int rowEndNew = rowEnd * nbPage;
String sNew = s.replaceAll(String.valueOf(rowEnd), String.valueOf(rowEndNew));
sheet.setPrintRanges(sNew);
System.err.println(" ****** Replace print ranges; Old:" + rowEnd + "--" + s + " New:" + rowEndNew + "--" + sNew);
752,7 → 756,7
return nbCellule;
}
 
private void fillTaxeDocumentMap(Element tableau, Sheet sheet, Map<String, Map<Integer, String>> mapStyle, boolean test) {
private void fillTaxeDocumentMap(Element tableau, Sheet sheet, boolean test) {
 
int line = Integer.parseInt(tableau.getAttributeValue("firstLine"));
List<Element> listElts = tableau.getChildren("element");
892,7 → 896,13
 
// on divise en 2 cellules si il y a des retours à la ligne
if (controleMultiline && value != null && value.toString().indexOf('\n') >= 0) {
String[] values = value.toString().split("\n");
String[] values;
if (cell.getRowsSpanned() < 2) {
// TODO : mettre une option pour splitter ou pas
values = value.toString().split("\n");
} else {
values = new String[] { value.toString() };
}
 
Point p = sheet.resolveHint(location);
int y = 0;
1030,7 → 1040,7
/**
* parcourt l'ensemble de la feuille pour trouver les style définit
*/
private Map<String, Map<Integer, String>> searchStyle(Sheet sheet, int colEnd, int rowEnd) {
private Map<String, Map<Integer, String>> searchStyle(Sheet sheet, int rowDeb, int colEnd, int rowEnd) {
 
if (cacheStyle.get(sheet) != null) {
return cacheStyle.get(sheet);
1043,8 → 1053,10
System.err.println("End column search : " + columnCount);
 
int rowCount = (rowEnd > 0) ? rowEnd : sheet.getRowCount();
int start = (rowDeb - 1 > 0) ? rowDeb - 1 : 0;
 
System.err.println("End row search : " + rowCount);
for (int i = 0; i < rowCount; i++) {
for (int i = start; i < rowCount; i++) {
int x = 0;
Map<Integer, String> mapCellStyle = new HashMap<>();
String style = "";
1119,12 → 1131,13
 
Object oLastColTmp = tableau.getAttributeValue("lastColumn");
int lastColumn = -1;
int endPageLine = Integer.parseInt(tableau.getAttributeValue("endPageLine"));
if (oLastColTmp != null) {
lastColumn = sheet.resolveHint(oLastColTmp.toString() + 1).x + 1;
}
int firstLine = Integer.parseInt(tableau.getAttributeValue("firstLine"));
int endLine = Integer.parseInt(tableau.getAttributeValue("endLine"));
 
Map<String, Map<Integer, String>> mapStyle = searchStyle(sheet, lastColumn, endPageLine);
Map<String, Map<Integer, String>> mapStyle = searchStyle(sheet, firstLine, lastColumn, endLine);
 
int nbPage = fillTable(tableau, row, sheet, mapStyle, true, rowLanguage);
 
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOgenerationListeXML.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
182,13 → 182,12
range[i] = string.subSequence(string.indexOf('.') + 1, string.length()).toString();
}
 
int rowEnd = -1;
if (range.length > 1) {
rowEnd = sheet.resolveHint(range[1]).y + 1;
int rowEndNew = rowEnd * (nbPage + 1);
int rowEnd = sheet.resolveHint(range[1]).y + 1;
int rowEndNew = rowEnd * nbPage;
String sNew = s.replaceAll(String.valueOf(rowEnd), String.valueOf(rowEndNew));
sheet.setPrintRanges(sNew);
System.err.println(" ****** Replace print ranges; Old:" + rowEnd + "--" + s + " New:" + rowEndNew + "--" + sNew);
System.err.println("OOgenerationListXML ****** Replace print ranges; Old:" + rowEnd + "--" + s + " New:" + rowEndNew + "--" + sNew);
}
}
 
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOXMLTableElement.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
83,47 → 83,20
}
 
public List<? extends SQLRowAccessor> getRows() {
SQLTable tableElt = Configuration.getInstance().getRoot().findTable(this.tableau.getAttributeValue("table"));
String foreignTableName = this.tableau.getAttributeValue("table");
 
SQLTable tableElt = null;
if (!this.row.isEmpty()) {
SQLTable tableRow = this.row.get(0).getTable();
if (tableRow.getDBRoot().contains(foreignTableName)) {
tableElt = tableRow.getDBRoot().getTable(foreignTableName);
} else {
tableElt = Configuration.getInstance().getRoot().findTable(foreignTableName);
}
}
 
if (tableElt != null) {
 
// // #if gestionnx
// Set<SQLField> fields =
// tablePourcentService.getForeignKeys(tableElt);
//
// if (((ComptaPropsConfiguration)
// Configuration.getInstance()).customerIsPreventec() && fields !=
// null && fields.size() > 0) {
// SQLSelect sel = new
// SQLSelect(Configuration.getInstance().getBase());
//
// sel.addSelectStar(tableElt);
// Set<SQLField> fieldsElt =
// tableElt.getForeignKeys(this.row.getTable());
// Where w = new Where(fieldsElt.iterator().next(), "=",
// this.row.getTable().getKey());
// w = w.and(new Where(fields.iterator().next(), "=",
// tableElt.getKey()));
// w = w.and(new Where(this.row.getTable().getKey(), "=",
// this.row.getID()));
// sel.setWhere(w);
// sel.addFieldOrder(tablePourcentService.getField("ID_VERIFICATEUR"));
// sel.addFieldOrder(fields.iterator().next());
// List<SQLRow> l = (List<SQLRow>)
// Configuration.getInstance().getBase().getDataSource().execute(sel.asString(),
// SQLRowListRSH.createFromSelect(sel, tableElt));
//
// // Suppression des doublons
// List<SQLRow> list = new ArrayList<SQLRow>();
// for (SQLRow sqlRow : l) {
// if (!list.contains(sqlRow)) {
// list.add(sqlRow);
// }
// }
// return list;
// }
// // #endif
 
return cache.getReferentRows(this.row, tableElt, this.tableau.getAttributeValue("groupBy"), this.tableau.getAttributeValue("orderBy"),
Boolean.valueOf(this.tableau.getAttributeValue("expandNomenclature")), this.tableau.getAttributeValue("foreignField"),
Boolean.valueOf(this.tableau.getAttributeValue("excludeZeroQty")));
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/OOXMLCache.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
134,10 → 134,18
} else {
for (SQLRowAccessor rowAccess : row) {
if (rowAccess != null && !rowAccess.isUndefined()) {
final Object[] array = tableForeign.getForeignKeys(rowAccess.getTable()).toArray();
final SQLField field1;
if (array == null || array.length == 0) {
field1 = tableForeign.getField("ID_" + tableForeign.getName().replaceAll("_ELEMENT", ""));
} else {
 
field1 = (SQLField) array[0];
}
if (w == null) {
w = new Where((SQLField) tableForeign.getForeignKeys(rowAccess.getTable()).toArray()[0], "=", rowAccess.getID());
w = new Where(field1, "=", rowAccess.getID());
} else {
w = w.or(new Where((SQLField) tableForeign.getForeignKeys(rowAccess.getTable()).toArray()[0], "=", rowAccess.getID()));
w = w.or(new Where(field1, "=", rowAccess.getID()));
}
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/SpreadSheetGeneratorCompta.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
71,7 → 71,10
System.err.println("Set Column Count to :: " + (colEnd + 1));
sheet.setColumnCount(colEnd + 1);
}
sheet.duplicateFirstRows(this.nbRowsPerPage, this.nbPage);
if (this.nbPage < 1) {
throw new IllegalArgumentException("invalid page count : " + this.nbPage);
}
sheet.duplicateFirstRows(this.nbRowsPerPage, this.nbPage - 1);
 
Object printRangeObj = sheet.getPrintRanges();
if (printRangeObj != null) {
82,13 → 85,12
range2[i] = string.subSequence(string.indexOf('.') + 1, string.length()).toString();
}
 
int end = -1;
if (range2.length > 1) {
end = sheet.resolveHint(range2[1]).y + 1;
long rowEndNew = end * (this.nbPage + 1);
int end = sheet.resolveHint(range2[1]).y + 1;
int rowEndNew = end * this.nbPage;
String sNew = s.replaceAll(String.valueOf(end), String.valueOf(rowEndNew));
sheet.setPrintRanges(sNew);
System.err.println(" ****** Replace print ranges; Old:" + end + "--" + s + " New:" + rowEndNew + "--" + sNew);
System.err.println("SpreadSheetGenerator ****** Replace print ranges; Old:" + end + "--" + s + " New:" + rowEndNew + "--" + sNew);
}
} else {
sheet.removePrintRanges();
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/PDFAttachment.java
New file
0,0 → 1,45
/*
* 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.erp.generationDoc;
 
public class PDFAttachment {
private String name;
private String fileName;
private byte[] bytes;
private String mimetype;
 
public PDFAttachment(String name, String fileName, byte[] bytes, String mimetype) {
this.name = name;
this.fileName = fileName;
this.bytes = bytes;
this.mimetype = mimetype;
}
 
public String getName() {
return name;
}
 
public String getFileName() {
return this.fileName;
}
 
public byte[] getBytes() {
return this.bytes;
}
 
public String getMimetype() {
return this.mimetype;
}
 
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/SheetUtils.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
19,15 → 19,22
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
 
import java.awt.Color;
import java.awt.Desktop;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
34,9 → 41,13
 
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;
import org.jopendocument.model.OpenDocument;
139,7 → 150,12
}
 
public static void convert2PDF(final OpenDocument doc, final File pdfFileToCreate) throws Exception {
convert2PDF(doc, pdfFileToCreate, Collections.emptyList());
}
 
public static void convert2PDF(final OpenDocument doc, final File pdfFileToCreate, final List<PDFAttachment> attachments) throws Exception {
assert (!SwingUtilities.isEventDispatchThread());
System.out.println("SheetUtils convert2PDF " + doc.getLoadedFile().getAbsolutePath() + " -> " + pdfFileToCreate.getAbsolutePath());
try {
PDDocument document = new PDDocument();
PDDocumentInformation info = new PDDocumentInformation();
149,8 → 165,29
info.setModificationDate(Calendar.getInstance());
document.setDocumentInformation(info);
 
FileOutputStream fileOutputStream = new FileOutputStream(pdfFileToCreate);
// Attachments
final PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode();
final Map<String, PDComplexFileSpecification> efMap = new HashMap<>();
for (final PDFAttachment att : attachments) {
// first create the file specification, which holds the embedded file
final PDComplexFileSpecification fs = new PDComplexFileSpecification();
fs.setFile(att.getFileName());
final InputStream is = new ByteArrayInputStream(att.getBytes());
final PDEmbeddedFile ef = new PDEmbeddedFile(document, is);
// set some of the attributes of the embedded file
ef.setSubtype(att.getMimetype());
ef.setSize(att.getBytes().length);
ef.setCreationDate(new GregorianCalendar());
fs.setEmbeddedFile(ef);
efMap.put(att.getName(), fs);
}
 
efTree.setNames(efMap);
// attachments are stored as part of the "names" dictionary in the document catalog
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(document.getDocumentCatalog());
names.setEmbeddedFiles(efTree);
document.getDocumentCatalog().setNames(names);
 
PdfBoxGraphics2DFontTextDrawer fontTextDrawer = new PdfBoxGraphics2DFontTextDrawerDefaultFonts();
final File dir = new File("Fonts");
if (dir.exists()) {
169,23 → 206,24
// Configure the renderer
ODTRenderer renderer = new ODTRenderer(doc);
renderer.setIgnoreMargins(false);
renderer.setResizeFactor(100);
renderer.setPaintMaxResolution(true);
PDRectangle pageSize = PDRectangle.A4;
 
// Scale the renderer to fit width or height
final double widthFactor = renderer.getPrintWidth() / pageSize.getWidth();
final double heightFactor = renderer.getPrintHeight() / pageSize.getHeight();
renderer.setResizeFactor(Math.max(widthFactor, heightFactor));
 
final int resizeFactor = (int) Math.ceil(Math.max(widthFactor, heightFactor));
renderer.setResizeFactor(resizeFactor);
// Print pages
for (int i = 0; i < renderer.getPrintedPagesNumber(); i++) {
PDPage page = new PDPage(pageSize);
final PDPage page = new PDPage(pageSize);
document.addPage(page);
PdfBoxGraphics2D g2 = new PdfBoxGraphics2D(document, pageSize.getWidth(), pageSize.getHeight());
g2.setFontTextDrawer(fontTextDrawer);
 
// centrage horizontal, alignement vertical en haut
g2.translate((PageSize.A4.getWidth() - renderer.getPrintWidthInPixel()) / 2.0, 0);
final double hMargin = (pageSize.getWidth() - renderer.getPageWidthInPixel()) / 2.0;
g2.translate(hMargin, 0);
 
// Render
renderer.setCurrentPage(i);
199,8 → 237,9
contentStream.transform(matrix);
contentStream.drawForm(xform);
contentStream.close();
 
}
document.save(fileOutputStream);
document.save(pdfFileToCreate);
// Close the PDF document
document.close();
 
226,4 → 265,12
final File f = new File(file.getParent(), name);
return f;
}
 
public static void main(String[] args) throws Exception {
// final OpenDocument doc = new OpenDocument(new File("Documents/Facture_FACT128.ods"));
final OpenDocument doc = new OpenDocument(new File("../ODSViewer/documents/bug_marges_pdf/Devis.ods"));
final File file = new File("out.pdf");
convert2PDF(doc, file);
Desktop.getDesktop().open(file);
}
}
/trunk/OpenConcerto/src/org/openconcerto/erp/generationDoc/SheetXml.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,12
 
import org.openconcerto.erp.config.ComptaPropsConfiguration;
import org.openconcerto.erp.core.common.ui.PreviewFrame;
import org.openconcerto.erp.core.customerrelationship.mail.EmailTemplate;
import org.openconcerto.erp.core.customerrelationship.mail.ValueListener;
import org.openconcerto.erp.core.sales.invoice.report.VenteFactureXmlSheet;
import org.openconcerto.erp.core.sales.quote.report.PaypalStamper;
import org.openconcerto.erp.generationDoc.element.TypeModeleSQLElement;
import org.openconcerto.erp.model.MouseSheetXmlListeListener;
import org.openconcerto.erp.preferences.PayPalPreferencePanel;
import org.openconcerto.erp.storage.CloudStorageEngine;
import org.openconcerto.erp.storage.StorageEngine;
25,13 → 28,21
import org.openconcerto.openoffice.OOUtils;
import org.jopendocument.link.Component;
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.element.SQLComponent;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLBase;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.preferences.SQLPreferences;
import org.openconcerto.sql.view.EditFrame;
import org.openconcerto.sql.view.EditPanel.EditMode;
import org.openconcerto.ui.FrameUtil;
import org.openconcerto.utils.Action;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.cc.ITransformer;
import org.openconcerto.utils.i18n.Grammar;
import org.openconcerto.utils.i18n.TranslationManager;
 
import java.awt.GraphicsEnvironment;
import java.awt.print.PrinterJob;
49,7 → 60,9
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
140,15 → 153,19
return this.elt;
}
 
public Future<SheetXml> showPrintAndExportAsynchronous(final boolean showDocument, final boolean printDocument, final boolean exportToPDF) {
return showPrintAndExportAsynchronous(showDocument, printDocument, exportToPDF, Collections.emptyList());
}
 
/**
* Show, print and export the document to PDF. This method is asynchronous, but is executed in a
* single threaded queue shared with createDocument
*/
public Future<SheetXml> showPrintAndExportAsynchronous(final boolean showDocument, final boolean printDocument, final boolean exportToPDF) {
public Future<SheetXml> showPrintAndExportAsynchronous(final boolean showDocument, final boolean printDocument, final boolean exportToPDF, List<Action> actions) {
final Callable<SheetXml> c = new Callable<SheetXml>() {
@Override
public SheetXml call() throws Exception {
showPrintAndExport(showDocument, printDocument, exportToPDF);
showPrintAndExport(showDocument, printDocument, exportToPDF, actions);
return SheetXml.this;
}
};
156,6 → 173,55
 
}
 
public Future<SheetXml> showPrintAndExportAsynchronous(final boolean showDocument, final boolean printDocument, final boolean exportToPDF, final SQLElement element, SQLRow row) {
final Callable<SheetXml> c = new Callable<SheetXml>() {
 
@Override